@zhin.js/adapter-icqq 1.1.15 → 1.1.17
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/CHANGELOG.md +62 -0
- package/PERMITS.md +14 -18
- package/README.md +12 -0
- package/adapters/icqq/index.js +2 -0
- package/adapters/icqq/index.ts +2 -0
- package/agents/icqq/conversation.js +72 -0
- package/agents/icqq/conversation.ts +67 -0
- package/agents/icqq/members.js +16 -0
- package/agents/icqq/members.ts +17 -0
- package/agents/icqq/messages.js +144 -0
- package/agents/icqq/messages.ts +144 -0
- package/agents/icqq/skills/icqq-conversation/SKILL.md +24 -0
- package/agents/icqq/skills/icqq-conversation/tools/conversation_info/index.js +61 -0
- package/agents/icqq/skills/icqq-conversation/tools/conversation_info/index.ts +61 -0
- package/agents/icqq/skills/icqq-directory/tools/user_info/index.js +2 -1
- package/agents/icqq/skills/icqq-directory/tools/user_info/index.ts +2 -1
- package/agents/icqq/skills/icqq-group-admin/SKILL.md +11 -1
- package/agents/icqq/skills/icqq-group-admin/tools/announce/index.js +2 -1
- package/agents/icqq/skills/icqq-group-admin/tools/announce/index.ts +2 -1
- package/agents/icqq/skills/icqq-group-admin/tools/essence/index.js +3 -2
- package/agents/icqq/skills/icqq-group-admin/tools/essence/index.ts +3 -2
- package/agents/icqq/skills/icqq-group-admin/tools/group_invite/index.js +6 -5
- package/agents/icqq/skills/icqq-group-admin/tools/group_invite/index.ts +5 -4
- package/agents/icqq/skills/icqq-group-admin/tools/group_kick/index.js +4 -3
- package/agents/icqq/skills/icqq-group-admin/tools/group_kick/index.ts +4 -3
- package/agents/icqq/skills/icqq-group-admin/tools/group_leave/index.js +34 -0
- package/agents/icqq/skills/icqq-group-admin/tools/group_leave/index.ts +33 -0
- package/agents/icqq/skills/icqq-group-admin/tools/recall_member_message/index.js +34 -0
- package/agents/icqq/skills/icqq-group-admin/tools/recall_member_message/index.ts +33 -0
- package/agents/icqq/skills/icqq-group-admin/tools/set_anonymous/index.js +5 -4
- package/agents/icqq/skills/icqq-group-admin/tools/set_anonymous/index.ts +4 -3
- package/agents/icqq/skills/icqq-group-admin/tools/set_mute/index.js +8 -7
- package/agents/icqq/skills/icqq-group-admin/tools/set_mute/index.ts +6 -5
- package/agents/icqq/skills/icqq-group-admin/tools/set_title/index.js +8 -7
- package/agents/icqq/skills/icqq-group-admin/tools/set_title/index.ts +6 -5
- package/agents/icqq/skills/icqq-group-directory/tools/group_member_info/index.js +18 -7
- package/agents/icqq/skills/icqq-group-directory/tools/group_member_info/index.ts +17 -5
- package/agents/icqq/skills/icqq-group-interaction/tools/group_sign/index.js +3 -2
- package/agents/icqq/skills/icqq-group-interaction/tools/group_sign/index.ts +3 -2
- package/agents/icqq/skills/icqq-group-interaction/tools/poke/index.js +2 -1
- package/agents/icqq/skills/icqq-group-interaction/tools/poke/index.ts +2 -1
- package/agents/icqq/skills/icqq-group-state/tools/group_files/index.js +2 -1
- package/agents/icqq/skills/icqq-group-state/tools/group_files/index.ts +2 -1
- package/agents/icqq/skills/icqq-interaction/tools/send_user_like/index.js +3 -2
- package/agents/icqq/skills/icqq-interaction/tools/send_user_like/index.ts +3 -2
- package/agents/icqq/skills/icqq-messages/SKILL.md +28 -0
- package/agents/icqq/skills/icqq-messages/tools/forward_messages/index.js +42 -0
- package/agents/icqq/skills/icqq-messages/tools/forward_messages/index.ts +40 -0
- package/agents/icqq/skills/icqq-messages/tools/quote_reply/index.js +24 -0
- package/agents/icqq/skills/icqq-messages/tools/quote_reply/index.ts +24 -0
- package/agents/icqq/skills/icqq-messages/tools/recall_own_message/index.js +18 -0
- package/agents/icqq/skills/icqq-messages/tools/recall_own_message/index.ts +17 -0
- package/agents/icqq/system.md +5 -1
- package/commands/icqq/endpoint/definition.js +9 -7
- package/commands/icqq/endpoint/definition.ts +9 -9
- package/lib/endpoint.d.ts +1 -0
- package/lib/endpoint.js +12 -8
- package/lib/group-moderation.js +7 -2
- package/lib/group-scene.d.ts +11 -0
- package/lib/group-scene.js +71 -0
- package/lib/i18n.d.ts +1 -0
- package/lib/i18n.js +47 -0
- package/lib/icqq-inbound.d.ts +2 -0
- package/lib/icqq-inbound.js +3 -0
- package/lib/icqq-inbox.d.ts +5 -0
- package/lib/icqq-inbox.js +14 -0
- package/lib/icqq-login-assist.d.ts +2 -0
- package/lib/icqq-login-assist.js +8 -7
- package/lib/protocol.d.ts +3 -0
- package/lib/protocol.js +18 -3
- package/package.json +16 -16
- package/schema.json +250 -42
- package/src/endpoint.ts +13 -7
- package/src/group-moderation.ts +7 -2
- package/src/group-scene.ts +76 -0
- package/src/i18n.ts +48 -0
- package/src/icqq-inbound.ts +5 -0
- package/src/icqq-inbox.ts +15 -0
- package/src/icqq-login-assist.ts +10 -7
- package/src/protocol.ts +19 -3
package/schema.json
CHANGED
|
@@ -4,75 +4,169 @@
|
|
|
4
4
|
"additionalProperties": false,
|
|
5
5
|
"properties": {
|
|
6
6
|
"master": {
|
|
7
|
-
"type": [
|
|
8
|
-
|
|
7
|
+
"type": [
|
|
8
|
+
"string",
|
|
9
|
+
"number"
|
|
10
|
+
],
|
|
11
|
+
"description": "框架 master QQ uin(/approve、AI/工具权限);endpoints[i].master 可逐项覆盖",
|
|
12
|
+
"x-i18n": {
|
|
13
|
+
"en": {
|
|
14
|
+
"description": "Framework master QQ uin list for approval and AI/tool permissions. endpoints[i].master can override this value."
|
|
15
|
+
}
|
|
16
|
+
}
|
|
9
17
|
},
|
|
10
18
|
"trusted": {
|
|
11
19
|
"type": "array",
|
|
12
|
-
"items": {
|
|
13
|
-
|
|
20
|
+
"items": {
|
|
21
|
+
"type": [
|
|
22
|
+
"string",
|
|
23
|
+
"number"
|
|
24
|
+
]
|
|
25
|
+
},
|
|
26
|
+
"description": "框架 trusted QQ uin 列表(弱于 master)",
|
|
27
|
+
"x-i18n": {
|
|
28
|
+
"en": {
|
|
29
|
+
"description": "Framework trusted QQ uin list, below master authority."
|
|
30
|
+
}
|
|
31
|
+
}
|
|
14
32
|
},
|
|
15
33
|
"password": {
|
|
16
34
|
"type": "string",
|
|
17
|
-
"description": "QQ 密码(可选,不填则扫码登录)"
|
|
35
|
+
"description": "QQ 密码(可选,不填则扫码登录)",
|
|
36
|
+
"x-i18n": {
|
|
37
|
+
"en": {
|
|
38
|
+
"description": "QQ password (optional); scan a QR code to log in when omitted."
|
|
39
|
+
}
|
|
40
|
+
}
|
|
18
41
|
},
|
|
19
42
|
"platform": {
|
|
20
43
|
"type": "number",
|
|
21
|
-
"enum": [
|
|
44
|
+
"enum": [
|
|
45
|
+
1,
|
|
46
|
+
2,
|
|
47
|
+
3,
|
|
48
|
+
4,
|
|
49
|
+
5,
|
|
50
|
+
6,
|
|
51
|
+
7
|
|
52
|
+
],
|
|
22
53
|
"default": 1,
|
|
23
|
-
"description": "登录设备平台:1=Android, 2=aPad, 3=Watch, 4=iMac, 5=iPad, 6=Tim, 7=Custom"
|
|
54
|
+
"description": "登录设备平台:1=Android, 2=aPad, 3=Watch, 4=iMac, 5=iPad, 6=Tim, 7=Custom",
|
|
55
|
+
"x-i18n": {
|
|
56
|
+
"en": {
|
|
57
|
+
"description": "Login device platform: 1=Android, 2=aPad, 3=Watch, 4=iMac, 5=iPad, 6=Tim, 7=Custom."
|
|
58
|
+
}
|
|
59
|
+
}
|
|
24
60
|
},
|
|
25
61
|
"ver": {
|
|
26
62
|
"type": "string",
|
|
27
|
-
"description": "协议版本号,仅在对应 platform 有多个版本时有效,不填则使用最新版本"
|
|
63
|
+
"description": "协议版本号,仅在对应 platform 有多个版本时有效,不填则使用最新版本",
|
|
64
|
+
"x-i18n": {
|
|
65
|
+
"en": {
|
|
66
|
+
"description": "Protocol version, only applicable when the selected platform has multiple versions. Uses the latest when omitted."
|
|
67
|
+
}
|
|
68
|
+
}
|
|
28
69
|
},
|
|
29
70
|
"signApiAddr": {
|
|
30
71
|
"type": "string",
|
|
31
|
-
"description": "签名服务器地址;未配置时若安装了 @icqqjs/qqsign 则自动使用本地签名"
|
|
72
|
+
"description": "签名服务器地址;未配置时若安装了 @icqqjs/qqsign 则自动使用本地签名",
|
|
73
|
+
"x-i18n": {
|
|
74
|
+
"en": {
|
|
75
|
+
"description": "Signing server URL. When omitted, uses local signing automatically if @icqqjs/qqsign is installed."
|
|
76
|
+
}
|
|
77
|
+
}
|
|
32
78
|
},
|
|
33
79
|
"dataDir": {
|
|
34
80
|
"type": "string",
|
|
35
|
-
"description": "数据存储文件夹路径,默认为主模块下的 data 文件夹"
|
|
81
|
+
"description": "数据存储文件夹路径,默认为主模块下的 data 文件夹",
|
|
82
|
+
"x-i18n": {
|
|
83
|
+
"en": {
|
|
84
|
+
"description": "Data directory path; defaults to the data directory under the main module."
|
|
85
|
+
}
|
|
86
|
+
}
|
|
36
87
|
},
|
|
37
88
|
"autoReconnect": {
|
|
38
89
|
"type": "boolean",
|
|
39
90
|
"default": true,
|
|
40
|
-
"description": "断线后是否自动重连(默认 true)"
|
|
91
|
+
"description": "断线后是否自动重连(默认 true)",
|
|
92
|
+
"x-i18n": {
|
|
93
|
+
"en": {
|
|
94
|
+
"description": "Reconnect automatically after disconnecting (default true)."
|
|
95
|
+
}
|
|
96
|
+
}
|
|
41
97
|
},
|
|
42
98
|
"outboundMedia": {
|
|
43
99
|
"type": "string",
|
|
44
|
-
"enum": [
|
|
100
|
+
"enum": [
|
|
101
|
+
"file",
|
|
102
|
+
"base64"
|
|
103
|
+
],
|
|
45
104
|
"default": "file",
|
|
46
|
-
"description": "出站媒体模式:file=落盘本地路径(默认); base64=CQ base64:// 内联"
|
|
105
|
+
"description": "出站媒体模式:file=落盘本地路径(默认); base64=CQ base64:// 内联",
|
|
106
|
+
"x-i18n": {
|
|
107
|
+
"en": {
|
|
108
|
+
"description": "Outbound media mode: file uses local file paths (default); base64 uses inline CQ base64:// data."
|
|
109
|
+
}
|
|
110
|
+
}
|
|
47
111
|
},
|
|
48
112
|
"ignoreSelf": {
|
|
49
113
|
"type": "boolean",
|
|
50
114
|
"default": true,
|
|
51
|
-
"description": "群聊和频道中是否过滤自己的消息(默认 true)"
|
|
115
|
+
"description": "群聊和频道中是否过滤自己的消息(默认 true)",
|
|
116
|
+
"x-i18n": {
|
|
117
|
+
"en": {
|
|
118
|
+
"description": "Filter the bot's own messages in groups and channels (default true)."
|
|
119
|
+
}
|
|
120
|
+
}
|
|
52
121
|
},
|
|
53
122
|
"resend": {
|
|
54
123
|
"type": "boolean",
|
|
55
124
|
"default": false,
|
|
56
|
-
"description": "被风控时是否尝试用分片发送(默认 false)"
|
|
125
|
+
"description": "被风控时是否尝试用分片发送(默认 false)",
|
|
126
|
+
"x-i18n": {
|
|
127
|
+
"en": {
|
|
128
|
+
"description": "Attempt fragmented sending when rate-limited by the platform (default false)."
|
|
129
|
+
}
|
|
130
|
+
}
|
|
57
131
|
},
|
|
58
132
|
"cacheGroupMember": {
|
|
59
133
|
"type": "boolean",
|
|
60
134
|
"default": true,
|
|
61
|
-
"description": "是否缓存群员列表(默认 true);群多时(500+)会多占约 100MB+ 内存"
|
|
135
|
+
"description": "是否缓存群员列表(默认 true);群多时(500+)会多占约 100MB+ 内存",
|
|
136
|
+
"x-i18n": {
|
|
137
|
+
"en": {
|
|
138
|
+
"description": "Cache group member lists (default true). Large numbers of groups (500+) may use approximately 100 MB or more of extra memory."
|
|
139
|
+
}
|
|
140
|
+
}
|
|
62
141
|
},
|
|
63
142
|
"autoServer": {
|
|
64
143
|
"type": "boolean",
|
|
65
144
|
"default": true,
|
|
66
|
-
"description": "是否自动选择最优服务器(默认 true)"
|
|
145
|
+
"description": "是否自动选择最优服务器(默认 true)",
|
|
146
|
+
"x-i18n": {
|
|
147
|
+
"en": {
|
|
148
|
+
"description": "Automatically select the optimal server (default true)."
|
|
149
|
+
}
|
|
150
|
+
}
|
|
67
151
|
},
|
|
68
152
|
"qqnt": {
|
|
69
153
|
"type": "boolean",
|
|
70
154
|
"default": true,
|
|
71
|
-
"description": "是否使用 QQNT 协议(默认 true)"
|
|
155
|
+
"description": "是否使用 QQNT 协议(默认 true)",
|
|
156
|
+
"x-i18n": {
|
|
157
|
+
"en": {
|
|
158
|
+
"description": "Use the QQNT protocol (default true)."
|
|
159
|
+
}
|
|
160
|
+
}
|
|
72
161
|
},
|
|
73
162
|
"ntLogin": {
|
|
74
163
|
"type": "boolean",
|
|
75
|
-
"description": "是否使用 NT 登录方式"
|
|
164
|
+
"description": "是否使用 NT 登录方式",
|
|
165
|
+
"x-i18n": {
|
|
166
|
+
"en": {
|
|
167
|
+
"description": "Use the NT login method."
|
|
168
|
+
}
|
|
169
|
+
}
|
|
76
170
|
},
|
|
77
171
|
"endpoints": {
|
|
78
172
|
"type": "array",
|
|
@@ -88,81 +182,195 @@
|
|
|
88
182
|
"description": "QQ uin"
|
|
89
183
|
},
|
|
90
184
|
"master": {
|
|
91
|
-
"type": [
|
|
92
|
-
|
|
185
|
+
"type": [
|
|
186
|
+
"string",
|
|
187
|
+
"number"
|
|
188
|
+
],
|
|
189
|
+
"description": "本 endpoint 的 master QQ uin;覆盖顶层 master",
|
|
190
|
+
"x-i18n": {
|
|
191
|
+
"en": {
|
|
192
|
+
"description": "Master QQ uin list for this endpoint, overriding the top-level master list."
|
|
193
|
+
}
|
|
194
|
+
}
|
|
93
195
|
},
|
|
94
196
|
"trusted": {
|
|
95
197
|
"type": "array",
|
|
96
|
-
"items": {
|
|
97
|
-
|
|
198
|
+
"items": {
|
|
199
|
+
"type": [
|
|
200
|
+
"string",
|
|
201
|
+
"number"
|
|
202
|
+
]
|
|
203
|
+
},
|
|
204
|
+
"description": "本 endpoint 的 trusted 列表",
|
|
205
|
+
"x-i18n": {
|
|
206
|
+
"en": {
|
|
207
|
+
"description": "Trusted users for this endpoint."
|
|
208
|
+
}
|
|
209
|
+
}
|
|
98
210
|
},
|
|
99
211
|
"password": {
|
|
100
212
|
"type": "string",
|
|
101
|
-
"description": "本 endpoint 的 QQ 密码"
|
|
213
|
+
"description": "本 endpoint 的 QQ 密码",
|
|
214
|
+
"x-i18n": {
|
|
215
|
+
"en": {
|
|
216
|
+
"description": "QQ password for this endpoint."
|
|
217
|
+
}
|
|
218
|
+
}
|
|
102
219
|
},
|
|
103
220
|
"platform": {
|
|
104
221
|
"type": "number",
|
|
105
|
-
"enum": [
|
|
106
|
-
|
|
222
|
+
"enum": [
|
|
223
|
+
1,
|
|
224
|
+
2,
|
|
225
|
+
3,
|
|
226
|
+
4,
|
|
227
|
+
5,
|
|
228
|
+
6,
|
|
229
|
+
7
|
|
230
|
+
],
|
|
231
|
+
"description": "登录设备平台:1=Android, 2=aPad, 3=Watch, 4=iMac, 5=iPad, 6=Tim, 7=Custom",
|
|
232
|
+
"x-i18n": {
|
|
233
|
+
"en": {
|
|
234
|
+
"description": "Login device platform: 1=Android, 2=aPad, 3=Watch, 4=iMac, 5=iPad, 6=Tim, 7=Custom."
|
|
235
|
+
}
|
|
236
|
+
}
|
|
107
237
|
},
|
|
108
238
|
"ver": {
|
|
109
239
|
"type": "string",
|
|
110
|
-
"description": "协议版本号"
|
|
240
|
+
"description": "协议版本号",
|
|
241
|
+
"x-i18n": {
|
|
242
|
+
"en": {
|
|
243
|
+
"description": "Protocol version."
|
|
244
|
+
}
|
|
245
|
+
}
|
|
111
246
|
},
|
|
112
247
|
"signApiAddr": {
|
|
113
248
|
"type": "string",
|
|
114
|
-
"description": "签名服务器地址"
|
|
249
|
+
"description": "签名服务器地址",
|
|
250
|
+
"x-i18n": {
|
|
251
|
+
"en": {
|
|
252
|
+
"description": "Signing server URL."
|
|
253
|
+
}
|
|
254
|
+
}
|
|
115
255
|
},
|
|
116
256
|
"dataDir": {
|
|
117
257
|
"type": "string",
|
|
118
|
-
"description": "数据存储文件夹路径"
|
|
258
|
+
"description": "数据存储文件夹路径",
|
|
259
|
+
"x-i18n": {
|
|
260
|
+
"en": {
|
|
261
|
+
"description": "Data directory path."
|
|
262
|
+
}
|
|
263
|
+
}
|
|
119
264
|
},
|
|
120
265
|
"autoReconnect": {
|
|
121
266
|
"type": "boolean",
|
|
122
|
-
"description": "断线后是否自动重连"
|
|
267
|
+
"description": "断线后是否自动重连",
|
|
268
|
+
"x-i18n": {
|
|
269
|
+
"en": {
|
|
270
|
+
"description": "Reconnect automatically after disconnecting."
|
|
271
|
+
}
|
|
272
|
+
}
|
|
123
273
|
},
|
|
124
274
|
"outboundMedia": {
|
|
125
275
|
"type": "string",
|
|
126
|
-
"enum": [
|
|
127
|
-
|
|
276
|
+
"enum": [
|
|
277
|
+
"file",
|
|
278
|
+
"base64"
|
|
279
|
+
],
|
|
280
|
+
"description": "出站媒体模式",
|
|
281
|
+
"x-i18n": {
|
|
282
|
+
"en": {
|
|
283
|
+
"description": "Outbound media mode."
|
|
284
|
+
}
|
|
285
|
+
}
|
|
128
286
|
},
|
|
129
287
|
"ignoreSelf": {
|
|
130
288
|
"type": "boolean",
|
|
131
|
-
"description": "是否过滤自己的消息"
|
|
289
|
+
"description": "是否过滤自己的消息",
|
|
290
|
+
"x-i18n": {
|
|
291
|
+
"en": {
|
|
292
|
+
"description": "Filter the bot's own messages."
|
|
293
|
+
}
|
|
294
|
+
}
|
|
132
295
|
},
|
|
133
296
|
"resend": {
|
|
134
297
|
"type": "boolean",
|
|
135
|
-
"description": "被风控时是否分片发送"
|
|
298
|
+
"description": "被风控时是否分片发送",
|
|
299
|
+
"x-i18n": {
|
|
300
|
+
"en": {
|
|
301
|
+
"description": "Use fragmented sending when rate-limited by the platform."
|
|
302
|
+
}
|
|
303
|
+
}
|
|
136
304
|
},
|
|
137
305
|
"cacheGroupMember": {
|
|
138
306
|
"type": "boolean",
|
|
139
|
-
"description": "是否缓存群员列表"
|
|
307
|
+
"description": "是否缓存群员列表",
|
|
308
|
+
"x-i18n": {
|
|
309
|
+
"en": {
|
|
310
|
+
"description": "Cache group member lists."
|
|
311
|
+
}
|
|
312
|
+
}
|
|
140
313
|
},
|
|
141
314
|
"autoServer": {
|
|
142
315
|
"type": "boolean",
|
|
143
|
-
"description": "是否自动选择最优服务器"
|
|
316
|
+
"description": "是否自动选择最优服务器",
|
|
317
|
+
"x-i18n": {
|
|
318
|
+
"en": {
|
|
319
|
+
"description": "Automatically select the optimal server."
|
|
320
|
+
}
|
|
321
|
+
}
|
|
144
322
|
},
|
|
145
323
|
"qqnt": {
|
|
146
324
|
"type": "boolean",
|
|
147
|
-
"description": "是否使用 QQNT 协议"
|
|
325
|
+
"description": "是否使用 QQNT 协议",
|
|
326
|
+
"x-i18n": {
|
|
327
|
+
"en": {
|
|
328
|
+
"description": "Use the QQNT protocol."
|
|
329
|
+
}
|
|
330
|
+
}
|
|
148
331
|
},
|
|
149
332
|
"ntLogin": {
|
|
150
333
|
"type": "boolean",
|
|
151
|
-
"description": "是否使用 NT 登录方式"
|
|
334
|
+
"description": "是否使用 NT 登录方式",
|
|
335
|
+
"x-i18n": {
|
|
336
|
+
"en": {
|
|
337
|
+
"description": "Use the NT login method."
|
|
338
|
+
}
|
|
339
|
+
}
|
|
152
340
|
},
|
|
153
341
|
"commandPrefix": {
|
|
154
342
|
"type": "string",
|
|
155
|
-
"description": "本 endpoint 的命令前缀"
|
|
343
|
+
"description": "本 endpoint 的命令前缀",
|
|
344
|
+
"x-i18n": {
|
|
345
|
+
"en": {
|
|
346
|
+
"description": "Command prefix for this endpoint."
|
|
347
|
+
}
|
|
348
|
+
}
|
|
156
349
|
}
|
|
157
350
|
},
|
|
158
|
-
"required": [
|
|
351
|
+
"required": [
|
|
352
|
+
"id"
|
|
353
|
+
]
|
|
354
|
+
},
|
|
355
|
+
"x-i18n": {
|
|
356
|
+
"en": {
|
|
357
|
+
"description": "Multiple accounts: mount multiple endpoints in one plugin instance. Each entry uses the top-level fields; id is required and other fields override defaults."
|
|
358
|
+
}
|
|
159
359
|
}
|
|
160
360
|
},
|
|
161
361
|
"commandPrefix": {
|
|
162
362
|
"type": "string",
|
|
163
363
|
"default": "",
|
|
164
|
-
"description": "命令前缀(默认 '' 无前缀)。endpoints[i] 可逐项覆盖"
|
|
364
|
+
"description": "命令前缀(默认 '' 无前缀)。endpoints[i] 可逐项覆盖",
|
|
365
|
+
"x-i18n": {
|
|
366
|
+
"en": {
|
|
367
|
+
"description": "Command prefix, empty by default. Each endpoints[i] can override it."
|
|
368
|
+
}
|
|
369
|
+
}
|
|
165
370
|
}
|
|
166
371
|
},
|
|
167
|
-
"required": [
|
|
372
|
+
"required": [
|
|
373
|
+
"master",
|
|
374
|
+
"endpoints"
|
|
375
|
+
]
|
|
168
376
|
}
|
package/src/endpoint.ts
CHANGED
|
@@ -39,6 +39,7 @@ import {
|
|
|
39
39
|
type IcqqMessageEvent,
|
|
40
40
|
} from './icqq-inbound.js';
|
|
41
41
|
import {
|
|
42
|
+
icqqNoticeDedupeKey,
|
|
42
43
|
isIcqqNoticePayload,
|
|
43
44
|
isIcqqRequestPayload,
|
|
44
45
|
} from './icqq-inbox.js';
|
|
@@ -62,11 +63,13 @@ import {
|
|
|
62
63
|
import type { SystemMessage as IcqqSystemMessage } from './types.js';
|
|
63
64
|
import { ManagedIcqqClient, type IcqqBackgroundOperation } from './managed-client.js';
|
|
64
65
|
import { inspectGroupMember, moderateGroupMember } from './group-moderation.js';
|
|
66
|
+
import { inspectGroupMemberProfile, inspectGroupScene } from './group-scene.js';
|
|
65
67
|
|
|
66
68
|
export interface IcqqEndpointOptions {
|
|
67
69
|
readonly id: CapabilityId;
|
|
68
70
|
readonly config: ResolvedIcqqConfig;
|
|
69
71
|
readonly loginAssist: LoginAssist;
|
|
72
|
+
readonly locale?: import('zhin.js/i18n').Locale;
|
|
70
73
|
}
|
|
71
74
|
|
|
72
75
|
const BOUND_EVENTS = [
|
|
@@ -114,6 +117,8 @@ export class IcqqEndpoint extends Endpoint<Client> {
|
|
|
114
117
|
|
|
115
118
|
readonly management: EndpointManagement = Object.freeze<EndpointManagement>({
|
|
116
119
|
inspectGroupMember: (group, user, signal) => inspectGroupMember(this.client, group, user, signal),
|
|
120
|
+
inspectGroupScene: (group, signal) => inspectGroupScene(this.client, group, signal),
|
|
121
|
+
inspectGroupMemberProfile: (group, user, signal) => inspectGroupMemberProfile(this.client, group, user, signal),
|
|
117
122
|
moderateGroupMember: (group, user, action, signal) => moderateGroupMember(
|
|
118
123
|
this.client, group, user, action, signal, () => this.#open && !this.#retiring && !this.#stopped,
|
|
119
124
|
),
|
|
@@ -149,6 +154,11 @@ export class IcqqEndpoint extends Endpoint<Client> {
|
|
|
149
154
|
deleteFriend: async (userId) => {
|
|
150
155
|
await this.client.deleteFriend(Number(userId));
|
|
151
156
|
},
|
|
157
|
+
sendGroupNotice: async (groupId, content) => {
|
|
158
|
+
if (await this.client.sendGroupNotice(Number(groupId), content) === false) {
|
|
159
|
+
throw new Error('QQ did not confirm the group notice');
|
|
160
|
+
}
|
|
161
|
+
},
|
|
152
162
|
});
|
|
153
163
|
|
|
154
164
|
readonly control: EndpointControl = Object.freeze({
|
|
@@ -699,6 +709,7 @@ export class IcqqEndpoint extends Endpoint<Client> {
|
|
|
699
709
|
this.#logSystemEvent(`login.${step}`, event);
|
|
700
710
|
void runIcqqLoginAssistStep({
|
|
701
711
|
assist: this.#options.loginAssist,
|
|
712
|
+
locale: this.#options.locale,
|
|
702
713
|
client: this.client,
|
|
703
714
|
adapter: 'icqq',
|
|
704
715
|
endpointKey: this.endpointName,
|
|
@@ -816,6 +827,7 @@ export class IcqqEndpoint extends Endpoint<Client> {
|
|
|
816
827
|
channelType: normalized.channelType,
|
|
817
828
|
channelId: normalized.channelId,
|
|
818
829
|
channelParentGroupId: normalized.channelParentGroupId,
|
|
830
|
+
name: normalized.groupName,
|
|
819
831
|
});
|
|
820
832
|
const quoteId = resolveIcqqQuoteIdFromEvent(data);
|
|
821
833
|
const quoted = messageLookupFromIcqqSource(findIcqqNestedMessageSource(data));
|
|
@@ -935,13 +947,7 @@ export class IcqqEndpoint extends Endpoint<Client> {
|
|
|
935
947
|
|
|
936
948
|
#onNoticeEvent(payload: Record<string, unknown> | null): void {
|
|
937
949
|
if (!payload || !isIcqqNoticePayload(payload)) return;
|
|
938
|
-
|
|
939
|
-
payload.time,
|
|
940
|
-
payload.operator_id ?? payload.user_id,
|
|
941
|
-
payload.notice_type ?? payload.type,
|
|
942
|
-
payload.sub_type,
|
|
943
|
-
].join('_');
|
|
944
|
-
if (!this.#sideEventDeduper.shouldProcess(`notice:${key}`)) return;
|
|
950
|
+
if (!this.#sideEventDeduper.shouldProcess(icqqNoticeDedupeKey(payload))) return;
|
|
945
951
|
this.#dispatchIcqqSideEvent(payload);
|
|
946
952
|
}
|
|
947
953
|
|
package/src/group-moderation.ts
CHANGED
|
@@ -8,17 +8,22 @@ export async function inspectGroupMember(
|
|
|
8
8
|
signal.throwIfAborted();
|
|
9
9
|
const group = numericId(groupId);
|
|
10
10
|
const user = numericId(userId);
|
|
11
|
+
const selfLookup = client.getGroupMemberInfo(group, client.uin, true);
|
|
11
12
|
const [self, member] = await Promise.all([
|
|
12
|
-
|
|
13
|
-
client.getGroupMemberInfo(group, user, true),
|
|
13
|
+
selfLookup,
|
|
14
|
+
user === client.uin ? selfLookup : client.getGroupMemberInfo(group, user, true),
|
|
14
15
|
]);
|
|
15
16
|
signal.throwIfAborted();
|
|
16
17
|
const joinedAt = member.join_time * 1000;
|
|
18
|
+
const platformRole = member.user_id === user && member.group_id === group
|
|
19
|
+
&& (member.role === 'owner' || member.role === 'admin' || member.role === 'member')
|
|
20
|
+
? member.role : undefined;
|
|
17
21
|
return {
|
|
18
22
|
canModerate: client.isOnline() && user !== client.uin
|
|
19
23
|
&& self.user_id === client.uin && self.group_id === group
|
|
20
24
|
&& member.user_id === user && member.group_id === group
|
|
21
25
|
&& (self.role === 'owner' || self.role === 'admin') && member.role === 'member',
|
|
26
|
+
...(platformRole ? { platformRole } : {}),
|
|
22
27
|
...(Number.isSafeInteger(joinedAt) && joinedAt > 0 && joinedAt <= Date.now() ? { joinedAt } : {}),
|
|
23
28
|
};
|
|
24
29
|
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { Client } from '@icqqjs/icqq';
|
|
2
|
+
import type { GroupMemberProfile, GroupSceneFacts } from 'zhin.js/adapter';
|
|
3
|
+
|
|
4
|
+
/** Listing a very large roster only to find admins is not worth the packet; adminIds stay unknown. */
|
|
5
|
+
export const ADMIN_SCAN_MAX_MEMBERS = 1000;
|
|
6
|
+
const MAX_ADMIN_IDS = 20;
|
|
7
|
+
|
|
8
|
+
function positiveId(value: unknown): string | undefined {
|
|
9
|
+
return typeof value === 'number' && Number.isSafeInteger(value) && value > 0 ? String(value) : undefined;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function numericId(value: string): number {
|
|
13
|
+
const id = Number(value);
|
|
14
|
+
if (!/^[1-9]\d*$/.test(value) || !Number.isSafeInteger(id)) throw new Error('Invalid QQ identifier');
|
|
15
|
+
return id;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* ADR 0041 scene facts: one uncached group-info read, plus (small groups only) one roster read for
|
|
20
|
+
* admins. A roster failure leaves adminIds unknown instead of failing the other facts.
|
|
21
|
+
*/
|
|
22
|
+
export async function inspectGroupScene(client: Client, groupId: string, signal: AbortSignal): Promise<GroupSceneFacts> {
|
|
23
|
+
signal.throwIfAborted();
|
|
24
|
+
const group = numericId(groupId);
|
|
25
|
+
const info = await client.getGroupInfo(group, true);
|
|
26
|
+
signal.throwIfAborted();
|
|
27
|
+
if (info.group_id !== group) return {};
|
|
28
|
+
const memberCount = Number.isSafeInteger(info.member_count) && info.member_count > 0 ? info.member_count : undefined;
|
|
29
|
+
const ownerId = positiveId(info.owner_id);
|
|
30
|
+
let adminIds: string[] | undefined;
|
|
31
|
+
if (memberCount === undefined || memberCount <= ADMIN_SCAN_MAX_MEMBERS) {
|
|
32
|
+
try {
|
|
33
|
+
const roster = await client.getGroupMemberList(group);
|
|
34
|
+
signal.throwIfAborted();
|
|
35
|
+
adminIds = [...roster.values()]
|
|
36
|
+
.filter(member => member.role === 'admin' && member.group_id === group)
|
|
37
|
+
.map(member => positiveId(member.user_id))
|
|
38
|
+
.filter((id): id is string => id !== undefined)
|
|
39
|
+
.slice(0, MAX_ADMIN_IDS);
|
|
40
|
+
} catch {
|
|
41
|
+
signal.throwIfAborted();
|
|
42
|
+
adminIds = undefined;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const name = typeof info.group_name === 'string' && info.group_name.trim() ? info.group_name.trim() : undefined;
|
|
46
|
+
const nowSeconds = Date.now() / 1000;
|
|
47
|
+
return {
|
|
48
|
+
...(name ? { name } : {}),
|
|
49
|
+
...(ownerId ? { ownerId } : {}),
|
|
50
|
+
...(adminIds ? { adminIds } : {}),
|
|
51
|
+
...(memberCount !== undefined ? { memberCount } : {}),
|
|
52
|
+
...(typeof info.shutup_time_whole === 'number' ? { muteAll: info.shutup_time_whole > nowSeconds } : {}),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** ADR 0041 member facts from one uncached lookup (same rationale as the agent `freshMember` helper). */
|
|
57
|
+
export async function inspectGroupMemberProfile(
|
|
58
|
+
client: Client, groupId: string, userId: string, signal: AbortSignal,
|
|
59
|
+
): Promise<GroupMemberProfile> {
|
|
60
|
+
signal.throwIfAborted();
|
|
61
|
+
const group = numericId(groupId);
|
|
62
|
+
const user = numericId(userId);
|
|
63
|
+
const member = await client.getGroupMemberInfo(group, user, true);
|
|
64
|
+
signal.throwIfAborted();
|
|
65
|
+
if (member.user_id !== user || member.group_id !== group) return {};
|
|
66
|
+
const card = typeof member.card === 'string' && member.card.trim() ? member.card.trim() : undefined;
|
|
67
|
+
const title = typeof member.title === 'string' && member.title.trim() ? member.title.trim() : undefined;
|
|
68
|
+
const shutupSeconds = member.shutup_time;
|
|
69
|
+
return {
|
|
70
|
+
...(member.role === 'owner' || member.role === 'admin' || member.role === 'member' ? { platformRole: member.role } : {}),
|
|
71
|
+
...(card ? { card } : {}),
|
|
72
|
+
...(title ? { title } : {}),
|
|
73
|
+
...(typeof shutupSeconds === 'number' && Number.isFinite(shutupSeconds)
|
|
74
|
+
? { mutedUntil: shutupSeconds * 1000 > Date.now() ? shutupSeconds * 1000 : 0 } : {}),
|
|
75
|
+
};
|
|
76
|
+
}
|
package/src/i18n.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { createMessages } from 'zhin.js/i18n';
|
|
2
|
+
|
|
3
|
+
export const messages = createMessages({
|
|
4
|
+
"direct": {
|
|
5
|
+
"zh-CN": "direct(直连 @icqqjs/icqq)",
|
|
6
|
+
"en": "direct (@icqqjs/icqq)"
|
|
7
|
+
},
|
|
8
|
+
"addUsage": {
|
|
9
|
+
"zh-CN": "用法:{command} <uin>(uin 为纯数字 QQ 号)",
|
|
10
|
+
"en": "Usage: {command} <uin> (a numeric QQ account ID)"
|
|
11
|
+
},
|
|
12
|
+
"uinRequired": {
|
|
13
|
+
"zh-CN": "请提供纯数字 QQ 号(uin)",
|
|
14
|
+
"en": "Provide a numeric QQ account ID (uin)"
|
|
15
|
+
},
|
|
16
|
+
"uinInvalid": {
|
|
17
|
+
"zh-CN": "icqq endpoint 名必须是纯数字 QQ 号(uin)",
|
|
18
|
+
"en": "The ICQQ endpoint name must be a numeric QQ account ID (uin)"
|
|
19
|
+
},
|
|
20
|
+
"endpointAdded": {
|
|
21
|
+
"zh-CN": "✅ endpoint「{id}」已追加到 {file} 的 plugins.icqq.endpoints。\n重启 zhin 后生效。",
|
|
22
|
+
"en": "✅ Endpoint “{id}” added to plugins.icqq.endpoints in {file}.\nRestart zhin to apply."
|
|
23
|
+
},
|
|
24
|
+
"endpointAddFailed": {
|
|
25
|
+
"zh-CN": "添加失败:{error}",
|
|
26
|
+
"en": "Could not add the endpoint: {error}"
|
|
27
|
+
},
|
|
28
|
+
"loginQr": {
|
|
29
|
+
"zh-CN": "扫码登录:请在手机 QQ 扫描二维码,完成后在 Console / 终端确认继续",
|
|
30
|
+
"en": "Scan the QR code in mobile QQ, then confirm in Console or your terminal."
|
|
31
|
+
},
|
|
32
|
+
"loginSlider": {
|
|
33
|
+
"zh-CN": "滑动验证:打开链接完成滑块,将返回的 ticket(可含 randstr)提交继续",
|
|
34
|
+
"en": "Open the link, complete the slider challenge, and submit the returned ticket (with randstr if provided)."
|
|
35
|
+
},
|
|
36
|
+
"loginSms": {
|
|
37
|
+
"zh-CN": "设备锁:已请求短信验证码,请提交短信验证码继续",
|
|
38
|
+
"en": "Device verification: an SMS code was requested. Submit the code to continue."
|
|
39
|
+
},
|
|
40
|
+
"loginAuth": {
|
|
41
|
+
"zh-CN": "身份验证:完成后在 Console / 终端确认继续",
|
|
42
|
+
"en": "Complete identity verification, then confirm in Console or your terminal."
|
|
43
|
+
},
|
|
44
|
+
"loginOther": {
|
|
45
|
+
"zh-CN": "登录辅助({step})",
|
|
46
|
+
"en": "Login assistance ({step})"
|
|
47
|
+
}
|
|
48
|
+
});
|
package/src/icqq-inbound.ts
CHANGED
|
@@ -129,6 +129,8 @@ export interface NormalizedIcqqInbound {
|
|
|
129
129
|
channelId: string;
|
|
130
130
|
/** 群临时会话:来源群号 */
|
|
131
131
|
channelParentGroupId?: string;
|
|
132
|
+
/** 群聊时来自 group_name */
|
|
133
|
+
groupName?: string;
|
|
132
134
|
userId: string;
|
|
133
135
|
nickname: string;
|
|
134
136
|
/** 群聊时来自 sender.role(私聊无) */
|
|
@@ -847,6 +849,9 @@ export function normalizeIcqqInboundMessage(
|
|
|
847
849
|
channelType,
|
|
848
850
|
channelId,
|
|
849
851
|
channelParentGroupId,
|
|
852
|
+
...(channelType === "group" && typeof ext.group_name === "string" && ext.group_name.trim()
|
|
853
|
+
? { groupName: ext.group_name.trim() }
|
|
854
|
+
: {}),
|
|
850
855
|
userId: String(userIdNum),
|
|
851
856
|
nickname,
|
|
852
857
|
senderRole,
|
package/src/icqq-inbox.ts
CHANGED
|
@@ -20,6 +20,21 @@ export function isIcqqNoticePayload(payload: unknown): boolean {
|
|
|
20
20
|
&& (postType === 'notice' || postType.startsWith('notice.'));
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
/**
|
|
24
|
+
* notice 去重键。成员与操作人分开记:同一管理员先后拉两个人进群时,`operator_id ?? user_id`
|
|
25
|
+
* 会撞键(icqq 群通知又常不带 time),后一次入群被当重复吞掉。
|
|
26
|
+
*/
|
|
27
|
+
export function icqqNoticeDedupeKey(payload: Record<string, unknown>): string {
|
|
28
|
+
return `notice:${[
|
|
29
|
+
payload.time,
|
|
30
|
+
payload.group_id,
|
|
31
|
+
payload.user_id,
|
|
32
|
+
payload.operator_id,
|
|
33
|
+
payload.notice_type ?? payload.type,
|
|
34
|
+
payload.sub_type,
|
|
35
|
+
].join('_')}`;
|
|
36
|
+
}
|
|
37
|
+
|
|
23
38
|
/**
|
|
24
39
|
* request 事件载荷 → unified_inbox_request 行。
|
|
25
40
|
* platform_request_id 取 flag(审批回执句柄),缺省回退 seq;两者皆无则无法审批,丢弃。
|