koishi-plugin-lili-hub 0.3.2 → 0.3.4
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 +152 -0
- package/lib/index.js +749 -73
- package/package.json +3 -2
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { Context, Schema } from 'koishi';
|
|
2
|
+
export declare const name = "lili-hub";
|
|
3
|
+
export declare const inject: string[];
|
|
4
|
+
declare module 'koishi' {
|
|
5
|
+
interface Tables {
|
|
6
|
+
lili_message: LiliMessage;
|
|
7
|
+
lili_roulette: LiliRoulette;
|
|
8
|
+
lili_vote: LiliVote;
|
|
9
|
+
lili_duel: LiliDuel;
|
|
10
|
+
lili_chain_record: LiliChainRecord;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
interface LiliMessage {
|
|
14
|
+
id: number;
|
|
15
|
+
gid: string;
|
|
16
|
+
userId: string;
|
|
17
|
+
userName: string;
|
|
18
|
+
content: string;
|
|
19
|
+
timestamp: number;
|
|
20
|
+
messageId: string;
|
|
21
|
+
}
|
|
22
|
+
interface LiliRoulette {
|
|
23
|
+
id: number;
|
|
24
|
+
gid: string;
|
|
25
|
+
hitUserId: string;
|
|
26
|
+
timestamp: number;
|
|
27
|
+
}
|
|
28
|
+
interface LiliVote {
|
|
29
|
+
id: number;
|
|
30
|
+
gid: string;
|
|
31
|
+
targetUserId: string;
|
|
32
|
+
voterUserId: string;
|
|
33
|
+
voteType: 'mute' | 'unmute';
|
|
34
|
+
timestamp: number;
|
|
35
|
+
}
|
|
36
|
+
interface LiliDuel {
|
|
37
|
+
id: number;
|
|
38
|
+
gid: string;
|
|
39
|
+
winnerId: string;
|
|
40
|
+
winnerName: string;
|
|
41
|
+
loserId: string;
|
|
42
|
+
loserName: string;
|
|
43
|
+
winnerTime: number;
|
|
44
|
+
loserTime: number;
|
|
45
|
+
timestamp: number;
|
|
46
|
+
}
|
|
47
|
+
interface LiliChainRecord {
|
|
48
|
+
id: number;
|
|
49
|
+
gid: string;
|
|
50
|
+
userId: string;
|
|
51
|
+
userName: string;
|
|
52
|
+
chainContent: string;
|
|
53
|
+
timestamp: number;
|
|
54
|
+
}
|
|
55
|
+
interface EventLine {
|
|
56
|
+
voice: string;
|
|
57
|
+
text: string;
|
|
58
|
+
}
|
|
59
|
+
interface RouletteConfig {
|
|
60
|
+
chamberSize: number;
|
|
61
|
+
muteDurationMin: number;
|
|
62
|
+
muteDurationMax: number;
|
|
63
|
+
misfireRate: number;
|
|
64
|
+
drinksToDrunkMin: number;
|
|
65
|
+
drinksToDrunkMax: number;
|
|
66
|
+
drunkRateIncrement: number;
|
|
67
|
+
drunkDurationMin: number;
|
|
68
|
+
drunkDurationMax: number;
|
|
69
|
+
drunkMisfireRate: number;
|
|
70
|
+
drunkRampageRate: number;
|
|
71
|
+
drunkRevengeRate: number;
|
|
72
|
+
maxHistoryRounds: number;
|
|
73
|
+
groups: string;
|
|
74
|
+
groupMode: 'whitelist' | 'blacklist';
|
|
75
|
+
}
|
|
76
|
+
interface VoiceConfig {
|
|
77
|
+
voiceEnabled: boolean;
|
|
78
|
+
spin: EventLine;
|
|
79
|
+
shoot: EventLine;
|
|
80
|
+
hit: EventLine;
|
|
81
|
+
misfire: EventLine;
|
|
82
|
+
drunkMisfire: EventLine;
|
|
83
|
+
firstBlood: EventLine;
|
|
84
|
+
doubleKill: EventLine;
|
|
85
|
+
tripleKill: EventLine;
|
|
86
|
+
quadraKill: EventLine;
|
|
87
|
+
pentaKill: EventLine;
|
|
88
|
+
rampage: EventLine;
|
|
89
|
+
revenge: EventLine;
|
|
90
|
+
noTarget: EventLine;
|
|
91
|
+
drink: EventLine;
|
|
92
|
+
drunk: EventLine;
|
|
93
|
+
stillDrunk: EventLine;
|
|
94
|
+
sober: EventLine;
|
|
95
|
+
}
|
|
96
|
+
interface VoteMuteConfig {
|
|
97
|
+
enabled: boolean;
|
|
98
|
+
groups: string;
|
|
99
|
+
groupMode: 'whitelist' | 'blacklist';
|
|
100
|
+
voteWindowDays: number;
|
|
101
|
+
muteThreshold: number;
|
|
102
|
+
unmuteThreshold: number;
|
|
103
|
+
muteSeconds: number;
|
|
104
|
+
}
|
|
105
|
+
interface FoldConfig {
|
|
106
|
+
enabled: boolean;
|
|
107
|
+
groups: string;
|
|
108
|
+
groupMode: 'whitelist' | 'blacklist';
|
|
109
|
+
minutes: number;
|
|
110
|
+
}
|
|
111
|
+
interface ImitationConfig {
|
|
112
|
+
enabled: boolean;
|
|
113
|
+
groups: string;
|
|
114
|
+
groupMode: 'whitelist' | 'blacklist';
|
|
115
|
+
interval: number;
|
|
116
|
+
rate: number;
|
|
117
|
+
}
|
|
118
|
+
interface DedupConfig {
|
|
119
|
+
enabled: boolean;
|
|
120
|
+
groups: string;
|
|
121
|
+
groupMode: 'whitelist' | 'blacklist';
|
|
122
|
+
}
|
|
123
|
+
interface DuelConfig {
|
|
124
|
+
enabled: boolean;
|
|
125
|
+
groups: string;
|
|
126
|
+
groupMode: 'whitelist' | 'blacklist';
|
|
127
|
+
countdownSeconds: number;
|
|
128
|
+
acceptTimeoutSeconds: number;
|
|
129
|
+
drawTimeoutSeconds: number;
|
|
130
|
+
earlyDrawMuteSeconds: number;
|
|
131
|
+
lateMuteSeconds: number;
|
|
132
|
+
}
|
|
133
|
+
interface ChainConfig {
|
|
134
|
+
enabled: boolean;
|
|
135
|
+
groups: string;
|
|
136
|
+
groupMode: 'whitelist' | 'blacklist';
|
|
137
|
+
defaultDeadlineHour: number;
|
|
138
|
+
}
|
|
139
|
+
export interface Config {
|
|
140
|
+
debug: boolean;
|
|
141
|
+
roulette: RouletteConfig;
|
|
142
|
+
voice: VoiceConfig;
|
|
143
|
+
voteMute: VoteMuteConfig;
|
|
144
|
+
fold: FoldConfig;
|
|
145
|
+
imitation: ImitationConfig;
|
|
146
|
+
dedup: DedupConfig;
|
|
147
|
+
duel: DuelConfig;
|
|
148
|
+
chain: ChainConfig;
|
|
149
|
+
}
|
|
150
|
+
export declare const Config: Schema<Config>;
|
|
151
|
+
export declare function apply(ctx: Context, config: Config): void;
|
|
152
|
+
export {};
|
package/lib/index.js
CHANGED
|
@@ -16,18 +16,20 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
16
16
|
};
|
|
17
17
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
18
18
|
|
|
19
|
-
//
|
|
19
|
+
// src/index.ts
|
|
20
20
|
var src_exports = {};
|
|
21
21
|
__export(src_exports, {
|
|
22
22
|
Config: () => Config,
|
|
23
23
|
apply: () => apply,
|
|
24
24
|
inject: () => inject,
|
|
25
|
-
name: () => name
|
|
25
|
+
name: () => name,
|
|
26
|
+
optional: () => optional
|
|
26
27
|
});
|
|
27
28
|
module.exports = __toCommonJS(src_exports);
|
|
28
29
|
var import_koishi = require("koishi");
|
|
29
30
|
var name = "lili-hub";
|
|
30
31
|
var inject = ["database"];
|
|
32
|
+
var optional = ["schedule", "dialogue"];
|
|
31
33
|
var SPIN_TEXT = [
|
|
32
34
|
"\u4E3D\u4E3D\u638F\u51FA\u4E86\u5DE6\u8F6E\u624B\u67AA\uFF0C\u5728\u624B\u91CC\u8F6C\u4E86\u8F6C\u2026\u2026",
|
|
33
35
|
"\u4E3D\u4E3D\u5439\u4E86\u5439\u67AA\u53E3\uFF0C\u719F\u7EC3\u5730\u88C5\u4E0A\u4E86\u5B50\u5F39\u2026\u2026",
|
|
@@ -291,7 +293,8 @@ var Config = import_koishi.Schema.object({
|
|
|
291
293
|
chain: import_koishi.Schema.object({
|
|
292
294
|
enabled: import_koishi.Schema.boolean().description("\u542F\u7528\u7FA4\u63A5\u9F99\u529F\u80FD").default(false),
|
|
293
295
|
groups: import_koishi.Schema.string().description("\u7FA4\u53F7\u5217\u8868\uFF08\u9017\u53F7\u5206\u9694\uFF09").default(""),
|
|
294
|
-
groupMode: import_koishi.Schema.union(["whitelist", "blacklist"]).description("\u7FA4\u7EC4\u8FC7\u6EE4\u6A21\u5F0F\uFF1A\u767D\u540D\u5355 / \u9ED1\u540D\u5355").default("whitelist")
|
|
296
|
+
groupMode: import_koishi.Schema.union(["whitelist", "blacklist"]).description("\u7FA4\u7EC4\u8FC7\u6EE4\u6A21\u5F0F\uFF1A\u767D\u540D\u5355 / \u9ED1\u540D\u5355").default("whitelist"),
|
|
297
|
+
defaultDeadlineHour: import_koishi.Schema.number().description("\u9ED8\u8BA4\u622A\u6B62\u5C0F\u65F6\uFF080-23\uFF09\uFF0C\u521B\u5EFA\u63A5\u9F99\u4E0D\u586B\u65F6\u95F4\u65F6\u9ED8\u8BA4\u5F53\u5929\u6B64\u65F6\u95F4\u622A\u6B62").default(21).min(0).max(23)
|
|
295
298
|
}).description("\u{1F4DD} \u7FA4\u63A5\u9F99")
|
|
296
299
|
});
|
|
297
300
|
function getGroupId(session) {
|
|
@@ -307,6 +310,49 @@ function parseGroupList(raw) {
|
|
|
307
310
|
if (!raw || !raw.trim()) return [];
|
|
308
311
|
return raw.split(",").map((s) => s.trim()).filter(Boolean);
|
|
309
312
|
}
|
|
313
|
+
function elementsToCQCode(elements) {
|
|
314
|
+
if (!elements || !Array.isArray(elements)) return "";
|
|
315
|
+
return elements.map((el) => {
|
|
316
|
+
switch (el.type) {
|
|
317
|
+
case "text":
|
|
318
|
+
return el.attrs?.content || "";
|
|
319
|
+
case "image": {
|
|
320
|
+
const file = el.attrs?.file || el.attrs?.url || "";
|
|
321
|
+
const url = el.attrs?.url || "";
|
|
322
|
+
if (url && file && url !== file) return `[CQ:image,file=${file},url=${url}]`;
|
|
323
|
+
return `[CQ:image,file=${file}]`;
|
|
324
|
+
}
|
|
325
|
+
case "at":
|
|
326
|
+
return `[CQ:at,qq=${el.attrs?.id || ""}]`;
|
|
327
|
+
case "face": {
|
|
328
|
+
const id = el.attrs?.id;
|
|
329
|
+
return id != null ? `[CQ:face,id=${id}]` : "";
|
|
330
|
+
}
|
|
331
|
+
case "mface": {
|
|
332
|
+
const id = el.attrs?.emoji_id || el.attrs?.emojiId;
|
|
333
|
+
return id ? `[CQ:mface,id=${id}]` : "";
|
|
334
|
+
}
|
|
335
|
+
case "record": {
|
|
336
|
+
const file = el.attrs?.file;
|
|
337
|
+
return file ? `[CQ:record,file=${file}]` : "";
|
|
338
|
+
}
|
|
339
|
+
case "video": {
|
|
340
|
+
const file = el.attrs?.file;
|
|
341
|
+
return file ? `[CQ:video,file=${file}]` : "";
|
|
342
|
+
}
|
|
343
|
+
case "reply":
|
|
344
|
+
return "";
|
|
345
|
+
// 回复元素不参与
|
|
346
|
+
case "forward":
|
|
347
|
+
return "";
|
|
348
|
+
// 嵌套转发跳过
|
|
349
|
+
default: {
|
|
350
|
+
if (el.attrs?.content) return String(el.attrs.content);
|
|
351
|
+
return "";
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}).join("");
|
|
355
|
+
}
|
|
310
356
|
function normalizeText(text) {
|
|
311
357
|
return text.replace(/\[CQ:[^\]]*\]/g, "").replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim();
|
|
312
358
|
}
|
|
@@ -389,19 +435,252 @@ function apply(ctx, config) {
|
|
|
389
435
|
loserTime: "unsigned",
|
|
390
436
|
timestamp: "unsigned"
|
|
391
437
|
}, { autoInc: true });
|
|
438
|
+
ctx.model.extend("lili_chain_record", {
|
|
439
|
+
id: "unsigned",
|
|
440
|
+
gid: "string",
|
|
441
|
+
userId: "string",
|
|
442
|
+
userName: "string",
|
|
443
|
+
chainContent: "string",
|
|
444
|
+
timestamp: "unsigned"
|
|
445
|
+
}, { autoInc: true });
|
|
446
|
+
ctx.model.extend("lili_chain", {
|
|
447
|
+
id: "unsigned",
|
|
448
|
+
gid: "string",
|
|
449
|
+
chainId: "unsigned",
|
|
450
|
+
trueId: "string",
|
|
451
|
+
content: "string",
|
|
452
|
+
creatorId: "string",
|
|
453
|
+
creatorName: "string",
|
|
454
|
+
participants: "text",
|
|
455
|
+
createdAt: "unsigned",
|
|
456
|
+
deadline: "unsigned",
|
|
457
|
+
active: "boolean"
|
|
458
|
+
}, { autoInc: true });
|
|
392
459
|
const drinkStates = /* @__PURE__ */ new Map();
|
|
393
460
|
const rouletteStates = /* @__PURE__ */ new Map();
|
|
394
461
|
const imitationCounters = /* @__PURE__ */ new Map();
|
|
395
462
|
const duelStates = /* @__PURE__ */ new Map();
|
|
396
463
|
const chainStates = /* @__PURE__ */ new Map();
|
|
464
|
+
let chainTrueIdCounter = 0;
|
|
465
|
+
async function initTrueIdCounter() {
|
|
466
|
+
try {
|
|
467
|
+
const rows = await ctx.database.get("lili_chain", {}, { fields: ["trueId"] });
|
|
468
|
+
let maxNum = 0;
|
|
469
|
+
for (const row of rows) {
|
|
470
|
+
if (row.trueId && /^JL(\d+)$/.test(row.trueId)) {
|
|
471
|
+
const n = parseInt(RegExp.$1);
|
|
472
|
+
if (n > maxNum) maxNum = n;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
chainTrueIdCounter = maxNum;
|
|
476
|
+
} catch {
|
|
477
|
+
chainTrueIdCounter = 0;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
function nextTrueId() {
|
|
481
|
+
chainTrueIdCounter++;
|
|
482
|
+
return `JL${String(chainTrueIdCounter).padStart(4, "0")}`;
|
|
483
|
+
}
|
|
484
|
+
async function loadChains() {
|
|
485
|
+
try {
|
|
486
|
+
await initTrueIdCounter();
|
|
487
|
+
const rows = await ctx.database.get("lili_chain", { active: true });
|
|
488
|
+
for (const row of rows) {
|
|
489
|
+
let trueId = row.trueId;
|
|
490
|
+
if (!trueId) {
|
|
491
|
+
trueId = nextTrueId();
|
|
492
|
+
await ctx.database.set("lili_chain", { gid: row.gid, chainId: row.chainId }, { trueId });
|
|
493
|
+
}
|
|
494
|
+
const chain = {
|
|
495
|
+
id: row.chainId,
|
|
496
|
+
trueId,
|
|
497
|
+
content: row.content,
|
|
498
|
+
creatorId: row.creatorId,
|
|
499
|
+
creatorName: row.creatorName,
|
|
500
|
+
participants: JSON.parse(row.participants || "[]"),
|
|
501
|
+
createdAt: row.createdAt,
|
|
502
|
+
deadline: row.deadline
|
|
503
|
+
};
|
|
504
|
+
const arr = chainStates.get(row.gid) || [];
|
|
505
|
+
arr.push(chain);
|
|
506
|
+
chainStates.set(row.gid, arr);
|
|
507
|
+
if (chain.deadline > Date.now()) {
|
|
508
|
+
scheduleChainDeadline(row.gid, chain);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
dbg("\u63A5\u9F99-\u4ECE\u6570\u636E\u5E93\u52A0\u8F7D", { count: rows.length });
|
|
512
|
+
} catch (e) {
|
|
513
|
+
dbg("\u63A5\u9F99-\u52A0\u8F7D\u5931\u8D25", { error: String(e) });
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
async function saveChain(gid, chain) {
|
|
517
|
+
try {
|
|
518
|
+
const existing = await ctx.database.get("lili_chain", { gid, chainId: chain.id });
|
|
519
|
+
const data = {
|
|
520
|
+
gid,
|
|
521
|
+
chainId: chain.id,
|
|
522
|
+
trueId: chain.trueId,
|
|
523
|
+
content: chain.content,
|
|
524
|
+
creatorId: chain.creatorId,
|
|
525
|
+
creatorName: chain.creatorName,
|
|
526
|
+
participants: JSON.stringify(chain.participants),
|
|
527
|
+
createdAt: chain.createdAt,
|
|
528
|
+
deadline: chain.deadline,
|
|
529
|
+
active: true
|
|
530
|
+
};
|
|
531
|
+
if (existing.length > 0) {
|
|
532
|
+
await ctx.database.set("lili_chain", { gid, chainId: chain.id }, data);
|
|
533
|
+
} else {
|
|
534
|
+
await ctx.database.create("lili_chain", data);
|
|
535
|
+
}
|
|
536
|
+
} catch (e) {
|
|
537
|
+
dbg("\u63A5\u9F99-\u4FDD\u5B58\u5931\u8D25", { gid, chainId: chain.id, error: String(e) });
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
async function deactivateChain(gid, chainId) {
|
|
541
|
+
try {
|
|
542
|
+
await ctx.database.set("lili_chain", { gid, chainId }, { active: false });
|
|
543
|
+
} catch (e) {
|
|
544
|
+
dbg("\u63A5\u9F99-\u6807\u8BB0\u5220\u9664\u5931\u8D25", { gid, chainId, error: String(e) });
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
loadChains();
|
|
548
|
+
async function scheduleChainDeadline(gid, chain, session) {
|
|
549
|
+
try {
|
|
550
|
+
const cmd = `\u67E5\u770B\u63A5\u9F99 ${chain.trueId}`;
|
|
551
|
+
const existing = await ctx.database.get("schedule", { command: cmd });
|
|
552
|
+
for (const e of existing) {
|
|
553
|
+
await ctx.database.remove("schedule", { id: e.id });
|
|
554
|
+
}
|
|
555
|
+
const bots = ctx.bots ? [...ctx.bots] : [];
|
|
556
|
+
const botSid = bots.length > 0 ? bots[0].sid : ctx.bot?.sid || "";
|
|
557
|
+
if (!botSid) return;
|
|
558
|
+
let event;
|
|
559
|
+
if (session?.event) {
|
|
560
|
+
event = JSON.parse(JSON.stringify(session.event));
|
|
561
|
+
event.timestamp = Date.now();
|
|
562
|
+
event.message = { ...event.message || {}, content: cmd };
|
|
563
|
+
} else {
|
|
564
|
+
event = {
|
|
565
|
+
selfId: botSid,
|
|
566
|
+
platform: bots[0]?.platform || "onebot",
|
|
567
|
+
timestamp: Date.now(),
|
|
568
|
+
type: "message-created",
|
|
569
|
+
subtype: "group",
|
|
570
|
+
channel: { id: gid, type: 0 },
|
|
571
|
+
guild: { id: gid },
|
|
572
|
+
user: { id: chain.creatorId, name: chain.creatorName },
|
|
573
|
+
message: { content: cmd },
|
|
574
|
+
_type: bots[0]?.platform || "onebot"
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
await ctx.database.create("schedule", {
|
|
578
|
+
assignee: botSid,
|
|
579
|
+
time: new Date(chain.deadline),
|
|
580
|
+
interval: 0,
|
|
581
|
+
command: cmd,
|
|
582
|
+
event
|
|
583
|
+
});
|
|
584
|
+
dbg("\u63A5\u9F99-schedule\u5DF2\u5199\u5165", { gid, trueId: chain.trueId, deadline: new Date(chain.deadline).toLocaleString("zh-CN") });
|
|
585
|
+
} catch (e) {
|
|
586
|
+
dbg("\u63A5\u9F99-schedule\u5199\u5165\u5931\u8D25", { error: String(e) });
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
function getNextChainId(gid) {
|
|
590
|
+
const chains = chainStates.get(gid);
|
|
591
|
+
if (!chains || chains.length === 0) return 1;
|
|
592
|
+
const maxId = Math.max(...chains.map((c) => c.id));
|
|
593
|
+
return maxId + 1;
|
|
594
|
+
}
|
|
397
595
|
function formatChain(chain) {
|
|
398
|
-
const
|
|
596
|
+
const deadlineStr = new Date(chain.deadline).toLocaleString("zh-CN", {
|
|
597
|
+
month: "2-digit",
|
|
598
|
+
day: "2-digit",
|
|
599
|
+
hour: "2-digit",
|
|
600
|
+
minute: "2-digit"
|
|
601
|
+
});
|
|
602
|
+
const header = `\u{1F4DD} \u63A5\u9F99${chain.id}\uFF1A${chain.content}\uFF08\u622A\u6B62 ${deadlineStr}\uFF09`;
|
|
603
|
+
const lines = [header];
|
|
399
604
|
for (let i = 0; i < chain.participants.length; i++) {
|
|
400
605
|
const p = chain.participants[i];
|
|
401
|
-
|
|
606
|
+
const remark = p.remark ? `\uFF08${p.remark}\uFF09` : "";
|
|
607
|
+
lines.push(`${i + 1}.${p.userName}\uFF08${p.userId}\uFF09${remark}`);
|
|
402
608
|
}
|
|
403
609
|
return lines.join("\n");
|
|
404
610
|
}
|
|
611
|
+
function parseDeadline(raw, defaultHour) {
|
|
612
|
+
if (!raw || !raw.trim()) return null;
|
|
613
|
+
const s = raw.trim();
|
|
614
|
+
const now = /* @__PURE__ */ new Date();
|
|
615
|
+
const fullMatch = s.match(/^(\d{1,2})-(\d{1,2})\s+(\d{1,2})[::](\d{2})$/);
|
|
616
|
+
if (fullMatch) {
|
|
617
|
+
const [_, m, d, h2, min] = fullMatch;
|
|
618
|
+
const dt = new Date(now.getFullYear(), parseInt(m) - 1, parseInt(d), parseInt(h2), parseInt(min), 0, 0);
|
|
619
|
+
if (isNaN(dt.getTime())) return null;
|
|
620
|
+
if (dt.getTime() < Date.now() - 864e5) dt.setFullYear(dt.getFullYear() + 1);
|
|
621
|
+
return dt.getTime();
|
|
622
|
+
}
|
|
623
|
+
const timeMatch = s.match(/^(\d{1,2})[::](\d{2})$/);
|
|
624
|
+
if (timeMatch) {
|
|
625
|
+
const [_, h2, min] = timeMatch;
|
|
626
|
+
const dt = new Date(now.getFullYear(), now.getMonth(), now.getDate(), parseInt(h2), parseInt(min), 0, 0);
|
|
627
|
+
if (isNaN(dt.getTime())) return null;
|
|
628
|
+
if (dt.getTime() < Date.now()) dt.setDate(dt.getDate() + 1);
|
|
629
|
+
return dt.getTime();
|
|
630
|
+
}
|
|
631
|
+
return null;
|
|
632
|
+
}
|
|
633
|
+
function getDefaultDeadline(defaultHour) {
|
|
634
|
+
const now = /* @__PURE__ */ new Date();
|
|
635
|
+
const dt = new Date(now.getFullYear(), now.getMonth(), now.getDate(), defaultHour, 0, 0, 0);
|
|
636
|
+
if (dt.getTime() < Date.now()) dt.setDate(dt.getDate() + 1);
|
|
637
|
+
return dt.getTime();
|
|
638
|
+
}
|
|
639
|
+
async function cleanupExpiredChains(gid) {
|
|
640
|
+
const chains = chainStates.get(gid);
|
|
641
|
+
if (!chains) return;
|
|
642
|
+
const now = Date.now();
|
|
643
|
+
const expired = chains.filter((c) => c.deadline <= now);
|
|
644
|
+
const remaining = chains.filter((c) => c.deadline > now);
|
|
645
|
+
for (const chain of expired) {
|
|
646
|
+
await deactivateChain(gid, chain.id);
|
|
647
|
+
}
|
|
648
|
+
if (remaining.length === 0) {
|
|
649
|
+
chainStates.delete(gid);
|
|
650
|
+
} else if (remaining.length !== chains.length) {
|
|
651
|
+
chainStates.set(gid, remaining);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
async function recordChainParticipation(gid, userId, userName, chainContent) {
|
|
655
|
+
try {
|
|
656
|
+
await ctx.database.create("lili_chain_record", {
|
|
657
|
+
gid,
|
|
658
|
+
userId,
|
|
659
|
+
userName,
|
|
660
|
+
chainContent,
|
|
661
|
+
timestamp: Date.now()
|
|
662
|
+
});
|
|
663
|
+
dbg("\u63A5\u9F99-\u8BB0\u5F55\u53C2\u4E0E", { gid, userId, userName });
|
|
664
|
+
} catch (e) {
|
|
665
|
+
dbg("\u63A5\u9F99-\u8BB0\u5F55\u53C2\u4E0E\u5931\u8D25", { gid, error: String(e) });
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
async function resolveDialogueChainContent(session, gid, index) {
|
|
669
|
+
try {
|
|
670
|
+
const allDialogues = await ctx.database.get("dialogue", {}, { sort: { id: "asc" }, limit: index });
|
|
671
|
+
if (!allDialogues || allDialogues.length < index) return null;
|
|
672
|
+
const d = allDialogues[index - 1];
|
|
673
|
+
if (!d) return null;
|
|
674
|
+
const question = d.question || d.original || "";
|
|
675
|
+
const answer = d.answer || "";
|
|
676
|
+
const content = question ? `${question} ${answer}` : answer;
|
|
677
|
+
dbg("\u63A5\u9F99-dialogue\u5F15\u7528", { gid, dialogueIndex: index, dialogueId: d.id, question, answer: answer.substring(0, 50) });
|
|
678
|
+
return content || null;
|
|
679
|
+
} catch (e) {
|
|
680
|
+
ctx.logger("lili-hub").warn(`[\u63A5\u9F99] dialogue\u5F15\u7528\u5931\u8D25 gid=${gid} index=${index} error=${String(e)}`);
|
|
681
|
+
return null;
|
|
682
|
+
}
|
|
683
|
+
}
|
|
405
684
|
function isDrunk(gid) {
|
|
406
685
|
const s = drinkStates.get(gid);
|
|
407
686
|
if (!s || s.drunkUntil === 0) return false;
|
|
@@ -716,26 +995,43 @@ ${import_koishi.segment.at(targetId)} \u5DF2\u88AB\u89E3\u9664\u7981\u8A00\u3002
|
|
|
716
995
|
return `\u{1F50A} \u5DF2\u64A4\u56DE\u4F60\u5BF9 ${import_koishi.segment.at(targetId)} \u7684\u7981\u8A00\u7968\u3002
|
|
717
996
|
\u{1F4CA} \u5F53\u524D\u7981\u8A00\u7968\uFF1A${muteCount}\uFF08\u9700 \u2264 ${vm.unmuteThreshold} \u624D\u4F1A\u89E3\u7981\uFF09`;
|
|
718
997
|
});
|
|
719
|
-
ctx.command("\u4E3D\u4E3D\u6298\u53E0
|
|
998
|
+
ctx.command("\u4E3D\u4E3D\u6298\u53E0 [target:text] [minutes:number]", "\u5408\u5E76\u8F6C\u53D1\u5E76\u64A4\u56DE\u76EE\u6807\u7528\u6237\u6700\u8FD1 X \u5206\u949F\u5185\u7684\u6D88\u606F").action(async ({ session }, targetArg, minutesArg) => {
|
|
720
999
|
if (!session?.userId) return;
|
|
721
1000
|
const gid = getGroupId(session);
|
|
722
1001
|
const fc = config.fold;
|
|
723
1002
|
if (!fc.enabled) return;
|
|
724
1003
|
if (!isGroupAllowed(gid, fc.groups, fc.groupMode)) return;
|
|
725
|
-
|
|
1004
|
+
let targetId = null;
|
|
1005
|
+
let explicitMins = void 0;
|
|
1006
|
+
if (session.elements) {
|
|
1007
|
+
const atEl = session.elements.find((el) => el.type === "at" && el.attrs?.id);
|
|
1008
|
+
if (atEl) targetId = String(atEl.attrs.id);
|
|
1009
|
+
}
|
|
1010
|
+
if (!targetId && targetArg) {
|
|
1011
|
+
const t = targetArg.trim();
|
|
1012
|
+
if (/^\d{5,12}$/.test(t)) {
|
|
1013
|
+
targetId = t;
|
|
1014
|
+
} else if (/^\d{1,4}$/.test(t)) {
|
|
1015
|
+
explicitMins = parseInt(t);
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
if (!targetId) targetId = String(session.userId);
|
|
1019
|
+
const foldedMinutes = typeof minutesArg === "number" && minutesArg >= 1 && minutesArg <= 1440 ? minutesArg : explicitMins && explicitMins >= 1 && explicitMins <= 1440 ? explicitMins : fc.minutes;
|
|
726
1020
|
if (!targetId) return "\u{1F4E6} \u65E0\u6CD5\u89E3\u6790\u76EE\u6807\u7528\u6237\u3002\u8BF7\u4F7F\u7528 @ \u529F\u80FD\u6216\u76F4\u63A5\u8F93\u5165 QQ \u53F7\u3002";
|
|
727
|
-
const foldedMinutes = typeof minutesArg === "number" && minutesArg >= 1 && minutesArg <= 1440 ? minutesArg : fc.minutes;
|
|
728
1021
|
const since = Date.now() - foldedMinutes * 60 * 1e3;
|
|
729
|
-
|
|
1022
|
+
ctx.logger("lili-hub").debug(`[\u6298\u53E0] \u67E5\u8BE2 gid=${gid} targetId=${targetId} since=${new Date(since).toLocaleTimeString("zh-CN")} foldedMinutes=${foldedMinutes}`);
|
|
730
1023
|
let messages;
|
|
731
1024
|
try {
|
|
732
|
-
messages = await ctx.database.get("lili_message", { gid, userId: targetId, timestamp: { $gte: since } }, { sort: { timestamp: "asc" }, limit: 100 });
|
|
1025
|
+
messages = await ctx.database.get("lili_message", { gid, userId: String(targetId), timestamp: { $gte: since } }, { sort: { timestamp: "asc" }, limit: 100 });
|
|
733
1026
|
} catch (e) {
|
|
734
|
-
|
|
1027
|
+
ctx.logger("lili-hub").warn(`[\u6298\u53E0] \u6570\u636E\u5E93\u67E5\u8BE2\u5931\u8D25 gid=${gid} targetId=${targetId} error=${String(e)}`);
|
|
735
1028
|
return "\u{1F4E6} \u67E5\u8BE2\u6D88\u606F\u5931\u8D25\u3002";
|
|
736
1029
|
}
|
|
737
|
-
|
|
738
|
-
if (!messages || messages.length === 0)
|
|
1030
|
+
ctx.logger("lili-hub").debug(`[\u6298\u53E0] \u67E5\u8BE2\u7ED3\u679C gid=${gid} targetId=${targetId} count=${messages?.length || 0}`);
|
|
1031
|
+
if (!messages || messages.length === 0) {
|
|
1032
|
+
ctx.logger("lili-hub").warn(`[\u6298\u53E0] \u65E0\u53D1\u8A00\u8BB0\u5F55 gid=${gid} targetId=${targetId} since=${new Date(since).toLocaleTimeString("zh-CN")} minutes=${foldedMinutes}`);
|
|
1033
|
+
return `\u{1F4E6} ${import_koishi.segment.at(targetId)} \u6700\u8FD1 ${foldedMinutes} \u5206\u949F\u5185\u6CA1\u6709\u53D1\u8A00\u8BB0\u5F55\u3002`;
|
|
1034
|
+
}
|
|
739
1035
|
const nodes = messages.map((m) => ({
|
|
740
1036
|
type: "node",
|
|
741
1037
|
data: { name: m.userName, uin: m.userId, content: m.content, time: String(Math.floor(m.timestamp / 1e3)) }
|
|
@@ -758,14 +1054,18 @@ ${import_koishi.segment.at(targetId)} \u5DF2\u88AB\u89E3\u9664\u7981\u8A00\u3002
|
|
|
758
1054
|
await session.send(`\u{1F4E6} \u4E3D\u4E3D\u6298\u53E0\u4E86 ${import_koishi.segment.at(targetId)} \u6700\u8FD1 ${foldedMinutes} \u5206\u949F\u7684\u6D88\u606F\uFF1A
|
|
759
1055
|
` + lines.join("\n"));
|
|
760
1056
|
}
|
|
1057
|
+
let deletedCount = 0;
|
|
761
1058
|
for (const m of messages) {
|
|
762
1059
|
if (m.messageId) {
|
|
763
1060
|
try {
|
|
764
|
-
await session.bot.deleteMessage(
|
|
765
|
-
|
|
1061
|
+
await session.bot.deleteMessage(gid, String(m.messageId));
|
|
1062
|
+
deletedCount++;
|
|
1063
|
+
} catch (e) {
|
|
1064
|
+
dbg("\u6298\u53E0-\u64A4\u56DE\u5931\u8D25", { messageId: m.messageId, error: String(e) });
|
|
766
1065
|
}
|
|
767
1066
|
}
|
|
768
1067
|
}
|
|
1068
|
+
dbg("\u6298\u53E0-\u64A4\u56DE\u5B8C\u6210", { gid, total: messages.length, deleted: deletedCount });
|
|
769
1069
|
});
|
|
770
1070
|
ctx.command("\u4E3D\u4E3D\u5BF9\u51B3 <user:user>", "\u53D1\u8D77\u5BF9\u51B3").action(async ({ session }, targetUser) => {
|
|
771
1071
|
if (!session?.userId) return;
|
|
@@ -1037,76 +1337,396 @@ ${import_koishi.segment.at(targetId)} \u5DF2\u88AB\u89E3\u9664\u7981\u8A00\u3002
|
|
|
1037
1337
|
}
|
|
1038
1338
|
return next();
|
|
1039
1339
|
});
|
|
1040
|
-
ctx.command("\u521B\u5EFA\u63A5\u9F99 <content:
|
|
1340
|
+
ctx.command("\u521B\u5EFA\u63A5\u9F99 <content:text>", "\u521B\u5EFA\u7FA4\u63A5\u9F99\uFF0C\u652F\u6301\u622A\u6B62\u65F6\u95F4\uFF08\u5982 21:00 \u6216 06-30 21:00\uFF09\uFF0C\u4E0D\u586B\u9ED8\u8BA4\u5F53\u5929\u914D\u7F6E\u7684\u5C0F\u65F6\u6574\u70B9").action(async ({ session }, content) => {
|
|
1041
1341
|
if (!session?.userId) return;
|
|
1042
1342
|
const gid = getGroupId(session);
|
|
1043
1343
|
const cc = config.chain;
|
|
1044
1344
|
if (!cc.enabled) return;
|
|
1045
1345
|
if (!isGroupAllowed(gid, cc.groups, cc.groupMode)) return;
|
|
1046
1346
|
if (!content || !content.trim()) {
|
|
1047
|
-
return "\u{1F4DD} \
|
|
1347
|
+
return "\u{1F4DD} \u683C\u5F0F\uFF1A\u521B\u5EFA\u63A5\u9F99 \u5185\u5BB9 [\u622A\u6B62\u65F6\u95F4]\n\u622A\u6B62\u65F6\u95F4\u793A\u4F8B\uFF1A21:00 \u6216 06-30 21:00\uFF0824\u5C0F\u65F6\u5236\uFF09";
|
|
1048
1348
|
}
|
|
1049
|
-
const
|
|
1349
|
+
const raw = content.trim();
|
|
1350
|
+
const timePattern = /(\d{1,2}[::]\d{2}|\d{1,2}-\d{1,2}\s+\d{1,2}[::]\d{2})$/;
|
|
1351
|
+
const timeMatch = raw.match(timePattern);
|
|
1352
|
+
let chainContent;
|
|
1353
|
+
let deadline;
|
|
1354
|
+
if (timeMatch) {
|
|
1355
|
+
chainContent = raw.slice(0, timeMatch.index).trim();
|
|
1356
|
+
const parsed = parseDeadline(timeMatch[1], cc.defaultDeadlineHour);
|
|
1357
|
+
deadline = parsed ?? getDefaultDeadline(cc.defaultDeadlineHour);
|
|
1358
|
+
} else {
|
|
1359
|
+
chainContent = raw;
|
|
1360
|
+
deadline = getDefaultDeadline(cc.defaultDeadlineHour);
|
|
1361
|
+
}
|
|
1362
|
+
if (!chainContent) {
|
|
1363
|
+
return "\u{1F4DD} \u8BF7\u63D0\u4F9B\u63A5\u9F99\u5185\u5BB9\u3002\u683C\u5F0F\uFF1A\u521B\u5EFA\u63A5\u9F99 \u5185\u5BB9 [\u622A\u6B62\u65F6\u95F4]";
|
|
1364
|
+
}
|
|
1365
|
+
const dialogueMatch = chainContent.match(/^#(\d+)\s*/);
|
|
1366
|
+
if (dialogueMatch) {
|
|
1367
|
+
const dialogueIndex = parseInt(dialogueMatch[1]);
|
|
1368
|
+
chainContent = await resolveDialogueChainContent(session, gid, dialogueIndex);
|
|
1369
|
+
if (chainContent === null) {
|
|
1370
|
+
return `\u{1F4DD} \u672A\u627E\u5230\u7B2C ${dialogueIndex} \u4E2A\u95EE\u7B54\u5BF9\uFF0C\u6216 dialogue \u63D2\u4EF6\u672A\u52A0\u8F7D\u3002`;
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
await cleanupExpiredChains(gid);
|
|
1050
1374
|
const userId = String(session.userId);
|
|
1051
1375
|
const userName = session.username || userId;
|
|
1052
|
-
|
|
1376
|
+
const chainId = getNextChainId(gid);
|
|
1377
|
+
const trueId = nextTrueId();
|
|
1378
|
+
const chain = {
|
|
1379
|
+
id: chainId,
|
|
1380
|
+
trueId,
|
|
1053
1381
|
content: chainContent,
|
|
1054
1382
|
creatorId: userId,
|
|
1055
1383
|
creatorName: userName,
|
|
1056
1384
|
participants: [{ userId, userName }],
|
|
1057
|
-
createdAt: Date.now()
|
|
1385
|
+
createdAt: Date.now(),
|
|
1386
|
+
deadline
|
|
1387
|
+
};
|
|
1388
|
+
const chains = chainStates.get(gid) || [];
|
|
1389
|
+
chains.push(chain);
|
|
1390
|
+
chainStates.set(gid, chains);
|
|
1391
|
+
await saveChain(gid, chain);
|
|
1392
|
+
await scheduleChainDeadline(gid, chain, session);
|
|
1393
|
+
await recordChainParticipation(gid, userId, userName, chainContent);
|
|
1394
|
+
dbg("\u63A5\u9F99-\u521B\u5EFA", { gid, chainId, creatorId: userId, content: chainContent, deadline: new Date(deadline).toLocaleString("zh-CN") });
|
|
1395
|
+
const deadlineStr = new Date(deadline).toLocaleString("zh-CN", {
|
|
1396
|
+
month: "2-digit",
|
|
1397
|
+
day: "2-digit",
|
|
1398
|
+
hour: "2-digit",
|
|
1399
|
+
minute: "2-digit"
|
|
1058
1400
|
});
|
|
1059
|
-
|
|
1060
|
-
|
|
1401
|
+
return `\u{1F4DD} \u63A5\u9F99${chainId} \u5DF2\u521B\u5EFA\uFF01\u622A\u6B62 ${deadlineStr}
|
|
1402
|
+
|
|
1403
|
+
${formatChain(chain)}`;
|
|
1061
1404
|
});
|
|
1062
|
-
ctx.command("\u67E5\u770B\u63A5\u9F99", "\u67E5\u770B\u5F53\u524D\u7FA4\u63A5\u9F99").action(async ({ session }) => {
|
|
1405
|
+
ctx.command("\u67E5\u770B\u63A5\u9F99 [query:text]", "\u67E5\u770B\u5F53\u524D\u7FA4\u63A5\u9F99\uFF08\u53EF\u6307\u5B9A\u63A5\u9F99\u7F16\u53F7\uFF09").action(async ({ session }, query) => {
|
|
1063
1406
|
const gid = getGroupId(session);
|
|
1064
1407
|
const cc = config.chain;
|
|
1065
1408
|
if (!cc.enabled) return;
|
|
1409
|
+
if (query && /^JL\d{4}$/.test(query)) {
|
|
1410
|
+
return await handleScheduleDeadline(query);
|
|
1411
|
+
}
|
|
1066
1412
|
if (!isGroupAllowed(gid, cc.groups, cc.groupMode)) return;
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1413
|
+
await cleanupExpiredChains(gid);
|
|
1414
|
+
const chains = chainStates.get(gid);
|
|
1415
|
+
if (!chains || chains.length === 0) return "\u{1F4DD} \u672C\u7FA4\u6682\u65E0\u8FDB\u884C\u4E2D\u7684\u63A5\u9F99\u3002";
|
|
1416
|
+
if (query && /^\d+$/.test(query)) {
|
|
1417
|
+
const index = parseInt(query);
|
|
1418
|
+
const chain = chains.find((c) => c.id === index);
|
|
1419
|
+
if (!chain) return `\u{1F4DD} \u63A5\u9F99${index} \u4E0D\u5B58\u5728\u3002\u5F53\u524D\u63A5\u9F99\u7F16\u53F7\uFF1A${chains.map((c) => c.id).join("\u3001")}`;
|
|
1420
|
+
return formatChain(chain);
|
|
1421
|
+
}
|
|
1422
|
+
if (chains.length === 1) return formatChain(chains[0]);
|
|
1423
|
+
const lines = [`\u{1F4DD} \u672C\u7FA4\u6709 ${chains.length} \u4E2A\u8FDB\u884C\u4E2D\u7684\u63A5\u9F99\uFF1A`];
|
|
1424
|
+
for (const c of chains) {
|
|
1425
|
+
const deadlineStr = new Date(c.deadline).toLocaleString("zh-CN", { month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit" });
|
|
1426
|
+
lines.push(`${c.id}. ${c.content}\uFF08${c.participants.length}\u4EBA\u53C2\u4E0E\uFF0C\u622A\u6B62 ${deadlineStr}\uFF09`);
|
|
1427
|
+
}
|
|
1428
|
+
lines.push('\u56DE\u590D"\u63A5\u9F99N"\u53C2\u4E0E\u5BF9\u5E94\u7F16\u53F7\u7684\u63A5\u9F99\uFF0C\u5982"\u63A5\u9F991"');
|
|
1429
|
+
return lines.join("\n");
|
|
1430
|
+
}).usage("\u67E5\u770B\u63A5\u9F99 / \u67E5\u770B\u63A5\u9F99 1 \u2014 \u67E5\u770B\u672C\u7FA4\u63A5\u9F99\n\uFF08JL\u5F00\u5934\u7684 trueID \u4EC5\u4F9B schedule \u5185\u90E8\u4F7F\u7528\uFF0C\u4E0D\u5916\u663E\uFF09");
|
|
1431
|
+
async function handleScheduleDeadline(trueId) {
|
|
1432
|
+
for (const [chainGid, chains] of chainStates) {
|
|
1433
|
+
const chain = chains.find((c) => c.trueId === trueId);
|
|
1434
|
+
if (chain) {
|
|
1435
|
+
await deactivateChain(chainGid, chain.id);
|
|
1436
|
+
const newChains = chains.filter((c) => c.trueId !== trueId);
|
|
1437
|
+
if (newChains.length === 0) chainStates.delete(chainGid);
|
|
1438
|
+
else chainStates.set(chainGid, newChains);
|
|
1439
|
+
try {
|
|
1440
|
+
const finalList = formatChain(chain);
|
|
1441
|
+
const bots = ctx.bots ? [...ctx.bots] : [ctx.bot].filter(Boolean);
|
|
1442
|
+
for (const bot of bots) {
|
|
1443
|
+
try {
|
|
1444
|
+
await bot.sendMessage(chainGid, `\u23F0 \u65F6\u95F4\u5230\uFF01\u63A5\u9F99${chain.id} \u5DF2\u622A\u6B62\u3002
|
|
1445
|
+
|
|
1446
|
+
${finalList}`);
|
|
1447
|
+
break;
|
|
1448
|
+
} catch {
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
} catch (e) {
|
|
1452
|
+
dbg("\u63A5\u9F99-\u622A\u6B62\u901A\u77E5\u5931\u8D25", { gid: chainGid, trueId, chainId: chain.id, error: String(e) });
|
|
1453
|
+
}
|
|
1454
|
+
return;
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
try {
|
|
1458
|
+
const rows = await ctx.database.get("lili_chain", { trueId, active: true });
|
|
1459
|
+
if (rows.length > 0) {
|
|
1460
|
+
const row = rows[0];
|
|
1461
|
+
await deactivateChain(row.gid, row.chainId);
|
|
1462
|
+
const chain = {
|
|
1463
|
+
id: row.chainId,
|
|
1464
|
+
trueId: row.trueId,
|
|
1465
|
+
content: row.content,
|
|
1466
|
+
creatorId: row.creatorId,
|
|
1467
|
+
creatorName: row.creatorName,
|
|
1468
|
+
participants: JSON.parse(row.participants || "[]"),
|
|
1469
|
+
createdAt: row.createdAt,
|
|
1470
|
+
deadline: row.deadline
|
|
1471
|
+
};
|
|
1472
|
+
try {
|
|
1473
|
+
const finalList = formatChain(chain);
|
|
1474
|
+
const bots = ctx.bots ? [...ctx.bots] : [ctx.bot].filter(Boolean);
|
|
1475
|
+
for (const bot of bots) {
|
|
1476
|
+
try {
|
|
1477
|
+
await bot.sendMessage(row.gid, `\u23F0 \u65F6\u95F4\u5230\uFF01\u63A5\u9F99${chain.id} \u5DF2\u622A\u6B62\u3002
|
|
1478
|
+
|
|
1479
|
+
${finalList}`);
|
|
1480
|
+
break;
|
|
1481
|
+
} catch {
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
} catch (e) {
|
|
1485
|
+
dbg("\u63A5\u9F99-\u622A\u6B62\u901A\u77E5\u5931\u8D25(DB)", { gid: row.gid, trueId, error: String(e) });
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
} catch {
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
function parseChainCancelArgs(session) {
|
|
1492
|
+
let targetUserId;
|
|
1493
|
+
if (session.elements) {
|
|
1494
|
+
const atEl = session.elements.find((el) => el.type === "at" && el.attrs?.id);
|
|
1495
|
+
if (atEl) targetUserId = atEl.attrs.id;
|
|
1496
|
+
}
|
|
1497
|
+
const content = session.content || "";
|
|
1498
|
+
const text = content.replace(/\[CQ:at[^\]]*\]/g, "").replace(/^取消接龙\s*/, "").trim();
|
|
1499
|
+
if (!text) return { targetUserId };
|
|
1500
|
+
const parts = text.split(/\s+/);
|
|
1501
|
+
if (!targetUserId) {
|
|
1502
|
+
const lastPart = parts[parts.length - 1];
|
|
1503
|
+
if (/^\d{5,12}$/.test(lastPart)) {
|
|
1504
|
+
targetUserId = lastPart;
|
|
1505
|
+
parts.pop();
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
if (parts.length > 0 && /^\d+$/.test(parts[0])) {
|
|
1509
|
+
const chainIndex = parseInt(parts[0]);
|
|
1510
|
+
if (chainIndex >= 1 && chainIndex <= 999) return { chainIndex, targetUserId };
|
|
1511
|
+
}
|
|
1512
|
+
return { targetUserId };
|
|
1513
|
+
}
|
|
1514
|
+
ctx.command("\u53D6\u6D88\u63A5\u9F99", "\u9000\u51FA\uFF08\u6216\u5E2E\u4EBA\u9000\u51FA\uFF09\u7FA4\u63A5\u9F99\u3002\u683C\u5F0F\uFF1A\u53D6\u6D88\u63A5\u9F99 / \u53D6\u6D88\u63A5\u9F99 1 / \u53D6\u6D88\u63A5\u9F99 @\u7528\u6237 / \u53D6\u6D88\u63A5\u9F99 1 123456").alias("\u9000\u51FA\u63A5\u9F99").action(async ({ session }) => {
|
|
1072
1515
|
const gid = getGroupId(session);
|
|
1073
1516
|
const cc = config.chain;
|
|
1074
1517
|
if (!cc.enabled) return;
|
|
1075
1518
|
if (!isGroupAllowed(gid, cc.groups, cc.groupMode)) return;
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
const
|
|
1080
|
-
|
|
1081
|
-
const
|
|
1519
|
+
await cleanupExpiredChains(gid);
|
|
1520
|
+
const chains = chainStates.get(gid);
|
|
1521
|
+
if (!chains || chains.length === 0) return "\u{1F4DD} \u672C\u7FA4\u6682\u65E0\u8FDB\u884C\u4E2D\u7684\u63A5\u9F99\u3002";
|
|
1522
|
+
const { chainIndex, targetUserId } = parseChainCancelArgs(session);
|
|
1523
|
+
const selfId = String(session.userId);
|
|
1524
|
+
const opTarget = targetUserId || selfId;
|
|
1525
|
+
const isSelf = opTarget === selfId;
|
|
1526
|
+
let chain;
|
|
1527
|
+
if (chainIndex !== void 0) {
|
|
1528
|
+
chain = chains.find((c) => c.id === chainIndex);
|
|
1529
|
+
if (!chain) return `\u{1F4DD} \u63A5\u9F99${chainIndex} \u4E0D\u5B58\u5728\u3002\u5F53\u524D\u63A5\u9F99\u7F16\u53F7\uFF1A${chains.map((c) => c.id).join("\u3001")}`;
|
|
1530
|
+
} else if (chains.length === 1) {
|
|
1531
|
+
chain = chains[0];
|
|
1532
|
+
} else {
|
|
1533
|
+
const targetChains = chains.filter((c) => c.participants.some((p2) => p2.userId === opTarget));
|
|
1534
|
+
if (targetChains.length === 0) {
|
|
1535
|
+
return isSelf ? "\u{1F4DD} \u4F60\u6CA1\u6709\u53C2\u4E0E\u4EFB\u4F55\u63A5\u9F99\u3002" : "\u{1F4DD} \u8BE5\u7528\u6237\u672A\u53C2\u4E0E\u4EFB\u4F55\u63A5\u9F99\u3002";
|
|
1536
|
+
}
|
|
1537
|
+
if (targetChains.length === 1) {
|
|
1538
|
+
chain = targetChains[0];
|
|
1539
|
+
} else {
|
|
1540
|
+
const cmd = isSelf ? "\u53D6\u6D88\u63A5\u9F99" : "\u53D6\u6D88\u63A5\u9F99";
|
|
1541
|
+
const suffix = isSelf ? "" : ` @\u7528\u6237`;
|
|
1542
|
+
return `\u{1F4DD} ${isSelf ? "\u4F60" : "\u8BE5\u7528\u6237"}\u53C2\u4E0E\u4E86\u591A\u4E2A\u63A5\u9F99\uFF0C\u8BF7\u6307\u5B9A\u7F16\u53F7\uFF1A${targetChains.map((c) => `${cmd} ${c.id}${suffix}`).join(" \u6216 ")}`;
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
const idx = chain.participants.findIndex((p2) => p2.userId === opTarget);
|
|
1546
|
+
if (idx === -1) {
|
|
1547
|
+
return isSelf ? `\u{1F4DD} \u4F60\u6CA1\u6709\u53C2\u4E0E\u63A5\u9F99${chain.id}\u3002` : `\u{1F4DD} \u8BE5\u7528\u6237\u6CA1\u6709\u53C2\u4E0E\u63A5\u9F99${chain.id}\u3002`;
|
|
1548
|
+
}
|
|
1549
|
+
const p = chain.participants[idx];
|
|
1082
1550
|
chain.participants.splice(idx, 1);
|
|
1083
|
-
dbg("\u63A5\u9F99-\
|
|
1551
|
+
dbg("\u63A5\u9F99-\u53D6\u6D88", { gid, chainId: chain.id, opIsSelf: isSelf, userId: p.userId, userName: p.userName, remaining: chain.participants.length });
|
|
1084
1552
|
if (chain.participants.length === 0) {
|
|
1085
|
-
|
|
1086
|
-
|
|
1553
|
+
const newChains = chains.filter((c) => c.id !== chain.id);
|
|
1554
|
+
if (newChains.length === 0) chainStates.delete(gid);
|
|
1555
|
+
else chainStates.set(gid, newChains);
|
|
1556
|
+
await deactivateChain(gid, chain.id);
|
|
1557
|
+
const who = isSelf ? `\u4F60` : `${p.userName}`;
|
|
1558
|
+
return `\u{1F4DD} ${who}${isSelf ? "" : " \u88AB"}\u9000\u51FA\u4E86\u63A5\u9F99${chain.id}\uFF0C\u8BE5\u63A5\u9F99\u5DF2\u56E0\u65E0\u4EBA\u53C2\u4E0E\u800C\u7ED3\u675F\u3002`;
|
|
1087
1559
|
}
|
|
1088
|
-
await
|
|
1560
|
+
await saveChain(gid, chain);
|
|
1561
|
+
if (isSelf) return `\u{1F4DD} \u4F60\u5DF2\u9000\u51FA\u63A5\u9F99${chain.id}\u3002
|
|
1089
1562
|
|
|
1090
|
-
${formatChain(chain)}
|
|
1563
|
+
${formatChain(chain)}`;
|
|
1564
|
+
return `\u{1F4DD} ${p.userName} \u5DF2\u88AB\u79FB\u51FA\u63A5\u9F99${chain.id}\u3002
|
|
1565
|
+
|
|
1566
|
+
${formatChain(chain)}`;
|
|
1567
|
+
});
|
|
1568
|
+
ctx.command("\u63A5\u9F99\u699C", "\u67E5\u770B\u672C\u7FA4\u63A5\u9F99\u6392\u884C\u699C\uFF08\u7EDF\u8BA1\u6BCF\u4EBA\u53C2\u4E0E\u6B21\u6570\uFF09").action(async ({ session }) => {
|
|
1569
|
+
const gid = getGroupId(session);
|
|
1570
|
+
const cc = config.chain;
|
|
1571
|
+
if (!cc.enabled) return;
|
|
1572
|
+
if (!isGroupAllowed(gid, cc.groups, cc.groupMode)) return;
|
|
1573
|
+
try {
|
|
1574
|
+
const records = await ctx.database.get("lili_chain_record", { gid });
|
|
1575
|
+
if (!records || records.length === 0) return "\u{1F4DD} \u672C\u7FA4\u6682\u65E0\u63A5\u9F99\u8BB0\u5F55\u3002";
|
|
1576
|
+
const userStats = /* @__PURE__ */ new Map();
|
|
1577
|
+
for (const r of records) {
|
|
1578
|
+
const existing = userStats.get(r.userId);
|
|
1579
|
+
if (existing) {
|
|
1580
|
+
existing.count++;
|
|
1581
|
+
if (r.timestamp > existing.lastTime) existing.lastTime = r.timestamp;
|
|
1582
|
+
} else {
|
|
1583
|
+
userStats.set(r.userId, { userName: r.userName, count: 1, lastTime: r.timestamp });
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
const sorted = [...userStats.entries()].sort((a, b) => b[1].count - a[1].count);
|
|
1587
|
+
const lines = ["\u{1F3C6} \u7FA4\u63A5\u9F99\u6392\u884C\u699C"];
|
|
1588
|
+
for (let i = 0; i < sorted.length; i++) {
|
|
1589
|
+
const [userId, stat] = sorted[i];
|
|
1590
|
+
const lastStr = new Date(stat.lastTime).toLocaleString("zh-CN", { month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit" });
|
|
1591
|
+
const medal = i === 0 ? "\u{1F947}" : i === 1 ? "\u{1F948}" : i === 2 ? "\u{1F949}" : `${i + 1}.`;
|
|
1592
|
+
lines.push(`${medal} ${stat.userName} \u2014 ${stat.count} \u6B21\uFF08\u6700\u8FD1\uFF1A${lastStr}\uFF09`);
|
|
1593
|
+
}
|
|
1594
|
+
return lines.join("\n");
|
|
1595
|
+
} catch (e) {
|
|
1596
|
+
dbg("\u63A5\u9F99\u699C\u67E5\u8BE2\u5931\u8D25", { gid, error: String(e) });
|
|
1597
|
+
return "\u{1F4DD} \u67E5\u8BE2\u6392\u884C\u699C\u5931\u8D25\u3002";
|
|
1598
|
+
}
|
|
1599
|
+
});
|
|
1600
|
+
ctx.command("\u6E05\u9664\u6D88\u606F\u8BB0\u5F55", "\u6E05\u9664 lili_message \u6570\u636E\u5E93\uFF08\u6C34\u8FC7\u4E86/\u6298\u53E0\u7684\u6D88\u606F\u8BB0\u5F55\uFF09").action(async () => {
|
|
1601
|
+
try {
|
|
1602
|
+
await ctx.database.remove("lili_message", {});
|
|
1603
|
+
return "\u{1F9F9} \u5DF2\u6E05\u9664\u5168\u90E8\u6D88\u606F\u8BB0\u5F55\u3002";
|
|
1604
|
+
} catch (e) {
|
|
1605
|
+
dbg("\u6E05\u9664\u6D88\u606F\u8BB0\u5F55\u5931\u8D25", { error: String(e) });
|
|
1606
|
+
return "\u{1F9F9} \u6E05\u9664\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u6570\u636E\u5E93\u8FDE\u63A5\u3002";
|
|
1607
|
+
}
|
|
1091
1608
|
});
|
|
1092
1609
|
ctx.middleware(async (session, next) => {
|
|
1093
1610
|
if (!config.chain.enabled) return next();
|
|
1094
1611
|
const gid = getGroupId(session);
|
|
1095
1612
|
if (!gid) return next();
|
|
1096
1613
|
if (!isGroupAllowed(gid, config.chain.groups, config.chain.groupMode)) return next();
|
|
1097
|
-
const
|
|
1098
|
-
|
|
1099
|
-
const
|
|
1100
|
-
if (
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1614
|
+
const rawText = (session.content || "").trim();
|
|
1615
|
+
const modifyPattern = /^接龙(\d+)\s*~\s*(.+)$/;
|
|
1616
|
+
const modifyMatch = rawText.match(modifyPattern);
|
|
1617
|
+
if (modifyMatch) {
|
|
1618
|
+
await cleanupExpiredChains(gid);
|
|
1619
|
+
const chains2 = chainStates.get(gid);
|
|
1620
|
+
if (!chains2 || chains2.length === 0) {
|
|
1621
|
+
await session.send("\u{1F4DD} \u672C\u7FA4\u6682\u65E0\u8FDB\u884C\u4E2D\u7684\u63A5\u9F99\u3002");
|
|
1622
|
+
return;
|
|
1623
|
+
}
|
|
1624
|
+
const chainIdx = parseInt(modifyMatch[1]);
|
|
1625
|
+
const newContent = modifyMatch[2].trim();
|
|
1626
|
+
const chain2 = chains2.find((c) => c.id === chainIdx);
|
|
1627
|
+
if (!chain2) {
|
|
1628
|
+
await session.send(`\u{1F4DD} \u63A5\u9F99${chainIdx} \u4E0D\u5B58\u5728\u3002\u5F53\u524D\u63A5\u9F99\u7F16\u53F7\uFF1A${chains2.map((c) => c.id).join("\u3001")}`);
|
|
1629
|
+
return;
|
|
1630
|
+
}
|
|
1631
|
+
const userId2 = String(session.userId);
|
|
1632
|
+
if (chain2.creatorId !== userId2) {
|
|
1633
|
+
await session.send("\u{1F4DD} \u53EA\u6709\u63A5\u9F99\u521B\u5EFA\u8005\u624D\u80FD\u4FEE\u6539\u5185\u5BB9\u3002");
|
|
1634
|
+
return;
|
|
1635
|
+
}
|
|
1636
|
+
if (Date.now() > chain2.deadline) {
|
|
1637
|
+
await session.send(`\u{1F4DD} \u63A5\u9F99${chain2.id} \u5DF2\u622A\u6B62\uFF0C\u65E0\u6CD5\u4FEE\u6539\u3002`);
|
|
1638
|
+
return;
|
|
1639
|
+
}
|
|
1640
|
+
const oldContent = chain2.content;
|
|
1641
|
+
chain2.content = newContent;
|
|
1642
|
+
await saveChain(gid, chain2);
|
|
1643
|
+
dbg("\u63A5\u9F99-\u4FEE\u6539", { gid, chainId: chain2.id, userId: userId2, oldContent, newContent });
|
|
1644
|
+
await session.send(`\u{1F4DD} \u63A5\u9F99${chain2.id} \u5185\u5BB9\u5DF2\u66F4\u65B0\uFF1A${oldContent} \u2192 ${newContent}
|
|
1645
|
+
|
|
1646
|
+
${formatChain(chain2)}`);
|
|
1104
1647
|
return;
|
|
1105
1648
|
}
|
|
1106
|
-
const
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
await
|
|
1649
|
+
const chainPattern = /^接龙(\d*)\s*(.*)$/;
|
|
1650
|
+
const match = rawText.match(chainPattern);
|
|
1651
|
+
if (!match) return next();
|
|
1652
|
+
await cleanupExpiredChains(gid);
|
|
1653
|
+
const chains = chainStates.get(gid);
|
|
1654
|
+
if (!chains || chains.length === 0) return next();
|
|
1655
|
+
const chainIndexStrPart = match[1] || "";
|
|
1656
|
+
const restRaw = match[2] || "";
|
|
1657
|
+
let helpTargetId;
|
|
1658
|
+
let restText = rawText.replace(/\[CQ:at[^\]]*\]/g, "").replace(/^接龙\d*\s*/, "").trim();
|
|
1659
|
+
if (session.elements) {
|
|
1660
|
+
const atEl = session.elements.find((el) => el.type === "at" && el.attrs?.id);
|
|
1661
|
+
if (atEl) {
|
|
1662
|
+
helpTargetId = atEl.attrs.id;
|
|
1663
|
+
restText = restText.replace(/@\S+/g, "").trim();
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
if (!helpTargetId && restText) {
|
|
1667
|
+
const parts = restText.split(/\s+/);
|
|
1668
|
+
const lastPart = parts[parts.length - 1];
|
|
1669
|
+
if (/^\d{5,12}$/.test(lastPart)) {
|
|
1670
|
+
helpTargetId = lastPart;
|
|
1671
|
+
parts.pop();
|
|
1672
|
+
restText = parts.join(" ").trim();
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
restText = restText.replace(/@\S+/g, "").replace(/\[CQ:at[^\]]*\]/g, "").replace(/<at[^>]*\/?>/g, "").trim();
|
|
1676
|
+
let remark;
|
|
1677
|
+
if (restText) {
|
|
1678
|
+
const remarkMatch = restText.match(/^[((](.+)[))]$/);
|
|
1679
|
+
if (remarkMatch) {
|
|
1680
|
+
remark = remarkMatch[1].trim();
|
|
1681
|
+
} else {
|
|
1682
|
+
remark = restText || void 0;
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
const chainIndexStr = chainIndexStrPart;
|
|
1686
|
+
const userId = helpTargetId || String(session.userId);
|
|
1687
|
+
let userName;
|
|
1688
|
+
if (helpTargetId && helpTargetId !== String(session.userId)) {
|
|
1689
|
+
const atEl = session.elements?.find((el) => el.type === "at" && String(el.attrs?.id) === String(helpTargetId));
|
|
1690
|
+
userName = atEl?.attrs?.name || helpTargetId;
|
|
1691
|
+
} else {
|
|
1692
|
+
userName = session.username || String(session.userId);
|
|
1693
|
+
}
|
|
1694
|
+
let chain;
|
|
1695
|
+
if (chainIndexStr) {
|
|
1696
|
+
const idx = parseInt(chainIndexStr);
|
|
1697
|
+
chain = chains.find((c) => c.id === idx);
|
|
1698
|
+
if (!chain) {
|
|
1699
|
+
await session.send(`\u{1F4DD} \u63A5\u9F99${idx} \u4E0D\u5B58\u5728\u3002\u5F53\u524D\u63A5\u9F99\u7F16\u53F7\uFF1A${chains.map((c) => c.id).join("\u3001")}`);
|
|
1700
|
+
return;
|
|
1701
|
+
}
|
|
1702
|
+
} else if (chains.length === 1) {
|
|
1703
|
+
chain = chains[0];
|
|
1704
|
+
} else {
|
|
1705
|
+
const lines = [helpTargetId ? `\u{1F4DD} \u5E2E ${userName} \u63A5\u9F99\uFF1F\u672C\u7FA4\u6709 ${chains.length} \u4E2A\u8FDB\u884C\u4E2D\u7684\u63A5\u9F99\uFF0C\u8BF7\u6307\u5B9A\u7F16\u53F7\uFF1A` : `\u{1F4DD} \u672C\u7FA4\u6709 ${chains.length} \u4E2A\u8FDB\u884C\u4E2D\u7684\u63A5\u9F99\uFF0C\u8BF7\u6307\u5B9A\u7F16\u53F7\uFF1A`];
|
|
1706
|
+
for (const c of chains) {
|
|
1707
|
+
const deadlineStr = new Date(c.deadline).toLocaleString("zh-CN", { month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit" });
|
|
1708
|
+
lines.push(`\u63A5\u9F99${c.id}\uFF1A${c.content}\uFF08${c.participants.length}\u4EBA\u53C2\u4E0E\uFF0C\u622A\u6B62 ${deadlineStr}\uFF09`);
|
|
1709
|
+
}
|
|
1710
|
+
const prefix = helpTargetId ? "\u63A5\u9F99" : "";
|
|
1711
|
+
lines.push(`\u56DE\u590D"${prefix}\u63A5\u9F991"\u3001"${prefix}\u63A5\u9F992"\u7B49\u53C2\u4E0E\u5BF9\u5E94\u63A5\u9F99`);
|
|
1712
|
+
await session.send(lines.join("\n"));
|
|
1713
|
+
return;
|
|
1714
|
+
}
|
|
1715
|
+
if (Date.now() > chain.deadline) {
|
|
1716
|
+
await session.send(`\u{1F4DD} \u63A5\u9F99${chain.id} \u5DF2\u622A\u6B62\uFF01`);
|
|
1717
|
+
return;
|
|
1718
|
+
}
|
|
1719
|
+
if (chain.participants.some((p) => String(p.userId) === String(userId))) {
|
|
1720
|
+
await session.send(helpTargetId ? `\u{1F4DD} ${userName} \u5DF2\u7ECF\u53C2\u4E0E\u8FC7\u63A5\u9F99${chain.id}\u4E86\uFF01` : `\u{1F4DD} \u4F60\u5DF2\u7ECF\u53C2\u4E0E\u8FC7\u63A5\u9F99${chain.id}\u4E86\uFF01`);
|
|
1721
|
+
return;
|
|
1722
|
+
}
|
|
1723
|
+
const finalUserId = String(userId);
|
|
1724
|
+
chain.participants.push({ userId: finalUserId, userName, remark });
|
|
1725
|
+
dbg("\u63A5\u9F99-\u53C2\u4E0E", { gid, chainId: chain.id, userId: finalUserId, remark, helpTarget: !!helpTargetId, participantCount: chain.participants.length });
|
|
1726
|
+
await saveChain(gid, chain);
|
|
1727
|
+
await recordChainParticipation(gid, finalUserId, userName, chain.content);
|
|
1728
|
+
const whoStr = helpTargetId ? `\u5DF2\u5E2E ${userName} \u52A0\u5165\u63A5\u9F99${chain.id}` : "";
|
|
1729
|
+
await session.send(`${whoStr ? "\u{1F4DD} " + whoStr + "\n\n" : ""}${formatChain(chain)}`);
|
|
1110
1730
|
return;
|
|
1111
1731
|
});
|
|
1112
1732
|
ctx.middleware(async (session, next) => {
|
|
@@ -1207,29 +1827,67 @@ ${formatChain(chain)}`);
|
|
|
1207
1827
|
});
|
|
1208
1828
|
if (isFwd && forwardTexts.length > 0) {
|
|
1209
1829
|
dbg("\u67E5\u91CD\u68C0\u6D4B", { gid, forwardTextsCount: forwardTexts.length });
|
|
1830
|
+
const textContents = [];
|
|
1831
|
+
const imageContents = [];
|
|
1210
1832
|
for (const fwdText of forwardTexts) {
|
|
1211
1833
|
if (!fwdText) continue;
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1834
|
+
if (fwdText.startsWith("[img:")) {
|
|
1835
|
+
imageContents.push(fwdText);
|
|
1836
|
+
} else if (fwdText.startsWith("[face:") || fwdText.startsWith("[mface:")) {
|
|
1837
|
+
} else {
|
|
1838
|
+
textContents.push(fwdText);
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
const sevenDaysAgoMs = Date.now() - 7 * 24 * 60 * 60 * 1e3;
|
|
1842
|
+
let recentMsgs = [];
|
|
1843
|
+
try {
|
|
1844
|
+
recentMsgs = await ctx.database.get("lili_message", { gid, timestamp: { $gte: sevenDaysAgoMs } }, { sort: { timestamp: "asc" }, limit: 1e3 });
|
|
1845
|
+
} catch (e) {
|
|
1846
|
+
dbg("\u67E5\u91CD\u67E5\u8BE2\u5F02\u5E38", { gid, error: String(e) });
|
|
1847
|
+
}
|
|
1848
|
+
let matched = null;
|
|
1849
|
+
for (const fwdText of textContents) {
|
|
1850
|
+
if (fwdText.length <= 30) continue;
|
|
1851
|
+
const match = recentMsgs.find((m) => {
|
|
1852
|
+
if (m.content === fwdText) return true;
|
|
1853
|
+
return normalizeText(m.content) === fwdText;
|
|
1854
|
+
});
|
|
1855
|
+
if (match) {
|
|
1856
|
+
matched = match;
|
|
1857
|
+
dbg("\u67E5\u91CD\u547D\u4E2D-\u6587\u5B57", { gid, fwdText: fwdText.substring(0, 50), length: fwdText.length });
|
|
1858
|
+
break;
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1861
|
+
if (!matched && imageContents.length >= 2) {
|
|
1862
|
+
let imageMatchCount = 0;
|
|
1863
|
+
let firstImgMatch = null;
|
|
1864
|
+
for (const imgContent of imageContents) {
|
|
1865
|
+
const found = recentMsgs.find((m) => m.content === imgContent || normalizeText(m.content) === imgContent);
|
|
1866
|
+
if (found) {
|
|
1867
|
+
imageMatchCount++;
|
|
1868
|
+
if (!firstImgMatch) firstImgMatch = found;
|
|
1229
1869
|
}
|
|
1230
|
-
} catch (e) {
|
|
1231
|
-
dbg("\u67E5\u91CD\u67E5\u8BE2\u5F02\u5E38", { gid, error: String(e) });
|
|
1232
1870
|
}
|
|
1871
|
+
if (imageMatchCount >= 2) {
|
|
1872
|
+
matched = firstImgMatch;
|
|
1873
|
+
dbg("\u67E5\u91CD\u547D\u4E2D-\u56FE\u7247", { gid, imageMatchCount, totalImages: imageContents.length });
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
dbg("\u67E5\u91CD\u7ED3\u679C", {
|
|
1877
|
+
gid,
|
|
1878
|
+
textCount: textContents.length,
|
|
1879
|
+
imageCount: imageContents.length,
|
|
1880
|
+
matched: !!matched
|
|
1881
|
+
});
|
|
1882
|
+
if (matched) {
|
|
1883
|
+
const original = matched;
|
|
1884
|
+
const dateStr = new Date(original.timestamp).toLocaleString("zh-CN", { month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit" });
|
|
1885
|
+
await session.send([
|
|
1886
|
+
(0, import_koishi.h)("quote", { id: original.messageId }),
|
|
1887
|
+
(0, import_koishi.h)("at", { id: session.userId }),
|
|
1888
|
+
import_koishi.h.text(` \u{1F4A7} \u6C34\u8FC7\u4E86\uFF01\u8FD9\u6761\u6D88\u606F\u5728 ${dateStr} \u5C31\u53D1\u8FC7\u4E86\uFF01`)
|
|
1889
|
+
]);
|
|
1890
|
+
return next();
|
|
1233
1891
|
}
|
|
1234
1892
|
}
|
|
1235
1893
|
}
|
|
@@ -1276,7 +1934,7 @@ ${formatChain(chain)}`);
|
|
|
1276
1934
|
if (shouldRecord && !isFwd) {
|
|
1277
1935
|
const shouldStore = shouldRecordAll || normalizedText && textLen >= 15 && textLen <= 60;
|
|
1278
1936
|
if (shouldStore) {
|
|
1279
|
-
const storeContent = shouldRecordAll ? session.
|
|
1937
|
+
const storeContent = shouldRecordAll ? elementsToCQCode(session.elements) || normalizedText || "" : normalizedText;
|
|
1280
1938
|
if (storeContent) {
|
|
1281
1939
|
try {
|
|
1282
1940
|
await ctx.database.create("lili_message", {
|
|
@@ -1287,9 +1945,9 @@ ${formatChain(chain)}`);
|
|
|
1287
1945
|
timestamp: Date.now(),
|
|
1288
1946
|
messageId: session.messageId || ""
|
|
1289
1947
|
});
|
|
1290
|
-
dbg("\u6D88\u606F\u5DF2\u5165\u5E93", { gid, userId: String(session.userId), contentLen: storeContent.length, shouldRecordAll, messageId: session.messageId });
|
|
1948
|
+
dbg("\u6D88\u606F\u5DF2\u5165\u5E93", { gid, userId: String(session.userId), contentLen: storeContent.length, shouldRecordAll, messageId: session.messageId, contentPreview: storeContent.substring(0, 40) });
|
|
1291
1949
|
} catch (e) {
|
|
1292
|
-
|
|
1950
|
+
ctx.logger("lili-hub").warn(`[\u6D88\u606F\u5165\u5E93\u5931\u8D25] gid=${gid} userId=${session.userId} error=${String(e)}`);
|
|
1293
1951
|
}
|
|
1294
1952
|
}
|
|
1295
1953
|
}
|
|
@@ -1326,11 +1984,29 @@ ${formatChain(chain)}`);
|
|
|
1326
1984
|
} catch {
|
|
1327
1985
|
}
|
|
1328
1986
|
}, 36e5 * 6);
|
|
1987
|
+
ctx.setInterval(async () => {
|
|
1988
|
+
const now = Date.now();
|
|
1989
|
+
for (const [gid, chains] of chainStates) {
|
|
1990
|
+
const remaining = chains.filter((c) => c.deadline > now);
|
|
1991
|
+
if (remaining.length === 0) {
|
|
1992
|
+
chainStates.delete(gid);
|
|
1993
|
+
} else if (remaining.length !== chains.length) {
|
|
1994
|
+
chainStates.set(gid, remaining);
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
}, 3e5);
|
|
1998
|
+
ctx.setInterval(async () => {
|
|
1999
|
+
try {
|
|
2000
|
+
await ctx.database.remove("lili_chain_record", { timestamp: { $lt: Date.now() - 90 * 24 * 60 * 60 * 1e3 } });
|
|
2001
|
+
} catch {
|
|
2002
|
+
}
|
|
2003
|
+
}, 36e5 * 12);
|
|
1329
2004
|
}
|
|
1330
2005
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1331
2006
|
0 && (module.exports = {
|
|
1332
2007
|
Config,
|
|
1333
2008
|
apply,
|
|
1334
2009
|
inject,
|
|
1335
|
-
name
|
|
2010
|
+
name,
|
|
2011
|
+
optional
|
|
1336
2012
|
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "koishi-plugin-lili-hub",
|
|
3
3
|
"description": "丽丽Hub — 自用丽丽主题QQ群娱乐插件:俄罗斯轮盘赌、喝酒发酒疯、模仿群友说话、合并消息查重(水过了)",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.4",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"typings": "lib/index.d.ts",
|
|
7
7
|
"files": [
|
|
@@ -28,7 +28,8 @@
|
|
|
28
28
|
"en": "Lili Hub — Russian roulette, drinking games, speech imitation, and message dedup"
|
|
29
29
|
},
|
|
30
30
|
"service": {
|
|
31
|
-
"required": ["database"]
|
|
31
|
+
"required": ["database"],
|
|
32
|
+
"optional": ["schedule", "dialogue"]
|
|
32
33
|
}
|
|
33
34
|
}
|
|
34
35
|
}
|