@zhin.js/plugin-group-suite 1.0.1 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/README.md +42 -58
  2. package/commands/checkin/index.js +11 -0
  3. package/commands/checkin/index.ts +12 -0
  4. package/commands/forget/index.js +10 -0
  5. package/commands/forget/index.ts +12 -0
  6. package/commands/keyword-add/[keyword]/index.js +16 -0
  7. package/commands/keyword-add/[keyword]/index.ts +16 -0
  8. package/commands/keyword-list/index.js +14 -0
  9. package/commands/keyword-list/index.ts +14 -0
  10. package/commands/keyword-remove/[keyword]/index.js +16 -0
  11. package/commands/keyword-remove/[keyword]/index.ts +15 -0
  12. package/commands/mypoints/index.js +10 -0
  13. package/commands/mypoints/index.ts +12 -0
  14. package/commands/mystats/index.js +10 -0
  15. package/commands/mystats/index.ts +12 -0
  16. package/commands/rank/index.js +11 -0
  17. package/commands/rank/index.ts +12 -0
  18. package/commands/stats/index.js +14 -0
  19. package/commands/stats/index.ts +21 -0
  20. package/commands/stats-rank/index.js +13 -0
  21. package/commands/stats-rank/index.ts +20 -0
  22. package/commands/stats-week/index.js +13 -0
  23. package/commands/stats-week/index.ts +20 -0
  24. package/commands/teach/index.js +11 -0
  25. package/commands/teach/index.ts +12 -0
  26. package/commands/teach-list/[[page]]/index.js +13 -0
  27. package/commands/teach-list/[[page]]/index.ts +14 -0
  28. package/commands/teach-regex/index.js +11 -0
  29. package/commands/teach-regex/index.ts +12 -0
  30. package/lib/checkin-lib.d.ts +27 -0
  31. package/lib/checkin-lib.d.ts.map +1 -0
  32. package/lib/checkin-lib.js +133 -0
  33. package/lib/checkin-lib.js.map +1 -0
  34. package/lib/config.d.ts +4 -57
  35. package/lib/config.d.ts.map +1 -1
  36. package/lib/config.js +18 -40
  37. package/lib/config.js.map +1 -1
  38. package/lib/db-store.d.ts +5 -0
  39. package/lib/db-store.d.ts.map +1 -0
  40. package/lib/db-store.js +11 -0
  41. package/lib/db-store.js.map +1 -0
  42. package/lib/index.d.ts +9 -1
  43. package/lib/index.d.ts.map +1 -1
  44. package/lib/index.js +7 -29
  45. package/lib/index.js.map +1 -1
  46. package/lib/keyword-store.d.ts +5 -0
  47. package/lib/keyword-store.d.ts.map +1 -0
  48. package/lib/keyword-store.js +17 -0
  49. package/lib/keyword-store.js.map +1 -0
  50. package/lib/memory-store.d.ts +31 -0
  51. package/lib/memory-store.d.ts.map +1 -0
  52. package/lib/memory-store.js +63 -0
  53. package/lib/memory-store.js.map +1 -0
  54. package/lib/runtime-state.d.ts +24 -0
  55. package/lib/runtime-state.d.ts.map +1 -0
  56. package/lib/runtime-state.js +15 -0
  57. package/lib/runtime-state.js.map +1 -0
  58. package/lib/shared-runtime.d.ts +26 -0
  59. package/lib/shared-runtime.d.ts.map +1 -0
  60. package/lib/shared-runtime.js +34 -0
  61. package/lib/shared-runtime.js.map +1 -0
  62. package/lib/stats-lib.d.ts +39 -0
  63. package/lib/stats-lib.d.ts.map +1 -0
  64. package/lib/stats-lib.js +172 -0
  65. package/lib/stats-lib.js.map +1 -0
  66. package/lib/tables.d.ts +4 -0
  67. package/lib/tables.d.ts.map +1 -0
  68. package/lib/tables.js +40 -0
  69. package/lib/tables.js.map +1 -0
  70. package/lib/teach-lib.d.ts +23 -0
  71. package/lib/teach-lib.d.ts.map +1 -0
  72. package/lib/teach-lib.js +228 -0
  73. package/lib/teach-lib.js.map +1 -0
  74. package/middlewares/keyword-reply/index.js +36 -0
  75. package/middlewares/keyword-reply/index.ts +37 -0
  76. package/middlewares/stats-count/index.js +20 -0
  77. package/middlewares/stats-count/index.ts +22 -0
  78. package/middlewares/teach-reply/index.js +21 -0
  79. package/middlewares/teach-reply/index.ts +22 -0
  80. package/package.json +57 -14
  81. package/plugin.js +38 -0
  82. package/schema.json +17 -0
  83. package/skills/checkin/tools/checkin_query/index.js +42 -0
  84. package/skills/checkin/tools/checkin_query/index.ts +41 -0
  85. package/skills/checkin/tools/checkin_rank/index.js +34 -0
  86. package/skills/checkin/tools/checkin_rank/index.ts +34 -0
  87. package/skills/group-daily-analysis/SKILL.md +11 -4
  88. package/skills/group-daily-analysis/tools/group_announce/index.js +14 -0
  89. package/skills/group-daily-analysis/tools/group_announce/index.ts +14 -0
  90. package/skills/group-daily-analysis/tools/stats_query/index.js +35 -0
  91. package/skills/group-daily-analysis/tools/stats_query/index.ts +36 -0
  92. package/skills/group-daily-analysis/tools/stats_user/index.js +50 -0
  93. package/skills/group-daily-analysis/tools/stats_user/index.ts +46 -0
  94. package/src/checkin-lib.ts +167 -0
  95. package/src/config.ts +23 -57
  96. package/src/db-store.ts +19 -0
  97. package/src/index.ts +39 -33
  98. package/src/keyword-store.ts +18 -0
  99. package/src/memory-store.ts +90 -0
  100. package/src/runtime-state.ts +41 -0
  101. package/src/shared-runtime.ts +44 -0
  102. package/src/stats-lib.ts +198 -0
  103. package/src/tables.ts +46 -0
  104. package/src/teach-lib.ts +294 -0
  105. package/lib/admin.d.ts +0 -4
  106. package/lib/admin.d.ts.map +0 -1
  107. package/lib/admin.js +0 -123
  108. package/lib/admin.js.map +0 -1
  109. package/lib/checkin.d.ts +0 -5
  110. package/lib/checkin.d.ts.map +0 -1
  111. package/lib/checkin.js +0 -168
  112. package/lib/checkin.js.map +0 -1
  113. package/lib/daily-analysis.d.ts +0 -7
  114. package/lib/daily-analysis.d.ts.map +0 -1
  115. package/lib/daily-analysis.js +0 -334
  116. package/lib/daily-analysis.js.map +0 -1
  117. package/lib/shared.d.ts +0 -25
  118. package/lib/shared.d.ts.map +0 -1
  119. package/lib/shared.js +0 -40
  120. package/lib/shared.js.map +0 -1
  121. package/lib/stats.d.ts +0 -12
  122. package/lib/stats.d.ts.map +0 -1
  123. package/lib/stats.js +0 -256
  124. package/lib/stats.js.map +0 -1
  125. package/lib/teach.d.ts +0 -4
  126. package/lib/teach.d.ts.map +0 -1
  127. package/lib/teach.js +0 -294
  128. package/lib/teach.js.map +0 -1
  129. package/plugin.yml +0 -3
  130. package/src/admin.ts +0 -145
  131. package/src/checkin.ts +0 -196
  132. package/src/daily-analysis.ts +0 -458
  133. package/src/shared.ts +0 -51
  134. package/src/stats.ts +0 -297
  135. package/src/teach.ts +0 -307
  136. package/tools/checkin-query/checkin-query.tool.md +0 -11
  137. package/tools/checkin-query/handler.ts +0 -22
  138. package/tools/checkin-rank/checkin-rank.tool.md +0 -11
  139. package/tools/checkin-rank/handler.ts +0 -16
  140. package/tools/stats-query/handler.ts +0 -31
  141. package/tools/stats-query/stats-query.tool.md +0 -16
  142. package/tools/stats-user/handler.ts +0 -28
  143. package/tools/stats-user/stats-user.tool.md +0 -12
@@ -0,0 +1,37 @@
1
+ import { defineMiddleware } from 'zhin.js/middleware';
2
+ import type { Message } from '@zhin.js/core/runtime';
3
+ import { resolveGroupSuiteConfig, type GroupSuiteConfig } from '../../src/config.js';
4
+ import { matchKeyword } from '../../src/keyword-store.js';
5
+ import { resolveGroupSuiteRuntime } from '../../src/runtime-state.js';
6
+
7
+ /**
8
+ * Keyword auto-reply (enabled via config.keywordReply).
9
+ * Uses Runtime Message.content / metadata (legacy used $raw / $channel).
10
+ */
11
+ export default defineMiddleware<Message, GroupSuiteConfig>({
12
+ target: 'inbound',
13
+ async handle(context, next) {
14
+ const config = resolveGroupSuiteConfig(context.config);
15
+ if (!config.keywordReply) {
16
+ await next();
17
+ return;
18
+ }
19
+ const meta = context.input.metadata ?? {};
20
+ const channelType = String(meta.type ?? meta.channelType ?? '');
21
+ if (channelType === 'private') {
22
+ await next();
23
+ return;
24
+ }
25
+ const text = typeof context.input.content === 'string' ? context.input.content.trim() : '';
26
+ if (!text) {
27
+ await next();
28
+ return;
29
+ }
30
+ const reply = matchKeyword(text, resolveGroupSuiteRuntime(context).keywords);
31
+ if (reply) {
32
+ await context.input.$reply(reply);
33
+ return;
34
+ }
35
+ await next();
36
+ },
37
+ });
@@ -0,0 +1,20 @@
1
+ // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { defineMiddleware } from 'zhin.js/middleware';
3
+ import { recordMessage } from "../../lib/stats-lib.js";
4
+ import { resolveGroupSuiteRuntime } from "../../lib/runtime-state.js";
5
+ /**
6
+ * Buffer inbound messages into in-memory message_stats (flushed on query).
7
+ * Skips command messages (legacy used `isActionMessage` to skip interactive
8
+ * action/command messages; the Runtime pipeline represents commands as
9
+ * text starting with the dispatcher prefix '/').
10
+ */
11
+ export default defineMiddleware({
12
+ target: 'inbound',
13
+ async handle(context, next) {
14
+ const content = typeof context.input.content === 'string' ? context.input.content : '';
15
+ if (!content.startsWith('/')) {
16
+ recordMessage(context.input, resolveGroupSuiteRuntime(context));
17
+ }
18
+ await next();
19
+ },
20
+ });
@@ -0,0 +1,22 @@
1
+ import { defineMiddleware } from 'zhin.js/middleware';
2
+ import type { Message } from '@zhin.js/core/runtime';
3
+ import type { GroupSuiteConfig } from '../../src/config.js';
4
+ import { recordMessage } from '../../src/stats-lib.js';
5
+ import { resolveGroupSuiteRuntime } from '../../src/runtime-state.js';
6
+
7
+ /**
8
+ * Buffer inbound messages into in-memory message_stats (flushed on query).
9
+ * Skips command messages (legacy used `isActionMessage` to skip interactive
10
+ * action/command messages; the Runtime pipeline represents commands as
11
+ * text starting with the dispatcher prefix '/').
12
+ */
13
+ export default defineMiddleware<Message, GroupSuiteConfig>({
14
+ target: 'inbound',
15
+ async handle(context, next) {
16
+ const content = typeof context.input.content === 'string' ? context.input.content : '';
17
+ if (!content.startsWith('/')) {
18
+ recordMessage(context.input, resolveGroupSuiteRuntime(context));
19
+ }
20
+ await next();
21
+ },
22
+ });
@@ -0,0 +1,21 @@
1
+ // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { defineMiddleware } from 'zhin.js/middleware';
3
+ import { resolveGroupSuiteConfig } from "../../lib/config.js";
4
+ import { tryTeachReply } from "../../lib/teach-lib.js";
5
+ import { resolveGroupSuiteRuntime } from "../../lib/runtime-state.js";
6
+ /**
7
+ * Teach Q&A auto-reply (exact / optional regex).
8
+ * Uses Runtime Message.content / metadata.
9
+ */
10
+ export default defineMiddleware({
11
+ target: 'inbound',
12
+ async handle(context, next) {
13
+ const config = resolveGroupSuiteConfig(context.config);
14
+ const reply = await tryTeachReply(context.input, config, resolveGroupSuiteRuntime(context));
15
+ if (reply) {
16
+ await context.input.$reply(reply);
17
+ return;
18
+ }
19
+ await next();
20
+ },
21
+ });
@@ -0,0 +1,22 @@
1
+ import { defineMiddleware } from 'zhin.js/middleware';
2
+ import type { Message } from '@zhin.js/core/runtime';
3
+ import { resolveGroupSuiteConfig, type GroupSuiteConfig } from '../../src/config.js';
4
+ import { tryTeachReply } from '../../src/teach-lib.js';
5
+ import { resolveGroupSuiteRuntime } from '../../src/runtime-state.js';
6
+
7
+ /**
8
+ * Teach Q&A auto-reply (exact / optional regex).
9
+ * Uses Runtime Message.content / metadata.
10
+ */
11
+ export default defineMiddleware<Message, GroupSuiteConfig>({
12
+ target: 'inbound',
13
+ async handle(context, next) {
14
+ const config = resolveGroupSuiteConfig(context.config);
15
+ const reply = await tryTeachReply(context.input, config, resolveGroupSuiteRuntime(context));
16
+ if (reply) {
17
+ await context.input.$reply(reply);
18
+ return;
19
+ }
20
+ await next();
21
+ },
22
+ });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@zhin.js/plugin-group-suite",
3
- "version": "1.0.1",
4
- "description": "Zhin.js 群运营套件 — 群管、签到、统计、日常分析、问答(合并原 5 个插件)",
3
+ "version": "1.1.2",
4
+ "description": "Zhin.js 群运营套件 — slice-2 checkin/teach/stats + keywords (Plugin Runtime, in-memory)",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
7
7
  "types": "./lib/index.d.ts",
@@ -14,11 +14,13 @@
14
14
  "./package.json": "./package.json"
15
15
  },
16
16
  "files": [
17
+ "plugin.js",
18
+ "schema.json",
19
+ "commands",
20
+ "middlewares",
17
21
  "src",
18
22
  "lib",
19
23
  "skills",
20
- "tools",
21
- "plugin.yml",
22
24
  "README.md"
23
25
  ],
24
26
  "keywords": [
@@ -29,8 +31,7 @@
29
31
  "group",
30
32
  "checkin",
31
33
  "stats",
32
- "teach",
33
- "welcome"
34
+ "teach"
34
35
  ],
35
36
  "author": {
36
37
  "name": "lc-cn",
@@ -39,19 +40,31 @@
39
40
  },
40
41
  "license": "MIT",
41
42
  "dependencies": {
42
- "@zhin.js/satori": "0.2.13"
43
+ "@zhin.js/core": "1.1.37",
44
+ "@zhin.js/satori": "1.1.0",
45
+ "@zhin.js/tool": "1.1.1",
46
+ "@zhin.js/skill": "1.1.1"
43
47
  },
44
48
  "devDependencies": {
45
49
  "typescript": "^6.0.3",
46
- "zhin.js": "2.0.1",
47
- "@zhin.js/plugin-html-renderer": "1.0.1"
50
+ "vitest": "^4.1.10",
51
+ "zod": "^4.4.3",
52
+ "zhin.js": "1.1.2"
48
53
  },
49
54
  "peerDependencies": {
50
- "zhin.js": "2.0.1",
51
- "@zhin.js/plugin-html-renderer": "1.0.1"
55
+ "zod": "^4.0.0",
56
+ "@zhin.js/command": "^1.1.1",
57
+ "@zhin.js/middleware": "^1.1.1",
58
+ "zhin.js": "^1.1.2"
52
59
  },
53
60
  "peerDependenciesMeta": {
54
- "@zhin.js/plugin-html-renderer": {
61
+ "@zhin.js/command": {
62
+ "optional": true
63
+ },
64
+ "@zhin.js/middleware": {
65
+ "optional": true
66
+ },
67
+ "zod": {
55
68
  "optional": true
56
69
  }
57
70
  },
@@ -64,8 +77,38 @@
64
77
  "access": "public",
65
78
  "registry": "https://registry.npmjs.org"
66
79
  },
80
+ "engines": {
81
+ "node": "^20.19.0 || >=22.12.0"
82
+ },
83
+ "zhin": {
84
+ "protocol": 1,
85
+ "type": "plugin",
86
+ "entry": "./plugin.js",
87
+ "engine": "^1.0.0",
88
+ "runtime": "trusted",
89
+ "features": [
90
+ {
91
+ "package": "@zhin.js/command",
92
+ "api": "^1.0.0"
93
+ },
94
+ {
95
+ "package": "@zhin.js/middleware",
96
+ "api": "^1.0.0"
97
+ },
98
+ {
99
+ "package": "@zhin.js/tool",
100
+ "api": "^1.0.0"
101
+ },
102
+ {
103
+ "package": "@zhin.js/skill",
104
+ "api": "^1.0.0"
105
+ }
106
+ ],
107
+ "plugins": []
108
+ },
67
109
  "scripts": {
68
- "build": "tsc --build",
69
- "clean": "rimraf lib"
110
+ "build": "tsc",
111
+ "clean": "rimraf lib",
112
+ "test": "NODE_OPTIONS=--experimental-strip-types vitest run --root ../../.. plugins/utils/group-suite/tests"
70
113
  }
71
114
  }
package/plugin.js ADDED
@@ -0,0 +1,38 @@
1
+ // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { definePlugin, databaseHostToken } from 'zhin.js';
3
+ import { createInMemoryGroupSuiteDb } from "./lib/memory-store.js";
4
+ import { createGroupSuiteRuntime, groupSuiteRuntimeToken } from "./lib/runtime-state.js";
5
+ import { flushStatsBuffer } from "./lib/stats-lib.js";
6
+ import { defineGroupSuiteTables } from "./lib/tables.js";
7
+ /**
8
+ * Plugin Runtime:
9
+ * - checkin / teach / stats commands + keyword middleware
10
+ * - DB: prefer `databaseHostToken`; else in-memory fallback
11
+ * - Stats buffer: periodic flush every 10s + final flush on dispose
12
+ */
13
+ export default definePlugin({
14
+ name: 'group-suite',
15
+ metadata: {
16
+ displayName: 'Group Suite',
17
+ },
18
+ setup(context) {
19
+ // host 与 memory 模型表面结构兼容(where 均返回 PromiseLike),可直接互换。
20
+ const db = (() => {
21
+ if (context.resources.has(databaseHostToken)) {
22
+ const host = context.resources.use(databaseHostToken);
23
+ defineGroupSuiteTables(host);
24
+ return host;
25
+ }
26
+ return createInMemoryGroupSuiteDb();
27
+ })();
28
+ const runtime = createGroupSuiteRuntime(db);
29
+ context.resources.provide(groupSuiteRuntimeToken, runtime);
30
+ const flushTimer = setInterval(() => {
31
+ void flushStatsBuffer(runtime);
32
+ }, 10_000);
33
+ context.lifecycle.add(async () => {
34
+ clearInterval(flushTimer);
35
+ await flushStatsBuffer(runtime);
36
+ });
37
+ },
38
+ });
package/schema.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "type": "object",
4
+ "additionalProperties": false,
5
+ "properties": {
6
+ "keywordReply": { "type": "boolean", "default": false },
7
+ "basePointsMin": { "type": "number", "default": 10 },
8
+ "basePointsMax": { "type": "number", "default": 30 },
9
+ "streakBonus": { "type": "number", "default": 5 },
10
+ "streakCap": { "type": "number", "default": 50 },
11
+ "rankSize": { "type": "number", "default": 10 },
12
+ "teachMaxPerGroup": { "type": "number", "default": 200 },
13
+ "teachCooldownMs": { "type": "number", "default": 3000 },
14
+ "teachAllowRegex": { "type": "boolean", "default": true },
15
+ "teachPageSize": { "type": "number", "default": 10 }
16
+ }
17
+ }
@@ -0,0 +1,42 @@
1
+ // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { defineAgentTool } from '@zhin.js/tool';
3
+ import { getCheckinModel } from "../../../../lib/db-store.js";
4
+ import { groupSuiteRuntimeToken } from "../../../../lib/runtime-state.js";
5
+ import { todayStr } from "../../../../lib/shared-runtime.js";
6
+ export default defineAgentTool({
7
+ description: '查询用户的签到积分,或汇总当前范围的签到数据',
8
+ inputSchema: {
9
+ type: 'object',
10
+ properties: {
11
+ user_id: { type: 'string', description: '用户 ID;省略时返回汇总' },
12
+ group_id: { type: 'string', description: '群 ID;省略时使用当前 IM 会话' },
13
+ },
14
+ },
15
+ requiresApproval: 'never',
16
+ async execute({ user_id: userId, group_id: requestedGroupId }, context) {
17
+ const runtime = context.use(groupSuiteRuntimeToken);
18
+ const model = getCheckinModel(runtime.db);
19
+ if (!model)
20
+ return '签到数据库尚未就绪';
21
+ const groupId = requestedGroupId ?? currentGroupId(context.origin);
22
+ const query = {
23
+ ...(userId ? { user_id: userId } : {}),
24
+ ...(groupId ? { context_type: 'group', context_id: groupId } : {}),
25
+ };
26
+ const rows = Object.keys(query).length > 0
27
+ ? await model.select().where(query)
28
+ : await model.select();
29
+ if (userId) {
30
+ if (rows.length === 0)
31
+ return `用户 ${userId} 没有签到记录`;
32
+ const user = rows[0];
33
+ return `${String(user.user_name ?? userId)}: 积分=${Number(user.points ?? 0)}, 累计=${Number(user.total_checkins ?? 0)}天, 连续=${Number(user.streak ?? 0)}天, 最长=${Number(user.max_streak ?? 0)}天, 上次=${String(user.last_checkin ?? '')}`;
34
+ }
35
+ const totalPoints = rows.reduce((sum, user) => sum + Number(user.points ?? 0), 0);
36
+ const todayCount = rows.filter((user) => user.last_checkin === todayStr()).length;
37
+ return `签到系统统计\n总用户: ${rows.length}\n总积分: ${totalPoints}\n今日签到: ${todayCount}人`;
38
+ },
39
+ });
40
+ function currentGroupId(origin) {
41
+ return origin.kind === 'im' && origin.scope !== 'private' ? origin.sceneId : '';
42
+ }
@@ -0,0 +1,41 @@
1
+ import { defineAgentTool } from '@zhin.js/tool';
2
+ import { getCheckinModel } from '../../../../src/db-store.js';
3
+ import { groupSuiteRuntimeToken } from '../../../../src/runtime-state.js';
4
+ import { todayStr } from '../../../../src/shared-runtime.js';
5
+
6
+ export default defineAgentTool<{ user_id?: string; group_id?: string }>({
7
+ description: '查询用户的签到积分,或汇总当前范围的签到数据',
8
+ inputSchema: {
9
+ type: 'object',
10
+ properties: {
11
+ user_id: { type: 'string', description: '用户 ID;省略时返回汇总' },
12
+ group_id: { type: 'string', description: '群 ID;省略时使用当前 IM 会话' },
13
+ },
14
+ },
15
+ requiresApproval: 'never',
16
+ async execute({ user_id: userId, group_id: requestedGroupId }, context) {
17
+ const runtime = context.use(groupSuiteRuntimeToken);
18
+ const model = getCheckinModel(runtime.db);
19
+ if (!model) return '签到数据库尚未就绪';
20
+ const groupId = requestedGroupId ?? currentGroupId(context.origin);
21
+ const query = {
22
+ ...(userId ? { user_id: userId } : {}),
23
+ ...(groupId ? { context_type: 'group', context_id: groupId } : {}),
24
+ };
25
+ const rows = Object.keys(query).length > 0
26
+ ? await model.select().where(query)
27
+ : await model.select();
28
+ if (userId) {
29
+ if (rows.length === 0) return `用户 ${userId} 没有签到记录`;
30
+ const user = rows[0]!;
31
+ return `${String(user.user_name ?? userId)}: 积分=${Number(user.points ?? 0)}, 累计=${Number(user.total_checkins ?? 0)}天, 连续=${Number(user.streak ?? 0)}天, 最长=${Number(user.max_streak ?? 0)}天, 上次=${String(user.last_checkin ?? '')}`;
32
+ }
33
+ const totalPoints = rows.reduce((sum, user) => sum + Number(user.points ?? 0), 0);
34
+ const todayCount = rows.filter((user) => user.last_checkin === todayStr()).length;
35
+ return `签到系统统计\n总用户: ${rows.length}\n总积分: ${totalPoints}\n今日签到: ${todayCount}人`;
36
+ },
37
+ });
38
+
39
+ function currentGroupId(origin: import('@zhin.js/tool').ToolInvocationOrigin): string {
40
+ return origin.kind === 'im' && origin.scope !== 'private' ? origin.sceneId : '';
41
+ }
@@ -0,0 +1,34 @@
1
+ // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { defineAgentTool } from '@zhin.js/tool';
3
+ import { getCheckinModel } from "../../../../lib/db-store.js";
4
+ import { groupSuiteRuntimeToken } from "../../../../lib/runtime-state.js";
5
+ export default defineAgentTool({
6
+ description: '查询当前群或指定群的签到积分排行榜',
7
+ inputSchema: {
8
+ type: 'object',
9
+ properties: {
10
+ limit: { type: 'number', description: '返回数量,默认 10,最多 50' },
11
+ group_id: { type: 'string', description: '群 ID;省略时使用当前 IM 会话' },
12
+ },
13
+ },
14
+ requiresApproval: 'never',
15
+ async execute({ limit, group_id: requestedGroupId }, context) {
16
+ const model = getCheckinModel(context.use(groupSuiteRuntimeToken).db);
17
+ if (!model)
18
+ return '签到数据库尚未就绪';
19
+ const groupId = requestedGroupId
20
+ ?? (context.origin.kind === 'im' && context.origin.scope !== 'private'
21
+ ? context.origin.sceneId
22
+ : '');
23
+ const rows = groupId
24
+ ? await model.select().where({ context_type: 'group', context_id: groupId })
25
+ : await model.select();
26
+ const count = Math.min(Math.max(Number(limit) || 10, 1), 50);
27
+ const ranked = [...rows]
28
+ .sort((left, right) => Number(right.points ?? 0) - Number(left.points ?? 0))
29
+ .slice(0, count);
30
+ if (ranked.length === 0)
31
+ return '暂无排行数据';
32
+ return ranked.map((user, index) => `${index + 1}. ${String(user.user_name ?? user.user_id)} — ${Number(user.points ?? 0)}分 (连续${Number(user.streak ?? 0)}天)`).join('\n');
33
+ },
34
+ });
@@ -0,0 +1,34 @@
1
+ import { defineAgentTool } from '@zhin.js/tool';
2
+ import { getCheckinModel } from '../../../../src/db-store.js';
3
+ import { groupSuiteRuntimeToken } from '../../../../src/runtime-state.js';
4
+
5
+ export default defineAgentTool<{ limit?: number; group_id?: string }>({
6
+ description: '查询当前群或指定群的签到积分排行榜',
7
+ inputSchema: {
8
+ type: 'object',
9
+ properties: {
10
+ limit: { type: 'number', description: '返回数量,默认 10,最多 50' },
11
+ group_id: { type: 'string', description: '群 ID;省略时使用当前 IM 会话' },
12
+ },
13
+ },
14
+ requiresApproval: 'never',
15
+ async execute({ limit, group_id: requestedGroupId }, context) {
16
+ const model = getCheckinModel(context.use(groupSuiteRuntimeToken).db);
17
+ if (!model) return '签到数据库尚未就绪';
18
+ const groupId = requestedGroupId
19
+ ?? (context.origin.kind === 'im' && context.origin.scope !== 'private'
20
+ ? context.origin.sceneId
21
+ : '');
22
+ const rows = groupId
23
+ ? await model.select().where({ context_type: 'group', context_id: groupId })
24
+ : await model.select();
25
+ const count = Math.min(Math.max(Number(limit) || 10, 1), 50);
26
+ const ranked = [...rows]
27
+ .sort((left, right) => Number(right.points ?? 0) - Number(left.points ?? 0))
28
+ .slice(0, count);
29
+ if (ranked.length === 0) return '暂无排行数据';
30
+ return ranked.map((user, index) =>
31
+ `${index + 1}. ${String(user.user_name ?? user.user_id)} — ${Number(user.points ?? 0)}分 (连续${Number(user.streak ?? 0)}天)`,
32
+ ).join('\n');
33
+ },
34
+ });
@@ -1,8 +1,8 @@
1
1
  ---
2
2
  name: group-daily-analysis
3
3
  description: >-
4
- 群日常分析:基于收件箱统计群消息数量、参与人数、活跃时段,
5
- 支持按群启用/禁用和定时分析报告。通过命令和定时任务运行,无 AI 工具。
4
+ 群日常分析:查询群消息数量、参与人数和成员活跃度,并生成群公告文本。
5
+ 用户询问今日、本周、本月群统计、成员活跃度或需要整理公告时使用。
6
6
  keywords:
7
7
  - 群分析
8
8
  - 日常分析
@@ -14,16 +14,23 @@ tags:
14
14
  - analysis
15
15
  - stats
16
16
  - inbox
17
- tools: []
17
+ tools:
18
+ - stats_query
19
+ - stats_user
20
+ - group_announce
18
21
  ---
19
22
 
20
23
  # 群日常分析
21
24
 
22
- 基于收件箱数据自动分析群活跃度。无 AI 工具可调用。
25
+ 基于运行时统计数据分析群活跃度,并在需要时生成统一格式的群公告文本。
23
26
 
24
27
  ## 能力
25
28
 
26
29
  - 统计群消息数量、参与人数、活跃时段分布
30
+ - 查询指定成员的今日、本周和累计消息数
31
+ - 生成群公告或通知文本
27
32
  - 支持按群启用/禁用分析功能
28
33
  - 定时任务自动生成分析报告推送到群内
29
34
  - 通过聊天命令管理分析设置
35
+
36
+ `group_announce` 只生成文本,不直接发送消息;发送仍走统一消息链路。
@@ -0,0 +1,14 @@
1
+ // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { defineAgentTool } from '@zhin.js/tool';
3
+ export default defineAgentTool({
4
+ description: '生成群聊公告或通知消息',
5
+ inputSchema: {
6
+ type: 'object',
7
+ properties: { message: { type: 'string', description: '公告内容' } },
8
+ required: ['message'],
9
+ },
10
+ requiresApproval: 'never',
11
+ execute({ message }) {
12
+ return `📢 群公告:\n${message}`;
13
+ },
14
+ });
@@ -0,0 +1,14 @@
1
+ import { defineAgentTool } from '@zhin.js/tool';
2
+
3
+ export default defineAgentTool<{ message: string }>({
4
+ description: '生成群聊公告或通知消息',
5
+ inputSchema: {
6
+ type: 'object',
7
+ properties: { message: { type: 'string', description: '公告内容' } },
8
+ required: ['message'],
9
+ },
10
+ requiresApproval: 'never',
11
+ execute({ message }) {
12
+ return `📢 群公告:\n${message}`;
13
+ },
14
+ });
@@ -0,0 +1,35 @@
1
+ // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { defineAgentTool } from '@zhin.js/tool';
3
+ import { groupSuiteRuntimeToken } from "../../../../lib/runtime-state.js";
4
+ import { flushStatsBuffer, monthStartStr, queryStats, weekStartStr } from "../../../../lib/stats-lib.js";
5
+ import { todayStr } from "../../../../lib/shared-runtime.js";
6
+ export default defineAgentTool({
7
+ description: '查询当前群或指定群的消息统计',
8
+ inputSchema: {
9
+ type: 'object',
10
+ properties: {
11
+ group_id: { type: 'string', description: '群 ID;省略时使用当前 IM 会话' },
12
+ period: { type: 'string', enum: ['today', 'week', 'month'], description: '统计时段' },
13
+ },
14
+ },
15
+ requiresApproval: 'never',
16
+ async execute({ group_id: requestedGroupId, period = 'today' }, context) {
17
+ const runtime = context.use(groupSuiteRuntimeToken);
18
+ await flushStatsBuffer(runtime);
19
+ const groupId = requestedGroupId
20
+ ?? (context.origin.kind === 'im' && context.origin.scope !== 'private'
21
+ ? context.origin.sceneId
22
+ : '');
23
+ const fromDate = period === 'week'
24
+ ? weekStartStr()
25
+ : period === 'month' ? monthStartStr() : todayStr();
26
+ const stats = await queryStats(groupId, fromDate, runtime);
27
+ if (stats.size === 0)
28
+ return `${period} 暂无消息统计数据`;
29
+ const ranked = [...stats.values()].sort((left, right) => right.count - left.count);
30
+ const total = ranked.reduce((sum, item) => sum + item.count, 0);
31
+ const lines = ranked.slice(0, 10)
32
+ .map((item, index) => `${index + 1}. ${item.name} — ${item.count}条`);
33
+ return `消息统计 (${period})\n总消息: ${total}条, 活跃用户: ${stats.size}人\n${lines.join('\n')}`;
34
+ },
35
+ });
@@ -0,0 +1,36 @@
1
+ import { defineAgentTool } from '@zhin.js/tool';
2
+ import { groupSuiteRuntimeToken } from '../../../../src/runtime-state.js';
3
+ import { flushStatsBuffer, monthStartStr, queryStats, weekStartStr } from '../../../../src/stats-lib.js';
4
+ import { todayStr } from '../../../../src/shared-runtime.js';
5
+
6
+ type Period = 'today' | 'week' | 'month';
7
+
8
+ export default defineAgentTool<{ group_id?: string; period?: Period }>({
9
+ description: '查询当前群或指定群的消息统计',
10
+ inputSchema: {
11
+ type: 'object',
12
+ properties: {
13
+ group_id: { type: 'string', description: '群 ID;省略时使用当前 IM 会话' },
14
+ period: { type: 'string', enum: ['today', 'week', 'month'], description: '统计时段' },
15
+ },
16
+ },
17
+ requiresApproval: 'never',
18
+ async execute({ group_id: requestedGroupId, period = 'today' }, context) {
19
+ const runtime = context.use(groupSuiteRuntimeToken);
20
+ await flushStatsBuffer(runtime);
21
+ const groupId = requestedGroupId
22
+ ?? (context.origin.kind === 'im' && context.origin.scope !== 'private'
23
+ ? context.origin.sceneId
24
+ : '');
25
+ const fromDate = period === 'week'
26
+ ? weekStartStr()
27
+ : period === 'month' ? monthStartStr() : todayStr();
28
+ const stats = await queryStats(groupId, fromDate, runtime);
29
+ if (stats.size === 0) return `${period} 暂无消息统计数据`;
30
+ const ranked = [...stats.values()].sort((left, right) => right.count - left.count);
31
+ const total = ranked.reduce((sum, item) => sum + item.count, 0);
32
+ const lines = ranked.slice(0, 10)
33
+ .map((item, index) => `${index + 1}. ${item.name} — ${item.count}条`);
34
+ return `消息统计 (${period})\n总消息: ${total}条, 活跃用户: ${stats.size}人\n${lines.join('\n')}`;
35
+ },
36
+ });
@@ -0,0 +1,50 @@
1
+ // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { defineAgentTool } from '@zhin.js/tool';
3
+ import { getStatsModel } from "../../../../lib/db-store.js";
4
+ import { groupSuiteRuntimeToken } from "../../../../lib/runtime-state.js";
5
+ import { todayStr } from "../../../../lib/shared-runtime.js";
6
+ import { flushStatsBuffer, weekStartStr } from "../../../../lib/stats-lib.js";
7
+ export default defineAgentTool({
8
+ description: '查询指定用户在当前群或指定群的消息统计',
9
+ inputSchema: {
10
+ type: 'object',
11
+ properties: {
12
+ user_id: { type: 'string', description: '用户 ID' },
13
+ group_id: { type: 'string', description: '群 ID;省略时使用当前 IM 会话' },
14
+ },
15
+ required: ['user_id'],
16
+ },
17
+ requiresApproval: 'never',
18
+ async execute({ user_id: userId, group_id: requestedGroupId }, context) {
19
+ const runtime = context.use(groupSuiteRuntimeToken);
20
+ await flushStatsBuffer(runtime);
21
+ const model = getStatsModel(runtime.db);
22
+ if (!model)
23
+ return '统计数据库尚未就绪';
24
+ const groupId = requestedGroupId
25
+ ?? (context.origin.kind === 'im' && context.origin.scope !== 'private'
26
+ ? context.origin.sceneId
27
+ : '');
28
+ const rows = await model.select().where({
29
+ user_id: userId,
30
+ ...(groupId ? { group_id: groupId } : {}),
31
+ });
32
+ if (rows.length === 0)
33
+ return `用户 ${userId} 暂无消息记录`;
34
+ const today = todayStr();
35
+ const weekStart = weekStartStr();
36
+ let todayCount = 0;
37
+ let weekCount = 0;
38
+ let totalCount = 0;
39
+ for (const row of rows) {
40
+ const count = Number(row.count ?? 0);
41
+ totalCount += count;
42
+ if (String(row.date) >= weekStart)
43
+ weekCount += count;
44
+ if (row.date === today)
45
+ todayCount += count;
46
+ }
47
+ const name = String(rows[0].user_name ?? userId);
48
+ return `${name} 的统计\n今日: ${todayCount}条\n本周: ${weekCount}条\n总计: ${totalCount}条\n活跃天数: ${rows.length}天`;
49
+ },
50
+ });