koishi-plugin-jrys-plus 1.0.0 → 1.0.2
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 +2 -12
- package/lib/index.js +7 -7
- package/lib/ranks.d.ts +2 -2
- package/lib/ranks.js +12 -74
- package/lib/signin.d.ts +9 -8
- package/lib/signin.js +12 -8
- package/package.json +1 -1
package/lib/index.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* koishi-plugin-jrys-plus
|
|
3
|
-
* 今日运势签到 +
|
|
3
|
+
* 今日运势签到 + 签到天数排行榜(精简版)
|
|
4
4
|
*
|
|
5
5
|
* 改动:
|
|
6
6
|
* - 移除金币/经验值展示和进度条
|
|
7
7
|
* - 🍀 替换为 ⭐
|
|
8
8
|
* - 日期问候模块移除半透明背景
|
|
9
|
-
* -
|
|
9
|
+
* - 仅保留签到天数排行榜
|
|
10
10
|
*/
|
|
11
11
|
import { Context, Schema } from 'koishi';
|
|
12
12
|
import * as si from './signin';
|
|
@@ -18,12 +18,10 @@ export interface Config {
|
|
|
18
18
|
levelSet: si.LevelInfo[];
|
|
19
19
|
fortuneSet: si.FortuneInfo[];
|
|
20
20
|
event: RollEvent[];
|
|
21
|
-
expCommand: string;
|
|
22
21
|
signCommand: string;
|
|
23
22
|
imageMode: boolean;
|
|
24
23
|
limit: number;
|
|
25
24
|
borderwidth: number;
|
|
26
|
-
next_ExpDisplay: boolean;
|
|
27
25
|
pre_next_LevelDisplay: boolean;
|
|
28
26
|
}
|
|
29
27
|
export declare const Config: Schema<Schemastery.ObjectS<{
|
|
@@ -58,12 +56,10 @@ export declare const Config: Schema<Schemastery.ObjectS<{
|
|
|
58
56
|
bad: Schema<string, string>;
|
|
59
57
|
}>[]>;
|
|
60
58
|
}> | Schemastery.ObjectS<{
|
|
61
|
-
expCommand: Schema<string, string>;
|
|
62
59
|
signCommand: Schema<string, string>;
|
|
63
60
|
imageMode: Schema<boolean, boolean>;
|
|
64
61
|
limit: Schema<number, number>;
|
|
65
62
|
borderwidth: Schema<number, number>;
|
|
66
|
-
next_ExpDisplay: Schema<boolean, boolean>;
|
|
67
63
|
pre_next_LevelDisplay: Schema<boolean, boolean>;
|
|
68
64
|
}>, {
|
|
69
65
|
imgUrl: string;
|
|
@@ -85,12 +81,10 @@ export declare const Config: Schema<Schemastery.ObjectS<{
|
|
|
85
81
|
bad: Schema<string, string>;
|
|
86
82
|
}>[];
|
|
87
83
|
} & {
|
|
88
|
-
expCommand: string;
|
|
89
84
|
signCommand: string;
|
|
90
85
|
imageMode: boolean;
|
|
91
86
|
limit: number;
|
|
92
87
|
borderwidth: number;
|
|
93
|
-
next_ExpDisplay: boolean;
|
|
94
88
|
pre_next_LevelDisplay: boolean;
|
|
95
89
|
}>;
|
|
96
90
|
export declare const inject: {
|
|
@@ -133,12 +127,10 @@ declare const _default: {
|
|
|
133
127
|
bad: Schema<string, string>;
|
|
134
128
|
}>[]>;
|
|
135
129
|
}> | Schemastery.ObjectS<{
|
|
136
|
-
expCommand: Schema<string, string>;
|
|
137
130
|
signCommand: Schema<string, string>;
|
|
138
131
|
imageMode: Schema<boolean, boolean>;
|
|
139
132
|
limit: Schema<number, number>;
|
|
140
133
|
borderwidth: Schema<number, number>;
|
|
141
|
-
next_ExpDisplay: Schema<boolean, boolean>;
|
|
142
134
|
pre_next_LevelDisplay: Schema<boolean, boolean>;
|
|
143
135
|
}>, {
|
|
144
136
|
imgUrl: string;
|
|
@@ -160,12 +152,10 @@ declare const _default: {
|
|
|
160
152
|
bad: Schema<string, string>;
|
|
161
153
|
}>[];
|
|
162
154
|
} & {
|
|
163
|
-
expCommand: string;
|
|
164
155
|
signCommand: string;
|
|
165
156
|
imageMode: boolean;
|
|
166
157
|
limit: number;
|
|
167
158
|
borderwidth: number;
|
|
168
|
-
next_ExpDisplay: boolean;
|
|
169
159
|
pre_next_LevelDisplay: boolean;
|
|
170
160
|
}>;
|
|
171
161
|
};
|
package/lib/index.js
CHANGED
|
@@ -40,13 +40,13 @@ 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
45
|
* 改动:
|
|
46
46
|
* - 移除金币/经验值展示和进度条
|
|
47
47
|
* - 🍀 替换为 ⭐
|
|
48
48
|
* - 日期问候模块移除半透明背景
|
|
49
|
-
* -
|
|
49
|
+
* - 仅保留签到天数排行榜
|
|
50
50
|
*/
|
|
51
51
|
const koishi_1 = require("koishi");
|
|
52
52
|
const url_1 = require("url");
|
|
@@ -88,12 +88,10 @@ exports.Config = koishi_1.Schema.intersect([
|
|
|
88
88
|
.description('自定义黄历事件'),
|
|
89
89
|
}).description('签到/运势设置'),
|
|
90
90
|
koishi_1.Schema.object({
|
|
91
|
-
expCommand: koishi_1.Schema.string().description('经验排行榜命令').default('jrysranks'),
|
|
92
91
|
signCommand: koishi_1.Schema.string().description('签到天数排行榜命令').default('jrysranksign'),
|
|
93
92
|
imageMode: koishi_1.Schema.boolean().description('排行榜是否使用图片模式(需要 puppeteer)').default(true),
|
|
94
93
|
limit: koishi_1.Schema.number().description('排行榜显示的最大条目数').min(1).max(100).default(10),
|
|
95
94
|
borderwidth: koishi_1.Schema.number().description('文本模式边框宽度').default(14),
|
|
96
|
-
next_ExpDisplay: koishi_1.Schema.boolean().description('排行榜中显示升级所需经验').default(true),
|
|
97
95
|
pre_next_LevelDisplay: koishi_1.Schema.boolean().description('排行榜中显示前后等级信息').default(true),
|
|
98
96
|
}).description('排行榜设置'),
|
|
99
97
|
]);
|
|
@@ -130,12 +128,14 @@ async function fetchHitokoto() {
|
|
|
130
128
|
/* ── 主入口 ── */
|
|
131
129
|
function apply(ctx, config) {
|
|
132
130
|
si.initDatabase(ctx);
|
|
133
|
-
const
|
|
131
|
+
const db = ctx.database;
|
|
132
|
+
const puppeteer = ctx.puppeteer;
|
|
133
|
+
const signin = new si.Signin(db, config);
|
|
134
134
|
const jrys = new roll_1.Jrys();
|
|
135
135
|
// 合并事件
|
|
136
136
|
const eventJson = [...event_1.defaultEventJson, ...config.event];
|
|
137
137
|
// 注册排行榜
|
|
138
|
-
(0, ranks_1.registerRanks)(ctx, config, () => config.levelSet);
|
|
138
|
+
(0, ranks_1.registerRanks)(ctx, db, config, () => config.levelSet);
|
|
139
139
|
/* ── 运势签到命令 ── */
|
|
140
140
|
ctx.command('jrys', '今日运势')
|
|
141
141
|
.userFields(['id', 'name'])
|
|
@@ -186,7 +186,7 @@ function apply(ctx, config) {
|
|
|
186
186
|
.replace('{{BADDO}}', baddo);
|
|
187
187
|
const outPath = path_1.default.resolve(__dirname, 'templates', '_fortune_render.html');
|
|
188
188
|
fs_1.default.writeFileSync(outPath, html);
|
|
189
|
-
page = await
|
|
189
|
+
page = await puppeteer.page();
|
|
190
190
|
await page.setViewport({ width: 600, height: 1080 * 2 });
|
|
191
191
|
await page.goto(`file:///${outPath}`);
|
|
192
192
|
await page.waitForSelector('#body', { timeout: 10000 });
|
package/lib/ranks.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { Context } from 'koishi';
|
|
1
|
+
import { Context, Database } from 'koishi';
|
|
2
2
|
import { LevelInfo } from './signin';
|
|
3
|
-
export declare function registerRanks(ctx: Context, config: any, getLevelConfig: () => LevelInfo[]): void;
|
|
3
|
+
export declare function registerRanks(ctx: Context, db: Database<any>, config: any, getLevelConfig: () => LevelInfo[]): void;
|
package/lib/ranks.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.registerRanks = registerRanks;
|
|
4
4
|
const koishi_1 = require("koishi");
|
|
5
|
+
const signin_1 = require("./signin");
|
|
5
6
|
const fs_1 = require("fs");
|
|
6
7
|
const path_1 = require("path");
|
|
7
8
|
/* ── 模板路径候选 ── */
|
|
@@ -21,55 +22,7 @@ async function resolveTemplatePath() {
|
|
|
21
22
|
}
|
|
22
23
|
throw new Error('未找到 rank-card.html 模板文件');
|
|
23
24
|
}
|
|
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
25
|
/* ── 文本渲染 ── */
|
|
33
|
-
function renderExpText(users, levelConfig, config) {
|
|
34
|
-
const divider = '┏' + '—'.repeat(config.borderwidth) + '┓';
|
|
35
|
-
const midDivider = '┣' + '—'.repeat(config.borderwidth) + '┫';
|
|
36
|
-
const endDivider = '┗' + '—'.repeat(config.borderwidth) + '┛';
|
|
37
|
-
const header = [divider, `┃ 🏆 经验排行榜 TOP.${config.limit} `, midDivider].join('\n');
|
|
38
|
-
const rankings = users.map((user, index) => {
|
|
39
|
-
const medal = index < 3 ? ['👑', '⭐', '✧'][index] : '•';
|
|
40
|
-
const expStr = user.exp.toLocaleString();
|
|
41
|
-
let lines = [`┃ ${medal} ${index + 1}. ${user.displayName}`];
|
|
42
|
-
if (config.next_ExpDisplay) {
|
|
43
|
-
const sorted = [...levelConfig].sort((a, b) => a.levelExp - b.levelExp);
|
|
44
|
-
const cur = getLevelInfo(user.exp, levelConfig);
|
|
45
|
-
const idx = sorted.findIndex(l => l.levelExp === cur.levelExp);
|
|
46
|
-
const next = sorted[idx + 1];
|
|
47
|
-
lines.push(next ? `┃ ⚡${expStr} exp (下一级:${next.levelExp} exp)` : `┃ ⚡${expStr} (Max)`);
|
|
48
|
-
}
|
|
49
|
-
else {
|
|
50
|
-
lines.push(`┃ ⚡${expStr} exp`);
|
|
51
|
-
}
|
|
52
|
-
if (config.pre_next_LevelDisplay && levelConfig.length) {
|
|
53
|
-
const sorted = [...levelConfig].sort((a, b) => a.levelExp - b.levelExp);
|
|
54
|
-
const cur = getLevelInfo(user.exp, levelConfig);
|
|
55
|
-
const idx = sorted.findIndex(l => l.levelExp === cur.levelExp);
|
|
56
|
-
const prev = sorted[idx - 1]?.levelName;
|
|
57
|
-
const next = sorted[idx + 1]?.levelName;
|
|
58
|
-
let line = '┃ ✨';
|
|
59
|
-
if (prev)
|
|
60
|
-
line += `${prev} → `;
|
|
61
|
-
line += `「${cur.levelName}」`;
|
|
62
|
-
if (next)
|
|
63
|
-
line += ` → ${next}`;
|
|
64
|
-
lines.push(line);
|
|
65
|
-
}
|
|
66
|
-
else if (levelConfig.length) {
|
|
67
|
-
lines.push(`┃ ✨${getLevelInfo(user.exp, levelConfig).levelName}`);
|
|
68
|
-
}
|
|
69
|
-
return lines.join('\n');
|
|
70
|
-
}).join('\n\n');
|
|
71
|
-
return [header, rankings, endDivider].join('\n');
|
|
72
|
-
}
|
|
73
26
|
function renderSignText(users, levelConfig, config) {
|
|
74
27
|
const divider = '┏' + '—'.repeat(config.borderwidth) + '┓';
|
|
75
28
|
const midDivider = '┣' + '—'.repeat(config.borderwidth) + '┫';
|
|
@@ -80,7 +33,7 @@ function renderSignText(users, levelConfig, config) {
|
|
|
80
33
|
let lines = [`┃ ${medal} ${index + 1}. ${user.displayName}`, `┃ 📅${user.signCount.toLocaleString()} 天`];
|
|
81
34
|
if (config.pre_next_LevelDisplay && levelConfig.length) {
|
|
82
35
|
const sorted = [...levelConfig].sort((a, b) => a.levelExp - b.levelExp);
|
|
83
|
-
const cur = getLevelInfo(user.exp, levelConfig);
|
|
36
|
+
const cur = (0, signin_1.getLevelInfo)(user.exp, levelConfig);
|
|
84
37
|
const idx = sorted.findIndex(l => l.levelExp === cur.levelExp);
|
|
85
38
|
const prev = sorted[idx - 1]?.levelName;
|
|
86
39
|
const next = sorted[idx + 1]?.levelName;
|
|
@@ -97,20 +50,20 @@ function renderSignText(users, levelConfig, config) {
|
|
|
97
50
|
return [header, rankings, endDivider].join('\n');
|
|
98
51
|
}
|
|
99
52
|
/* ── 图片渲染 ── */
|
|
100
|
-
async function renderRankImage(ctx,
|
|
53
|
+
async function renderRankImage(ctx, users, totalUsers, limit, getLevelConfig) {
|
|
101
54
|
try {
|
|
102
55
|
const path = await resolveTemplatePath();
|
|
103
56
|
let template = await fs_1.promises.readFile(path, 'utf-8');
|
|
104
57
|
const levelConfig = getLevelConfig();
|
|
105
58
|
const data = {
|
|
106
|
-
type,
|
|
59
|
+
type: 'sign',
|
|
107
60
|
limit,
|
|
108
61
|
channelName: '当前频道',
|
|
109
62
|
totalUsers,
|
|
110
63
|
updateTime: new Date().toLocaleString('zh-CN'),
|
|
111
64
|
users: users.map(user => {
|
|
112
65
|
const sorted = [...levelConfig].sort((a, b) => a.levelExp - b.levelExp);
|
|
113
|
-
const cur = getLevelInfo(user.exp, levelConfig);
|
|
66
|
+
const cur = (0, signin_1.getLevelInfo)(user.exp, levelConfig);
|
|
114
67
|
const idx = sorted.findIndex(l => l.levelExp === cur.levelExp);
|
|
115
68
|
const prev = sorted[idx - 1]?.levelName;
|
|
116
69
|
const nextObj = sorted[idx + 1];
|
|
@@ -124,7 +77,7 @@ async function renderRankImage(ctx, type, users, totalUsers, limit, getLevelConf
|
|
|
124
77
|
return {
|
|
125
78
|
displayName: user.displayName,
|
|
126
79
|
originalId: user.name,
|
|
127
|
-
value:
|
|
80
|
+
value: user.signCount,
|
|
128
81
|
levelName: cur.levelName,
|
|
129
82
|
levelColor: cur.levelColor,
|
|
130
83
|
currentLevelExp: cur.levelExp,
|
|
@@ -153,13 +106,13 @@ async function renderRankImage(ctx, type, users, totalUsers, limit, getLevelConf
|
|
|
153
106
|
}
|
|
154
107
|
}
|
|
155
108
|
/* ── 注册排行命令 ── */
|
|
156
|
-
function registerRanks(ctx, config, getLevelConfig) {
|
|
109
|
+
function registerRanks(ctx, db, config, getLevelConfig) {
|
|
157
110
|
const logger = ctx.logger('jrys-plus');
|
|
158
111
|
function canUseImage() {
|
|
159
112
|
return config.imageMode && !!ctx.puppeteer;
|
|
160
113
|
}
|
|
161
|
-
async function getRankedUsers(
|
|
162
|
-
const all = await
|
|
114
|
+
async function getRankedUsers() {
|
|
115
|
+
const all = await db.get('jrys', {}, { sort: { signCount: 'desc' } });
|
|
163
116
|
if (!all.length)
|
|
164
117
|
return null;
|
|
165
118
|
const users = all.slice(0, config.limit).map(u => ({
|
|
@@ -168,31 +121,16 @@ function registerRanks(ctx, config, getLevelConfig) {
|
|
|
168
121
|
}));
|
|
169
122
|
return users;
|
|
170
123
|
}
|
|
171
|
-
ctx.command(config.expCommand || 'jrysranks')
|
|
172
|
-
.action(async ({ session }) => {
|
|
173
|
-
const users = await getRankedUsers(session, 'exp');
|
|
174
|
-
if (users === null)
|
|
175
|
-
return '暂无数据';
|
|
176
|
-
if (!users.length)
|
|
177
|
-
return '当前频道暂无数据';
|
|
178
|
-
if (canUseImage()) {
|
|
179
|
-
const total = (await ctx.database.get('jrys', {})).length;
|
|
180
|
-
const img = await renderRankImage(ctx, 'exp', users, total, config.limit, getLevelConfig);
|
|
181
|
-
if (img)
|
|
182
|
-
return img;
|
|
183
|
-
}
|
|
184
|
-
return renderExpText(users, getLevelConfig(), config);
|
|
185
|
-
});
|
|
186
124
|
ctx.command(config.signCommand || 'jrysranksign')
|
|
187
125
|
.action(async ({ session }) => {
|
|
188
|
-
const users = await getRankedUsers(
|
|
126
|
+
const users = await getRankedUsers();
|
|
189
127
|
if (users === null)
|
|
190
128
|
return '暂无数据';
|
|
191
129
|
if (!users.length)
|
|
192
130
|
return '当前频道暂无数据';
|
|
193
131
|
if (canUseImage()) {
|
|
194
|
-
const total = (await
|
|
195
|
-
const img = await renderRankImage(ctx,
|
|
132
|
+
const total = (await db.get('jrys', {})).length;
|
|
133
|
+
const img = await renderRankImage(ctx, users, total, config.limit, getLevelConfig);
|
|
196
134
|
if (img)
|
|
197
135
|
return img;
|
|
198
136
|
}
|
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;
|
|
@@ -38,21 +38,22 @@ export interface SigninConfig {
|
|
|
38
38
|
levelSet: LevelInfo[];
|
|
39
39
|
fortuneSet: FortuneInfo[];
|
|
40
40
|
}
|
|
41
|
+
export declare function getLevelInfo(exp: number, levels: LevelInfo[]): LevelInfo;
|
|
41
42
|
export declare class Signin {
|
|
42
|
-
private
|
|
43
|
+
private db;
|
|
43
44
|
private cfg;
|
|
44
|
-
constructor(
|
|
45
|
+
constructor(db: Database<any>, cfg: SigninConfig);
|
|
45
46
|
/** 执行签到。返回 0=成功, 1=已签到 */
|
|
46
47
|
callSignin(uid: number, userid: string, luck: number): Promise<{
|
|
47
|
-
status: number;
|
|
48
|
-
allExp: number;
|
|
49
|
-
signTime: Date;
|
|
50
|
-
count: number;
|
|
51
|
-
} | {
|
|
52
48
|
status: number;
|
|
53
49
|
allExp?: undefined;
|
|
54
50
|
signTime?: undefined;
|
|
55
51
|
count?: undefined;
|
|
52
|
+
} | {
|
|
53
|
+
status: number;
|
|
54
|
+
allExp: any;
|
|
55
|
+
signTime: Date;
|
|
56
|
+
count: any;
|
|
56
57
|
}>;
|
|
57
58
|
getLevelInfo(exp: number): {
|
|
58
59
|
levelInfo: LevelInfo;
|
package/lib/signin.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Signin = exports.defaultFortuneInfo = exports.defaultLevelInfo = void 0;
|
|
4
4
|
exports.initDatabase = initDatabase;
|
|
5
|
+
exports.getLevelInfo = getLevelInfo;
|
|
5
6
|
const timeGreetings = [
|
|
6
7
|
{ range: [0, 5], message: '晚安' },
|
|
7
8
|
{ range: [5, 9], message: '早上好' },
|
|
@@ -59,24 +60,27 @@ function initDatabase(ctx) {
|
|
|
59
60
|
signCount: 'unsigned',
|
|
60
61
|
});
|
|
61
62
|
}
|
|
62
|
-
|
|
63
|
-
|
|
63
|
+
function getLevelInfo(exp, levels) {
|
|
64
|
+
if (!levels?.length)
|
|
65
|
+
return { level: 0, levelExp: 0, levelName: '无等级', levelColor: '#666666' };
|
|
66
|
+
const sorted = [...levels].sort((a, b) => b.levelExp - a.levelExp);
|
|
67
|
+
return sorted.find(l => exp >= l.levelExp) || sorted[sorted.length - 1];
|
|
68
|
+
}
|
|
64
69
|
class Signin {
|
|
65
|
-
constructor(
|
|
66
|
-
this.
|
|
70
|
+
constructor(db, cfg) {
|
|
71
|
+
this.db = db;
|
|
67
72
|
this.cfg = cfg;
|
|
68
73
|
}
|
|
69
74
|
/** 执行签到。返回 0=成功, 1=已签到 */
|
|
70
75
|
async callSignin(uid, userid, luck) {
|
|
71
76
|
const date = new Date();
|
|
72
|
-
const roll = new roll_1.Jrys();
|
|
73
77
|
// 经验值仅用于后端等级/排行计算,不在签到卡上展示
|
|
74
78
|
const exp = Math.round((Math.random() * 0.5 + luck / 200) *
|
|
75
79
|
(this.cfg.signExp[1] - this.cfg.signExp[0])) + this.cfg.signExp[0];
|
|
76
|
-
const userData = await this.
|
|
80
|
+
const userData = await this.db.get('jrys', { id: uid });
|
|
77
81
|
if (userData.length === 0) {
|
|
78
82
|
const accExp = exp;
|
|
79
|
-
await this.
|
|
83
|
+
await this.db.create('jrys', {
|
|
80
84
|
id: uid,
|
|
81
85
|
name: userid,
|
|
82
86
|
time: date,
|
|
@@ -90,7 +94,7 @@ class Signin {
|
|
|
90
94
|
}
|
|
91
95
|
const accExp = userData[0].exp + exp;
|
|
92
96
|
const accCount = userData[0].signCount + 1;
|
|
93
|
-
await this.
|
|
97
|
+
await this.db.set('jrys', { id: uid }, {
|
|
94
98
|
name: userid,
|
|
95
99
|
time: date,
|
|
96
100
|
exp: accExp,
|