@zhin.js/adapter-icqq 1.1.20 → 1.1.22
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 +64 -0
- package/PERMITS.md +1 -0
- package/README.md +11 -1
- package/adapters/icqq/index.js +96 -25
- package/adapters/icqq/index.ts +97 -27
- package/agents/icqq/messages.js +20 -0
- package/agents/icqq/messages.ts +20 -0
- package/agents/icqq/skills/icqq-conversation/SKILL.md +2 -2
- package/agents/icqq/skills/icqq-conversation/tools/conversation_info/index.js +1 -1
- package/agents/icqq/skills/icqq-conversation/tools/conversation_info/index.ts +1 -1
- package/agents/icqq/skills/icqq-group-admin/SKILL.md +8 -3
- package/agents/icqq/skills/icqq-group-admin/tools/announce/index.js +2 -0
- package/agents/icqq/skills/icqq-group-admin/tools/announce/index.ts +2 -0
- package/agents/icqq/skills/icqq-group-admin/tools/essence/index.js +2 -0
- package/agents/icqq/skills/icqq-group-admin/tools/essence/index.ts +2 -0
- package/agents/icqq/skills/icqq-group-admin/tools/group_kick/index.js +2 -0
- package/agents/icqq/skills/icqq-group-admin/tools/group_kick/index.ts +2 -0
- package/agents/icqq/skills/icqq-group-admin/tools/group_leave/index.js +1 -1
- package/agents/icqq/skills/icqq-group-admin/tools/group_leave/index.ts +1 -1
- package/agents/icqq/skills/icqq-group-admin/tools/recall_member_message/index.js +3 -1
- package/agents/icqq/skills/icqq-group-admin/tools/recall_member_message/index.ts +3 -1
- package/agents/icqq/skills/icqq-group-admin/tools/set_group_admin/index.js +43 -0
- package/agents/icqq/skills/icqq-group-admin/tools/set_group_admin/index.ts +36 -0
- package/agents/icqq/skills/icqq-group-admin/tools/set_mute/index.js +2 -0
- package/agents/icqq/skills/icqq-group-admin/tools/set_mute/index.ts +2 -0
- package/agents/icqq/skills/icqq-group-admin/tools/set_title/index.js +2 -0
- package/agents/icqq/skills/icqq-group-admin/tools/set_title/index.ts +2 -0
- package/agents/icqq/skills/icqq-messages/SKILL.md +1 -1
- package/agents/icqq/skills/icqq-messages/tools/recall_own_message/index.js +1 -1
- package/agents/icqq/skills/icqq-messages/tools/recall_own_message/index.ts +1 -1
- package/lib/endpoint.js +6 -2
- package/lib/group-actions.d.ts +10 -0
- package/lib/group-actions.js +94 -0
- package/lib/protocol.d.ts +2 -1
- package/lib/self-profile.d.ts +6 -0
- package/lib/self-profile.js +56 -0
- package/lib/sensitive-commands.d.ts +8 -0
- package/lib/sensitive-commands.js +16 -0
- package/package.json +26 -20
- package/plugin.js +8 -1
- package/prompt-sections/platform/index.js +1 -1
- package/prompt-sections/platform/index.ts +1 -1
- package/schema.json +106 -1
- package/src/endpoint.ts +6 -2
- package/src/group-actions.ts +105 -0
- package/src/protocol.ts +2 -1
- package/src/self-profile.ts +55 -0
- package/src/sensitive-commands.ts +19 -0
package/schema.json
CHANGED
|
@@ -433,6 +433,27 @@
|
|
|
433
433
|
"endpoint",
|
|
434
434
|
"id"
|
|
435
435
|
]
|
|
436
|
+
},
|
|
437
|
+
"nickname": {
|
|
438
|
+
"type": [
|
|
439
|
+
"string",
|
|
440
|
+
"null"
|
|
441
|
+
],
|
|
442
|
+
"description": "账号昵称兜底",
|
|
443
|
+
"x-i18n": {
|
|
444
|
+
"en": {
|
|
445
|
+
"description": "Fallback account nickname"
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
},
|
|
449
|
+
"profile": {
|
|
450
|
+
"$ref": "#/$defs/selfProfileFallback",
|
|
451
|
+
"description": "账号资料兜底",
|
|
452
|
+
"x-i18n": {
|
|
453
|
+
"en": {
|
|
454
|
+
"description": "Fallback account profile"
|
|
455
|
+
}
|
|
456
|
+
}
|
|
436
457
|
}
|
|
437
458
|
},
|
|
438
459
|
"required": [
|
|
@@ -541,10 +562,94 @@
|
|
|
541
562
|
"endpoint",
|
|
542
563
|
"id"
|
|
543
564
|
]
|
|
565
|
+
},
|
|
566
|
+
"nickname": {
|
|
567
|
+
"type": [
|
|
568
|
+
"string",
|
|
569
|
+
"null"
|
|
570
|
+
],
|
|
571
|
+
"description": "账号昵称兜底",
|
|
572
|
+
"x-i18n": {
|
|
573
|
+
"en": {
|
|
574
|
+
"description": "Fallback account nickname"
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
},
|
|
578
|
+
"profile": {
|
|
579
|
+
"$ref": "#/$defs/selfProfileFallback",
|
|
580
|
+
"description": "账号资料兜底",
|
|
581
|
+
"x-i18n": {
|
|
582
|
+
"en": {
|
|
583
|
+
"description": "Fallback account profile"
|
|
584
|
+
}
|
|
585
|
+
}
|
|
544
586
|
}
|
|
545
587
|
},
|
|
546
588
|
"required": [
|
|
547
589
|
"master",
|
|
548
590
|
"endpoints"
|
|
549
|
-
]
|
|
591
|
+
],
|
|
592
|
+
"$defs": {
|
|
593
|
+
"selfProfileFallback": {
|
|
594
|
+
"type": "object",
|
|
595
|
+
"additionalProperties": false,
|
|
596
|
+
"properties": {
|
|
597
|
+
"gender": {
|
|
598
|
+
"type": [
|
|
599
|
+
"string",
|
|
600
|
+
"null"
|
|
601
|
+
],
|
|
602
|
+
"enum": [
|
|
603
|
+
"male",
|
|
604
|
+
"female",
|
|
605
|
+
"other",
|
|
606
|
+
"unknown",
|
|
607
|
+
null
|
|
608
|
+
]
|
|
609
|
+
},
|
|
610
|
+
"age": {
|
|
611
|
+
"type": [
|
|
612
|
+
"integer",
|
|
613
|
+
"null"
|
|
614
|
+
],
|
|
615
|
+
"minimum": 0
|
|
616
|
+
},
|
|
617
|
+
"birthday": {
|
|
618
|
+
"type": [
|
|
619
|
+
"string",
|
|
620
|
+
"null"
|
|
621
|
+
],
|
|
622
|
+
"pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$"
|
|
623
|
+
},
|
|
624
|
+
"signature": {
|
|
625
|
+
"type": [
|
|
626
|
+
"string",
|
|
627
|
+
"null"
|
|
628
|
+
]
|
|
629
|
+
},
|
|
630
|
+
"avatar": {
|
|
631
|
+
"type": [
|
|
632
|
+
"string",
|
|
633
|
+
"null"
|
|
634
|
+
],
|
|
635
|
+
"pattern": "^(https?://[^\\s/?#]+(?:[/?#].*)?|)$"
|
|
636
|
+
},
|
|
637
|
+
"interests": {
|
|
638
|
+
"type": [
|
|
639
|
+
"array",
|
|
640
|
+
"null"
|
|
641
|
+
],
|
|
642
|
+
"items": {
|
|
643
|
+
"type": "string"
|
|
644
|
+
}
|
|
645
|
+
},
|
|
646
|
+
"bio": {
|
|
647
|
+
"type": [
|
|
648
|
+
"string",
|
|
649
|
+
"null"
|
|
650
|
+
]
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
}
|
|
550
655
|
}
|
package/src/endpoint.ts
CHANGED
|
@@ -33,7 +33,8 @@ import {
|
|
|
33
33
|
type EndpointGroupNoticeRequest,
|
|
34
34
|
} from 'zhin.js/adapter';
|
|
35
35
|
import type { EndpointContentPort } from '@zhin.js/adapter';
|
|
36
|
-
import { receiveOneBotLikeSideEvent
|
|
36
|
+
import { receiveOneBotLikeSideEvent } from '@zhin.js/adapter-onebot-events';
|
|
37
|
+
import { buildSystem, toCanonicalSegments, type LoginAssist, type LoginAssistEndpointIdentity, type PendingLoginTask } from '@zhin.js/core';
|
|
37
38
|
import { EndpointDeliveryError, type EndpointSendPart, type EndpointSendResult, type MessageRef } from '@zhin.js/im-contract';
|
|
38
39
|
import { formatCompact, getAdapterLogger, truncatePreview } from '@zhin.js/logger';
|
|
39
40
|
import type { CapabilityId } from 'zhin.js';
|
|
@@ -77,8 +78,10 @@ import {
|
|
|
77
78
|
} from './protocol.js';
|
|
78
79
|
import type { SystemMessage as IcqqSystemMessage } from './types.js';
|
|
79
80
|
import { ManagedIcqqClient, type IcqqBackgroundOperation } from './managed-client.js';
|
|
81
|
+
import { createIcqqActionPorts } from './group-actions.js';
|
|
80
82
|
import { inspectGroupMember, moderateGroupMember } from './group-moderation.js';
|
|
81
83
|
import { inspectGroupMemberProfile, inspectGroupScene } from './group-scene.js';
|
|
84
|
+
import { createIcqqSelfProfile } from './self-profile.js';
|
|
82
85
|
|
|
83
86
|
export interface IcqqEndpointOptions {
|
|
84
87
|
readonly id: CapabilityId;
|
|
@@ -188,6 +191,7 @@ export class IcqqEndpoint extends ManagedIcqqClient implements Endpoint {
|
|
|
188
191
|
throw new Error('QQ did not confirm the group notice');
|
|
189
192
|
}
|
|
190
193
|
},
|
|
194
|
+
...createIcqqActionPorts(() => this),
|
|
191
195
|
});
|
|
192
196
|
|
|
193
197
|
readonly #control: EndpointControl = Object.freeze<EndpointControl>({
|
|
@@ -239,7 +243,7 @@ export class IcqqEndpoint extends ManagedIcqqClient implements Endpoint {
|
|
|
239
243
|
start: signal => this.#startFramework(signal), open: () => this.#openFramework(), close: () => this.#closeFramework(), stop: () => this.#stopFramework(),
|
|
240
244
|
send: request => this.#sendFramework(request), get name() { return name(); },
|
|
241
245
|
get transportState() { return state(); },
|
|
242
|
-
management: this.#management, control: this.#control,
|
|
246
|
+
management: this.#management, control: this.#control, profile: createIcqqSelfProfile(this),
|
|
243
247
|
get content() { return content(); },
|
|
244
248
|
});
|
|
245
249
|
this.#contentResolver = new IcqqContentResolver(this);
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import type { Client, MemberInfo } from '@icqqjs/icqq';
|
|
2
|
+
import { createEndpointOperation, type EndpointManagement, type EndpointOperation } from 'zhin.js/adapter';
|
|
3
|
+
|
|
4
|
+
type ActionPorts = Pick<
|
|
5
|
+
EndpointManagement,
|
|
6
|
+
| 'pokeUser'
|
|
7
|
+
| 'unmuteGroupMember'
|
|
8
|
+
| 'muteAllGroupMembers'
|
|
9
|
+
| 'setGroupCard'
|
|
10
|
+
| 'setGroupTitle'
|
|
11
|
+
| 'setGroupEssence'
|
|
12
|
+
| 'inviteGroupMember'
|
|
13
|
+
>;
|
|
14
|
+
|
|
15
|
+
function numericId(value: string): number {
|
|
16
|
+
const id = Number(value);
|
|
17
|
+
if (!/^[1-9]\d*$/.test(value) || !Number.isSafeInteger(id)) throw new Error('Invalid QQ identifier');
|
|
18
|
+
return id;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Uncached member lookup; `pickMember` and the cached roster would report stale roles. */
|
|
22
|
+
async function freshMember(operation: EndpointOperation, client: Client, group: number, user: number): Promise<MemberInfo | undefined> {
|
|
23
|
+
try {
|
|
24
|
+
const member = await operation.prepare(() => client.getGroupMemberInfo(group, user, true));
|
|
25
|
+
return member.user_id === user && member.group_id === group ? member : undefined;
|
|
26
|
+
} catch {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function requireBotRole(operation: EndpointOperation, client: Client, group: number, roles: readonly string[], message: string): Promise<void> {
|
|
32
|
+
const self = await freshMember(operation, client, group, client.uin);
|
|
33
|
+
if (!self || !roles.includes(self.role)) throw new Error(message);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function confirmed(ok: unknown, what: string): void {
|
|
37
|
+
if (ok === false) throw new Error(`QQ did not confirm ${what}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* ADR 0044 action ports of the icqq endpoint. Where the bot's role decides the outcome the role is
|
|
42
|
+
* read fresh from the platform first, so a stale cache can neither allow nor refuse the action.
|
|
43
|
+
* Each request carries its own lifetime: reads are `prepare`, the native effect is `run`.
|
|
44
|
+
*/
|
|
45
|
+
export function createIcqqActionPorts(getClient: () => Client): ActionPorts {
|
|
46
|
+
return {
|
|
47
|
+
async pokeUser({ userId, groupId, signal }) {
|
|
48
|
+
const client = getClient();
|
|
49
|
+
const operation = createEndpointOperation({ signal });
|
|
50
|
+
const user = numericId(userId);
|
|
51
|
+
confirmed(
|
|
52
|
+
groupId === undefined
|
|
53
|
+
? await operation.run(() => client.pickFriend(user).poke(false))
|
|
54
|
+
: await operation.run(() => client.sendGroupPoke(numericId(groupId), user)),
|
|
55
|
+
'poke',
|
|
56
|
+
);
|
|
57
|
+
},
|
|
58
|
+
async unmuteGroupMember({ groupId, userId, signal }) {
|
|
59
|
+
const client = getClient();
|
|
60
|
+
const operation = createEndpointOperation({ signal });
|
|
61
|
+
const group = numericId(groupId);
|
|
62
|
+
await requireBotRole(operation, client, group, ['owner', 'admin'], 'Bot is not a group admin');
|
|
63
|
+
confirmed(await operation.run(() => client.setGroupBan(group, numericId(userId), 0)), 'unmute');
|
|
64
|
+
},
|
|
65
|
+
async muteAllGroupMembers({ groupId, enabled, signal }) {
|
|
66
|
+
const client = getClient();
|
|
67
|
+
const operation = createEndpointOperation({ signal });
|
|
68
|
+
const group = numericId(groupId);
|
|
69
|
+
await requireBotRole(operation, client, group, ['owner', 'admin'], 'Bot is not a group admin');
|
|
70
|
+
confirmed(await operation.run(() => client.setGroupWholeBan(group, enabled)), 'whole-group mute');
|
|
71
|
+
},
|
|
72
|
+
async setGroupCard({ groupId, userId, card, signal }) {
|
|
73
|
+
const client = getClient();
|
|
74
|
+
const operation = createEndpointOperation({ signal });
|
|
75
|
+
const group = numericId(groupId);
|
|
76
|
+
const user = numericId(userId);
|
|
77
|
+
if (user !== client.uin) await requireBotRole(operation, client, group, ['owner', 'admin'], 'Bot is not a group admin');
|
|
78
|
+
confirmed(await operation.run(() => client.setGroupCard(group, user, card)), 'card change');
|
|
79
|
+
},
|
|
80
|
+
async setGroupTitle({ groupId, userId, title, durationSeconds, signal }) {
|
|
81
|
+
const client = getClient();
|
|
82
|
+
const operation = createEndpointOperation({ signal });
|
|
83
|
+
const group = numericId(groupId);
|
|
84
|
+
const user = numericId(userId);
|
|
85
|
+
await requireBotRole(operation, client, group, ['owner'], 'Only the group owner can set special titles');
|
|
86
|
+
if (!await freshMember(operation, client, group, user)) throw new Error('Target is not a group member');
|
|
87
|
+
confirmed(await operation.run(() => client.setGroupSpecialTitle(group, user, title, durationSeconds ?? -1)), 'title change');
|
|
88
|
+
},
|
|
89
|
+
async setGroupEssence({ groupId, messageId, enabled, signal }) {
|
|
90
|
+
const client = getClient();
|
|
91
|
+
const operation = createEndpointOperation({ signal });
|
|
92
|
+
await requireBotRole(operation, client, numericId(groupId), ['owner', 'admin'], 'Bot is not a group admin');
|
|
93
|
+
if (enabled) await operation.run(() => client.setEssenceMessage(messageId));
|
|
94
|
+
else await operation.run(() => client.removeEssenceMessage(messageId));
|
|
95
|
+
},
|
|
96
|
+
async inviteGroupMember({ groupId, userId, signal }) {
|
|
97
|
+
const client = getClient();
|
|
98
|
+
const operation = createEndpointOperation({ signal });
|
|
99
|
+
const group = numericId(groupId);
|
|
100
|
+
const user = numericId(userId);
|
|
101
|
+
if (await freshMember(operation, client, group, user)) throw new Error('Target is already a group member');
|
|
102
|
+
confirmed(await operation.run(() => client.inviteFriend(group, user)), 'invitation');
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
}
|
package/src/protocol.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { EndpointProfileConfiguration } from 'zhin.js/adapter';
|
|
1
2
|
import type { MessageElem, Sendable, ShareElem } from '@icqqjs/icqq';
|
|
2
3
|
import { isMediaRef, type ConversationRef } from '@zhin.js/im-contract';
|
|
3
4
|
import { resolveAdapterNotifyTarget, type AdapterNotifyTarget, type ResolvedAdapterNotifyTarget } from 'zhin.js/adapter';
|
|
@@ -232,7 +233,7 @@ export const Actions = {
|
|
|
232
233
|
} as const;
|
|
233
234
|
|
|
234
235
|
/** One expanded ICQQ endpoint configuration produced by AdapterIndex. */
|
|
235
|
-
export interface IcqqEndpointConfig {
|
|
236
|
+
export interface IcqqEndpointConfig extends EndpointProfileConfiguration {
|
|
236
237
|
readonly id: string;
|
|
237
238
|
readonly autoReconnect?: boolean;
|
|
238
239
|
readonly outboundMedia?: 'file' | 'base64';
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { Client } from '@icqqjs/icqq';
|
|
2
|
+
import { createEndpointOperation, type EndpointProfile } from 'zhin.js/adapter';
|
|
3
|
+
import type { SelfProfile } from '@zhin.js/im-contract';
|
|
4
|
+
|
|
5
|
+
type IcqqProfileClient = Pick<Client,
|
|
6
|
+
'uin' | 'nickname' | 'isOnline' | 'getProfile' | 'setNickname' | 'setGender' |
|
|
7
|
+
'setSignature' | 'setDescription' | 'setAvatar'>;
|
|
8
|
+
|
|
9
|
+
/** ICQQ owns the platform facts; config fallbacks and call leases belong to Adapter. */
|
|
10
|
+
export function createIcqqSelfProfile(client: IcqqProfileClient): EndpointProfile {
|
|
11
|
+
return Object.freeze<EndpointProfile>({
|
|
12
|
+
writableFields: Object.freeze(['nickname', 'gender', 'signature', 'bio', 'avatar']),
|
|
13
|
+
get current() { return client.isOnline() ? { nickname: client.nickname } : {}; },
|
|
14
|
+
async read({ signal }) {
|
|
15
|
+
const raw = await createEndpointOperation({ signal }).prepare(() => client.getProfile(client.uin));
|
|
16
|
+
const profile: SelfProfile = {
|
|
17
|
+
...(typeof raw.nickname === 'string' ? { nickname: raw.nickname } : {}),
|
|
18
|
+
...(raw.sex === 'male' || raw.sex === 'female' || raw.sex === 'unknown' ? { gender: raw.sex } : {}),
|
|
19
|
+
...(typeof raw.age === 'number' && Number.isFinite(raw.age) ? { age: raw.age } : {}),
|
|
20
|
+
...(typeof raw.signature === 'string' ? { signature: raw.signature } : {}),
|
|
21
|
+
...(typeof raw.avatar?.url === 'string' ? { avatar: raw.avatar.url } : {}),
|
|
22
|
+
};
|
|
23
|
+
return profile;
|
|
24
|
+
},
|
|
25
|
+
async write({ change, signal }) {
|
|
26
|
+
const operation = createEndpointOperation({ signal });
|
|
27
|
+
let accepted: boolean;
|
|
28
|
+
switch (change.field) {
|
|
29
|
+
case 'nickname': accepted = await operation.run(() => client.setNickname(change.value)); break;
|
|
30
|
+
case 'gender': {
|
|
31
|
+
if (change.value === 'other') return { status: 'rejected', message: 'ICQQ supports male, female or unknown gender' };
|
|
32
|
+
const gender = change.value === 'male' ? 1 : change.value === 'female' ? 2 : 0;
|
|
33
|
+
accepted = await operation.run(() => client.setGender(gender));
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
case 'signature': accepted = await operation.run(() => client.setSignature(change.value)); break;
|
|
37
|
+
case 'bio': accepted = await operation.run(() => client.setDescription(change.value)); break;
|
|
38
|
+
case 'avatar': {
|
|
39
|
+
if (change.value.kind === 'file') {
|
|
40
|
+
return { status: 'rejected', message: 'ICQQ avatar requires resolved image bytes, URL or local path' };
|
|
41
|
+
}
|
|
42
|
+
const image = change.value.kind === 'base64' ? Buffer.from(change.value.value, 'base64') : change.value.value;
|
|
43
|
+
await operation.run(() => client.setAvatar(image));
|
|
44
|
+
// The input is not the platform CDN URL; it must not become an observed avatar.
|
|
45
|
+
return { status: 'updated' };
|
|
46
|
+
}
|
|
47
|
+
default: return { status: 'rejected', message: `ICQQ cannot safely update ${change.field}` };
|
|
48
|
+
}
|
|
49
|
+
if (accepted && change.field === 'nickname') client.nickname = change.value;
|
|
50
|
+
return accepted
|
|
51
|
+
? { status: 'updated', profile: { [change.field]: change.value } }
|
|
52
|
+
: { status: 'rejected', message: `ICQQ rejected the ${change.field} change` };
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { SensitiveExecCommandRule } from '@zhin.js/agent';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The `icqq` command-line client can kick members, move money and delete files. Under an `allowlist`
|
|
5
|
+
* exec policy its non-sensitive subcommands run freely, while these need owner approval (ADR 0044:
|
|
6
|
+
* this knowledge lives with the adapter, the exec policy only reads the registered rule).
|
|
7
|
+
*/
|
|
8
|
+
export const ICQQ_SENSITIVE_SUBCOMMAND_REGEXES: readonly RegExp[] = Object.freeze([
|
|
9
|
+
/\bicqq\s+friend\s+(?:delete|block|move)\b/i,
|
|
10
|
+
/\bicqq\s+group\s+(?:kick|mute|set|admin|announce|transfer|quit|dissolve|invite)\b/i,
|
|
11
|
+
/\bicqq\s+(?:request|wallet|pay|recall)\b/i,
|
|
12
|
+
/\bicqq\s+file\s+(?:upload|delete)\b/i,
|
|
13
|
+
/\bicqq\s+gfs\b.*\b(?:rm|delete|del)\b/i,
|
|
14
|
+
]);
|
|
15
|
+
|
|
16
|
+
export const icqqSensitiveExecRule: SensitiveExecCommandRule = Object.freeze({
|
|
17
|
+
command: 'icqq',
|
|
18
|
+
sensitive: ICQQ_SENSITIVE_SUBCOMMAND_REGEXES,
|
|
19
|
+
});
|