@zhin.js/plugin-group-suite 1.1.0 → 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.
- package/README.md +2 -2
- package/commands/{checkin.js → checkin/index.js} +3 -3
- package/commands/{checkin.ts → checkin/index.ts} +3 -3
- package/commands/{forget.js → forget/index.js} +2 -2
- package/commands/{forget.ts → forget/index.ts} +3 -3
- package/commands/keyword-add/{[keyword].js → [keyword]/index.js} +3 -3
- package/commands/keyword-add/{[keyword].ts → [keyword]/index.ts} +4 -4
- package/commands/{keyword-list.js → keyword-list/index.js} +3 -3
- package/commands/{keyword-list.ts → keyword-list/index.ts} +4 -4
- package/commands/keyword-remove/{[keyword].js → [keyword]/index.js} +3 -3
- package/commands/keyword-remove/{[keyword].ts → [keyword]/index.ts} +4 -4
- package/commands/{mypoints.js → mypoints/index.js} +2 -2
- package/commands/{mypoints.ts → mypoints/index.ts} +3 -3
- package/commands/{mystats.js → mystats/index.js} +2 -2
- package/commands/{mystats.ts → mystats/index.ts} +3 -3
- package/commands/{rank.js → rank/index.js} +3 -3
- package/commands/{rank.ts → rank/index.ts} +3 -3
- package/commands/{stats.js → stats/index.js} +4 -4
- package/commands/{stats.ts → stats/index.ts} +4 -4
- package/commands/{stats-rank.js → stats-rank/index.js} +3 -3
- package/commands/{stats-rank.ts → stats-rank/index.ts} +3 -3
- package/commands/{stats-week.js → stats-week/index.js} +3 -3
- package/commands/{stats-week.ts → stats-week/index.ts} +3 -3
- package/commands/{teach.js → teach/index.js} +3 -3
- package/commands/{teach.ts → teach/index.ts} +3 -3
- package/commands/teach-list/{[[page]].js → [[page]]/index.js} +3 -3
- package/commands/teach-list/{[[page]].ts → [[page]]/index.ts} +3 -3
- package/commands/{teach-regex.js → teach-regex/index.js} +3 -3
- package/commands/{teach-regex.ts → teach-regex/index.ts} +3 -3
- package/lib/checkin-lib.d.ts +3 -3
- package/lib/checkin-lib.d.ts.map +1 -1
- package/lib/checkin-lib.js +9 -10
- package/lib/checkin-lib.js.map +1 -1
- package/lib/db-store.d.ts +3 -10
- package/lib/db-store.d.ts.map +1 -1
- package/lib/db-store.js +7 -34
- package/lib/db-store.js.map +1 -1
- package/lib/index.d.ts +4 -4
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +4 -4
- package/lib/index.js.map +1 -1
- package/lib/keyword-store.d.ts +4 -5
- package/lib/keyword-store.d.ts.map +1 -1
- package/lib/keyword-store.js +4 -8
- package/lib/keyword-store.js.map +1 -1
- package/lib/memory-store.d.ts +1 -1
- package/lib/memory-store.js +1 -1
- package/lib/runtime-state.d.ts +3 -4
- package/lib/runtime-state.d.ts.map +1 -1
- package/lib/runtime-state.js +2 -8
- package/lib/runtime-state.js.map +1 -1
- package/lib/stats-lib.d.ts +5 -7
- package/lib/stats-lib.d.ts.map +1 -1
- package/lib/stats-lib.js +5 -10
- package/lib/stats-lib.js.map +1 -1
- package/lib/tables.js +1 -1
- package/lib/teach-lib.d.ts +4 -6
- package/lib/teach-lib.d.ts.map +1 -1
- package/lib/teach-lib.js +6 -11
- package/lib/teach-lib.js.map +1 -1
- package/middlewares/{keyword-reply.js → keyword-reply/index.js} +4 -4
- package/middlewares/{keyword-reply.ts → keyword-reply/index.ts} +4 -4
- package/middlewares/{stats-count.js → stats-count/index.js} +2 -2
- package/middlewares/{stats-count.ts → stats-count/index.ts} +3 -3
- package/middlewares/{teach-reply.js → teach-reply/index.js} +3 -3
- package/middlewares/{teach-reply.ts → teach-reply/index.ts} +3 -3
- package/package.json +18 -14
- package/plugin.js +0 -2
- package/skills/checkin/tools/checkin_query/index.js +42 -0
- package/skills/checkin/tools/checkin_query/index.ts +41 -0
- package/skills/checkin/tools/checkin_rank/index.js +34 -0
- package/skills/checkin/tools/checkin_rank/index.ts +34 -0
- package/skills/group-daily-analysis/SKILL.md +36 -0
- package/skills/group-daily-analysis/tools/group_announce/index.js +14 -0
- package/skills/group-daily-analysis/tools/group_announce/index.ts +14 -0
- package/skills/group-daily-analysis/tools/stats_query/index.js +35 -0
- package/skills/group-daily-analysis/tools/stats_query/index.ts +36 -0
- package/skills/group-daily-analysis/tools/stats_user/index.js +50 -0
- package/skills/group-daily-analysis/tools/stats_user/index.ts +46 -0
- package/src/checkin-lib.ts +15 -13
- package/src/db-store.ts +6 -38
- package/src/index.ts +0 -8
- package/src/keyword-store.ts +4 -10
- package/src/memory-store.ts +1 -1
- package/src/runtime-state.ts +5 -8
- package/src/stats-lib.ts +10 -17
- package/src/tables.ts +1 -1
- package/src/teach-lib.ts +11 -18
- package/agent/skills/group-daily-analysis.md +0 -29
- package/agent/tools/group_announce.ts +0 -12
- package/tools/checkin-query/checkin-query.tool.md +0 -11
- package/tools/checkin-query/handler.js +0 -22
- package/tools/checkin-query/handler.ts +0 -22
- package/tools/checkin-rank/checkin-rank.tool.md +0 -11
- package/tools/checkin-rank/handler.js +0 -15
- package/tools/checkin-rank/handler.ts +0 -16
- package/tools/stats-query/handler.js +0 -28
- package/tools/stats-query/handler.ts +0 -31
- package/tools/stats-query/stats-query.tool.md +0 -16
- package/tools/stats-user/handler.js +0 -29
- package/tools/stats-user/handler.ts +0 -29
- package/tools/stats-user/stats-user.tool.md +0 -12
- /package/{agent/skills/checkin.md → skills/checkin/SKILL.md} +0 -0
package/plugin.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
2
|
import { definePlugin, databaseHostToken } from 'zhin.js';
|
|
3
|
-
import { registerGroupSuiteDb } from "./lib/db-store.js";
|
|
4
3
|
import { createInMemoryGroupSuiteDb } from "./lib/memory-store.js";
|
|
5
4
|
import { createGroupSuiteRuntime, groupSuiteRuntimeToken } from "./lib/runtime-state.js";
|
|
6
5
|
import { flushStatsBuffer } from "./lib/stats-lib.js";
|
|
@@ -28,7 +27,6 @@ export default definePlugin({
|
|
|
28
27
|
})();
|
|
29
28
|
const runtime = createGroupSuiteRuntime(db);
|
|
30
29
|
context.resources.provide(groupSuiteRuntimeToken, runtime);
|
|
31
|
-
context.lifecycle.add(registerGroupSuiteDb(db));
|
|
32
30
|
const flushTimer = setInterval(() => {
|
|
33
31
|
void flushStatsBuffer(runtime);
|
|
34
32
|
}, 10_000);
|
|
@@ -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
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: group-daily-analysis
|
|
3
|
+
description: >-
|
|
4
|
+
群日常分析:查询群消息数量、参与人数和成员活跃度,并生成群公告文本。
|
|
5
|
+
用户询问今日、本周、本月群统计、成员活跃度或需要整理公告时使用。
|
|
6
|
+
keywords:
|
|
7
|
+
- 群分析
|
|
8
|
+
- 日常分析
|
|
9
|
+
- 群统计
|
|
10
|
+
- 活跃
|
|
11
|
+
- 报告
|
|
12
|
+
tags:
|
|
13
|
+
- group
|
|
14
|
+
- analysis
|
|
15
|
+
- stats
|
|
16
|
+
- inbox
|
|
17
|
+
tools:
|
|
18
|
+
- stats_query
|
|
19
|
+
- stats_user
|
|
20
|
+
- group_announce
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
# 群日常分析
|
|
24
|
+
|
|
25
|
+
基于运行时统计数据分析群活跃度,并在需要时生成统一格式的群公告文本。
|
|
26
|
+
|
|
27
|
+
## 能力
|
|
28
|
+
|
|
29
|
+
- 统计群消息数量、参与人数、活跃时段分布
|
|
30
|
+
- 查询指定成员的今日、本周和累计消息数
|
|
31
|
+
- 生成群公告或通知文本
|
|
32
|
+
- 支持按群启用/禁用分析功能
|
|
33
|
+
- 定时任务自动生成分析报告推送到群内
|
|
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
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { defineAgentTool } from '@zhin.js/tool';
|
|
2
|
+
import { getStatsModel } from '../../../../src/db-store.js';
|
|
3
|
+
import { groupSuiteRuntimeToken } from '../../../../src/runtime-state.js';
|
|
4
|
+
import { todayStr } from '../../../../src/shared-runtime.js';
|
|
5
|
+
import { flushStatsBuffer, weekStartStr } from '../../../../src/stats-lib.js';
|
|
6
|
+
|
|
7
|
+
export default defineAgentTool<{ user_id: string; group_id?: string }>({
|
|
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) return '统计数据库尚未就绪';
|
|
23
|
+
const groupId = requestedGroupId
|
|
24
|
+
?? (context.origin.kind === 'im' && context.origin.scope !== 'private'
|
|
25
|
+
? context.origin.sceneId
|
|
26
|
+
: '');
|
|
27
|
+
const rows = await model.select().where({
|
|
28
|
+
user_id: userId,
|
|
29
|
+
...(groupId ? { group_id: groupId } : {}),
|
|
30
|
+
});
|
|
31
|
+
if (rows.length === 0) return `用户 ${userId} 暂无消息记录`;
|
|
32
|
+
const today = todayStr();
|
|
33
|
+
const weekStart = weekStartStr();
|
|
34
|
+
let todayCount = 0;
|
|
35
|
+
let weekCount = 0;
|
|
36
|
+
let totalCount = 0;
|
|
37
|
+
for (const row of rows) {
|
|
38
|
+
const count = Number(row.count ?? 0);
|
|
39
|
+
totalCount += count;
|
|
40
|
+
if (String(row.date) >= weekStart) weekCount += count;
|
|
41
|
+
if (row.date === today) todayCount += count;
|
|
42
|
+
}
|
|
43
|
+
const name = String(rows[0]!.user_name ?? userId);
|
|
44
|
+
return `${name} 的统计\n今日: ${todayCount}条\n本周: ${weekCount}条\n总计: ${totalCount}条\n活跃天数: ${rows.length}天`;
|
|
45
|
+
},
|
|
46
|
+
});
|
package/src/checkin-lib.ts
CHANGED
|
@@ -50,30 +50,32 @@ async function getOrCreateUser(
|
|
|
50
50
|
* 同一 user_id+context 的签到串行化(per-user promise 链),
|
|
51
51
|
* 避免 check-then-act 竞态导致连发两条都签到成功。
|
|
52
52
|
*/
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
function serializedCheckin<T>(
|
|
54
|
+
chains: Map<string, Promise<unknown>>,
|
|
55
|
+
key: string,
|
|
56
|
+
task: () => Promise<T>,
|
|
57
|
+
): Promise<T> {
|
|
58
|
+
const prev = chains.get(key) ?? Promise.resolve();
|
|
57
59
|
const next = prev.then(task, task);
|
|
58
|
-
|
|
60
|
+
chains.set(key, next);
|
|
59
61
|
return next.finally(() => {
|
|
60
|
-
if (
|
|
62
|
+
if (chains.get(key) === next) chains.delete(key);
|
|
61
63
|
});
|
|
62
64
|
}
|
|
63
65
|
|
|
64
66
|
export async function doCheckin(
|
|
65
67
|
input: { sender?: { readonly id: string; readonly name?: string }; target?: string; metadata?: Readonly<Record<string, unknown>> },
|
|
66
68
|
cfg: GroupSuiteConfig,
|
|
67
|
-
runtime
|
|
69
|
+
runtime: GroupSuiteRuntime,
|
|
68
70
|
): Promise<string> {
|
|
69
|
-
const M = getCheckinModel(runtime
|
|
71
|
+
const M = getCheckinModel(runtime.db);
|
|
70
72
|
if (!M) return '签到数据库尚未就绪,请稍后重试';
|
|
71
73
|
|
|
72
74
|
const { id: userId, name: userName } = resolveSender(input);
|
|
73
75
|
if (!userId) return '无法获取用户信息';
|
|
74
76
|
|
|
75
77
|
const { type: ctxType, id: ctxId } = resolveContextKey(input);
|
|
76
|
-
return serializedCheckin(`${userId}:${ctxType}:${ctxId}`, async () => {
|
|
78
|
+
return serializedCheckin(runtime.checkinChains, `${userId}:${ctxType}:${ctxId}`, async () => {
|
|
77
79
|
const user = await getOrCreateUser(M, userId, userName, ctxType, ctxId);
|
|
78
80
|
if (!user) return '签到失败,请重试';
|
|
79
81
|
|
|
@@ -112,9 +114,9 @@ export async function doCheckin(
|
|
|
112
114
|
|
|
113
115
|
export async function myPoints(
|
|
114
116
|
input: { sender?: { readonly id: string; readonly name?: string }; target?: string; metadata?: Readonly<Record<string, unknown>> },
|
|
115
|
-
runtime
|
|
117
|
+
runtime: GroupSuiteRuntime,
|
|
116
118
|
): Promise<string> {
|
|
117
|
-
const M = getCheckinModel(runtime
|
|
119
|
+
const M = getCheckinModel(runtime.db);
|
|
118
120
|
if (!M) return '签到数据库尚未就绪';
|
|
119
121
|
const { id: userId } = resolveSender(input);
|
|
120
122
|
const { type: ctxType, id: ctxId } = resolveContextKey(input);
|
|
@@ -139,9 +141,9 @@ export async function myPoints(
|
|
|
139
141
|
export async function pointsRank(
|
|
140
142
|
input: { sender?: { readonly id: string; readonly name?: string }; target?: string; metadata?: Readonly<Record<string, unknown>> },
|
|
141
143
|
cfg: GroupSuiteConfig,
|
|
142
|
-
runtime
|
|
144
|
+
runtime: GroupSuiteRuntime,
|
|
143
145
|
): Promise<string> {
|
|
144
|
-
const M = getCheckinModel(runtime
|
|
146
|
+
const M = getCheckinModel(runtime.db);
|
|
145
147
|
if (!M) return '签到数据库尚未就绪';
|
|
146
148
|
const { type: ctxType, id: ctxId } = resolveContextKey(input);
|
|
147
149
|
const all = (await M.select().where(
|
package/src/db-store.ts
CHANGED
|
@@ -1,51 +1,19 @@
|
|
|
1
1
|
import {
|
|
2
2
|
CHECKIN_TABLE,
|
|
3
|
-
createInMemoryGroupSuiteDb,
|
|
4
3
|
STATS_TABLE,
|
|
5
4
|
TEACH_TABLE,
|
|
6
5
|
type GroupSuiteMemoryDb,
|
|
7
6
|
type GroupSuiteModel,
|
|
8
7
|
} from './memory-store.js';
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
/** Ensure an in-memory store when Runtime DatabaseFeature Resource is not wired yet. */
|
|
14
|
-
export function ensureGroupSuiteMemoryDb(): GroupSuiteMemoryDb {
|
|
15
|
-
if (!_db) _db = createInMemoryGroupSuiteDb();
|
|
16
|
-
return _db;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function getGroupSuiteDb(): GroupSuiteMemoryDb | null {
|
|
20
|
-
return registrations[registrations.length - 1]?.value ?? ensureGroupSuiteMemoryDb();
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function setGroupSuiteDb(db: GroupSuiteMemoryDb | null): void {
|
|
24
|
-
_db = db;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function registerGroupSuiteDb(db: GroupSuiteMemoryDb): () => void {
|
|
28
|
-
const registration = Object.freeze({ value: db });
|
|
29
|
-
registrations.push(registration);
|
|
30
|
-
return () => {
|
|
31
|
-
const index = registrations.lastIndexOf(registration);
|
|
32
|
-
if (index >= 0) registrations.splice(index, 1);
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/** Test helper: clear module store. */
|
|
37
|
-
export function resetGroupSuiteDb(): void {
|
|
38
|
-
_db = null;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export function getCheckinModel(db = getGroupSuiteDb()): GroupSuiteModel | null {
|
|
42
|
-
return db?.models.get(CHECKIN_TABLE) ?? null;
|
|
9
|
+
export function getCheckinModel(db: GroupSuiteMemoryDb): GroupSuiteModel | undefined {
|
|
10
|
+
return db.models.get(CHECKIN_TABLE);
|
|
43
11
|
}
|
|
44
12
|
|
|
45
|
-
export function getTeachModel(db
|
|
46
|
-
return db
|
|
13
|
+
export function getTeachModel(db: GroupSuiteMemoryDb): GroupSuiteModel | undefined {
|
|
14
|
+
return db.models.get(TEACH_TABLE);
|
|
47
15
|
}
|
|
48
16
|
|
|
49
|
-
export function getStatsModel(db
|
|
50
|
-
return db
|
|
17
|
+
export function getStatsModel(db: GroupSuiteMemoryDb): GroupSuiteModel | undefined {
|
|
18
|
+
return db.models.get(STATS_TABLE);
|
|
51
19
|
}
|
package/src/index.ts
CHANGED
|
@@ -9,17 +9,11 @@ export {
|
|
|
9
9
|
listKeywords,
|
|
10
10
|
matchKeyword,
|
|
11
11
|
removeKeyword,
|
|
12
|
-
resetKeywords,
|
|
13
12
|
} from './keyword-store.js';
|
|
14
13
|
export {
|
|
15
|
-
ensureGroupSuiteMemoryDb,
|
|
16
14
|
getCheckinModel,
|
|
17
|
-
getGroupSuiteDb,
|
|
18
15
|
getStatsModel,
|
|
19
16
|
getTeachModel,
|
|
20
|
-
resetGroupSuiteDb,
|
|
21
|
-
registerGroupSuiteDb,
|
|
22
|
-
setGroupSuiteDb,
|
|
23
17
|
} from './db-store.js';
|
|
24
18
|
export {
|
|
25
19
|
createGroupSuiteRuntime,
|
|
@@ -29,7 +23,6 @@ export {
|
|
|
29
23
|
export type { GroupSuiteRuntime, PendingStatsIncrement } from './runtime-state.js';
|
|
30
24
|
export {
|
|
31
25
|
parseTeachPair,
|
|
32
|
-
resetTeachCooldown,
|
|
33
26
|
teachAdd,
|
|
34
27
|
teachForget,
|
|
35
28
|
teachList,
|
|
@@ -41,7 +34,6 @@ export {
|
|
|
41
34
|
myStatsText,
|
|
42
35
|
queryStats,
|
|
43
36
|
recordMessage,
|
|
44
|
-
resetStatsBuffer,
|
|
45
37
|
statsRankText,
|
|
46
38
|
weekStartStr,
|
|
47
39
|
} from './stats-lib.js';
|
package/src/keyword-store.ts
CHANGED
|
@@ -1,24 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export function addKeyword(keyword: string, reply: string, store = keywords): void {
|
|
1
|
+
export function addKeyword(keyword: string, reply: string, store: Map<string, string>): void {
|
|
4
2
|
store.set(keyword, reply);
|
|
5
3
|
}
|
|
6
4
|
|
|
7
|
-
export function removeKeyword(keyword: string, store
|
|
5
|
+
export function removeKeyword(keyword: string, store: Map<string, string>): boolean {
|
|
8
6
|
return store.delete(keyword);
|
|
9
7
|
}
|
|
10
8
|
|
|
11
|
-
export function listKeywords(store
|
|
9
|
+
export function listKeywords(store: Map<string, string>): Array<[string, string]> {
|
|
12
10
|
return Array.from(store.entries());
|
|
13
11
|
}
|
|
14
12
|
|
|
15
|
-
export function matchKeyword(text: string, store
|
|
13
|
+
export function matchKeyword(text: string, store: Map<string, string>): string | null {
|
|
16
14
|
for (const [keyword, reply] of store) {
|
|
17
15
|
if (text.includes(keyword)) return reply;
|
|
18
16
|
}
|
|
19
17
|
return null;
|
|
20
18
|
}
|
|
21
|
-
|
|
22
|
-
export function resetKeywords(): void {
|
|
23
|
-
keywords.clear();
|
|
24
|
-
}
|
package/src/memory-store.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* In-memory group-suite models
|
|
2
|
+
* In-memory group-suite models used when no DatabaseHost is available.
|
|
3
3
|
*
|
|
4
4
|
* 模型表面与 `@zhin.js/plugin-runtime` 的 DatabaseHostModel 结构兼容
|
|
5
5
|
* (where 返回 PromiseLike,host 侧是链式 Selection),
|
package/src/runtime-state.ts
CHANGED
|
@@ -15,6 +15,7 @@ export interface GroupSuiteRuntime {
|
|
|
15
15
|
readonly keywords: Map<string, string>;
|
|
16
16
|
readonly teachCooldowns: Map<string, number>;
|
|
17
17
|
readonly statsBuffer: Map<string, PendingStatsIncrement>;
|
|
18
|
+
readonly checkinChains: Map<string, Promise<unknown>>;
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
export const groupSuiteRuntimeToken = createToken<GroupSuiteRuntime>(
|
|
@@ -28,17 +29,13 @@ export function createGroupSuiteRuntime(db: GroupSuiteMemoryDb): GroupSuiteRunti
|
|
|
28
29
|
keywords: new Map(),
|
|
29
30
|
teachCooldowns: new Map(),
|
|
30
31
|
statsBuffer: new Map(),
|
|
32
|
+
checkinChains: new Map(),
|
|
31
33
|
};
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
export function resolveGroupSuiteRuntime(context: {
|
|
35
|
-
owner?:
|
|
37
|
+
readonly owner?: unknown;
|
|
36
38
|
use<T>(token: Token<T>): T;
|
|
37
|
-
}): GroupSuiteRuntime
|
|
38
|
-
|
|
39
|
-
return context.use(groupSuiteRuntimeToken);
|
|
40
|
-
} catch (error) {
|
|
41
|
-
if (context.owner?.id) throw error;
|
|
42
|
-
return undefined;
|
|
43
|
-
}
|
|
39
|
+
}): GroupSuiteRuntime {
|
|
40
|
+
return context.use(groupSuiteRuntimeToken);
|
|
44
41
|
}
|