@zhin.js/plugin-group-suite 1.0.0 → 1.1.0

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.
Files changed (138) hide show
  1. package/README.md +42 -58
  2. package/agent/tools/group_announce.ts +12 -0
  3. package/commands/checkin.js +11 -0
  4. package/commands/checkin.ts +12 -0
  5. package/commands/forget.js +10 -0
  6. package/commands/forget.ts +12 -0
  7. package/commands/keyword-add/[keyword].js +16 -0
  8. package/commands/keyword-add/[keyword].ts +16 -0
  9. package/commands/keyword-list.js +14 -0
  10. package/commands/keyword-list.ts +14 -0
  11. package/commands/keyword-remove/[keyword].js +16 -0
  12. package/commands/keyword-remove/[keyword].ts +15 -0
  13. package/commands/mypoints.js +10 -0
  14. package/commands/mypoints.ts +12 -0
  15. package/commands/mystats.js +10 -0
  16. package/commands/mystats.ts +12 -0
  17. package/commands/rank.js +11 -0
  18. package/commands/rank.ts +12 -0
  19. package/commands/stats-rank.js +13 -0
  20. package/commands/stats-rank.ts +20 -0
  21. package/commands/stats-week.js +13 -0
  22. package/commands/stats-week.ts +20 -0
  23. package/commands/stats.js +14 -0
  24. package/commands/stats.ts +21 -0
  25. package/commands/teach-list/[[page]].js +13 -0
  26. package/commands/teach-list/[[page]].ts +14 -0
  27. package/commands/teach-regex.js +11 -0
  28. package/commands/teach-regex.ts +12 -0
  29. package/commands/teach.js +11 -0
  30. package/commands/teach.ts +12 -0
  31. package/lib/analysis.d.ts +1 -1
  32. package/lib/analysis.d.ts.map +1 -1
  33. package/lib/checkin-lib.d.ts +27 -0
  34. package/lib/checkin-lib.d.ts.map +1 -0
  35. package/lib/checkin-lib.js +134 -0
  36. package/lib/checkin-lib.js.map +1 -0
  37. package/lib/config.d.ts +4 -57
  38. package/lib/config.d.ts.map +1 -1
  39. package/lib/config.js +18 -40
  40. package/lib/config.js.map +1 -1
  41. package/lib/db-store.d.ts +12 -0
  42. package/lib/db-store.d.ts.map +1 -0
  43. package/lib/db-store.js +38 -0
  44. package/lib/db-store.js.map +1 -0
  45. package/lib/index.d.ts +9 -1
  46. package/lib/index.d.ts.map +1 -1
  47. package/lib/index.js +7 -29
  48. package/lib/index.js.map +1 -1
  49. package/lib/keyword-store.d.ts +6 -0
  50. package/lib/keyword-store.d.ts.map +1 -0
  51. package/lib/keyword-store.js +21 -0
  52. package/lib/keyword-store.js.map +1 -0
  53. package/lib/memory-store.d.ts +31 -0
  54. package/lib/memory-store.d.ts.map +1 -0
  55. package/lib/memory-store.js +63 -0
  56. package/lib/memory-store.js.map +1 -0
  57. package/lib/runtime-state.d.ts +25 -0
  58. package/lib/runtime-state.d.ts.map +1 -0
  59. package/lib/runtime-state.js +21 -0
  60. package/lib/runtime-state.js.map +1 -0
  61. package/lib/shared-runtime.d.ts +26 -0
  62. package/lib/shared-runtime.d.ts.map +1 -0
  63. package/lib/shared-runtime.js +34 -0
  64. package/lib/shared-runtime.js.map +1 -0
  65. package/lib/stats-lib.d.ts +41 -0
  66. package/lib/stats-lib.d.ts.map +1 -0
  67. package/lib/stats-lib.js +177 -0
  68. package/lib/stats-lib.js.map +1 -0
  69. package/lib/tables.d.ts +4 -0
  70. package/lib/tables.d.ts.map +1 -0
  71. package/lib/tables.js +40 -0
  72. package/lib/tables.js.map +1 -0
  73. package/lib/teach-lib.d.ts +25 -0
  74. package/lib/teach-lib.d.ts.map +1 -0
  75. package/lib/teach-lib.js +233 -0
  76. package/lib/teach-lib.js.map +1 -0
  77. package/middlewares/keyword-reply.js +36 -0
  78. package/middlewares/keyword-reply.ts +37 -0
  79. package/middlewares/stats-count.js +20 -0
  80. package/middlewares/stats-count.ts +22 -0
  81. package/middlewares/teach-reply.js +21 -0
  82. package/middlewares/teach-reply.ts +22 -0
  83. package/package.json +53 -14
  84. package/plugin.js +40 -0
  85. package/schema.json +17 -0
  86. package/src/analysis.ts +1 -1
  87. package/src/checkin-lib.ts +165 -0
  88. package/src/config.ts +23 -57
  89. package/src/db-store.ts +51 -0
  90. package/src/index.ts +47 -33
  91. package/src/keyword-store.ts +24 -0
  92. package/src/memory-store.ts +90 -0
  93. package/src/runtime-state.ts +44 -0
  94. package/src/shared-runtime.ts +44 -0
  95. package/src/stats-lib.ts +205 -0
  96. package/src/tables.ts +46 -0
  97. package/src/teach-lib.ts +301 -0
  98. package/tools/checkin-query/handler.js +22 -0
  99. package/tools/checkin-query/handler.ts +2 -2
  100. package/tools/checkin-rank/handler.js +15 -0
  101. package/tools/checkin-rank/handler.ts +1 -1
  102. package/tools/stats-query/handler.js +28 -0
  103. package/tools/stats-query/handler.ts +2 -2
  104. package/tools/stats-user/handler.js +29 -0
  105. package/tools/stats-user/handler.ts +3 -2
  106. package/lib/admin.d.ts +0 -4
  107. package/lib/admin.d.ts.map +0 -1
  108. package/lib/admin.js +0 -123
  109. package/lib/admin.js.map +0 -1
  110. package/lib/checkin.d.ts +0 -5
  111. package/lib/checkin.d.ts.map +0 -1
  112. package/lib/checkin.js +0 -168
  113. package/lib/checkin.js.map +0 -1
  114. package/lib/daily-analysis.d.ts +0 -7
  115. package/lib/daily-analysis.d.ts.map +0 -1
  116. package/lib/daily-analysis.js +0 -334
  117. package/lib/daily-analysis.js.map +0 -1
  118. package/lib/shared.d.ts +0 -25
  119. package/lib/shared.d.ts.map +0 -1
  120. package/lib/shared.js +0 -40
  121. package/lib/shared.js.map +0 -1
  122. package/lib/stats.d.ts +0 -12
  123. package/lib/stats.d.ts.map +0 -1
  124. package/lib/stats.js +0 -256
  125. package/lib/stats.js.map +0 -1
  126. package/lib/teach.d.ts +0 -4
  127. package/lib/teach.d.ts.map +0 -1
  128. package/lib/teach.js +0 -294
  129. package/lib/teach.js.map +0 -1
  130. package/plugin.yml +0 -3
  131. package/src/admin.ts +0 -145
  132. package/src/checkin.ts +0 -196
  133. package/src/daily-analysis.ts +0 -458
  134. package/src/shared.ts +0 -51
  135. package/src/stats.ts +0 -297
  136. package/src/teach.ts +0 -307
  137. /package/{skills/checkin/SKILL.md → agent/skills/checkin.md} +0 -0
  138. /package/{skills/group-daily-analysis/SKILL.md → agent/skills/group-daily-analysis.md} +0 -0
@@ -0,0 +1,301 @@
1
+ import type { GroupSuiteConfig } from './config.js';
2
+ import { getTeachModel } from './db-store.js';
3
+ import {
4
+ resolveContextKey,
5
+ resolveSender,
6
+ ts,
7
+ } from './shared-runtime.js';
8
+ import type { GroupSuiteRuntime } from './runtime-state.js';
9
+
10
+ const cooldownMap = new Map<string, number>();
11
+
12
+ export const TEACH_USAGE_HINT =
13
+ '格式:teach 关键词 回答(回答可含空格),或 teach 问题|答案';
14
+
15
+ function isSafeRegex(pattern: string): boolean {
16
+ if (pattern.length > 200) return false;
17
+ if (/(\+|\*|\{)\)?(\+|\*|\{)/.test(pattern)) return false;
18
+ if (/\([^)]*(\+|\*)[^)]*(\+|\*)[^)]*\)[+*]/.test(pattern)) return false;
19
+ const altGroups = pattern.match(/\([^)]*\|[^)]*\)/g) || [];
20
+ for (const g of altGroups) {
21
+ if ((g.match(/\|/g) || []).length > 10) return false;
22
+ }
23
+ return true;
24
+ }
25
+
26
+ export function parseTeachPair(raw: string): { question: string; answer: string } | null {
27
+ if (!raw) return null;
28
+ const pipe = raw.indexOf('|');
29
+ if (pipe >= 0) {
30
+ const question = raw.slice(0, pipe).trim();
31
+ const answer = raw.slice(pipe + 1).trim();
32
+ if (question && answer) return { question, answer };
33
+ return null;
34
+ }
35
+ const lastSpace = raw.lastIndexOf(' ');
36
+ if (lastSpace < 0) return null;
37
+ const question = raw.slice(0, lastSpace).trim();
38
+ const answer = raw.slice(lastSpace + 1).trim();
39
+ if (!question || !answer) return null;
40
+ return { question, answer };
41
+ }
42
+
43
+ function processAnswer(
44
+ answer: string,
45
+ sender: { id: string; name: string },
46
+ match?: RegExpMatchArray,
47
+ ): string {
48
+ let result = answer;
49
+ result = result.replace(/\{sender\}/g, sender.name || '你');
50
+ result = result.replace(/\{sender\.id\}/g, sender.id || '');
51
+ result = result.replace(/\{time\}/g, new Date().toLocaleTimeString('zh-CN'));
52
+ result = result.replace(/\{date\}/g, new Date().toLocaleDateString('zh-CN'));
53
+ if (match) {
54
+ for (let i = 1; i < match.length; i++) {
55
+ result = result.replace(new RegExp(`\\$${i}`, 'g'), match[i] || '');
56
+ }
57
+ }
58
+ return result;
59
+ }
60
+
61
+ function qaWhereKey(row: {
62
+ question: string;
63
+ context_type: string;
64
+ context_id: string;
65
+ is_regex?: number | boolean;
66
+ }) {
67
+ return {
68
+ question: row.question,
69
+ context_type: row.context_type,
70
+ context_id: row.context_id,
71
+ is_regex: row.is_regex ? 1 : 0,
72
+ };
73
+ }
74
+
75
+ type MessageInput = {
76
+ sender?: { readonly id: string; readonly name?: string };
77
+ target?: string;
78
+ content?: unknown;
79
+ metadata?: Readonly<Record<string, unknown>>;
80
+ };
81
+
82
+ async function findMatch(
83
+ content: string,
84
+ ctxType: string,
85
+ ctxId: string,
86
+ cfg: GroupSuiteConfig,
87
+ runtime?: GroupSuiteRuntime,
88
+ ): Promise<Record<string, unknown> | null> {
89
+ const QA = getTeachModel(runtime?.db);
90
+ if (!QA) return null;
91
+ const allItems = (await QA.select()) as Record<string, unknown>[];
92
+ const candidates = allItems.filter(
93
+ (item) =>
94
+ item.context_type === 'global' ||
95
+ (item.context_type === ctxType && item.context_id === ctxId),
96
+ );
97
+ for (const item of candidates) {
98
+ if (!item.is_regex && item.question === content) return item;
99
+ }
100
+ if (cfg.teachAllowRegex) {
101
+ for (const item of candidates) {
102
+ if (!item.is_regex) continue;
103
+ try {
104
+ if (new RegExp(String(item.question), 'i').test(content)) return item;
105
+ } catch {
106
+ continue;
107
+ }
108
+ }
109
+ }
110
+ return null;
111
+ }
112
+
113
+ export async function teachAdd(
114
+ input: MessageInput,
115
+ cfg: GroupSuiteConfig,
116
+ payload: string,
117
+ regex = false,
118
+ runtime?: GroupSuiteRuntime,
119
+ ): Promise<string> {
120
+ const QA = getTeachModel(runtime?.db);
121
+ if (!QA) return '问答数据库尚未就绪,请稍后重试';
122
+ if (regex && !cfg.teachAllowRegex) return '管理员已禁用正则问答';
123
+
124
+ const parsed = parseTeachPair(payload.trim());
125
+ if (!parsed) {
126
+ return regex
127
+ ? '格式:teach-regex 正则 回答,或 teach-regex 正则|回答'
128
+ : TEACH_USAGE_HINT;
129
+ }
130
+ const { question, answer } = parsed;
131
+
132
+ if (regex) {
133
+ try {
134
+ new RegExp(question, 'i');
135
+ } catch (e) {
136
+ return `正则表达式不合法: ${(e as Error).message}`;
137
+ }
138
+ if (!isSafeRegex(question)) return '该正则可能导致性能问题,请简化';
139
+ }
140
+
141
+ const { type: ctxType, id: ctxId } = resolveContextKey(input);
142
+ const sender = resolveSender(input);
143
+
144
+ if (ctxType === 'group') {
145
+ const existing = (await QA.select().where({
146
+ context_type: ctxType,
147
+ context_id: ctxId,
148
+ })) as Record<string, unknown>[];
149
+ if (existing.length >= cfg.teachMaxPerGroup) {
150
+ return `该群问答数已达上限 (${cfg.teachMaxPerGroup})`;
151
+ }
152
+ }
153
+
154
+ const duplicate = (await QA.select().where({
155
+ question,
156
+ context_type: ctxType,
157
+ context_id: ctxId,
158
+ })) as Record<string, unknown>[];
159
+
160
+ if (duplicate.length > 0 && !regex) {
161
+ await QA.update({ answer, updated_at: ts() }).where(
162
+ qaWhereKey(duplicate[0] as {
163
+ question: string;
164
+ context_type: string;
165
+ context_id: string;
166
+ is_regex?: number | boolean;
167
+ }),
168
+ );
169
+ return `已更新问答:「${question}」→「${answer}」`;
170
+ }
171
+
172
+ await QA.insert({
173
+ question,
174
+ answer,
175
+ is_regex: regex ? 1 : 0,
176
+ context_type: ctxType,
177
+ context_id: ctxId,
178
+ creator_id: sender.id,
179
+ creator_name: sender.name,
180
+ hit_count: 0,
181
+ created_at: ts(),
182
+ updated_at: ts(),
183
+ });
184
+
185
+ return regex
186
+ ? `学会了!匹配 /${question}/ 的消息我会回复「${answer}」`
187
+ : `学会了!发送「${question}」我会回复「${answer}」`;
188
+ }
189
+
190
+ export async function teachList(
191
+ input: MessageInput,
192
+ cfg: GroupSuiteConfig,
193
+ page = 1,
194
+ runtime?: GroupSuiteRuntime,
195
+ ): Promise<string> {
196
+ const QA = getTeachModel(runtime?.db);
197
+ if (!QA) return '问答数据库尚未就绪';
198
+ const safePage = Math.max(1, page);
199
+ const { type: ctxType, id: ctxId } = resolveContextKey(input);
200
+ const allItems = (await QA.select().where(
201
+ ctxType === 'global'
202
+ ? { context_type: 'global' }
203
+ : { context_type: ctxType, context_id: ctxId },
204
+ )) as Record<string, unknown>[];
205
+ if (allItems.length === 0) return '还没有教过任何问答哦~';
206
+ const totalPages = Math.ceil(allItems.length / cfg.teachPageSize);
207
+ const pageNum = Math.min(safePage, totalPages);
208
+ const start = (pageNum - 1) * cfg.teachPageSize;
209
+ const pageItems = allItems.slice(start, start + cfg.teachPageSize);
210
+ const lines = pageItems.map((item, i) => {
211
+ const prefix = item.is_regex ? '[正则]' : '[精确]';
212
+ const q = item.is_regex ? `/${item.question}/` : item.question;
213
+ return `${start + i + 1}. ${prefix} ${q} → ${item.answer}`;
214
+ });
215
+ const header = ctxType === 'global' ? '全局问答列表' : '本群问答列表';
216
+ return `${header}\n${lines.join('\n')}\n第 ${pageNum}/${totalPages} 页 · 共 ${allItems.length} 条`;
217
+ }
218
+
219
+ export async function teachForget(
220
+ input: MessageInput,
221
+ questionRaw: string,
222
+ runtime?: GroupSuiteRuntime,
223
+ ): Promise<string> {
224
+ const QA = getTeachModel(runtime?.db);
225
+ if (!QA) return '问答数据库尚未就绪';
226
+ const question = questionRaw.trim();
227
+ if (!question) return '请提供要删除的问题';
228
+ const { type: ctxType, id: ctxId } = resolveContextKey(input);
229
+ const items = (await QA.select().where({
230
+ question,
231
+ context_type: ctxType,
232
+ context_id: ctxId,
233
+ })) as Record<string, unknown>[];
234
+ if (items.length === 0) return `没有找到问题「${question}」`;
235
+ await QA.delete().where(
236
+ qaWhereKey(items[0] as {
237
+ question: string;
238
+ context_type: string;
239
+ context_id: string;
240
+ is_regex?: number | boolean;
241
+ }),
242
+ );
243
+ return `已忘记「${question}」的回答`;
244
+ }
245
+
246
+ /** Try teach auto-reply; returns reply text or null. */
247
+ export async function tryTeachReply(
248
+ input: MessageInput,
249
+ cfg: GroupSuiteConfig,
250
+ runtime?: GroupSuiteRuntime,
251
+ ): Promise<string | null> {
252
+ const content = typeof input.content === 'string' ? input.content.trim() : '';
253
+ if (!content) return null;
254
+ const { type: ctxType, id: ctxId } = resolveContextKey(input);
255
+ const matched = await findMatch(content, ctxType, ctxId, cfg, runtime);
256
+ if (!matched) return null;
257
+
258
+ const cooldownKey = `${matched.question}:${ctxType}:${ctxId}:${matched.is_regex ?? 0}`;
259
+ const cooldowns = runtime?.teachCooldowns ?? cooldownMap;
260
+ const last = cooldowns.get(cooldownKey);
261
+ if (last && Date.now() - last < cfg.teachCooldownMs) return null;
262
+ cooldowns.set(cooldownKey, Date.now());
263
+
264
+ const QA = getTeachModel(runtime?.db);
265
+ if (QA) {
266
+ try {
267
+ await QA.update({
268
+ hit_count: Number(matched.hit_count || 0) + 1,
269
+ updated_at: ts(),
270
+ }).where(
271
+ qaWhereKey(matched as {
272
+ question: string;
273
+ context_type: string;
274
+ context_id: string;
275
+ is_regex?: number | boolean;
276
+ }),
277
+ );
278
+ } catch {
279
+ /* ignore */
280
+ }
281
+ }
282
+
283
+ const sender = resolveSender(input);
284
+ let answer = String(matched.answer ?? '');
285
+ if (matched.is_regex) {
286
+ try {
287
+ const m = content.match(new RegExp(String(matched.question), 'i'));
288
+ answer = processAnswer(answer, sender, m || undefined);
289
+ } catch {
290
+ /* ignore */
291
+ }
292
+ } else {
293
+ answer = processAnswer(answer, sender);
294
+ }
295
+ return answer;
296
+ }
297
+
298
+ /** Test helper */
299
+ export function resetTeachCooldown(): void {
300
+ cooldownMap.clear();
301
+ }
@@ -0,0 +1,22 @@
1
+ // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { getCheckinModel } from "../../lib/db-store.js";
3
+ import { todayStr } from "../../lib/shared-runtime.js";
4
+ export default async function (args) {
5
+ const M = getCheckinModel();
6
+ if (!M)
7
+ return '签到数据库尚未就绪';
8
+ const userId = args.user_id;
9
+ if (userId) {
10
+ const rows = await M.select().where({ user_id: userId });
11
+ if (rows.length === 0)
12
+ return `用户 ${userId} 没有签到记录`;
13
+ const u = rows[0];
14
+ return `${u.user_name}: 积分=${u.points}, 累计=${u.total_checkins}天, 连续=${u.streak}天, 最长=${u.max_streak}天, 上次=${u.last_checkin}`;
15
+ }
16
+ const all = await M.select();
17
+ const totalUsers = all.length;
18
+ const totalPoints = all.reduce((s, u) => s + (u.points || 0), 0);
19
+ const today = todayStr();
20
+ const todayCount = all.filter((u) => u.last_checkin === today).length;
21
+ return `签到系统统计\n总用户: ${totalUsers}\n总积分: ${totalPoints}\n今日签到: ${todayCount}人`;
22
+ }
@@ -1,5 +1,5 @@
1
- import { getCheckinModel } from '../../src/checkin.js';
2
- import { todayStr } from '../../src/shared.js';
1
+ import { getCheckinModel } from '../../src/db-store.js';
2
+ import { todayStr } from '../../src/shared-runtime.js';
3
3
 
4
4
  export default async function (args: { user_id?: string }) {
5
5
  const M = getCheckinModel();
@@ -0,0 +1,15 @@
1
+ // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { getCheckinModel } from "../../lib/db-store.js";
3
+ export default async function (args) {
4
+ const M = getCheckinModel();
5
+ if (!M)
6
+ return '签到数据库尚未就绪';
7
+ const limit = Math.min(Math.max(Number(args.limit) || 10, 1), 50);
8
+ const all = await M.select();
9
+ const sorted = all.sort((a, b) => (b.points || 0) - (a.points || 0)).slice(0, limit);
10
+ if (sorted.length === 0)
11
+ return '暂无排行数据';
12
+ return sorted
13
+ .map((u, i) => `${i + 1}. ${u.user_name || u.user_id} — ${u.points}分 (连续${u.streak}天)`)
14
+ .join('\n');
15
+ }
@@ -1,4 +1,4 @@
1
- import { getCheckinModel } from '../../src/checkin.js';
1
+ import { getCheckinModel } from '../../src/db-store.js';
2
2
 
3
3
  export default async function (args: { limit?: number }) {
4
4
  const M = getCheckinModel();
@@ -0,0 +1,28 @@
1
+ // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { flushStatsBuffer, queryStats, weekStartStr, monthStartStr, } from "../../lib/stats-lib.js";
3
+ import { todayStr } from "../../lib/shared-runtime.js";
4
+ export default async function (args) {
5
+ await flushStatsBuffer();
6
+ const groupId = args.group_id || '';
7
+ const period = args.period || 'today';
8
+ let fromDate;
9
+ switch (period) {
10
+ case 'week':
11
+ fromDate = weekStartStr();
12
+ break;
13
+ case 'month':
14
+ fromDate = monthStartStr();
15
+ break;
16
+ default:
17
+ fromDate = todayStr();
18
+ break;
19
+ }
20
+ const stats = await queryStats(groupId, fromDate);
21
+ if (stats.size === 0)
22
+ return `${period} 暂无消息统计数据`;
23
+ const sorted = [...stats.entries()].sort((a, b) => b[1].count - a[1].count);
24
+ const total = sorted.reduce((s, [, v]) => s + v.count, 0);
25
+ const top10 = sorted.slice(0, 10);
26
+ const lines = top10.map(([, v], i) => `${i + 1}. ${v.name} — ${v.count}条`);
27
+ return `消息统计 (${period})\n总消息: ${total}条, 活跃用户: ${stats.size}人\n${lines.join('\n')}`;
28
+ }
@@ -3,8 +3,8 @@ import {
3
3
  queryStats,
4
4
  weekStartStr,
5
5
  monthStartStr,
6
- } from '../../src/stats.js';
7
- import { todayStr } from '../../src/shared.js';
6
+ } from '../../src/stats-lib.js';
7
+ import { todayStr } from '../../src/shared-runtime.js';
8
8
 
9
9
  export default async function (args: { group_id?: string; period?: string }) {
10
10
  await flushStatsBuffer();
@@ -0,0 +1,29 @@
1
+ // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { flushStatsBuffer, weekStartStr } from "../../lib/stats-lib.js";
3
+ import { getStatsModel } from "../../lib/db-store.js";
4
+ import { todayStr } from "../../lib/shared-runtime.js";
5
+ export default async function (args) {
6
+ await flushStatsBuffer();
7
+ const M = getStatsModel();
8
+ if (!M)
9
+ return '统计数据库尚未就绪';
10
+ const userId = args.user_id;
11
+ if (!userId)
12
+ return '请提供用户ID';
13
+ const rows = await M.select().where({ user_id: userId });
14
+ if (rows.length === 0)
15
+ return `用户 ${userId} 暂无消息记录`;
16
+ const today = todayStr();
17
+ const weekStart = weekStartStr();
18
+ let todayCount = 0, weekCount = 0, totalCount = 0;
19
+ for (const row of rows) {
20
+ const c = row.count || 0;
21
+ totalCount += c;
22
+ if (row.date >= weekStart)
23
+ weekCount += c;
24
+ if (row.date === today)
25
+ todayCount += c;
26
+ }
27
+ const name = rows[0].user_name || userId;
28
+ return `${name} 的统计\n今日: ${todayCount}条\n本周: ${weekCount}条\n总计: ${totalCount}条\n活跃天数: ${rows.length}天`;
29
+ }
@@ -1,5 +1,6 @@
1
- import { flushStatsBuffer, getStatsModel, weekStartStr } from '../../src/stats.js';
2
- import { todayStr } from '../../src/shared.js';
1
+ import { flushStatsBuffer, weekStartStr } from '../../src/stats-lib.js';
2
+ import { getStatsModel } from '../../src/db-store.js';
3
+ import { todayStr } from '../../src/shared-runtime.js';
3
4
 
4
5
  export default async function (args: { user_id: string }) {
5
6
  await flushStatsBuffer();
package/lib/admin.d.ts DELETED
@@ -1,4 +0,0 @@
1
- import { type Plugin } from "zhin.js";
2
- import type { GroupSuiteConfig } from "./config.js";
3
- export declare function registerAdmin(plugin: Plugin, cfg: GroupSuiteConfig): void;
4
- //# sourceMappingURL=admin.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"admin.d.ts","sourceRoot":"","sources":["../src/admin.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,MAAM,EAEZ,MAAM,SAAS,CAAC;AAEjB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,gBAAgB,GAAG,IAAI,CAuIzE"}
package/lib/admin.js DELETED
@@ -1,123 +0,0 @@
1
- import { formatCompact, MessageCommand, ZhinTool, } from "zhin.js";
2
- import { adapterAllowed } from "./shared.js";
3
- export function registerAdmin(plugin, cfg) {
4
- const { addCommand, addTool, logger } = plugin;
5
- const welcomeTemplate = cfg.welcome;
6
- const noticeAdapters = cfg.noticeAdapters;
7
- plugin.on("notice.receive", async (notice) => {
8
- if (!adapterAllowed(noticeAdapters, String(notice.$adapter)))
9
- return;
10
- if (notice.$type === "group_member_increase") {
11
- if (notice.$channel.type !== "group")
12
- return;
13
- const targetName = notice.$target?.name || notice.$target?.id || "新成员";
14
- const welcomeMsg = `🎉 ${targetName},${welcomeTemplate}`;
15
- try {
16
- const adapter = plugin.inject(notice.$adapter);
17
- if (!adapter?.sendMessage)
18
- return;
19
- await adapter.sendMessage({
20
- context: notice.$adapter,
21
- bot: notice.$bot,
22
- content: welcomeMsg,
23
- id: notice.$channel.id,
24
- type: notice.$channel.type,
25
- });
26
- }
27
- catch (e) {
28
- logger.warn(formatCompact({
29
- op: "welcome",
30
- ok: false,
31
- error: e instanceof Error ? e.message : String(e),
32
- }));
33
- }
34
- return;
35
- }
36
- if (cfg.recallNotify && notice.$type === "group_recall") {
37
- if (notice.$channel.type !== "group")
38
- return;
39
- const operatorName = notice.$operator?.name || notice.$operator?.id || "某人";
40
- const msg = `⚠️ ${operatorName} 撤回了一条消息`;
41
- try {
42
- const adapter = plugin.inject(notice.$adapter);
43
- if (!adapter?.sendMessage)
44
- return;
45
- await adapter.sendMessage({
46
- context: notice.$adapter,
47
- bot: notice.$bot,
48
- content: msg,
49
- id: notice.$channel.id,
50
- type: notice.$channel.type,
51
- });
52
- }
53
- catch (e) {
54
- logger.warn(formatCompact({
55
- op: "recall_notice",
56
- ok: false,
57
- error: e instanceof Error ? e.message : String(e),
58
- }));
59
- }
60
- }
61
- });
62
- if (!cfg.keywordReply)
63
- return;
64
- const keywords = new Map();
65
- plugin.root.addMiddleware(async (message, next) => {
66
- if (message.$channel.type !== "group") {
67
- await next();
68
- return;
69
- }
70
- const raw = message.$raw?.trim();
71
- if (!raw) {
72
- await next();
73
- return;
74
- }
75
- for (const [keyword, reply] of keywords) {
76
- if (raw.includes(keyword)) {
77
- await message.$reply(reply);
78
- return;
79
- }
80
- }
81
- await next();
82
- });
83
- addCommand(new MessageCommand("添加关键词 <keyword:text> <reply:text>")
84
- .desc("添加一个关键词自动回复对")
85
- .action(async (_message, result) => {
86
- const { keyword, reply } = result.params;
87
- if (!keyword || !reply)
88
- return "请提供关键词和回复内容";
89
- keywords.set(keyword, reply);
90
- return `已添加关键词「${keyword}」→「${reply}」`;
91
- }));
92
- addCommand(new MessageCommand("删除关键词 <keyword:text>")
93
- .desc("删除一个关键词")
94
- .action(async (_message, result) => {
95
- const { keyword } = result.params;
96
- if (!keyword)
97
- return "请提供要删除的关键词";
98
- if (!keywords.has(keyword))
99
- return `关键词「${keyword}」不存在`;
100
- keywords.delete(keyword);
101
- return `已删除关键词「${keyword}」`;
102
- }));
103
- addCommand(new MessageCommand("关键词列表")
104
- .desc("查看所有关键词回复对")
105
- .action(async () => {
106
- if (keywords.size === 0)
107
- return "当前没有设置任何关键词";
108
- const lines = Array.from(keywords.entries()).map(([k, v], i) => `${i + 1}. 「${k}」→「${v}」`);
109
- return `关键词列表(共 ${keywords.size} 条):\n${lines.join("\n")}`;
110
- }));
111
- addTool(new ZhinTool("group_announce")
112
- .desc("向群聊发送公告/通知消息")
113
- .keyword("群公告", "通知", "announce")
114
- .param("message", { type: "string", description: "要发送的公告内容" }, true)
115
- .execute(async (args) => {
116
- const content = args.message;
117
- if (!content?.trim())
118
- return "公告内容不能为空";
119
- return `📢 群公告:\n${content}`;
120
- })
121
- .toTool());
122
- }
123
- //# sourceMappingURL=admin.js.map
package/lib/admin.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"admin.js","sourceRoot":"","sources":["../src/admin.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,cAAc,EAEd,QAAQ,GACT,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAG7C,MAAM,UAAU,aAAa,CAAC,MAAc,EAAE,GAAqB;IACjE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC;IAC/C,MAAM,eAAe,GAAG,GAAG,CAAC,OAAO,CAAC;IACpC,MAAM,cAAc,GAAG,GAAG,CAAC,cAAc,CAAC;IAE1C,MAAM,CAAC,EAAE,CAAC,gBAAgB,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE;QAC3C,IAAI,CAAC,cAAc,CAAC,cAAc,EAAE,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YAAE,OAAO;QAErE,IAAI,MAAM,CAAC,KAAK,KAAK,uBAAuB,EAAE,CAAC;YAC7C,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,KAAK,OAAO;gBAAE,OAAO;YAC7C,MAAM,UAAU,GACd,MAAM,CAAC,OAAO,EAAE,IAAI,IAAI,MAAM,CAAC,OAAO,EAAE,EAAE,IAAI,KAAK,CAAC;YACtD,MAAM,UAAU,GAAG,MAAM,UAAU,IAAI,eAAe,EAAE,CAAC;YACzD,IAAI,CAAC;gBACH,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,QAAiB,CAAQ,CAAC;gBAC/D,IAAI,CAAC,OAAO,EAAE,WAAW;oBAAE,OAAO;gBAClC,MAAM,OAAO,CAAC,WAAW,CAAC;oBACxB,OAAO,EAAE,MAAM,CAAC,QAAQ;oBACxB,GAAG,EAAE,MAAM,CAAC,IAAI;oBAChB,OAAO,EAAE,UAAU;oBACnB,EAAE,EAAE,MAAM,CAAC,QAAQ,CAAC,EAAE;oBACtB,IAAI,EAAE,MAAM,CAAC,QAAQ,CAAC,IAAI;iBAC3B,CAAC,CAAC;YACL,CAAC;YAAC,OAAO,CAAU,EAAE,CAAC;gBACpB,MAAM,CAAC,IAAI,CACT,aAAa,CAAC;oBACZ,EAAE,EAAE,SAAS;oBACb,EAAE,EAAE,KAAK;oBACT,KAAK,EAAE,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;iBAClD,CAAC,CACH,CAAC;YACJ,CAAC;YACD,OAAO;QACT,CAAC;QAED,IAAI,GAAG,CAAC,YAAY,IAAI,MAAM,CAAC,KAAK,KAAK,cAAc,EAAE,CAAC;YACxD,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,KAAK,OAAO;gBAAE,OAAO;YAC7C,MAAM,YAAY,GAChB,MAAM,CAAC,SAAS,EAAE,IAAI,IAAI,MAAM,CAAC,SAAS,EAAE,EAAE,IAAI,IAAI,CAAC;YACzD,MAAM,GAAG,GAAG,MAAM,YAAY,UAAU,CAAC;YACzC,IAAI,CAAC;gBACH,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,QAAiB,CAAQ,CAAC;gBAC/D,IAAI,CAAC,OAAO,EAAE,WAAW;oBAAE,OAAO;gBAClC,MAAM,OAAO,CAAC,WAAW,CAAC;oBACxB,OAAO,EAAE,MAAM,CAAC,QAAQ;oBACxB,GAAG,EAAE,MAAM,CAAC,IAAI;oBAChB,OAAO,EAAE,GAAG;oBACZ,EAAE,EAAE,MAAM,CAAC,QAAQ,CAAC,EAAE;oBACtB,IAAI,EAAE,MAAM,CAAC,QAAQ,CAAC,IAAI;iBAC3B,CAAC,CAAC;YACL,CAAC;YAAC,OAAO,CAAU,EAAE,CAAC;gBACpB,MAAM,CAAC,IAAI,CACT,aAAa,CAAC;oBACZ,EAAE,EAAE,eAAe;oBACnB,EAAE,EAAE,KAAK;oBACT,KAAK,EAAE,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;iBAClD,CAAC,CACH,CAAC;YACJ,CAAC;QACH,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,GAAG,CAAC,YAAY;QAAE,OAAO;IAE9B,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAkB,CAAC;IAE3C,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE;QAChD,IAAI,OAAO,CAAC,QAAQ,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YACtC,MAAM,IAAI,EAAE,CAAC;YACb,OAAO;QACT,CAAC;QACD,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC;QACjC,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,MAAM,IAAI,EAAE,CAAC;YACb,OAAO;QACT,CAAC;QACD,KAAK,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,QAAQ,EAAE,CAAC;YACxC,IAAI,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC1B,MAAM,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;gBAC5B,OAAO;YACT,CAAC;QACH,CAAC;QACD,MAAM,IAAI,EAAE,CAAC;IACf,CAAC,CAAC,CAAC;IAEH,UAAU,CACR,IAAI,cAAc,CAAC,mCAAmC,CAAC;SACpD,IAAI,CAAC,cAAc,CAAC;SACpB,MAAM,CAAC,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE;QACjC,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,MAAM,CAAC,MAGjC,CAAC;QACF,IAAI,CAAC,OAAO,IAAI,CAAC,KAAK;YAAE,OAAO,aAAa,CAAC;QAC7C,QAAQ,CAAC,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QAC7B,OAAO,UAAU,OAAO,MAAM,KAAK,GAAG,CAAC;IACzC,CAAC,CAAC,CACL,CAAC;IAEF,UAAU,CACR,IAAI,cAAc,CAAC,sBAAsB,CAAC;SACvC,IAAI,CAAC,SAAS,CAAC;SACf,MAAM,CAAC,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE;QACjC,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,CAAC,MAA6B,CAAC;QACzD,IAAI,CAAC,OAAO;YAAE,OAAO,YAAY,CAAC;QAClC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC;YAAE,OAAO,OAAO,OAAO,MAAM,CAAC;QACxD,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QACzB,OAAO,UAAU,OAAO,GAAG,CAAC;IAC9B,CAAC,CAAC,CACL,CAAC;IAEF,UAAU,CACR,IAAI,cAAc,CAAC,OAAO,CAAC;SACxB,IAAI,CAAC,YAAY,CAAC;SAClB,MAAM,CAAC,KAAK,IAAI,EAAE;QACjB,IAAI,QAAQ,CAAC,IAAI,KAAK,CAAC;YAAE,OAAO,aAAa,CAAC;QAC9C,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC,GAAG,CAC9C,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CACzC,CAAC;QACF,OAAO,WAAW,QAAQ,CAAC,IAAI,SAAS,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;IAC7D,CAAC,CAAC,CACL,CAAC;IAEF,OAAO,CACL,IAAI,QAAQ,CAAC,gBAAgB,CAAC;SAC3B,IAAI,CAAC,cAAc,CAAC;SACpB,OAAO,CAAC,KAAK,EAAE,IAAI,EAAE,UAAU,CAAC;SAChC,KAAK,CAAC,SAAS,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,UAAU,EAAE,EAAE,IAAI,CAAC;SACnE,OAAO,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;QACtB,MAAM,OAAO,GAAG,IAAI,CAAC,OAAiB,CAAC;QACvC,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE;YAAE,OAAO,UAAU,CAAC;QACxC,OAAO,YAAY,OAAO,EAAE,CAAC;IAC/B,CAAC,CAAC;SACD,MAAM,EAAE,CACZ,CAAC;AACJ,CAAC"}
package/lib/checkin.d.ts DELETED
@@ -1,5 +0,0 @@
1
- import { type Plugin } from "zhin.js";
2
- import type { GroupSuiteConfig } from "./config.js";
3
- export declare function getCheckinModel(): any;
4
- export declare function registerCheckin(plugin: Plugin, cfg: GroupSuiteConfig): void;
5
- //# sourceMappingURL=checkin.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"checkin.d.ts","sourceRoot":"","sources":["../src/checkin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiC,KAAK,MAAM,EAAE,MAAM,SAAS,CAAC;AACrE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAepD,wBAAgB,eAAe,IAAI,GAAG,CAErC;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,gBAAgB,GAAG,IAAI,CA+K3E"}