@qihoo/tuitui-openclaw-channel 1.0.5 → 1.0.6
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/package.json +1 -11
- package/src/channel.ts +81 -306
- package/src/types.ts +16 -22
- package/src/utils.ts +105 -31
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qihoo/tuitui-openclaw-channel",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"maintainers": [
|
|
5
5
|
{
|
|
6
6
|
"name": "huzunjie",
|
|
@@ -25,16 +25,6 @@
|
|
|
25
25
|
"extensions": [
|
|
26
26
|
"./index.ts"
|
|
27
27
|
],
|
|
28
|
-
"channel": {
|
|
29
|
-
"id": "tuitui-openclaw-channel",
|
|
30
|
-
"label": "TuiTui",
|
|
31
|
-
"selectionLabel": "TuiTui",
|
|
32
|
-
"docsPath": "/channels/tuitui",
|
|
33
|
-
"docsLabel": "tuitui",
|
|
34
|
-
"blurb": "TuiTui chat integration.",
|
|
35
|
-
"aliases": [],
|
|
36
|
-
"order": 90
|
|
37
|
-
},
|
|
38
28
|
"install": {
|
|
39
29
|
"npmSpec": "@qihoo/tuitui-openclaw-channel",
|
|
40
30
|
"localPath": "extensions/tuitui",
|
package/src/channel.ts
CHANGED
|
@@ -5,56 +5,49 @@
|
|
|
5
5
|
* Supports single chat (text, image, voice, file) and group chat with @mentions.
|
|
6
6
|
*/
|
|
7
7
|
import WebSocket from 'ws';
|
|
8
|
-
import
|
|
8
|
+
import { z } from 'zod';
|
|
9
9
|
import {
|
|
10
10
|
DEFAULT_ACCOUNT_ID,
|
|
11
11
|
setAccountEnabledInConfigSection,
|
|
12
12
|
deleteAccountFromConfigSection,
|
|
13
|
-
registerPluginHttpRoute,
|
|
14
13
|
buildChannelConfigSchema,
|
|
15
14
|
} from 'openclaw/plugin-sdk';
|
|
16
|
-
import {
|
|
17
|
-
import type {
|
|
18
|
-
TuiTuiInboundMessage,
|
|
19
|
-
TuiTuiOutboundTextMessage,
|
|
20
|
-
TuiTuiOutboundImageMessage,
|
|
21
|
-
TuiTuiOutboundAttachmentMessage,
|
|
22
|
-
} from './types';
|
|
15
|
+
import type { TuiTuiInboundMessage } from './types';
|
|
23
16
|
import {
|
|
24
17
|
CHANNEL_ID,
|
|
25
18
|
CHANNEL_NAME,
|
|
26
|
-
postTuituiMsg,
|
|
27
|
-
uploadFileToTuiTui,
|
|
28
19
|
buildMessageBody,
|
|
29
20
|
tuituiEmojiReaction,
|
|
30
21
|
checkAccount,
|
|
22
|
+
sendTextMsg,
|
|
23
|
+
sendPageMsg,
|
|
24
|
+
sendMediaMsg,
|
|
31
25
|
} from "./utils";
|
|
32
26
|
|
|
33
27
|
function resolveAccount(cfg: any, accountId?: string | null) {
|
|
34
|
-
const channelConfig = cfg?.channels?.[CHANNEL_ID]
|
|
35
|
-
const targetId = accountId
|
|
28
|
+
const channelConfig = cfg?.channels?.[CHANNEL_ID] || {};
|
|
29
|
+
const targetId = accountId || DEFAULT_ACCOUNT_ID;
|
|
36
30
|
const acct = targetId === DEFAULT_ACCOUNT_ID ? channelConfig : channelConfig.accounts?.[targetId];
|
|
37
|
-
|
|
38
31
|
return {
|
|
39
32
|
accountId: targetId,
|
|
40
|
-
enabled: acct?.enabled
|
|
33
|
+
enabled: acct?.enabled || false,
|
|
41
34
|
appId: acct?.appId as string | undefined,
|
|
42
35
|
appSecret: acct?.appSecret as string | undefined,
|
|
43
|
-
dmPolicy: acct?.dmPolicy
|
|
44
|
-
allowFrom: acct?.allowFrom
|
|
36
|
+
dmPolicy: acct?.dmPolicy || 'pairing',
|
|
37
|
+
allowFrom: acct?.allowFrom || [],
|
|
45
38
|
// 群组策略与白名单、群组级覆盖
|
|
46
39
|
groupPolicy: (acct?.groupPolicy as string | undefined) ?? 'allowlist',
|
|
47
|
-
groupAllowFrom:
|
|
40
|
+
groupAllowFrom: acct.groupAllowFrom || [],
|
|
48
41
|
groups: (acct?.groups as Record<string, { requireMention?: boolean, shouldReply?: boolean }> | undefined) ?? {},
|
|
49
42
|
};
|
|
50
43
|
}
|
|
51
44
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
45
|
+
const isConfigured = (account: any)=> Boolean(
|
|
46
|
+
String(account?.appId ?? '').trim() &&
|
|
47
|
+
String(account?.appSecret ?? '').trim()
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
const isToGroup = (chatId: string) => /^\d+$/.test(chatId);
|
|
58
51
|
|
|
59
52
|
export function createTuiTuiChannelPlugin(apiRuntime: any) {
|
|
60
53
|
return {
|
|
@@ -71,7 +64,7 @@ export function createTuiTuiChannelPlugin(apiRuntime: any) {
|
|
|
71
64
|
},
|
|
72
65
|
|
|
73
66
|
capabilities: {
|
|
74
|
-
chatTypes: [
|
|
67
|
+
chatTypes: ['direct' as const, 'group' as const],
|
|
75
68
|
media: true,
|
|
76
69
|
threads: false,
|
|
77
70
|
reactions: false,
|
|
@@ -141,16 +134,12 @@ export function createTuiTuiChannelPlugin(apiRuntime: any) {
|
|
|
141
134
|
const base = cfg?.channels?.[CHANNEL_ID];
|
|
142
135
|
if (!base) return [];
|
|
143
136
|
const ids = new Set<string>();
|
|
144
|
-
if (base.enabled
|
|
145
|
-
|
|
146
|
-
}
|
|
147
|
-
if (base.accounts) {
|
|
148
|
-
for (const k in base.accounts) ids.add(k);
|
|
149
|
-
}
|
|
137
|
+
if (base.enabled) ids.add(DEFAULT_ACCOUNT_ID);
|
|
138
|
+
if (base.accounts) for (const k in base.accounts) ids.add(k);
|
|
150
139
|
return Array.from(ids);
|
|
151
140
|
},
|
|
152
141
|
|
|
153
|
-
resolveAccount
|
|
142
|
+
resolveAccount,
|
|
154
143
|
|
|
155
144
|
defaultAccountId: (_cfg: any) => DEFAULT_ACCOUNT_ID,
|
|
156
145
|
|
|
@@ -181,10 +170,10 @@ export function createTuiTuiChannelPlugin(apiRuntime: any) {
|
|
|
181
170
|
},
|
|
182
171
|
|
|
183
172
|
deleteAccount: ({ cfg, accountId }: any) => {
|
|
184
|
-
|
|
173
|
+
return accountId === DEFAULT_ACCOUNT_ID
|
|
185
174
|
// For default account, we don't delete the entire config
|
|
186
175
|
// Instead, we disable it and clear sensitive fields
|
|
187
|
-
|
|
176
|
+
? {
|
|
188
177
|
...cfg,
|
|
189
178
|
channels: {
|
|
190
179
|
...cfg.channels,
|
|
@@ -195,23 +184,22 @@ export function createTuiTuiChannelPlugin(apiRuntime: any) {
|
|
|
195
184
|
appSecret: undefined,
|
|
196
185
|
},
|
|
197
186
|
},
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
});
|
|
187
|
+
}
|
|
188
|
+
// For named accounts, use the standard delete function
|
|
189
|
+
: deleteAccountFromConfigSection({
|
|
190
|
+
cfg,
|
|
191
|
+
sectionKey: CHANNEL_ID,
|
|
192
|
+
accountId,
|
|
193
|
+
clearBaseFields: [
|
|
194
|
+
'appId',
|
|
195
|
+
'appSecret',
|
|
196
|
+
'dmPolicy',
|
|
197
|
+
'allowFrom',
|
|
198
|
+
'groupPolicy',
|
|
199
|
+
'groupAllowFrom',
|
|
200
|
+
'groups',
|
|
201
|
+
],
|
|
202
|
+
});
|
|
215
203
|
},
|
|
216
204
|
},
|
|
217
205
|
|
|
@@ -234,12 +222,12 @@ export function createTuiTuiChannelPlugin(apiRuntime: any) {
|
|
|
234
222
|
|
|
235
223
|
security: {
|
|
236
224
|
resolveDmPolicy: ({ cfg, accountId, account }: { cfg: any; accountId?: string | null; account?: any; }) => {
|
|
237
|
-
|
|
238
|
-
const
|
|
225
|
+
account = account || resolveAccount(cfg, accountId);
|
|
226
|
+
const accId = accountId ?? account.accountId ?? DEFAULT_ACCOUNT_ID;
|
|
239
227
|
let allowFromPath =`channels.${CHANNEL_ID}.`;
|
|
240
|
-
if (
|
|
228
|
+
if (accId !== DEFAULT_ACCOUNT_ID) allowFromPath += `accounts.${accId}.`;
|
|
241
229
|
|
|
242
|
-
const policy =
|
|
230
|
+
const policy = account.dmPolicy ?? 'pairing';
|
|
243
231
|
// dmPolicy semantics:
|
|
244
232
|
// - open: always allow everyone (["*"]), ignore allowFrom values.
|
|
245
233
|
// - pairing: unknown senders get a pairing code; approvals add to allowFrom store.
|
|
@@ -247,7 +235,7 @@ export function createTuiTuiChannelPlugin(apiRuntime: any) {
|
|
|
247
235
|
// - disabled: block all DMs.
|
|
248
236
|
return {
|
|
249
237
|
policy,
|
|
250
|
-
allowFrom: policy === 'open' ? ['*'] : (
|
|
238
|
+
allowFrom: policy === 'open' ? ['*'] : (account.allowFrom ?? []),
|
|
251
239
|
policyPath: `${allowFromPath}dmPolicy`,
|
|
252
240
|
allowFromPath,
|
|
253
241
|
approveHint: `openclaw pairing approve ${CHANNEL_ID} <code>`,
|
|
@@ -274,144 +262,45 @@ export function createTuiTuiChannelPlugin(apiRuntime: any) {
|
|
|
274
262
|
deliveryMode: 'gateway' as const,
|
|
275
263
|
textChunkLimit: 2000,
|
|
276
264
|
|
|
277
|
-
sendText: async ({ cfg, to, text, accountId, account
|
|
278
|
-
|
|
279
|
-
checkAccount(
|
|
265
|
+
sendText: async ({ cfg, to, text, accountId, account }: any) => {
|
|
266
|
+
account = account || resolveAccount(cfg, accountId);
|
|
267
|
+
checkAccount(account, 'send text');
|
|
280
268
|
|
|
281
|
-
const
|
|
269
|
+
const chatId = String(to || '').trim();
|
|
282
270
|
// Determine if this is a group message based on 'to' being all digits (group) or not (direct)
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
// Format message according to TuiTui's required structure
|
|
286
|
-
const payload: TuiTuiOutboundTextMessage = {
|
|
287
|
-
tousers: isGroup ? [] : [toStr],
|
|
288
|
-
togroups: isGroup ? [toStr] : [],
|
|
289
|
-
at: [],
|
|
290
|
-
msgtype: 'text',
|
|
291
|
-
text: { content: text },
|
|
292
|
-
};
|
|
293
|
-
|
|
294
|
-
log?.info?.(
|
|
295
|
-
`[${CHANNEL_ID}] sendText -
|
|
296
|
-
accountId=${accountId ?? 'default'},
|
|
297
|
-
to=${toStr},
|
|
298
|
-
target_users: [${payload.tousers.join(",")}],
|
|
299
|
-
target_groups: [${payload.togroups.join(",")}],
|
|
300
|
-
isGroup: ${isGroup},
|
|
301
|
-
textLength=${String(text ?? '').length},
|
|
302
|
-
text: ${text}`
|
|
303
|
-
);
|
|
271
|
+
await sendTextMsg(account, chatId, isToGroup(chatId), text);
|
|
304
272
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
return { channel: CHANNEL_ID, messageId: `tt-${Date.now()}`, chatId: toStr };
|
|
273
|
+
return { channel: CHANNEL_ID, messageId: `tuitui-text-${Date.now()}`, chatId };
|
|
308
274
|
},
|
|
309
275
|
|
|
310
276
|
sendCustom: async ({ cfg, to, payload, accountId, account, chatType, groupId, log }: any) => {
|
|
311
|
-
|
|
312
|
-
checkAccount(
|
|
277
|
+
account = account || resolveAccount(cfg, accountId);
|
|
278
|
+
checkAccount(account, 'send custom message');
|
|
313
279
|
|
|
314
280
|
// If it's a page message, we need to construct it
|
|
315
281
|
if (payload?.msgtype !== 'page') {
|
|
316
282
|
throw new Error(`[${CHANNEL_ID}] unsupported custom message type: ${payload?.msgtype}`);
|
|
317
283
|
}
|
|
318
284
|
|
|
319
|
-
const
|
|
285
|
+
const chatId = String(to || '').trim();
|
|
320
286
|
// Determine if this is a group message
|
|
321
287
|
// WORKAROUND: OpenClaw core doesn't pass chatType/groupId to sendPayload.
|
|
322
288
|
// If `to` is a numeric string and chatType/groupId are undefined, assume it's a group.
|
|
323
|
-
const
|
|
324
|
-
|
|
325
|
-
const toGroupId = groupId || (isGroup ? toStr : undefined);
|
|
326
|
-
const toUserId = isGroup ? undefined : toStr;
|
|
327
|
-
|
|
328
|
-
const _payload: any = {
|
|
329
|
-
tousers: payload.tousers || (toUserId ? [toUserId] : []),
|
|
330
|
-
togroups: payload.togroups || (toGroupId ? [toGroupId] : []),
|
|
331
|
-
msgtype: 'page',
|
|
332
|
-
page: { ...payload.page }
|
|
333
|
-
};
|
|
334
|
-
|
|
335
|
-
log?.info?.(
|
|
336
|
-
`[${CHANNEL_ID}] sending page to TuiTui -
|
|
337
|
-
target_users: [${_payload.tousers.join(",")}],
|
|
338
|
-
target_groups: [${_payload.togroups.join(",")}],
|
|
339
|
-
chatType: ${chatType ?? 'direct'},
|
|
340
|
-
account=${accountId ?? 'default'},
|
|
341
|
-
chatType=${chatType ?? 'direct'},
|
|
342
|
-
to=${toStr},
|
|
343
|
-
groupId=${groupId ?? '-'}`,
|
|
344
|
-
);
|
|
345
|
-
|
|
346
|
-
await postTuituiMsg(_account, _payload, 'tuitui.send.page', log);
|
|
289
|
+
const isGroup = chatType === 'group' || !!groupId || (isToGroup(chatId) && !chatType);
|
|
290
|
+
await sendPageMsg(account, chatId, isGroup, payload.page);
|
|
347
291
|
|
|
348
|
-
return { channel: CHANNEL_ID, messageId: `
|
|
292
|
+
return { channel: CHANNEL_ID, messageId: `tuitui-page-${Date.now()}`, chatId };
|
|
349
293
|
},
|
|
350
294
|
|
|
351
295
|
sendMedia: async ({ cfg, to, mediaUrl, accountId, account, log }: any) => {
|
|
352
|
-
|
|
353
|
-
checkAccount(
|
|
296
|
+
account = account || resolveAccount(cfg, accountId);
|
|
297
|
+
checkAccount(account, 'send media');
|
|
354
298
|
|
|
355
|
-
const
|
|
299
|
+
const chatId = String(to || '').trim();
|
|
356
300
|
// Determine if this is a group message based on 'to' being all digits (group) or not (direct)
|
|
357
|
-
|
|
358
|
-
const toGroupId = isGroup ? toStr : undefined;
|
|
359
|
-
const toUserId = isGroup ? undefined : toStr;
|
|
360
|
-
|
|
361
|
-
// Detect image/file by URL pattern first; fallback to upload API type auto-detection when needed.
|
|
362
|
-
// NOTE: relying on `mediaUrl.includes("image")` is too broad and can misclassify files.
|
|
363
|
-
const isImageByExt = /\.(jpg|jpeg|png|gif|webp|bmp|svg)(?:$|[?#])/i.test(mediaUrl);
|
|
364
|
-
const isDataUrlImage = /^data:image\//i.test(mediaUrl);
|
|
365
|
-
const isImage = isImageByExt || isDataUrlImage;
|
|
366
|
-
const uploadType = isImage ? 'image' : 'file';
|
|
367
|
-
|
|
368
|
-
log?.debug?.(
|
|
369
|
-
`[${CHANNEL_ID}] sendMedia classify
|
|
370
|
-
account=${accountId ?? 'default'},
|
|
371
|
-
to=${toStr},
|
|
372
|
-
isGroup=${isGroup},
|
|
373
|
-
mediaUrl=${mediaUrl},
|
|
374
|
-
isImageByExt=${isImageByExt},
|
|
375
|
-
isDataUrlImage=${isDataUrlImage},
|
|
376
|
-
uploadType=${uploadType}`,
|
|
377
|
-
);
|
|
378
|
-
|
|
379
|
-
const media_id = await uploadFileToTuiTui(mediaUrl, _account, uploadType);
|
|
380
|
-
if (!media_id) {
|
|
381
|
-
log?.error?.(`[${CHANNEL_ID}] sendMedia upload failed to get media_id from TuiTui for mediaUrl: ${mediaUrl}`);
|
|
382
|
-
return;
|
|
383
|
-
}
|
|
384
|
-
log?.debug?.(`[${CHANNEL_ID}] sendMedia upload done type=${uploadType} media_id=${media_id}`);
|
|
385
|
-
|
|
386
|
-
// Keep outbound msgtype consistent with upload type.
|
|
387
|
-
// 群消息:togroups 填群ID,tousers 是空数组;私聊:tousers 填 to;均不设置 at
|
|
388
|
-
const payload: any = {
|
|
389
|
-
tousers: toUserId ? [toUserId] : [],
|
|
390
|
-
togroups: toGroupId ? [toGroupId] : [],
|
|
391
|
-
msgtype: uploadType === 'image' ? 'image' : 'attachment',
|
|
392
|
-
image: { media_id },
|
|
393
|
-
};
|
|
301
|
+
await sendMediaMsg(account, chatId, isToGroup(chatId), mediaUrl);
|
|
394
302
|
|
|
395
|
-
|
|
396
|
-
`[${CHANNEL_ID}] sending media to TuiTui -
|
|
397
|
-
target_users: [${payload.tousers.join(',')}],
|
|
398
|
-
target_groups: [${payload.togroups.join(',')}],
|
|
399
|
-
type: ${payload.msgtype},
|
|
400
|
-
isGroup=${isGroup},
|
|
401
|
-
msgtype=${payload.msgtype},
|
|
402
|
-
account=${accountId ?? 'default'},
|
|
403
|
-
to=${toStr},
|
|
404
|
-
isGroup=${isGroup},
|
|
405
|
-
payload=${JSON.stringify(payload)},
|
|
406
|
-
toGroupId=${toGroupId},
|
|
407
|
-
toUserId=${toUserId},
|
|
408
|
-
uploadType=${uploadType},
|
|
409
|
-
media_id=${media_id}`
|
|
410
|
-
);
|
|
411
|
-
|
|
412
|
-
await postTuituiMsg(_account, payload, 'tuitui.send.media', log);
|
|
413
|
-
|
|
414
|
-
return { channel: CHANNEL_ID, messageId: `tt-${Date.now()}`, chatId: toStr };
|
|
303
|
+
return { channel: CHANNEL_ID, messageId: `tuitui-media-${Date.now()}`, chatId };
|
|
415
304
|
},
|
|
416
305
|
},
|
|
417
306
|
|
|
@@ -610,16 +499,7 @@ export function createTuiTuiChannelPlugin(apiRuntime: any) {
|
|
|
610
499
|
}
|
|
611
500
|
|
|
612
501
|
if (!normalizedGroupAllowFrom.includes(String(groupId))) {
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
const payload: TuiTuiOutboundTextMessage = {
|
|
616
|
-
tousers: [],
|
|
617
|
-
togroups: [groupId],
|
|
618
|
-
at: [],
|
|
619
|
-
msgtype: "text",
|
|
620
|
-
text: { content: `当前openclaw群聊策略为白名单,需要主人在群白名单(Group Allow From)增加当前群ID:\n${groupId}` },
|
|
621
|
-
};
|
|
622
|
-
await postTuituiMsg(account, payload, 'tuitui.groupPolicy.reply', log);
|
|
502
|
+
await sendTextMsg(account, groupId, true, `当前openclaw群聊策略为白名单,需要主人在群白名单(Group Allow From)增加当前群ID:\n${groupId}`, 'tuitui.groupPolicy.reply');
|
|
623
503
|
return;
|
|
624
504
|
}
|
|
625
505
|
|
|
@@ -696,21 +576,11 @@ export function createTuiTuiChannelPlugin(apiRuntime: any) {
|
|
|
696
576
|
apiRuntime?.channel?.pairing?.buildPairingReply?.({
|
|
697
577
|
channel: CHANNEL_ID,
|
|
698
578
|
code: req.code,
|
|
699
|
-
}) ?? '
|
|
579
|
+
}) ?? '需要进行配对。请让机器人所有者进行批准。';
|
|
700
580
|
|
|
701
581
|
// tousers 使用 userAccount(推推用户账号)
|
|
702
582
|
const toUname = String(userAccount || '').trim();
|
|
703
|
-
|
|
704
|
-
`[${CHANNEL_ID}] pairing.reply target=${toUname}`,
|
|
705
|
-
);
|
|
706
|
-
const payload: TuiTuiOutboundTextMessage = {
|
|
707
|
-
tousers: toUname ? [toUname] : [],
|
|
708
|
-
togroups: [],
|
|
709
|
-
at: [],
|
|
710
|
-
msgtype: 'text',
|
|
711
|
-
text: { content: replyText },
|
|
712
|
-
};
|
|
713
|
-
await postTuituiMsg(account, payload, 'tuitui.pairing.reply', log);
|
|
583
|
+
await sendTextMsg(account, toUname, false, replyText, 'tuitui.pairing.reply');
|
|
714
584
|
}
|
|
715
585
|
} catch (err) {
|
|
716
586
|
log?.warn?.(`[${CHANNEL_ID}] pairing flow failed for ${senderForPolicy}: ${String(err)}` );
|
|
@@ -778,7 +648,6 @@ export function createTuiTuiChannelPlugin(apiRuntime: any) {
|
|
|
778
648
|
if (replyToId) msgCtx.ReplyToId = replyToId;
|
|
779
649
|
|
|
780
650
|
// Dispatch via the SDK's buffered block dispatcher
|
|
781
|
-
let sentCount = 0;
|
|
782
651
|
if (!apiRuntime) {
|
|
783
652
|
log?.error?.(`[${CHANNEL_ID}] TuiTuiRuntime error,未能发送回复。`);
|
|
784
653
|
return;
|
|
@@ -800,8 +669,6 @@ export function createTuiTuiChannelPlugin(apiRuntime: any) {
|
|
|
800
669
|
[key: string]: any;
|
|
801
670
|
};
|
|
802
671
|
}) => {
|
|
803
|
-
// mark that we received a deliver call (attempt to send reply)
|
|
804
|
-
sentCount++;
|
|
805
672
|
|
|
806
673
|
// 如果设置了 suppressReply 标志,则不发送回复
|
|
807
674
|
if (suppressReply) {
|
|
@@ -809,125 +676,33 @@ export function createTuiTuiChannelPlugin(apiRuntime: any) {
|
|
|
809
676
|
return;
|
|
810
677
|
}
|
|
811
678
|
|
|
679
|
+
const chatTarget = chatTypeIsGroup ? groupId : userAccount;
|
|
812
680
|
// Handle custom messages (e.g., page messages)
|
|
813
681
|
if (payload.custom) {
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
const pagePayload: any = {
|
|
820
|
-
tousers: customPayload.tousers || (chatTypeIsDirect && userAccount ? [userAccount] : []),
|
|
821
|
-
togroups: customPayload.togroups || (chatTypeIsGroup && groupId ? [groupId] : []),
|
|
822
|
-
msgtype: 'page',
|
|
823
|
-
page: customPayload.page,
|
|
824
|
-
};
|
|
825
|
-
|
|
826
|
-
log?.info?.(
|
|
827
|
-
`[${CHANNEL_ID}] sending page reply to TuiTui -
|
|
828
|
-
target_users: [${pagePayload.tousers.join(',')}],
|
|
829
|
-
target_groups: [${pagePayload.togroups.join(',')}]
|
|
830
|
-
chatType=${chatType},
|
|
831
|
-
tousers=${JSON.stringify(pagePayload.tousers)},
|
|
832
|
-
togroups=${JSON.stringify(pagePayload.togroups)},
|
|
833
|
-
title=${String(pagePayload?.page?.title ?? '')}`,
|
|
834
|
-
);
|
|
835
|
-
|
|
836
|
-
await postTuituiMsg(account, pagePayload, 'tuitui.deliver.page', log);
|
|
837
|
-
} else {
|
|
838
|
-
log?.warn?.(`[${CHANNEL_ID}] Unsupported custom message type: ${customPayload.msgtype}`);
|
|
839
|
-
}
|
|
840
|
-
} catch (e) {
|
|
841
|
-
log?.error?.(`[${CHANNEL_ID}] Error sending custom reply to TuiTui: ${e}`);
|
|
682
|
+
const { msgtype, page, tousers, togroups } = payload.custom;
|
|
683
|
+
// Handle page message type
|
|
684
|
+
if (msgtype === 'page' && page) {
|
|
685
|
+
await sendPageMsg(account, chatTarget, chatTypeIsGroup, page, 'tuitui.deliver.page', tousers, togroups);
|
|
686
|
+
return;
|
|
842
687
|
}
|
|
688
|
+
log?.warn?.(`[${CHANNEL_ID}] Unsupported custom message type: ${msgtype}`);
|
|
843
689
|
return;
|
|
844
690
|
}
|
|
845
|
-
|
|
846
691
|
if (payload.mediaUrl || payload.mediaUrls?.length) {
|
|
847
692
|
// Handle media messages
|
|
848
693
|
const mediaUrl = payload.mediaUrl || payload.mediaUrls?.[0];
|
|
849
694
|
if (mediaUrl) {
|
|
850
|
-
|
|
851
|
-
checkAccount(account, 'send media');
|
|
852
|
-
|
|
853
|
-
// Check if mediaUrl looks like an image
|
|
854
|
-
const isImage = /\.(jpg|jpeg|png|gif|webp|bmp)$/i.test(mediaUrl);
|
|
855
|
-
|
|
856
|
-
const media_id = await uploadFileToTuiTui(mediaUrl, account, isImage ? 'image' : 'file');
|
|
857
|
-
if (!media_id) {
|
|
858
|
-
log?.error?.(`[${CHANNEL_ID}] sendMedia upload failed to get media_id from TuiTui for mediaUrl: ${mediaUrl}`);
|
|
859
|
-
return;
|
|
860
|
-
}
|
|
861
|
-
|
|
862
|
-
// Build outbound message based on chat type
|
|
863
|
-
// 群消息:togroups 填群ID,at 是 userAccount 的字符串值,tousers 是空数组
|
|
864
|
-
// 私聊消息:tousers 填 userAccount,at 不存在,togroups 不存在
|
|
865
|
-
const _baseOutboundMsg = {
|
|
866
|
-
tousers: chatTypeIsGroup ? [] : userAccount ? [userAccount] : [],
|
|
867
|
-
togroups: chatTypeIsGroup && groupId ? [groupId] : [],
|
|
868
|
-
at: chatTypeIsGroup && userAccount ? userAccount : undefined,
|
|
869
|
-
};
|
|
870
|
-
const outboundMsg: TuiTuiOutboundImageMessage | TuiTuiOutboundAttachmentMessage =
|
|
871
|
-
isImage
|
|
872
|
-
? { ..._baseOutboundMsg, msgtype: 'image', image: { media_id } }
|
|
873
|
-
: { ..._baseOutboundMsg, msgtype: 'attachment', attachment: { media_id } };
|
|
874
|
-
|
|
875
|
-
log?.info?.(
|
|
876
|
-
`[${CHANNEL_ID}] sending media reply to TuiTui -
|
|
877
|
-
target_users: [${outboundMsg?.tousers?.join(",")}],
|
|
878
|
-
target_groups: [${outboundMsg?.togroups?.join(",")}],
|
|
879
|
-
type: ${outboundMsg.msgtype},
|
|
880
|
-
to=${JSON.stringify(outboundMsg.tousers)},
|
|
881
|
-
group=${JSON.stringify(outboundMsg.togroups)},
|
|
882
|
-
msgtype=${outboundMsg.msgtype},
|
|
883
|
-
chatType=${chatType},
|
|
884
|
-
userAccount=${userAccount},
|
|
885
|
-
groupId=${groupId},
|
|
886
|
-
isImage=${isImage},
|
|
887
|
-
outboundMsg.tousers=${JSON.stringify(outboundMsg.tousers)},
|
|
888
|
-
outboundMsg.togroups=${JSON.stringify(outboundMsg.togroups)},
|
|
889
|
-
outboundMsg.at=${outboundMsg.at}`
|
|
890
|
-
);
|
|
891
|
-
|
|
892
|
-
await postTuituiMsg(account, outboundMsg, 'tuitui.deliver.media', log);
|
|
893
|
-
} catch (e) {
|
|
894
|
-
log?.error?.(`[${CHANNEL_ID}] Error sending media reply to TuiTui: ${e}`);
|
|
895
|
-
}
|
|
896
|
-
}
|
|
897
|
-
} else if (payload.text || payload.body) {
|
|
898
|
-
// Handle text messages
|
|
899
|
-
const replyText = payload?.text ?? payload?.body;
|
|
900
|
-
if (replyText) {
|
|
901
|
-
try {
|
|
902
|
-
// Build outbound message based on chat type
|
|
903
|
-
// tousers 使用 userAccount(推推用户账号)
|
|
904
|
-
// at 使用 userAccount(群聊 @ 用户)
|
|
905
|
-
// 群消息回复时不再使用 reference_msgid 避免并发时引用错乱,依靠 @ 来提醒用户
|
|
906
|
-
const outboundMsg: TuiTuiOutboundTextMessage = {
|
|
907
|
-
tousers: chatTypeIsDirect && userAccount ? [userAccount] : [],
|
|
908
|
-
togroups: chatTypeIsGroup && groupId ? [groupId] : [],
|
|
909
|
-
at: chatTypeIsGroup && userAccount ? [userAccount] : [],
|
|
910
|
-
msgtype: 'text',
|
|
911
|
-
text: { content: replyText },
|
|
912
|
-
};
|
|
913
|
-
|
|
914
|
-
log?.info?.(
|
|
915
|
-
`[${CHANNEL_ID}] sending text reply to TuiTui -
|
|
916
|
-
target_users: [${outboundMsg.tousers.join(',')}],
|
|
917
|
-
target_groups: [${outboundMsg.togroups.join(',')}]
|
|
918
|
-
chatType=${chatType},
|
|
919
|
-
tousers=${JSON.stringify(outboundMsg.tousers)},
|
|
920
|
-
togroups=${JSON.stringify(outboundMsg.togroups)},
|
|
921
|
-
at=${JSON.stringify((outboundMsg as any).at ?? [])},
|
|
922
|
-
userAccount=${String(userAccount)},
|
|
923
|
-
groupId=${String(groupId)}`,
|
|
924
|
-
);
|
|
925
|
-
|
|
926
|
-
await postTuituiMsg(account, outboundMsg, 'tuitui.deliver.text', log);
|
|
927
|
-
} catch (e) {
|
|
928
|
-
log?.error?.(`[${CHANNEL_ID}] Error sending reply to TuiTui: ${e}`);
|
|
929
|
-
}
|
|
695
|
+
await sendMediaMsg(account, chatTarget, chatTypeIsGroup, mediaUrl, 'tuitui.deliver.media');
|
|
930
696
|
}
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
699
|
+
// Handle text messages
|
|
700
|
+
const replyText = payload?.text || payload?.body;
|
|
701
|
+
if (replyText) {
|
|
702
|
+
// at 使用 userAccount(群聊 @ 用户)
|
|
703
|
+
const atList = chatTypeIsGroup && userAccount ? [userAccount] : [];
|
|
704
|
+
// 群消息回复时不再使用 reference_msgid 避免并发时引用错乱,依靠 @ 来提醒用户
|
|
705
|
+
await sendTextMsg(account, chatTarget, chatTypeIsGroup, replyText, 'tuitui.deliver.text', atList);
|
|
931
706
|
}
|
|
932
707
|
},
|
|
933
708
|
onReplyStart: () => {
|
package/src/types.ts
CHANGED
|
@@ -64,7 +64,7 @@ export interface TuiTuiOutboundLinkMessage {
|
|
|
64
64
|
export interface TuiTuiOutboundImageMessage {
|
|
65
65
|
tousers?: string[];
|
|
66
66
|
togroups?: string[];
|
|
67
|
-
at?: string;
|
|
67
|
+
at?: string[];
|
|
68
68
|
msgtype: 'image';
|
|
69
69
|
image: { media_id: string };
|
|
70
70
|
}
|
|
@@ -72,37 +72,31 @@ export interface TuiTuiOutboundImageMessage {
|
|
|
72
72
|
export interface TuiTuiOutboundAttachmentMessage {
|
|
73
73
|
tousers?: string[];
|
|
74
74
|
togroups?: string[];
|
|
75
|
-
at?: string;
|
|
75
|
+
at?: string[];
|
|
76
76
|
msgtype: 'attachment';
|
|
77
77
|
attachment: { media_id: string };
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
export interface TuiTuiOutboundPageMessagePage {
|
|
81
|
+
title: string;
|
|
82
|
+
summary?: string;
|
|
83
|
+
content: string;
|
|
84
|
+
image?: string;
|
|
85
|
+
format?: 'html';
|
|
86
|
+
privilege?: 'specific' | 'scope' | 'corp' | 'any';
|
|
87
|
+
delims_left?: string;
|
|
88
|
+
delims_right?: string;
|
|
89
|
+
kv?: Record<string, string>;
|
|
90
|
+
default_value?: string;
|
|
91
|
+
debug?: boolean;
|
|
92
|
+
}
|
|
80
93
|
export interface TuiTuiOutboundPageMessage {
|
|
81
94
|
tousers: string[];
|
|
82
95
|
togroups: string[];
|
|
83
96
|
msgtype: 'page';
|
|
84
|
-
page:
|
|
85
|
-
title: string;
|
|
86
|
-
summary?: string;
|
|
87
|
-
content: string;
|
|
88
|
-
image?: string;
|
|
89
|
-
format?: 'html';
|
|
90
|
-
privilege?: 'specific' | 'scope' | 'corp' | 'any';
|
|
91
|
-
delims_left?: string;
|
|
92
|
-
delims_right?: string;
|
|
93
|
-
kv?: Record<string, string>;
|
|
94
|
-
default_value?: string;
|
|
95
|
-
debug?: boolean;
|
|
96
|
-
};
|
|
97
|
+
page: TuiTuiOutboundPageMessagePage;
|
|
97
98
|
}
|
|
98
99
|
|
|
99
|
-
export type TuiTuiOutboundMessage =
|
|
100
|
-
| TuiTuiOutboundTextMessage
|
|
101
|
-
| TuiTuiOutboundLinkMessage
|
|
102
|
-
| TuiTuiOutboundImageMessage
|
|
103
|
-
| TuiTuiOutboundAttachmentMessage
|
|
104
|
-
| TuiTuiOutboundPageMessage;
|
|
105
|
-
|
|
106
100
|
export interface TuiTuiMediaUploadResponse {
|
|
107
101
|
errcode: number;
|
|
108
102
|
errmsg: string;
|
package/src/utils.ts
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
|
-
import { createHash } from 'node:crypto';
|
|
2
1
|
import { readFileSync, existsSync, statSync } from 'node:fs';
|
|
3
|
-
import type { IncomingMessage } from 'node:http';
|
|
4
2
|
import { basename } from 'node:path';
|
|
5
3
|
import { fetchWithSsrFGuard } from 'openclaw/plugin-sdk';
|
|
6
|
-
import type {
|
|
4
|
+
import type {
|
|
5
|
+
TuiTuiMessageData,
|
|
6
|
+
TuiTuiMediaUploadResponse,
|
|
7
|
+
TuiTuiSingleEmojiReactionTarget,
|
|
8
|
+
TuiTuiGroupEmojiReactionTarget,
|
|
9
|
+
TuiTuiOutboundTextMessage,
|
|
10
|
+
TuiTuiOutboundPageMessage,
|
|
11
|
+
TuiTuiOutboundPageMessagePage,
|
|
12
|
+
TuiTuiOutboundImageMessage,
|
|
13
|
+
TuiTuiOutboundAttachmentMessage
|
|
14
|
+
} from './types';
|
|
7
15
|
|
|
8
16
|
/* 一些常量配置 */
|
|
9
17
|
export const CHANNEL_ID = 'tuitui';
|
|
@@ -41,11 +49,11 @@ function _fetch(opts: any): Promise<any> {
|
|
|
41
49
|
return fetchWithSsrFGuard({
|
|
42
50
|
//url: fileSrc,
|
|
43
51
|
policy: TUITUI_SSRF_POLICY,
|
|
44
|
-
//
|
|
52
|
+
//auditCtx: "tuitui.media.download",
|
|
45
53
|
...opts,
|
|
46
54
|
})
|
|
47
55
|
}
|
|
48
|
-
function _fetchJson(url: string, json: any,
|
|
56
|
+
function _fetchJson(url: string, json: any, auditCtx: string): Promise<any> {
|
|
49
57
|
return _fetch({
|
|
50
58
|
url,
|
|
51
59
|
init: {
|
|
@@ -53,42 +61,31 @@ function _fetchJson(url: string, json: any, auditContext: string): Promise<any>
|
|
|
53
61
|
headers: { 'Content-Type': 'application/json' },
|
|
54
62
|
body: JSON.stringify(json),
|
|
55
63
|
},
|
|
56
|
-
|
|
64
|
+
auditCtx,
|
|
57
65
|
});
|
|
58
66
|
}
|
|
59
|
-
export async function postTuituiMsg(account: any, json: any,
|
|
67
|
+
export async function postTuituiMsg(account: any, json: any, auditCtx: string): Promise<any> {
|
|
60
68
|
const { appId: appid, appSecret: secret } = account;
|
|
61
69
|
const { response, release } = await _fetchJson(
|
|
62
70
|
addParams2Url('https://im.live.360.cn:8282/robot/message/custom/send', { appid, secret }),
|
|
63
71
|
json,
|
|
64
|
-
|
|
72
|
+
auditCtx,
|
|
65
73
|
);
|
|
66
74
|
try {
|
|
67
75
|
const bodyText = await response.text();
|
|
68
|
-
|
|
69
|
-
try {
|
|
70
|
-
parsed = bodyText ? JSON.parse(bodyText) : null;
|
|
71
|
-
} catch(err) {
|
|
72
|
-
parsed = null;
|
|
73
|
-
}
|
|
76
|
+
const parsed = JSON.parse(bodyText);
|
|
74
77
|
|
|
75
|
-
|
|
76
|
-
`[${CHANNEL_ID}] ${auditContext} response status=${response.status} ok=${response.ok} body=${bodyText || '<empty>'}`,
|
|
77
|
-
);
|
|
78
|
+
console.debug(`[${CHANNEL_ID}] ${auditCtx} postTuituiMsg response status=${response.status} ok=${response.ok} body=${bodyText || '<empty>'}`);
|
|
78
79
|
|
|
79
80
|
if (!response.ok) {
|
|
80
|
-
throw new Error(
|
|
81
|
-
`[${CHANNEL_ID}] ${auditContext} Failed (response.ok unexpected): ${response.status} ${response.statusText}; body=${bodyText || '<empty>'}`,
|
|
82
|
-
);
|
|
81
|
+
throw new Error(`postTuituiMsg Failed (response.ok unexpected): ${response.status} ${response.statusText}; body=${bodyText || '<empty>'}`);
|
|
83
82
|
}
|
|
84
83
|
|
|
85
|
-
if (parsed
|
|
86
|
-
throw new Error(
|
|
87
|
-
`[${CHANNEL_ID}] ${auditContext} Failed (errcode unexpected): errcode=${parsed.errcode} errmsg=${parsed.errmsg ?? 'Unknown error'}`,
|
|
88
|
-
);
|
|
84
|
+
if (Number(parsed?.errcode) !== 0) {
|
|
85
|
+
throw new Error(`postTuituiMsg Failed (errcode unexpected): errcode=${parsed.errcode} errmsg=${parsed.errmsg ?? 'Unknown error'}`);
|
|
89
86
|
}
|
|
90
87
|
} catch(err) {
|
|
91
|
-
console.error(`[${CHANNEL_ID}] postTuituiMsg error:`, err, `\njson: ${JSON.stringify(json)}`);
|
|
88
|
+
console.error(`[${CHANNEL_ID}] ${auditCtx} postTuituiMsg error:`, err, `\njson: ${JSON.stringify(json)}`);
|
|
92
89
|
} finally {
|
|
93
90
|
await release();
|
|
94
91
|
}
|
|
@@ -148,7 +145,7 @@ export async function uploadFileToTuiTui(fileSrc: string, account: any, type: 'i
|
|
|
148
145
|
}
|
|
149
146
|
// HTTP/HTTPS URL
|
|
150
147
|
else if (/^https?\:/.test(fileSrc)) {
|
|
151
|
-
const { response, release } = await _fetch({ url: fileSrc,
|
|
148
|
+
const { response, release } = await _fetch({ url: fileSrc, auditCtx: 'tuitui.media.download' });
|
|
152
149
|
try {
|
|
153
150
|
if (!response.ok) {
|
|
154
151
|
throw new Error(`[${CHANNEL_ID}] Failed to download media from ${fileSrc}: ${response.status}`);
|
|
@@ -201,7 +198,7 @@ export async function uploadFileToTuiTui(fileSrc: string, account: any, type: 'i
|
|
|
201
198
|
const { response, release } = await _fetch({
|
|
202
199
|
url: addParams2Url('https://im.live.360.cn:8282/robot/media/upload', { appid, secret, type }),
|
|
203
200
|
init: { method: "POST", body },
|
|
204
|
-
|
|
201
|
+
auditCtx: "tuitui.media.upload",
|
|
205
202
|
});
|
|
206
203
|
try {
|
|
207
204
|
if (!response.ok) {
|
|
@@ -287,11 +284,11 @@ export async function tuituiEmojiReaction(
|
|
|
287
284
|
targetIsGroup: boolean,
|
|
288
285
|
msgid: string,
|
|
289
286
|
emoji: string
|
|
290
|
-
): Promise<
|
|
287
|
+
): Promise<void> {
|
|
291
288
|
const payload = {
|
|
292
289
|
msgtype: 'emoji_reaction',
|
|
293
|
-
tousers:[] as TuiTuiSingleEmojiReactionTarget[],
|
|
294
|
-
togroups:[] as TuiTuiGroupEmojiReactionTarget[],
|
|
290
|
+
tousers: [] as TuiTuiSingleEmojiReactionTarget[],
|
|
291
|
+
togroups: [] as TuiTuiGroupEmojiReactionTarget[],
|
|
295
292
|
emoji_reaction: { emoji, cancel: false},
|
|
296
293
|
};
|
|
297
294
|
if(targetIsGroup) {
|
|
@@ -312,6 +309,83 @@ export async function tuituiEmojiReaction(
|
|
|
312
309
|
} finally {
|
|
313
310
|
await release();
|
|
314
311
|
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function getTargets(chatId: string, isGroup: boolean): { tousers: string[]; togroups: string[] } {
|
|
315
|
+
const tousers: string[] = [];
|
|
316
|
+
const togroups: string[] = [];
|
|
317
|
+
(isGroup ? togroups : tousers).push(chatId);
|
|
318
|
+
return { tousers, togroups };
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export async function sendTextMsg(
|
|
322
|
+
account: any,
|
|
323
|
+
target: string | undefined,
|
|
324
|
+
isGroup: boolean,
|
|
325
|
+
content: string,
|
|
326
|
+
auditCtx: string = 'tuitui.send.text',
|
|
327
|
+
atList?: string[],
|
|
328
|
+
): Promise<void> {
|
|
329
|
+
if (!target) return console.error(`[${CHANNEL_ID}] sendTextMsg Error ${auditCtx}: Missing "target"`);
|
|
330
|
+
// Format message according to TuiTui's required structure
|
|
331
|
+
const msg: TuiTuiOutboundTextMessage = {
|
|
332
|
+
msgtype: 'text',
|
|
333
|
+
...getTargets(target, isGroup),
|
|
334
|
+
at: atList || [],
|
|
335
|
+
text: { content },
|
|
336
|
+
};
|
|
337
|
+
console.log(`[${CHANNEL_ID}] sendTextMsg ${auditCtx} - `, msg);
|
|
338
|
+
await postTuituiMsg(account, msg, auditCtx);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
export async function sendPageMsg(
|
|
342
|
+
account: any,
|
|
343
|
+
target: string | undefined,
|
|
344
|
+
isGroup: boolean,
|
|
345
|
+
page: TuiTuiOutboundPageMessagePage,
|
|
346
|
+
auditCtx: string = 'tuitui.send.page',
|
|
347
|
+
tousers?: string[],
|
|
348
|
+
togroups?: string[],
|
|
349
|
+
): Promise<void> {
|
|
350
|
+
if (!target) return console.error(`[${CHANNEL_ID}] sendPageMsg Error ${auditCtx}: Missing "target"`);
|
|
351
|
+
const targets = getTargets(target, isGroup);
|
|
352
|
+
tousers = tousers || targets.tousers;
|
|
353
|
+
togroups = togroups || targets.togroups;
|
|
354
|
+
const msg: TuiTuiOutboundPageMessage = {
|
|
355
|
+
msgtype: 'page',
|
|
356
|
+
tousers,
|
|
357
|
+
togroups,
|
|
358
|
+
page: { ...(page || {})}
|
|
359
|
+
};
|
|
360
|
+
console.log(`[${CHANNEL_ID}] sendPageMsg ${auditCtx} - `, msg);
|
|
361
|
+
await postTuituiMsg(account, msg, auditCtx);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
export async function sendMediaMsg(
|
|
365
|
+
account: any,
|
|
366
|
+
target: string | undefined,
|
|
367
|
+
isGroup: boolean,
|
|
368
|
+
mediaUrl: string,
|
|
369
|
+
auditCtx: string = 'tuitui.send.media',
|
|
370
|
+
): Promise<void> {
|
|
371
|
+
if (!target) return console.error(`[${CHANNEL_ID}] sendMediaMsg Error ${auditCtx}: Missing "target"`);
|
|
372
|
+
// Check if mediaUrl looks like an image
|
|
373
|
+
const isImage = /^data:image\//i.test(mediaUrl) || /\.(jpg|jpeg|png|gif)(?:$|[?#])/i.test(mediaUrl);
|
|
374
|
+
const mediaType = isImage ? 'image' : 'file';
|
|
375
|
+
|
|
376
|
+
const media_id = await uploadFileToTuiTui(mediaUrl, account, mediaType);
|
|
377
|
+
if (!media_id) {
|
|
378
|
+
console.error(`[${CHANNEL_ID}] uploadFileToTuiTui failed ${auditCtx}, {mediaUrl: ${mediaUrl}, mediaType: ${mediaType}}`);
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const targets = getTargets(target, isGroup);
|
|
383
|
+
const msg: TuiTuiOutboundImageMessage | TuiTuiOutboundAttachmentMessage =
|
|
384
|
+
isImage
|
|
385
|
+
? { ...targets, msgtype: 'image', image: { media_id } }
|
|
386
|
+
: { ...targets, msgtype: 'attachment', attachment: { media_id } };
|
|
387
|
+
|
|
388
|
+
console.log(`[${CHANNEL_ID}] sendMediaMsg ${auditCtx} - `, msg);
|
|
315
389
|
|
|
316
|
-
|
|
390
|
+
await postTuituiMsg(account, msg, auditCtx);
|
|
317
391
|
}
|