增加抽奖相关api
This commit is contained in:
parent
145a5cb06c
commit
544048bcf3
127
docs/api.md
127
docs/api.md
@ -110,6 +110,7 @@ SiweMessage的nonce说明(具体参考例子):
|
|||||||
"score": 0, // 完成任务可获得的积分
|
"score": 0, // 完成任务可获得的积分
|
||||||
"category": "", // 任务分类
|
"category": "", // 任务分类
|
||||||
"cfg": {}, // 其他一些任务相关配置参数, 比如icon, 或者其他未考虑的参数
|
"cfg": {}, // 其他一些任务相关配置参数, 比如icon, 或者其他未考虑的参数
|
||||||
|
"end": false, // 是否已经结束
|
||||||
"autoclaim": false // 任务完成后是否自动获取奖励
|
"autoclaim": false // 任务完成后是否自动获取奖励
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@ -362,3 +363,129 @@ query param
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 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": "数量"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
```
|
@ -13,6 +13,8 @@
|
|||||||
"category": "",
|
"category": "",
|
||||||
"autoclaim": false,
|
"autoclaim": false,
|
||||||
"cfg": {"icon": "twitter"},
|
"cfg": {"icon": "twitter"},
|
||||||
|
"start": "2024-01-01 00:00",
|
||||||
|
"end": "2025-01-01 00:00",
|
||||||
"params": {}
|
"params": {}
|
||||||
}, {
|
}, {
|
||||||
"id": "e2fclylj30vwcpe0szl",
|
"id": "e2fclylj30vwcpe0szl",
|
||||||
@ -25,6 +27,8 @@
|
|||||||
"autoclaim": false,
|
"autoclaim": false,
|
||||||
"pretasks": ["e2yhq2lj30vwcpedv7p"],
|
"pretasks": ["e2yhq2lj30vwcpedv7p"],
|
||||||
"cfg": {"account": "@_CounterFire", "icon": "twitter"},
|
"cfg": {"account": "@_CounterFire", "icon": "twitter"},
|
||||||
|
"start": "2024-01-01 00:00",
|
||||||
|
"end": "2025-01-01 00:00",
|
||||||
"params": {"time": 6, "failRate": 60}
|
"params": {"time": 6, "failRate": 60}
|
||||||
}, {
|
}, {
|
||||||
"id": "e2feyflj30vwcpe0sjy",
|
"id": "e2feyflj30vwcpe0sjy",
|
||||||
@ -37,6 +41,8 @@
|
|||||||
"autoclaim": false,
|
"autoclaim": false,
|
||||||
"pretasks": ["e2yhq2lj30vwcpedv7p"],
|
"pretasks": ["e2yhq2lj30vwcpedv7p"],
|
||||||
"cfg": {"icon": "twitter"},
|
"cfg": {"icon": "twitter"},
|
||||||
|
"start": "2024-01-01 00:00",
|
||||||
|
"end": "2025-01-01 00:00",
|
||||||
"params": {"time": 6, "failRate": 60}
|
"params": {"time": 6, "failRate": 60}
|
||||||
}, {
|
}, {
|
||||||
"id": "e2fuah0j30vwcpe0my7",
|
"id": "e2fuah0j30vwcpe0my7",
|
||||||
@ -49,6 +55,8 @@
|
|||||||
"autoclaim": false,
|
"autoclaim": false,
|
||||||
"pretasks": ["e2yhq2lj30vwcpedv7p"],
|
"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"},
|
"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}
|
"params": {"time": 6, "failRate": 60}
|
||||||
}, {
|
}, {
|
||||||
"id": "e2fuah0j30vwcpe1my7",
|
"id": "e2fuah0j30vwcpe1my7",
|
||||||
@ -61,6 +69,8 @@
|
|||||||
"autoclaim": false,
|
"autoclaim": false,
|
||||||
"pretasks": ["e2yhq2lj30vwcpedv7p"],
|
"pretasks": ["e2yhq2lj30vwcpedv7p"],
|
||||||
"cfg": {"icon": "twitter", "content": "输入框自动生成,活动开始时添加内容)"},
|
"cfg": {"icon": "twitter", "content": "输入框自动生成,活动开始时添加内容)"},
|
||||||
|
"start": "2024-01-01 00:00",
|
||||||
|
"end": "2025-01-01 00:00",
|
||||||
"params": {"time": 6, "failRate": 60}
|
"params": {"time": 6, "failRate": 60}
|
||||||
}, {
|
}, {
|
||||||
"id": "e2far3lj30vwcpe0mh7",
|
"id": "e2far3lj30vwcpe0mh7",
|
||||||
@ -73,6 +83,8 @@
|
|||||||
"autoclaim": false,
|
"autoclaim": false,
|
||||||
"pretasks": [],
|
"pretasks": [],
|
||||||
"cfg": {"icon": "discord"},
|
"cfg": {"icon": "discord"},
|
||||||
|
"start": "2024-01-01 00:00",
|
||||||
|
"end": "2025-01-01 00:00",
|
||||||
"params": {}
|
"params": {}
|
||||||
}, {
|
}, {
|
||||||
"id": "e2far3lj30vwcpe0mf8",
|
"id": "e2far3lj30vwcpe0mf8",
|
||||||
@ -85,6 +97,8 @@
|
|||||||
"autoclaim": false,
|
"autoclaim": false,
|
||||||
"pretasks": [],
|
"pretasks": [],
|
||||||
"cfg": {"icon": "discord"},
|
"cfg": {"icon": "discord"},
|
||||||
|
"start": "2024-01-01 00:00",
|
||||||
|
"end": "2025-01-01 00:00",
|
||||||
"params": {"time": 6, "failRate": 60}
|
"params": {"time": 6, "failRate": 60}
|
||||||
}, {
|
}, {
|
||||||
"id": "e2fak2lj30vwcpe0awc",
|
"id": "e2fak2lj30vwcpe0awc",
|
||||||
@ -97,6 +111,8 @@
|
|||||||
"autoclaim": false,
|
"autoclaim": false,
|
||||||
"pretasks": ["e2far3lj30vwcpe0mf8"],
|
"pretasks": ["e2far3lj30vwcpe0mf8"],
|
||||||
"cfg": {"icon": "discord"},
|
"cfg": {"icon": "discord"},
|
||||||
|
"start": "2024-01-01 00:00",
|
||||||
|
"end": "2025-01-01 00:00",
|
||||||
"params": {"time": 6, "failRate": 60}
|
"params": {"time": 6, "failRate": 60}
|
||||||
}, {
|
}, {
|
||||||
"id": "e2feyflj30vwcpe0sjx",
|
"id": "e2feyflj30vwcpe0sjx",
|
||||||
@ -109,6 +125,8 @@
|
|||||||
"autoclaim": false,
|
"autoclaim": false,
|
||||||
"pretasks": [],
|
"pretasks": [],
|
||||||
"cfg": {"icon": "youtube"},
|
"cfg": {"icon": "youtube"},
|
||||||
|
"start": "2024-01-01 00:00",
|
||||||
|
"end": "2025-01-01 00:00",
|
||||||
"params": {"time": 6, "failRate": 60}
|
"params": {"time": 6, "failRate": 60}
|
||||||
}, {
|
}, {
|
||||||
"id": "e2feyflj30vwcpe0sjz",
|
"id": "e2feyflj30vwcpe0sjz",
|
||||||
@ -121,6 +139,8 @@
|
|||||||
"autoclaim": false,
|
"autoclaim": false,
|
||||||
"pretasks": [],
|
"pretasks": [],
|
||||||
"cfg": {"icon": "youtube"},
|
"cfg": {"icon": "youtube"},
|
||||||
|
"start": "2024-01-01 00:00",
|
||||||
|
"end": "2025-01-01 00:00",
|
||||||
"params": {"time": 6, "failRate": 60}
|
"params": {"time": 6, "failRate": 60}
|
||||||
}, {
|
}, {
|
||||||
"id": "e2fuah0j30vwcpe2my7",
|
"id": "e2fuah0j30vwcpe2my7",
|
||||||
@ -133,6 +153,8 @@
|
|||||||
"autoclaim": false,
|
"autoclaim": false,
|
||||||
"pretasks": ["e2yhq2lj30vwcpedv7p"],
|
"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"},
|
"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}
|
"params": {"time": 6, "failRate": 60}
|
||||||
}, {
|
}, {
|
||||||
"id": "e2fuah0j30vwcpe2my7",
|
"id": "e2fuah0j30vwcpe2my7",
|
||||||
@ -145,6 +167,8 @@
|
|||||||
"autoclaim": false,
|
"autoclaim": false,
|
||||||
"pretasks": ["e2yhq2lj30vwcpedv7p"],
|
"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"},
|
"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}
|
"params": {"time": 6, "failRate": 60}
|
||||||
}, {
|
}, {
|
||||||
"id": "e2f7fplj30vwcpe0l97",
|
"id": "e2f7fplj30vwcpe0l97",
|
||||||
@ -157,6 +181,8 @@
|
|||||||
"autoclaim": false,
|
"autoclaim": false,
|
||||||
"pretasks": [],
|
"pretasks": [],
|
||||||
"cfg": {},
|
"cfg": {},
|
||||||
|
"start": "2024-01-01 00:00",
|
||||||
|
"end": "2025-01-01 00:00",
|
||||||
"params": {"days": 3}
|
"params": {"days": 3}
|
||||||
}, {
|
}, {
|
||||||
"id": "e2f7fplj30vwcpe0l98",
|
"id": "e2f7fplj30vwcpe0l98",
|
||||||
@ -169,7 +195,9 @@
|
|||||||
"autoclaim": false,
|
"autoclaim": false,
|
||||||
"pretasks": [],
|
"pretasks": [],
|
||||||
"cfg": {"account": "okx", "icon": "okx"},
|
"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",
|
"id": "e2f7fplj30vwcpe0l96",
|
||||||
"task": "DailyCheckIn",
|
"task": "DailyCheckIn",
|
||||||
@ -181,6 +209,8 @@
|
|||||||
"autoclaim": false,
|
"autoclaim": false,
|
||||||
"pretasks": [],
|
"pretasks": [],
|
||||||
"cfg": {"score": [0, 15, 20, 20, 40, 40, 60]},
|
"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]}
|
"params": {"days": 1, "score": [0, 15, 20, 20, 40, 40, 60]}
|
||||||
}, {
|
}, {
|
||||||
"id": "e2f7t4lj30vwcpe0ldr",
|
"id": "e2f7t4lj30vwcpe0ldr",
|
||||||
@ -188,6 +218,7 @@
|
|||||||
"title": "",
|
"title": "",
|
||||||
"desc": "Click here if your are referred by a friend!",
|
"desc": "Click here if your are referred by a friend!",
|
||||||
"type": 1,
|
"type": 1,
|
||||||
|
"category": "Social Tasks",
|
||||||
"show": true,
|
"show": true,
|
||||||
"autoclaim": false,
|
"autoclaim": false,
|
||||||
"pretasks": [],
|
"pretasks": [],
|
||||||
@ -205,9 +236,9 @@
|
|||||||
"category": "CF Pal",
|
"category": "CF Pal",
|
||||||
"autoclaim": false,
|
"autoclaim": false,
|
||||||
"pretasks": [],
|
"pretasks": [],
|
||||||
"cfg": {"address": "0x59e751c2037B710090035B6ea928e0cce80aC03f"},
|
"cfg": {"address": "0xCD4bb3402f1a444a1AF10F31946Ed37DaC0eaC4d"},
|
||||||
"score": 200,
|
"score": 200,
|
||||||
"params": {"address": "0x59e751c2037B710090035B6ea928e0cce80aC03f"}
|
"params": {"address": "0xCD4bb3402f1a444a1AF10F31946Ed37DaC0eaC4d"}
|
||||||
}, {
|
}, {
|
||||||
"id": "e2f7t4lj32vwcpe0ldr",
|
"id": "e2f7t4lj32vwcpe0ldr",
|
||||||
"task": "BurnNft",
|
"task": "BurnNft",
|
||||||
|
@ -4,6 +4,9 @@ export const ZERO_BYTES32 = '0x0000000000000000000000000000000000000000000000000
|
|||||||
|
|
||||||
export const MAX_BATCH_REQ_COUNT = 50
|
export const MAX_BATCH_REQ_COUNT = 50
|
||||||
|
|
||||||
|
export const EMPTY_REWARD = 'empty'
|
||||||
|
export const ITEM_FRAME = 'flame'
|
||||||
|
|
||||||
export const CONFIRM_MAIL_HTML = `
|
export const CONFIRM_MAIL_HTML = `
|
||||||
<h1>有东西需要你确认<h1>
|
<h1>有东西需要你确认<h1>
|
||||||
<p>{{title}}</p>
|
<p>{{title}}</p>
|
||||||
|
23
src/common/LotteryCache.ts
Normal file
23
src/common/LotteryCache.ts
Normal file
@ -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<string, typeof LotteryStats> = 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
28
src/configs/fusion.ts
Normal file
28
src/configs/fusion.ts
Normal file
@ -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,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
40
src/configs/items.ts
Normal file
40
src/configs/items.ts
Normal file
@ -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'
|
||||||
|
},
|
||||||
|
]
|
43
src/configs/lottery.ts
Normal file
43
src/configs/lottery.ts
Normal file
@ -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
|
||||||
|
},
|
||||||
|
|
||||||
|
]
|
||||||
|
}
|
@ -62,7 +62,7 @@ export default class ActivityController extends BaseController {
|
|||||||
let score = parseInt(records[i + 1])
|
let score = parseInt(records[i + 1])
|
||||||
const user = await ActivityUser.findById(id)
|
const user = await ActivityUser.findById(id)
|
||||||
let invite = ''
|
let invite = ''
|
||||||
if (user.inviteUser) {
|
if (user?.inviteUser) {
|
||||||
const inviteUser = await ActivityUser.findById(user.inviteUser)
|
const inviteUser = await ActivityUser.findById(user.inviteUser)
|
||||||
if (inviteUser) {
|
if (inviteUser) {
|
||||||
invite = inviteUser.address
|
invite = inviteUser.address
|
||||||
|
190
src/controllers/lottery.controller.ts
Normal file
190
src/controllers/lottery.controller.ts
Normal file
@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -47,6 +47,9 @@ export default class TasksController extends BaseController {
|
|||||||
if (!allTasks.has(task.task)) {
|
if (!allTasks.has(task.task)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (!task.isVaild()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (task.type === TaskTypeEnum.DAILY ) {
|
if (task.type === TaskTypeEnum.DAILY ) {
|
||||||
let id = `${task.id}:${dateTag}`
|
let id = `${task.id}:${dateTag}`
|
||||||
if (!taskAddedSet.has(id)) {
|
if (!taskAddedSet.has(id)) {
|
||||||
@ -79,10 +82,16 @@ export default class TasksController extends BaseController {
|
|||||||
if (dateTag && currentDateTag !== dateTag) {
|
if (dateTag && currentDateTag !== dateTag) {
|
||||||
throw new ZError(11, 'task date not match')
|
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);
|
let cfg = activity.tasks.find((t: TaskCfg) => t.id === taskId);
|
||||||
if (!cfg) {
|
if (!cfg) {
|
||||||
throw new ZError(12, 'task not found')
|
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) {
|
if (dateTag && cfg.type !== TaskTypeEnum.DAILY) {
|
||||||
throw new ZError(13, 'task is not daily task')
|
throw new ZError(13, 'task is not daily task')
|
||||||
}
|
}
|
||||||
@ -122,6 +131,16 @@ export default class TasksController extends BaseController {
|
|||||||
if (dateTag && currentDateTag !== dateTag) {
|
if (dateTag && currentDateTag !== dateTag) {
|
||||||
throw new ZError(11, 'task date not match')
|
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);
|
let currentTask = user.taskProgress.find((t: TaskStatus) => t.id === task);
|
||||||
if (!currentTask) {
|
if (!currentTask) {
|
||||||
throw new ZError(11, 'task not found')
|
throw new ZError(11, 'task not found')
|
||||||
@ -145,6 +164,17 @@ export default class TasksController extends BaseController {
|
|||||||
const user = req.user;
|
const user = req.user;
|
||||||
const activity = req.activity;
|
const activity = req.activity;
|
||||||
const { task } = req.params;
|
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);
|
let currentTask = user.taskProgress.find((t: TaskStatus) => t.id === task);
|
||||||
if (!currentTask) {
|
if (!currentTask) {
|
||||||
throw new ZError(11, 'task not found')
|
throw new ZError(11, 'task not found')
|
||||||
|
@ -36,8 +36,36 @@ export class TaskCfg {
|
|||||||
show: boolean
|
show: boolean
|
||||||
@prop({ type: mongoose.Schema.Types.Mixed })
|
@prop({ type: mongoose.Schema.Types.Mixed })
|
||||||
cfg: any
|
cfg: any
|
||||||
|
@prop()
|
||||||
|
start?: string
|
||||||
|
@prop()
|
||||||
|
end?: string
|
||||||
@prop({ type: mongoose.Schema.Types.Mixed })
|
@prop({ type: mongoose.Schema.Types.Mixed })
|
||||||
params: any
|
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 {}
|
interface ActivityInfoClass extends Base, TimeStamps {}
|
||||||
@dbconn()
|
@dbconn()
|
||||||
@ -67,11 +95,26 @@ class ActivityInfoClass extends BaseModule {
|
|||||||
@prop()
|
@prop()
|
||||||
public comment?: string
|
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() {
|
public toJson() {
|
||||||
let result = super.toJson()
|
let result = super.toJson()
|
||||||
let tasks = []
|
let tasks = []
|
||||||
|
const now = Date.now()
|
||||||
for (let task of this.tasks) {
|
for (let task of this.tasks) {
|
||||||
if (task.show) {
|
if (task.show && task.isStart()) {
|
||||||
tasks.push({
|
tasks.push({
|
||||||
id: task.id,
|
id: task.id,
|
||||||
task: task.task,
|
task: task.task,
|
||||||
@ -84,6 +127,7 @@ class ActivityInfoClass extends BaseModule {
|
|||||||
category: task.category,
|
category: task.category,
|
||||||
autoclaim: task.autoclaim,
|
autoclaim: task.autoclaim,
|
||||||
cfg: task.cfg,
|
cfg: task.cfg,
|
||||||
|
end: task.isEnd(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
31
src/models/ActivityItem.ts
Normal file
31
src/models/ActivityItem.ts
Normal file
@ -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'] })
|
33
src/models/LotteryRecord.ts
Normal file
33
src/models/LotteryRecord.ts
Normal file
@ -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'] })
|
41
src/models/LotteryStats.ts
Normal file
41
src/models/LotteryStats.ts
Normal file
@ -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'] })
|
57
src/models/chain/CheckIn.ts
Normal file
57
src/models/chain/CheckIn.ts
Normal file
@ -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'],
|
||||||
|
})
|
54
src/models/chain/NftHolder.ts
Normal file
54
src/models/chain/NftHolder.ts
Normal file
@ -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'],
|
||||||
|
})
|
66
src/models/chain/NftTransferEvent.ts
Normal file
66
src/models/chain/NftTransferEvent.ts
Normal file
@ -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'],
|
||||||
|
})
|
@ -1,3 +1,4 @@
|
|||||||
|
import { NftHolder } from "models/chain/NftHolder"
|
||||||
|
|
||||||
export const queryCheckInList = async (address: string, days: string | number | string[], limit: number = 0) => {
|
export const queryCheckInList = async (address: string, days: string | number | string[], limit: number = 0) => {
|
||||||
const url = process.env.CHAIN_SVR + '/task/check_in'
|
const url = process.env.CHAIN_SVR + '/task/check_in'
|
||||||
@ -35,3 +36,10 @@ export const queryBurnNftList = async (address: string, user: string, chain: num
|
|||||||
})
|
})
|
||||||
}).then((res) => res.json())
|
}).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
|
||||||
|
}
|
@ -10,16 +10,16 @@ export default class BurnNft extends ITask {
|
|||||||
static show: boolean = true
|
static show: boolean = true
|
||||||
async execute(data: any) {
|
async execute(data: any) {
|
||||||
const { task } = data
|
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 address = cfg.params.address.toLowerCase()
|
||||||
const chain = parseInt(process.env.CHAIN)
|
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) {
|
if (res.errcode) {
|
||||||
throw new ZError(res.errcode, res.errmsg)
|
throw new ZError(res.errcode, res.errmsg)
|
||||||
}
|
}
|
||||||
const nftList = res.data
|
const nftList = res.data
|
||||||
const localNft = await NftBurnRecord.find({
|
const localNft = await NftBurnRecord.find({
|
||||||
user: this.params.user.id,
|
user: this.user.id,
|
||||||
chain,
|
chain,
|
||||||
address,
|
address,
|
||||||
})
|
})
|
||||||
@ -28,7 +28,7 @@ export default class BurnNft extends ITask {
|
|||||||
let tmpNftSet = new Set();
|
let tmpNftSet = new Set();
|
||||||
for (let nft of localNft) {
|
for (let nft of localNft) {
|
||||||
localNftSet.add(nft.tokenId)
|
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
|
finishAmount += 1
|
||||||
tmpNftSet.add(nft.tokenId)
|
tmpNftSet.add(nft.tokenId)
|
||||||
}
|
}
|
||||||
@ -45,11 +45,11 @@ export default class BurnNft extends ITask {
|
|||||||
}
|
}
|
||||||
for (let finishNft of finishNfts) {
|
for (let finishNft of finishNfts) {
|
||||||
const record = new NftBurnRecord({
|
const record = new NftBurnRecord({
|
||||||
user: this.params.user.id,
|
user: this.user.id,
|
||||||
chain,
|
chain,
|
||||||
address,
|
address,
|
||||||
tokenId: finishNft,
|
tokenId: finishNft,
|
||||||
activity: this.params.activity.id,
|
activity: this.activity.id,
|
||||||
task: task.id
|
task: task.id
|
||||||
})
|
})
|
||||||
await record.save()
|
await record.save()
|
||||||
@ -66,7 +66,7 @@ export default class BurnNft extends ITask {
|
|||||||
task.status = TaskStatusEnum.PART_SUCCESS
|
task.status = TaskStatusEnum.PART_SUCCESS
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await this.params.user.save()
|
await this.user.save()
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
throw new ZError(100, 'save failed')
|
throw new ZError(100, 'save failed')
|
||||||
}
|
}
|
||||||
|
@ -4,8 +4,8 @@ import { TaskStatus, TaskStatusEnum } from "models/ActivityUser";
|
|||||||
import { TaskCfg, TaskTypeEnum } from "models/ActivityInfo";
|
import { TaskCfg, TaskTypeEnum } from "models/ActivityInfo";
|
||||||
import { queryCheckInList, queryCheckInSeq } from "services/chain.svr";
|
import { queryCheckInList, queryCheckInSeq } from "services/chain.svr";
|
||||||
import { updateRankScore } from "services/rank.svr";
|
import { updateRankScore } from "services/rank.svr";
|
||||||
|
import { LotteryCache } from "common/LotteryCache";
|
||||||
|
|
||||||
// TODO:: test
|
|
||||||
/**
|
/**
|
||||||
* 检查每日签到
|
* 检查每日签到
|
||||||
* days
|
* days
|
||||||
@ -17,9 +17,9 @@ export default class DailyCheckIn extends ITask {
|
|||||||
static show: boolean = true
|
static show: boolean = true
|
||||||
|
|
||||||
async execute(data: any) {
|
async execute(data: any) {
|
||||||
const { address } = this.params.user
|
const { address } = this.user
|
||||||
const { task } = data
|
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 days = cfg.params.days || 1
|
||||||
const limit = cfg.params.limit || 0
|
const limit = cfg.params.limit || 0
|
||||||
const res = cfg.type === TaskTypeEnum.DAILY ?
|
const res = cfg.type === TaskTypeEnum.DAILY ?
|
||||||
@ -28,8 +28,6 @@ export default class DailyCheckIn extends ITask {
|
|||||||
if (res.errcode) {
|
if (res.errcode) {
|
||||||
throw new ZError(res.errcode, res.errmsg)
|
throw new ZError(res.errcode, res.errmsg)
|
||||||
}
|
}
|
||||||
let success = false
|
|
||||||
|
|
||||||
|
|
||||||
if ((cfg.type === TaskTypeEnum.DAILY && task.status === TaskStatusEnum.RUNNING && res.data.length >= days)
|
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)) {
|
|| (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.timeFinish = Date.now()
|
||||||
task.data = res.data
|
task.data = res.data
|
||||||
try {
|
try {
|
||||||
await this.params.user.save()
|
await this.user.save()
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
throw new ZError(100, 'save daily checkin failed')
|
throw new ZError(100, 'save daily checkin failed')
|
||||||
}
|
}
|
||||||
@ -55,9 +53,9 @@ export default class DailyCheckIn extends ITask {
|
|||||||
public async claimReward(task: TaskStatus) {
|
public async claimReward(task: TaskStatus) {
|
||||||
// 增加连续签到奖励分
|
// 增加连续签到奖励分
|
||||||
// 请求前7天的签到记录, 往前查找连续签到的记录,
|
// 请求前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) {
|
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(':');
|
const [taskId, dateTag] = task.id.split(':');
|
||||||
let list: { day: string, time: number, count: number }[] = res.data;
|
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 count = list.length > 0 ? list[0].count : 0;
|
||||||
let seq = count % countCfg;
|
let seq = count % countCfg;
|
||||||
let score = cfg.params.score[seq] || 0 + cfg.score;
|
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) {
|
if (user.boost > 1 && Date.now() < user.boostExpire) {
|
||||||
score = Math.floor(score * user.boost)
|
score = Math.floor(score * user.boost)
|
||||||
}
|
}
|
||||||
await updateRankScore({
|
await updateRankScore({
|
||||||
user: this.params.user.id,
|
user: this.user.id,
|
||||||
score: score,
|
score: score,
|
||||||
activity: this.params.user.activity,
|
activity: this.user.activity,
|
||||||
scoreType: cfg.task,
|
scoreType: cfg.task,
|
||||||
scoreParams: {
|
scoreParams: {
|
||||||
taskId: task.id,
|
taskId: task.id,
|
||||||
@ -81,8 +79,15 @@ export default class DailyCheckIn extends ITask {
|
|||||||
boost: user.boost,
|
boost: user.boost,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
await this.claimItem(cfg)
|
||||||
} else {
|
} 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,10 +9,10 @@ export default class DiscordConnect extends ITask {
|
|||||||
static show: boolean = true
|
static show: boolean = true
|
||||||
|
|
||||||
async execute(data: any) {
|
async execute(data: any) {
|
||||||
const { address } = this.params.user
|
const { address } = this.user
|
||||||
const { task } = data
|
const { task } = data
|
||||||
const res = await checkDiscord(address)
|
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) {
|
if (res.status !== 200) {
|
||||||
throw new ZError(11, 'discord check failed')
|
throw new ZError(11, 'discord check failed')
|
||||||
}
|
}
|
||||||
@ -24,10 +24,10 @@ export default class DiscordConnect extends ITask {
|
|||||||
task.status = TaskStatusEnum.SUCCESS
|
task.status = TaskStatusEnum.SUCCESS
|
||||||
task.timeFinish = Date.now()
|
task.timeFinish = Date.now()
|
||||||
task.data = res.data.data
|
task.data = res.data.data
|
||||||
this.params.user.discordId = res.data.data.userid
|
this.user.discordId = res.data.data.userid
|
||||||
this.params.user.discordName = res.data.data.username
|
this.user.discordName = res.data.data.username
|
||||||
try {
|
try {
|
||||||
await this.params.user.save()
|
await this.user.save()
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
throw new ZError(100, 'discord already binded')
|
throw new ZError(100, 'discord already binded')
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ export default class DiscordJoin extends ITask {
|
|||||||
|
|
||||||
async execute(data: any) {
|
async execute(data: any) {
|
||||||
const { task } = data
|
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;
|
let time = cfg.params.time;
|
||||||
if (Date.now() - task.timeStart < time * 1000) {
|
if (Date.now() - task.timeStart < time * 1000) {
|
||||||
throw new ZError(11, 'check discord join failed')
|
throw new ZError(11, 'check discord join failed')
|
||||||
@ -22,7 +22,7 @@ export default class DiscordJoin extends ITask {
|
|||||||
task.timeFinish = Date.now()
|
task.timeFinish = Date.now()
|
||||||
task.data = {}
|
task.data = {}
|
||||||
try {
|
try {
|
||||||
await this.params.user.save()
|
await this.user.save()
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
throw new ZError(100, 'already join discord')
|
throw new ZError(100, 'already join discord')
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ export default class DiscordRole extends ITask {
|
|||||||
static show: boolean = true
|
static show: boolean = true
|
||||||
async execute(data: any) {
|
async execute(data: any) {
|
||||||
const { task } = data
|
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;
|
let time = cfg.params.time;
|
||||||
if (Date.now() - task.timeStart < time * 1000) {
|
if (Date.now() - task.timeStart < time * 1000) {
|
||||||
throw new ZError(11, 'check discord role failed')
|
throw new ZError(11, 'check discord role failed')
|
||||||
@ -21,7 +21,7 @@ export default class DiscordRole extends ITask {
|
|||||||
task.timeFinish = Date.now()
|
task.timeFinish = Date.now()
|
||||||
task.data = {}
|
task.data = {}
|
||||||
try {
|
try {
|
||||||
await this.params.user.save()
|
await this.user.save()
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
throw new ZError(100, 'already acquired discord role')
|
throw new ZError(100, 'already acquired discord role')
|
||||||
}
|
}
|
||||||
|
@ -11,17 +11,17 @@ export default class OkxLogin extends ITask {
|
|||||||
|
|
||||||
async execute(data: any) {
|
async execute(data: any) {
|
||||||
const { task } = data
|
const { task } = data
|
||||||
const { activity } = this.params.user
|
const { activity } = this.user
|
||||||
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 wallet = 'okx';
|
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 ) {
|
if (!record ) {
|
||||||
throw new ZError(11, 'task not finished')
|
throw new ZError(11, 'task not finished')
|
||||||
}
|
}
|
||||||
task.status = TaskStatusEnum.SUCCESS
|
task.status = TaskStatusEnum.SUCCESS
|
||||||
task.timeFinish = Date.now()
|
task.timeFinish = Date.now()
|
||||||
try {
|
try {
|
||||||
await this.params.user.save()
|
await this.user.save()
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
throw new ZError(100, 'discord already binded')
|
throw new ZError(100, 'discord already binded')
|
||||||
}
|
}
|
||||||
|
@ -2,13 +2,16 @@ import { ActivityUser, TaskStatus, TaskStatusEnum } from "models/ActivityUser";
|
|||||||
import { ITask } from "./base/ITask";
|
import { ITask } from "./base/ITask";
|
||||||
import { TaskCfg } from "models/ActivityInfo";
|
import { TaskCfg } from "models/ActivityInfo";
|
||||||
import { updateRankScore } from "services/rank.svr";
|
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) {
|
if (!user.inviteUser || scores.length <= level) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let userSup = await ActivityUser.findById(user.inviteUser)
|
let userSup = await ActivityUser.findById(user.inviteUser)
|
||||||
if (!userSup) {
|
if (!userSup || !userSup.address) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await updateRankScore({
|
await updateRankScore({
|
||||||
@ -21,7 +24,22 @@ const updateInviteScore = async (user: typeof ActivityUser, scores: number[], le
|
|||||||
level
|
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 {
|
export default class ShareCode extends ITask {
|
||||||
@ -30,20 +48,21 @@ export default class ShareCode extends ITask {
|
|||||||
|
|
||||||
async execute(data: any) {
|
async execute(data: any) {
|
||||||
let { task } = data
|
let { 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)
|
||||||
if (!this.params.user.inviteUser) {
|
if (!this.user.inviteUser) {
|
||||||
throw new Error('not finished')
|
throw new Error('not finished')
|
||||||
}
|
}
|
||||||
let scores = cfg.params.score;
|
let scores = cfg.params.score;
|
||||||
|
const items = cfg.params.inviteItems || [];
|
||||||
task.status = TaskStatusEnum.SUCCESS
|
task.status = TaskStatusEnum.SUCCESS
|
||||||
task.timeFinish = Date.now()
|
task.timeFinish = Date.now()
|
||||||
task.data = {}
|
task.data = {}
|
||||||
await this.params.user.save();
|
await this.user.save();
|
||||||
// According to configuration, add score to user who invite current user
|
// According to configuration, add score to user who invite current user
|
||||||
if (cfg.autoclaim) {
|
if (cfg.autoclaim) {
|
||||||
try {
|
try {
|
||||||
await super.claimReward(task);
|
await super.claimReward(task);
|
||||||
await updateInviteScore(this.params.user, scores, 0, task.task)
|
await updateInviteScore(this.user, scores, items, 0, task.task)
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
console.log(err)
|
console.log(err)
|
||||||
}
|
}
|
||||||
@ -53,8 +72,9 @@ export default class ShareCode extends ITask {
|
|||||||
|
|
||||||
public async claimReward(task: TaskStatus) {
|
public async claimReward(task: TaskStatus) {
|
||||||
await super.claimReward(task);
|
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;
|
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")
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -9,7 +9,7 @@ export default class TwitterConnect extends ITask {
|
|||||||
static show: boolean = true
|
static show: boolean = true
|
||||||
|
|
||||||
async execute(data: any) {
|
async execute(data: any) {
|
||||||
let { address } = this.params.user
|
let { address } = this.user
|
||||||
let res = await checkTwitter(address)
|
let res = await checkTwitter(address)
|
||||||
if (res.status !== 200) {
|
if (res.status !== 200) {
|
||||||
throw new ZError(11, 'twitter check failed')
|
throw new ZError(11, 'twitter check failed')
|
||||||
@ -22,14 +22,14 @@ export default class TwitterConnect extends ITask {
|
|||||||
task.status = TaskStatusEnum.SUCCESS
|
task.status = TaskStatusEnum.SUCCESS
|
||||||
task.timeFinish = Date.now()
|
task.timeFinish = Date.now()
|
||||||
task.data = res.data.data
|
task.data = res.data.data
|
||||||
this.params.user.twitterId = res.data.data.userid
|
this.user.twitterId = res.data.data.userid
|
||||||
this.params.user.twitterName = res.data.data.username
|
this.user.twitterName = res.data.data.username
|
||||||
try {
|
try {
|
||||||
await this.params.user.save()
|
await this.user.save()
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
throw new ZError(100, 'twitter already binded')
|
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) {
|
if (cfg.autoclaim) {
|
||||||
try {
|
try {
|
||||||
await this.claimReward(task);
|
await this.claimReward(task);
|
||||||
|
@ -8,7 +8,7 @@ export default class TwitterFollow extends ITask {
|
|||||||
static show: boolean = true
|
static show: boolean = true
|
||||||
async execute(data: any) {
|
async execute(data: any) {
|
||||||
const { task } = data
|
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;
|
let time = cfg.params.time;
|
||||||
if (Date.now() - task.timeStart < time * 1000) {
|
if (Date.now() - task.timeStart < time * 1000) {
|
||||||
throw new ZError(11, 'follow failed')
|
throw new ZError(11, 'follow failed')
|
||||||
@ -21,7 +21,7 @@ export default class TwitterFollow extends ITask {
|
|||||||
task.timeFinish = Date.now()
|
task.timeFinish = Date.now()
|
||||||
task.data = {}
|
task.data = {}
|
||||||
try {
|
try {
|
||||||
await this.params.user.save()
|
await this.user.save()
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
throw new ZError(100, 'save failed')
|
throw new ZError(100, 'save failed')
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ export default class TwitterRetweet extends ITask {
|
|||||||
static show: boolean = true
|
static show: boolean = true
|
||||||
async execute(data: any) {
|
async execute(data: any) {
|
||||||
const { task } = data
|
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;
|
let time = cfg.params.time;
|
||||||
if (Date.now() - task.timeStart < time * 1000) {
|
if (Date.now() - task.timeStart < time * 1000) {
|
||||||
throw new ZError(11, 'retweet failed')
|
throw new ZError(11, 'retweet failed')
|
||||||
@ -20,7 +20,7 @@ export default class TwitterRetweet extends ITask {
|
|||||||
task.timeFinish = Date.now()
|
task.timeFinish = Date.now()
|
||||||
task.data = {}
|
task.data = {}
|
||||||
try {
|
try {
|
||||||
await this.params.user.save()
|
await this.user.save()
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
throw new ZError(100, 'save failed')
|
throw new ZError(100, 'save failed')
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ export default class YoutubeFollow extends ITask {
|
|||||||
static show: boolean = true
|
static show: boolean = true
|
||||||
async execute(data: any) {
|
async execute(data: any) {
|
||||||
const { task } = data
|
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;
|
let time = cfg.params.time;
|
||||||
if (Date.now() - task.timeStart < time * 1000) {
|
if (Date.now() - task.timeStart < time * 1000) {
|
||||||
throw new ZError(11, 'follow failed')
|
throw new ZError(11, 'follow failed')
|
||||||
@ -21,7 +21,7 @@ export default class YoutubeFollow extends ITask {
|
|||||||
task.timeFinish = Date.now()
|
task.timeFinish = Date.now()
|
||||||
task.data = {}
|
task.data = {}
|
||||||
try {
|
try {
|
||||||
await this.params.user.save()
|
await this.user.save()
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
throw new ZError(100, 'save failed')
|
throw new ZError(100, 'save failed')
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ export default class YoutubePost extends ITask {
|
|||||||
static show: boolean = true
|
static show: boolean = true
|
||||||
async execute(data: any) {
|
async execute(data: any) {
|
||||||
const { task } = data
|
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;
|
let time = cfg.params.time;
|
||||||
if (Date.now() - task.timeStart < time * 1000) {
|
if (Date.now() - task.timeStart < time * 1000) {
|
||||||
throw new ZError(11, 'post failed')
|
throw new ZError(11, 'post failed')
|
||||||
@ -20,7 +20,7 @@ export default class YoutubePost extends ITask {
|
|||||||
task.timeFinish = Date.now()
|
task.timeFinish = Date.now()
|
||||||
task.data = {}
|
task.data = {}
|
||||||
try {
|
try {
|
||||||
await this.params.user.save()
|
await this.user.save()
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
throw new ZError(100, 'save failed')
|
throw new ZError(100, 'save failed')
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { TaskCfg } from "models/ActivityInfo"
|
import { ActivityInfo, TaskCfg } from "models/ActivityInfo"
|
||||||
import { TaskStatus, TaskStatusEnum } from "models/ActivityUser"
|
import { ActivityItem } from "models/ActivityItem"
|
||||||
|
import { ActivityUser, TaskStatus, TaskStatusEnum } from "models/ActivityUser"
|
||||||
import { updateRankScore } from "services/rank.svr"
|
import { updateRankScore } from "services/rank.svr"
|
||||||
|
|
||||||
export abstract class ITask {
|
export abstract class ITask {
|
||||||
@ -7,17 +8,21 @@ export abstract class ITask {
|
|||||||
static show: boolean = true
|
static show: boolean = true
|
||||||
static auto: boolean = false
|
static auto: boolean = false
|
||||||
|
|
||||||
params: any
|
user: typeof ActivityUser
|
||||||
constructor(params: any) {
|
activity: typeof ActivityInfo
|
||||||
|
|
||||||
|
|
||||||
|
constructor({user, activity}: {user: typeof ActivityUser, activity: typeof ActivityInfo}) {
|
||||||
// do nothing
|
// do nothing
|
||||||
this.params = params
|
this.user = user
|
||||||
|
this.activity = activity
|
||||||
}
|
}
|
||||||
abstract execute(data: any): Promise<boolean>
|
abstract execute(data: any): Promise<boolean>
|
||||||
|
|
||||||
public async claimReward(task: any) {
|
public async claimReward(task: any) {
|
||||||
const user = this.params.user
|
const user = this.user
|
||||||
const [taskId, dateTag] = task.id.split(':');
|
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) {
|
if (!cfg.score) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -48,6 +53,17 @@ export abstract class ITask {
|
|||||||
task.status = TaskStatusEnum.CLAIMED
|
task.status = TaskStatusEnum.CLAIMED
|
||||||
task.timeClaim = Date.now()
|
task.timeClaim = Date.now()
|
||||||
}
|
}
|
||||||
|
await this.claimItem(cfg)
|
||||||
await user.save()
|
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()})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user