koishi-plugin-jrys-plus 1.0.1 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.d.ts +4 -70
- package/lib/index.js +11 -28
- package/lib/ranks.d.ts +2 -3
- package/lib/ranks.js +13 -56
- package/lib/signin.d.ts +8 -15
- package/lib/signin.js +5 -35
- package/lib/templates/fortune.html +36 -23
- package/lib/templates/rank-card.html +5 -60
- package/package.json +1 -1
package/lib/index.d.ts
CHANGED
|
@@ -1,51 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* koishi-plugin-jrys-plus
|
|
3
|
-
* 今日运势签到 +
|
|
3
|
+
* 今日运势签到 + 签到天数排行榜
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
* -
|
|
7
|
-
* -
|
|
8
|
-
* - 日期问候模块移除半透明背景
|
|
9
|
-
* - 仅保留签到天数排行榜
|
|
5
|
+
* - 彩色星星替代运势文字
|
|
6
|
+
* - 无经验/等级系统
|
|
7
|
+
* - 仅签到天数排行榜
|
|
10
8
|
*/
|
|
11
9
|
import { Context, Schema } from 'koishi';
|
|
12
|
-
import * as si from './signin';
|
|
13
10
|
import { RollEvent } from './event';
|
|
14
11
|
export declare const name = "jrys-plus";
|
|
15
12
|
export interface Config {
|
|
16
13
|
imgUrl: string;
|
|
17
14
|
signExp: [number, number];
|
|
18
|
-
levelSet: si.LevelInfo[];
|
|
19
|
-
fortuneSet: si.FortuneInfo[];
|
|
20
15
|
event: RollEvent[];
|
|
21
16
|
signCommand: string;
|
|
22
17
|
imageMode: boolean;
|
|
23
18
|
limit: number;
|
|
24
19
|
borderwidth: number;
|
|
25
|
-
pre_next_LevelDisplay: boolean;
|
|
26
20
|
}
|
|
27
21
|
export declare const Config: Schema<Schemastery.ObjectS<{
|
|
28
22
|
imgUrl: Schema<string, string>;
|
|
29
23
|
signExp: Schema<[number?, number?, ...any[]], [number?, number?, ...any[]]>;
|
|
30
24
|
}> | Schemastery.ObjectS<{
|
|
31
|
-
levelSet: Schema<Schemastery.ObjectS<{
|
|
32
|
-
level: Schema<number, number>;
|
|
33
|
-
levelExp: Schema<number, number>;
|
|
34
|
-
levelName: Schema<string, string>;
|
|
35
|
-
levelColor: Schema<string, string>;
|
|
36
|
-
}>[], Schemastery.ObjectT<{
|
|
37
|
-
level: Schema<number, number>;
|
|
38
|
-
levelExp: Schema<number, number>;
|
|
39
|
-
levelName: Schema<string, string>;
|
|
40
|
-
levelColor: Schema<string, string>;
|
|
41
|
-
}>[]>;
|
|
42
|
-
fortuneSet: Schema<Schemastery.ObjectS<{
|
|
43
|
-
luck: Schema<number, number>;
|
|
44
|
-
desc: Schema<string, string>;
|
|
45
|
-
}>[], Schemastery.ObjectT<{
|
|
46
|
-
luck: Schema<number, number>;
|
|
47
|
-
desc: Schema<string, string>;
|
|
48
|
-
}>[]>;
|
|
49
25
|
event: Schema<Schemastery.ObjectS<{
|
|
50
26
|
name: Schema<string, string>;
|
|
51
27
|
good: Schema<string, string>;
|
|
@@ -60,21 +36,10 @@ export declare const Config: Schema<Schemastery.ObjectS<{
|
|
|
60
36
|
imageMode: Schema<boolean, boolean>;
|
|
61
37
|
limit: Schema<number, number>;
|
|
62
38
|
borderwidth: Schema<number, number>;
|
|
63
|
-
pre_next_LevelDisplay: Schema<boolean, boolean>;
|
|
64
39
|
}>, {
|
|
65
40
|
imgUrl: string;
|
|
66
41
|
signExp: [number?, number?, ...any[]];
|
|
67
42
|
} & import("cosmokit").Dict & {
|
|
68
|
-
levelSet: Schemastery.ObjectT<{
|
|
69
|
-
level: Schema<number, number>;
|
|
70
|
-
levelExp: Schema<number, number>;
|
|
71
|
-
levelName: Schema<string, string>;
|
|
72
|
-
levelColor: Schema<string, string>;
|
|
73
|
-
}>[];
|
|
74
|
-
fortuneSet: Schemastery.ObjectT<{
|
|
75
|
-
luck: Schema<number, number>;
|
|
76
|
-
desc: Schema<string, string>;
|
|
77
|
-
}>[];
|
|
78
43
|
event: Schemastery.ObjectT<{
|
|
79
44
|
name: Schema<string, string>;
|
|
80
45
|
good: Schema<string, string>;
|
|
@@ -85,7 +50,6 @@ export declare const Config: Schema<Schemastery.ObjectS<{
|
|
|
85
50
|
imageMode: boolean;
|
|
86
51
|
limit: number;
|
|
87
52
|
borderwidth: number;
|
|
88
|
-
pre_next_LevelDisplay: boolean;
|
|
89
53
|
}>;
|
|
90
54
|
export declare const inject: {
|
|
91
55
|
required: string[];
|
|
@@ -99,24 +63,6 @@ declare const _default: {
|
|
|
99
63
|
imgUrl: Schema<string, string>;
|
|
100
64
|
signExp: Schema<[number?, number?, ...any[]], [number?, number?, ...any[]]>;
|
|
101
65
|
}> | Schemastery.ObjectS<{
|
|
102
|
-
levelSet: Schema<Schemastery.ObjectS<{
|
|
103
|
-
level: Schema<number, number>;
|
|
104
|
-
levelExp: Schema<number, number>;
|
|
105
|
-
levelName: Schema<string, string>;
|
|
106
|
-
levelColor: Schema<string, string>;
|
|
107
|
-
}>[], Schemastery.ObjectT<{
|
|
108
|
-
level: Schema<number, number>;
|
|
109
|
-
levelExp: Schema<number, number>;
|
|
110
|
-
levelName: Schema<string, string>;
|
|
111
|
-
levelColor: Schema<string, string>;
|
|
112
|
-
}>[]>;
|
|
113
|
-
fortuneSet: Schema<Schemastery.ObjectS<{
|
|
114
|
-
luck: Schema<number, number>;
|
|
115
|
-
desc: Schema<string, string>;
|
|
116
|
-
}>[], Schemastery.ObjectT<{
|
|
117
|
-
luck: Schema<number, number>;
|
|
118
|
-
desc: Schema<string, string>;
|
|
119
|
-
}>[]>;
|
|
120
66
|
event: Schema<Schemastery.ObjectS<{
|
|
121
67
|
name: Schema<string, string>;
|
|
122
68
|
good: Schema<string, string>;
|
|
@@ -131,21 +77,10 @@ declare const _default: {
|
|
|
131
77
|
imageMode: Schema<boolean, boolean>;
|
|
132
78
|
limit: Schema<number, number>;
|
|
133
79
|
borderwidth: Schema<number, number>;
|
|
134
|
-
pre_next_LevelDisplay: Schema<boolean, boolean>;
|
|
135
80
|
}>, {
|
|
136
81
|
imgUrl: string;
|
|
137
82
|
signExp: [number?, number?, ...any[]];
|
|
138
83
|
} & import("cosmokit").Dict & {
|
|
139
|
-
levelSet: Schemastery.ObjectT<{
|
|
140
|
-
level: Schema<number, number>;
|
|
141
|
-
levelExp: Schema<number, number>;
|
|
142
|
-
levelName: Schema<string, string>;
|
|
143
|
-
levelColor: Schema<string, string>;
|
|
144
|
-
}>[];
|
|
145
|
-
fortuneSet: Schemastery.ObjectT<{
|
|
146
|
-
luck: Schema<number, number>;
|
|
147
|
-
desc: Schema<string, string>;
|
|
148
|
-
}>[];
|
|
149
84
|
event: Schemastery.ObjectT<{
|
|
150
85
|
name: Schema<string, string>;
|
|
151
86
|
good: Schema<string, string>;
|
|
@@ -156,7 +91,6 @@ declare const _default: {
|
|
|
156
91
|
imageMode: boolean;
|
|
157
92
|
limit: number;
|
|
158
93
|
borderwidth: number;
|
|
159
|
-
pre_next_LevelDisplay: boolean;
|
|
160
94
|
}>;
|
|
161
95
|
};
|
|
162
96
|
export default _default;
|
package/lib/index.js
CHANGED
|
@@ -40,13 +40,11 @@ exports.inject = exports.Config = exports.name = void 0;
|
|
|
40
40
|
exports.apply = apply;
|
|
41
41
|
/**
|
|
42
42
|
* koishi-plugin-jrys-plus
|
|
43
|
-
* 今日运势签到 +
|
|
43
|
+
* 今日运势签到 + 签到天数排行榜
|
|
44
44
|
*
|
|
45
|
-
*
|
|
46
|
-
* -
|
|
47
|
-
* -
|
|
48
|
-
* - 日期问候模块移除半透明背景
|
|
49
|
-
* - 仅保留签到天数排行榜
|
|
45
|
+
* - 彩色星星替代运势文字
|
|
46
|
+
* - 无经验/等级系统
|
|
47
|
+
* - 仅签到天数排行榜
|
|
50
48
|
*/
|
|
51
49
|
const koishi_1 = require("koishi");
|
|
52
50
|
const url_1 = require("url");
|
|
@@ -64,22 +62,10 @@ exports.Config = koishi_1.Schema.intersect([
|
|
|
64
62
|
.description('随机横图api或者本地图片目录路径。填 URL 则直接用;填本地路径则随机取目录内图片。')
|
|
65
63
|
.required(),
|
|
66
64
|
signExp: koishi_1.Schema.tuple([Number, Number])
|
|
67
|
-
.description('
|
|
65
|
+
.description('签到获得经验范围(仅用于后端累计,不在卡片展示)')
|
|
68
66
|
.default([1, 100]),
|
|
69
67
|
}).description('基础设置'),
|
|
70
68
|
koishi_1.Schema.object({
|
|
71
|
-
levelSet: koishi_1.Schema.array(koishi_1.Schema.object({
|
|
72
|
-
level: koishi_1.Schema.number().description('等级'),
|
|
73
|
-
levelExp: koishi_1.Schema.number().description('等级最低经验'),
|
|
74
|
-
levelName: koishi_1.Schema.string().description('等级名称'),
|
|
75
|
-
levelColor: koishi_1.Schema.string().role('color').description('等级颜色'),
|
|
76
|
-
})).role('table').default(si.defaultLevelInfo)
|
|
77
|
-
.description('经验等级设置(升序,最低等级经验必须为0)'),
|
|
78
|
-
fortuneSet: koishi_1.Schema.array(koishi_1.Schema.object({
|
|
79
|
-
luck: koishi_1.Schema.number().description('每级最低运势'),
|
|
80
|
-
desc: koishi_1.Schema.string().description('运势描述'),
|
|
81
|
-
})).role('table').default(si.defaultFortuneInfo)
|
|
82
|
-
.description('运势值描述(升序,最低一级必须为0,描述最长14个中文字符)'),
|
|
83
69
|
event: koishi_1.Schema.array(koishi_1.Schema.object({
|
|
84
70
|
name: koishi_1.Schema.string().description('事件名称'),
|
|
85
71
|
good: koishi_1.Schema.string().description('好的结局'),
|
|
@@ -92,7 +78,6 @@ exports.Config = koishi_1.Schema.intersect([
|
|
|
92
78
|
imageMode: koishi_1.Schema.boolean().description('排行榜是否使用图片模式(需要 puppeteer)').default(true),
|
|
93
79
|
limit: koishi_1.Schema.number().description('排行榜显示的最大条目数').min(1).max(100).default(10),
|
|
94
80
|
borderwidth: koishi_1.Schema.number().description('文本模式边框宽度').default(14),
|
|
95
|
-
pre_next_LevelDisplay: koishi_1.Schema.boolean().description('排行榜中显示前后等级信息').default(true),
|
|
96
81
|
}).description('排行榜设置'),
|
|
97
82
|
]);
|
|
98
83
|
exports.inject = {
|
|
@@ -128,12 +113,14 @@ async function fetchHitokoto() {
|
|
|
128
113
|
/* ── 主入口 ── */
|
|
129
114
|
function apply(ctx, config) {
|
|
130
115
|
si.initDatabase(ctx);
|
|
131
|
-
const
|
|
116
|
+
const db = ctx.database;
|
|
117
|
+
const puppeteer = ctx.puppeteer;
|
|
118
|
+
const signin = new si.Signin(db, config);
|
|
132
119
|
const jrys = new roll_1.Jrys();
|
|
133
120
|
// 合并事件
|
|
134
121
|
const eventJson = [...event_1.defaultEventJson, ...config.event];
|
|
135
122
|
// 注册排行榜
|
|
136
|
-
(0, ranks_1.registerRanks)(ctx,
|
|
123
|
+
(0, ranks_1.registerRanks)(ctx, db, config);
|
|
137
124
|
/* ── 运势签到命令 ── */
|
|
138
125
|
ctx.command('jrys', '今日运势')
|
|
139
126
|
.userFields(['id', 'name'])
|
|
@@ -147,11 +134,9 @@ function apply(ctx, config) {
|
|
|
147
134
|
return '今天已经签到过了哦~';
|
|
148
135
|
const month = (date.getMonth() + 1).toString().padStart(2, '0');
|
|
149
136
|
const day = date.getDate().toString().padStart(2, '0');
|
|
150
|
-
const luckInfo = signin.getFortuneInfo(luck);
|
|
151
137
|
const [gd1, gd2, bd1, bd2] = await jrys.getRandomObjects(eventJson, session.user.id);
|
|
152
138
|
const hitokoto = await fetchHitokoto();
|
|
153
139
|
const greeting = signin.getGreeting(date.getHours());
|
|
154
|
-
const levelinfo = signin.getLevelInfo(sign.allExp);
|
|
155
140
|
// 背景图
|
|
156
141
|
let bgUrl;
|
|
157
142
|
if (/^https?:\/\//i.test(config.imgUrl)) {
|
|
@@ -176,15 +161,13 @@ function apply(ctx, config) {
|
|
|
176
161
|
.replace('{{DAY}}', day)
|
|
177
162
|
.replace('{{HITOKOTO}}', hitokoto)
|
|
178
163
|
.replace('{{NAME}}', name)
|
|
179
|
-
.replace('{{LEVEL_NAME}}', levelinfo.levelInfo.levelName)
|
|
180
|
-
.replace('{{LEVEL_COLOR}}', levelinfo.levelInfo.levelColor)
|
|
181
164
|
.replace('{{LUCK}}', String(luck))
|
|
182
|
-
.replace('{{
|
|
165
|
+
.replace('{{UID}}', String(session.user.id))
|
|
183
166
|
.replace('{{GOODDO}}', gooddo)
|
|
184
167
|
.replace('{{BADDO}}', baddo);
|
|
185
168
|
const outPath = path_1.default.resolve(__dirname, 'templates', '_fortune_render.html');
|
|
186
169
|
fs_1.default.writeFileSync(outPath, html);
|
|
187
|
-
page = await
|
|
170
|
+
page = await puppeteer.page();
|
|
188
171
|
await page.setViewport({ width: 600, height: 1080 * 2 });
|
|
189
172
|
await page.goto(`file:///${outPath}`);
|
|
190
173
|
await page.waitForSelector('#body', { timeout: 10000 });
|
package/lib/ranks.d.ts
CHANGED
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
import { Context } from 'koishi';
|
|
2
|
-
|
|
3
|
-
export declare function registerRanks(ctx: Context, config: any, getLevelConfig: () => LevelInfo[]): void;
|
|
1
|
+
import { Context, Database } from 'koishi';
|
|
2
|
+
export declare function registerRanks(ctx: Context, db: Database<any>, config: any): void;
|
package/lib/ranks.js
CHANGED
|
@@ -21,77 +21,35 @@ async function resolveTemplatePath() {
|
|
|
21
21
|
}
|
|
22
22
|
throw new Error('未找到 rank-card.html 模板文件');
|
|
23
23
|
}
|
|
24
|
-
/* ── 等级查询 ── */
|
|
25
|
-
const DEFAULT_LEVEL = { level: 0, levelExp: 0, levelName: '无等级', levelColor: '#666666' };
|
|
26
|
-
function getLevelInfo(exp, levels) {
|
|
27
|
-
if (!levels?.length)
|
|
28
|
-
return DEFAULT_LEVEL;
|
|
29
|
-
const sorted = [...levels].sort((a, b) => b.levelExp - a.levelExp);
|
|
30
|
-
return sorted.find(l => exp >= l.levelExp) || sorted[sorted.length - 1];
|
|
31
|
-
}
|
|
32
24
|
/* ── 文本渲染 ── */
|
|
33
|
-
function renderSignText(users,
|
|
25
|
+
function renderSignText(users, config) {
|
|
34
26
|
const divider = '┏' + '—'.repeat(config.borderwidth) + '┓';
|
|
35
27
|
const midDivider = '┣' + '—'.repeat(config.borderwidth) + '┫';
|
|
36
28
|
const endDivider = '┗' + '—'.repeat(config.borderwidth) + '┛';
|
|
37
29
|
const header = [divider, `┃ 🏆 签到排行榜 TOP.${config.limit} `, midDivider].join('\n');
|
|
38
30
|
const rankings = users.map((user, index) => {
|
|
39
31
|
const medal = index < 3 ? ['👑', '⭐', '✧'][index] : '•';
|
|
40
|
-
|
|
41
|
-
if (config.pre_next_LevelDisplay && levelConfig.length) {
|
|
42
|
-
const sorted = [...levelConfig].sort((a, b) => a.levelExp - b.levelExp);
|
|
43
|
-
const cur = getLevelInfo(user.exp, levelConfig);
|
|
44
|
-
const idx = sorted.findIndex(l => l.levelExp === cur.levelExp);
|
|
45
|
-
const prev = sorted[idx - 1]?.levelName;
|
|
46
|
-
const next = sorted[idx + 1]?.levelName;
|
|
47
|
-
let line = '┃ ✨';
|
|
48
|
-
if (prev)
|
|
49
|
-
line += `${prev} → `;
|
|
50
|
-
line += `「${cur.levelName}」`;
|
|
51
|
-
if (next)
|
|
52
|
-
line += ` → ${next}`;
|
|
53
|
-
lines.push(line);
|
|
54
|
-
}
|
|
32
|
+
const lines = [`┃ ${medal} ${index + 1}. ${user.displayName}`, `┃ 📅${user.signCount.toLocaleString()} 天`];
|
|
55
33
|
return lines.join('\n');
|
|
56
34
|
}).join('\n\n');
|
|
57
35
|
return [header, rankings, endDivider].join('\n');
|
|
58
36
|
}
|
|
59
37
|
/* ── 图片渲染 ── */
|
|
60
|
-
async function renderRankImage(ctx, users, totalUsers, limit
|
|
38
|
+
async function renderRankImage(ctx, users, totalUsers, limit) {
|
|
61
39
|
try {
|
|
62
40
|
const path = await resolveTemplatePath();
|
|
63
41
|
let template = await fs_1.promises.readFile(path, 'utf-8');
|
|
64
|
-
const levelConfig = getLevelConfig();
|
|
65
42
|
const data = {
|
|
66
43
|
type: 'sign',
|
|
67
44
|
limit,
|
|
68
45
|
channelName: '当前频道',
|
|
69
46
|
totalUsers,
|
|
70
47
|
updateTime: new Date().toLocaleString('zh-CN'),
|
|
71
|
-
users: users.map(user => {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const nextObj = sorted[idx + 1];
|
|
77
|
-
const nextName = nextObj?.levelName;
|
|
78
|
-
let levelProgression = '';
|
|
79
|
-
if (prev)
|
|
80
|
-
levelProgression += `${prev} → `;
|
|
81
|
-
levelProgression += `「${cur.levelName}」`;
|
|
82
|
-
if (nextName)
|
|
83
|
-
levelProgression += ` → ${nextName}`;
|
|
84
|
-
return {
|
|
85
|
-
displayName: user.displayName,
|
|
86
|
-
originalId: user.name,
|
|
87
|
-
value: user.signCount,
|
|
88
|
-
levelName: cur.levelName,
|
|
89
|
-
levelColor: cur.levelColor,
|
|
90
|
-
currentLevelExp: cur.levelExp,
|
|
91
|
-
nextLevelExp: nextObj?.levelExp ?? null,
|
|
92
|
-
levelProgression,
|
|
93
|
-
};
|
|
94
|
-
}),
|
|
48
|
+
users: users.map(user => ({
|
|
49
|
+
displayName: user.displayName,
|
|
50
|
+
originalId: user.name,
|
|
51
|
+
value: user.signCount,
|
|
52
|
+
})),
|
|
95
53
|
};
|
|
96
54
|
template = template.replace('{{DATA}}', JSON.stringify(data));
|
|
97
55
|
const page = await ctx.puppeteer.page();
|
|
@@ -113,13 +71,12 @@ async function renderRankImage(ctx, users, totalUsers, limit, getLevelConfig) {
|
|
|
113
71
|
}
|
|
114
72
|
}
|
|
115
73
|
/* ── 注册排行命令 ── */
|
|
116
|
-
function registerRanks(ctx,
|
|
117
|
-
const logger = ctx.logger('jrys-plus');
|
|
74
|
+
function registerRanks(ctx, db, config) {
|
|
118
75
|
function canUseImage() {
|
|
119
76
|
return config.imageMode && !!ctx.puppeteer;
|
|
120
77
|
}
|
|
121
78
|
async function getRankedUsers() {
|
|
122
|
-
const all = await
|
|
79
|
+
const all = await db.get('jrys', {}, { sort: { signCount: 'desc' } });
|
|
123
80
|
if (!all.length)
|
|
124
81
|
return null;
|
|
125
82
|
const users = all.slice(0, config.limit).map(u => ({
|
|
@@ -136,11 +93,11 @@ function registerRanks(ctx, config, getLevelConfig) {
|
|
|
136
93
|
if (!users.length)
|
|
137
94
|
return '当前频道暂无数据';
|
|
138
95
|
if (canUseImage()) {
|
|
139
|
-
const total = (await
|
|
140
|
-
const img = await renderRankImage(ctx, users, total, config.limit
|
|
96
|
+
const total = (await db.get('jrys', {})).length;
|
|
97
|
+
const img = await renderRankImage(ctx, users, total, config.limit);
|
|
141
98
|
if (img)
|
|
142
99
|
return img;
|
|
143
100
|
}
|
|
144
|
-
return renderSignText(users,
|
|
101
|
+
return renderSignText(users, config);
|
|
145
102
|
});
|
|
146
103
|
}
|
package/lib/signin.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Context } from 'koishi';
|
|
1
|
+
import { Context, Database } from 'koishi';
|
|
2
2
|
declare module 'koishi' {
|
|
3
3
|
interface Tables {
|
|
4
4
|
jrys: _UserFortune;
|
|
@@ -35,29 +35,22 @@ export declare const defaultFortuneInfo: FortuneInfo[];
|
|
|
35
35
|
export declare function initDatabase(ctx: Context): void;
|
|
36
36
|
export interface SigninConfig {
|
|
37
37
|
signExp: [number, number];
|
|
38
|
-
levelSet: LevelInfo[];
|
|
39
|
-
fortuneSet: FortuneInfo[];
|
|
40
38
|
}
|
|
41
39
|
export declare class Signin {
|
|
42
|
-
private
|
|
40
|
+
private db;
|
|
43
41
|
private cfg;
|
|
44
|
-
constructor(
|
|
42
|
+
constructor(db: Database<any>, cfg: SigninConfig);
|
|
45
43
|
/** 执行签到。返回 0=成功, 1=已签到 */
|
|
46
44
|
callSignin(uid: number, userid: string, luck: number): Promise<{
|
|
47
|
-
status: number;
|
|
48
|
-
allExp: number;
|
|
49
|
-
signTime: Date;
|
|
50
|
-
count: number;
|
|
51
|
-
} | {
|
|
52
45
|
status: number;
|
|
53
46
|
allExp?: undefined;
|
|
54
47
|
signTime?: undefined;
|
|
55
48
|
count?: undefined;
|
|
49
|
+
} | {
|
|
50
|
+
status: number;
|
|
51
|
+
allExp: any;
|
|
52
|
+
signTime: Date;
|
|
53
|
+
count: any;
|
|
56
54
|
}>;
|
|
57
|
-
getLevelInfo(exp: number): {
|
|
58
|
-
levelInfo: LevelInfo;
|
|
59
|
-
nextExp: string | number;
|
|
60
|
-
};
|
|
61
|
-
getFortuneInfo(luck: number): string;
|
|
62
55
|
getGreeting(hour: number): string;
|
|
63
56
|
}
|
package/lib/signin.js
CHANGED
|
@@ -59,24 +59,21 @@ function initDatabase(ctx) {
|
|
|
59
59
|
signCount: 'unsigned',
|
|
60
60
|
});
|
|
61
61
|
}
|
|
62
|
-
/* ── 签到逻辑(无 monetary 依赖) ── */
|
|
63
|
-
const roll_1 = require("./roll");
|
|
64
62
|
class Signin {
|
|
65
|
-
constructor(
|
|
66
|
-
this.
|
|
63
|
+
constructor(db, cfg) {
|
|
64
|
+
this.db = db;
|
|
67
65
|
this.cfg = cfg;
|
|
68
66
|
}
|
|
69
67
|
/** 执行签到。返回 0=成功, 1=已签到 */
|
|
70
68
|
async callSignin(uid, userid, luck) {
|
|
71
69
|
const date = new Date();
|
|
72
|
-
const roll = new roll_1.Jrys();
|
|
73
70
|
// 经验值仅用于后端等级/排行计算,不在签到卡上展示
|
|
74
71
|
const exp = Math.round((Math.random() * 0.5 + luck / 200) *
|
|
75
72
|
(this.cfg.signExp[1] - this.cfg.signExp[0])) + this.cfg.signExp[0];
|
|
76
|
-
const userData = await this.
|
|
73
|
+
const userData = await this.db.get('jrys', { id: uid });
|
|
77
74
|
if (userData.length === 0) {
|
|
78
75
|
const accExp = exp;
|
|
79
|
-
await this.
|
|
76
|
+
await this.db.create('jrys', {
|
|
80
77
|
id: uid,
|
|
81
78
|
name: userid,
|
|
82
79
|
time: date,
|
|
@@ -90,7 +87,7 @@ class Signin {
|
|
|
90
87
|
}
|
|
91
88
|
const accExp = userData[0].exp + exp;
|
|
92
89
|
const accCount = userData[0].signCount + 1;
|
|
93
|
-
await this.
|
|
90
|
+
await this.db.set('jrys', { id: uid }, {
|
|
94
91
|
name: userid,
|
|
95
92
|
time: date,
|
|
96
93
|
exp: accExp,
|
|
@@ -98,33 +95,6 @@ class Signin {
|
|
|
98
95
|
});
|
|
99
96
|
return { status: 0, allExp: accExp, signTime: date, count: accCount };
|
|
100
97
|
}
|
|
101
|
-
getLevelInfo(exp) {
|
|
102
|
-
let index = 0;
|
|
103
|
-
for (let i = 0; i < this.cfg.levelSet.length; i++) {
|
|
104
|
-
if (exp >= this.cfg.levelSet[i].levelExp)
|
|
105
|
-
index++;
|
|
106
|
-
else
|
|
107
|
-
break;
|
|
108
|
-
}
|
|
109
|
-
let nExp;
|
|
110
|
-
if (index >= this.cfg.levelSet.length)
|
|
111
|
-
nExp = '???';
|
|
112
|
-
else
|
|
113
|
-
nExp = this.cfg.levelSet[index].levelExp;
|
|
114
|
-
index--;
|
|
115
|
-
return { levelInfo: this.cfg.levelSet[index], nextExp: nExp };
|
|
116
|
-
}
|
|
117
|
-
getFortuneInfo(luck) {
|
|
118
|
-
let index = 0;
|
|
119
|
-
for (let i = 0; i < this.cfg.fortuneSet.length; i++) {
|
|
120
|
-
if (luck >= this.cfg.fortuneSet[i].luck)
|
|
121
|
-
index++;
|
|
122
|
-
else
|
|
123
|
-
break;
|
|
124
|
-
}
|
|
125
|
-
index--;
|
|
126
|
-
return this.cfg.fortuneSet[index].desc;
|
|
127
|
-
}
|
|
128
98
|
getGreeting(hour) {
|
|
129
99
|
const g = timeGreetings.find(t => hour >= t.range[0] && hour < t.range[1]);
|
|
130
100
|
return g ? g.message : '你好';
|
|
@@ -64,25 +64,23 @@
|
|
|
64
64
|
}
|
|
65
65
|
.signin {
|
|
66
66
|
color: #838383;
|
|
67
|
-
margin: 0 0
|
|
67
|
+
margin: 0 0 24px 0;
|
|
68
68
|
font-size: 20px;
|
|
69
69
|
font-weight: 500;
|
|
70
|
+
text-align: center;
|
|
70
71
|
}
|
|
71
|
-
|
|
72
|
+
|
|
73
|
+
/* ── 彩色星星 ── */
|
|
74
|
+
.stars {
|
|
72
75
|
display: flex;
|
|
76
|
+
flex-wrap: wrap;
|
|
73
77
|
justify-content: center;
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
margin-bottom: 10px;
|
|
78
|
+
gap: 8px;
|
|
79
|
+
margin: 10px 0 20px 0;
|
|
77
80
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
display: flex;
|
|
82
|
-
flex-direction: row;
|
|
83
|
-
justify-content: space-between;
|
|
84
|
-
align-items: center;
|
|
85
|
-
margin: 10px 5px 0 0;
|
|
81
|
+
.stars span {
|
|
82
|
+
font-size: 36px;
|
|
83
|
+
line-height: 1;
|
|
86
84
|
}
|
|
87
85
|
|
|
88
86
|
/* ── 宜/忌 ── */
|
|
@@ -140,16 +138,7 @@
|
|
|
140
138
|
<div class="content">
|
|
141
139
|
<div class="signin"><strong>{{NAME}}</strong> 签到成功!</div>
|
|
142
140
|
|
|
143
|
-
<div class="
|
|
144
|
-
<span style="color: {{LEVEL_COLOR}};">{{LEVEL_NAME}}</span>
|
|
145
|
-
</div>
|
|
146
|
-
|
|
147
|
-
<div class="fortune">
|
|
148
|
-
<span style="font-size: 36px; font-weight: bold;">
|
|
149
|
-
<span style="font-size: 28px;">⭐</span>{{LUCK}}
|
|
150
|
-
</span>
|
|
151
|
-
<span style="font-size: 28px; color: #838383;">🌠{{LUCK_INFO}}</span>
|
|
152
|
-
</div>
|
|
141
|
+
<div class="stars" id="stars-area"></div>
|
|
153
142
|
|
|
154
143
|
<hr>
|
|
155
144
|
|
|
@@ -166,5 +155,29 @@
|
|
|
166
155
|
|
|
167
156
|
<div class="credit">随机生成 请勿迷信 | JRYS+</div>
|
|
168
157
|
</div>
|
|
158
|
+
|
|
159
|
+
<script>
|
|
160
|
+
(function() {
|
|
161
|
+
var luck = {{LUCK}};
|
|
162
|
+
// 运势 0~100 映射为 1~10 颗星
|
|
163
|
+
var starCount = Math.max(1, Math.ceil(luck / 10));
|
|
164
|
+
// 十种彩色
|
|
165
|
+
var colors = [
|
|
166
|
+
'#ff4757', '#ff6b81', '#ffa502', '#eccc68',
|
|
167
|
+
'#2ed573', '#1e90ff', '#5352ed', '#9b59b6',
|
|
168
|
+
'#ff6348', '#00d2d3'
|
|
169
|
+
];
|
|
170
|
+
// 用运势值 + 星星序号做种子,保证同一天同一用户颜色固定
|
|
171
|
+
var baseSeed = luck * 31 + {{UID}};
|
|
172
|
+
var area = document.getElementById('stars-area');
|
|
173
|
+
for (var i = 0; i < starCount; i++) {
|
|
174
|
+
var span = document.createElement('span');
|
|
175
|
+
span.textContent = '⭐';
|
|
176
|
+
var ci = (baseSeed + i * 7) % colors.length;
|
|
177
|
+
span.style.color = colors[ci];
|
|
178
|
+
area.appendChild(span);
|
|
179
|
+
}
|
|
180
|
+
})();
|
|
181
|
+
</script>
|
|
169
182
|
</body>
|
|
170
183
|
</html>
|
|
@@ -89,11 +89,10 @@
|
|
|
89
89
|
<script type="application/json" id="data-source">{{DATA}}</script>
|
|
90
90
|
<script>
|
|
91
91
|
const DATA = JSON.parse(document.getElementById("data-source").textContent);
|
|
92
|
-
const isExp = DATA.type === "exp";
|
|
93
92
|
const headerIcon = document.getElementById("header-icon");
|
|
94
|
-
headerIcon.className = "header-icon
|
|
95
|
-
headerIcon.textContent =
|
|
96
|
-
document.getElementById("header-title").textContent =
|
|
93
|
+
headerIcon.className = "header-icon sign";
|
|
94
|
+
headerIcon.textContent = "📅";
|
|
95
|
+
document.getElementById("header-title").textContent = "累计签到排行榜 TOP " + DATA.limit;
|
|
97
96
|
document.getElementById("header-sub").textContent = DATA.channelName || "当前频道";
|
|
98
97
|
const rankList = document.getElementById("rank-list");
|
|
99
98
|
DATA.users.forEach(function(user, i) {
|
|
@@ -109,62 +108,8 @@
|
|
|
109
108
|
userDiv.className = "rank-user";
|
|
110
109
|
var nameDiv = document.createElement("div");
|
|
111
110
|
nameDiv.className = "rank-name";
|
|
112
|
-
|
|
113
|
-
if (user.nickname && user.username && user.displayName === user.nickname) {
|
|
114
|
-
nameText += " (" + user.username + ")";
|
|
115
|
-
} else if (user.username && user.displayName !== user.originalId) {
|
|
116
|
-
nameText += " (" + user.originalId + ")";
|
|
117
|
-
}
|
|
118
|
-
nameDiv.textContent = nameText;
|
|
111
|
+
nameDiv.textContent = user.displayName;
|
|
119
112
|
userDiv.appendChild(nameDiv);
|
|
120
|
-
if (user.levelName) {
|
|
121
|
-
var subDiv = document.createElement("div");
|
|
122
|
-
subDiv.className = "rank-sub";
|
|
123
|
-
var color = user.levelColor || "var(--accent-color)";
|
|
124
|
-
if (user.levelProgression) {
|
|
125
|
-
var marker = "「" + user.levelName + "」";
|
|
126
|
-
var parts = user.levelProgression.split(marker);
|
|
127
|
-
if (parts.length === 2) {
|
|
128
|
-
subDiv.appendChild(document.createTextNode(parts[0]));
|
|
129
|
-
var span = document.createElement("span");
|
|
130
|
-
span.textContent = marker;
|
|
131
|
-
span.style.color = color;
|
|
132
|
-
span.style.fontWeight = "700";
|
|
133
|
-
subDiv.appendChild(span);
|
|
134
|
-
subDiv.appendChild(document.createTextNode(parts[1]));
|
|
135
|
-
} else {
|
|
136
|
-
subDiv.textContent = user.levelProgression;
|
|
137
|
-
}
|
|
138
|
-
} else {
|
|
139
|
-
var span2 = document.createElement("span");
|
|
140
|
-
span2.textContent = "「" + user.levelName + "」";
|
|
141
|
-
span2.style.color = color;
|
|
142
|
-
span2.style.fontWeight = "700";
|
|
143
|
-
subDiv.appendChild(span2);
|
|
144
|
-
}
|
|
145
|
-
userDiv.appendChild(subDiv);
|
|
146
|
-
}
|
|
147
|
-
if (isExp && user.nextLevelExp !== null && user.nextLevelExp !== undefined) {
|
|
148
|
-
var progressDiv = document.createElement("div");
|
|
149
|
-
progressDiv.className = "rank-progress";
|
|
150
|
-
var bar = document.createElement("div");
|
|
151
|
-
bar.className = "progress-bar";
|
|
152
|
-
var fill = document.createElement("div");
|
|
153
|
-
fill.className = "progress-fill";
|
|
154
|
-
var currentExp = user.value;
|
|
155
|
-
var baseLevelExp = user.currentLevelExp || 0;
|
|
156
|
-
var nextExp = user.nextLevelExp;
|
|
157
|
-
var progress = nextExp > baseLevelExp ? Math.min(((currentExp - baseLevelExp) / (nextExp - baseLevelExp)) * 100, 100) : 100;
|
|
158
|
-
fill.style.width = progress.toFixed(1) + "%";
|
|
159
|
-
fill.style.background = user.levelColor || "var(--accent-color)";
|
|
160
|
-
bar.appendChild(fill);
|
|
161
|
-
progressDiv.appendChild(bar);
|
|
162
|
-
var pText = document.createElement("div");
|
|
163
|
-
pText.className = "progress-text";
|
|
164
|
-
pText.textContent = progress >= 100 ? "MAX" : currentExp + "/" + nextExp;
|
|
165
|
-
progressDiv.appendChild(pText);
|
|
166
|
-
userDiv.appendChild(progressDiv);
|
|
167
|
-
}
|
|
168
113
|
item.appendChild(userDiv);
|
|
169
114
|
var valueDiv = document.createElement("div");
|
|
170
115
|
valueDiv.className = "rank-value";
|
|
@@ -174,7 +119,7 @@
|
|
|
174
119
|
valueDiv.appendChild(mainVal);
|
|
175
120
|
var labelVal = document.createElement("div");
|
|
176
121
|
labelVal.className = "rank-value-label";
|
|
177
|
-
labelVal.textContent =
|
|
122
|
+
labelVal.textContent = "天";
|
|
178
123
|
valueDiv.appendChild(labelVal);
|
|
179
124
|
item.appendChild(valueDiv);
|
|
180
125
|
rankList.appendChild(item);
|