@zhin.js/adapter-icqq 2.0.6 → 2.0.7

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 (56) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/lib/adapter.d.ts +7 -2
  3. package/lib/adapter.d.ts.map +1 -1
  4. package/lib/adapter.js +78 -33
  5. package/lib/adapter.js.map +1 -1
  6. package/lib/bot.d.ts +29 -29
  7. package/lib/bot.d.ts.map +1 -1
  8. package/lib/bot.js +226 -405
  9. package/lib/bot.js.map +1 -1
  10. package/lib/commands/index.d.ts +7 -0
  11. package/lib/commands/index.d.ts.map +1 -0
  12. package/lib/commands/index.js +30 -0
  13. package/lib/commands/index.js.map +1 -0
  14. package/lib/index.d.ts +1 -1
  15. package/lib/index.d.ts.map +1 -1
  16. package/lib/index.js +13 -297
  17. package/lib/index.js.map +1 -1
  18. package/lib/ipc-client.d.ts +60 -0
  19. package/lib/ipc-client.d.ts.map +1 -0
  20. package/lib/ipc-client.js +272 -0
  21. package/lib/ipc-client.js.map +1 -0
  22. package/lib/protocol.d.ts +174 -0
  23. package/lib/protocol.d.ts.map +1 -0
  24. package/lib/protocol.js +162 -0
  25. package/lib/protocol.js.map +1 -0
  26. package/lib/routes.d.ts +8 -0
  27. package/lib/routes.d.ts.map +1 -0
  28. package/lib/routes.js +67 -0
  29. package/lib/routes.js.map +1 -0
  30. package/lib/tools/index.d.ts +10 -0
  31. package/lib/tools/index.d.ts.map +1 -0
  32. package/lib/tools/index.js +336 -0
  33. package/lib/tools/index.js.map +1 -0
  34. package/lib/types.d.ts +45 -7
  35. package/lib/types.d.ts.map +1 -1
  36. package/lib/types.js +5 -0
  37. package/lib/types.js.map +1 -1
  38. package/package.json +3 -5
  39. package/plugin.yml +1 -1
  40. package/skills/icqq/SKILL.md +31 -64
  41. package/skills/icqq/references/friends.md +54 -0
  42. package/skills/icqq/references/general.md +145 -0
  43. package/skills/icqq/references/gfs.md +49 -0
  44. package/skills/icqq/references/groups.md +71 -0
  45. package/skills/icqq/references/messaging.md +66 -0
  46. package/skills/icqq/references/requests.md +27 -0
  47. package/skills/icqq/references/settings.md +38 -0
  48. package/src/adapter.ts +73 -35
  49. package/src/bot.ts +272 -443
  50. package/src/commands/index.ts +32 -0
  51. package/src/index.ts +14 -305
  52. package/src/ipc-client.ts +326 -0
  53. package/src/protocol.ts +242 -0
  54. package/src/routes.ts +83 -0
  55. package/src/tools/index.ts +407 -0
  56. package/src/types.ts +47 -7
@@ -0,0 +1,407 @@
1
+ /**
2
+ * ICQQ 平台特有工具注册 — 通过 IPC 调用守护进程 Actions
3
+ *
4
+ * 通用群管理工具由 createGroupManagementTools() 自动生成;
5
+ * 本模块注册 ICQQ 独有的扩展工具。
6
+ */
7
+ import type { ToolFeature } from "zhin.js";
8
+ import { createGroupManagementTools } from "zhin.js";
9
+ import type { IcqqAdapter } from "../adapter.js";
10
+ import { Actions } from "../protocol.js";
11
+
12
+ export function registerTools(
13
+ toolService: ToolFeature,
14
+ icqq: IcqqAdapter,
15
+ pluginName: string,
16
+ ): () => void {
17
+ const disposers: (() => void)[] = [];
18
+
19
+ // ── 通用群管工具 ───────────────────────────────────────────────────
20
+ const groupTools = createGroupManagementTools(icqq, "icqq");
21
+ disposers.push(...groupTools.map((t) => toolService.addTool(t, pluginName)));
22
+
23
+ // ── 设置头衔 ───────────────────────────────────────────────────────
24
+ disposers.push(
25
+ toolService.addTool(
26
+ {
27
+ name: "icqq_set_title",
28
+ description: "设置 QQ 群成员的专属头衔",
29
+ parameters: {
30
+ type: "object",
31
+ properties: {
32
+ bot: { type: "string", description: "Bot QQ号" },
33
+ group_id: { type: "number", description: "目标群号" },
34
+ user_id: { type: "number", description: "目标成员 QQ号" },
35
+ title: { type: "string", description: "头衔文字" },
36
+ duration: { type: "number", description: "持续时间(秒),-1永久" },
37
+ },
38
+ required: ["bot", "group_id", "user_id", "title"],
39
+ },
40
+ platforms: ["icqq"],
41
+ tags: ["icqq"],
42
+ execute: async (args: Record<string, any>) => {
43
+ const bot = icqq.bots.get(args.bot);
44
+ if (!bot) throw new Error(`Bot ${args.bot} 不存在`);
45
+ const resp = await bot.ipc.request(Actions.SET_GROUP_TITLE, {
46
+ group_id: args.group_id,
47
+ user_id: args.user_id,
48
+ title: args.title,
49
+ duration: args.duration ?? -1,
50
+ });
51
+ return { success: resp.ok, message: resp.ok ? `已将 ${args.user_id} 的头衔设为 "${args.title}"` : (resp.error ?? "设置失败") };
52
+ },
53
+ },
54
+ pluginName,
55
+ ),
56
+ );
57
+
58
+ // ── 发送群公告 ─────────────────────────────────────────────────────
59
+ disposers.push(
60
+ toolService.addTool(
61
+ {
62
+ name: "icqq_announce",
63
+ description: "发送 QQ 群公告(需要管理员权限)",
64
+ parameters: {
65
+ type: "object",
66
+ properties: {
67
+ bot: { type: "string", description: "Bot QQ号" },
68
+ group_id: { type: "number", description: "目标群号" },
69
+ content: { type: "string", description: "公告内容" },
70
+ },
71
+ required: ["bot", "group_id", "content"],
72
+ },
73
+ platforms: ["icqq"],
74
+ tags: ["icqq"],
75
+ execute: async (args: Record<string, any>) => {
76
+ const bot = icqq.bots.get(args.bot);
77
+ if (!bot) throw new Error(`Bot ${args.bot} 不存在`);
78
+ const resp = await bot.ipc.request(Actions.GROUP_ANNOUNCE, {
79
+ group_id: args.group_id,
80
+ content: args.content,
81
+ });
82
+ return { success: resp.ok, message: resp.ok ? "群公告已发送" : (resp.error ?? "发送失败") };
83
+ },
84
+ },
85
+ pluginName,
86
+ ),
87
+ );
88
+
89
+ // ── 戳一戳 ─────────────────────────────────────────────────────────
90
+ disposers.push(
91
+ toolService.addTool(
92
+ {
93
+ name: "icqq_poke",
94
+ description: "在 QQ 群中对某个成员执行戳一戳互动操作",
95
+ parameters: {
96
+ type: "object",
97
+ properties: {
98
+ bot: { type: "string", description: "Bot QQ号" },
99
+ group_id: { type: "number", description: "目标群号" },
100
+ user_id: { type: "number", description: "要戳的目标成员 QQ号" },
101
+ },
102
+ required: ["bot", "group_id", "user_id"],
103
+ },
104
+ platforms: ["icqq"],
105
+ tags: ["icqq"],
106
+ execute: async (args: Record<string, any>) => {
107
+ const bot = icqq.bots.get(args.bot);
108
+ if (!bot) throw new Error(`Bot ${args.bot} 不存在`);
109
+ const resp = await bot.ipc.request(Actions.GROUP_POKE, {
110
+ group_id: args.group_id,
111
+ user_id: args.user_id,
112
+ });
113
+ return { success: resp.ok, message: resp.ok ? `已戳了戳 ${args.user_id}` : (resp.error ?? "戳一戳失败") };
114
+ },
115
+ },
116
+ pluginName,
117
+ ),
118
+ );
119
+
120
+ // ── 获取被禁言列表 ─────────────────────────────────────────────────
121
+ disposers.push(
122
+ toolService.addTool(
123
+ {
124
+ name: "icqq_list_muted",
125
+ description: "查询 QQ 群中当前被禁言的成员列表",
126
+ parameters: {
127
+ type: "object",
128
+ properties: {
129
+ bot: { type: "string", description: "Bot QQ号" },
130
+ group_id: { type: "number", description: "目标群号" },
131
+ },
132
+ required: ["bot", "group_id"],
133
+ },
134
+ platforms: ["icqq"],
135
+ tags: ["icqq"],
136
+ execute: async (args: Record<string, any>) => {
137
+ const bot = icqq.bots.get(args.bot);
138
+ if (!bot) throw new Error(`Bot ${args.bot} 不存在`);
139
+ const resp = await bot.ipc.request(Actions.GROUP_MUTED_LIST, {
140
+ group_id: args.group_id,
141
+ });
142
+ if (!resp.ok) throw new Error(resp.error ?? "获取禁言列表失败");
143
+ const list = Array.isArray(resp.data) ? resp.data : [];
144
+ return { muted_members: list, count: list.length };
145
+ },
146
+ },
147
+ pluginName,
148
+ ),
149
+ );
150
+
151
+ // ── 给用户点赞 ─────────────────────────────────────────────────────
152
+ disposers.push(
153
+ toolService.addTool(
154
+ {
155
+ name: "icqq_send_user_like",
156
+ description: "给用户点赞(竖大拇指),每人每天最多 20 次",
157
+ parameters: {
158
+ type: "object",
159
+ properties: {
160
+ bot: { type: "string", description: "Bot QQ号" },
161
+ user_id: { type: "number", description: "要点赞的目标用户 QQ号" },
162
+ times: { type: "number", description: "点赞次数(1-20),默认 1" },
163
+ },
164
+ required: ["bot", "user_id"],
165
+ },
166
+ platforms: ["icqq"],
167
+ tags: ["icqq"],
168
+ execute: async (args: Record<string, any>) => {
169
+ const bot = icqq.bots.get(args.bot);
170
+ if (!bot) throw new Error(`Bot ${args.bot} 不存在`);
171
+ const resp = await bot.ipc.request(Actions.FRIEND_LIKE, {
172
+ user_id: args.user_id,
173
+ times: Math.min(args.times ?? 1, 20),
174
+ });
175
+ return { success: resp.ok, message: resp.ok ? `已给 ${args.user_id} 点赞` : (resp.error ?? "点赞失败") };
176
+ },
177
+ },
178
+ pluginName,
179
+ ),
180
+ );
181
+
182
+ // ── 设置匿名聊天 ───────────────────────────────────────────────────
183
+ disposers.push(
184
+ toolService.addTool(
185
+ {
186
+ name: "icqq_set_anonymous",
187
+ description: "开启或关闭 QQ 群的匿名聊天功能",
188
+ parameters: {
189
+ type: "object",
190
+ properties: {
191
+ bot: { type: "string", description: "Bot QQ号" },
192
+ group_id: { type: "number", description: "目标群号" },
193
+ enable: { type: "boolean", description: "true=开启,false=关闭,默认 true" },
194
+ },
195
+ required: ["bot", "group_id"],
196
+ },
197
+ platforms: ["icqq"],
198
+ tags: ["icqq"],
199
+ execute: async (args: Record<string, any>) => {
200
+ const bot = icqq.bots.get(args.bot);
201
+ if (!bot) throw new Error(`Bot ${args.bot} 不存在`);
202
+ const enable = args.enable ?? true;
203
+ const resp = await bot.ipc.request(Actions.GROUP_ALLOW_ANONY, {
204
+ group_id: args.group_id,
205
+ enable,
206
+ });
207
+ return { success: resp.ok, message: resp.ok ? (enable ? "已开启匿名聊天" : "已关闭匿名聊天") : (resp.error ?? "操作失败") };
208
+ },
209
+ },
210
+ pluginName,
211
+ ),
212
+ );
213
+
214
+ // ── 群文件列表 ─────────────────────────────────────────────────────
215
+ disposers.push(
216
+ toolService.addTool(
217
+ {
218
+ name: "icqq_group_files",
219
+ description: "获取 QQ 群的群文件列表",
220
+ parameters: {
221
+ type: "object",
222
+ properties: {
223
+ bot: { type: "string", description: "Bot 名称" },
224
+ group_id: { type: "number", description: "群号" },
225
+ },
226
+ required: ["bot", "group_id"],
227
+ },
228
+ platforms: ["icqq"],
229
+ tags: ["icqq"],
230
+ execute: async (args: Record<string, any>) => {
231
+ const bot = icqq.bots.get(args.bot);
232
+ if (!bot) throw new Error(`Bot ${args.bot} 不存在`);
233
+ const resp = await bot.ipc.request(Actions.GFS_LIST, {
234
+ group_id: args.group_id,
235
+ });
236
+ if (!resp.ok) throw new Error(resp.error ?? "获取群文件失败");
237
+ const files = Array.isArray(resp.data) ? resp.data : [];
238
+ if (!files.length) return { files: [], message: "群文件为空" };
239
+ return {
240
+ files: files.slice(0, 30).map((f: any) => ({
241
+ name: f.name,
242
+ size: f.size,
243
+ uploader: f.uploader_uin,
244
+ upload_time: f.upload_time,
245
+ })),
246
+ count: files.length,
247
+ };
248
+ },
249
+ },
250
+ pluginName,
251
+ ),
252
+ );
253
+
254
+ // ── 好友列表 ───────────────────────────────────────────────────────
255
+ disposers.push(
256
+ toolService.addTool(
257
+ {
258
+ name: "icqq_friend_list",
259
+ description: "获取 QQ 好友列表",
260
+ parameters: {
261
+ type: "object",
262
+ properties: {
263
+ bot: { type: "string", description: "Bot 名称" },
264
+ },
265
+ required: ["bot"],
266
+ },
267
+ platforms: ["icqq"],
268
+ tags: ["icqq"],
269
+ execute: async (args: Record<string, any>) => {
270
+ const bot = icqq.bots.get(args.bot);
271
+ if (!bot) throw new Error(`Bot ${args.bot} 不存在`);
272
+ const friends = Array.from(bot.friends.values()).map((f) => ({
273
+ user_id: f.user_id,
274
+ nickname: f.nickname,
275
+ remark: f.remark,
276
+ }));
277
+ return { friends: friends.slice(0, 50), count: bot.friends.size };
278
+ },
279
+ },
280
+ pluginName,
281
+ ),
282
+ );
283
+
284
+ // ── 群列表 ─────────────────────────────────────────────────────────
285
+ disposers.push(
286
+ toolService.addTool(
287
+ {
288
+ name: "icqq_group_list",
289
+ description: "获取 Bot 的 QQ 群列表",
290
+ parameters: {
291
+ type: "object",
292
+ properties: {
293
+ bot: { type: "string", description: "Bot 名称" },
294
+ },
295
+ required: ["bot"],
296
+ },
297
+ platforms: ["icqq"],
298
+ tags: ["icqq"],
299
+ execute: async (args: Record<string, any>) => {
300
+ const bot = icqq.bots.get(args.bot);
301
+ if (!bot) throw new Error(`Bot ${args.bot} 不存在`);
302
+ const groups = Array.from(bot.groups.values()).map((g) => ({
303
+ group_id: g.group_id,
304
+ group_name: g.group_name,
305
+ member_count: g.member_count,
306
+ max_member_count: g.max_member_count,
307
+ }));
308
+ return { groups: groups.slice(0, 50), count: bot.groups.size };
309
+ },
310
+ },
311
+ pluginName,
312
+ ),
313
+ );
314
+
315
+ // ── 群签到 ─────────────────────────────────────────────────────────
316
+ disposers.push(
317
+ toolService.addTool(
318
+ {
319
+ name: "icqq_group_sign",
320
+ description: "QQ 群签到打卡",
321
+ parameters: {
322
+ type: "object",
323
+ properties: {
324
+ bot: { type: "string", description: "Bot QQ号" },
325
+ group_id: { type: "number", description: "目标群号" },
326
+ },
327
+ required: ["bot", "group_id"],
328
+ },
329
+ platforms: ["icqq"],
330
+ tags: ["icqq"],
331
+ execute: async (args: Record<string, any>) => {
332
+ const bot = icqq.bots.get(args.bot);
333
+ if (!bot) throw new Error(`Bot ${args.bot} 不存在`);
334
+ const resp = await bot.ipc.request(Actions.GROUP_SIGN, {
335
+ group_id: args.group_id,
336
+ });
337
+ return { success: resp.ok, message: resp.ok ? "群签到成功" : (resp.error ?? "签到失败") };
338
+ },
339
+ },
340
+ pluginName,
341
+ ),
342
+ );
343
+
344
+ // ── 邀请入群 ───────────────────────────────────────────────────────
345
+ disposers.push(
346
+ toolService.addTool(
347
+ {
348
+ name: "icqq_group_invite",
349
+ description: "邀请好友加入 QQ 群",
350
+ parameters: {
351
+ type: "object",
352
+ properties: {
353
+ bot: { type: "string", description: "Bot QQ号" },
354
+ group_id: { type: "number", description: "目标群号" },
355
+ user_id: { type: "number", description: "要邀请的 QQ号" },
356
+ },
357
+ required: ["bot", "group_id", "user_id"],
358
+ },
359
+ platforms: ["icqq"],
360
+ tags: ["icqq"],
361
+ execute: async (args: Record<string, any>) => {
362
+ const bot = icqq.bots.get(args.bot);
363
+ if (!bot) throw new Error(`Bot ${args.bot} 不存在`);
364
+ const resp = await bot.ipc.request(Actions.GROUP_INVITE, {
365
+ group_id: args.group_id,
366
+ user_id: args.user_id,
367
+ });
368
+ return { success: resp.ok, message: resp.ok ? `已邀请 ${args.user_id} 加入群` : (resp.error ?? "邀请失败") };
369
+ },
370
+ },
371
+ pluginName,
372
+ ),
373
+ );
374
+
375
+ // ── 设为/移除精华消息 ──────────────────────────────────────────────
376
+ disposers.push(
377
+ toolService.addTool(
378
+ {
379
+ name: "icqq_essence",
380
+ description: "设置或移除 QQ 群精华消息",
381
+ parameters: {
382
+ type: "object",
383
+ properties: {
384
+ bot: { type: "string", description: "Bot QQ号" },
385
+ message_id: { type: "string", description: "消息 ID" },
386
+ action: { type: "string", description: "add=设为精华, remove=移除精华" },
387
+ },
388
+ required: ["bot", "message_id", "action"],
389
+ },
390
+ platforms: ["icqq"],
391
+ tags: ["icqq"],
392
+ execute: async (args: Record<string, any>) => {
393
+ const bot = icqq.bots.get(args.bot);
394
+ if (!bot) throw new Error(`Bot ${args.bot} 不存在`);
395
+ const action = args.action === "add" ? Actions.GROUP_ESSENCE_ADD : Actions.GROUP_ESSENCE_REMOVE;
396
+ const resp = await bot.ipc.request(action, {
397
+ message_id: args.message_id,
398
+ });
399
+ return { success: resp.ok, message: resp.ok ? (args.action === "add" ? "已设为精华" : "已移除精华") : (resp.error ?? "操作失败") };
400
+ },
401
+ },
402
+ pluginName,
403
+ ),
404
+ );
405
+
406
+ return () => disposers.forEach((d) => d());
407
+ }
package/src/types.ts CHANGED
@@ -1,9 +1,10 @@
1
1
  /**
2
2
  * ICQQ 适配器类型与配置
3
+ *
4
+ * 不再直接依赖 @icqqjs/icqq,通过 @icqqjs/cli 守护进程 IPC 通信。
3
5
  */
4
- import type { Config, GroupRole, MemberInfo } from "@icqqjs/icqq";
5
6
 
6
- export type { Config, GroupRole, MemberInfo };
7
+ export type GroupRole = "owner" | "admin" | "member";
7
8
 
8
9
  export interface IcqqSenderInfo {
9
10
  id: string;
@@ -16,13 +17,52 @@ export interface IcqqSenderInfo {
16
17
  title?: string;
17
18
  }
18
19
 
19
- export interface IcqqBotConfig extends Config {
20
+ /**
21
+ * Bot 配置:支持本地 IPC 和远程 RPC 两种连接模式。
22
+ *
23
+ * - 本地模式(默认):只需 name(QQ号),自动连接 ~/.icqq/<uin>/daemon.sock
24
+ * - 远程模式:额外配置 rpc.host / rpc.port / rpc.token
25
+ */
26
+ export interface IcqqBotConfig {
20
27
  context: "icqq";
28
+ /** QQ 号码字符串 */
21
29
  name: `${number}`;
22
- password?: string;
23
- scope?: string;
30
+ /** RPC 远程连接配置(不配置则使用本地 IPC) */
31
+ rpc?: {
32
+ /** 远程主机地址 */
33
+ host: string;
34
+ /** 远程端口 */
35
+ port: number;
36
+ /** 认证 token(用于 HMAC-SHA256 挑战-响应,不会明文传输) */
37
+ token: string;
38
+ };
39
+ }
40
+
41
+ /** IPC 返回的好友信息 */
42
+ export interface IpcFriendInfo {
43
+ user_id: number;
44
+ nickname: string;
45
+ remark?: string;
46
+ class_id?: number;
47
+ }
48
+
49
+ /** IPC 返回的群信息 */
50
+ export interface IpcGroupInfo {
51
+ group_id: number;
52
+ group_name: string;
53
+ member_count: number;
54
+ max_member_count: number;
55
+ owner_id?: number;
24
56
  }
25
57
 
26
- export interface IcqqBot {
27
- $config: IcqqBotConfig;
58
+ /** IPC 返回的群成员信息 */
59
+ export interface IpcMemberInfo {
60
+ user_id: number;
61
+ nickname: string;
62
+ card: string;
63
+ role: GroupRole;
64
+ title: string;
65
+ join_time?: number;
66
+ last_sent_time?: number;
67
+ shutup_time?: number;
28
68
  }