@zhin.js/adapter-discord 7.0.1 → 7.0.3
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 +24 -0
- package/README.md +3 -3
- package/lib/endpoint.d.ts +2 -2
- package/lib/endpoint.js +27 -20
- package/lib/protocol.d.ts +11 -0
- package/lib/protocol.js +15 -0
- package/package.json +15 -15
- package/src/endpoint.ts +27 -18
- package/src/protocol.ts +24 -0
- /package/commands/endpoint/add/{[name:string].js → [name].js} +0 -0
- /package/commands/endpoint/add/{[name:string].ts → [name].ts} +0 -0
- /package/commands/endpoint/remove/{[name:string].js → [name].js} +0 -0
- /package/commands/endpoint/remove/{[name:string].ts → [name].ts} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# @zhin.js/adapter-discord
|
|
2
2
|
|
|
3
|
+
## 7.0.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [afc0e66]
|
|
8
|
+
- Updated dependencies [2e41ad5]
|
|
9
|
+
- Updated dependencies [9f57124]
|
|
10
|
+
- @zhin.js/core@1.5.2
|
|
11
|
+
- @zhin.js/adapter@1.1.5
|
|
12
|
+
- @zhin.js/im-contract@1.0.1
|
|
13
|
+
- @zhin.js/plugin-runtime@1.1.3
|
|
14
|
+
- @zhin.js/command@1.0.7
|
|
15
|
+
- @zhin.js/host-http@1.0.6
|
|
16
|
+
- @zhin.js/agent@1.1.3
|
|
17
|
+
- zhin.js@6.0.2
|
|
18
|
+
|
|
19
|
+
## 7.0.2
|
|
20
|
+
|
|
21
|
+
### Patch Changes
|
|
22
|
+
|
|
23
|
+
- Updated dependencies [696ab1b]
|
|
24
|
+
- @zhin.js/agent@1.1.2
|
|
25
|
+
- zhin.js@6.0.1
|
|
26
|
+
|
|
3
27
|
## 7.0.1
|
|
4
28
|
|
|
5
29
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ Zhin.js Discord 适配器(Plugin Runtime),默认通过 **Gateway WebSocket
|
|
|
7
7
|
- Gateway WebSocket 入站(默认;无需公网 HTTPS / host)
|
|
8
8
|
- 解析 text / mention / attachment / embed / sticker / button
|
|
9
9
|
- 支持私聊、群组与服务器频道
|
|
10
|
-
- 出站 `send({
|
|
10
|
+
- 出站 `send({ conversation, payload })` → Discord channel message(text / media / embed / keyboard)
|
|
11
11
|
- 约定式 `defineAdapter` / `definePlugin`(无需 `usePlugin`)
|
|
12
12
|
- Interactions HTTP webhook 延期(需 `httpHostToken`);配置 `connection: interactions` 会明确报错
|
|
13
13
|
|
|
@@ -25,8 +25,8 @@ pnpm add @zhin.js/adapter-discord discord.js
|
|
|
25
25
|
- 配置经插件 `schema.json` 落到 `plugins.<instanceKey>`
|
|
26
26
|
- **无需** `@zhin.js/host-http` / `@zhin.js/host-router`(Gateway 路径)
|
|
27
27
|
|
|
28
|
-
入站:`gateway.receive({
|
|
29
|
-
出站:`send({
|
|
28
|
+
入站:`gateway.receive({ conversation, message, content: text, sender, metadata })`(`conversation` 为 `ConversationRef`,guild 频道带 `parent` guild 容器)
|
|
29
|
+
出站:`send({ conversation, payload })` → discord.js channel.send
|
|
30
30
|
|
|
31
31
|
### 平台权限(platform permit)
|
|
32
32
|
|
package/lib/endpoint.d.ts
CHANGED
|
@@ -20,7 +20,7 @@ export declare class DiscordGatewayEndpoint implements EndpointInstance {
|
|
|
20
20
|
open(): void;
|
|
21
21
|
close(): void;
|
|
22
22
|
stop(): Promise<void>;
|
|
23
|
-
send({
|
|
23
|
+
send({ conversation, payload }: EndpointSendRequest): Promise<string>;
|
|
24
24
|
recallMessage(messageId: string): Promise<void>;
|
|
25
25
|
/** Test / internal: admit a message when open. */
|
|
26
26
|
admit(msg: DiscordInboundMessage): void;
|
|
@@ -64,7 +64,7 @@ export declare class DiscordInteractionsEndpoint implements EndpointInstance {
|
|
|
64
64
|
open(): void;
|
|
65
65
|
close(): void;
|
|
66
66
|
stop(): Promise<void>;
|
|
67
|
-
send({
|
|
67
|
+
send({ conversation, payload }: EndpointSendRequest): Promise<string>;
|
|
68
68
|
recallMessage(messageId: string): Promise<void>;
|
|
69
69
|
admit(msg: DiscordInboundMessage): void;
|
|
70
70
|
}
|
package/lib/endpoint.js
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
* DiscordEndpoint — lifecycle, outbound, admit, gateway / interactions modes, agent tool surface.
|
|
3
3
|
*/
|
|
4
4
|
import { ChannelType } from 'discord.js';
|
|
5
|
-
import { formatLegacyMessageReference, nativeConversationId, parseLegacyMessageReference, } from '@zhin.js/im-contract';
|
|
5
|
+
import { formatLegacyConversationRef, formatLegacyMessageReference, nativeConversationId, parseLegacyMessageReference, } from '@zhin.js/im-contract';
|
|
6
6
|
import { formatCompact, getLogger } from '@zhin.js/logger';
|
|
7
7
|
import { registerDiscordAgentEndpoint } from './discord-agent-deps.js';
|
|
8
8
|
import { connectDiscordGatewayClient, defaultCreateClient, DEFAULT_INTENTS, resolveSenderRole, toMessageCreateOptions, } from './gateway.js';
|
|
9
|
-
import { formatButtonContent, formatButtonSegments, formatInboundContent, formatInboundSegments, formatOutboundBody, senderDisplayName, } from './protocol.js';
|
|
9
|
+
import { discordInboundConversation, formatButtonContent, formatButtonSegments, formatInboundContent, formatInboundSegments, formatOutboundBody, senderDisplayName, } from './protocol.js';
|
|
10
10
|
import { registerDiscordInteractionRoutes } from './webhook.js';
|
|
11
11
|
const DISCORD_API = 'https://discord.com/api/v10';
|
|
12
12
|
/** 出站 HTTP 调用统一 30s 超时。 */
|
|
@@ -90,10 +90,11 @@ export class DiscordGatewayEndpoint {
|
|
|
90
90
|
this.#started = false;
|
|
91
91
|
logger.debug(formatCompact({ op: 'disconnect', endpoint: this.#options.config.name }));
|
|
92
92
|
}
|
|
93
|
-
async send({
|
|
93
|
+
async send({ conversation, payload }) {
|
|
94
94
|
const body = formatOutboundBody(payload);
|
|
95
|
-
|
|
96
|
-
const
|
|
95
|
+
// recall 链路仍是本端点编码的 legacy 引用,在边界内部从 conversation 派生 target
|
|
96
|
+
const target = formatLegacyConversationRef(conversation);
|
|
97
|
+
const snowflake = await this.#sendBody(conversation.id, body);
|
|
97
98
|
const messageId = formatLegacyMessageReference({ target, messageId: snowflake });
|
|
98
99
|
logger.debug(formatCompact({
|
|
99
100
|
op: 'discord_send',
|
|
@@ -123,13 +124,13 @@ export class DiscordGatewayEndpoint {
|
|
|
123
124
|
return;
|
|
124
125
|
if (msg.authorBot)
|
|
125
126
|
return;
|
|
127
|
+
const conversation = discordInboundConversation(String(this.#options.id), msg);
|
|
126
128
|
void this.#options.gateway.receive({
|
|
127
|
-
|
|
128
|
-
|
|
129
|
+
conversation,
|
|
130
|
+
message: { conversation, id: msg.id },
|
|
129
131
|
content: formatInboundContent(msg),
|
|
130
132
|
segments: formatInboundSegments(msg),
|
|
131
133
|
sender: senderDisplayName(msg),
|
|
132
|
-
id: msg.id,
|
|
133
134
|
metadata: Object.freeze({
|
|
134
135
|
endpoint: this.#options.config.name,
|
|
135
136
|
channelKind: msg.channelKind,
|
|
@@ -142,7 +143,7 @@ export class DiscordGatewayEndpoint {
|
|
|
142
143
|
}).catch((err) => {
|
|
143
144
|
logger.warn(formatCompact({
|
|
144
145
|
op: 'discord_gateway_receive_failed',
|
|
145
|
-
target:
|
|
146
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
146
147
|
error: err instanceof Error ? err.message : String(err),
|
|
147
148
|
}));
|
|
148
149
|
});
|
|
@@ -151,13 +152,13 @@ export class DiscordGatewayEndpoint {
|
|
|
151
152
|
admitButton(interaction) {
|
|
152
153
|
if (!this.#open)
|
|
153
154
|
return;
|
|
155
|
+
const conversation = discordInboundConversation(String(this.#options.id), interaction);
|
|
154
156
|
void this.#options.gateway.receive({
|
|
155
|
-
|
|
156
|
-
|
|
157
|
+
conversation,
|
|
158
|
+
message: { conversation, id: interaction.id },
|
|
157
159
|
content: formatButtonContent(interaction),
|
|
158
160
|
segments: formatButtonSegments(interaction),
|
|
159
161
|
sender: interaction.userName,
|
|
160
|
-
id: interaction.id,
|
|
161
162
|
metadata: Object.freeze({
|
|
162
163
|
endpoint: this.#options.config.name,
|
|
163
164
|
eventType: 'button',
|
|
@@ -167,7 +168,7 @@ export class DiscordGatewayEndpoint {
|
|
|
167
168
|
}).catch((err) => {
|
|
168
169
|
logger.warn(formatCompact({
|
|
169
170
|
op: 'discord_gateway_receive_failed',
|
|
170
|
-
target:
|
|
171
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
171
172
|
error: err instanceof Error ? err.message : String(err),
|
|
172
173
|
}));
|
|
173
174
|
});
|
|
@@ -349,9 +350,9 @@ export class DiscordInteractionsEndpoint {
|
|
|
349
350
|
this.#started = false;
|
|
350
351
|
logger.debug(formatCompact({ op: 'disconnect', endpoint: this.#options.config.name }));
|
|
351
352
|
}
|
|
352
|
-
async send({
|
|
353
|
+
async send({ conversation, payload }) {
|
|
353
354
|
const body = formatOutboundBody(payload);
|
|
354
|
-
const channelId =
|
|
355
|
+
const channelId = conversation.id;
|
|
355
356
|
const response = await this.#fetch(`${DISCORD_API}/channels/${channelId}/messages`, {
|
|
356
357
|
method: 'POST',
|
|
357
358
|
headers: {
|
|
@@ -367,7 +368,13 @@ export class DiscordInteractionsEndpoint {
|
|
|
367
368
|
}
|
|
368
369
|
const data = JSON.parse(text);
|
|
369
370
|
const snowflake = data.id ?? '';
|
|
370
|
-
|
|
371
|
+
// recall 链路仍是本端点编码的 legacy 引用,在边界内部从 conversation 派生 target
|
|
372
|
+
return snowflake
|
|
373
|
+
? formatLegacyMessageReference({
|
|
374
|
+
target: formatLegacyConversationRef(conversation),
|
|
375
|
+
messageId: snowflake,
|
|
376
|
+
})
|
|
377
|
+
: '';
|
|
371
378
|
}
|
|
372
379
|
async recallMessage(messageId) {
|
|
373
380
|
if (!messageId)
|
|
@@ -390,13 +397,13 @@ export class DiscordInteractionsEndpoint {
|
|
|
390
397
|
admit(msg) {
|
|
391
398
|
if (!this.#open)
|
|
392
399
|
return;
|
|
400
|
+
const conversation = discordInboundConversation(String(this.#options.id), msg);
|
|
393
401
|
void this.#options.gateway.receive({
|
|
394
|
-
|
|
395
|
-
|
|
402
|
+
conversation,
|
|
403
|
+
message: { conversation, id: msg.id },
|
|
396
404
|
content: formatInboundContent(msg),
|
|
397
405
|
segments: formatInboundSegments(msg),
|
|
398
406
|
sender: senderDisplayName(msg),
|
|
399
|
-
id: msg.id,
|
|
400
407
|
metadata: Object.freeze({
|
|
401
408
|
endpoint: this.#options.config.name,
|
|
402
409
|
channelKind: msg.channelKind,
|
|
@@ -407,7 +414,7 @@ export class DiscordInteractionsEndpoint {
|
|
|
407
414
|
}).catch((err) => {
|
|
408
415
|
logger.warn(formatCompact({
|
|
409
416
|
op: 'discord_gateway_receive_failed',
|
|
410
|
-
target:
|
|
417
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
411
418
|
error: err instanceof Error ? err.message : String(err),
|
|
412
419
|
}));
|
|
413
420
|
});
|
package/lib/protocol.d.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Canonicalization is owned by gateway/core before endpoint.send.
|
|
4
4
|
*/
|
|
5
5
|
import type { Segment } from '@zhin.js/core/runtime';
|
|
6
|
+
import type { ConversationRef } from '@zhin.js/im-contract';
|
|
6
7
|
/** Plugin Runtime owner config (`plugins.<instanceKey>` / schema.json). */
|
|
7
8
|
export interface DiscordAdapterConfig {
|
|
8
9
|
readonly name?: string;
|
|
@@ -122,6 +123,16 @@ export interface DiscordOutboundBody {
|
|
|
122
123
|
}
|
|
123
124
|
export declare function resolveDiscordConfig(config?: DiscordAdapterConfig): ResolvedDiscordConfig;
|
|
124
125
|
export declare function resolveChannelKind(channelType: number | string | undefined): 'private' | 'group' | 'channel';
|
|
126
|
+
/**
|
|
127
|
+
* 入站归一化 → ConversationRef:DM → 'private';GroupDM → 'group';
|
|
128
|
+
* guild 频道 → 'channel' + guild 容器进 parent(guild 是频道容器,kind 'channel')。
|
|
129
|
+
* endpoint.adapter = owner PluginId(CapabilityId 以 \0 分隔)。
|
|
130
|
+
*/
|
|
131
|
+
export declare function discordInboundConversation(endpointId: string, msg: {
|
|
132
|
+
readonly channelId: string;
|
|
133
|
+
readonly channelKind: 'private' | 'group' | 'channel';
|
|
134
|
+
readonly guildId?: string;
|
|
135
|
+
}): ConversationRef;
|
|
125
136
|
export declare function senderDisplayName(msg: DiscordInboundMessage): string;
|
|
126
137
|
/** Build inbound text for MessageGateway.receive (gateway owns reply routing). */
|
|
127
138
|
export declare function formatInboundContent(msg: DiscordInboundMessage): string;
|
package/lib/protocol.js
CHANGED
|
@@ -59,6 +59,21 @@ export function resolveChannelKind(channelType) {
|
|
|
59
59
|
return 'group';
|
|
60
60
|
return 'channel';
|
|
61
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* 入站归一化 → ConversationRef:DM → 'private';GroupDM → 'group';
|
|
64
|
+
* guild 频道 → 'channel' + guild 容器进 parent(guild 是频道容器,kind 'channel')。
|
|
65
|
+
* endpoint.adapter = owner PluginId(CapabilityId 以 \0 分隔)。
|
|
66
|
+
*/
|
|
67
|
+
export function discordInboundConversation(endpointId, msg) {
|
|
68
|
+
return {
|
|
69
|
+
endpoint: { id: endpointId, adapter: endpointId.split('\0')[0] ?? endpointId },
|
|
70
|
+
kind: msg.channelKind,
|
|
71
|
+
id: msg.channelId,
|
|
72
|
+
...(msg.guildId && msg.channelKind === 'channel'
|
|
73
|
+
? { parent: { kind: 'channel', id: msg.guildId } }
|
|
74
|
+
: {}),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
62
77
|
export function senderDisplayName(msg) {
|
|
63
78
|
return msg.authorName || msg.authorId;
|
|
64
79
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/adapter-discord",
|
|
3
|
-
"version": "7.0.
|
|
3
|
+
"version": "7.0.3",
|
|
4
4
|
"description": "Zhin.js Discord adapter for Plugin Runtime (Gateway WebSocket)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -33,21 +33,21 @@
|
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"discord.js": "^14.27.0",
|
|
36
|
-
"@zhin.js/adapter": "1.1.
|
|
37
|
-
"@zhin.js/command": "1.0.
|
|
38
|
-
"@zhin.js/core": "1.5.
|
|
39
|
-
"@zhin.js/host-http": "1.0.
|
|
40
|
-
"@zhin.js/im-contract": "1.0.
|
|
36
|
+
"@zhin.js/adapter": "1.1.5",
|
|
37
|
+
"@zhin.js/command": "1.0.7",
|
|
38
|
+
"@zhin.js/core": "1.5.2",
|
|
39
|
+
"@zhin.js/host-http": "1.0.6",
|
|
40
|
+
"@zhin.js/im-contract": "1.0.1",
|
|
41
41
|
"@zhin.js/logger": "1.0.75",
|
|
42
|
-
"@zhin.js/plugin-runtime": "1.1.
|
|
42
|
+
"@zhin.js/plugin-runtime": "1.1.3"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
|
45
45
|
"zod": "^4.0.0",
|
|
46
|
-
"@zhin.js/adapter": "1.1.
|
|
47
|
-
"@zhin.js/agent": "1.1.
|
|
48
|
-
"@zhin.js/core": "1.5.
|
|
49
|
-
"@zhin.js/plugin-runtime": "1.1.
|
|
50
|
-
"zhin.js": "6.0.
|
|
46
|
+
"@zhin.js/adapter": "1.1.5",
|
|
47
|
+
"@zhin.js/agent": "1.1.3",
|
|
48
|
+
"@zhin.js/core": "1.5.2",
|
|
49
|
+
"@zhin.js/plugin-runtime": "1.1.3",
|
|
50
|
+
"zhin.js": "6.0.2"
|
|
51
51
|
},
|
|
52
52
|
"peerDependenciesMeta": {
|
|
53
53
|
"zhin.js": {
|
|
@@ -65,9 +65,9 @@
|
|
|
65
65
|
"typescript": "^6.0.3",
|
|
66
66
|
"vitest": "^4.1.10",
|
|
67
67
|
"zod": "^4.4.3",
|
|
68
|
-
"@zhin.js/agent": "1.1.
|
|
69
|
-
"@zhin.js/host-http": "1.0.
|
|
70
|
-
"zhin.js": "6.0.
|
|
68
|
+
"@zhin.js/agent": "1.1.3",
|
|
69
|
+
"@zhin.js/host-http": "1.0.6",
|
|
70
|
+
"zhin.js": "6.0.2"
|
|
71
71
|
},
|
|
72
72
|
"files": [
|
|
73
73
|
"adapters",
|
package/src/endpoint.ts
CHANGED
|
@@ -13,6 +13,7 @@ import type {
|
|
|
13
13
|
import type { MessageGateway } from '@zhin.js/core/runtime';
|
|
14
14
|
import type { HttpHost, HttpRouteRegistration } from '@zhin.js/host-http';
|
|
15
15
|
import {
|
|
16
|
+
formatLegacyConversationRef,
|
|
16
17
|
formatLegacyMessageReference,
|
|
17
18
|
nativeConversationId,
|
|
18
19
|
parseLegacyMessageReference,
|
|
@@ -30,6 +31,7 @@ import {
|
|
|
30
31
|
type DiscordClientTransport,
|
|
31
32
|
} from './gateway.js';
|
|
32
33
|
import {
|
|
34
|
+
discordInboundConversation,
|
|
33
35
|
formatButtonContent,
|
|
34
36
|
formatButtonSegments,
|
|
35
37
|
formatInboundContent,
|
|
@@ -145,10 +147,11 @@ export class DiscordGatewayEndpoint implements EndpointInstance {
|
|
|
145
147
|
logger.debug(formatCompact({ op: 'disconnect', endpoint: this.#options.config.name }));
|
|
146
148
|
}
|
|
147
149
|
|
|
148
|
-
async send({
|
|
150
|
+
async send({ conversation, payload }: EndpointSendRequest): Promise<string> {
|
|
149
151
|
const body = formatOutboundBody(payload);
|
|
150
|
-
|
|
151
|
-
const
|
|
152
|
+
// recall 链路仍是本端点编码的 legacy 引用,在边界内部从 conversation 派生 target
|
|
153
|
+
const target = formatLegacyConversationRef(conversation);
|
|
154
|
+
const snowflake = await this.#sendBody(conversation.id, body);
|
|
152
155
|
const messageId = formatLegacyMessageReference({ target, messageId: snowflake });
|
|
153
156
|
logger.debug(formatCompact({
|
|
154
157
|
op: 'discord_send',
|
|
@@ -175,13 +178,13 @@ export class DiscordGatewayEndpoint implements EndpointInstance {
|
|
|
175
178
|
admit(msg: DiscordInboundMessage): void {
|
|
176
179
|
if (!this.#open) return;
|
|
177
180
|
if (msg.authorBot) return;
|
|
181
|
+
const conversation = discordInboundConversation(String(this.#options.id), msg);
|
|
178
182
|
void this.#options.gateway.receive({
|
|
179
|
-
|
|
180
|
-
|
|
183
|
+
conversation,
|
|
184
|
+
message: { conversation, id: msg.id },
|
|
181
185
|
content: formatInboundContent(msg),
|
|
182
186
|
segments: formatInboundSegments(msg),
|
|
183
187
|
sender: senderDisplayName(msg),
|
|
184
|
-
id: msg.id,
|
|
185
188
|
metadata: Object.freeze({
|
|
186
189
|
endpoint: this.#options.config.name,
|
|
187
190
|
channelKind: msg.channelKind,
|
|
@@ -194,7 +197,7 @@ export class DiscordGatewayEndpoint implements EndpointInstance {
|
|
|
194
197
|
}).catch((err) => {
|
|
195
198
|
logger.warn(formatCompact({
|
|
196
199
|
op: 'discord_gateway_receive_failed',
|
|
197
|
-
target:
|
|
200
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
198
201
|
error: err instanceof Error ? err.message : String(err),
|
|
199
202
|
}));
|
|
200
203
|
});
|
|
@@ -203,13 +206,13 @@ export class DiscordGatewayEndpoint implements EndpointInstance {
|
|
|
203
206
|
/** Test / internal: admit a button interaction when open. */
|
|
204
207
|
admitButton(interaction: DiscordButtonInbound): void {
|
|
205
208
|
if (!this.#open) return;
|
|
209
|
+
const conversation = discordInboundConversation(String(this.#options.id), interaction);
|
|
206
210
|
void this.#options.gateway.receive({
|
|
207
|
-
|
|
208
|
-
|
|
211
|
+
conversation,
|
|
212
|
+
message: { conversation, id: interaction.id },
|
|
209
213
|
content: formatButtonContent(interaction),
|
|
210
214
|
segments: formatButtonSegments(interaction),
|
|
211
215
|
sender: interaction.userName,
|
|
212
|
-
id: interaction.id,
|
|
213
216
|
metadata: Object.freeze({
|
|
214
217
|
endpoint: this.#options.config.name,
|
|
215
218
|
eventType: 'button',
|
|
@@ -219,7 +222,7 @@ export class DiscordGatewayEndpoint implements EndpointInstance {
|
|
|
219
222
|
}).catch((err) => {
|
|
220
223
|
logger.warn(formatCompact({
|
|
221
224
|
op: 'discord_gateway_receive_failed',
|
|
222
|
-
target:
|
|
225
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
223
226
|
error: err instanceof Error ? err.message : String(err),
|
|
224
227
|
}));
|
|
225
228
|
});
|
|
@@ -465,9 +468,9 @@ export class DiscordInteractionsEndpoint implements EndpointInstance {
|
|
|
465
468
|
logger.debug(formatCompact({ op: 'disconnect', endpoint: this.#options.config.name }));
|
|
466
469
|
}
|
|
467
470
|
|
|
468
|
-
async send({
|
|
471
|
+
async send({ conversation, payload }: EndpointSendRequest): Promise<string> {
|
|
469
472
|
const body = formatOutboundBody(payload);
|
|
470
|
-
const channelId =
|
|
473
|
+
const channelId = conversation.id;
|
|
471
474
|
const response = await this.#fetch(`${DISCORD_API}/channels/${channelId}/messages`, {
|
|
472
475
|
method: 'POST',
|
|
473
476
|
headers: {
|
|
@@ -483,7 +486,13 @@ export class DiscordInteractionsEndpoint implements EndpointInstance {
|
|
|
483
486
|
}
|
|
484
487
|
const data = JSON.parse(text) as { id?: string };
|
|
485
488
|
const snowflake = data.id ?? '';
|
|
486
|
-
|
|
489
|
+
// recall 链路仍是本端点编码的 legacy 引用,在边界内部从 conversation 派生 target
|
|
490
|
+
return snowflake
|
|
491
|
+
? formatLegacyMessageReference({
|
|
492
|
+
target: formatLegacyConversationRef(conversation),
|
|
493
|
+
messageId: snowflake,
|
|
494
|
+
})
|
|
495
|
+
: '';
|
|
487
496
|
}
|
|
488
497
|
|
|
489
498
|
async recallMessage(messageId: string): Promise<void> {
|
|
@@ -505,13 +514,13 @@ export class DiscordInteractionsEndpoint implements EndpointInstance {
|
|
|
505
514
|
|
|
506
515
|
admit(msg: DiscordInboundMessage): void {
|
|
507
516
|
if (!this.#open) return;
|
|
517
|
+
const conversation = discordInboundConversation(String(this.#options.id), msg);
|
|
508
518
|
void this.#options.gateway.receive({
|
|
509
|
-
|
|
510
|
-
|
|
519
|
+
conversation,
|
|
520
|
+
message: { conversation, id: msg.id },
|
|
511
521
|
content: formatInboundContent(msg),
|
|
512
522
|
segments: formatInboundSegments(msg),
|
|
513
523
|
sender: senderDisplayName(msg),
|
|
514
|
-
id: msg.id,
|
|
515
524
|
metadata: Object.freeze({
|
|
516
525
|
endpoint: this.#options.config.name,
|
|
517
526
|
channelKind: msg.channelKind,
|
|
@@ -522,7 +531,7 @@ export class DiscordInteractionsEndpoint implements EndpointInstance {
|
|
|
522
531
|
}).catch((err) => {
|
|
523
532
|
logger.warn(formatCompact({
|
|
524
533
|
op: 'discord_gateway_receive_failed',
|
|
525
|
-
target:
|
|
534
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
526
535
|
error: err instanceof Error ? err.message : String(err),
|
|
527
536
|
}));
|
|
528
537
|
});
|
package/src/protocol.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { createPublicKey, verify as cryptoVerify } from 'node:crypto';
|
|
7
7
|
import { isMediaRef } from '@zhin.js/core';
|
|
8
8
|
import type { Segment } from '@zhin.js/core/runtime';
|
|
9
|
+
import type { ConversationRef } from '@zhin.js/im-contract';
|
|
9
10
|
import { formatCompact, getLogger } from '@zhin.js/logger';
|
|
10
11
|
|
|
11
12
|
const logger = getLogger('discord');
|
|
@@ -199,6 +200,29 @@ export function resolveChannelKind(channelType: number | string | undefined): 'p
|
|
|
199
200
|
return 'channel';
|
|
200
201
|
}
|
|
201
202
|
|
|
203
|
+
/**
|
|
204
|
+
* 入站归一化 → ConversationRef:DM → 'private';GroupDM → 'group';
|
|
205
|
+
* guild 频道 → 'channel' + guild 容器进 parent(guild 是频道容器,kind 'channel')。
|
|
206
|
+
* endpoint.adapter = owner PluginId(CapabilityId 以 \0 分隔)。
|
|
207
|
+
*/
|
|
208
|
+
export function discordInboundConversation(
|
|
209
|
+
endpointId: string,
|
|
210
|
+
msg: {
|
|
211
|
+
readonly channelId: string;
|
|
212
|
+
readonly channelKind: 'private' | 'group' | 'channel';
|
|
213
|
+
readonly guildId?: string;
|
|
214
|
+
},
|
|
215
|
+
): ConversationRef {
|
|
216
|
+
return {
|
|
217
|
+
endpoint: { id: endpointId, adapter: endpointId.split('\0')[0] ?? endpointId },
|
|
218
|
+
kind: msg.channelKind,
|
|
219
|
+
id: msg.channelId,
|
|
220
|
+
...(msg.guildId && msg.channelKind === 'channel'
|
|
221
|
+
? { parent: { kind: 'channel' as const, id: msg.guildId } }
|
|
222
|
+
: {}),
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
202
226
|
export function senderDisplayName(msg: DiscordInboundMessage): string {
|
|
203
227
|
return msg.authorName || msg.authorId;
|
|
204
228
|
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|