From 544048bcf3792c054eb41121f3d961fb1a021dd2 Mon Sep 17 00:00:00 2001
From: CounterFire2023 <136581895+CounterFire2023@users.noreply.github.com>
Date: Thu, 11 Jan 2024 15:17:34 +0800
Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=8A=BD=E5=A5=96=E7=9B=B8?=
=?UTF-8?q?=E5=85=B3api?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/api.md | 127 +++++++++++++++++
initdatas/activity_info.json | 37 ++++-
src/common/Constants.ts | 3 +
src/common/LotteryCache.ts | 23 +++
src/configs/fusion.ts | 28 ++++
src/configs/items.ts | 40 ++++++
src/configs/lottery.ts | 43 ++++++
src/controllers/activity.controller.ts | 2 +-
src/controllers/lottery.controller.ts | 190 +++++++++++++++++++++++++
src/controllers/tasks.controller.ts | 30 ++++
src/models/ActivityInfo.ts | 46 +++++-
src/models/ActivityItem.ts | 31 ++++
src/models/LotteryRecord.ts | 33 +++++
src/models/LotteryStats.ts | 41 ++++++
src/models/chain/CheckIn.ts | 57 ++++++++
src/models/chain/NftHolder.ts | 54 +++++++
src/models/chain/NftTransferEvent.ts | 66 +++++++++
src/services/chain.svr.ts | 8 ++
src/tasks/BurnNft.ts | 14 +-
src/tasks/DailyCheckIn.ts | 29 ++--
src/tasks/DiscordConnect.ts | 10 +-
src/tasks/DiscordJoin.ts | 4 +-
src/tasks/DiscordRole.ts | 4 +-
src/tasks/OkxLogin.ts | 8 +-
src/tasks/ShareCode.ts | 38 +++--
src/tasks/TwitterConnect.ts | 10 +-
src/tasks/TwitterFollow.ts | 4 +-
src/tasks/TwitterRetweet.ts | 4 +-
src/tasks/YoutubeFollow.ts | 4 +-
src/tasks/YoutubePost.ts | 4 +-
src/tasks/base/ITask.ts | 30 +++-
31 files changed, 956 insertions(+), 66 deletions(-)
create mode 100644 src/common/LotteryCache.ts
create mode 100644 src/configs/fusion.ts
create mode 100644 src/configs/items.ts
create mode 100644 src/configs/lottery.ts
create mode 100644 src/controllers/lottery.controller.ts
create mode 100644 src/models/ActivityItem.ts
create mode 100644 src/models/LotteryRecord.ts
create mode 100644 src/models/LotteryStats.ts
create mode 100644 src/models/chain/CheckIn.ts
create mode 100644 src/models/chain/NftHolder.ts
create mode 100644 src/models/chain/NftTransferEvent.ts
diff --git a/docs/api.md b/docs/api.md
index 6da0f55..646f676 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -110,6 +110,7 @@ SiweMessage的nonce说明(具体参考例子):
"score": 0, // 完成任务可获得的积分
"category": "", // 任务分类
"cfg": {}, // 其他一些任务相关配置参数, 比如icon, 或者其他未考虑的参数
+ "end": false, // 是否已经结束
"autoclaim": false // 任务完成后是否自动获取奖励
}
],
@@ -361,4 +362,130 @@ query param
"count": 0 //连签天数
},
]
+```
+
+### 13.\* 抽奖状态
+
+#### Request
+
+- URL:`/api/lottery/stats`
+- 方法:`GET`
+- 头部:
+ - Authorization: Bearer JWT_token
+
+
+#### Response
+
+```json
+{
+ "amount": 1, // 今日可抽数量
+ "daily": 0, // 今日通过每日签到获得的抽奖机会
+ "share": 0, // 今日通过邀请获得的抽奖机会
+ "gacha": 0, // 今日通过gacha获得的抽奖机会
+ "used": 0, // 今日已抽数量
+ "day": "20240110",
+ "items": [{
+ "id": "thunder",
+ "amount": 1, //数量
+ }]
+ }
+```
+
+### 14.\* 活动配置
+
+#### Request
+
+- URL:`/api/lottery/items`
+- 方法:`GET`
+- 头部:
+ - Authorization: Bearer JWT_token
+
+
+#### Response
+
+```json
+{
+ "start": "活动开始时间",
+ "end": "活动结束时间",
+ "items": [
+ {
+ "id": "物品id",
+ "name": "物品名称",
+ "amount": 1 //物品数量
+ },
+ ]
+}
+
+```
+
+### 15.\* 抽奖
+
+#### Request
+
+- URL:`/api/lottery/draw`
+- 方法:`GET`
+- 头部:
+ - Authorization: Bearer JWT_token
+
+
+#### Response
+
+```json
+{
+ "id": "物品id 或 empty(未获得任何奖励)",
+ "amount": "数量"
+}
+```
+
+### 16.\* 合成
+
+#### Request
+
+- URL:`/api/lottery/fusion`
+- 方法:`POST`
+- 头部:
+ - Authorization: Bearer JWT_token
+
+
+#### Response
+
+```json
+{
+ "id": "物品id",
+ "amount": "数量"
+}
+```
+
+### 17.\* 查询抽奖历史记录
+
+#### Request
+
+- URL:`/api/lottery/history`
+- 方法:`POST`
+- 头部:
+ - Authorization: Bearer JWT_token
+
+body:
+
+```json
+{
+ "day": "20240111", // 按日查询, 传格式化后的日期(yyyyMMDD), "all"表示查询所有
+ "page": 1, // 分页, 从1开始, 可为空, 默认1
+ "limit": 10, // 分页记录数, 可为空, 默认10
+}
+```
+
+#### Response
+
+```json
+{
+ "count": 100, //记录总数
+ "page": 1, // 分页
+ "limit": 10, // 分页记录数
+ "records":[{
+ "id": "物品id",
+ "day": "20240111",
+ "amount": "数量"
+ }]
+}
```
\ No newline at end of file
diff --git a/initdatas/activity_info.json b/initdatas/activity_info.json
index 5dddd21..b511266 100644
--- a/initdatas/activity_info.json
+++ b/initdatas/activity_info.json
@@ -13,6 +13,8 @@
"category": "",
"autoclaim": false,
"cfg": {"icon": "twitter"},
+ "start": "2024-01-01 00:00",
+ "end": "2025-01-01 00:00",
"params": {}
}, {
"id": "e2fclylj30vwcpe0szl",
@@ -25,6 +27,8 @@
"autoclaim": false,
"pretasks": ["e2yhq2lj30vwcpedv7p"],
"cfg": {"account": "@_CounterFire", "icon": "twitter"},
+ "start": "2024-01-01 00:00",
+ "end": "2025-01-01 00:00",
"params": {"time": 6, "failRate": 60}
}, {
"id": "e2feyflj30vwcpe0sjy",
@@ -37,6 +41,8 @@
"autoclaim": false,
"pretasks": ["e2yhq2lj30vwcpedv7p"],
"cfg": {"icon": "twitter"},
+ "start": "2024-01-01 00:00",
+ "end": "2025-01-01 00:00",
"params": {"time": 6, "failRate": 60}
}, {
"id": "e2fuah0j30vwcpe0my7",
@@ -49,6 +55,8 @@
"autoclaim": false,
"pretasks": ["e2yhq2lj30vwcpedv7p"],
"cfg": {"icon": "twitter", "content": "Just joined Counter Fire! 🎮 Excited about the endless opportunities ahead. 🔥 Let's team up and conquer together! Come in with me #CounterFire #GamingAdventures"},
+ "start": "2024-01-01 00:00",
+ "end": "2025-01-01 00:00",
"params": {"time": 6, "failRate": 60}
}, {
"id": "e2fuah0j30vwcpe1my7",
@@ -61,6 +69,8 @@
"autoclaim": false,
"pretasks": ["e2yhq2lj30vwcpedv7p"],
"cfg": {"icon": "twitter", "content": "输入框自动生成,活动开始时添加内容)"},
+ "start": "2024-01-01 00:00",
+ "end": "2025-01-01 00:00",
"params": {"time": 6, "failRate": 60}
}, {
"id": "e2far3lj30vwcpe0mh7",
@@ -73,6 +83,8 @@
"autoclaim": false,
"pretasks": [],
"cfg": {"icon": "discord"},
+ "start": "2024-01-01 00:00",
+ "end": "2025-01-01 00:00",
"params": {}
}, {
"id": "e2far3lj30vwcpe0mf8",
@@ -85,6 +97,8 @@
"autoclaim": false,
"pretasks": [],
"cfg": {"icon": "discord"},
+ "start": "2024-01-01 00:00",
+ "end": "2025-01-01 00:00",
"params": {"time": 6, "failRate": 60}
}, {
"id": "e2fak2lj30vwcpe0awc",
@@ -97,6 +111,8 @@
"autoclaim": false,
"pretasks": ["e2far3lj30vwcpe0mf8"],
"cfg": {"icon": "discord"},
+ "start": "2024-01-01 00:00",
+ "end": "2025-01-01 00:00",
"params": {"time": 6, "failRate": 60}
}, {
"id": "e2feyflj30vwcpe0sjx",
@@ -109,6 +125,8 @@
"autoclaim": false,
"pretasks": [],
"cfg": {"icon": "youtube"},
+ "start": "2024-01-01 00:00",
+ "end": "2025-01-01 00:00",
"params": {"time": 6, "failRate": 60}
}, {
"id": "e2feyflj30vwcpe0sjz",
@@ -121,6 +139,8 @@
"autoclaim": false,
"pretasks": [],
"cfg": {"icon": "youtube"},
+ "start": "2024-01-01 00:00",
+ "end": "2025-01-01 00:00",
"params": {"time": 6, "failRate": 60}
}, {
"id": "e2fuah0j30vwcpe2my7",
@@ -133,6 +153,8 @@
"autoclaim": false,
"pretasks": ["e2yhq2lj30vwcpedv7p"],
"cfg": {"icon": "twitter", "content": "Just scored xx Flame on @_CounterFire! 🔥 Join me in the action-packed fun and let's play to earn! #GamingAdventures #CounterFire"},
+ "start": "2024-01-01 00:00",
+ "end": "2025-01-01 00:00",
"params": {"time": 6, "failRate": 60}
}, {
"id": "e2fuah0j30vwcpe2my7",
@@ -145,6 +167,8 @@
"autoclaim": false,
"pretasks": ["e2yhq2lj30vwcpedv7p"],
"cfg": {"icon": "twitter", "content": "Just scored xx Flame on @_CounterFire! 🔥 Join me in the action-packed fun and let's play to earn! #GamingAdventures #CounterFire"},
+ "start": "2024-01-01 00:00",
+ "end": "2025-01-01 00:00",
"params": {"time": 6, "failRate": 60}
}, {
"id": "e2f7fplj30vwcpe0l97",
@@ -157,6 +181,8 @@
"autoclaim": false,
"pretasks": [],
"cfg": {},
+ "start": "2024-01-01 00:00",
+ "end": "2025-01-01 00:00",
"params": {"days": 3}
}, {
"id": "e2f7fplj30vwcpe0l98",
@@ -169,7 +195,9 @@
"autoclaim": false,
"pretasks": [],
"cfg": {"account": "okx", "icon": "okx"},
- "params": {}
+ "start": "2024-01-01 00:00",
+ "end": "2025-01-01 00:00",
+ "params": {"item": {"lottery_ticket": 1}}
}, {
"id": "e2f7fplj30vwcpe0l96",
"task": "DailyCheckIn",
@@ -181,6 +209,8 @@
"autoclaim": false,
"pretasks": [],
"cfg": {"score": [0, 15, 20, 20, 40, 40, 60]},
+ "start": "2024-01-01 00:00",
+ "end": "2025-01-01 00:00",
"params": {"days": 1, "score": [0, 15, 20, 20, 40, 40, 60]}
}, {
"id": "e2f7t4lj30vwcpe0ldr",
@@ -188,6 +218,7 @@
"title": "",
"desc": "Click here if your are referred by a friend!",
"type": 1,
+ "category": "Social Tasks",
"show": true,
"autoclaim": false,
"pretasks": [],
@@ -205,9 +236,9 @@
"category": "CF Pal",
"autoclaim": false,
"pretasks": [],
- "cfg": {"address": "0x59e751c2037B710090035B6ea928e0cce80aC03f"},
+ "cfg": {"address": "0xCD4bb3402f1a444a1AF10F31946Ed37DaC0eaC4d"},
"score": 200,
- "params": {"address": "0x59e751c2037B710090035B6ea928e0cce80aC03f"}
+ "params": {"address": "0xCD4bb3402f1a444a1AF10F31946Ed37DaC0eaC4d"}
}, {
"id": "e2f7t4lj32vwcpe0ldr",
"task": "BurnNft",
diff --git a/src/common/Constants.ts b/src/common/Constants.ts
index a3dfa70..a5b214a 100644
--- a/src/common/Constants.ts
+++ b/src/common/Constants.ts
@@ -4,6 +4,9 @@ export const ZERO_BYTES32 = '0x0000000000000000000000000000000000000000000000000
export const MAX_BATCH_REQ_COUNT = 50
+export const EMPTY_REWARD = 'empty'
+export const ITEM_FRAME = 'flame'
+
export const CONFIRM_MAIL_HTML = `
有东西需要你确认
{{title}}
diff --git a/src/common/LotteryCache.ts b/src/common/LotteryCache.ts
new file mode 100644
index 0000000..fa9ce6a
--- /dev/null
+++ b/src/common/LotteryCache.ts
@@ -0,0 +1,23 @@
+import { singleton } from "decorators/singleton";
+import { LotteryStats } from "models/LotteryStats";
+import { formatDate } from "utils/date.util";
+
+@singleton
+export class LotteryCache {
+ map: Map = new Map();
+
+ public async getData(user: string, activity: string) {
+ const dateTag = formatDate(new Date());
+ if (!this.map.has(user+dateTag)) {
+ const record = await LotteryStats.insertOrUpdate({user, activity, dateTag}, {})
+ this.map.set(user+dateTag, record);
+ }
+ return this.map.get(user+dateTag);
+ }
+
+ public async flush() {
+ for (let record of this.map.values()) {
+ await record.save();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/configs/fusion.ts b/src/configs/fusion.ts
new file mode 100644
index 0000000..2cab3f3
--- /dev/null
+++ b/src/configs/fusion.ts
@@ -0,0 +1,28 @@
+export const FUSION_CFG = {
+ target: {
+ id: 'torch',
+ amount: 1,
+ },
+ source: [
+ {
+ id: 'thunder',
+ amount: 1,
+ },
+ {
+ id: 'light',
+ amount: 1,
+ },
+ {
+ id: 'spark',
+ amount: 1,
+ },
+ {
+ id: 'blaze',
+ amount: 1,
+ },
+ {
+ id: 'kindle',
+ amount: 1,
+ },
+ ]
+}
\ No newline at end of file
diff --git a/src/configs/items.ts b/src/configs/items.ts
new file mode 100644
index 0000000..eca16e7
--- /dev/null
+++ b/src/configs/items.ts
@@ -0,0 +1,40 @@
+export interface IItem {
+ id: string
+ name: string
+ score?: number
+}
+export const ALL_ITEMS: IItem[] = [
+ {
+ id: 'usdt',
+ name: 'USDT',
+ },
+ {
+ id: 'flame',
+ name: 'Flame',
+ score: 1,
+ },
+ {
+ id: 'thunder',
+ name: 'Thunder',
+ },
+ {
+ id: 'light',
+ name: 'Light',
+ },
+ {
+ id: 'spark',
+ name: 'Spark',
+ },
+ {
+ id: 'blaze',
+ name: 'Blaze',
+ },
+ {
+ id: 'kindle',
+ name: 'Kindle',
+ },
+ {
+ id: 'torch',
+ name: 'Torch'
+ },
+]
diff --git a/src/configs/lottery.ts b/src/configs/lottery.ts
new file mode 100644
index 0000000..145b8d7
--- /dev/null
+++ b/src/configs/lottery.ts
@@ -0,0 +1,43 @@
+
+export const LOTTERY_CFG = {
+ start: '2024-01-01 00:00:00',
+ end: '2025-01-01 00:00:00',
+ rewards: [
+ {
+ item: 'usdt',
+ amount: 20,
+ probability: 10000
+ },
+ {
+ item: 'flame',
+ amount: 30,
+ probability: 300000
+ },
+ {
+ item: 'thunder',
+ amount: 1,
+ probability: 170000
+ },
+ {
+ item: 'light',
+ amount: 1,
+ probability: 180000
+ },
+ {
+ item: 'spark',
+ amount: 1,
+ probability: 130000
+ },
+ {
+ item: 'blaze',
+ amount: 1,
+ probability: 100000
+ },
+ {
+ item: 'kindle',
+ amount: 1,
+ probability: 100000
+ },
+
+ ]
+}
\ No newline at end of file
diff --git a/src/controllers/activity.controller.ts b/src/controllers/activity.controller.ts
index a72f34f..689bca3 100644
--- a/src/controllers/activity.controller.ts
+++ b/src/controllers/activity.controller.ts
@@ -62,7 +62,7 @@ export default class ActivityController extends BaseController {
let score = parseInt(records[i + 1])
const user = await ActivityUser.findById(id)
let invite = ''
- if (user.inviteUser) {
+ if (user?.inviteUser) {
const inviteUser = await ActivityUser.findById(user.inviteUser)
if (inviteUser) {
invite = inviteUser.address
diff --git a/src/controllers/lottery.controller.ts b/src/controllers/lottery.controller.ts
new file mode 100644
index 0000000..1af683a
--- /dev/null
+++ b/src/controllers/lottery.controller.ts
@@ -0,0 +1,190 @@
+import { EMPTY_REWARD, ITEM_FRAME } from "common/Constants";
+import { LotteryCache } from "common/LotteryCache";
+import { ZError } from "common/ZError";
+import BaseController from "common/base.controller";
+import { FUSION_CFG } from "configs/fusion";
+import { ALL_ITEMS } from "configs/items";
+import { LOTTERY_CFG } from "configs/lottery";
+import { router } from "decorators/router";
+import { ActivityItem } from "models/ActivityItem";
+import { LotteryRecord } from "models/LotteryRecord";
+import { updateRankScore } from "services/rank.svr";
+import { formatDate } from "utils/date.util";
+
+const ROUND = 1000000;
+
+// Get random prizes according to the set probability
+const draw = (rewards: {probability: number}[]) => {
+ let total = 0;
+ let random = Math.floor(Math.random() * ROUND);
+ let reward = null;
+ for (let r of rewards) {
+ total += r.probability;
+ if (random < total) {
+ reward = r;
+ break;
+ }
+ }
+ return {id: reward?.item || EMPTY_REWARD, amount: reward?.amount || 1};
+}
+
+export default class LotteryController extends BaseController {
+ @router('get /api/lottery/stats')
+ async userStats(req) {
+ let user = req.user;
+ let record = await new LotteryCache().getData(user.id, user.activity);
+ let result:any = record.toJson();
+ let items = await ActivityItem.find({user: user.id, activity: user.activity});
+ result.items = items.map((i) => i.toJson());
+ return result;
+ }
+
+ @router('post /api/lottery/history')
+ async userLotteryHistory(req) {
+ let user = req.user;
+ let { day, page, limit } = req.params;
+ const query: any = {user: user.id, activity: user.activity};
+ if (day !== 'all') {
+ query.dateTag = day;
+ }
+ page = +page || 1
+ limit = +limit || 10
+ let start = page * limit || 0
+
+
+ let historys = await LotteryRecord.find(query).skip(start).limit(limit);
+ let total = await LotteryRecord.countDocuments(query);
+ return {
+ count: total,
+ page: +page,
+ limit,
+ records: historys.map((h) => h.toJson())
+ }
+ }
+
+ @router('get /api/lottery/items')
+ async items(req) {
+ const items = ALL_ITEMS;
+ const cfgs = LOTTERY_CFG;
+ const itemMap = new Map();
+ for (let item of items) {
+ itemMap.set(item.id, item);
+ }
+ let result = [];
+ for (let cfg of cfgs.rewards) {
+ result.push({
+ id: cfg.item,
+ amount: cfg.amount,
+ name: itemMap.get(cfg.item).name,
+ })
+ }
+ return {items: result, start: cfgs.start, end: cfgs.end};
+ }
+
+
+ @router('get /api/lottery/draw')
+ async draw(req) {
+ let user = req.user;
+ const { start, end, rewards } = LOTTERY_CFG;
+ const startTime = new Date(start).getTime();
+ const endTime = new Date(end).getTime();
+ const now = Date.now();
+ if (now < startTime) {
+ throw new ZError(10, 'lottery not start');
+ }
+ if (now > endTime) {
+ throw new ZError(11, 'lottery end');
+ }
+ let record = await new LotteryCache().getData(user.id, user.activity);
+ if (record.amount <= 0) {
+ throw new ZError(12, 'no chance');
+ }
+ record.amount -= 1;
+ record.used += 1;
+ let reward = draw(rewards);
+ const dateTag = formatDate(new Date());
+ let history = new LotteryRecord({
+ user: user.id,
+ activity: user.activity,
+ dateTag,
+ reward: reward?.id || EMPTY_REWARD,
+ amount: reward.amount || 0,
+ });
+ await history.save();
+ const items = ALL_ITEMS;
+ const itemMap = new Map();
+ for (let item of items) {
+ itemMap.set(item.id, item);
+ }
+ if (itemMap.get(reward.id)?.score) {
+ let score = (reward?.amount || 0) * itemMap.get(reward.id).score;
+ if (user.boost > 1 && Date.now() < user.boostExpire) {
+ score = Math.floor(score * user.boost)
+ }
+ await updateRankScore({
+ user: user.id,
+ score,
+ activity: user.activity,
+ scoreType: "draw",
+ scoreParams: {
+ date: dateTag
+ }
+ })
+ }
+ await ActivityItem.insertOrUpdate({
+ user: user.id,
+ activity: user.activity,
+ item: reward.id
+ },
+ {
+ $inc: {amount: reward.amount},
+ last: Date.now()
+ })
+ return reward;
+ }
+
+ @router('get /api/lottery/fusion')
+ async fusion(req) {
+ let user = req.user;
+ let items = await ActivityItem.find({user: user.id, activity: user.activity});
+ let itemCountMap = new Map();
+ for (let item of items) {
+ itemCountMap.set(item.item, item.amount);
+ }
+ for (let item of FUSION_CFG.source) {
+ if (!itemCountMap.has(item.id)) {
+ throw new ZError(13, 'no enough item');
+ }
+ if (itemCountMap.get(item.id) < item.amount) {
+ throw new ZError(14, 'no enough item');
+ }
+ }
+ for (let item of FUSION_CFG.source) {
+ await ActivityItem.insertOrUpdate({
+ user: user.id,
+ activity: user.activity,
+ item: item.id
+ },
+ {
+ $inc: {amount: -item.amount},
+ last: Date.now()
+ })
+ }
+
+ await ActivityItem.insertOrUpdate({
+ user: user.id,
+ activity: user.activity,
+ item: FUSION_CFG.target.id
+ },
+ {
+ $inc: {amount: FUSION_CFG.target.amount},
+ last: Date.now()
+ })
+
+ return {
+ id: FUSION_CFG.target.id,
+ amount: FUSION_CFG.target.amount,
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/controllers/tasks.controller.ts b/src/controllers/tasks.controller.ts
index a411b07..03df86e 100644
--- a/src/controllers/tasks.controller.ts
+++ b/src/controllers/tasks.controller.ts
@@ -47,6 +47,9 @@ export default class TasksController extends BaseController {
if (!allTasks.has(task.task)) {
continue;
}
+ if (!task.isVaild()) {
+ continue;
+ }
if (task.type === TaskTypeEnum.DAILY ) {
let id = `${task.id}:${dateTag}`
if (!taskAddedSet.has(id)) {
@@ -79,10 +82,16 @@ export default class TasksController extends BaseController {
if (dateTag && currentDateTag !== dateTag) {
throw new ZError(11, 'task date not match')
}
+ if (!activity.isVaild()) {
+ throw new ZError(15, 'activity not start or end')
+ }
let cfg = activity.tasks.find((t: TaskCfg) => t.id === taskId);
if (!cfg) {
throw new ZError(12, 'task not found')
}
+ if (!cfg.isVaild()) {
+ throw new ZError(16, 'task not start or end')
+ }
if (dateTag && cfg.type !== TaskTypeEnum.DAILY) {
throw new ZError(13, 'task is not daily task')
}
@@ -122,6 +131,16 @@ export default class TasksController extends BaseController {
if (dateTag && currentDateTag !== dateTag) {
throw new ZError(11, 'task date not match')
}
+ if (!activity.isVaild()) {
+ throw new ZError(15, 'activity not start or end')
+ }
+ let cfg = activity.tasks.find((t: TaskCfg) => t.id === taskId);
+ if (!cfg) {
+ throw new ZError(12, 'task not found')
+ }
+ if (!cfg.isVaild()) {
+ throw new ZError(16, 'task not start or end')
+ }
let currentTask = user.taskProgress.find((t: TaskStatus) => t.id === task);
if (!currentTask) {
throw new ZError(11, 'task not found')
@@ -145,6 +164,17 @@ export default class TasksController extends BaseController {
const user = req.user;
const activity = req.activity;
const { task } = req.params;
+ const [taskId, dateTag] = task.split(':');
+ if (!activity.isVaild()) {
+ throw new ZError(15, 'activity not start or end')
+ }
+ let cfg = activity.tasks.find((t: TaskCfg) => t.id === taskId);
+ if (!cfg) {
+ throw new ZError(14, 'task not found')
+ }
+ if (!cfg.isVaild()) {
+ throw new ZError(16, 'task not start or end')
+ }
let currentTask = user.taskProgress.find((t: TaskStatus) => t.id === task);
if (!currentTask) {
throw new ZError(11, 'task not found')
diff --git a/src/models/ActivityInfo.ts b/src/models/ActivityInfo.ts
index 0f68b76..24d3b99 100644
--- a/src/models/ActivityInfo.ts
+++ b/src/models/ActivityInfo.ts
@@ -36,8 +36,36 @@ export class TaskCfg {
show: boolean
@prop({ type: mongoose.Schema.Types.Mixed })
cfg: any
+ @prop()
+ start?: string
+ @prop()
+ end?: string
@prop({ type: mongoose.Schema.Types.Mixed })
params: any
+
+ public isStart() {
+ const now = Date.now()
+ if (this.start) {
+ let start = new Date(this.start).getTime()
+ if (now < start) {
+ return false
+ }
+ }
+ }
+ public isEnd() {
+ const now = Date.now()
+ if (this.end) {
+ let end = new Date(this.end).getTime()
+ if (now > end) {
+ return false
+ }
+ }
+ return true
+ }
+
+ public isVaild() {
+ return this.isStart() && !this.isEnd()
+ }
}
interface ActivityInfoClass extends Base, TimeStamps {}
@dbconn()
@@ -67,11 +95,26 @@ class ActivityInfoClass extends BaseModule {
@prop()
public comment?: string
+ public isValie() {
+ const now = Date.now()
+ if (this.startTime) {
+ if (now < this.startTime) {
+ return false
+ }
+ }
+ if (this.endTime) {
+ if (now > this.endTime) {
+ return false
+ }
+ }
+ return true
+ }
public toJson() {
let result = super.toJson()
let tasks = []
+ const now = Date.now()
for (let task of this.tasks) {
- if (task.show) {
+ if (task.show && task.isStart()) {
tasks.push({
id: task.id,
task: task.task,
@@ -84,6 +127,7 @@ class ActivityInfoClass extends BaseModule {
category: task.category,
autoclaim: task.autoclaim,
cfg: task.cfg,
+ end: task.isEnd(),
})
}
}
diff --git a/src/models/ActivityItem.ts b/src/models/ActivityItem.ts
new file mode 100644
index 0000000..45c02ec
--- /dev/null
+++ b/src/models/ActivityItem.ts
@@ -0,0 +1,31 @@
+import { dbconn } from 'decorators/dbconn'
+import { getModelForClass, index, modelOptions, mongoose, prop } from '@typegoose/typegoose'
+import { Severity } from '@typegoose/typegoose/lib/internal/constants'
+import { BaseModule } from './Base'
+
+/**
+ */
+@dbconn()
+@index({ user: 1, activity: 1}, { unique: false })
+@index({ user: 1, activity: 1, item: 1 }, { unique: true })
+@modelOptions({ schemaOptions: { collection: 'activity_item', timestamps: true }, options: { allowMixed: Severity.ALLOW } })
+class ActivityItemClass extends BaseModule {
+ @prop()
+ public user: string
+ @prop()
+ public activity: string
+ @prop()
+ public item: string
+ @prop({default: 0})
+ public amount: number
+ @prop()
+ public last: number
+
+ public toJson() {
+ return {
+ id: this.item,
+ amount: this.amount
+ }
+ }
+}
+export const ActivityItem = getModelForClass(ActivityItemClass, { existingConnection: ActivityItemClass['db'] })
diff --git a/src/models/LotteryRecord.ts b/src/models/LotteryRecord.ts
new file mode 100644
index 0000000..7608737
--- /dev/null
+++ b/src/models/LotteryRecord.ts
@@ -0,0 +1,33 @@
+import { dbconn } from 'decorators/dbconn'
+import { getModelForClass, index, modelOptions, mongoose, prop } from '@typegoose/typegoose'
+import { Severity } from '@typegoose/typegoose/lib/internal/constants'
+import { BaseModule } from './Base'
+
+/**
+ * 用户抽奖记录
+ */
+@dbconn()
+@index({ user: 1, activity: 1}, { unique: false })
+@index({ user: 1, activity: 1, dateTag: 1}, { unique: false })
+@modelOptions({ schemaOptions: { collection: 'lottery_record', timestamps: true }, options: { allowMixed: Severity.ALLOW } })
+class LotteryRecordClass extends BaseModule {
+ @prop()
+ public user: string
+ @prop()
+ public activity: string
+ @prop()
+ public dateTag: string
+ @prop()
+ public reward?: string
+ @prop({default: 0})
+ public amount: number
+
+ public toJson() {
+ return {
+ day: this.dateTag,
+ id: this.reward,
+ amount: this.amount,
+ }
+ }
+}
+export const LotteryRecord = getModelForClass(LotteryRecordClass, { existingConnection: LotteryRecordClass['db'] })
diff --git a/src/models/LotteryStats.ts b/src/models/LotteryStats.ts
new file mode 100644
index 0000000..d5d4f39
--- /dev/null
+++ b/src/models/LotteryStats.ts
@@ -0,0 +1,41 @@
+import { dbconn } from 'decorators/dbconn'
+import { getModelForClass, index, modelOptions, mongoose, prop } from '@typegoose/typegoose'
+import { Severity } from '@typegoose/typegoose/lib/internal/constants'
+import { BaseModule } from './Base'
+
+/**
+ * 用户抽奖状态
+ */
+@dbconn()
+@index({ user: 1, activity: 1, dateTag: 1}, { unique: false })
+@modelOptions({ schemaOptions: { collection: 'lottery_stats', timestamps: true }, options: { allowMixed: Severity.ALLOW } })
+class LotteryStatsClass extends BaseModule {
+ @prop()
+ public user: string
+ @prop()
+ public activity: string
+ @prop({default: 0})
+ public amount: number
+ @prop({default: 0})
+ public daily: number
+ @prop({default: 0})
+ public gacha: number
+ @prop({default: 0 })
+ public share: number
+ @prop({default: 0})
+ public used: number
+ @prop()
+ public dateTag: string
+
+
+ public toJson() {
+ return {
+ amount: this.amount,
+ daily: this.daily,
+ gacha: this.gacha,
+ used: this.used,
+ day: this.dateTag,
+ }
+ }
+}
+export const LotteryStats = getModelForClass(LotteryStatsClass, { existingConnection: LotteryStatsClass['db'] })
diff --git a/src/models/chain/CheckIn.ts b/src/models/chain/CheckIn.ts
new file mode 100644
index 0000000..d8d9dc9
--- /dev/null
+++ b/src/models/chain/CheckIn.ts
@@ -0,0 +1,57 @@
+import { getModelForClass, index, modelOptions, prop } from '@typegoose/typegoose'
+import { dbconn } from 'decorators/dbconn'
+import { BaseModule } from '../Base'
+import { formatDate, yesterday } from 'utils/date.util'
+
+@dbconn('chain')
+@index({ from: 1 }, { unique: false })
+@index({ from: 1, dateTag: 1}, { unique: true })
+@index({ from: 1, blockTime: 1}, { unique: false })
+@modelOptions({
+ schemaOptions: { collection: 'check_in_event', timestamps: true },
+})
+export class CheckInClass extends BaseModule {
+ @prop({ required: true })
+ public from!: string
+ @prop()
+ public to: string
+ @prop({ required: true })
+ public hash: string
+ @prop()
+ public blockNumber: string
+ @prop()
+ public blockHash: string
+ @prop()
+ public blockTime: number
+ @prop()
+ public dateTag: string
+ // 连签天数
+ @prop({default: 0})
+ public count: number
+ @prop()
+ public value: string
+ @prop()
+ public input: string
+
+ public static async saveEvent(event: any) {
+ const preDay = formatDate(yesterday());
+ const preDayEvent = await CheckIn.findOne({ from: event.from, dateTag: preDay })
+ if (preDayEvent) {
+ event.count = preDayEvent.count + 1
+ }
+ return CheckIn.insertOrUpdate({ hash: event.hash }, event)
+ }
+
+ public toJson() {
+ return {
+ address: this.from,
+ day: this.dateTag,
+ time: this.blockTime,
+ count: this.count
+ }
+ }
+}
+
+export const CheckIn = getModelForClass(CheckInClass, {
+ existingConnection: CheckInClass['db'],
+})
diff --git a/src/models/chain/NftHolder.ts b/src/models/chain/NftHolder.ts
new file mode 100644
index 0000000..e07274d
--- /dev/null
+++ b/src/models/chain/NftHolder.ts
@@ -0,0 +1,54 @@
+import { getModelForClass, index, modelOptions, prop } from '@typegoose/typegoose'
+import { dbconn } from 'decorators/dbconn'
+import { BaseModule } from '../Base'
+import { ZERO_ADDRESS } from 'common/Constants'
+
+@dbconn('chain')
+@index({ chain: 1, address: 1, tokenId: 1 }, { unique: true })
+@index({ chain: 1, address: 1, user: 1 }, { unique: false })
+@modelOptions({
+ schemaOptions: { collection: 'nft_holder', timestamps: true },
+})
+export class NftHolderClass extends BaseModule {
+ @prop({ required: true })
+ public address!: string
+ @prop({ required: true })
+ public chain: string
+ @prop({ required: true })
+ public tokenId: string
+ @prop()
+ public blockNumber: number
+ @prop()
+ public user: string
+ @prop({default: false})
+ public burn: boolean
+
+
+ public static async saveData(event: any) {
+ const address = event.address;
+ const chain = event.chain;
+ const tokenId = event.tokenId;
+ const blockNumer = event.blockNumber;
+ const burn = event.to === ZERO_ADDRESS
+
+ let record = await NftHolder.findOne({ address, chain, tokenId })
+ if (!record) {
+ record = new NftHolder({ address, chain, tokenId, blockNumber: blockNumer, user: event.to, burn })
+ } else {
+ if (record.blockNumber < blockNumer) {
+ if (burn) {
+ record.burn = true
+ } else {
+ record.user = event.to
+ }
+ record.blockNumber = blockNumer
+ }
+ }
+ await record.save();
+
+ }
+}
+
+export const NftHolder = getModelForClass(NftHolderClass, {
+ existingConnection: NftHolderClass['db'],
+})
diff --git a/src/models/chain/NftTransferEvent.ts b/src/models/chain/NftTransferEvent.ts
new file mode 100644
index 0000000..4f3fcbb
--- /dev/null
+++ b/src/models/chain/NftTransferEvent.ts
@@ -0,0 +1,66 @@
+import { getModelForClass, index, modelOptions, prop } from '@typegoose/typegoose'
+import { dbconn } from 'decorators/dbconn'
+import { BaseModule } from '../Base'
+
+@dbconn('chain')
+@index({ chain: 1, address: 1, tokenId: 1 }, { unique: false })
+@index({ chain: 1, address: 1, from: 1, to: 1 }, { unique: false })
+@index({ chain: 1, hash: 1, logIndex: 1}, { unique: true })
+@modelOptions({
+ schemaOptions: { collection: 'nft_transfer_event', timestamps: true },
+})
+export class NftTransferEventClass extends BaseModule {
+ @prop({ required: true })
+ public address!: string
+ @prop({ required: true })
+ public chain: string
+ @prop({ required: true })
+ public logIndex: number
+ @prop()
+ public event: string
+ @prop({ required: true })
+ public hash: string
+ @prop()
+ public blockNumber: number
+ @prop()
+ public blockHash: string
+ @prop()
+ public removed: boolean
+ @prop()
+ public from: string
+ @prop()
+ public to: string
+ @prop()
+ public tokenId: string
+ @prop()
+ public blockTime: number
+ @prop({ default: 0 })
+ public version: number
+
+ public static async saveEvent(event: any) {
+ const tokenId = event.tokenId || event.value
+ if (!tokenId) {
+ return
+ }
+ const logIndex = parseInt(event.logIndex || '0')
+ const from = event.from.toLowerCase()
+ const to = event.to.toLowerCase()
+ const hash = event.hash || event.transactionHash
+ const data = {
+ address: event.address.toLowerCase(),
+ blockNumber: parseInt(event.blockNumber),
+ removed: event.removed,
+ from,
+ to,
+ tokenId,
+ // blockTime: new Date(event.time).getTime(),
+ $inc: { version: 1 },
+ }
+
+ return NftTransferEvent.insertOrUpdate({ hash, logIndex, chain: event.chain }, data)
+ }
+}
+
+export const NftTransferEvent = getModelForClass(NftTransferEventClass, {
+ existingConnection: NftTransferEventClass['db'],
+})
diff --git a/src/services/chain.svr.ts b/src/services/chain.svr.ts
index fb3f217..2a36103 100644
--- a/src/services/chain.svr.ts
+++ b/src/services/chain.svr.ts
@@ -1,3 +1,4 @@
+import { NftHolder } from "models/chain/NftHolder"
export const queryCheckInList = async (address: string, days: string | number | string[], limit: number = 0) => {
const url = process.env.CHAIN_SVR + '/task/check_in'
@@ -34,4 +35,11 @@ export const queryBurnNftList = async (address: string, user: string, chain: num
chain
})
}).then((res) => res.json())
+}
+
+export const checkHadGacha = async (user: string) => {
+ const chain = process.env.CHAIN+''
+ const address = process.env.GACHA_CONTRACT
+ const record = await NftHolder.findOne({user, chain, address})
+ return !!record
}
\ No newline at end of file
diff --git a/src/tasks/BurnNft.ts b/src/tasks/BurnNft.ts
index 3cf10e8..d6388d0 100644
--- a/src/tasks/BurnNft.ts
+++ b/src/tasks/BurnNft.ts
@@ -10,16 +10,16 @@ export default class BurnNft extends ITask {
static show: boolean = true
async execute(data: any) {
const { task } = data
- let cfg = this.params.activity.tasks.find((t: TaskCfg) => t.id === task.id)
+ let cfg = this.activity.tasks.find((t: TaskCfg) => t.id === task.id)
const address = cfg.params.address.toLowerCase()
const chain = parseInt(process.env.CHAIN)
- const res = await queryBurnNftList(address, this.params.user.address, chain)
+ const res = await queryBurnNftList(address, this.user.address, chain)
if (res.errcode) {
throw new ZError(res.errcode, res.errmsg)
}
const nftList = res.data
const localNft = await NftBurnRecord.find({
- user: this.params.user.id,
+ user: this.user.id,
chain,
address,
})
@@ -28,7 +28,7 @@ export default class BurnNft extends ITask {
let tmpNftSet = new Set();
for (let nft of localNft) {
localNftSet.add(nft.tokenId)
- if (nft.activity === this.params.activity.id && nft.task === task.id) {
+ if (nft.activity === this.activity.id && nft.task === task.id) {
finishAmount += 1
tmpNftSet.add(nft.tokenId)
}
@@ -45,11 +45,11 @@ export default class BurnNft extends ITask {
}
for (let finishNft of finishNfts) {
const record = new NftBurnRecord({
- user: this.params.user.id,
+ user: this.user.id,
chain,
address,
tokenId: finishNft,
- activity: this.params.activity.id,
+ activity: this.activity.id,
task: task.id
})
await record.save()
@@ -66,7 +66,7 @@ export default class BurnNft extends ITask {
task.status = TaskStatusEnum.PART_SUCCESS
}
try {
- await this.params.user.save()
+ await this.user.save()
} catch(err) {
throw new ZError(100, 'save failed')
}
diff --git a/src/tasks/DailyCheckIn.ts b/src/tasks/DailyCheckIn.ts
index aa580c4..7d9056a 100644
--- a/src/tasks/DailyCheckIn.ts
+++ b/src/tasks/DailyCheckIn.ts
@@ -4,8 +4,8 @@ import { TaskStatus, TaskStatusEnum } from "models/ActivityUser";
import { TaskCfg, TaskTypeEnum } from "models/ActivityInfo";
import { queryCheckInList, queryCheckInSeq } from "services/chain.svr";
import { updateRankScore } from "services/rank.svr";
+import { LotteryCache } from "common/LotteryCache";
-// TODO:: test
/**
* 检查每日签到
* days
@@ -17,9 +17,9 @@ export default class DailyCheckIn extends ITask {
static show: boolean = true
async execute(data: any) {
- const { address } = this.params.user
+ const { address } = this.user
const { task } = data
- let cfg = this.params.activity.tasks.find((t: TaskCfg) => t.id === task.id)
+ let cfg = this.activity.tasks.find((t: TaskCfg) => t.id === task.id)
const days = cfg.params.days || 1
const limit = cfg.params.limit || 0
const res = cfg.type === TaskTypeEnum.DAILY ?
@@ -28,8 +28,6 @@ export default class DailyCheckIn extends ITask {
if (res.errcode) {
throw new ZError(res.errcode, res.errmsg)
}
- let success = false
-
if ((cfg.type === TaskTypeEnum.DAILY && task.status === TaskStatusEnum.RUNNING && res.data.length >= days)
|| (cfg.type === TaskTypeEnum.ONCE && task.status === TaskStatusEnum.RUNNING && res.data.count >= days)) {
@@ -37,7 +35,7 @@ export default class DailyCheckIn extends ITask {
task.timeFinish = Date.now()
task.data = res.data
try {
- await this.params.user.save()
+ await this.user.save()
} catch(err) {
throw new ZError(100, 'save daily checkin failed')
}
@@ -55,9 +53,9 @@ export default class DailyCheckIn extends ITask {
public async claimReward(task: TaskStatus) {
// 增加连续签到奖励分
// 请求前7天的签到记录, 往前查找连续签到的记录,
- let cfg = this.params.activity.tasks.find((t: TaskCfg) => t.id === task.id)
+ let cfg = this.activity.tasks.find((t: TaskCfg) => t.id === task.id)
if (cfg.type === TaskTypeEnum.DAILY) {
- const res = await queryCheckInList(this.params.user.address, 1, 0)
+ const res = await queryCheckInList(this.user.address, 1, 0)
const [taskId, dateTag] = task.id.split(':');
let list: { day: string, time: number, count: number }[] = res.data;
@@ -65,14 +63,14 @@ export default class DailyCheckIn extends ITask {
let count = list.length > 0 ? list[0].count : 0;
let seq = count % countCfg;
let score = cfg.params.score[seq] || 0 + cfg.score;
- const user = this.params.user
+ const user = this.user
if (user.boost > 1 && Date.now() < user.boostExpire) {
score = Math.floor(score * user.boost)
}
await updateRankScore({
- user: this.params.user.id,
+ user: this.user.id,
score: score,
- activity: this.params.user.activity,
+ activity: this.user.activity,
scoreType: cfg.task,
scoreParams: {
taskId: task.id,
@@ -81,8 +79,15 @@ export default class DailyCheckIn extends ITask {
boost: user.boost,
}
})
+ await this.claimItem(cfg)
} else {
- super.claimReward(task);
+ await super.claimReward(task);
+ }
+ // 更新gacha拥有者抽奖次数, 这里写死, 等想到好的方式再处理
+ let record = await new LotteryCache().getData(this.user.id, this.user.activity);
+ if (record.daily === 0) {
+ record.daily += 1
+ record.amount += 1
}
}
diff --git a/src/tasks/DiscordConnect.ts b/src/tasks/DiscordConnect.ts
index e659de1..cfc5278 100644
--- a/src/tasks/DiscordConnect.ts
+++ b/src/tasks/DiscordConnect.ts
@@ -9,10 +9,10 @@ export default class DiscordConnect extends ITask {
static show: boolean = true
async execute(data: any) {
- const { address } = this.params.user
+ const { address } = this.user
const { task } = data
const res = await checkDiscord(address)
- let cfg = this.params.activity.tasks.find((t: TaskCfg) => t.id === task.id)
+ let cfg = this.activity.tasks.find((t: TaskCfg) => t.id === task.id)
if (res.status !== 200) {
throw new ZError(11, 'discord check failed')
}
@@ -24,10 +24,10 @@ export default class DiscordConnect extends ITask {
task.status = TaskStatusEnum.SUCCESS
task.timeFinish = Date.now()
task.data = res.data.data
- this.params.user.discordId = res.data.data.userid
- this.params.user.discordName = res.data.data.username
+ this.user.discordId = res.data.data.userid
+ this.user.discordName = res.data.data.username
try {
- await this.params.user.save()
+ await this.user.save()
} catch(err) {
throw new ZError(100, 'discord already binded')
}
diff --git a/src/tasks/DiscordJoin.ts b/src/tasks/DiscordJoin.ts
index 141bcb5..b260af1 100644
--- a/src/tasks/DiscordJoin.ts
+++ b/src/tasks/DiscordJoin.ts
@@ -9,7 +9,7 @@ export default class DiscordJoin extends ITask {
async execute(data: any) {
const { task } = data
- let cfg = this.params.activity.tasks.find((t: TaskCfg) => t.id === task.id)
+ let cfg = this.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')
@@ -22,7 +22,7 @@ export default class DiscordJoin extends ITask {
task.timeFinish = Date.now()
task.data = {}
try {
- await this.params.user.save()
+ await this.user.save()
} catch(err) {
throw new ZError(100, 'already join discord')
}
diff --git a/src/tasks/DiscordRole.ts b/src/tasks/DiscordRole.ts
index bd11cf7..873876a 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 === task.id)
+ let cfg = this.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')
@@ -21,7 +21,7 @@ export default class DiscordRole extends ITask {
task.timeFinish = Date.now()
task.data = {}
try {
- await this.params.user.save()
+ await this.user.save()
} catch(err) {
throw new ZError(100, 'already acquired discord role')
}
diff --git a/src/tasks/OkxLogin.ts b/src/tasks/OkxLogin.ts
index c18050e..9107b56 100644
--- a/src/tasks/OkxLogin.ts
+++ b/src/tasks/OkxLogin.ts
@@ -11,17 +11,17 @@ 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 === task.id)
+ const { activity } = this.user
+ let cfg = this.activity.tasks.find((t: TaskCfg) => t.id === task.id)
let wallet = 'okx';
- let record = LoginRecord.findOne({ user: this.params.user.id, activity, wallet})
+ let record = LoginRecord.findOne({ user: this.user.id, activity, wallet})
if (!record ) {
throw new ZError(11, 'task not finished')
}
task.status = TaskStatusEnum.SUCCESS
task.timeFinish = Date.now()
try {
- await this.params.user.save()
+ await this.user.save()
} catch(err) {
throw new ZError(100, 'discord already binded')
}
diff --git a/src/tasks/ShareCode.ts b/src/tasks/ShareCode.ts
index 433effe..a3aa713 100644
--- a/src/tasks/ShareCode.ts
+++ b/src/tasks/ShareCode.ts
@@ -2,13 +2,16 @@ import { ActivityUser, TaskStatus, TaskStatusEnum } from "models/ActivityUser";
import { ITask } from "./base/ITask";
import { TaskCfg } from "models/ActivityInfo";
import { updateRankScore } from "services/rank.svr";
+import { ActivityItem } from "models/ActivityItem";
+import { LotteryCache } from "common/LotteryCache";
+import { checkHadGacha } from "services/chain.svr";
-const updateInviteScore = async (user: typeof ActivityUser, scores: number[], level: number, reason: string) => {
+const updateInviteScore = async (user: typeof ActivityUser, scores: number[], items: any[], level: number, reason: string) => {
if (!user.inviteUser || scores.length <= level) {
return;
}
let userSup = await ActivityUser.findById(user.inviteUser)
- if (!userSup) {
+ if (!userSup || !userSup.address) {
return;
}
await updateRankScore({
@@ -21,7 +24,22 @@ const updateInviteScore = async (user: typeof ActivityUser, scores: number[], le
level
}
})
- await updateInviteScore(userSup, scores, level + 1, reason)
+ // 更新gacha拥有者抽奖次数, 这里写死, 等想到好的方式再处理
+ let record = await new LotteryCache().getData(userSup.id, userSup.activity);
+ record.share += 1
+ record.amount += 1
+ if (record.gacha ===0 && checkHadGacha(userSup.address)) {
+ record.gacha += 1
+ record.amount += 1
+ }
+ if (items.length > level) {
+ for (let key in items[level]) {
+ let amount = items[level][key]
+ await ActivityItem.insertOrUpdate(
+ {user: userSup, activity: userSup.activity, item: key}, {$inc: {amount}})
+ }
+ }
+ await updateInviteScore(userSup, scores, items, level + 1, reason)
}
export default class ShareCode extends ITask {
@@ -30,20 +48,21 @@ export default class ShareCode extends ITask {
async execute(data: any) {
let { task } = data
- let cfg = this.params.activity.tasks.find((t: TaskCfg) => t.id === task.id)
- if (!this.params.user.inviteUser) {
+ let cfg = this.activity.tasks.find((t: TaskCfg) => t.id === task.id)
+ if (!this.user.inviteUser) {
throw new Error('not finished')
}
let scores = cfg.params.score;
+ const items = cfg.params.inviteItems || [];
task.status = TaskStatusEnum.SUCCESS
task.timeFinish = Date.now()
task.data = {}
- await this.params.user.save();
+ await this.user.save();
// According to configuration, add score to user who invite current user
if (cfg.autoclaim) {
try {
await super.claimReward(task);
- await updateInviteScore(this.params.user, scores, 0, task.task)
+ await updateInviteScore(this.user, scores, items, 0, task.task)
} catch(err) {
console.log(err)
}
@@ -53,8 +72,9 @@ export default class ShareCode extends ITask {
public async claimReward(task: TaskStatus) {
await super.claimReward(task);
- let cfg = this.params.activity.tasks.find((t: TaskCfg) => t.id === task.id)
+ let cfg = this.activity.tasks.find((t: TaskCfg) => t.id === task.id)
let scores = cfg.params.score;
- await updateInviteScore(this.params.user, scores, 0, "invite")
+ const items = cfg.params.inviteItems || [];
+ await updateInviteScore(this.user, scores, items, 0, "invite")
}
}
\ No newline at end of file
diff --git a/src/tasks/TwitterConnect.ts b/src/tasks/TwitterConnect.ts
index 091e0a0..cac695e 100644
--- a/src/tasks/TwitterConnect.ts
+++ b/src/tasks/TwitterConnect.ts
@@ -9,7 +9,7 @@ export default class TwitterConnect extends ITask {
static show: boolean = true
async execute(data: any) {
- let { address } = this.params.user
+ let { address } = this.user
let res = await checkTwitter(address)
if (res.status !== 200) {
throw new ZError(11, 'twitter check failed')
@@ -22,14 +22,14 @@ export default class TwitterConnect extends ITask {
task.status = TaskStatusEnum.SUCCESS
task.timeFinish = Date.now()
task.data = res.data.data
- this.params.user.twitterId = res.data.data.userid
- this.params.user.twitterName = res.data.data.username
+ this.user.twitterId = res.data.data.userid
+ this.user.twitterName = res.data.data.username
try {
- await this.params.user.save()
+ await this.user.save()
} catch(err) {
throw new ZError(100, 'twitter already binded')
}
- let cfg = this.params.activity.tasks.find((t: TaskCfg) => t.id === task.id)
+ let cfg = this.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 e7fc7ba..87a28d5 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 === task.id)
+ let cfg = this.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')
@@ -21,7 +21,7 @@ export default class TwitterFollow extends ITask {
task.timeFinish = Date.now()
task.data = {}
try {
- await this.params.user.save()
+ await this.user.save()
} catch(err) {
throw new ZError(100, 'save failed')
}
diff --git a/src/tasks/TwitterRetweet.ts b/src/tasks/TwitterRetweet.ts
index cff0ffa..ff9d706 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 === task.id)
+ let cfg = this.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')
@@ -20,7 +20,7 @@ export default class TwitterRetweet extends ITask {
task.timeFinish = Date.now()
task.data = {}
try {
- await this.params.user.save()
+ await this.user.save()
} catch(err) {
throw new ZError(100, 'save failed')
}
diff --git a/src/tasks/YoutubeFollow.ts b/src/tasks/YoutubeFollow.ts
index a7dd3ce..b2770b4 100644
--- a/src/tasks/YoutubeFollow.ts
+++ b/src/tasks/YoutubeFollow.ts
@@ -8,7 +8,7 @@ export default class YoutubeFollow extends ITask {
static show: boolean = true
async execute(data: any) {
const { task } = data
- let cfg = this.params.activity.tasks.find((t: TaskCfg) => t.id === task.id)
+ let cfg = this.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')
@@ -21,7 +21,7 @@ export default class YoutubeFollow extends ITask {
task.timeFinish = Date.now()
task.data = {}
try {
- await this.params.user.save()
+ await this.user.save()
} catch(err) {
throw new ZError(100, 'save failed')
}
diff --git a/src/tasks/YoutubePost.ts b/src/tasks/YoutubePost.ts
index 7250c19..15377dc 100644
--- a/src/tasks/YoutubePost.ts
+++ b/src/tasks/YoutubePost.ts
@@ -7,7 +7,7 @@ export default class YoutubePost extends ITask {
static show: boolean = true
async execute(data: any) {
const { task } = data
- let cfg = this.params.activity.tasks.find(t => t.id === task.id)
+ let cfg = this.activity.tasks.find(t => t.id === task.id)
let time = cfg.params.time;
if (Date.now() - task.timeStart < time * 1000) {
throw new ZError(11, 'post failed')
@@ -20,7 +20,7 @@ export default class YoutubePost extends ITask {
task.timeFinish = Date.now()
task.data = {}
try {
- await this.params.user.save()
+ await this.user.save()
} catch(err) {
throw new ZError(100, 'save failed')
}
diff --git a/src/tasks/base/ITask.ts b/src/tasks/base/ITask.ts
index 1a9d9f9..3f9c203 100644
--- a/src/tasks/base/ITask.ts
+++ b/src/tasks/base/ITask.ts
@@ -1,5 +1,6 @@
-import { TaskCfg } from "models/ActivityInfo"
-import { TaskStatus, TaskStatusEnum } from "models/ActivityUser"
+import { ActivityInfo, TaskCfg } from "models/ActivityInfo"
+import { ActivityItem } from "models/ActivityItem"
+import { ActivityUser, TaskStatus, TaskStatusEnum } from "models/ActivityUser"
import { updateRankScore } from "services/rank.svr"
export abstract class ITask {
@@ -7,17 +8,21 @@ export abstract class ITask {
static show: boolean = true
static auto: boolean = false
- params: any
- constructor(params: any) {
+ user: typeof ActivityUser
+ activity: typeof ActivityInfo
+
+
+ constructor({user, activity}: {user: typeof ActivityUser, activity: typeof ActivityInfo}) {
// do nothing
- this.params = params
+ this.user = user
+ this.activity = activity
}
abstract execute(data: any): Promise
public async claimReward(task: any) {
- const user = this.params.user
+ const user = this.user
const [taskId, dateTag] = task.id.split(':');
- const cfg = this.params.activity.tasks.find((t: TaskCfg) => t.id === taskId)
+ const cfg = this.activity.tasks.find((t: TaskCfg) => t.id === taskId)
if (!cfg.score) {
return;
}
@@ -48,6 +53,17 @@ export abstract class ITask {
task.status = TaskStatusEnum.CLAIMED
task.timeClaim = Date.now()
}
+ await this.claimItem(cfg)
await user.save()
}
+
+ public async claimItem(cfg: TaskCfg) {
+ if (cfg.params.items) {
+ for (let key in cfg.params.items) {
+ let amount = cfg.params.items[key]
+ await ActivityItem.insertOrUpdate(
+ {user: this.user.id, activity: this.user.activity, item: key}, {$inc: {amount}, last: Date.now()})
+ }
+ }
+ }
}
\ No newline at end of file