@xmanrui/dsh-im 1.5.0 → 2.0.0
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.en.md +16 -4
- package/README.md +16 -4
- package/lib/index.js +208 -178
- package/package.json +1 -1
- package/plugin-src/host/index.mjs +2 -0
- package/src/channels/dingtalk/config-store.mjs +5 -3
- package/src/channels/dingtalk/dingtalk-api.mjs +2 -1
- package/src/channels/dingtalk/dingtalk-bridge.mjs +45 -40
- package/src/channels/dingtalk/dingtalk-card-stream.mjs +3 -1
- package/src/channels/dingtalk/dingtalk-controller.mjs +24 -23
- package/src/channels/dingtalk/dingtalk-runtime.mjs +4 -3
- package/src/channels/dingtalk/state-store.mjs +3 -1
- package/src/channels/discord/config-store.mjs +2 -1
- package/src/channels/discord/discord-api.mjs +25 -1
- package/src/channels/discord/discord-runtime.mjs +237 -11
- package/src/channels/feishu/bridge.mjs +131 -123
- package/src/channels/feishu/feishu-cards.mjs +62 -54
- package/src/channels/feishu/feishu-channel.mjs +5 -4
- package/src/channels/feishu/feishu-runtime.mjs +10 -9
- package/src/channels/feishu/message-utils.mjs +4 -3
- package/src/channels/office/office-job-executor.mjs +15 -14
- package/src/channels/office/office-runtime.mjs +6 -5
- package/src/channels/qq/config-store.mjs +3 -1
- package/src/channels/qq/markdown-reply.mjs +4 -2
- package/src/channels/qq/qq-bridge.mjs +57 -52
- package/src/channels/qq/qq-controller.mjs +17 -16
- package/src/channels/qq/qq-runtime.mjs +3 -2
- package/src/channels/shared/agent-preset.mjs +3 -1
- package/src/channels/shared/compact-command.mjs +15 -14
- package/src/channels/shared/connection-test.mjs +6 -4
- package/src/channels/shared/control-command.mjs +13 -11
- package/src/channels/shared/editable-message-stream.mjs +4 -2
- package/src/channels/shared/harness-approval.mjs +23 -21
- package/src/channels/shared/harness-client.mjs +3 -2
- package/src/channels/shared/harness-question.mjs +8 -6
- package/src/channels/shared/i18n-en/dingtalk.mjs +52 -0
- package/src/channels/shared/i18n-en/discord.mjs +8 -0
- package/src/channels/shared/i18n-en/feishu.mjs +217 -0
- package/src/channels/shared/i18n-en/office.mjs +23 -0
- package/src/channels/shared/i18n-en/qq.mjs +54 -0
- package/src/channels/shared/i18n-en/shared-a.mjs +91 -0
- package/src/channels/shared/i18n-en/shared-b.mjs +215 -0
- package/src/channels/shared/i18n-en/shared-c.mjs +99 -0
- package/src/channels/shared/i18n-en/slack.mjs +27 -0
- package/src/channels/shared/i18n-en/telegram.mjs +21 -0
- package/src/channels/shared/i18n-en/wecom.mjs +38 -0
- package/src/channels/shared/i18n-en/weixin.mjs +80 -0
- package/src/channels/shared/i18n-en/whatsapp.mjs +17 -0
- package/src/channels/shared/i18n-en.mjs +35 -0
- package/src/channels/shared/i18n.mjs +35 -0
- package/src/channels/shared/image-prompt.mjs +17 -15
- package/src/channels/shared/inbound-file.mjs +5 -3
- package/src/channels/shared/model-command.mjs +35 -31
- package/src/channels/shared/preset-command.mjs +41 -38
- package/src/channels/shared/semantic/artifact-delivery.mjs +1 -1
- package/src/channels/shared/semantic/delivery.mjs +38 -0
- package/src/channels/shared/text-harness-bridge.mjs +130 -63
- package/src/channels/shared/token-bot-controller.mjs +32 -11
- package/src/channels/shared/workspace-command.mjs +69 -58
- package/src/channels/slack/config-store.mjs +3 -1
- package/src/channels/slack/slack-api.mjs +6 -5
- package/src/channels/slack/slack-controller.mjs +10 -9
- package/src/channels/slack/slack-runtime.mjs +16 -4
- package/src/channels/telegram/config-store.mjs +2 -1
- package/src/channels/telegram/telegram-api.mjs +72 -6
- package/src/channels/telegram/telegram-rich-message.mjs +147 -0
- package/src/channels/telegram/telegram-runtime.mjs +373 -5
- package/src/channels/wecom/config-store.mjs +3 -1
- package/src/channels/wecom/wecom-bridge.mjs +59 -55
- package/src/channels/wecom/wecom-controller.mjs +20 -19
- package/src/channels/wecom/wecom-runtime.mjs +2 -1
- package/src/channels/weixin/config-store.mjs +2 -1
- package/src/channels/weixin/weixin-bridge.mjs +50 -49
- package/src/channels/weixin/weixin-controller.mjs +22 -21
- package/src/channels/weixin/weixin-runtime.mjs +3 -2
- package/src/channels/whatsapp/config-store.mjs +3 -1
- package/src/channels/whatsapp/whatsapp-bridge.mjs +3 -1
- package/src/channels/whatsapp/whatsapp-controller.mjs +15 -14
- package/src/channels/whatsapp/whatsapp-runtime.mjs +5 -4
- package/src/channels/whatsapp/whatsapp-web-session.mjs +3 -1
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
import { createEditableMessageStream, splitMessageText } from '../shared/editable-message-stream.mjs';
|
|
2
2
|
import { fetchFileStream } from '../shared/file-download.mjs';
|
|
3
3
|
import { fetchImageBuffer } from '../shared/image-prompt.mjs';
|
|
4
|
+
import { t } from '../shared/i18n.mjs';
|
|
4
5
|
import { DiscordApi } from './discord-api.mjs';
|
|
5
6
|
import { createDiscordBridgeStatus, DiscordHarnessBridge } from './discord-bridge.mjs';
|
|
6
7
|
|
|
7
|
-
const DISCORD_GATEWAY_INTENTS = (1 << 0) | (1 << 9) | (1 << 12);
|
|
8
|
+
const DISCORD_GATEWAY_INTENTS = (1 << 0) | (1 << 9) | (1 << 12) | (1 << 15);
|
|
9
|
+
const THREAD_RECOVERY_TIMEOUT_MS = 5_000;
|
|
8
10
|
const RECONNECT_DELAYS_MS = Object.freeze([1_000, 3_000, 5_000, 10_000, 30_000]);
|
|
11
|
+
const GUILD_TEXT = 0;
|
|
12
|
+
const GUILD_ANNOUNCEMENT = 5;
|
|
13
|
+
const ANNOUNCEMENT_THREAD = 10;
|
|
14
|
+
const PUBLIC_THREAD = 11;
|
|
15
|
+
const PRIVATE_THREAD = 12;
|
|
16
|
+
const THREAD_TYPES = new Set([ANNOUNCEMENT_THREAD, PUBLIC_THREAD, PRIVATE_THREAD]);
|
|
9
17
|
const IMAGE_MEDIA_TYPES = new Set(['image/jpeg', 'image/png', 'image/webp', 'image/gif']);
|
|
10
18
|
const IMAGE_FILE_TYPES = new Map([
|
|
11
19
|
['.jpg', 'image/jpeg'],
|
|
@@ -40,12 +48,12 @@ function eventData(event) {
|
|
|
40
48
|
|
|
41
49
|
function gatewayCloseError(code) {
|
|
42
50
|
if (code === 4004) {
|
|
43
|
-
const error = new Error('Discord Bot Token 无效,请重新填写。');
|
|
51
|
+
const error = new Error(t('Discord Bot Token 无效,请重新填写。'));
|
|
44
52
|
error.code = 'discord-401';
|
|
45
53
|
return error;
|
|
46
54
|
}
|
|
47
55
|
if (code === 4013 || code === 4014) {
|
|
48
|
-
const error = new Error('Discord Gateway Intents 配置不正确,请检查 Developer Portal 的 Bot 设置。');
|
|
56
|
+
const error = new Error(t('Discord Gateway Intents 配置不正确,请检查 Developer Portal 的 Bot 设置。'));
|
|
49
57
|
error.code = 'discord-intents';
|
|
50
58
|
return error;
|
|
51
59
|
}
|
|
@@ -59,6 +67,94 @@ function stripBotMention(text, botId) {
|
|
|
59
67
|
return text.replace(new RegExp(`<@!?${botId}>`, 'g'), '').trim();
|
|
60
68
|
}
|
|
61
69
|
|
|
70
|
+
function cleanThreadName(message, botId) {
|
|
71
|
+
const name = stripBotMention(message?.content ?? '', botId).replace(/\s+/g, ' ').trim()
|
|
72
|
+
|| 'DeepSeek Harness';
|
|
73
|
+
return [...name].slice(0, 100).join('');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function isThreadChannel(channel) {
|
|
77
|
+
return THREAD_TYPES.has(Number(channel?.type));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function isThreadFromMessage(channel, message) {
|
|
81
|
+
return isThreadChannel(channel)
|
|
82
|
+
&& String(channel?.id ?? '') === String(message?.id ?? '')
|
|
83
|
+
&& String(channel?.parent_id ?? '') === String(message?.channel_id ?? '');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function rememberChannel(channel, callback) {
|
|
87
|
+
if (channel?.id) callback?.(channel);
|
|
88
|
+
return channel;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function withConversationRoute(normalized, channel, botId, {
|
|
92
|
+
fromSourceMessage = false,
|
|
93
|
+
fallback = null,
|
|
94
|
+
notice = null,
|
|
95
|
+
} = {}) {
|
|
96
|
+
const channelId = String(channel?.id ?? normalized.conversationId);
|
|
97
|
+
const thread = isThreadChannel(channel);
|
|
98
|
+
const managed = thread && String(channel?.owner_id ?? '') === String(botId);
|
|
99
|
+
const parentId = thread && channel?.parent_id ? String(channel.parent_id) : channelId;
|
|
100
|
+
return {
|
|
101
|
+
...normalized,
|
|
102
|
+
conversationId: channelId,
|
|
103
|
+
addressed: normalized.addressed || managed,
|
|
104
|
+
requiresMention: thread ? !managed : normalized.kind === 'group',
|
|
105
|
+
replyTarget: {
|
|
106
|
+
channelId,
|
|
107
|
+
...(!fromSourceMessage && normalized.replyTarget?.replyToMessageId
|
|
108
|
+
? { replyToMessageId: normalized.replyTarget.replyToMessageId }
|
|
109
|
+
: {}),
|
|
110
|
+
...(notice ? { notice } : {}),
|
|
111
|
+
},
|
|
112
|
+
connectionTestTarget: { channelId },
|
|
113
|
+
conversationRoute: {
|
|
114
|
+
peerId: parentId,
|
|
115
|
+
...(thread ? { threadId: channelId, managed } : {}),
|
|
116
|
+
...(fallback ? { fallback } : {}),
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function uncertainThreadCreate(error) {
|
|
122
|
+
const status = Number(error?.status);
|
|
123
|
+
return !Number.isInteger(status) || status >= 500;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function threadCreateUncertain(cause) {
|
|
127
|
+
const error = new Error('Discord Thread creation result is uncertain', { cause });
|
|
128
|
+
error.code = 'discord-thread-create-uncertain';
|
|
129
|
+
return error;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async function findCreatedThread(api, message, { signal, onChannel } = {}) {
|
|
133
|
+
try {
|
|
134
|
+
const channel = rememberChannel(await api.getChannel({
|
|
135
|
+
channelId: String(message.id),
|
|
136
|
+
signal,
|
|
137
|
+
}), onChannel);
|
|
138
|
+
return isThreadFromMessage(channel, message) ? channel : null;
|
|
139
|
+
} catch (error) {
|
|
140
|
+
if (error?.status === 404) return null;
|
|
141
|
+
throw error;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async function sendThreadUncertainNotice(api, normalized, signal) {
|
|
146
|
+
try {
|
|
147
|
+
await api.createMessage({
|
|
148
|
+
channelId: normalized.replyTarget.channelId,
|
|
149
|
+
replyToMessageId: normalized.replyTarget.replyToMessageId,
|
|
150
|
+
content: 'Thread 创建结果暂时无法确认。若已创建,请在对应 Thread 中重试;若未创建,请稍后重新 @机器人。',
|
|
151
|
+
signal,
|
|
152
|
+
});
|
|
153
|
+
} catch (error) {
|
|
154
|
+
if (signal?.aborted) throw signal.reason ?? error;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
62
158
|
function attachmentMediaType(attachment) {
|
|
63
159
|
const value = typeof attachment?.content_type === 'string'
|
|
64
160
|
? attachment.content_type.split(';', 1)[0].trim().toLowerCase() : '';
|
|
@@ -109,7 +205,8 @@ function discordFileSource(attachment, fetchImpl) {
|
|
|
109
205
|
}
|
|
110
206
|
|
|
111
207
|
export function normalizeDiscordMessage(message, botId, { fetchImpl = fetch } = {}) {
|
|
112
|
-
if (!message?.id || !message?.channel_id || !message?.author?.id
|
|
208
|
+
if (!message?.id || !message?.channel_id || !message?.author?.id
|
|
209
|
+
|| Number(message.type) === 21) return null;
|
|
113
210
|
const direct = !message.guild_id;
|
|
114
211
|
const addressed = direct
|
|
115
212
|
|| message.mentions?.some((mention) => String(mention?.id) === String(botId));
|
|
@@ -135,9 +232,84 @@ export function normalizeDiscordMessage(message, botId, { fetchImpl = fetch } =
|
|
|
135
232
|
};
|
|
136
233
|
}
|
|
137
234
|
|
|
235
|
+
export async function resolveDiscordMessageRoute(message, botId, {
|
|
236
|
+
api,
|
|
237
|
+
channel,
|
|
238
|
+
fetchImpl = fetch,
|
|
239
|
+
signal,
|
|
240
|
+
onChannel,
|
|
241
|
+
} = {}) {
|
|
242
|
+
const normalized = normalizeDiscordMessage(message, botId, { fetchImpl });
|
|
243
|
+
if (!normalized || normalized.senderIsBot) return normalized;
|
|
244
|
+
signal?.throwIfAborted();
|
|
245
|
+
if (normalized.kind === 'direct') {
|
|
246
|
+
return withConversationRoute(normalized, { id: normalized.conversationId, type: 1 }, botId);
|
|
247
|
+
}
|
|
248
|
+
if (!api || typeof api.getChannel !== 'function') {
|
|
249
|
+
throw new TypeError('Discord route resolution requires the Discord API');
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const sourceChannel = rememberChannel(channel ?? await api.getChannel({
|
|
253
|
+
channelId: normalized.conversationId,
|
|
254
|
+
signal,
|
|
255
|
+
}), onChannel);
|
|
256
|
+
if (isThreadChannel(sourceChannel)) {
|
|
257
|
+
return withConversationRoute(normalized, sourceChannel, botId);
|
|
258
|
+
}
|
|
259
|
+
if (!normalized.addressed) return withConversationRoute(normalized, sourceChannel, botId);
|
|
260
|
+
|
|
261
|
+
const sourceType = Number(sourceChannel?.type);
|
|
262
|
+
if (sourceType !== GUILD_TEXT && sourceType !== GUILD_ANNOUNCEMENT) {
|
|
263
|
+
return withConversationRoute(normalized, sourceChannel, botId, {
|
|
264
|
+
fallback: 'unsupported-channel',
|
|
265
|
+
notice: '当前频道不支持自动创建 Thread,已直接在当前频道回复。',
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
signal?.throwIfAborted();
|
|
270
|
+
let created;
|
|
271
|
+
try {
|
|
272
|
+
created = rememberChannel(await api.startThreadFromMessage({
|
|
273
|
+
channelId: normalized.conversationId,
|
|
274
|
+
messageId: normalized.messageId,
|
|
275
|
+
name: cleanThreadName(message, botId),
|
|
276
|
+
signal,
|
|
277
|
+
}), onChannel);
|
|
278
|
+
if (!isThreadFromMessage(created, message)) {
|
|
279
|
+
throw new Error('Discord returned an invalid thread for the source message');
|
|
280
|
+
}
|
|
281
|
+
} catch (error) {
|
|
282
|
+
let recovered;
|
|
283
|
+
try {
|
|
284
|
+
recovered = await findCreatedThread(api, message, {
|
|
285
|
+
signal: AbortSignal.timeout(THREAD_RECOVERY_TIMEOUT_MS),
|
|
286
|
+
onChannel,
|
|
287
|
+
});
|
|
288
|
+
} catch (recoveryError) {
|
|
289
|
+
if (signal?.aborted) throw signal.reason ?? error;
|
|
290
|
+
await sendThreadUncertainNotice(api, normalized, signal);
|
|
291
|
+
throw threadCreateUncertain(recoveryError);
|
|
292
|
+
}
|
|
293
|
+
if (recovered) {
|
|
294
|
+
return withConversationRoute(normalized, recovered, botId, { fromSourceMessage: true });
|
|
295
|
+
}
|
|
296
|
+
if (signal?.aborted) throw signal.reason ?? error;
|
|
297
|
+
if (uncertainThreadCreate(error)) {
|
|
298
|
+
await sendThreadUncertainNotice(api, normalized, signal);
|
|
299
|
+
throw threadCreateUncertain(error);
|
|
300
|
+
}
|
|
301
|
+
return withConversationRoute(normalized, sourceChannel, botId, {
|
|
302
|
+
fallback: 'thread-create-failed',
|
|
303
|
+
notice: '无法创建 Thread,已直接在当前频道回复。',
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
return withConversationRoute(normalized, created, botId, { fromSourceMessage: true });
|
|
307
|
+
}
|
|
308
|
+
|
|
138
309
|
export class DiscordBotClient {
|
|
139
310
|
#api;
|
|
140
311
|
#signal;
|
|
312
|
+
#deliveredNotices = new WeakSet();
|
|
141
313
|
|
|
142
314
|
constructor({ api, signal }) {
|
|
143
315
|
this.#api = api;
|
|
@@ -145,7 +317,9 @@ export class DiscordBotClient {
|
|
|
145
317
|
}
|
|
146
318
|
|
|
147
319
|
async sendText(target, text) {
|
|
148
|
-
const
|
|
320
|
+
const notice = !this.#deliveredNotices.has(target) && target?.notice
|
|
321
|
+
? String(target.notice) : null;
|
|
322
|
+
const chunks = splitMessageText(notice ? `${notice}\n\n${text}` : text, 1_900);
|
|
149
323
|
const providerMessageIds = [];
|
|
150
324
|
for (const [index, chunk] of chunks.entries()) {
|
|
151
325
|
const result = await this.#api.createMessage({
|
|
@@ -154,6 +328,7 @@ export class DiscordBotClient {
|
|
|
154
328
|
replyToMessageId: index === 0 ? target.replyToMessageId : undefined,
|
|
155
329
|
signal: this.#signal,
|
|
156
330
|
});
|
|
331
|
+
if (notice && index === 0) this.#deliveredNotices.add(target);
|
|
157
332
|
if (typeof result?.id === 'string' && result.id) providerMessageIds.push(result.id);
|
|
158
333
|
}
|
|
159
334
|
return { providerMessageIds };
|
|
@@ -173,21 +348,25 @@ export class DiscordBotClient {
|
|
|
173
348
|
}
|
|
174
349
|
|
|
175
350
|
async openStream(target) {
|
|
351
|
+
const notice = !this.#deliveredNotices.has(target) && target?.notice
|
|
352
|
+
? String(target.notice) : null;
|
|
353
|
+
const decorate = (content) => notice ? `${notice}\n\n${content}` : content;
|
|
176
354
|
const stream = createEditableMessageStream({
|
|
177
|
-
limit: 1_900,
|
|
355
|
+
limit: notice ? 1_800 : 1_900,
|
|
178
356
|
create: async (content) => {
|
|
179
357
|
const message = await this.#api.createMessage({
|
|
180
358
|
channelId: target.channelId,
|
|
181
|
-
content,
|
|
359
|
+
content: decorate(content),
|
|
182
360
|
replyToMessageId: target.replyToMessageId,
|
|
183
361
|
signal: this.#signal,
|
|
184
362
|
});
|
|
363
|
+
if (notice) this.#deliveredNotices.add(target);
|
|
185
364
|
return message.id;
|
|
186
365
|
},
|
|
187
366
|
edit: (messageId, content) => this.#api.editMessage({
|
|
188
367
|
channelId: target.channelId,
|
|
189
368
|
messageId,
|
|
190
|
-
content,
|
|
369
|
+
content: decorate(content),
|
|
191
370
|
signal: this.#signal,
|
|
192
371
|
}),
|
|
193
372
|
sendRemainder: (content) => this.#api.createMessage({
|
|
@@ -241,6 +420,8 @@ export class DiscordRuntime {
|
|
|
241
420
|
#generation = 0;
|
|
242
421
|
#stopped = true;
|
|
243
422
|
#starting = null;
|
|
423
|
+
#channels = new Map();
|
|
424
|
+
#routing = new Map();
|
|
244
425
|
|
|
245
426
|
constructor({
|
|
246
427
|
config,
|
|
@@ -299,6 +480,8 @@ export class DiscordRuntime {
|
|
|
299
480
|
this.#resumeUrl = null;
|
|
300
481
|
this.#sequence = null;
|
|
301
482
|
this.#reconnectAttempt = 0;
|
|
483
|
+
this.#channels.clear();
|
|
484
|
+
this.#routing.clear();
|
|
302
485
|
this.#status.startedAt = new Date().toISOString();
|
|
303
486
|
this.#status.connectionState = 'connecting';
|
|
304
487
|
this.#status.lastError = null;
|
|
@@ -432,11 +615,21 @@ export class DiscordRuntime {
|
|
|
432
615
|
markReady();
|
|
433
616
|
} else if (packet.t === 'RESUMED') {
|
|
434
617
|
markReady();
|
|
618
|
+
} else if (packet.t === 'GUILD_CREATE') {
|
|
619
|
+
for (const channel of [...(packet.d?.channels ?? []), ...(packet.d?.threads ?? [])]) {
|
|
620
|
+
this.#rememberChannel(channel);
|
|
621
|
+
}
|
|
622
|
+
} else if (packet.t === 'CHANNEL_CREATE' || packet.t === 'CHANNEL_UPDATE'
|
|
623
|
+
|| packet.t === 'THREAD_CREATE' || packet.t === 'THREAD_UPDATE') {
|
|
624
|
+
this.#rememberChannel(packet.d);
|
|
625
|
+
} else if (packet.t === 'CHANNEL_DELETE' || packet.t === 'THREAD_DELETE') {
|
|
626
|
+
if (packet.d?.id) this.#channels.delete(String(packet.d.id));
|
|
627
|
+
} else if (packet.t === 'THREAD_LIST_SYNC') {
|
|
628
|
+
for (const channel of packet.d?.threads ?? []) this.#rememberChannel(channel);
|
|
435
629
|
} else if (packet.t === 'MESSAGE_CREATE') {
|
|
436
|
-
const message = normalizeDiscordMessage(packet.d, this.#config.platformId);
|
|
437
630
|
const bridge = this.#bridge;
|
|
438
|
-
if (
|
|
439
|
-
void bridge
|
|
631
|
+
if (packet.d && bridge) {
|
|
632
|
+
void this.#acceptMessage(packet.d, bridge).catch((error) => {
|
|
440
633
|
if (generation !== this.#generation || this.#stopped) return;
|
|
441
634
|
this.#logger.error?.(
|
|
442
635
|
`[dsh-im:discord] bot ${this.#config.botId} message handling failed:`,
|
|
@@ -475,6 +668,38 @@ export class DiscordRuntime {
|
|
|
475
668
|
});
|
|
476
669
|
}
|
|
477
670
|
|
|
671
|
+
#rememberChannel(channel) {
|
|
672
|
+
if (!channel?.id) return;
|
|
673
|
+
this.#channels.set(String(channel.id), channel);
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
async #acceptMessage(message, bridge) {
|
|
677
|
+
const messageId = String(message?.id ?? '');
|
|
678
|
+
if (!messageId || this.#state.hasSeen(messageId)) return;
|
|
679
|
+
let route = this.#routing.get(messageId);
|
|
680
|
+
if (!route) {
|
|
681
|
+
route = resolveDiscordMessageRoute(message, this.#config.platformId, {
|
|
682
|
+
api: this.#api,
|
|
683
|
+
channel: this.#channels.get(String(message.channel_id)),
|
|
684
|
+
signal: this.#abortController?.signal,
|
|
685
|
+
onChannel: (resolved) => this.#rememberChannel(resolved),
|
|
686
|
+
});
|
|
687
|
+
this.#routing.set(messageId, route);
|
|
688
|
+
void route.finally(() => {
|
|
689
|
+
if (this.#routing.get(messageId) === route) this.#routing.delete(messageId);
|
|
690
|
+
}).catch(() => undefined);
|
|
691
|
+
}
|
|
692
|
+
try {
|
|
693
|
+
const normalized = await route;
|
|
694
|
+
if (normalized) await bridge.accept(normalized);
|
|
695
|
+
} catch (error) {
|
|
696
|
+
if (error?.code === 'discord-thread-create-uncertain') {
|
|
697
|
+
await this.#state.markSeen(messageId);
|
|
698
|
+
}
|
|
699
|
+
throw error;
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
|
|
478
703
|
#sendGateway(socket, payload) {
|
|
479
704
|
if (socket.readyState !== 1) return;
|
|
480
705
|
socket.send(JSON.stringify(payload));
|
|
@@ -541,6 +766,7 @@ export class DiscordRuntime {
|
|
|
541
766
|
this.#socket = null;
|
|
542
767
|
this.#bridge = null;
|
|
543
768
|
this.#api = null;
|
|
769
|
+
this.#routing.clear();
|
|
544
770
|
try {
|
|
545
771
|
if (socket && socket.readyState < 2) socket.close(1000, 'Plugin stopped');
|
|
546
772
|
} catch (error) {
|