mioku-plugin-deerpipe 2.0.1 → 2.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/README.md +8 -8
- package/commands.ts +21 -16
- package/db.ts +7 -7
- package/image.ts +8 -6
- package/index.ts +53 -10
- package/package.json +2 -76
- package/types.ts +4 -4
- package/utils.ts +36 -13
package/README.md
CHANGED
|
@@ -21,13 +21,13 @@
|
|
|
21
21
|
|
|
22
22
|
| 指令 | 说明 | 权限 |
|
|
23
23
|
|-------------------|---------------------|------|
|
|
24
|
-
|
|
|
25
|
-
|
|
|
26
|
-
|
|
|
27
|
-
|
|
|
28
|
-
|
|
|
29
|
-
|
|
|
30
|
-
|
|
|
31
|
-
|
|
|
24
|
+
| `.🦌` / `.鹿` | 自🦌一次 | 全员 |
|
|
25
|
+
| `.🦌 @某人` | 帮某人🦌一次(仅群组) | 全员 |
|
|
26
|
+
| `.补🦌 <日>` | 补签本月某天 | 全员 |
|
|
27
|
+
| `.🦌历` / `.🦌历 @某人` | 查看本月🦌日历 | 全员 |
|
|
28
|
+
| `.🦌榜` | 查看本月本群🦌排行榜(仅群组) | 全员 |
|
|
29
|
+
| `.帮🦌 on/off` | 允许/禁止别人帮自己🦌 | 全员 |
|
|
30
|
+
| `.帮🦌 on/off @某人` | 允许/禁止帮某人🦌 | 群管理员 |
|
|
31
|
+
| `.禁🦌 @某人 [时长]` | 禁止某人🦌一段时间,省略时长视为解禁 | 群管理员 |
|
|
32
32
|
|
|
33
33
|
时长支持 `30s` / `5m` / `2h` / `1d` / `1w` 等写法,最长 30 天。
|
package/commands.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
getAtUserId,
|
|
8
8
|
parseDuration,
|
|
9
9
|
replyImage,
|
|
10
|
+
resolveAvatarUrl,
|
|
10
11
|
resolveScene,
|
|
11
12
|
resolveUserName,
|
|
12
13
|
} from "./utils";
|
|
@@ -30,12 +31,12 @@ function isGroupAdmin(event: any): boolean {
|
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
export type DeerCommand =
|
|
33
|
-
| { type: "deer"; targetUserId?:
|
|
34
|
+
| { type: "deer"; targetUserId?: string }
|
|
34
35
|
| { type: "past"; day: number }
|
|
35
|
-
| { type: "calendar"; targetUserId?:
|
|
36
|
+
| { type: "calendar"; targetUserId?: string }
|
|
36
37
|
| { type: "rank" }
|
|
37
|
-
| { type: "set_can_be_helped"; allowed: boolean; targetUserId?:
|
|
38
|
-
| { type: "set_no_deer"; targetUserId:
|
|
38
|
+
| { type: "set_can_be_helped"; allowed: boolean; targetUserId?: string }
|
|
39
|
+
| { type: "set_no_deer"; targetUserId: string; durationText?: string }
|
|
39
40
|
| { type: "invalid_past" }
|
|
40
41
|
| { type: "none" };
|
|
41
42
|
|
|
@@ -124,7 +125,7 @@ export async function handleDeerCommand(
|
|
|
124
125
|
|
|
125
126
|
async function handleDeer(
|
|
126
127
|
cmdCtx: CommandContext,
|
|
127
|
-
targetUserId?:
|
|
128
|
+
targetUserId?: string,
|
|
128
129
|
): Promise<void> {
|
|
129
130
|
const { ctx, db, screenshot, event } = cmdCtx;
|
|
130
131
|
const scene = resolveScene(event);
|
|
@@ -136,7 +137,7 @@ async function handleDeer(
|
|
|
136
137
|
}
|
|
137
138
|
|
|
138
139
|
const userId =
|
|
139
|
-
targetUserId != null ? targetUserId :
|
|
140
|
+
targetUserId != null ? targetUserId : String(event.user_id ?? "").trim();
|
|
140
141
|
const user = db.getOrCreateUser(scene.key, userId);
|
|
141
142
|
|
|
142
143
|
if (targetUserId && !user.canBeHelped) {
|
|
@@ -173,10 +174,11 @@ async function handleDeer(
|
|
|
173
174
|
records: result.records,
|
|
174
175
|
name,
|
|
175
176
|
userId,
|
|
177
|
+
avatar: resolveAvatarUrl(event, userId),
|
|
176
178
|
});
|
|
177
179
|
|
|
178
180
|
const prefix = targetUserId
|
|
179
|
-
? [{ type: "text", data: { text: "成功帮 " } }, ctx.segment.at(
|
|
181
|
+
? [{ type: "text", data: { text: "成功帮 " } }, ctx.segment.at(targetUserId), { type: "text", data: { text: " 🦌了\n" } }]
|
|
180
182
|
: [{ type: "text", data: { text: "成功🦌了\n" } }];
|
|
181
183
|
|
|
182
184
|
await replyImage(event, ctx.segment, imagePath, prefix);
|
|
@@ -195,7 +197,7 @@ async function handlePast(
|
|
|
195
197
|
return;
|
|
196
198
|
}
|
|
197
199
|
|
|
198
|
-
const userId =
|
|
200
|
+
const userId = String(event.user_id ?? "").trim();
|
|
199
201
|
db.getOrCreateUser(scene.key, userId);
|
|
200
202
|
|
|
201
203
|
const result = await db.checkIn(
|
|
@@ -215,6 +217,7 @@ async function handlePast(
|
|
|
215
217
|
records: result.records,
|
|
216
218
|
name,
|
|
217
219
|
userId,
|
|
220
|
+
avatar: resolveAvatarUrl(event, userId),
|
|
218
221
|
});
|
|
219
222
|
|
|
220
223
|
const text = result.ok ? "成功补🦌\n" : "不能补🦌已经🦌过的日子捏\n";
|
|
@@ -228,14 +231,14 @@ async function handlePast(
|
|
|
228
231
|
|
|
229
232
|
async function handleCalendar(
|
|
230
233
|
cmdCtx: CommandContext,
|
|
231
|
-
targetUserId?:
|
|
234
|
+
targetUserId?: string,
|
|
232
235
|
): Promise<void> {
|
|
233
236
|
const { ctx, db, screenshot, event } = cmdCtx;
|
|
234
237
|
const scene = resolveScene(event);
|
|
235
238
|
if (targetUserId && !scene.isGroup) return;
|
|
236
239
|
|
|
237
240
|
const now = new Date();
|
|
238
|
-
const userId = targetUserId ??
|
|
241
|
+
const userId = targetUserId ?? String(event.user_id ?? "").trim();
|
|
239
242
|
db.getOrCreateUser(scene.key, userId);
|
|
240
243
|
const records = db.getRecords(
|
|
241
244
|
scene.key,
|
|
@@ -252,6 +255,7 @@ async function handleCalendar(
|
|
|
252
255
|
records,
|
|
253
256
|
name,
|
|
254
257
|
userId,
|
|
258
|
+
avatar: resolveAvatarUrl(event, userId),
|
|
255
259
|
});
|
|
256
260
|
|
|
257
261
|
await replyImage(event, ctx.segment, imagePath);
|
|
@@ -275,6 +279,7 @@ async function handleRank(cmdCtx: CommandContext): Promise<void> {
|
|
|
275
279
|
rank: idx + 1,
|
|
276
280
|
userId: entry.userId,
|
|
277
281
|
name: await resolveUserName(ctx, event, entry.userId),
|
|
282
|
+
avatar: resolveAvatarUrl(event, entry.userId),
|
|
278
283
|
count: entry.count,
|
|
279
284
|
})),
|
|
280
285
|
);
|
|
@@ -292,7 +297,7 @@ async function handleRank(cmdCtx: CommandContext): Promise<void> {
|
|
|
292
297
|
async function handleSetCanBeHelped(
|
|
293
298
|
cmdCtx: CommandContext,
|
|
294
299
|
allowed: boolean,
|
|
295
|
-
targetUserId?:
|
|
300
|
+
targetUserId?: string,
|
|
296
301
|
): Promise<void> {
|
|
297
302
|
const { ctx, db, event } = cmdCtx;
|
|
298
303
|
const scene = resolveScene(event);
|
|
@@ -303,7 +308,7 @@ async function handleSetCanBeHelped(
|
|
|
303
308
|
return;
|
|
304
309
|
}
|
|
305
310
|
|
|
306
|
-
const userId = targetUserId ??
|
|
311
|
+
const userId = targetUserId ?? String(event.user_id ?? "").trim();
|
|
307
312
|
const user = db.getOrCreateUser(scene.key, userId);
|
|
308
313
|
user.canBeHelped = allowed;
|
|
309
314
|
await db.updateUser(user);
|
|
@@ -312,7 +317,7 @@ async function handleSetCanBeHelped(
|
|
|
312
317
|
await event.reply(
|
|
313
318
|
[
|
|
314
319
|
{ type: "text", data: { text: `已${allowed ? "允许" : "禁止"}帮 ` } },
|
|
315
|
-
ctx.segment.at(
|
|
320
|
+
ctx.segment.at(targetUserId),
|
|
316
321
|
{ type: "text", data: { text: " 🦌" } },
|
|
317
322
|
],
|
|
318
323
|
true,
|
|
@@ -324,7 +329,7 @@ async function handleSetCanBeHelped(
|
|
|
324
329
|
|
|
325
330
|
async function handleSetNoDeer(
|
|
326
331
|
cmdCtx: CommandContext,
|
|
327
|
-
targetUserId:
|
|
332
|
+
targetUserId: string,
|
|
328
333
|
durationText?: string,
|
|
329
334
|
): Promise<void> {
|
|
330
335
|
const { ctx, db, event } = cmdCtx;
|
|
@@ -361,7 +366,7 @@ async function handleSetNoDeer(
|
|
|
361
366
|
await event.reply(
|
|
362
367
|
[
|
|
363
368
|
{ type: "text", data: { text: "已解禁 " } },
|
|
364
|
-
ctx.segment.at(
|
|
369
|
+
ctx.segment.at(targetUserId),
|
|
365
370
|
{ type: "text", data: { text: " 的🦌权" } },
|
|
366
371
|
],
|
|
367
372
|
true,
|
|
@@ -370,7 +375,7 @@ async function handleSetNoDeer(
|
|
|
370
375
|
await event.reply(
|
|
371
376
|
[
|
|
372
377
|
{ type: "text", data: { text: "已禁止 " } },
|
|
373
|
-
ctx.segment.at(
|
|
378
|
+
ctx.segment.at(targetUserId),
|
|
374
379
|
{
|
|
375
380
|
type: "text",
|
|
376
381
|
data: {
|
package/db.ts
CHANGED
|
@@ -18,17 +18,17 @@ interface RecordRow {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
export interface DeerDatabase {
|
|
21
|
-
getOrCreateUser(scene: string, userId:
|
|
21
|
+
getOrCreateUser(scene: string, userId: string): DeerUser;
|
|
22
22
|
updateUser(user: DeerUser): Promise<void>;
|
|
23
23
|
getRecords(
|
|
24
24
|
scene: string,
|
|
25
|
-
userId:
|
|
25
|
+
userId: string,
|
|
26
26
|
year: number,
|
|
27
27
|
month: number,
|
|
28
28
|
): Map<number, number>;
|
|
29
29
|
checkIn(
|
|
30
30
|
scene: string,
|
|
31
|
-
userId:
|
|
31
|
+
userId: string,
|
|
32
32
|
year: number,
|
|
33
33
|
month: number,
|
|
34
34
|
day: number,
|
|
@@ -44,7 +44,7 @@ export interface DeerDatabase {
|
|
|
44
44
|
close(): void;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
function userKey(scene: string, userId:
|
|
47
|
+
function userKey(scene: string, userId: string): string {
|
|
48
48
|
return `${scene}:${userId}`;
|
|
49
49
|
}
|
|
50
50
|
|
|
@@ -125,7 +125,7 @@ export async function initDeerDatabase(): Promise<DeerDatabase> {
|
|
|
125
125
|
|
|
126
126
|
function loadRecords(
|
|
127
127
|
scene: string,
|
|
128
|
-
userId:
|
|
128
|
+
userId: string,
|
|
129
129
|
year: number,
|
|
130
130
|
month: number,
|
|
131
131
|
): Map<number, number> {
|
|
@@ -214,9 +214,9 @@ export async function initDeerDatabase(): Promise<DeerDatabase> {
|
|
|
214
214
|
$scene: scene,
|
|
215
215
|
$monthKey: monthKey(year, month),
|
|
216
216
|
$limit: limit,
|
|
217
|
-
}) as Array<{ user_id:
|
|
217
|
+
}) as Array<{ user_id: string; total: number }>;
|
|
218
218
|
return rows.map((row) => ({
|
|
219
|
-
userId: row.user_id,
|
|
219
|
+
userId: String(row.user_id),
|
|
220
220
|
count: row.total,
|
|
221
221
|
}));
|
|
222
222
|
},
|
package/image.ts
CHANGED
|
@@ -2,7 +2,7 @@ import * as fs from "fs/promises";
|
|
|
2
2
|
import * as path from "path";
|
|
3
3
|
import { fileURLToPath } from "url";
|
|
4
4
|
import type { ScreenshotService } from "mioku";
|
|
5
|
-
import { escapeHtml,
|
|
5
|
+
import { escapeHtml, isNightMode } from "./utils";
|
|
6
6
|
|
|
7
7
|
interface CalendarOptions {
|
|
8
8
|
year: number;
|
|
@@ -10,13 +10,15 @@ interface CalendarOptions {
|
|
|
10
10
|
todayDay: number;
|
|
11
11
|
records: Map<number, number>;
|
|
12
12
|
name: string;
|
|
13
|
-
userId:
|
|
13
|
+
userId: string;
|
|
14
|
+
avatar?: string;
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
interface RankRow {
|
|
17
18
|
rank: number;
|
|
18
19
|
name: string;
|
|
19
|
-
userId:
|
|
20
|
+
userId: string;
|
|
21
|
+
avatar?: string;
|
|
20
22
|
count: number;
|
|
21
23
|
}
|
|
22
24
|
|
|
@@ -127,7 +129,7 @@ export async function generateCalendarImage(
|
|
|
127
129
|
screenshotService: ScreenshotService,
|
|
128
130
|
options: CalendarOptions,
|
|
129
131
|
): Promise<string> {
|
|
130
|
-
const { year, month, records, name,
|
|
132
|
+
const { year, month, records, name, avatar } = options;
|
|
131
133
|
const assets = await loadAssets();
|
|
132
134
|
// 日历始终使用白天主题(按用户要求 🦌 / 🦌历 / 补🦌 不适配夜间模式)
|
|
133
135
|
const theme = DAY_THEME;
|
|
@@ -237,7 +239,7 @@ export async function generateCalendarImage(
|
|
|
237
239
|
</head>
|
|
238
240
|
<body>
|
|
239
241
|
<div class="canvas">
|
|
240
|
-
<img class="avatar" src="${escapeHtml(
|
|
242
|
+
<img class="avatar" src="${escapeHtml(String(avatar ?? "").trim() || assets.defaultAvatar)}" onerror="this.onerror=null;this.src='${assets.defaultAvatar}'" />
|
|
241
243
|
<div class="title">${year}-${String(month).padStart(2, "0")} 🦌签到日历</div>
|
|
242
244
|
<div class="subtitle">@${escapeHtml(name)}</div>
|
|
243
245
|
${cellsHtml}
|
|
@@ -271,7 +273,7 @@ export async function generateRankImage(
|
|
|
271
273
|
.map((row, idx) => {
|
|
272
274
|
const y = headerHeight + idx * rowHeight;
|
|
273
275
|
return `
|
|
274
|
-
<img class="rank-avatar" src="${escapeHtml(
|
|
276
|
+
<img class="rank-avatar" src="${escapeHtml(String(row.avatar ?? "").trim() || assets.defaultAvatar)}" onerror="this.onerror=null;this.src='${assets.defaultAvatar}'" style="top:${y + 10}px" />
|
|
275
277
|
<div class="rank-name" style="top:${y + 10}px">@${escapeHtml(row.name)}</div>
|
|
276
278
|
<div class="rank-count" style="top:${y + 50}px">x${row.count}</div>
|
|
277
279
|
`;
|
package/index.ts
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
definePlugin,
|
|
3
|
+
type CommandDefinition,
|
|
4
|
+
type MessageEvent,
|
|
5
|
+
type MiokuContext,
|
|
6
|
+
} from "mioku";
|
|
2
7
|
import { getService, Services } from "mioku";
|
|
3
8
|
import { initDeerDatabase, type DeerDatabase } from "./db";
|
|
4
9
|
import { handleDeerCommand, parseDeerCommand } from "./commands";
|
|
5
10
|
|
|
6
11
|
const deerpipePlugin = definePlugin({
|
|
7
12
|
name: "deerpipe",
|
|
8
|
-
version: "1.0.0",
|
|
9
|
-
description: "🦌管签到插件,支持自🦌、帮🦌、补🦌、🦌历、🦌榜",
|
|
10
13
|
|
|
11
14
|
async setup(ctx: MiokuContext) {
|
|
12
15
|
ctx.logger.info("deerpipe 插件正在初始化...");
|
|
@@ -30,7 +33,6 @@ const deerpipePlugin = definePlugin({
|
|
|
30
33
|
};
|
|
31
34
|
}
|
|
32
35
|
|
|
33
|
-
// 启动时清掉非本月的旧数据,与原版 cleanup 行为一致
|
|
34
36
|
try {
|
|
35
37
|
const now = new Date();
|
|
36
38
|
await db.cleanupOtherMonths(now.getFullYear(), now.getMonth() + 1);
|
|
@@ -38,7 +40,6 @@ const deerpipePlugin = definePlugin({
|
|
|
38
40
|
ctx.logger.warn(`deerpipe 启动期数据清理失败: ${error}`);
|
|
39
41
|
}
|
|
40
42
|
|
|
41
|
-
// 每周一 4:00 清理跨月数据
|
|
42
43
|
ctx.cron("0 4 * * 1", async () => {
|
|
43
44
|
try {
|
|
44
45
|
const now = new Date();
|
|
@@ -49,13 +50,9 @@ const deerpipePlugin = definePlugin({
|
|
|
49
50
|
}
|
|
50
51
|
});
|
|
51
52
|
|
|
52
|
-
|
|
53
|
-
const text = ctx.text(event);
|
|
54
|
-
if (!text) return;
|
|
55
|
-
|
|
53
|
+
const run = async (event: MessageEvent, text: string) => {
|
|
56
54
|
const cmd = parseDeerCommand(text, Array.from(event.message ?? []));
|
|
57
55
|
if (cmd.type === "none") return;
|
|
58
|
-
|
|
59
56
|
try {
|
|
60
57
|
await handleDeerCommand(cmd, {
|
|
61
58
|
ctx,
|
|
@@ -71,6 +68,52 @@ const deerpipePlugin = definePlugin({
|
|
|
71
68
|
// 忽略二次失败
|
|
72
69
|
}
|
|
73
70
|
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const cmd = (command: CommandDefinition) =>
|
|
74
|
+
ctx.command({ ...command, prefixes: false });
|
|
75
|
+
|
|
76
|
+
cmd({
|
|
77
|
+
name: "🦌",
|
|
78
|
+
match: /^(?:🦌|鹿)(?:\s|$|@)/,
|
|
79
|
+
description: "签到一次(可加 @某人 帮其签到)",
|
|
80
|
+
usage: ".🦌 / .🦌 @张三",
|
|
81
|
+
handler: ({ event, body }) => run(event, body),
|
|
82
|
+
});
|
|
83
|
+
cmd({
|
|
84
|
+
name: "补🦌",
|
|
85
|
+
match: /^补(?:🦌|鹿)(?:\s|\d|$)/,
|
|
86
|
+
description: "补签本月之前未签到的某天",
|
|
87
|
+
usage: ".补🦌 5",
|
|
88
|
+
handler: ({ event, body }) => run(event, body),
|
|
89
|
+
});
|
|
90
|
+
cmd({
|
|
91
|
+
name: "🦌历",
|
|
92
|
+
match: /^(?:🦌|鹿)历(?:\s|$|@)/,
|
|
93
|
+
description: "查看本月🦌签到日历(可加 @某人)",
|
|
94
|
+
usage: ".🦌历 / .🦌历 @张三",
|
|
95
|
+
handler: ({ event, body }) => run(event, body),
|
|
96
|
+
});
|
|
97
|
+
cmd({
|
|
98
|
+
name: "🦌榜",
|
|
99
|
+
match: /^(?:🦌|鹿)榜(?:\s|$)/,
|
|
100
|
+
description: "查看本月本群🦌签到排行榜(仅群组)",
|
|
101
|
+
usage: ".🦌榜",
|
|
102
|
+
handler: ({ event, body }) => run(event, body),
|
|
103
|
+
});
|
|
104
|
+
cmd({
|
|
105
|
+
name: "帮🦌",
|
|
106
|
+
match: /^帮(?:🦌|鹿)(?:\s|$|@)/,
|
|
107
|
+
description: "允许/禁止被别人帮🦌;@某人需管理员",
|
|
108
|
+
usage: ".帮🦌 off",
|
|
109
|
+
handler: ({ event, body }) => run(event, body),
|
|
110
|
+
});
|
|
111
|
+
cmd({
|
|
112
|
+
name: "禁🦌",
|
|
113
|
+
match: /^禁(?:🦌|鹿)(?:\s|@|$)/,
|
|
114
|
+
description: "禁止某人在一段时间内🦌,省略时长视为解禁(仅管理员)",
|
|
115
|
+
usage: ".禁🦌 @张三 1d",
|
|
116
|
+
handler: ({ event, body }) => run(event, body),
|
|
74
117
|
});
|
|
75
118
|
|
|
76
119
|
ctx.logger.info("deerpipe 插件初始化完成");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mioku-plugin-deerpipe",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.3",
|
|
4
4
|
"description": "🦌管签到插件,支持自🦌、帮🦌、补🦌、🦌历、🦌榜",
|
|
5
5
|
"main": "index.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -15,81 +15,7 @@
|
|
|
15
15
|
"services": [
|
|
16
16
|
"screenshot",
|
|
17
17
|
"help"
|
|
18
|
-
]
|
|
19
|
-
"accessHooks": [
|
|
20
|
-
{
|
|
21
|
-
"id": "🦌",
|
|
22
|
-
"match": "/^(?:🦌|鹿)(?:\\s|$|@)/",
|
|
23
|
-
"description": "自🦌或帮🦌"
|
|
24
|
-
},
|
|
25
|
-
{
|
|
26
|
-
"id": "补🦌",
|
|
27
|
-
"match": "/^补(?:🦌|鹿)(?:\\s|\\d|$)/",
|
|
28
|
-
"description": "补签本月之前未签到的某天"
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
"id": "🦌历",
|
|
32
|
-
"match": "/^(?:🦌|鹿)历(?:\\s|$|@)/",
|
|
33
|
-
"description": "查看本月🦌签到日历"
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
"id": "🦌榜",
|
|
37
|
-
"match": "/^(?:🦌|鹿)榜(?:\\s|$)/",
|
|
38
|
-
"description": "查看本月🦌签到排行榜"
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
"id": "帮🦌",
|
|
42
|
-
"match": "/^帮(?:🦌|鹿)(?:\\s|$|@)/",
|
|
43
|
-
"description": "允许/禁止被别人帮🦌"
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
"id": "禁🦌",
|
|
47
|
-
"match": "/^禁(?:🦌|鹿)(?:\\s|@|$)/",
|
|
48
|
-
"description": "禁止某人在一段时间内🦌"
|
|
49
|
-
}
|
|
50
|
-
],
|
|
51
|
-
"help": {
|
|
52
|
-
"title": "🦌管签到",
|
|
53
|
-
"description": "每月🦌管签到,含日历与排行榜",
|
|
54
|
-
"commands": [
|
|
55
|
-
{
|
|
56
|
-
"cmd": "🦌",
|
|
57
|
-
"desc": "签到一次(可加 @某人 帮其签到)",
|
|
58
|
-
"usage": "🦌 / 🦌 @张三",
|
|
59
|
-
"role": "member"
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
"cmd": "补🦌 <日>",
|
|
63
|
-
"desc": "补签本月之前未签到的某天",
|
|
64
|
-
"usage": "补🦌 5",
|
|
65
|
-
"role": "member"
|
|
66
|
-
},
|
|
67
|
-
{
|
|
68
|
-
"cmd": "🦌历",
|
|
69
|
-
"desc": "查看本月🦌签到日历(可加 @某人)",
|
|
70
|
-
"usage": "🦌历 / 🦌历 @张三",
|
|
71
|
-
"role": "member"
|
|
72
|
-
},
|
|
73
|
-
{
|
|
74
|
-
"cmd": "🦌榜",
|
|
75
|
-
"desc": "查看本月本群🦌签到排行榜(仅群组)",
|
|
76
|
-
"usage": "🦌榜",
|
|
77
|
-
"role": "member"
|
|
78
|
-
},
|
|
79
|
-
{
|
|
80
|
-
"cmd": "帮🦌 <on|off> [@某人]",
|
|
81
|
-
"desc": "允许/禁止被别人帮🦌;@某人需管理员",
|
|
82
|
-
"usage": "帮🦌 off",
|
|
83
|
-
"role": "member"
|
|
84
|
-
},
|
|
85
|
-
{
|
|
86
|
-
"cmd": "禁🦌 @某人 [时长]",
|
|
87
|
-
"desc": "禁止某人在一段时间内🦌,省略时长视为解禁(仅管理员)",
|
|
88
|
-
"usage": "禁🦌 @张三 1d",
|
|
89
|
-
"role": "admin"
|
|
90
|
-
}
|
|
91
|
-
]
|
|
92
|
-
}
|
|
18
|
+
]
|
|
93
19
|
},
|
|
94
20
|
"peerDependencies": {
|
|
95
21
|
"mioku": "^1.0.0"
|
package/types.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export interface DeerUser {
|
|
2
2
|
scene: string;
|
|
3
|
-
userId:
|
|
3
|
+
userId: string;
|
|
4
4
|
canBeHelped: boolean;
|
|
5
5
|
noDeerUntil: number | null;
|
|
6
6
|
}
|
|
@@ -11,13 +11,13 @@ export interface DeerCheckInResult {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
export interface DeerRankEntry {
|
|
14
|
-
userId:
|
|
14
|
+
userId: string;
|
|
15
15
|
count: number;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
export interface DeerScene {
|
|
19
19
|
key: string;
|
|
20
20
|
isGroup: boolean;
|
|
21
|
-
groupId?:
|
|
22
|
-
privateUserId?:
|
|
21
|
+
groupId?: string;
|
|
22
|
+
privateUserId?: string;
|
|
23
23
|
}
|
package/utils.ts
CHANGED
|
@@ -2,18 +2,41 @@ import { createGroupRef, type MiokuContext } from "mioku";
|
|
|
2
2
|
import * as fs from "fs/promises";
|
|
3
3
|
import type { DeerScene } from "./types";
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
const QQ_AVATAR_ADAPTERS = new Set(["onebotv11", "icqq"]);
|
|
6
|
+
const QQ_ID_RE = /^\d{4,12}$/;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 生成头像地址。仅在 QQ 系适配器且 id 是 QQ 号时才回退到 qlogo,
|
|
10
|
+
* 其他平台(如 qq-official 的 openid)没有通用头像接口,返回空串由调用方降级。
|
|
11
|
+
*/
|
|
12
|
+
export function getAvatarUrl(
|
|
13
|
+
userId: string,
|
|
14
|
+
options?: { avatar?: unknown; adapter?: unknown },
|
|
15
|
+
): string {
|
|
16
|
+
const direct = String(options?.avatar ?? "").trim();
|
|
17
|
+
if (direct) return direct;
|
|
18
|
+
const id = String(userId ?? "").trim();
|
|
19
|
+
const adapter = String(options?.adapter ?? "").trim().toLowerCase();
|
|
20
|
+
if (adapter && !QQ_AVATAR_ADAPTERS.has(adapter)) return "";
|
|
21
|
+
return QQ_ID_RE.test(id) ? `https://q1.qlogo.cn/g?b=qq&nk=${id}&s=640` : "";
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function resolveAvatarUrl(event: any, userId: string): string {
|
|
25
|
+
const id = String(userId ?? "").trim();
|
|
26
|
+
const isSelf = id !== "" && id === String(event?.user_id ?? "").trim();
|
|
27
|
+
const avatar = isSelf ? event?.sender?.avatar ?? event?.avatar : undefined;
|
|
28
|
+
return getAvatarUrl(id, { avatar, adapter: event?.bot?.adapter });
|
|
7
29
|
}
|
|
8
30
|
|
|
9
|
-
export function getAtUserId(message: any[]):
|
|
31
|
+
export function getAtUserId(message: any[]): string | undefined {
|
|
10
32
|
if (!Array.isArray(message)) return undefined;
|
|
11
33
|
for (const seg of message) {
|
|
12
34
|
if (seg?.type !== "at") continue;
|
|
13
|
-
const raw = seg?.qq ?? seg?.data?.
|
|
14
|
-
if (raw
|
|
15
|
-
const
|
|
16
|
-
if (
|
|
35
|
+
const raw = seg?.data?.qq ?? seg?.qq ?? seg?.data?.target ?? seg?.target;
|
|
36
|
+
if (raw == null) continue;
|
|
37
|
+
const id = String(raw).trim();
|
|
38
|
+
if (!id || id === "all") continue;
|
|
39
|
+
return id;
|
|
17
40
|
}
|
|
18
41
|
return undefined;
|
|
19
42
|
}
|
|
@@ -21,22 +44,22 @@ export function getAtUserId(message: any[]): number | undefined {
|
|
|
21
44
|
export function resolveScene(event: any): DeerScene {
|
|
22
45
|
if (event?.message_type === "group" && event?.group_id != null) {
|
|
23
46
|
return {
|
|
24
|
-
key: `g:${event.group_id}`,
|
|
47
|
+
key: `g:${String(event.group_id)}`,
|
|
25
48
|
isGroup: true,
|
|
26
|
-
groupId:
|
|
49
|
+
groupId: String(event.group_id),
|
|
27
50
|
};
|
|
28
51
|
}
|
|
29
52
|
return {
|
|
30
|
-
key: `p:${event.user_id}`,
|
|
53
|
+
key: `p:${String(event.user_id ?? "")}`,
|
|
31
54
|
isGroup: false,
|
|
32
|
-
privateUserId:
|
|
55
|
+
privateUserId: String(event?.user_id ?? ""),
|
|
33
56
|
};
|
|
34
57
|
}
|
|
35
58
|
|
|
36
59
|
export async function resolveUserName(
|
|
37
60
|
ctx: MiokuContext,
|
|
38
61
|
event: any,
|
|
39
|
-
userId:
|
|
62
|
+
userId: string,
|
|
40
63
|
): Promise<string> {
|
|
41
64
|
const bot = event?.bot;
|
|
42
65
|
if (event?.message_type === "group" && event?.group_id != null && bot) {
|
|
@@ -50,7 +73,7 @@ export async function resolveUserName(
|
|
|
50
73
|
// fall through
|
|
51
74
|
}
|
|
52
75
|
}
|
|
53
|
-
if (
|
|
76
|
+
if (String(event?.user_id ?? "") === userId && event?.sender) {
|
|
54
77
|
const name =
|
|
55
78
|
String(event.sender?.card || "").trim() ||
|
|
56
79
|
String(event.sender?.nickname || "").trim();
|