diff --git a/docs/api.md b/docs/api.md index 5a5f170..f6f8bfe 100644 --- a/docs/api.md +++ b/docs/api.md @@ -101,12 +101,14 @@ SiweMessage的nonce说明(具体参考例子): "tasks": [ // 该活动需要完成的任务 { "id": "任务id", + "task": "任务类型", "title": "任务名", "desc": "任务描述", "type": 1, //任务类型, 1: 一次性任务, 2: 日常任务 "pretasks": ["task id 1"], //前置任务 "score": 0, // 完成任务可获得的积分 "category": "", // 任务分类 + "data": {}, // 其他一些任务相关配置参数, 比如icon, 或者其他未考虑的参数 "autoclaim": false // 任务完成后是否自动获取奖励 } ], diff --git a/initdatas/activity_info.json b/initdatas/activity_info.json index 64678b0..c554ac7 100644 --- a/initdatas/activity_info.json +++ b/initdatas/activity_info.json @@ -4,50 +4,108 @@ "name": "First Activity1", "description": "This is the first test activity", "tasks": [{ - "id": "TwitterConnect", + "id": "e2yhq2lj30vwcpedv7p", + "task": "TwitterConnect", "title": "Connect Twitter", "type": 1, "desc": "", "score": 100, "category": "", "autoclaim": true, + "data": {}, "params": {} }, { - "id": "TwitterFollow", + "id": "e2fclylj30vwcpe0szl", + "task": "TwitterFollow", "title": "Follow Twitter", "type": 1, - "desc": "", - "category": "", + "desc": "Follow Counter Fire’s official X account", + "category": "Social Tasks", "score": 100, "autoclaim": false, "pretasks": ["TwitterConnect"], + "data": {}, "params": {"time": 6, "failRate": 60} }, { - "id": "OkxLogin", - "title": "okx wallet login", + "id": "e2feyflj30vwcpe0sjy", + "task": "TwitterFollow", + "title": "Follow Twitter", "type": 1, "desc": "", - "category": "", + "category": "Social Tasks", "score": 100, "autoclaim": false, - "pretasks": [], - "params": {} + "pretasks": ["TwitterConnect"], + "data": {}, + "params": {"time": 6, "failRate": 60} }, { - "id": "TwitterRetweet", - "title": "ReTwitt", + "id": "e2fuah0j30vwcpe0my7", + "task": "TwitterRetweet", + "title": "ReTwitter", + "type": 2, + "desc": "", + "category": "Social Tasks", + "score": 100, + "autoclaim": false, + "pretasks": ["TwitterConnect"], + "data": {}, + "params": {"time": 6, "failRate": 60} + }, { + "id": "e2far3lj30vwcpe0mh7", + "task": "DiscordConnect", + "title": "Connect Discord", "type": 2, "desc": "", "category": "", "score": 100, "autoclaim": false, - "pretasks": ["TwitterConnect"], + "pretasks": [], + "data": {}, + "params": {} + }, { + "id": "e2far3lj30vwcpe0mf8", + "task": "DiscordJoin", + "title": "Join Discord", + "type": 2, + "desc": "", + "category": "", + "score": 100, + "autoclaim": false, + "pretasks": [], + "data": {}, "params": {"time": 6, "failRate": 60} }, { - "id": "UpdateScore", + "id": "e2fak2lj30vwcpe0awc", + "task": "DiscordRole", + "title": "Discord Role", + "type": 2, + "desc": "", + "category": "Social Tasks", + "score": 100, + "autoclaim": false, + "pretasks": ["DiscordJoin"], + "data": {}, + "params": {"time": 6, "failRate": 60} + }, { + "id": "e2f7fplj30vwcpe0l98", + "task": "OkxLogin", + "title": "okx wallet login", + "type": 1, + "desc": "", + "category": "Special Quests", + "score": 100, + "autoclaim": false, + "pretasks": [], + "data": {}, + "params": {} + }, { + "id": "e2f7t4lj30vwcpe0ldr", + "task": "ShareCode", "type": 1, "show": false, "autoclaim": false, - "pretasks": ["TwitterConnect", "TwitterFollow"], + "pretasks": [], + "data": {}, "params": {"score": [100, 20]} }], "startTime": 1702628292366, diff --git a/src/controllers/activity.controller.ts b/src/controllers/activity.controller.ts index 47b4fe1..11e1abe 100644 --- a/src/controllers/activity.controller.ts +++ b/src/controllers/activity.controller.ts @@ -63,7 +63,7 @@ export default class ActivityController extends BaseController { let user = await ActivityUser.findById(id) results.push({ rank: start + i / 2 + 1, - address: user.address, + address: user?.address || 'unknow', score }) } diff --git a/src/controllers/tasks.controller.ts b/src/controllers/tasks.controller.ts index 2ab4519..05ad4cd 100644 --- a/src/controllers/tasks.controller.ts +++ b/src/controllers/tasks.controller.ts @@ -44,20 +44,20 @@ export default class TasksController extends BaseController { let modified = false; let visibleTasks = new Set(); for (let task of activity.tasks) { - if (!allTasks.has(task.id)) { + if (!allTasks.has(task.task)) { continue; } if (task.type === TaskTypeEnum.DAILY ) { let id = `${task.id}:${dateTag}` if (!taskAddedSet.has(id)) { modified = true; - user.taskProgress.push({id, dateTag: dateTag, status: TaskStatusEnum.NOT_START}) + user.taskProgress.push({id, task:task.task ,dateTag: dateTag, status: TaskStatusEnum.NOT_START}) } if (task.show) visibleTasks.add(id); } else if (task.type === TaskTypeEnum.ONCE ) { if (!taskAddedSet.has(task.id)) { modified = true; - user.taskProgress.push({id: task.id, status: TaskStatusEnum.NOT_START}) + user.taskProgress.push({id: task.id, task: task.task, status: TaskStatusEnum.NOT_START}) } if (task.show) visibleTasks.add(task.id); } @@ -132,7 +132,7 @@ export default class TasksController extends BaseController { throw new ZError(11, 'task not begin'); } if (currentTask.status === TaskStatusEnum.RUNNING) { - let Task = require('../tasks/' + taskId); + let Task = require('../tasks/' + currentTask.task); let taskInstance = new Task.default({user, activity}); await taskInstance.execute({task: currentTask}); } @@ -155,7 +155,7 @@ export default class TasksController extends BaseController { throw new ZError(13, 'task not end') } - const Task = require('../tasks/' + task); + const Task = require('../tasks/' + currentTask.task); const taskInstance = new Task.default({user, activity}); await taskInstance.claimReward(currentTask); return currentTask diff --git a/src/models/ActivityInfo.ts b/src/models/ActivityInfo.ts index 78a24b1..558e41b 100644 --- a/src/models/ActivityInfo.ts +++ b/src/models/ActivityInfo.ts @@ -15,6 +15,8 @@ export class TaskCfg { @prop() id: string @prop() + task: string + @prop() title: string @prop({ enum: TaskTypeEnum, default: TaskTypeEnum.ONCE }) type: TaskTypeEnum @@ -31,6 +33,8 @@ export class TaskCfg { @prop({default: true}) show: boolean @prop({ type: mongoose.Schema.Types.Mixed }) + data: any + @prop({ type: mongoose.Schema.Types.Mixed }) params: any } interface ActivityInfoClass extends Base, TimeStamps {} @@ -68,6 +72,7 @@ class ActivityInfoClass extends BaseModule { if (task.show) { tasks.push({ id: task.id, + task: task.task, title: task.title, desc: task.desc, type: task.type, @@ -75,6 +80,7 @@ class ActivityInfoClass extends BaseModule { score: task.score, category: task.category, autoclaim: task.autoclaim, + data: task.data, }) } } diff --git a/src/models/ActivityUser.ts b/src/models/ActivityUser.ts index 0cf55e8..e89ffc3 100644 --- a/src/models/ActivityUser.ts +++ b/src/models/ActivityUser.ts @@ -19,6 +19,8 @@ export enum TaskStatusEnum { export class TaskStatus { @prop() id: string + @prop() + task: string // 0: not start, 1: running, 2: success @prop({ enum: TaskStatusEnum, default: TaskStatusEnum.NOT_START }) status: TaskStatusEnum diff --git a/src/tasks/DiscordConnect.ts b/src/tasks/DiscordConnect.ts new file mode 100644 index 0000000..478faec --- /dev/null +++ b/src/tasks/DiscordConnect.ts @@ -0,0 +1,44 @@ +import { checkDiscord } from "services/oauth.svr"; +import { ITask } from "./base/ITask"; +import { ZError } from "common/ZError"; +import { TaskStatusEnum } from "models/ActivityUser"; +import { TaskCfg } from "models/ActivityInfo"; + +export default class DiscordConnect extends ITask { + static desc = 'join discord' + static show: boolean = true + + async execute(data: any) { + const { address } = this.params.user + const { task } = data + const res = await checkDiscord(address) + let cfg = this.params.activity.tasks.find((t: TaskCfg) => t.id === task.id) + if (res.status !== 200) { + throw new ZError(11, 'discord check failed') + } + if (res.data.errcode) { + throw new ZError(res.data.errcode, res.data.errmsg) + } + + if (res.data.data.userid && task.status === TaskStatusEnum.RUNNING) { + task.status = TaskStatusEnum.SUCCESS + task.timeFinish = Date.now() + task.data = res.data.data + task.discordId = res.data.data.userid + try { + await this.params.user.save() + } catch(err) { + throw new ZError(100, 'discord already binded') + } + if (cfg.autoclaim) { + try { + await this.claimReward(task); + } catch(err) { + console.log(err) + } + } + } + return true + } + +} \ No newline at end of file diff --git a/src/tasks/DiscordJoin.ts b/src/tasks/DiscordJoin.ts index 53866df..141bcb5 100644 --- a/src/tasks/DiscordJoin.ts +++ b/src/tasks/DiscordJoin.ts @@ -1,4 +1,3 @@ -import { checkDiscord } from "services/oauth.svr"; import { ITask } from "./base/ITask"; import { ZError } from "common/ZError"; import { TaskStatusEnum } from "models/ActivityUser"; @@ -9,32 +8,29 @@ export default class DiscordJoin extends ITask { static show: boolean = true async execute(data: any) { - const { address } = this.params.user - const res = await checkDiscord(address) - let cfg = this.params.activity.tasks.find((t: TaskCfg) => t.id === this.constructor.name) - if (res.status !== 200) { - throw new ZError(11, 'discord check failed') - } - if (res.data.errcode) { - throw new ZError(res.data.errcode, res.data.errmsg) - } const { task } = data - if (res.data.data.userid && task.status === TaskStatusEnum.RUNNING) { - task.status = TaskStatusEnum.SUCCESS - task.timeFinish = Date.now() - task.data = res.data.data - task.discordId = res.data.data.userid + let cfg = this.params.activity.tasks.find((t: TaskCfg) => t.id === task.id) + let time = cfg.params.time; + if (Date.now() - task.timeStart < time * 1000) { + throw new ZError(11, 'check discord join failed') + } + let num = Math.random() * 100 + if (num < cfg.params.failRate) { + throw new ZError(12, 'check discord join failed') + } + task.status = TaskStatusEnum.SUCCESS + task.timeFinish = Date.now() + task.data = {} + try { + await this.params.user.save() + } catch(err) { + throw new ZError(100, 'already join discord') + } + if (cfg.autoclaim) { try { - await this.params.user.save() + await this.claimReward(task); } catch(err) { - throw new ZError(100, 'discord already binded') - } - if (cfg.autoclaim) { - try { - await this.claimReward(task); - } catch(err) { - console.log(err) - } + console.log(err) } } return true diff --git a/src/tasks/DiscordRole.ts b/src/tasks/DiscordRole.ts index ce9d365..bd11cf7 100644 --- a/src/tasks/DiscordRole.ts +++ b/src/tasks/DiscordRole.ts @@ -8,7 +8,7 @@ export default class DiscordRole extends ITask { static show: boolean = true async execute(data: any) { const { task } = data - let cfg = this.params.activity.tasks.find((t: TaskCfg) => t.id === this.constructor.name) + let cfg = this.params.activity.tasks.find((t: TaskCfg) => t.id === task.id) let time = cfg.params.time; if (Date.now() - task.timeStart < time * 1000) { throw new ZError(11, 'check discord role failed') diff --git a/src/tasks/OkxLogin.ts b/src/tasks/OkxLogin.ts index 0d2c85e..c18050e 100644 --- a/src/tasks/OkxLogin.ts +++ b/src/tasks/OkxLogin.ts @@ -12,7 +12,7 @@ export default class OkxLogin extends ITask { async execute(data: any) { const { task } = data const { activity } = this.params.user - let cfg = this.params.activity.tasks.find((t: TaskCfg) => t.id === this.constructor.name) + let cfg = this.params.activity.tasks.find((t: TaskCfg) => t.id === task.id) let wallet = 'okx'; let record = LoginRecord.findOne({ user: this.params.user.id, activity, wallet}) if (!record ) { diff --git a/src/tasks/ShareCode.ts b/src/tasks/ShareCode.ts index 9a29997..956768a 100644 --- a/src/tasks/ShareCode.ts +++ b/src/tasks/ShareCode.ts @@ -29,8 +29,8 @@ export default class ShareCode extends ITask { static show: boolean = false async execute(data: any) { - let task = this.params.user.taskProgress.find((t: TaskStatus) => t.id === this.constructor.name) - let cfg = this.params.activity.tasks.find((t: TaskCfg) => t.id === this.constructor.name) + let {task} = data + let cfg = this.params.activity.tasks.find((t: TaskCfg) => t.id === task.id) let scores = cfg.params.score; task.status = TaskStatusEnum.SUCCESS task.timeFinish = Date.now() @@ -39,7 +39,7 @@ export default class ShareCode extends ITask { // According to configuration, add score to user who invite current user if (cfg.autoclaim) { try { - await updateInviteScore(this.params.user, scores, 0, this.constructor.name) + await updateInviteScore(this.params.user, scores, 0, task.task) } catch(err) { console.log(err) } @@ -48,8 +48,8 @@ export default class ShareCode extends ITask { } public async claimReward(task: TaskStatus) { - let cfg = this.params.activity.tasks.find((t: TaskCfg) => t.id === this.constructor.name) + let cfg = this.params.activity.tasks.find((t: TaskCfg) => t.id === task.id) let scores = cfg.params.score; - await updateInviteScore(this.params.user, scores, 0, this.constructor.name) + await updateInviteScore(this.params.user, scores, 0, task.task) } } \ No newline at end of file diff --git a/src/tasks/TwitterConnect.ts b/src/tasks/TwitterConnect.ts index 37fb530..b42a500 100644 --- a/src/tasks/TwitterConnect.ts +++ b/src/tasks/TwitterConnect.ts @@ -28,7 +28,7 @@ export default class TwitterConnect extends ITask { } catch(err) { throw new ZError(100, 'twitter already binded') } - let cfg = this.params.activity.tasks.find((t: TaskCfg) => t.id === this.constructor.name) + let cfg = this.params.activity.tasks.find((t: TaskCfg) => t.id === task.id) if (cfg.autoclaim) { try { await this.claimReward(task); diff --git a/src/tasks/TwitterFollow.ts b/src/tasks/TwitterFollow.ts index 490c22c..e7fc7ba 100644 --- a/src/tasks/TwitterFollow.ts +++ b/src/tasks/TwitterFollow.ts @@ -8,7 +8,7 @@ export default class TwitterFollow extends ITask { static show: boolean = true async execute(data: any) { const { task } = data - let cfg = this.params.activity.tasks.find((t: TaskCfg) => t.id === this.constructor.name) + let cfg = this.params.activity.tasks.find((t: TaskCfg) => t.id === task.id) let time = cfg.params.time; if (Date.now() - task.timeStart < time * 1000) { throw new ZError(11, 'follow failed') diff --git a/src/tasks/TwitterRetweet.ts b/src/tasks/TwitterRetweet.ts index 4c3a1d6..cff0ffa 100644 --- a/src/tasks/TwitterRetweet.ts +++ b/src/tasks/TwitterRetweet.ts @@ -7,7 +7,7 @@ export default class TwitterRetweet extends ITask { static show: boolean = true async execute(data: any) { const { task } = data - let cfg = this.params.activity.tasks.find(t => t.id === this.constructor.name) + let cfg = this.params.activity.tasks.find(t => t.id === task.id) let time = cfg.params.time; if (Date.now() - task.timeStart < time * 1000) { throw new ZError(11, 'retweet failed') diff --git a/src/tasks/base/ITask.ts b/src/tasks/base/ITask.ts index 24c8887..6a20fb4 100644 --- a/src/tasks/base/ITask.ts +++ b/src/tasks/base/ITask.ts @@ -25,7 +25,7 @@ export abstract class ITask { user: user.id, score: cfg.score, activity: user.activity, - scoreType: taskId, + scoreType: cfg.task, scoreParams: { date: dateTag, taskId: task.id diff --git a/src/utils/security.util.ts b/src/utils/security.util.ts index eb5ab9e..f6f2bce 100644 --- a/src/utils/security.util.ts +++ b/src/utils/security.util.ts @@ -136,4 +136,19 @@ export const base58ToHex = (base58String: string) => { num = num * BigInt(58) + BigInt(charIndex); } return num.toString(16); +} + +export const hexToBase32 = (hexString: string) => { + const bytes = hexString.match(/.{1,2}/g).map(byte => parseInt(byte, 16)); + const base32Alphabet = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l'; + let base32String = ''; + + let num = BigInt('0x' + hexString); + while (num > BigInt(0)) { + const remainder = num % BigInt(32); + num = num / BigInt(32); + base32String = base32Alphabet[Number(remainder)] + base32String; + } + + return base32String; } \ No newline at end of file