@alemonjs/qq-bot 2.1.30 → 2.1.32
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 +14 -0
- package/dist/assets/index.css +1 -1
- package/dist/assets/index.js +3 -3
- package/dist/index.html +1 -1
- package/lib/config.d.ts +8 -0
- package/lib/config.js +23 -1
- package/lib/desktop.js +8 -1
- package/lib/index.js +5 -8
- package/lib/index.webhook.js +3 -0
- package/lib/index.websoket.d.ts +2 -0
- package/lib/index.websoket.js +18 -16
- package/lib/message/group/C2C_MESSAGE_CREATE.d.ts +7 -0
- package/lib/message/group/GROUP_AT_MESSAGE_CREATE.d.ts +7 -0
- package/lib/message/group/GROUP_MESSAGE_CREATE.d.ts +7 -0
- package/lib/register.d.ts +10 -1
- package/lib/register.js +260 -9
- package/lib/sdk/api.d.ts +58 -0
- package/lib/sdk/api.js +252 -14
- package/lib/sdk/client.webhook.js +14 -14
- package/lib/sdk/client.websoket.d.ts +2 -1
- package/lib/sdk/client.websoket.js +321 -110
- package/lib/sdk/config.d.ts +3 -1
- package/lib/sdk/config.js +6 -2
- package/lib/sdk/registry.d.ts +25 -0
- package/lib/sdk/registry.js +116 -0
- package/lib/sdk/session.d.ts +18 -0
- package/lib/sdk/session.js +56 -0
- package/lib/sdk/typing.d.ts +2 -0
- package/lib/sends.js +25 -4
- package/package.json +5 -3
package/dist/index.html
CHANGED
package/lib/config.d.ts
CHANGED
|
@@ -6,7 +6,15 @@ export type Options = {
|
|
|
6
6
|
master_id?: string[];
|
|
7
7
|
markdownToText?: boolean;
|
|
8
8
|
hideUnsupported?: boolean | number;
|
|
9
|
+
bots?: Record<string, Omit<sdkOptions, 'app_id'> & {
|
|
10
|
+
app_id?: string;
|
|
11
|
+
}>;
|
|
12
|
+
default_bot?: string;
|
|
9
13
|
} & sdkOptions;
|
|
10
14
|
export declare const getQQBotConfig: () => Options;
|
|
15
|
+
export declare const getQQBotBots: () => {
|
|
16
|
+
bots: Map<string, sdkOptions>;
|
|
17
|
+
defaultBot?: string;
|
|
18
|
+
};
|
|
11
19
|
export declare const getIdentity: (UserId: string) => readonly [boolean, string];
|
|
12
20
|
export declare const getMaster: (UserId: string) => readonly [boolean, string];
|
package/lib/config.js
CHANGED
|
@@ -8,6 +8,28 @@ const getQQBotConfig = () => {
|
|
|
8
8
|
const bagValue = value[platformFullName] || {};
|
|
9
9
|
return { ...commonValue, ...bagValue };
|
|
10
10
|
};
|
|
11
|
+
const getQQBotBots = () => {
|
|
12
|
+
const config = getQQBotConfig();
|
|
13
|
+
const { bots: configuredBots, default_bot, master_id: _masterId, master_key: _masterKey, markdownToText: _markdown, hideUnsupported: _hide, ...common } = config;
|
|
14
|
+
const bots = new Map();
|
|
15
|
+
if (configuredBots && Object.keys(configuredBots).length) {
|
|
16
|
+
for (const [botId, value] of Object.entries(configuredBots)) {
|
|
17
|
+
if (value.app_id && value.app_id !== botId)
|
|
18
|
+
throw new Error(`qq-bot bots.${botId}.app_id must match its map key`);
|
|
19
|
+
if (!value.secret)
|
|
20
|
+
throw new Error(`qq-bot bots.${botId}.secret is required`);
|
|
21
|
+
bots.set(botId, { ...common, ...value, app_id: botId, secret: value.secret });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
else if (config.app_id && config.secret) {
|
|
25
|
+
bots.set(config.app_id, common);
|
|
26
|
+
}
|
|
27
|
+
if (bots.size > 1 && !default_bot)
|
|
28
|
+
throw new Error('qq-bot.default_bot is required when multiple bots are configured');
|
|
29
|
+
if (default_bot && !bots.has(default_bot))
|
|
30
|
+
throw new Error(`qq-bot.default_bot ${default_bot} is not configured`);
|
|
31
|
+
return { bots, defaultBot: default_bot || (bots.size === 1 ? bots.keys().next().value : undefined) };
|
|
32
|
+
};
|
|
11
33
|
const getIdentity = (UserId) => {
|
|
12
34
|
const isMasterUser = UserId ? isMaster(UserId, platform) : false;
|
|
13
35
|
const UserKey = createUserHashKey({
|
|
@@ -20,4 +42,4 @@ const getMaster = (UserId) => {
|
|
|
20
42
|
return getIdentity(UserId);
|
|
21
43
|
};
|
|
22
44
|
|
|
23
|
-
export { getIdentity, getMaster, getQQBotConfig, platform, platformFullName };
|
|
45
|
+
export { getIdentity, getMaster, getQQBotBots, getQQBotConfig, platform, platformFullName };
|
package/lib/desktop.js
CHANGED
|
@@ -2,6 +2,7 @@ import { readFileSync } from 'fs';
|
|
|
2
2
|
import { dirname, join } from 'path';
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
4
|
import { getConfig, getConfigValue } from 'alemonjs';
|
|
5
|
+
import { getQQBotRegistry } from './index.websoket.js';
|
|
5
6
|
|
|
6
7
|
const __dirname$1 = dirname(fileURLToPath(import.meta.url));
|
|
7
8
|
const activate = context => {
|
|
@@ -22,7 +23,7 @@ const activate = context => {
|
|
|
22
23
|
webView.onMessage(data => {
|
|
23
24
|
try {
|
|
24
25
|
if (data.type === 'qq-bot.form.save') {
|
|
25
|
-
const db = data.data;
|
|
26
|
+
const { bots_json: _botsJson, ...db } = data.data;
|
|
26
27
|
const config = getConfig();
|
|
27
28
|
const value = config.value ?? {};
|
|
28
29
|
value['qq-bot'] = {
|
|
@@ -43,6 +44,12 @@ const activate = context => {
|
|
|
43
44
|
data: config['qq-bot'] ?? {}
|
|
44
45
|
});
|
|
45
46
|
}
|
|
47
|
+
else if (data.type === 'qq-bot.status') {
|
|
48
|
+
webView.postMessage({
|
|
49
|
+
type: 'qq-bot.status',
|
|
50
|
+
data: getQQBotRegistry()?.getConnectionStatus() ?? { Platform: 'qq-bot', state: 'stopped', bots: [] }
|
|
51
|
+
});
|
|
52
|
+
}
|
|
46
53
|
}
|
|
47
54
|
catch (e) {
|
|
48
55
|
console.error(e);
|
package/lib/index.js
CHANGED
|
@@ -1,16 +1,13 @@
|
|
|
1
|
-
import { definePlatform
|
|
1
|
+
import { definePlatform } from 'alemonjs';
|
|
2
2
|
import { start } from './index.webhook.js';
|
|
3
3
|
import { start as start$1 } from './index.websoket.js';
|
|
4
|
-
import {
|
|
4
|
+
import { getQQBotConfig } from './config.js';
|
|
5
|
+
export { platform } from './config.js';
|
|
5
6
|
export { useClient, useMode, useValue } from './hook.js';
|
|
6
7
|
export { QQBotAPI as API } from './sdk/api.js';
|
|
7
8
|
|
|
8
9
|
const main = () => {
|
|
9
|
-
|
|
10
|
-
if (!value) {
|
|
11
|
-
value = {};
|
|
12
|
-
}
|
|
13
|
-
const config = value[platform];
|
|
10
|
+
const config = getQQBotConfig();
|
|
14
11
|
if (config?.route || config?.port || config?.ws) {
|
|
15
12
|
start();
|
|
16
13
|
}
|
|
@@ -20,4 +17,4 @@ const main = () => {
|
|
|
20
17
|
};
|
|
21
18
|
var index = definePlatform({ main });
|
|
22
19
|
|
|
23
|
-
export { index as default
|
|
20
|
+
export { index as default };
|
package/lib/index.webhook.js
CHANGED
|
@@ -4,6 +4,9 @@ import { getQQBotConfig } from './config.js';
|
|
|
4
4
|
|
|
5
5
|
const start = () => {
|
|
6
6
|
const config = getQQBotConfig();
|
|
7
|
+
if (config.bots && Object.keys(config.bots).length > 1) {
|
|
8
|
+
throw new Error('qq-bot.bots is only supported by WebSocket mode; remove route/port/ws to use the QQ WebSocket gateway');
|
|
9
|
+
}
|
|
7
10
|
const { master_id: _, master_key: __, ...cfgConfig } = config;
|
|
8
11
|
const client = new QQBotClient({
|
|
9
12
|
...cfgConfig
|
package/lib/index.websoket.d.ts
CHANGED
package/lib/index.websoket.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { getQQBotConfig } from './config.js';
|
|
1
|
+
import { getQQBotConfig, getQQBotBots } from './config.js';
|
|
2
|
+
import { QQBotRegistry } from './sdk/registry.js';
|
|
4
3
|
|
|
4
|
+
let activeRegistry;
|
|
5
|
+
const getQQBotRegistry = () => activeRegistry;
|
|
5
6
|
const start = () => {
|
|
6
7
|
const config = getQQBotConfig();
|
|
8
|
+
const { bots, defaultBot } = getQQBotBots();
|
|
9
|
+
const registry = new QQBotRegistry(defaultBot);
|
|
10
|
+
activeRegistry = registry;
|
|
7
11
|
const notPrivateIntents = [
|
|
8
12
|
'GUILDS',
|
|
9
13
|
'GUILD_MEMBERS',
|
|
@@ -28,18 +32,16 @@ const start = () => {
|
|
|
28
32
|
else {
|
|
29
33
|
intents.push(...notPrivateIntents, ...isGroupIntents, ...pubIntents);
|
|
30
34
|
}
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
void client.connect(config?.gatewayURL);
|
|
42
|
-
register(client);
|
|
35
|
+
for (const [botId, botConfig] of bots) {
|
|
36
|
+
const client = registry.add(botId, {
|
|
37
|
+
...botConfig,
|
|
38
|
+
intents: botConfig.intents || intents,
|
|
39
|
+
is_private: botConfig.is_private ?? false,
|
|
40
|
+
sandbox: botConfig.sandbox ?? false,
|
|
41
|
+
shard: botConfig.shard ?? [0, 1]
|
|
42
|
+
});
|
|
43
|
+
void client.connect(botConfig.gatewayURL);
|
|
44
|
+
}
|
|
43
45
|
};
|
|
44
46
|
|
|
45
|
-
export { start };
|
|
47
|
+
export { getQQBotRegistry, start };
|
package/lib/register.d.ts
CHANGED
|
@@ -1,2 +1,11 @@
|
|
|
1
|
+
import { cbpPlatform } from 'alemonjs';
|
|
1
2
|
import { QQBotClients } from './sdk/client.websoket';
|
|
2
|
-
export
|
|
3
|
+
export type QQBotRegistration = {
|
|
4
|
+
onAction: (data: any, consume: (result: any[]) => void) => Promise<void>;
|
|
5
|
+
onApi: (data: any, consume: (result: any[]) => void) => Promise<void>;
|
|
6
|
+
};
|
|
7
|
+
export declare const register: (client: QQBotClients, options?: {
|
|
8
|
+
botId?: string;
|
|
9
|
+
cbp?: ReturnType<typeof cbpPlatform>;
|
|
10
|
+
bindActions?: boolean;
|
|
11
|
+
}) => QQBotRegistration;
|
package/lib/register.js
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
import { cbpPlatform, FormatEvent, ResultCode, createResult } from 'alemonjs';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { stat, readFile } from 'node:fs/promises';
|
|
4
|
+
import { basename } from 'node:path';
|
|
2
5
|
import { GROUP_AT_MESSAGE_CREATE, C2C_MESSAGE_CREATE, DIRECT_MESSAGE_CREATE, AT_MESSAGE_CREATE, MESSAGE_CREATE } from './sends.js';
|
|
3
6
|
import { getQQBotConfig, platform, getMaster, getIdentity } from './config.js';
|
|
4
7
|
|
|
5
|
-
const register = (client) => {
|
|
8
|
+
const register = (client, options) => {
|
|
9
|
+
const MAX_MEDIA_SIZE = 100 * 1024 * 1024;
|
|
6
10
|
const config = getQQBotConfig();
|
|
7
|
-
const botId = String(config?.app_id ?? '');
|
|
11
|
+
const botId = String(options?.botId ?? config?.app_id ?? '');
|
|
8
12
|
const port = process.env?.port || config?.port || 17117;
|
|
9
13
|
const url = `ws://127.0.0.1:${port}`;
|
|
10
|
-
const cbp = cbpPlatform(url);
|
|
14
|
+
const cbp = options?.cbp ?? cbpPlatform(url);
|
|
11
15
|
const createUserAvatarURL = (authorId) => {
|
|
12
|
-
return `https://q.qlogo.cn/qqapp/${
|
|
16
|
+
return `https://q.qlogo.cn/qqapp/${botId}/${authorId}/640`;
|
|
13
17
|
};
|
|
14
18
|
const getGroupMessageMeta = (event) => {
|
|
15
19
|
const author = event?.author;
|
|
@@ -39,6 +43,122 @@ const register = (client) => {
|
|
|
39
43
|
auditTime
|
|
40
44
|
};
|
|
41
45
|
};
|
|
46
|
+
const getMediaItems = (attachments) => {
|
|
47
|
+
return (attachments || []).flatMap(attachment => {
|
|
48
|
+
if (!attachment?.url) {
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
const mimeType = attachment.content_type || '';
|
|
52
|
+
const Type = mimeType.startsWith('image/')
|
|
53
|
+
? 'image'
|
|
54
|
+
: mimeType.startsWith('audio/')
|
|
55
|
+
? 'audio'
|
|
56
|
+
: mimeType.startsWith('video/')
|
|
57
|
+
? 'video'
|
|
58
|
+
: 'file';
|
|
59
|
+
return [
|
|
60
|
+
{
|
|
61
|
+
Type,
|
|
62
|
+
Url: attachment.url,
|
|
63
|
+
...(attachment.filename && { FileName: attachment.filename }),
|
|
64
|
+
...(typeof attachment.size === 'number' && { FileSize: attachment.size }),
|
|
65
|
+
...(mimeType && { MimeType: mimeType })
|
|
66
|
+
}
|
|
67
|
+
];
|
|
68
|
+
});
|
|
69
|
+
};
|
|
70
|
+
const normalizeTarget = (target) => {
|
|
71
|
+
if (!target?.targetId || !target.scope) {
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
return target;
|
|
75
|
+
};
|
|
76
|
+
const validateTargetBot = (target) => {
|
|
77
|
+
return !target.BotId || target.BotId === botId;
|
|
78
|
+
};
|
|
79
|
+
const mediaCache = new Map();
|
|
80
|
+
const mediaType = (type) => (type === 'image' ? 1 : type === 'video' ? 2 : type === 'audio' ? 3 : 4);
|
|
81
|
+
const prepareMedia = async (target, params) => {
|
|
82
|
+
const sources = [params?.url, params?.data, params?.filePath, params?.fileId].filter(value => value !== undefined && value !== '').length;
|
|
83
|
+
if (sources !== 1) {
|
|
84
|
+
throw new Error('Provide exactly one media source: url, data, filePath, or fileId');
|
|
85
|
+
}
|
|
86
|
+
if (params.fileId) {
|
|
87
|
+
return { fileId: String(params.fileId), reused: true };
|
|
88
|
+
}
|
|
89
|
+
let data = params.data;
|
|
90
|
+
let buffer;
|
|
91
|
+
let filePath;
|
|
92
|
+
let fileSize;
|
|
93
|
+
let hashes;
|
|
94
|
+
let name = params.name;
|
|
95
|
+
let hashSource = String(params.url || '');
|
|
96
|
+
if (params.filePath) {
|
|
97
|
+
const metadata = await stat(String(params.filePath));
|
|
98
|
+
if (!metadata.isFile()) {
|
|
99
|
+
throw new Error('media filePath must point to a regular file');
|
|
100
|
+
}
|
|
101
|
+
if (metadata.size > MAX_MEDIA_SIZE) {
|
|
102
|
+
throw new Error('QQ media files must not exceed 100 MiB');
|
|
103
|
+
}
|
|
104
|
+
filePath = String(params.filePath);
|
|
105
|
+
fileSize = metadata.size;
|
|
106
|
+
hashes = await client.getMediaFileHashes(filePath);
|
|
107
|
+
hashSource = hashes.sha256;
|
|
108
|
+
name ||= basename(filePath);
|
|
109
|
+
if (fileSize < 5 * 1024 * 1024) {
|
|
110
|
+
buffer = await readFile(filePath);
|
|
111
|
+
data = `base64://${buffer.toString('base64')}`;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
else if (data) {
|
|
115
|
+
hashSource = data;
|
|
116
|
+
const value = data.replace(/^base64:\/\//, '');
|
|
117
|
+
buffer = Buffer.from(value, 'base64');
|
|
118
|
+
if (buffer.length > MAX_MEDIA_SIZE) {
|
|
119
|
+
throw new Error('QQ media files must not exceed 100 MiB');
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
const key = [botId, target.scope, target.targetId, params.type, createHash('sha256').update(hashSource).digest('hex')].join(':');
|
|
123
|
+
const cached = mediaCache.get(key);
|
|
124
|
+
if (cached && (!cached.expiresAt || cached.expiresAt > Date.now())) {
|
|
125
|
+
return { fileId: cached.fileId, expiresAt: cached.expiresAt, reused: true };
|
|
126
|
+
}
|
|
127
|
+
return { key, url: params.url, data, buffer, filePath, fileSize, hashes, name, reused: false };
|
|
128
|
+
};
|
|
129
|
+
const uploadMedia = async (target, params) => {
|
|
130
|
+
if (!validateTargetBot(target)) {
|
|
131
|
+
throw new Error(`BotId ${target.BotId} is not active`);
|
|
132
|
+
}
|
|
133
|
+
if (target.scope !== 'group' && target.scope !== 'c2c') {
|
|
134
|
+
throw new Error('QQ media upload only supports group and c2c targets');
|
|
135
|
+
}
|
|
136
|
+
const prepared = await prepareMedia(target, params);
|
|
137
|
+
if (prepared.fileId) {
|
|
138
|
+
return prepared;
|
|
139
|
+
}
|
|
140
|
+
const fileType = mediaType(params.type);
|
|
141
|
+
const value = prepared.filePath && prepared.fileSize && prepared.fileSize >= 5 * 1024 * 1024
|
|
142
|
+
? await client.postChunkedRichMedia({
|
|
143
|
+
scope: target.scope,
|
|
144
|
+
targetId: target.targetId,
|
|
145
|
+
fileType,
|
|
146
|
+
filePath: prepared.filePath,
|
|
147
|
+
size: prepared.fileSize,
|
|
148
|
+
hashes: prepared.hashes,
|
|
149
|
+
name: prepared.name
|
|
150
|
+
})
|
|
151
|
+
: prepared.buffer && prepared.buffer.length >= 5 * 1024 * 1024
|
|
152
|
+
? await client.postChunkedRichMedia({ scope: target.scope, targetId: target.targetId, fileType, data: prepared.buffer, name: prepared.name })
|
|
153
|
+
: target.scope === 'group'
|
|
154
|
+
? await client.postRichMediaByGroup(target.targetId, { file_type: fileType, url: prepared.url, file_data: prepared.data, srv_send_msg: false })
|
|
155
|
+
: await client.postRichMediaByUser(target.targetId, { file_type: fileType, url: prepared.url, file_data: prepared.data, srv_send_msg: false });
|
|
156
|
+
const expiresAt = value.ttl ? Date.now() + value.ttl * 1000 : undefined;
|
|
157
|
+
if (prepared.key) {
|
|
158
|
+
mediaCache.set(prepared.key, { fileId: value.file_info, expiresAt });
|
|
159
|
+
}
|
|
160
|
+
return { fileId: value.file_info, expiresAt, reused: false };
|
|
161
|
+
};
|
|
42
162
|
const createUserMeta = (UserId, extra = {}) => {
|
|
43
163
|
const [IsMaster, UserKey] = getIdentity(UserId);
|
|
44
164
|
return {
|
|
@@ -85,6 +205,7 @@ const register = (client) => {
|
|
|
85
205
|
})
|
|
86
206
|
.addMessage({ MessageId: meta.messageId })
|
|
87
207
|
.addText({ MessageText: msg?.trim() })
|
|
208
|
+
.addMedia({ MessageMedia: getMediaItems(event.attachments) })
|
|
88
209
|
.addOpen({ OpenId: meta.openId })
|
|
89
210
|
.add({ tag: 'GROUP_MESSAGE_CREATE' }).value);
|
|
90
211
|
});
|
|
@@ -128,6 +249,7 @@ const register = (client) => {
|
|
|
128
249
|
})
|
|
129
250
|
.addMessage({ MessageId: meta.messageId })
|
|
130
251
|
.addText({ MessageText: msg?.trim() })
|
|
252
|
+
.addMedia({ MessageMedia: getMediaItems(event.attachments) })
|
|
131
253
|
.addOpen({ OpenId: meta.openId })
|
|
132
254
|
.add({ tag: 'GROUP_AT_MESSAGE_CREATE' }).value);
|
|
133
255
|
});
|
|
@@ -147,6 +269,7 @@ const register = (client) => {
|
|
|
147
269
|
})
|
|
148
270
|
.addMessage({ MessageId: event.id })
|
|
149
271
|
.addText({ MessageText: event.content?.trim() })
|
|
272
|
+
.addMedia({ MessageMedia: getMediaItems(event.attachments) })
|
|
150
273
|
.addOpen({ OpenId: `C2C:${event.author.user_openid}` })
|
|
151
274
|
.add({ tag: 'C2C_MESSAGE_CREATE' }).value);
|
|
152
275
|
});
|
|
@@ -170,6 +293,7 @@ const register = (client) => {
|
|
|
170
293
|
})
|
|
171
294
|
.addMessage({ MessageId: event.id })
|
|
172
295
|
.addText({ MessageText: msg?.trim() })
|
|
296
|
+
.addMedia({ MessageMedia: getMediaItems(event.attachments) })
|
|
173
297
|
.addOpen({ OpenId: `DIRECT:${event.guild_id}` })
|
|
174
298
|
.add({ tag: 'DIRECT_MESSAGE_CREATE' }).value);
|
|
175
299
|
});
|
|
@@ -195,6 +319,7 @@ const register = (client) => {
|
|
|
195
319
|
})
|
|
196
320
|
.addMessage({ MessageId: event.id })
|
|
197
321
|
.addText({ MessageText: msg?.trim() })
|
|
322
|
+
.addMedia({ MessageMedia: getMediaItems(event.attachments) })
|
|
198
323
|
.addOpen({ OpenId: `DIRECT:${event.guild_id}` })
|
|
199
324
|
.add({ tag: 'AT_MESSAGE_CREATE' }).value);
|
|
200
325
|
});
|
|
@@ -238,6 +363,7 @@ const register = (client) => {
|
|
|
238
363
|
})
|
|
239
364
|
.addMessage({ MessageId: event.id })
|
|
240
365
|
.addText({ MessageText: msg?.trim() })
|
|
366
|
+
.addMedia({ MessageMedia: getMediaItems(event.attachments) })
|
|
241
367
|
.addOpen({ OpenId: `DIRECT:${event.guild_id}` })
|
|
242
368
|
.add({ tag: 'MESSAGE_CREATE' }).value);
|
|
243
369
|
});
|
|
@@ -260,6 +386,11 @@ const register = (client) => {
|
|
|
260
386
|
})
|
|
261
387
|
.addMessage({ MessageId: `INTERACTION_CREATE:${event.id}` })
|
|
262
388
|
.addText({ MessageText: MessageText })
|
|
389
|
+
.addInteraction({
|
|
390
|
+
InteractionId: event.id,
|
|
391
|
+
InteractionData: JSON.stringify(event.data.resolved),
|
|
392
|
+
Target: { scope: 'group', targetId: event.group_openid, BotId: botId }
|
|
393
|
+
})
|
|
263
394
|
.addOpen({ OpenId: `C2C:${event.group_member_openid}` })
|
|
264
395
|
.add({ tag: 'INTERACTION_CREATE_GROUP' }).value;
|
|
265
396
|
cbp.send(e);
|
|
@@ -280,6 +411,11 @@ const register = (client) => {
|
|
|
280
411
|
})
|
|
281
412
|
.addMessage({ MessageId: event.id })
|
|
282
413
|
.addText({ MessageText: MessageText })
|
|
414
|
+
.addInteraction({
|
|
415
|
+
InteractionId: event.id,
|
|
416
|
+
InteractionData: JSON.stringify(event.data.resolved),
|
|
417
|
+
Target: { scope: 'c2c', targetId: event.user_openid, BotId: botId }
|
|
418
|
+
})
|
|
283
419
|
.addOpen({ OpenId: `C2C:${event.user_openid}` })
|
|
284
420
|
.add({ tag: 'INTERACTION_CREATE_C2C' }).value;
|
|
285
421
|
cbp.send(e);
|
|
@@ -296,6 +432,11 @@ const register = (client) => {
|
|
|
296
432
|
.addUser({ UserId: event.data.resolved.user_id, UserKey, UserAvatar: UserAvatar, IsMaster: isMaster, IsBot: false })
|
|
297
433
|
.addMessage({ MessageId: event.data.resolved.message_id })
|
|
298
434
|
.addText({ MessageText: MessageText })
|
|
435
|
+
.addInteraction({
|
|
436
|
+
InteractionId: event.id,
|
|
437
|
+
InteractionData: JSON.stringify(event.data.resolved),
|
|
438
|
+
Target: { scope: 'channel', targetId: event.channel_id, BotId: botId }
|
|
439
|
+
})
|
|
299
440
|
.addOpen({ OpenId: `DIRECT:${event.guild_id}` })
|
|
300
441
|
.add({ tag: 'INTERACTION_CREATE_GUILD' }).value;
|
|
301
442
|
cbp.send(e);
|
|
@@ -483,6 +624,24 @@ const register = (client) => {
|
|
|
483
624
|
const api = {
|
|
484
625
|
active: {
|
|
485
626
|
send: {
|
|
627
|
+
target: async (target, val, replyId) => {
|
|
628
|
+
if (!validateTargetBot(target)) {
|
|
629
|
+
return [createResult(ResultCode.FailParams, `BotId ${target.BotId} is not active`, null)];
|
|
630
|
+
}
|
|
631
|
+
if (target.scope === 'group') {
|
|
632
|
+
return GROUP_AT_MESSAGE_CREATE(client, { ChannelId: target.targetId, MessageId: replyId }, val);
|
|
633
|
+
}
|
|
634
|
+
if (target.scope === 'c2c') {
|
|
635
|
+
return C2C_MESSAGE_CREATE(client, { UserId: target.targetId, MessageId: replyId }, val);
|
|
636
|
+
}
|
|
637
|
+
if (target.scope === 'channel') {
|
|
638
|
+
return AT_MESSAGE_CREATE(client, { ChannelId: target.targetId, MessageId: replyId }, val);
|
|
639
|
+
}
|
|
640
|
+
if (target.scope === 'direct') {
|
|
641
|
+
return DIRECT_MESSAGE_CREATE(client, { UserId: target.targetId, MessageId: replyId }, val);
|
|
642
|
+
}
|
|
643
|
+
return [createResult(ResultCode.FailParams, `Unsupported target scope: ${target.scope}`, null)];
|
|
644
|
+
},
|
|
486
645
|
channel: async (SpaceId, val) => {
|
|
487
646
|
if (/^GUILD:/.test(SpaceId)) {
|
|
488
647
|
const id = SpaceId.replace('GUILD:', '');
|
|
@@ -619,11 +778,44 @@ const register = (client) => {
|
|
|
619
778
|
const res = await api.active.send.user(userId, paramFormat);
|
|
620
779
|
consume(res);
|
|
621
780
|
}
|
|
781
|
+
else if (data.action === 'message.send.target') {
|
|
782
|
+
const target = normalizeTarget(data.payload.target);
|
|
783
|
+
if (!target) {
|
|
784
|
+
consume([createResult(ResultCode.FailParams, 'message.send.target 缺少 target', null)]);
|
|
785
|
+
return;
|
|
786
|
+
}
|
|
787
|
+
const res = await api.active.send.target(target, data.payload.params?.format || [], data.payload.params?.replyId);
|
|
788
|
+
consume(res);
|
|
789
|
+
}
|
|
622
790
|
else if (data.action === 'message.delete') {
|
|
623
|
-
const channelId = data.payload.ChannelId;
|
|
624
791
|
const messageId = data.payload.MessageId;
|
|
792
|
+
const params = data.payload.params ?? {};
|
|
793
|
+
const target = normalizeTarget(data.payload.target);
|
|
794
|
+
if (target && !validateTargetBot(target)) {
|
|
795
|
+
consume([createResult(ResultCode.FailParams, `BotId ${target.BotId} is not active`, null)]);
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
const scope = target?.scope ?? params.scope ?? params.messageType;
|
|
799
|
+
const request = scope === 'group'
|
|
800
|
+
? client.grouMessageDelte(String(target?.targetId ?? params.groupId ?? data.payload.GroupId ?? data.payload.ChannelId), messageId)
|
|
801
|
+
: scope === 'user' || scope === 'c2c'
|
|
802
|
+
? client.userMessageDelete(String(target?.targetId ?? params.userId ?? data.payload.UserId), messageId)
|
|
803
|
+
: scope === 'direct'
|
|
804
|
+
? client.dmsMessageDelete(String(target?.targetId ?? params.guildId ?? data.payload.GuildId ?? data.payload.ChannelId), messageId)
|
|
805
|
+
: client.channelsMessagesDelete(target?.targetId ?? data.payload.ChannelId, messageId);
|
|
806
|
+
const res = await request.then(r => createResult(ResultCode.Ok, data.action, r)).catch(err => createResult(ResultCode.Fail, data.action, err));
|
|
807
|
+
consume([res]);
|
|
808
|
+
}
|
|
809
|
+
else if (data.action === 'interaction.ack') {
|
|
810
|
+
const params = data.payload.params ?? {};
|
|
811
|
+
const interactionId = String(params.interactionId ?? data.payload.InteractionId ?? '');
|
|
812
|
+
const mode = data.payload.target?.scope === 'channel' || data.payload.target?.scope === 'direct' || params.mode === 'guild' ? 'guild' : 'group';
|
|
813
|
+
if (!interactionId) {
|
|
814
|
+
consume([createResult(ResultCode.Fail, 'interaction.ack 缺少 InteractionId', null)]);
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
625
817
|
const res = await client
|
|
626
|
-
.
|
|
818
|
+
.interactionResponse(mode, interactionId, params.code)
|
|
627
819
|
.then(r => createResult(ResultCode.Ok, data.action, r))
|
|
628
820
|
.catch(err => createResult(ResultCode.Fail, data.action, err));
|
|
629
821
|
consume([res]);
|
|
@@ -870,7 +1062,63 @@ const register = (client) => {
|
|
|
870
1062
|
consume([res]);
|
|
871
1063
|
}
|
|
872
1064
|
else if (data.action === 'media.upload') {
|
|
873
|
-
|
|
1065
|
+
const target = normalizeTarget(data.payload.target);
|
|
1066
|
+
if (!target) {
|
|
1067
|
+
consume([createResult(ResultCode.Warn, 'QQ media.upload requires target', null)]);
|
|
1068
|
+
return;
|
|
1069
|
+
}
|
|
1070
|
+
const res = await uploadMedia(target, data.payload.params || {})
|
|
1071
|
+
.then(value => createResult(ResultCode.Ok, data.action, value))
|
|
1072
|
+
.catch(err => createResult(ResultCode.Fail, data.action, err));
|
|
1073
|
+
consume([res]);
|
|
1074
|
+
}
|
|
1075
|
+
else if (data.action === 'media.send') {
|
|
1076
|
+
const target = normalizeTarget(data.payload.target);
|
|
1077
|
+
if (!target) {
|
|
1078
|
+
consume([createResult(ResultCode.FailParams, 'media.send 缺少 target', null)]);
|
|
1079
|
+
return;
|
|
1080
|
+
}
|
|
1081
|
+
if (!validateTargetBot(target)) {
|
|
1082
|
+
consume([createResult(ResultCode.FailParams, `BotId ${target.BotId} is not active`, null)]);
|
|
1083
|
+
return;
|
|
1084
|
+
}
|
|
1085
|
+
const params = data.payload.params || {};
|
|
1086
|
+
if (params.fileId) {
|
|
1087
|
+
const send = target.scope === 'group'
|
|
1088
|
+
? client.groupOpenMessages(target.targetId, { msg_type: 7, content: params.content || '', media: { file_info: params.fileId } })
|
|
1089
|
+
: target.scope === 'c2c'
|
|
1090
|
+
? client.usersOpenMessages(target.targetId, { msg_type: 7, content: params.content || '', media: { file_info: params.fileId } })
|
|
1091
|
+
: null;
|
|
1092
|
+
if (!send) {
|
|
1093
|
+
consume([createResult(ResultCode.Warn, 'QQ media.send only supports group and c2c targets', null)]);
|
|
1094
|
+
return;
|
|
1095
|
+
}
|
|
1096
|
+
const res = await send
|
|
1097
|
+
.then(value => createResult(ResultCode.Ok, data.action, { id: value.id }))
|
|
1098
|
+
.catch(err => createResult(ResultCode.Fail, data.action, err));
|
|
1099
|
+
consume([res]);
|
|
1100
|
+
return;
|
|
1101
|
+
}
|
|
1102
|
+
const upload = await uploadMedia(target, params)
|
|
1103
|
+
.then(value => value.fileId)
|
|
1104
|
+
.catch(error => {
|
|
1105
|
+
consume([createResult(ResultCode.Fail, data.action, error)]);
|
|
1106
|
+
return null;
|
|
1107
|
+
});
|
|
1108
|
+
if (!upload) {
|
|
1109
|
+
return;
|
|
1110
|
+
}
|
|
1111
|
+
await onactions({ action: 'media.send', payload: { target, params: { ...params, fileId: upload, url: undefined, data: undefined, filePath: undefined } } }, consume);
|
|
1112
|
+
}
|
|
1113
|
+
else if (data.action === 'connection.status') {
|
|
1114
|
+
const status = client.getConnectionStatus();
|
|
1115
|
+
consume([
|
|
1116
|
+
createResult(ResultCode.Ok, data.action, {
|
|
1117
|
+
Platform: platform,
|
|
1118
|
+
state: status.state,
|
|
1119
|
+
bots: [{ BotId: botId, ...status }]
|
|
1120
|
+
})
|
|
1121
|
+
]);
|
|
874
1122
|
}
|
|
875
1123
|
else if (data.action === 'permission.get') {
|
|
876
1124
|
const res = await client
|
|
@@ -920,7 +1168,6 @@ const register = (client) => {
|
|
|
920
1168
|
consume([createResult(ResultCode.Fail, '请求失败', error)]);
|
|
921
1169
|
}
|
|
922
1170
|
};
|
|
923
|
-
cbp.onactions((data, consume) => void onactions(data, consume));
|
|
924
1171
|
const onapis = async (data, consume) => {
|
|
925
1172
|
const key = data.payload?.key;
|
|
926
1173
|
const params = data.payload?.params;
|
|
@@ -947,7 +1194,11 @@ const register = (client) => {
|
|
|
947
1194
|
consume([createResult(ResultCode.Fail, '请求失败', error)]);
|
|
948
1195
|
}
|
|
949
1196
|
};
|
|
950
|
-
|
|
1197
|
+
if (options?.bindActions !== false) {
|
|
1198
|
+
cbp.onactions((data, consume) => void onactions(data, consume));
|
|
1199
|
+
cbp.onapis((data, consume) => void onapis(data, consume));
|
|
1200
|
+
}
|
|
1201
|
+
return { onAction: onactions, onApi: onapis };
|
|
951
1202
|
};
|
|
952
1203
|
|
|
953
1204
|
export { register };
|