@dsh-plugin/dsh-synapse 0.0.0-stage → 0.1.0-dev.41044818627
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/LICENSE +661 -0
- package/NOTICE.md +18 -0
- package/README.md +136 -2
- package/cordis.patch.yml +3 -0
- package/lib/bot.cjs +8314 -0
- package/lib/bot.cjs.map +7 -0
- package/lib/build.json +1 -0
- package/lib/client.js +46 -0
- package/lib/host/agent.d.ts +21 -0
- package/lib/host/agent.js +151 -0
- package/lib/host/agent.js.map +1 -0
- package/lib/host/bot.d.ts +17 -0
- package/lib/host/bot.js +158 -0
- package/lib/host/bot.js.map +1 -0
- package/lib/host/client.d.ts +2 -0
- package/lib/host/client.js +19 -0
- package/lib/host/client.js.map +1 -0
- package/lib/host/config.d.ts +24 -0
- package/lib/host/config.js +192 -0
- package/lib/host/config.js.map +1 -0
- package/lib/host/files.d.ts +29 -0
- package/lib/host/files.js +188 -0
- package/lib/host/files.js.map +1 -0
- package/lib/host/gateway.d.ts +33 -0
- package/lib/host/gateway.js +237 -0
- package/lib/host/gateway.js.map +1 -0
- package/lib/host/github.d.ts +47 -0
- package/lib/host/github.js +219 -0
- package/lib/host/github.js.map +1 -0
- package/lib/host/http.d.ts +4 -0
- package/lib/host/http.js +41 -0
- package/lib/host/http.js.map +1 -0
- package/lib/host/index.d.ts +32 -0
- package/lib/host/index.js +192 -0
- package/lib/host/index.js.map +1 -0
- package/lib/host/inference-worker.d.ts +1 -0
- package/lib/host/inference-worker.js +72 -0
- package/lib/host/inference-worker.js.map +1 -0
- package/lib/host/mcp.d.ts +138 -0
- package/lib/host/mcp.js +104 -0
- package/lib/host/mcp.js.map +1 -0
- package/lib/host/memory.d.ts +26 -0
- package/lib/host/memory.js +155 -0
- package/lib/host/memory.js.map +1 -0
- package/lib/host/panel.d.ts +1 -0
- package/lib/host/panel.html +174 -0
- package/lib/host/panel.js +388 -0
- package/lib/host/panel.js.map +1 -0
- package/lib/host/runtime.d.ts +21 -0
- package/lib/host/runtime.js +145 -0
- package/lib/host/runtime.js.map +1 -0
- package/lib/host/types.d.ts +112 -0
- package/lib/host/types.js +2 -0
- package/lib/host/types.js.map +1 -0
- package/package.json +94 -4
- package/scripts/build.mjs +90 -0
- package/scripts/check.mjs +13 -0
- package/scripts/dump-npm-latest.mjs +49 -0
- package/scripts/model-smoke.mjs +25 -0
- package/src/agent.ts +203 -0
- package/src/bot.ts +173 -0
- package/src/client.ts +20 -0
- package/src/config.ts +217 -0
- package/src/files.ts +206 -0
- package/src/gateway.ts +276 -0
- package/src/github.ts +263 -0
- package/src/http.ts +41 -0
- package/src/index.ts +227 -0
- package/src/inference-worker.ts +89 -0
- package/src/mcp.ts +112 -0
- package/src/memory.ts +178 -0
- package/src/panel.html +174 -0
- package/src/panel.ts +440 -0
- package/src/runtime.ts +164 -0
- package/src/types.ts +106 -0
- package/test/core.test.mjs +279 -0
- package/test/fixtures/mcp-server.mjs +14 -0
- package/test/integration.test.mjs +253 -0
- package/test/panel.test.mjs +73 -0
- package/tsconfig.json +18 -0
- package/vendor/gugle-chat-bot/LICENSE +661 -0
- package/vendor/gugle-chat-bot/package.json +57 -0
- package/vendor/gugle-chat-bot/src/allow-rule.yml +9345 -0
- package/vendor/gugle-chat-bot/src/api.json +4285 -0
- package/vendor/gugle-chat-bot/src/command/index.ts +141 -0
- package/vendor/gugle-chat-bot/src/config/features.ts +548 -0
- package/vendor/gugle-chat-bot/src/config/index.ts +184 -0
- package/vendor/gugle-chat-bot/src/config/manager.ts +357 -0
- package/vendor/gugle-chat-bot/src/constants/index.ts +13 -0
- package/vendor/gugle-chat-bot/src/custom.ts +548 -0
- package/vendor/gugle-chat-bot/src/event/index.ts +118 -0
- package/vendor/gugle-chat-bot/src/features/admin/index.ts +414 -0
- package/vendor/gugle-chat-bot/src/features/bili/image/impl/video.ts +39 -0
- package/vendor/gugle-chat-bot/src/features/bili/image/index.ts +37 -0
- package/vendor/gugle-chat-bot/src/features/bili/index.ts +65 -0
- package/vendor/gugle-chat-bot/src/features/bili/template/style/video.css +97 -0
- package/vendor/gugle-chat-bot/src/features/bili/template/video.html +57 -0
- package/vendor/gugle-chat-bot/src/features/discord-bridge/index.ts +1128 -0
- package/vendor/gugle-chat-bot/src/features/discord-bridge/markdown.ts +105 -0
- package/vendor/gugle-chat-bot/src/features/discord-bridge/slash.ts +355 -0
- package/vendor/gugle-chat-bot/src/features/discord-bridge/source.ts +96 -0
- package/vendor/gugle-chat-bot/src/features/discord-bridge/template/message.html +14 -0
- package/vendor/gugle-chat-bot/src/features/discord-bridge/template/style/message.css +144 -0
- package/vendor/gugle-chat-bot/src/features/github/api.ts +93 -0
- package/vendor/gugle-chat-bot/src/features/github/binding.ts +161 -0
- package/vendor/gugle-chat-bot/src/features/github/image/impl/issue.ts +132 -0
- package/vendor/gugle-chat-bot/src/features/github/image/impl/pr.ts +144 -0
- package/vendor/gugle-chat-bot/src/features/github/image/impl/release.ts +160 -0
- package/vendor/gugle-chat-bot/src/features/github/image/index.ts +310 -0
- package/vendor/gugle-chat-bot/src/features/github/index.ts +302 -0
- package/vendor/gugle-chat-bot/src/features/github/template/issue.html +118 -0
- package/vendor/gugle-chat-bot/src/features/github/template/pull_request.html +125 -0
- package/vendor/gugle-chat-bot/src/features/github/template/release.html +53 -0
- package/vendor/gugle-chat-bot/src/features/github/template/style/issue.css +468 -0
- package/vendor/gugle-chat-bot/src/features/github/template/style/pull_request.css +469 -0
- package/vendor/gugle-chat-bot/src/features/github/template/style/release.css +412 -0
- package/vendor/gugle-chat-bot/src/features/hermes/client.ts +184 -0
- package/vendor/gugle-chat-bot/src/features/hermes/config.ts +210 -0
- package/vendor/gugle-chat-bot/src/features/hermes/context.ts +67 -0
- package/vendor/gugle-chat-bot/src/features/hermes/history.ts +86 -0
- package/vendor/gugle-chat-bot/src/features/hermes/index.ts +1533 -0
- package/vendor/gugle-chat-bot/src/features/hermes/message.ts +319 -0
- package/vendor/gugle-chat-bot/src/features/hermes/reaction.ts +181 -0
- package/vendor/gugle-chat-bot/src/features/hermes/render.ts +118 -0
- package/vendor/gugle-chat-bot/src/features/hermes/reply-units.ts +331 -0
- package/vendor/gugle-chat-bot/src/features/hermes/skills.ts +406 -0
- package/vendor/gugle-chat-bot/src/features/hermes/template/approval.html +30 -0
- package/vendor/gugle-chat-bot/src/features/hermes/template/progress.html +21 -0
- package/vendor/gugle-chat-bot/src/features/hermes/template/style/approval.css +81 -0
- package/vendor/gugle-chat-bot/src/features/hermes/template/style/progress.css +120 -0
- package/vendor/gugle-chat-bot/src/features/hermes/trigger.ts +86 -0
- package/vendor/gugle-chat-bot/src/features/hermes/types.ts +327 -0
- package/vendor/gugle-chat-bot/src/features/management/index.ts +156 -0
- package/vendor/gugle-chat-bot/src/features/minecraft/index.ts +127 -0
- package/vendor/gugle-chat-bot/src/features/modrinth/index.ts +71 -0
- package/vendor/gugle-chat-bot/src/features/parentheses/index.ts +117 -0
- package/vendor/gugle-chat-bot/src/features/peak-valley-timer/index.ts +452 -0
- package/vendor/gugle-chat-bot/src/features/poke/index.ts +24 -0
- package/vendor/gugle-chat-bot/src/features/version-tracker/index.ts +78 -0
- package/vendor/gugle-chat-bot/src/features/welcome/index.ts +58 -0
- package/vendor/gugle-chat-bot/src/github-ip.json +5814 -0
- package/vendor/gugle-chat-bot/src/image/index.ts +278 -0
- package/vendor/gugle-chat-bot/src/index.ts +695 -0
- package/vendor/gugle-chat-bot/src/logger/index.ts +54 -0
- package/vendor/gugle-chat-bot/src/result.json +359 -0
- package/vendor/gugle-chat-bot/src/type/bili.d.ts +825 -0
- package/vendor/gugle-chat-bot/src/type/event.d.ts +146 -0
- package/vendor/gugle-chat-bot/src/type/github.d.ts +446 -0
- package/vendor/gugle-chat-bot/src/type/index.d.ts +254 -0
- package/vendor/gugle-chat-bot/tsconfig.json +46 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import axios, { AxiosResponse } from 'axios';
|
|
2
|
+
import { Message } from '@/type';
|
|
3
|
+
import { getFeatureGroups } from '@/config/features';
|
|
4
|
+
import { checkReleasedVersion, parseMavenLastUpdated } from '@/features/version-tracker';
|
|
5
|
+
import { QQBot } from '@/index';
|
|
6
|
+
|
|
7
|
+
export declare type ModrinthVersion = {
|
|
8
|
+
success: boolean;
|
|
9
|
+
latest: string;
|
|
10
|
+
/** Maven <lastUpdated>(14 位 YYYYMMDDHHmmss)/ 13 位毫秒时间戳 / ISO,解析为毫秒;无则 0 */
|
|
11
|
+
lastUpdated: number;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export class ModrinthAPI {
|
|
15
|
+
public static async getModVersion(slug: string): Promise<ModrinthVersion> {
|
|
16
|
+
try {
|
|
17
|
+
const res: AxiosResponse<string> = await axios.get(
|
|
18
|
+
`https://api.modrinth.com/maven/maven/modrinth/${slug}/maven-metadata.xml`
|
|
19
|
+
);
|
|
20
|
+
const xml = res.data;
|
|
21
|
+
|
|
22
|
+
// Parse XML to extract latest version
|
|
23
|
+
const latestMatch = xml.match(/<latest>(.*?)<\/latest>/);
|
|
24
|
+
if (!latestMatch || !latestMatch[1]) {
|
|
25
|
+
return {
|
|
26
|
+
success: false,
|
|
27
|
+
latest: '',
|
|
28
|
+
lastUpdated: 0
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Parse <lastUpdated>(标准 Maven 元数据时间戳,单调递增)
|
|
33
|
+
const lastUpdatedMatch = xml.match(/<lastUpdated>(.*?)<\/lastUpdated>/);
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
success: true,
|
|
37
|
+
latest: latestMatch[1],
|
|
38
|
+
lastUpdated: parseMavenLastUpdated(lastUpdatedMatch?.[1] || '')
|
|
39
|
+
};
|
|
40
|
+
} catch (_) {
|
|
41
|
+
return {
|
|
42
|
+
success: false,
|
|
43
|
+
latest: '',
|
|
44
|
+
lastUpdated: 0
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public static checkVersion(bot: QQBot, slug: string, name: string) {
|
|
50
|
+
ModrinthAPI.getModVersion(slug).then(version => {
|
|
51
|
+
if (!version.success) return;
|
|
52
|
+
// 用 Maven lastUpdated 时间戳单调性判断新发布,避免 CDN 缓存抖动重复发送
|
|
53
|
+
checkReleasedVersion(`modrinth:${slug}`, version.latest, version.lastUpdated).then((shouldNotify: boolean) => {
|
|
54
|
+
if (!shouldNotify) return;
|
|
55
|
+
const msg: Message[] = [
|
|
56
|
+
{
|
|
57
|
+
type: 'text',
|
|
58
|
+
data: {
|
|
59
|
+
text: `${name}更新了!
|
|
60
|
+
· 最新版本:${version.latest}
|
|
61
|
+
· 下载直链:https://api.modrinth.com/maven/maven/modrinth/${slug}/${version.latest}/${slug}-${version.latest}.jar`
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
];
|
|
65
|
+
for (const listener of getFeatureGroups('modrinth')) {
|
|
66
|
+
bot.sendGroupMsg(listener, msg);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { GroupMessageWSMSG, Message, TextMessage } from '@/type';
|
|
2
|
+
import { getFeatureGroups } from '@/config/features';
|
|
3
|
+
|
|
4
|
+
export class ParenthesesMatching {
|
|
5
|
+
static readonly bracketPairs: Record<string, string> = {};
|
|
6
|
+
static readonly closingBrackets: Set<string> = new Set();
|
|
7
|
+
static readonly bracketTypeMap: Record<string, string> = {};
|
|
8
|
+
static readonly reversePairs: Record<string, string> = {};
|
|
9
|
+
|
|
10
|
+
static {
|
|
11
|
+
ParenthesesMatching.add('()', 'paren');
|
|
12
|
+
ParenthesesMatching.add('()', 'full width paren');
|
|
13
|
+
ParenthesesMatching.add('⁽⁾', 'superscript paren');
|
|
14
|
+
ParenthesesMatching.add('₍₎', 'subscript paren');
|
|
15
|
+
ParenthesesMatching.add('︵︶', 'vertical paren');
|
|
16
|
+
ParenthesesMatching.add('[]', 'square');
|
|
17
|
+
ParenthesesMatching.add('[]', 'full width square');
|
|
18
|
+
ParenthesesMatching.add('【】', 'solid square');
|
|
19
|
+
ParenthesesMatching.add('〖〗', 'hollow square');
|
|
20
|
+
ParenthesesMatching.add('︗︘', 'vertical hollow square');
|
|
21
|
+
ParenthesesMatching.add('︻︼', 'vertical solid square');
|
|
22
|
+
ParenthesesMatching.add('{}', 'brace');
|
|
23
|
+
ParenthesesMatching.add('﹛﹜', 'full width brace');
|
|
24
|
+
ParenthesesMatching.add('︷︸', 'vertical brace');
|
|
25
|
+
ParenthesesMatching.add('<>', 'angle');
|
|
26
|
+
ParenthesesMatching.add('‹›', 'angle');
|
|
27
|
+
ParenthesesMatching.add('︿﹀', 'vertical angle');
|
|
28
|
+
ParenthesesMatching.add('“”', 'full width quote');
|
|
29
|
+
ParenthesesMatching.add('‘’', 'full width single quote');
|
|
30
|
+
ParenthesesMatching.add('「」', 'traditional quote');
|
|
31
|
+
ParenthesesMatching.add('『』', 'hollow traditional quote');
|
|
32
|
+
ParenthesesMatching.add('⎡⎦', 'long traditional quote');
|
|
33
|
+
ParenthesesMatching.add('⎣⎤', 'reverse traditional quote');
|
|
34
|
+
ParenthesesMatching.add('﹁﹂', 'vertical traditional quote');
|
|
35
|
+
ParenthesesMatching.add('﹃﹄', 'vertical hollow traditional quote');
|
|
36
|
+
ParenthesesMatching.add('《》', 'book');
|
|
37
|
+
ParenthesesMatching.add('︽︾', 'vertical book');
|
|
38
|
+
ParenthesesMatching.add('〔〕', 'hexagonal');
|
|
39
|
+
ParenthesesMatching.add('︹︺', 'vertical hexagonal');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
static add(bracket: string, type: string) {
|
|
43
|
+
if (bracket.length !== 2) return;
|
|
44
|
+
const left = bracket[0];
|
|
45
|
+
const right = bracket[1];
|
|
46
|
+
if (!left || !right) return;
|
|
47
|
+
ParenthesesMatching.bracketPairs[left] = right;
|
|
48
|
+
ParenthesesMatching.closingBrackets.add(right);
|
|
49
|
+
ParenthesesMatching.bracketTypeMap[left] = type;
|
|
50
|
+
ParenthesesMatching.bracketTypeMap[right] = type;
|
|
51
|
+
ParenthesesMatching.reversePairs[right] = left;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
public static parenthesesMatching(msg: GroupMessageWSMSG, sentMessage: Message[]) {
|
|
55
|
+
if (!getFeatureGroups('parentheses').includes(msg.group_id)) return;
|
|
56
|
+
const receivedMessage: TextMessage[] = [];
|
|
57
|
+
msg.message.forEach(message => {
|
|
58
|
+
if (message.type != 'text') return;
|
|
59
|
+
receivedMessage.push(message);
|
|
60
|
+
});
|
|
61
|
+
const stack: { char: string; position: number }[] = [];
|
|
62
|
+
const strMsg = receivedMessage.map(msg => msg.data.text).join(' ');
|
|
63
|
+
for (let i = 0; i < strMsg.length; i++) {
|
|
64
|
+
const char: string = strMsg[i]!;
|
|
65
|
+
if (ParenthesesMatching.bracketPairs[char]) {
|
|
66
|
+
stack.push({ char, position: i });
|
|
67
|
+
} else if (ParenthesesMatching.closingBrackets.has(char)) {
|
|
68
|
+
const expected = ParenthesesMatching.reversePairs[char];
|
|
69
|
+
const charType = ParenthesesMatching.bracketTypeMap[char];
|
|
70
|
+
if (stack.length > 0 && stack[stack.length - 1]!.char === expected) {
|
|
71
|
+
stack.pop();
|
|
72
|
+
} else {
|
|
73
|
+
let errorMsg = `括号匹配错误:第 ${i + 1} 个字符 '${char}' `;
|
|
74
|
+
if (stack.length === 0) {
|
|
75
|
+
let errorType = '括号';
|
|
76
|
+
let errorDirection = '左';
|
|
77
|
+
if (charType && charType.endsWith('quote')) errorType = '引号';
|
|
78
|
+
if (charType && charType.startsWith('vertical')) errorDirection = '上';
|
|
79
|
+
errorMsg += `没有对应的${errorDirection}${errorType}`;
|
|
80
|
+
} else {
|
|
81
|
+
const topBracket = stack[stack.length - 1]!;
|
|
82
|
+
const expectedClosing = ParenthesesMatching.bracketPairs[topBracket.char];
|
|
83
|
+
const topType = ParenthesesMatching.bracketTypeMap[topBracket.char];
|
|
84
|
+
const currentType = ParenthesesMatching.bracketTypeMap[char];
|
|
85
|
+
|
|
86
|
+
if (topType !== currentType) {
|
|
87
|
+
errorMsg += `与第 ${topBracket.position + 1} 个字符 '${topBracket.char}' 类型不匹配,期望 '${expectedClosing}'`;
|
|
88
|
+
} else {
|
|
89
|
+
errorMsg += `位置错误`;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
sentMessage.push({
|
|
94
|
+
type: 'text',
|
|
95
|
+
data: {
|
|
96
|
+
text: errorMsg
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
let result = '';
|
|
105
|
+
while (stack.length > 0) {
|
|
106
|
+
const leftBracket = stack.pop()!;
|
|
107
|
+
result += ParenthesesMatching.bracketPairs[leftBracket.char];
|
|
108
|
+
}
|
|
109
|
+
if (result.trim().length == 0) return;
|
|
110
|
+
sentMessage.push({
|
|
111
|
+
type: 'text',
|
|
112
|
+
data: {
|
|
113
|
+
text: result
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
import dayjs from 'dayjs';
|
|
2
|
+
import { QQBot } from '@/index';
|
|
3
|
+
import { getFeatureGroups } from '@/config/features';
|
|
4
|
+
import { readOrCreate } from '@/config/manager';
|
|
5
|
+
import { Message } from '@/type';
|
|
6
|
+
|
|
7
|
+
export type PeakValleyMode = 'peak' | 'valley';
|
|
8
|
+
|
|
9
|
+
export type DayToken = 'Mon' | 'Tue' | 'Wed' | 'Thu' | 'Fri' | 'Sat' | 'Sun';
|
|
10
|
+
|
|
11
|
+
export interface PeakValleyTimerTimeRange {
|
|
12
|
+
start: string;
|
|
13
|
+
end: string;
|
|
14
|
+
days: DayToken[];
|
|
15
|
+
mode?: PeakValleyMode;
|
|
16
|
+
msg?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface PeakValleyTimerConfig {
|
|
20
|
+
groups?: number[];
|
|
21
|
+
mode?: PeakValleyMode;
|
|
22
|
+
time: Array<string | PeakValleyTimerTimeRange>;
|
|
23
|
+
peak_msg?: string;
|
|
24
|
+
valley_msg?: string;
|
|
25
|
+
cmd_peak_msg?: string;
|
|
26
|
+
cmd_valley_msg?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface NormalizedTimeRange {
|
|
30
|
+
startMinutes: number;
|
|
31
|
+
endMinutes: number;
|
|
32
|
+
days: number[];
|
|
33
|
+
msg: string;
|
|
34
|
+
mode?: PeakValleyMode;
|
|
35
|
+
key: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const DAY_MAP: Record<DayToken, number> = {
|
|
39
|
+
Mon: 1,
|
|
40
|
+
Tue: 2,
|
|
41
|
+
Wed: 3,
|
|
42
|
+
Thu: 4,
|
|
43
|
+
Fri: 5,
|
|
44
|
+
Sat: 6,
|
|
45
|
+
Sun: 0
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const DAY_ORDER: DayToken[] = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
|
|
49
|
+
|
|
50
|
+
const TIME_PATTERN: RegExp = /^([01]?\d|2[0-3]):([0-5]\d)$/;
|
|
51
|
+
|
|
52
|
+
const CHECK_INTERVAL_MS = 30 * 1000;
|
|
53
|
+
const CATCH_UP_WINDOW_SECONDS = 90;
|
|
54
|
+
|
|
55
|
+
const defaultConfig: PeakValleyTimerConfig = {
|
|
56
|
+
groups: getFeatureGroups('command'),
|
|
57
|
+
mode: 'peak',
|
|
58
|
+
time: ['9:00-12:00 Mon-Fri', '14:00-18:00 Mon-Fri'],
|
|
59
|
+
peak_msg: '梁文峰时间到!\n当前时间是${time}',
|
|
60
|
+
valley_msg: '梁文谷时间到!\n当前时间是${time}',
|
|
61
|
+
cmd_peak_msg: '当前时间是${time},梁文峰时间!',
|
|
62
|
+
cmd_valley_msg: '当前时间是${time},梁文谷时间!'
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
function isPeakValleyMode(value: unknown): value is PeakValleyMode {
|
|
66
|
+
return value === 'peak' || value === 'valley';
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function isNumberArray(value: unknown): value is number[] {
|
|
70
|
+
return Array.isArray(value) && value.every(item => typeof item === 'number' && Number.isSafeInteger(item));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function isNonEmptyString(value: unknown): value is string {
|
|
74
|
+
return typeof value === 'string' && value.trim().length > 0;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function parseTimeToMinutes(time: string): number | null {
|
|
78
|
+
if (!TIME_PATTERN.test(time)) return null;
|
|
79
|
+
const [hour, minute] = time.split(':');
|
|
80
|
+
const hourValue = Number.parseInt(hour || '0', 10);
|
|
81
|
+
const minuteValue = Number.parseInt(minute || '0', 10);
|
|
82
|
+
if (Number.isNaN(hourValue) || Number.isNaN(minuteValue)) return null;
|
|
83
|
+
return hourValue * 60 + minuteValue;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function expandDayToken(token: string): DayToken[] | null {
|
|
87
|
+
const trimmed = token.trim();
|
|
88
|
+
if (!trimmed) return null;
|
|
89
|
+
const hyphenIndex = trimmed.indexOf('-');
|
|
90
|
+
if (hyphenIndex === -1) {
|
|
91
|
+
if (!(trimmed in DAY_MAP)) return null;
|
|
92
|
+
return [trimmed as DayToken];
|
|
93
|
+
}
|
|
94
|
+
const from = trimmed.slice(0, hyphenIndex) as DayToken;
|
|
95
|
+
const to = trimmed.slice(hyphenIndex + 1) as DayToken;
|
|
96
|
+
if (!(from in DAY_MAP) || !(to in DAY_MAP)) return null;
|
|
97
|
+
const fromIndex = DAY_ORDER.indexOf(from);
|
|
98
|
+
const toIndex = DAY_ORDER.indexOf(to);
|
|
99
|
+
const days: DayToken[] = [];
|
|
100
|
+
let index = fromIndex;
|
|
101
|
+
while (true) {
|
|
102
|
+
const current = DAY_ORDER[index % DAY_ORDER.length];
|
|
103
|
+
if (!current) break;
|
|
104
|
+
days.push(current);
|
|
105
|
+
if (current === to) break;
|
|
106
|
+
index = (index + 1) % DAY_ORDER.length;
|
|
107
|
+
}
|
|
108
|
+
return days;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function parseDaysToken(daysToken: string): DayToken[] | null {
|
|
112
|
+
if (!daysToken.trim()) return [];
|
|
113
|
+
const tokens = daysToken.split(',').map(token => token.trim()).filter(token => token.length > 0);
|
|
114
|
+
const result: DayToken[] = [];
|
|
115
|
+
for (const token of tokens) {
|
|
116
|
+
const expanded = expandDayToken(token);
|
|
117
|
+
if (!expanded) return null;
|
|
118
|
+
result.push(...expanded);
|
|
119
|
+
}
|
|
120
|
+
return Array.from(new Set(result));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function parseTimeRangeToken(token: string): PeakValleyTimerTimeRange | null {
|
|
124
|
+
const parts = token.trim().split(/\s+/);
|
|
125
|
+
const timePart = parts[0];
|
|
126
|
+
if (!timePart) return null;
|
|
127
|
+
const dashIndex = timePart.indexOf('-');
|
|
128
|
+
if (dashIndex === -1) return null;
|
|
129
|
+
const start = timePart.slice(0, dashIndex).trim();
|
|
130
|
+
const end = timePart.slice(dashIndex + 1).trim();
|
|
131
|
+
if (parseTimeToMinutes(start) === null || parseTimeToMinutes(end) === null) return null;
|
|
132
|
+
const daysToken = parts.slice(1).join(' ');
|
|
133
|
+
const days = parseDaysToken(daysToken);
|
|
134
|
+
if (days === null) return null;
|
|
135
|
+
return { start, end, days };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function normalizeTimeRange(time: unknown): NormalizedTimeRange | null {
|
|
139
|
+
let resolved: PeakValleyTimerTimeRange | null = null;
|
|
140
|
+
if (typeof time === 'string') {
|
|
141
|
+
resolved = parseTimeRangeToken(time);
|
|
142
|
+
} else if (time && typeof time === 'object' && !Array.isArray(time)) {
|
|
143
|
+
const candidate = time as Record<string, unknown>;
|
|
144
|
+
if (isNonEmptyString(candidate.start) && isNonEmptyString(candidate.end)) {
|
|
145
|
+
const daysValue = candidate.days;
|
|
146
|
+
let days: DayToken[] = [];
|
|
147
|
+
if (daysValue !== undefined) {
|
|
148
|
+
if (!Array.isArray(daysValue) || !daysValue.every(isNonEmptyString)) return null;
|
|
149
|
+
const parsedDays = parseDaysToken(daysValue.join(','));
|
|
150
|
+
if (parsedDays === null) return null;
|
|
151
|
+
days = parsedDays;
|
|
152
|
+
}
|
|
153
|
+
const parsedMode = isPeakValleyMode(candidate.mode) ? candidate.mode : undefined;
|
|
154
|
+
const parsedMsg = isNonEmptyString(candidate.msg) ? candidate.msg : undefined;
|
|
155
|
+
if (parsedMode || parsedMsg) {
|
|
156
|
+
resolved = {
|
|
157
|
+
start: candidate.start,
|
|
158
|
+
end: candidate.end,
|
|
159
|
+
days,
|
|
160
|
+
...(parsedMode ? { mode: parsedMode } : {}),
|
|
161
|
+
...(parsedMsg ? { msg: parsedMsg } : {})
|
|
162
|
+
};
|
|
163
|
+
} else {
|
|
164
|
+
resolved = {
|
|
165
|
+
start: candidate.start,
|
|
166
|
+
end: candidate.end,
|
|
167
|
+
days
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (!resolved) return null;
|
|
173
|
+
|
|
174
|
+
const startMinutes = parseTimeToMinutes(resolved.start);
|
|
175
|
+
const endMinutes = parseTimeToMinutes(resolved.end);
|
|
176
|
+
if (startMinutes === null || endMinutes === null) return null;
|
|
177
|
+
|
|
178
|
+
const mode = resolved.mode;
|
|
179
|
+
const msg = resolved.msg;
|
|
180
|
+
const key = `${resolved.start}-${resolved.end}-${resolved.days.join(',')}-${mode || ''}-${msg || ''}`;
|
|
181
|
+
return {
|
|
182
|
+
startMinutes: startMinutes,
|
|
183
|
+
endMinutes: endMinutes,
|
|
184
|
+
days: resolved.days.map(day => DAY_MAP[day]),
|
|
185
|
+
msg: msg || '',
|
|
186
|
+
...(mode ? { mode: mode } : {}),
|
|
187
|
+
key
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function normalizeConfig(raw: PeakValleyTimerConfig | null): {
|
|
192
|
+
groups: number[];
|
|
193
|
+
mode: PeakValleyMode;
|
|
194
|
+
time: NormalizedTimeRange[];
|
|
195
|
+
peak_msg: string;
|
|
196
|
+
valley_msg: string;
|
|
197
|
+
cmd_peak_msg: string;
|
|
198
|
+
cmd_valley_msg: string;
|
|
199
|
+
} {
|
|
200
|
+
const groups =
|
|
201
|
+
raw && raw.groups !== undefined ? (isNumberArray(raw.groups) ? raw.groups : defaultConfig.groups!) : defaultConfig.groups!;
|
|
202
|
+
const mode = raw && raw.mode !== undefined ? (isPeakValleyMode(raw.mode) ? raw.mode : 'peak') : 'peak';
|
|
203
|
+
|
|
204
|
+
let time: NormalizedTimeRange[] = [];
|
|
205
|
+
if (raw && Array.isArray(raw.time)) {
|
|
206
|
+
time = raw.time.map(normalizeTimeRange).filter((range): range is NormalizedTimeRange => range !== null);
|
|
207
|
+
}
|
|
208
|
+
if (time.length === 0) {
|
|
209
|
+
time = defaultConfig.time.map(normalizeTimeRange).filter((range): range is NormalizedTimeRange => range !== null);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const peak_msg =
|
|
213
|
+
raw && raw.peak_msg !== undefined
|
|
214
|
+
? isNonEmptyString(raw.peak_msg)
|
|
215
|
+
? raw.peak_msg
|
|
216
|
+
: defaultConfig.peak_msg!
|
|
217
|
+
: defaultConfig.peak_msg!;
|
|
218
|
+
const valley_msg =
|
|
219
|
+
raw && raw.valley_msg !== undefined
|
|
220
|
+
? isNonEmptyString(raw.valley_msg)
|
|
221
|
+
? raw.valley_msg
|
|
222
|
+
: defaultConfig.valley_msg!
|
|
223
|
+
: defaultConfig.valley_msg!;
|
|
224
|
+
const cmd_peak_msg =
|
|
225
|
+
raw && raw.cmd_peak_msg !== undefined
|
|
226
|
+
? isNonEmptyString(raw.cmd_peak_msg)
|
|
227
|
+
? raw.cmd_peak_msg
|
|
228
|
+
: defaultConfig.cmd_peak_msg!
|
|
229
|
+
: defaultConfig.cmd_peak_msg!;
|
|
230
|
+
const cmd_valley_msg =
|
|
231
|
+
raw && raw.cmd_valley_msg !== undefined
|
|
232
|
+
? isNonEmptyString(raw.cmd_valley_msg)
|
|
233
|
+
? raw.cmd_valley_msg
|
|
234
|
+
: defaultConfig.cmd_valley_msg!
|
|
235
|
+
: defaultConfig.cmd_valley_msg!;
|
|
236
|
+
return { groups, mode, time, peak_msg, valley_msg, cmd_peak_msg, cmd_valley_msg };
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function normalizeRawConfig(raw: unknown): PeakValleyTimerConfig | null {
|
|
240
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return null;
|
|
241
|
+
const record = raw as Record<string, unknown>;
|
|
242
|
+
const time = record.time;
|
|
243
|
+
if (time !== undefined && !Array.isArray(time)) return null;
|
|
244
|
+
return raw as PeakValleyTimerConfig;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function loadConfig(): ReturnType<typeof normalizeConfig> {
|
|
248
|
+
const { data } = readOrCreate<PeakValleyTimerConfig>({
|
|
249
|
+
path: 'configs/peak-valley-timer.json',
|
|
250
|
+
factory: () => defaultConfig,
|
|
251
|
+
version: 1,
|
|
252
|
+
normalize: normalizeRawConfig
|
|
253
|
+
});
|
|
254
|
+
return normalizeConfig(data);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export class PeakValleyTimer {
|
|
258
|
+
private static instance?: PeakValleyTimer = undefined;
|
|
259
|
+
|
|
260
|
+
public static getInstance(): PeakValleyTimer {
|
|
261
|
+
if (!PeakValleyTimer.instance) {
|
|
262
|
+
PeakValleyTimer.instance = new PeakValleyTimer();
|
|
263
|
+
}
|
|
264
|
+
return PeakValleyTimer.instance;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
private groups: number[];
|
|
268
|
+
private mode: PeakValleyMode;
|
|
269
|
+
private time: NormalizedTimeRange[];
|
|
270
|
+
private peakMsg: string;
|
|
271
|
+
private valleyMsg: string;
|
|
272
|
+
private cmdPeakMsg: string;
|
|
273
|
+
private cmdValleyMsg: string;
|
|
274
|
+
private readonly reportedToday: Set<string> = new Set();
|
|
275
|
+
private timer: NodeJS.Timeout | undefined = undefined;
|
|
276
|
+
|
|
277
|
+
public constructor() {
|
|
278
|
+
const config = loadConfig();
|
|
279
|
+
this.groups = config.groups;
|
|
280
|
+
this.mode = config.mode;
|
|
281
|
+
this.time = config.time;
|
|
282
|
+
this.peakMsg = config.peak_msg;
|
|
283
|
+
this.valleyMsg = config.valley_msg;
|
|
284
|
+
this.cmdPeakMsg = config.cmd_peak_msg;
|
|
285
|
+
this.cmdValleyMsg = config.cmd_valley_msg;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* 从磁盘重新读取配置(/reload 用)。
|
|
290
|
+
* 本类在构造时把配置读进字段(非 ConfigStore 热重载),故需显式重载入口。
|
|
291
|
+
*/
|
|
292
|
+
public reloadConfig(): void {
|
|
293
|
+
const config = loadConfig();
|
|
294
|
+
this.groups = config.groups;
|
|
295
|
+
this.mode = config.mode;
|
|
296
|
+
this.time = config.time;
|
|
297
|
+
this.peakMsg = config.peak_msg;
|
|
298
|
+
this.valleyMsg = config.valley_msg;
|
|
299
|
+
this.cmdPeakMsg = config.cmd_peak_msg;
|
|
300
|
+
this.cmdValleyMsg = config.cmd_valley_msg;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
public start(bot: QQBot): void {
|
|
304
|
+
bot.logger?.info(
|
|
305
|
+
`PeakValleyTimer started: ${this.time.length} time range(s), ${this.groups.length} group(s), mode=${this.mode}`
|
|
306
|
+
);
|
|
307
|
+
this.timer = setInterval(() => this.check(bot), CHECK_INTERVAL_MS);
|
|
308
|
+
setTimeout(() => this.check(bot), 1000);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
public stop(): void {
|
|
312
|
+
if (this.timer) {
|
|
313
|
+
clearInterval(this.timer);
|
|
314
|
+
this.timer = undefined;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* /pvtime 命令处理:根据当前时间是否处于某个 peak/valley 区间返回对应消息。
|
|
320
|
+
* 命中区间 → 该区间类型(自身 mode 优先,否则全局 mode)的 cmd 消息;
|
|
321
|
+
* 未命中任何区间 → 全局 mode 相反类型的 cmd 消息。
|
|
322
|
+
*/
|
|
323
|
+
public getCommandMessage(): string {
|
|
324
|
+
const now = dayjs();
|
|
325
|
+
const weekday = now.day();
|
|
326
|
+
const nowMinutes = now.hour() * 60 + now.minute();
|
|
327
|
+
const currentTime = now.format('HH:mm');
|
|
328
|
+
|
|
329
|
+
for (const range of this.time) {
|
|
330
|
+
if (this.isInRange(range, weekday, nowMinutes)) {
|
|
331
|
+
const rangeMode: PeakValleyMode = range.mode ?? this.mode;
|
|
332
|
+
return this.renderMessage(rangeMode === 'peak' ? this.cmdPeakMsg : this.cmdValleyMsg, currentTime);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
const opposite: PeakValleyMode = this.mode === 'peak' ? 'valley' : 'peak';
|
|
336
|
+
return this.renderMessage(opposite === 'peak' ? this.cmdPeakMsg : this.cmdValleyMsg, currentTime);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* 判断某时刻是否处于区间内。区间归属日:不跨天区间当天在 days 中;
|
|
341
|
+
* 跨天区间(end <= start)[start, 24:00) 当天在 days,[0, end) 前一天在 days。
|
|
342
|
+
*/
|
|
343
|
+
private isInRange(range: NormalizedTimeRange, weekday: number, nowMinutes: number): boolean {
|
|
344
|
+
if (range.endMinutes > range.startMinutes) {
|
|
345
|
+
return range.days.includes(weekday) && nowMinutes >= range.startMinutes && nowMinutes < range.endMinutes;
|
|
346
|
+
}
|
|
347
|
+
if (nowMinutes >= range.startMinutes && range.days.includes(weekday)) return true;
|
|
348
|
+
if (nowMinutes < range.endMinutes && range.days.includes((weekday + 6) % 7)) return true;
|
|
349
|
+
return false;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
private renderMessage(template: string, currentTime: string): string {
|
|
353
|
+
return template.replace(/\$\{time\}/g, currentTime);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
private check(bot: QQBot): void {
|
|
357
|
+
const now = dayjs();
|
|
358
|
+
const today = now.format('YYYY-MM-DD');
|
|
359
|
+
const weekday = now.day();
|
|
360
|
+
const secondsIntoDay = now.hour() * 3600 + now.minute() * 60 + now.second();
|
|
361
|
+
const nowMinutes = now.hour() * 60 + now.minute();
|
|
362
|
+
|
|
363
|
+
for (const range of this.time) {
|
|
364
|
+
this.checkStart(bot, range, today, weekday, secondsIntoDay, now, nowMinutes);
|
|
365
|
+
this.checkEnd(bot, range, today, weekday, secondsIntoDay, now, nowMinutes);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* 区间开始触发:当天在 days 中且到达 start 时刻 → 报区间类型"到"。
|
|
371
|
+
*/
|
|
372
|
+
private checkStart(
|
|
373
|
+
bot: QQBot,
|
|
374
|
+
range: NormalizedTimeRange,
|
|
375
|
+
today: string,
|
|
376
|
+
weekday: number,
|
|
377
|
+
secondsIntoDay: number,
|
|
378
|
+
now: dayjs.Dayjs,
|
|
379
|
+
nowMinutes: number
|
|
380
|
+
): void {
|
|
381
|
+
if (!range.days.includes(weekday)) return;
|
|
382
|
+
const elapsed = secondsIntoDay - range.startMinutes * 60;
|
|
383
|
+
if (elapsed < 0 || elapsed >= CATCH_UP_WINDOW_SECONDS) return;
|
|
384
|
+
const reportKey = `${today}:${range.key}:start`;
|
|
385
|
+
if (this.reportedToday.has(reportKey)) return;
|
|
386
|
+
this.reportedToday.add(reportKey);
|
|
387
|
+
bot.logger?.info(`PeakValleyTimer start triggered: ${range.key} at ${nowMinutes}`);
|
|
388
|
+
const rangeMode: PeakValleyMode = range.mode ?? this.mode;
|
|
389
|
+
const message = this.buildMessage(rangeMode, range.msg, now);
|
|
390
|
+
this.broadcast(bot, message);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* 区间结束触发:到达 end 时刻 → 报相反类型"到"(离开该类型时段)。
|
|
395
|
+
* 不跨天:end 触发日在 days 中当天;跨天(end <= start):start 在
|
|
396
|
+
* 开始日 D 的 startMinutes,end 在 D+1 的 endMinutes,因此要求
|
|
397
|
+
* 前一天在 days 中,且此刻尚未到今天的 start。
|
|
398
|
+
*/
|
|
399
|
+
private checkEnd(
|
|
400
|
+
bot: QQBot,
|
|
401
|
+
range: NormalizedTimeRange,
|
|
402
|
+
today: string,
|
|
403
|
+
weekday: number,
|
|
404
|
+
secondsIntoDay: number,
|
|
405
|
+
now: dayjs.Dayjs,
|
|
406
|
+
nowMinutes: number
|
|
407
|
+
): void {
|
|
408
|
+
if (range.endMinutes > range.startMinutes) {
|
|
409
|
+
if (!range.days.includes(weekday)) return;
|
|
410
|
+
} else {
|
|
411
|
+
// 跨天区间:end 在开始日的次日凌晨
|
|
412
|
+
const prevWeekday = (weekday + 6) % 7;
|
|
413
|
+
if (!range.days.includes(prevWeekday)) return;
|
|
414
|
+
if (nowMinutes >= range.startMinutes) return; // 今天已到 start,属于新一轮区间
|
|
415
|
+
}
|
|
416
|
+
const elapsed = secondsIntoDay - range.endMinutes * 60;
|
|
417
|
+
if (elapsed < 0 || elapsed >= CATCH_UP_WINDOW_SECONDS) return;
|
|
418
|
+
const reportKey = `${today}:${range.key}:end`;
|
|
419
|
+
if (this.reportedToday.has(reportKey)) return;
|
|
420
|
+
this.reportedToday.add(reportKey);
|
|
421
|
+
bot.logger?.info(`PeakValleyTimer end triggered: ${range.key} at ${nowMinutes}`);
|
|
422
|
+
const rangeMode: PeakValleyMode = range.mode ?? this.mode;
|
|
423
|
+
const opposite: PeakValleyMode = rangeMode === 'peak' ? 'valley' : 'peak';
|
|
424
|
+
const message = this.buildMessage(opposite, range.msg, now);
|
|
425
|
+
this.broadcast(bot, message);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
private broadcast(bot: QQBot, message: string): void {
|
|
429
|
+
for (const groupId of this.groups) {
|
|
430
|
+
const sentMessage: Message[] = [
|
|
431
|
+
{
|
|
432
|
+
type: 'text',
|
|
433
|
+
data: {
|
|
434
|
+
text: message
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
];
|
|
438
|
+
bot.sendGroupMsg(groupId, sentMessage);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
private buildMessage(rangeMode: PeakValleyMode, customMsg: string | undefined, now: dayjs.Dayjs): string {
|
|
443
|
+
const currentTime = now.format('HH:mm');
|
|
444
|
+
let template: string;
|
|
445
|
+
if (customMsg) {
|
|
446
|
+
template = customMsg;
|
|
447
|
+
} else {
|
|
448
|
+
template = rangeMode === 'valley' ? this.valleyMsg : this.peakMsg;
|
|
449
|
+
}
|
|
450
|
+
return template.replace(/\$\{time\}/g, currentTime);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Message, NotifyNoticeWSMSG } from '@/type';
|
|
2
|
+
import { QQBot } from '@/index';
|
|
3
|
+
import { getFeatureGroups } from '@/config/features';
|
|
4
|
+
import dayjs from 'dayjs';
|
|
5
|
+
|
|
6
|
+
export class Poke {
|
|
7
|
+
public static processPokeMsg(bot: QQBot, msg: NotifyNoticeWSMSG, sentMessage: Message[]) {
|
|
8
|
+
const hour = dayjs().hour();
|
|
9
|
+
if (hour > 7 && hour < 21) return;
|
|
10
|
+
if (!msg.group_id || !getFeatureGroups('poke').includes(msg.group_id)) return;
|
|
11
|
+
const loginInfo = bot.getLoginInfoSync();
|
|
12
|
+
if (!loginInfo || msg.target_id !== loginInfo.user_id) return;
|
|
13
|
+
const msgPool = ['下一个', '零分', '负分', '给我滚'];
|
|
14
|
+
const banPool = [60, 120, 240, 300];
|
|
15
|
+
const rand = Math.round(Math.random() * msgPool.length);
|
|
16
|
+
sentMessage.push({
|
|
17
|
+
type: 'text',
|
|
18
|
+
data: {
|
|
19
|
+
text: msgPool[rand] || '零分'
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
bot.ban(msg.group_id, msg.user_id, banPool[rand] || 120);
|
|
23
|
+
}
|
|
24
|
+
}
|