@zhin.js/adapter-discord 7.0.2 → 7.0.5
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 +95 -0
- package/README.md +3 -3
- package/adapters/discord.js +2 -2
- package/adapters/discord.ts +2 -2
- package/lib/discord-agent-deps.d.ts +3 -3
- package/lib/discord-agent-deps.js +7 -7
- package/lib/endpoint.d.ts +2 -2
- package/lib/endpoint.js +56 -44
- package/lib/index.d.ts +1 -1
- package/lib/index.js +1 -1
- package/lib/platform-permit.d.ts +2 -3
- package/lib/platform-permit.js +3 -13
- package/lib/protocol.d.ts +15 -4
- package/lib/protocol.js +19 -4
- package/package.json +17 -16
- package/plugin.js +6 -4
- package/schema.json +41 -5
- package/src/discord-agent-deps.ts +9 -9
- package/src/endpoint.ts +59 -44
- package/src/index.ts +0 -1
- package/src/platform-permit.ts +4 -11
- package/src/protocol.ts +32 -8
- /package/commands/endpoint/add/{[name:string].js → [id].js} +0 -0
- /package/commands/endpoint/add/{[name:string].ts → [id].ts} +0 -0
- /package/commands/endpoint/remove/{[name:string].js → [id].js} +0 -0
- /package/commands/endpoint/remove/{[name:string].ts → [id].ts} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,100 @@
|
|
|
1
1
|
# @zhin.js/adapter-discord
|
|
2
2
|
|
|
3
|
+
## 7.0.5
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 51015d6: refactor(adapters): migrate platform-permit to PermissionSubject
|
|
8
|
+
|
|
9
|
+
All adapter platform-permit checkers now accept PermissionSubject
|
|
10
|
+
(duck-typed from Message/CommandSession) instead of Message directly.
|
|
11
|
+
registerPlatformPermitChecker is replaced by host.registerPlatform in
|
|
12
|
+
the adapter plugin setup. activity-feedback service updated for new
|
|
13
|
+
PermissionHost integration.
|
|
14
|
+
|
|
15
|
+
- Updated dependencies [6f9c366]
|
|
16
|
+
- Updated dependencies [373a56b]
|
|
17
|
+
- Updated dependencies [0de46a8]
|
|
18
|
+
- Updated dependencies [c106ecc]
|
|
19
|
+
- Updated dependencies [ca92e03]
|
|
20
|
+
- Updated dependencies [b0f37ae]
|
|
21
|
+
- Updated dependencies [ba08a2f]
|
|
22
|
+
- Updated dependencies [b08f7fe]
|
|
23
|
+
- Updated dependencies [daffd4c]
|
|
24
|
+
- Updated dependencies [36c7400]
|
|
25
|
+
- Updated dependencies [a9fa72e]
|
|
26
|
+
- Updated dependencies [c8de3ef]
|
|
27
|
+
- Updated dependencies [60f0fc8]
|
|
28
|
+
- Updated dependencies [574c990]
|
|
29
|
+
- Updated dependencies [d096f16]
|
|
30
|
+
- Updated dependencies [3f29623]
|
|
31
|
+
- Updated dependencies [2916852]
|
|
32
|
+
- Updated dependencies [d047869]
|
|
33
|
+
- Updated dependencies [162fa34]
|
|
34
|
+
- Updated dependencies [e62561e]
|
|
35
|
+
- Updated dependencies [3eeeb46]
|
|
36
|
+
- Updated dependencies [85d0f82]
|
|
37
|
+
- Updated dependencies [61bfc1c]
|
|
38
|
+
- Updated dependencies [04bad47]
|
|
39
|
+
- Updated dependencies [e40b048]
|
|
40
|
+
- Updated dependencies [5a5b1bb]
|
|
41
|
+
- Updated dependencies [f1708c3]
|
|
42
|
+
- Updated dependencies [d254a81]
|
|
43
|
+
- Updated dependencies [7123c47]
|
|
44
|
+
- Updated dependencies [05befc1]
|
|
45
|
+
- Updated dependencies [0663b6a]
|
|
46
|
+
- Updated dependencies [f28f9b3]
|
|
47
|
+
- Updated dependencies [9f340f7]
|
|
48
|
+
- Updated dependencies [e53444f]
|
|
49
|
+
- Updated dependencies [5eedd26]
|
|
50
|
+
- Updated dependencies [92b0dd7]
|
|
51
|
+
- Updated dependencies [f919b6f]
|
|
52
|
+
- Updated dependencies [098e411]
|
|
53
|
+
- Updated dependencies [e1b7c01]
|
|
54
|
+
- Updated dependencies [9b94f87]
|
|
55
|
+
- Updated dependencies [a7df753]
|
|
56
|
+
- @zhin.js/agent@1.1.5
|
|
57
|
+
- @zhin.js/host-http@1.0.8
|
|
58
|
+
- @zhin.js/permission@1.0.1
|
|
59
|
+
- @zhin.js/im-contract@1.0.3
|
|
60
|
+
- @zhin.js/adapter@1.1.7
|
|
61
|
+
- @zhin.js/plugin-runtime@1.1.5
|
|
62
|
+
- @zhin.js/command@1.0.9
|
|
63
|
+
- @zhin.js/core@1.5.4
|
|
64
|
+
- zhin.js@6.0.4
|
|
65
|
+
|
|
66
|
+
## 7.0.4
|
|
67
|
+
|
|
68
|
+
### Patch Changes
|
|
69
|
+
|
|
70
|
+
- f8c7a54: fix: im
|
|
71
|
+
- Updated dependencies [f8c7a54]
|
|
72
|
+
- @zhin.js/logger@1.0.76
|
|
73
|
+
- @zhin.js/host-http@1.0.7
|
|
74
|
+
- @zhin.js/adapter@1.1.6
|
|
75
|
+
- @zhin.js/agent@1.1.4
|
|
76
|
+
- @zhin.js/command@1.0.8
|
|
77
|
+
- @zhin.js/core@1.5.3
|
|
78
|
+
- @zhin.js/im-contract@1.0.2
|
|
79
|
+
- @zhin.js/plugin-runtime@1.1.4
|
|
80
|
+
- zhin.js@6.0.3
|
|
81
|
+
|
|
82
|
+
## 7.0.3
|
|
83
|
+
|
|
84
|
+
### Patch Changes
|
|
85
|
+
|
|
86
|
+
- Updated dependencies [afc0e66]
|
|
87
|
+
- Updated dependencies [2e41ad5]
|
|
88
|
+
- Updated dependencies [9f57124]
|
|
89
|
+
- @zhin.js/core@1.5.2
|
|
90
|
+
- @zhin.js/adapter@1.1.5
|
|
91
|
+
- @zhin.js/im-contract@1.0.1
|
|
92
|
+
- @zhin.js/plugin-runtime@1.1.3
|
|
93
|
+
- @zhin.js/command@1.0.7
|
|
94
|
+
- @zhin.js/host-http@1.0.6
|
|
95
|
+
- @zhin.js/agent@1.1.3
|
|
96
|
+
- zhin.js@6.0.2
|
|
97
|
+
|
|
3
98
|
## 7.0.2
|
|
4
99
|
|
|
5
100
|
### 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/adapters/discord.js
CHANGED
|
@@ -21,8 +21,8 @@ export default defineAdapter({
|
|
|
21
21
|
const config = resolveDiscordConfig(context.config);
|
|
22
22
|
const gateway = context.use(messageGatewayToken);
|
|
23
23
|
// 注册到插件运行时状态(discord.endpoint list 的"运行中"数据源)
|
|
24
|
-
context.use(discordRuntimeStateToken).endpoints.set(config.
|
|
25
|
-
|
|
24
|
+
context.use(discordRuntimeStateToken).endpoints.set(config.id, {
|
|
25
|
+
id: config.id,
|
|
26
26
|
mode: config.connection,
|
|
27
27
|
});
|
|
28
28
|
if (config.connection === 'interactions') {
|
package/adapters/discord.ts
CHANGED
|
@@ -37,8 +37,8 @@ export default defineAdapter<DiscordAdapterConfig>({
|
|
|
37
37
|
const config = resolveDiscordConfig(context.config);
|
|
38
38
|
const gateway = context.use(messageGatewayToken);
|
|
39
39
|
// 注册到插件运行时状态(discord.endpoint list 的"运行中"数据源)
|
|
40
|
-
context.use(discordRuntimeStateToken).endpoints.set(config.
|
|
41
|
-
|
|
40
|
+
context.use(discordRuntimeStateToken).endpoints.set(config.id, {
|
|
41
|
+
id: config.id,
|
|
42
42
|
mode: config.connection,
|
|
43
43
|
});
|
|
44
44
|
if (config.connection === 'interactions') {
|
|
@@ -25,11 +25,11 @@ export interface DiscordAgentEndpoint {
|
|
|
25
25
|
getGuildInfo(guildId: string): Promise<unknown>;
|
|
26
26
|
}
|
|
27
27
|
export interface DiscordAgentDeps {
|
|
28
|
-
getEndpoint: (
|
|
28
|
+
getEndpoint: (endpointKey: string) => DiscordAgentEndpoint;
|
|
29
29
|
/** Alias kept for existing agent/tools that call getGatewayEndpoint. */
|
|
30
|
-
getGatewayEndpoint: (
|
|
30
|
+
getGatewayEndpoint: (endpointKey: string) => DiscordAgentEndpoint;
|
|
31
31
|
}
|
|
32
|
-
export declare function registerDiscordAgentEndpoint(
|
|
32
|
+
export declare function registerDiscordAgentEndpoint(endpointKey: string, endpoint: DiscordAgentEndpoint): () => void;
|
|
33
33
|
/** Optional override used by tests / transitional callers. Pass `null` to clear. */
|
|
34
34
|
export declare function setDiscordAgentDeps(deps: DiscordAgentDeps | null): void;
|
|
35
35
|
export declare function getDiscordAgentDeps(): DiscordAgentDeps;
|
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
*/
|
|
5
5
|
const endpoints = new Map();
|
|
6
6
|
let override = null;
|
|
7
|
-
export function registerDiscordAgentEndpoint(
|
|
8
|
-
endpoints.set(
|
|
7
|
+
export function registerDiscordAgentEndpoint(endpointKey, endpoint) {
|
|
8
|
+
endpoints.set(endpointKey, endpoint);
|
|
9
9
|
return () => {
|
|
10
|
-
if (endpoints.get(
|
|
11
|
-
endpoints.delete(
|
|
10
|
+
if (endpoints.get(endpointKey) === endpoint) {
|
|
11
|
+
endpoints.delete(endpointKey);
|
|
12
12
|
}
|
|
13
13
|
};
|
|
14
14
|
}
|
|
@@ -16,10 +16,10 @@ export function registerDiscordAgentEndpoint(endpointId, endpoint) {
|
|
|
16
16
|
export function setDiscordAgentDeps(deps) {
|
|
17
17
|
override = deps;
|
|
18
18
|
}
|
|
19
|
-
function lookup(
|
|
20
|
-
const registered = endpoints.get(
|
|
19
|
+
function lookup(endpointKey) {
|
|
20
|
+
const registered = endpoints.get(endpointKey);
|
|
21
21
|
if (!registered)
|
|
22
|
-
throw new Error(`Endpoint ${
|
|
22
|
+
throw new Error(`Endpoint ${endpointKey} 不存在`);
|
|
23
23
|
return registered;
|
|
24
24
|
}
|
|
25
25
|
export function getDiscordAgentDeps() {
|
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,17 +2,17 @@
|
|
|
2
2
|
* DiscordEndpoint — lifecycle, outbound, admit, gateway / interactions modes, agent tool surface.
|
|
3
3
|
*/
|
|
4
4
|
import { ChannelType } from 'discord.js';
|
|
5
|
-
import {
|
|
6
|
-
import { formatCompact,
|
|
5
|
+
import { formatLegacyConversationRef, formatLegacyMessageRef, nativeConversationId, parseLegacyMessageReference, } from '@zhin.js/im-contract';
|
|
6
|
+
import { formatCompact, getAdapterLogger } 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 超时。 */
|
|
13
13
|
const OUTBOUND_TIMEOUT_MS = 30_000;
|
|
14
|
-
const logger = getLogger('discord');
|
|
15
14
|
export class DiscordGatewayEndpoint {
|
|
15
|
+
#logger;
|
|
16
16
|
#options;
|
|
17
17
|
#createClient;
|
|
18
18
|
#client = null;
|
|
@@ -37,6 +37,7 @@ export class DiscordGatewayEndpoint {
|
|
|
37
37
|
},
|
|
38
38
|
});
|
|
39
39
|
constructor(options) {
|
|
40
|
+
this.#logger = getAdapterLogger('discord', options.config.id);
|
|
40
41
|
this.#options = options;
|
|
41
42
|
this.#createClient = options.createClient ?? defaultCreateClient;
|
|
42
43
|
}
|
|
@@ -45,7 +46,7 @@ export class DiscordGatewayEndpoint {
|
|
|
45
46
|
return;
|
|
46
47
|
this.#started = true;
|
|
47
48
|
try {
|
|
48
|
-
this.#unregisterAgent = registerDiscordAgentEndpoint(this.#options.config.
|
|
49
|
+
this.#unregisterAgent = registerDiscordAgentEndpoint(this.#options.config.id, this);
|
|
49
50
|
const intents = this.#options.config.intents?.length
|
|
50
51
|
? [...this.#options.config.intents]
|
|
51
52
|
: DEFAULT_INTENTS;
|
|
@@ -54,16 +55,16 @@ export class DiscordGatewayEndpoint {
|
|
|
54
55
|
onMessage: (msg) => this.admit(msg),
|
|
55
56
|
onButton: (interaction) => this.admitButton(interaction),
|
|
56
57
|
});
|
|
57
|
-
logger.info(formatCompact({
|
|
58
|
+
this.#logger.info(formatCompact({
|
|
58
59
|
op: 'connect',
|
|
59
|
-
endpoint: this.#options.config.
|
|
60
|
+
endpoint: this.#options.config.id,
|
|
60
61
|
mode: 'gateway',
|
|
61
62
|
user: this.#client.user?.tag,
|
|
62
63
|
}));
|
|
63
64
|
}
|
|
64
65
|
catch (error) {
|
|
65
66
|
await this.stop();
|
|
66
|
-
logger.error('Failed to connect Discord gateway:', error);
|
|
67
|
+
this.#logger.error('Failed to connect Discord gateway:', error);
|
|
67
68
|
throw error;
|
|
68
69
|
}
|
|
69
70
|
}
|
|
@@ -88,17 +89,17 @@ export class DiscordGatewayEndpoint {
|
|
|
88
89
|
this.#client = null;
|
|
89
90
|
}
|
|
90
91
|
this.#started = false;
|
|
91
|
-
logger.debug(formatCompact({ op: 'disconnect'
|
|
92
|
+
this.#logger.debug(formatCompact({ op: 'disconnect' }));
|
|
92
93
|
}
|
|
93
|
-
async send({
|
|
94
|
+
async send({ conversation, payload }) {
|
|
94
95
|
const body = formatOutboundBody(payload);
|
|
95
|
-
|
|
96
|
-
const snowflake = await this.#sendBody(
|
|
97
|
-
const messageId =
|
|
98
|
-
logger.debug(formatCompact({
|
|
96
|
+
// recall 链路仍是本端点编码的 legacy 引用,在边界内部从 conversation 派生
|
|
97
|
+
const snowflake = await this.#sendBody(conversation.id, body);
|
|
98
|
+
const messageId = formatLegacyMessageRef({ conversation, id: snowflake });
|
|
99
|
+
this.#logger.debug(formatCompact({
|
|
99
100
|
op: 'discord_send',
|
|
100
|
-
endpoint: this.#options.config.
|
|
101
|
-
target,
|
|
101
|
+
endpoint: this.#options.config.id,
|
|
102
|
+
target: formatLegacyConversationRef(conversation),
|
|
102
103
|
messageId,
|
|
103
104
|
}));
|
|
104
105
|
return messageId;
|
|
@@ -123,26 +124,30 @@ 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
|
-
sender:
|
|
132
|
-
|
|
133
|
+
sender: {
|
|
134
|
+
id: msg.authorId,
|
|
135
|
+
name: senderDisplayName(msg) || undefined,
|
|
136
|
+
...(resolveSenderRole(msg) ? { roles: [resolveSenderRole(msg)] } : {}),
|
|
137
|
+
},
|
|
138
|
+
endpointId: this.#options.config.id,
|
|
139
|
+
...(msg.mentionedBot ? { mentioned: true } : {}),
|
|
133
140
|
metadata: Object.freeze({
|
|
134
|
-
endpoint: this.#options.config.name,
|
|
135
141
|
channelKind: msg.channelKind,
|
|
136
142
|
userId: msg.authorId,
|
|
137
143
|
guildId: msg.guildId,
|
|
138
144
|
permissions: msg.permissionTokens,
|
|
139
145
|
role: resolveSenderRole(msg),
|
|
140
|
-
...(msg.mentionedBot ? { mentioned: true } : {}),
|
|
141
146
|
}),
|
|
142
147
|
}).catch((err) => {
|
|
143
|
-
logger.warn(formatCompact({
|
|
148
|
+
this.#logger.warn(formatCompact({
|
|
144
149
|
op: 'discord_gateway_receive_failed',
|
|
145
|
-
target:
|
|
150
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
146
151
|
error: err instanceof Error ? err.message : String(err),
|
|
147
152
|
}));
|
|
148
153
|
});
|
|
@@ -151,23 +156,23 @@ export class DiscordGatewayEndpoint {
|
|
|
151
156
|
admitButton(interaction) {
|
|
152
157
|
if (!this.#open)
|
|
153
158
|
return;
|
|
159
|
+
const conversation = discordInboundConversation(String(this.#options.id), interaction);
|
|
154
160
|
void this.#options.gateway.receive({
|
|
155
|
-
|
|
156
|
-
|
|
161
|
+
conversation,
|
|
162
|
+
message: { conversation, id: interaction.id },
|
|
157
163
|
content: formatButtonContent(interaction),
|
|
158
164
|
segments: formatButtonSegments(interaction),
|
|
159
|
-
sender: interaction.userName,
|
|
160
|
-
|
|
165
|
+
sender: { id: interaction.userId, name: interaction.userName },
|
|
166
|
+
endpointId: this.#options.config.id,
|
|
161
167
|
metadata: Object.freeze({
|
|
162
|
-
endpoint: this.#options.config.name,
|
|
163
168
|
eventType: 'button',
|
|
164
169
|
payload: interaction.customId,
|
|
165
170
|
sourceMessageId: interaction.sourceMessageId,
|
|
166
171
|
}),
|
|
167
172
|
}).catch((err) => {
|
|
168
|
-
logger.warn(formatCompact({
|
|
173
|
+
this.#logger.warn(formatCompact({
|
|
169
174
|
op: 'discord_gateway_receive_failed',
|
|
170
|
-
target:
|
|
175
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
171
176
|
error: err instanceof Error ? err.message : String(err),
|
|
172
177
|
}));
|
|
173
178
|
});
|
|
@@ -306,6 +311,7 @@ export class DiscordGatewayEndpoint {
|
|
|
306
311
|
}
|
|
307
312
|
}
|
|
308
313
|
export class DiscordInteractionsEndpoint {
|
|
314
|
+
#logger;
|
|
309
315
|
#options;
|
|
310
316
|
#fetch;
|
|
311
317
|
#routeReleases = [];
|
|
@@ -315,6 +321,7 @@ export class DiscordInteractionsEndpoint {
|
|
|
315
321
|
recall: (messageId) => this.recallMessage(messageId),
|
|
316
322
|
});
|
|
317
323
|
constructor(options) {
|
|
324
|
+
this.#logger = getAdapterLogger('discord', options.config.id);
|
|
318
325
|
this.#options = options;
|
|
319
326
|
this.#fetch = options.fetch ?? globalThis.fetch;
|
|
320
327
|
}
|
|
@@ -329,9 +336,9 @@ export class DiscordInteractionsEndpoint {
|
|
|
329
336
|
return;
|
|
330
337
|
this.#started = true;
|
|
331
338
|
this.#routeReleases.push(...registerDiscordInteractionRoutes(this.#options.http, this));
|
|
332
|
-
logger.info(formatCompact({
|
|
339
|
+
this.#logger.info(formatCompact({
|
|
333
340
|
op: 'connect',
|
|
334
|
-
endpoint: this.#options.config.
|
|
341
|
+
endpoint: this.#options.config.id,
|
|
335
342
|
mode: 'interactions',
|
|
336
343
|
path: this.#options.config.interactionsPath,
|
|
337
344
|
}));
|
|
@@ -347,11 +354,11 @@ export class DiscordInteractionsEndpoint {
|
|
|
347
354
|
for (const release of this.#routeReleases.splice(0))
|
|
348
355
|
release();
|
|
349
356
|
this.#started = false;
|
|
350
|
-
logger.debug(formatCompact({ op: 'disconnect'
|
|
357
|
+
this.#logger.debug(formatCompact({ op: 'disconnect' }));
|
|
351
358
|
}
|
|
352
|
-
async send({
|
|
359
|
+
async send({ conversation, payload }) {
|
|
353
360
|
const body = formatOutboundBody(payload);
|
|
354
|
-
const channelId =
|
|
361
|
+
const channelId = conversation.id;
|
|
355
362
|
const response = await this.#fetch(`${DISCORD_API}/channels/${channelId}/messages`, {
|
|
356
363
|
method: 'POST',
|
|
357
364
|
headers: {
|
|
@@ -367,7 +374,8 @@ export class DiscordInteractionsEndpoint {
|
|
|
367
374
|
}
|
|
368
375
|
const data = JSON.parse(text);
|
|
369
376
|
const snowflake = data.id ?? '';
|
|
370
|
-
|
|
377
|
+
// recall 链路仍是本端点编码的 legacy 引用,在边界内部从 conversation 派生
|
|
378
|
+
return snowflake ? formatLegacyMessageRef({ conversation, id: snowflake }) : '';
|
|
371
379
|
}
|
|
372
380
|
async recallMessage(messageId) {
|
|
373
381
|
if (!messageId)
|
|
@@ -390,24 +398,28 @@ export class DiscordInteractionsEndpoint {
|
|
|
390
398
|
admit(msg) {
|
|
391
399
|
if (!this.#open)
|
|
392
400
|
return;
|
|
401
|
+
const conversation = discordInboundConversation(String(this.#options.id), msg);
|
|
393
402
|
void this.#options.gateway.receive({
|
|
394
|
-
|
|
395
|
-
|
|
403
|
+
conversation,
|
|
404
|
+
message: { conversation, id: msg.id },
|
|
396
405
|
content: formatInboundContent(msg),
|
|
397
406
|
segments: formatInboundSegments(msg),
|
|
398
|
-
sender:
|
|
399
|
-
|
|
407
|
+
sender: {
|
|
408
|
+
id: msg.authorId,
|
|
409
|
+
name: senderDisplayName(msg) || undefined,
|
|
410
|
+
...(resolveSenderRole(msg) ? { roles: [resolveSenderRole(msg)] } : {}),
|
|
411
|
+
},
|
|
412
|
+
endpointId: this.#options.config.id,
|
|
400
413
|
metadata: Object.freeze({
|
|
401
|
-
endpoint: this.#options.config.name,
|
|
402
414
|
channelKind: msg.channelKind,
|
|
403
415
|
userId: msg.authorId,
|
|
404
416
|
guildId: msg.guildId,
|
|
405
417
|
eventType: 'application_command',
|
|
406
418
|
}),
|
|
407
419
|
}).catch((err) => {
|
|
408
|
-
logger.warn(formatCompact({
|
|
420
|
+
this.#logger.warn(formatCompact({
|
|
409
421
|
op: 'discord_gateway_receive_failed',
|
|
410
|
-
target:
|
|
422
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
411
423
|
error: err instanceof Error ? err.message : String(err),
|
|
412
424
|
}));
|
|
413
425
|
});
|
package/lib/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { activityTypeCode, formatButtonContent, formatInboundContent, formatOutboundBody, resolveChannelKind, resolveDiscordConfig, senderDisplayName, type DiscordAdapterConfig, type DiscordButtonInbound, type DiscordInboundAttachment, type DiscordInboundMessage, type DiscordOutboundBody, type DiscordWireSegment, type ResolvedDiscordConfig, type ResolvedDiscordGatewayConfig, type ResolvedDiscordInteractionsConfig, } from './protocol.js';
|
|
2
2
|
export { getDiscordAgentDeps, registerDiscordAgentEndpoint, setDiscordAgentDeps, type DiscordAgentDeps, type DiscordAgentEndpoint, } from './discord-agent-deps.js';
|
|
3
|
-
export { checkDiscordPlatformPermit, discordGroupPermitResolver, normalizeDiscordSenderForPermit, platformPermit,
|
|
3
|
+
export { checkDiscordPlatformPermit, discordGroupPermitResolver, normalizeDiscordSenderForPermit, platformPermit, } from './platform-permit.js';
|
|
4
4
|
export { DiscordGatewayEndpoint, DiscordInteractionsEndpoint, type CreateDiscordClient, type DiscordClientTransport, type DiscordEndpointOptions, type DiscordInteractionsEndpointOptions, } from './endpoint.js';
|
|
5
5
|
export { connectDiscordGatewayClient, defaultCreateClient, normalizeDiscordMessage, resolveSenderRole, toMessageCreateOptions, type DiscordGatewayConnectHandlers, } from './gateway.js';
|
|
6
6
|
export { handleDiscordInteractionRequest, registerDiscordInteractionRoutes, type DiscordInteractionsHandler, } from './webhook.js';
|
package/lib/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { activityTypeCode, formatButtonContent, formatInboundContent, formatOutboundBody, resolveChannelKind, resolveDiscordConfig, senderDisplayName, } from './protocol.js';
|
|
2
2
|
export { getDiscordAgentDeps, registerDiscordAgentEndpoint, setDiscordAgentDeps, } from './discord-agent-deps.js';
|
|
3
|
-
export { checkDiscordPlatformPermit, discordGroupPermitResolver, normalizeDiscordSenderForPermit, platformPermit,
|
|
3
|
+
export { checkDiscordPlatformPermit, discordGroupPermitResolver, normalizeDiscordSenderForPermit, platformPermit, } from './platform-permit.js';
|
|
4
4
|
export { DiscordGatewayEndpoint, DiscordInteractionsEndpoint, } from './endpoint.js';
|
|
5
5
|
export { connectDiscordGatewayClient, defaultCreateClient, normalizeDiscordMessage, resolveSenderRole, toMessageCreateOptions, } from './gateway.js';
|
|
6
6
|
export { handleDiscordInteractionRequest, registerDiscordInteractionRoutes, } from './webhook.js';
|
package/lib/platform-permit.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Discord platform permit — Guild 权限位
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
4
|
+
import type { PermissionSubject } from '@zhin.js/permission';
|
|
5
5
|
export declare function platformPermit(perm: string): string;
|
|
6
6
|
export declare function discordGroupPermitResolver(logicalPerm: string): string;
|
|
7
7
|
export declare function normalizeDiscordSenderForPermit(input: {
|
|
@@ -11,5 +11,4 @@ export declare function normalizeDiscordSenderForPermit(input: {
|
|
|
11
11
|
role?: string;
|
|
12
12
|
permissions?: string[];
|
|
13
13
|
};
|
|
14
|
-
export declare function checkDiscordPlatformPermit(perm: string,
|
|
15
|
-
export declare function registerDiscordPlatformPermitChecker(): () => void;
|
|
14
|
+
export declare function checkDiscordPlatformPermit(perm: string, subject: PermissionSubject): boolean;
|
package/lib/platform-permit.js
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Discord platform permit — Guild 权限位
|
|
3
|
-
*/
|
|
4
|
-
import { registerPlatformPermitChecker } from '@zhin.js/core';
|
|
5
1
|
const ADAPTER = 'discord';
|
|
6
2
|
export function platformPermit(perm) {
|
|
7
3
|
return `platform(${ADAPTER},${perm})`;
|
|
@@ -27,12 +23,9 @@ export function normalizeDiscordSenderForPermit(input) {
|
|
|
27
23
|
}
|
|
28
24
|
return { role: 'member', permissions };
|
|
29
25
|
}
|
|
30
|
-
function
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
export function checkDiscordPlatformPermit(perm, message) {
|
|
35
|
-
const { role, permissions } = senderPermits(message);
|
|
26
|
+
export function checkDiscordPlatformPermit(perm, subject) {
|
|
27
|
+
const role = subject.sender?.role?.[0];
|
|
28
|
+
const permissions = subject.sender?.permissions ?? [];
|
|
36
29
|
const has = (t) => permissions.includes(t);
|
|
37
30
|
switch (perm) {
|
|
38
31
|
case 'guild_owner':
|
|
@@ -47,6 +40,3 @@ export function checkDiscordPlatformPermit(perm, message) {
|
|
|
47
40
|
return false;
|
|
48
41
|
}
|
|
49
42
|
}
|
|
50
|
-
export function registerDiscordPlatformPermitChecker() {
|
|
51
|
-
return registerPlatformPermitChecker(ADAPTER, checkDiscordPlatformPermit);
|
|
52
|
-
}
|
package/lib/protocol.d.ts
CHANGED
|
@@ -3,9 +3,10 @@
|
|
|
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
|
-
readonly
|
|
9
|
+
readonly id?: string;
|
|
9
10
|
readonly token?: string;
|
|
10
11
|
/** Default `gateway`. `interactions` uses httpHostToken POST + Ed25519 verify. */
|
|
11
12
|
readonly connection?: 'gateway' | 'interactions';
|
|
@@ -25,7 +26,7 @@ export interface DiscordAdapterConfig {
|
|
|
25
26
|
/** Transitional: legacy root `endpoints[]` with `context: discord`. */
|
|
26
27
|
readonly endpoints?: ReadonlyArray<{
|
|
27
28
|
readonly context?: string;
|
|
28
|
-
readonly
|
|
29
|
+
readonly id?: string;
|
|
29
30
|
readonly token?: string;
|
|
30
31
|
readonly connection?: 'gateway' | 'interactions';
|
|
31
32
|
readonly intents?: readonly number[];
|
|
@@ -41,7 +42,7 @@ export interface DiscordAdapterConfig {
|
|
|
41
42
|
export interface ResolvedDiscordGatewayConfig {
|
|
42
43
|
readonly context: 'discord';
|
|
43
44
|
readonly connection: 'gateway';
|
|
44
|
-
readonly
|
|
45
|
+
readonly id: string;
|
|
45
46
|
readonly token: string;
|
|
46
47
|
readonly intents?: readonly number[];
|
|
47
48
|
readonly enableSlashCommands: boolean;
|
|
@@ -52,7 +53,7 @@ export interface ResolvedDiscordGatewayConfig {
|
|
|
52
53
|
export interface ResolvedDiscordInteractionsConfig {
|
|
53
54
|
readonly context: 'discord';
|
|
54
55
|
readonly connection: 'interactions';
|
|
55
|
-
readonly
|
|
56
|
+
readonly id: string;
|
|
56
57
|
readonly token: string;
|
|
57
58
|
readonly applicationId: string;
|
|
58
59
|
readonly publicKey: 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(endpointKey: 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
|
@@ -15,8 +15,8 @@ export function resolveDiscordConfig(config = {}) {
|
|
|
15
15
|
if (!token) {
|
|
16
16
|
throw new TypeError('Discord adapter requires token (plugins.<key>.token or endpoints with context: discord)');
|
|
17
17
|
}
|
|
18
|
-
const
|
|
19
|
-
|| (typeof entry?.
|
|
18
|
+
const id = (typeof config.id === 'string' && config.id)
|
|
19
|
+
|| (typeof entry?.id === 'string' && entry.id)
|
|
20
20
|
|| process.env.DISCORD_BOT_NAME
|
|
21
21
|
|| 'discord-bot';
|
|
22
22
|
const connection = config.connection
|
|
@@ -31,7 +31,7 @@ export function resolveDiscordConfig(config = {}) {
|
|
|
31
31
|
return {
|
|
32
32
|
context: 'discord',
|
|
33
33
|
connection: 'interactions',
|
|
34
|
-
|
|
34
|
+
id,
|
|
35
35
|
token,
|
|
36
36
|
applicationId,
|
|
37
37
|
publicKey,
|
|
@@ -41,7 +41,7 @@ export function resolveDiscordConfig(config = {}) {
|
|
|
41
41
|
return {
|
|
42
42
|
context: 'discord',
|
|
43
43
|
connection: 'gateway',
|
|
44
|
-
|
|
44
|
+
id,
|
|
45
45
|
token,
|
|
46
46
|
intents: config.intents ?? entry?.intents,
|
|
47
47
|
enableSlashCommands: config.enableSlashCommands === true
|
|
@@ -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(endpointKey, msg) {
|
|
68
|
+
return {
|
|
69
|
+
endpoint: { id: endpointKey, adapter: endpointKey.split('\0')[0] ?? endpointKey },
|
|
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.5",
|
|
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,22 @@
|
|
|
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.
|
|
41
|
-
"@zhin.js/logger": "1.0.
|
|
42
|
-
"@zhin.js/
|
|
36
|
+
"@zhin.js/adapter": "1.1.7",
|
|
37
|
+
"@zhin.js/command": "1.0.9",
|
|
38
|
+
"@zhin.js/core": "1.5.4",
|
|
39
|
+
"@zhin.js/host-http": "1.0.8",
|
|
40
|
+
"@zhin.js/im-contract": "1.0.3",
|
|
41
|
+
"@zhin.js/logger": "1.0.76",
|
|
42
|
+
"@zhin.js/permission": "1.0.1",
|
|
43
|
+
"@zhin.js/plugin-runtime": "1.1.5"
|
|
43
44
|
},
|
|
44
45
|
"peerDependencies": {
|
|
45
46
|
"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.
|
|
47
|
+
"@zhin.js/adapter": "1.1.7",
|
|
48
|
+
"@zhin.js/agent": "1.1.5",
|
|
49
|
+
"@zhin.js/core": "1.5.4",
|
|
50
|
+
"@zhin.js/plugin-runtime": "1.1.5",
|
|
51
|
+
"zhin.js": "6.0.4"
|
|
51
52
|
},
|
|
52
53
|
"peerDependenciesMeta": {
|
|
53
54
|
"zhin.js": {
|
|
@@ -65,9 +66,9 @@
|
|
|
65
66
|
"typescript": "^6.0.3",
|
|
66
67
|
"vitest": "^4.1.10",
|
|
67
68
|
"zod": "^4.4.3",
|
|
68
|
-
"@zhin.js/agent": "1.1.
|
|
69
|
-
"@zhin.js/host-http": "1.0.
|
|
70
|
-
"zhin.js": "6.0.
|
|
69
|
+
"@zhin.js/agent": "1.1.5",
|
|
70
|
+
"@zhin.js/host-http": "1.0.8",
|
|
71
|
+
"zhin.js": "6.0.4"
|
|
71
72
|
},
|
|
72
73
|
"files": [
|
|
73
74
|
"adapters",
|
package/plugin.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
2
|
import { createEndpointRuntimeState } from '@zhin.js/adapter';
|
|
3
3
|
import { definePlugin } from '@zhin.js/plugin-runtime';
|
|
4
|
-
import {
|
|
4
|
+
import { permissionHostToken } from '@zhin.js/permission';
|
|
5
|
+
import { checkDiscordPlatformPermit } from "./lib/platform-permit.js";
|
|
5
6
|
import { discordRuntimeStateToken } from "./lib/discord-runtime-state.js";
|
|
6
7
|
export default definePlugin({
|
|
7
8
|
name: 'discord',
|
|
@@ -9,9 +10,10 @@ export default definePlugin({
|
|
|
9
10
|
displayName: 'Discord Gateway Adapter',
|
|
10
11
|
},
|
|
11
12
|
setup(context) {
|
|
12
|
-
// 运行中 endpoint 注册表(discord.endpoint list 的"运行中"数据源)
|
|
13
13
|
context.resources.provide(discordRuntimeStateToken, createEndpointRuntimeState());
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
if (context.resources.has(permissionHostToken)) {
|
|
15
|
+
const host = context.resources.use(permissionHostToken);
|
|
16
|
+
return host.registerPlatform('discord', checkDiscordPlatformPermit);
|
|
17
|
+
}
|
|
16
18
|
},
|
|
17
19
|
});
|
package/schema.json
CHANGED
|
@@ -71,24 +71,60 @@
|
|
|
71
71
|
"default": "/discord/interactions",
|
|
72
72
|
"description": "POST path on httpHostToken when connection is interactions."
|
|
73
73
|
},
|
|
74
|
+
"master": {
|
|
75
|
+
"type": [
|
|
76
|
+
"string",
|
|
77
|
+
"number"
|
|
78
|
+
],
|
|
79
|
+
"description": "框架 master(Discord user snowflake;AI/工具权限、endpoint 管理)。endpoints[i].master 可逐项覆盖"
|
|
80
|
+
},
|
|
81
|
+
"trusted": {
|
|
82
|
+
"type": "array",
|
|
83
|
+
"items": {
|
|
84
|
+
"type": [
|
|
85
|
+
"string",
|
|
86
|
+
"number"
|
|
87
|
+
],
|
|
88
|
+
"description": "Trusted Discord user snowflake"
|
|
89
|
+
},
|
|
90
|
+
"description": "框架 trusted 用户列表(弱于 master)。endpoints[i].trusted 可逐项追加"
|
|
91
|
+
},
|
|
74
92
|
"endpoints": {
|
|
75
93
|
"type": "array",
|
|
76
|
-
"description": "多账号:一个插件实例挂多个 endpoint。每项与顶层字段同构(
|
|
94
|
+
"description": "多账号:一个插件实例挂多个 endpoint。每项与顶层字段同构(id 必填,其余覆盖顶层)",
|
|
77
95
|
"items": {
|
|
78
96
|
"type": "object",
|
|
79
97
|
"additionalProperties": true,
|
|
80
98
|
"properties": {
|
|
81
|
-
"
|
|
82
|
-
"type":
|
|
83
|
-
|
|
99
|
+
"master": {
|
|
100
|
+
"type": [
|
|
101
|
+
"string",
|
|
102
|
+
"number"
|
|
103
|
+
],
|
|
104
|
+
"description": "本 endpoint 的框架 master(Discord user snowflake);覆盖顶层 master"
|
|
105
|
+
},
|
|
106
|
+
"trusted": {
|
|
107
|
+
"type": "array",
|
|
108
|
+
"items": {
|
|
109
|
+
"type": [
|
|
110
|
+
"string",
|
|
111
|
+
"number"
|
|
112
|
+
],
|
|
113
|
+
"description": "Trusted Discord user snowflake"
|
|
114
|
+
},
|
|
115
|
+
"description": "本 endpoint 的 trusted 列表"
|
|
84
116
|
},
|
|
85
117
|
"token": {
|
|
86
118
|
"type": "string",
|
|
87
119
|
"description": "Discord bot token"
|
|
120
|
+
},
|
|
121
|
+
"id": {
|
|
122
|
+
"type": "string",
|
|
123
|
+
"description": "Discord bot name"
|
|
88
124
|
}
|
|
89
125
|
},
|
|
90
126
|
"required": [
|
|
91
|
-
"
|
|
127
|
+
"id",
|
|
92
128
|
"token"
|
|
93
129
|
]
|
|
94
130
|
}
|
|
@@ -39,22 +39,22 @@ export interface DiscordAgentEndpoint {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
export interface DiscordAgentDeps {
|
|
42
|
-
getEndpoint: (
|
|
42
|
+
getEndpoint: (endpointKey: string) => DiscordAgentEndpoint;
|
|
43
43
|
/** Alias kept for existing agent/tools that call getGatewayEndpoint. */
|
|
44
|
-
getGatewayEndpoint: (
|
|
44
|
+
getGatewayEndpoint: (endpointKey: string) => DiscordAgentEndpoint;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
const endpoints = new Map<string, DiscordAgentEndpoint>();
|
|
48
48
|
let override: DiscordAgentDeps | null = null;
|
|
49
49
|
|
|
50
50
|
export function registerDiscordAgentEndpoint(
|
|
51
|
-
|
|
51
|
+
endpointKey: string,
|
|
52
52
|
endpoint: DiscordAgentEndpoint,
|
|
53
53
|
): () => void {
|
|
54
|
-
endpoints.set(
|
|
54
|
+
endpoints.set(endpointKey, endpoint);
|
|
55
55
|
return () => {
|
|
56
|
-
if (endpoints.get(
|
|
57
|
-
endpoints.delete(
|
|
56
|
+
if (endpoints.get(endpointKey) === endpoint) {
|
|
57
|
+
endpoints.delete(endpointKey);
|
|
58
58
|
}
|
|
59
59
|
};
|
|
60
60
|
}
|
|
@@ -64,9 +64,9 @@ export function setDiscordAgentDeps(deps: DiscordAgentDeps | null): void {
|
|
|
64
64
|
override = deps;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
function lookup(
|
|
68
|
-
const registered = endpoints.get(
|
|
69
|
-
if (!registered) throw new Error(`Endpoint ${
|
|
67
|
+
function lookup(endpointKey: string): DiscordAgentEndpoint {
|
|
68
|
+
const registered = endpoints.get(endpointKey);
|
|
69
|
+
if (!registered) throw new Error(`Endpoint ${endpointKey} 不存在`);
|
|
70
70
|
return registered;
|
|
71
71
|
}
|
|
72
72
|
|
package/src/endpoint.ts
CHANGED
|
@@ -13,11 +13,12 @@ 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
|
-
|
|
16
|
+
formatLegacyConversationRef,
|
|
17
|
+
formatLegacyMessageRef,
|
|
17
18
|
nativeConversationId,
|
|
18
19
|
parseLegacyMessageReference,
|
|
19
20
|
} from '@zhin.js/im-contract';
|
|
20
|
-
import { formatCompact,
|
|
21
|
+
import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
|
|
21
22
|
import type { CapabilityId } from '@zhin.js/plugin-runtime';
|
|
22
23
|
import { registerDiscordAgentEndpoint } from './discord-agent-deps.js';
|
|
23
24
|
import {
|
|
@@ -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,
|
|
@@ -47,8 +49,6 @@ import { registerDiscordInteractionRoutes } from './webhook.js';
|
|
|
47
49
|
const DISCORD_API = 'https://discord.com/api/v10';
|
|
48
50
|
/** 出站 HTTP 调用统一 30s 超时。 */
|
|
49
51
|
const OUTBOUND_TIMEOUT_MS = 30_000;
|
|
50
|
-
const logger = getLogger('discord');
|
|
51
|
-
|
|
52
52
|
export type {
|
|
53
53
|
CreateDiscordClient,
|
|
54
54
|
DiscordClientTransport,
|
|
@@ -62,6 +62,8 @@ export interface DiscordEndpointOptions {
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
export class DiscordGatewayEndpoint implements EndpointInstance {
|
|
65
|
+
readonly #logger!: ReturnType<typeof getAdapterLogger>;
|
|
66
|
+
|
|
65
67
|
readonly #options: DiscordEndpointOptions;
|
|
66
68
|
readonly #createClient: CreateDiscordClient;
|
|
67
69
|
#client: DiscordClientTransport | null = null;
|
|
@@ -90,6 +92,7 @@ export class DiscordGatewayEndpoint implements EndpointInstance {
|
|
|
90
92
|
});
|
|
91
93
|
|
|
92
94
|
constructor(options: DiscordEndpointOptions) {
|
|
95
|
+
this.#logger = getAdapterLogger('discord', options.config.id);
|
|
93
96
|
this.#options = options;
|
|
94
97
|
this.#createClient = options.createClient ?? defaultCreateClient;
|
|
95
98
|
}
|
|
@@ -98,7 +101,7 @@ export class DiscordGatewayEndpoint implements EndpointInstance {
|
|
|
98
101
|
if (this.#started) return;
|
|
99
102
|
this.#started = true;
|
|
100
103
|
try {
|
|
101
|
-
this.#unregisterAgent = registerDiscordAgentEndpoint(this.#options.config.
|
|
104
|
+
this.#unregisterAgent = registerDiscordAgentEndpoint(this.#options.config.id, this);
|
|
102
105
|
const intents = this.#options.config.intents?.length
|
|
103
106
|
? [...this.#options.config.intents]
|
|
104
107
|
: DEFAULT_INTENTS;
|
|
@@ -107,15 +110,15 @@ export class DiscordGatewayEndpoint implements EndpointInstance {
|
|
|
107
110
|
onMessage: (msg) => this.admit(msg),
|
|
108
111
|
onButton: (interaction) => this.admitButton(interaction),
|
|
109
112
|
});
|
|
110
|
-
logger.info(formatCompact({
|
|
113
|
+
this.#logger.info(formatCompact({
|
|
111
114
|
op: 'connect',
|
|
112
|
-
endpoint: this.#options.config.
|
|
115
|
+
endpoint: this.#options.config.id,
|
|
113
116
|
mode: 'gateway',
|
|
114
117
|
user: this.#client.user?.tag,
|
|
115
118
|
}));
|
|
116
119
|
} catch (error) {
|
|
117
120
|
await this.stop();
|
|
118
|
-
logger.error('Failed to connect Discord gateway:', error);
|
|
121
|
+
this.#logger.error('Failed to connect Discord gateway:', error);
|
|
119
122
|
throw error;
|
|
120
123
|
}
|
|
121
124
|
}
|
|
@@ -142,18 +145,18 @@ export class DiscordGatewayEndpoint implements EndpointInstance {
|
|
|
142
145
|
this.#client = null;
|
|
143
146
|
}
|
|
144
147
|
this.#started = false;
|
|
145
|
-
logger.debug(formatCompact({ op: 'disconnect'
|
|
148
|
+
this.#logger.debug(formatCompact({ op: 'disconnect' }));
|
|
146
149
|
}
|
|
147
150
|
|
|
148
|
-
async send({
|
|
151
|
+
async send({ conversation, payload }: EndpointSendRequest): Promise<string> {
|
|
149
152
|
const body = formatOutboundBody(payload);
|
|
150
|
-
|
|
151
|
-
const snowflake = await this.#sendBody(
|
|
152
|
-
const messageId =
|
|
153
|
-
logger.debug(formatCompact({
|
|
153
|
+
// recall 链路仍是本端点编码的 legacy 引用,在边界内部从 conversation 派生
|
|
154
|
+
const snowflake = await this.#sendBody(conversation.id, body);
|
|
155
|
+
const messageId = formatLegacyMessageRef({ conversation, id: snowflake });
|
|
156
|
+
this.#logger.debug(formatCompact({
|
|
154
157
|
op: 'discord_send',
|
|
155
|
-
endpoint: this.#options.config.
|
|
156
|
-
target,
|
|
158
|
+
endpoint: this.#options.config.id,
|
|
159
|
+
target: formatLegacyConversationRef(conversation),
|
|
157
160
|
messageId,
|
|
158
161
|
}));
|
|
159
162
|
return messageId;
|
|
@@ -175,26 +178,30 @@ 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
|
-
sender:
|
|
184
|
-
|
|
187
|
+
sender: {
|
|
188
|
+
id: msg.authorId,
|
|
189
|
+
name: senderDisplayName(msg) || undefined,
|
|
190
|
+
...(resolveSenderRole(msg) ? { roles: [resolveSenderRole(msg)!] } : {}),
|
|
191
|
+
},
|
|
192
|
+
endpointId: this.#options.config.id,
|
|
193
|
+
...(msg.mentionedBot ? { mentioned: true } : {}),
|
|
185
194
|
metadata: Object.freeze({
|
|
186
|
-
endpoint: this.#options.config.name,
|
|
187
195
|
channelKind: msg.channelKind,
|
|
188
196
|
userId: msg.authorId,
|
|
189
197
|
guildId: msg.guildId,
|
|
190
198
|
permissions: msg.permissionTokens,
|
|
191
199
|
role: resolveSenderRole(msg),
|
|
192
|
-
...(msg.mentionedBot ? { mentioned: true } : {}),
|
|
193
200
|
}),
|
|
194
201
|
}).catch((err) => {
|
|
195
|
-
logger.warn(formatCompact({
|
|
202
|
+
this.#logger.warn(formatCompact({
|
|
196
203
|
op: 'discord_gateway_receive_failed',
|
|
197
|
-
target:
|
|
204
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
198
205
|
error: err instanceof Error ? err.message : String(err),
|
|
199
206
|
}));
|
|
200
207
|
});
|
|
@@ -203,23 +210,23 @@ export class DiscordGatewayEndpoint implements EndpointInstance {
|
|
|
203
210
|
/** Test / internal: admit a button interaction when open. */
|
|
204
211
|
admitButton(interaction: DiscordButtonInbound): void {
|
|
205
212
|
if (!this.#open) return;
|
|
213
|
+
const conversation = discordInboundConversation(String(this.#options.id), interaction);
|
|
206
214
|
void this.#options.gateway.receive({
|
|
207
|
-
|
|
208
|
-
|
|
215
|
+
conversation,
|
|
216
|
+
message: { conversation, id: interaction.id },
|
|
209
217
|
content: formatButtonContent(interaction),
|
|
210
218
|
segments: formatButtonSegments(interaction),
|
|
211
|
-
sender: interaction.userName,
|
|
212
|
-
|
|
219
|
+
sender: { id: interaction.userId, name: interaction.userName },
|
|
220
|
+
endpointId: this.#options.config.id,
|
|
213
221
|
metadata: Object.freeze({
|
|
214
|
-
endpoint: this.#options.config.name,
|
|
215
222
|
eventType: 'button',
|
|
216
223
|
payload: interaction.customId,
|
|
217
224
|
sourceMessageId: interaction.sourceMessageId,
|
|
218
225
|
}),
|
|
219
226
|
}).catch((err) => {
|
|
220
|
-
logger.warn(formatCompact({
|
|
227
|
+
this.#logger.warn(formatCompact({
|
|
221
228
|
op: 'discord_gateway_receive_failed',
|
|
222
|
-
target:
|
|
229
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
223
230
|
error: err instanceof Error ? err.message : String(err),
|
|
224
231
|
}));
|
|
225
232
|
});
|
|
@@ -416,6 +423,8 @@ export interface DiscordInteractionsEndpointOptions {
|
|
|
416
423
|
}
|
|
417
424
|
|
|
418
425
|
export class DiscordInteractionsEndpoint implements EndpointInstance {
|
|
426
|
+
readonly #logger!: ReturnType<typeof getAdapterLogger>;
|
|
427
|
+
|
|
419
428
|
readonly #options: DiscordInteractionsEndpointOptions;
|
|
420
429
|
readonly #fetch: typeof globalThis.fetch;
|
|
421
430
|
#routeReleases: HttpRouteRegistration[] = [];
|
|
@@ -426,6 +435,7 @@ export class DiscordInteractionsEndpoint implements EndpointInstance {
|
|
|
426
435
|
});
|
|
427
436
|
|
|
428
437
|
constructor(options: DiscordInteractionsEndpointOptions) {
|
|
438
|
+
this.#logger = getAdapterLogger('discord', options.config.id);
|
|
429
439
|
this.#options = options;
|
|
430
440
|
this.#fetch = options.fetch ?? globalThis.fetch;
|
|
431
441
|
}
|
|
@@ -442,9 +452,9 @@ export class DiscordInteractionsEndpoint implements EndpointInstance {
|
|
|
442
452
|
if (this.#started) return;
|
|
443
453
|
this.#started = true;
|
|
444
454
|
this.#routeReleases.push(...registerDiscordInteractionRoutes(this.#options.http, this));
|
|
445
|
-
logger.info(formatCompact({
|
|
455
|
+
this.#logger.info(formatCompact({
|
|
446
456
|
op: 'connect',
|
|
447
|
-
endpoint: this.#options.config.
|
|
457
|
+
endpoint: this.#options.config.id,
|
|
448
458
|
mode: 'interactions',
|
|
449
459
|
path: this.#options.config.interactionsPath,
|
|
450
460
|
}));
|
|
@@ -462,12 +472,12 @@ export class DiscordInteractionsEndpoint implements EndpointInstance {
|
|
|
462
472
|
this.#open = false;
|
|
463
473
|
for (const release of this.#routeReleases.splice(0)) release();
|
|
464
474
|
this.#started = false;
|
|
465
|
-
logger.debug(formatCompact({ op: 'disconnect'
|
|
475
|
+
this.#logger.debug(formatCompact({ op: 'disconnect' }));
|
|
466
476
|
}
|
|
467
477
|
|
|
468
|
-
async send({
|
|
478
|
+
async send({ conversation, payload }: EndpointSendRequest): Promise<string> {
|
|
469
479
|
const body = formatOutboundBody(payload);
|
|
470
|
-
const channelId =
|
|
480
|
+
const channelId = conversation.id;
|
|
471
481
|
const response = await this.#fetch(`${DISCORD_API}/channels/${channelId}/messages`, {
|
|
472
482
|
method: 'POST',
|
|
473
483
|
headers: {
|
|
@@ -483,7 +493,8 @@ export class DiscordInteractionsEndpoint implements EndpointInstance {
|
|
|
483
493
|
}
|
|
484
494
|
const data = JSON.parse(text) as { id?: string };
|
|
485
495
|
const snowflake = data.id ?? '';
|
|
486
|
-
|
|
496
|
+
// recall 链路仍是本端点编码的 legacy 引用,在边界内部从 conversation 派生
|
|
497
|
+
return snowflake ? formatLegacyMessageRef({ conversation, id: snowflake }) : '';
|
|
487
498
|
}
|
|
488
499
|
|
|
489
500
|
async recallMessage(messageId: string): Promise<void> {
|
|
@@ -505,24 +516,28 @@ export class DiscordInteractionsEndpoint implements EndpointInstance {
|
|
|
505
516
|
|
|
506
517
|
admit(msg: DiscordInboundMessage): void {
|
|
507
518
|
if (!this.#open) return;
|
|
519
|
+
const conversation = discordInboundConversation(String(this.#options.id), msg);
|
|
508
520
|
void this.#options.gateway.receive({
|
|
509
|
-
|
|
510
|
-
|
|
521
|
+
conversation,
|
|
522
|
+
message: { conversation, id: msg.id },
|
|
511
523
|
content: formatInboundContent(msg),
|
|
512
524
|
segments: formatInboundSegments(msg),
|
|
513
|
-
sender:
|
|
514
|
-
|
|
525
|
+
sender: {
|
|
526
|
+
id: msg.authorId,
|
|
527
|
+
name: senderDisplayName(msg) || undefined,
|
|
528
|
+
...(resolveSenderRole(msg) ? { roles: [resolveSenderRole(msg)!] } : {}),
|
|
529
|
+
},
|
|
530
|
+
endpointId: this.#options.config.id,
|
|
515
531
|
metadata: Object.freeze({
|
|
516
|
-
endpoint: this.#options.config.name,
|
|
517
532
|
channelKind: msg.channelKind,
|
|
518
533
|
userId: msg.authorId,
|
|
519
534
|
guildId: msg.guildId,
|
|
520
535
|
eventType: 'application_command',
|
|
521
536
|
}),
|
|
522
537
|
}).catch((err) => {
|
|
523
|
-
logger.warn(formatCompact({
|
|
538
|
+
this.#logger.warn(formatCompact({
|
|
524
539
|
op: 'discord_gateway_receive_failed',
|
|
525
|
-
target:
|
|
540
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
526
541
|
error: err instanceof Error ? err.message : String(err),
|
|
527
542
|
}));
|
|
528
543
|
});
|
package/src/index.ts
CHANGED
package/src/platform-permit.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Discord platform permit — Guild 权限位
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
4
|
+
import type { PermissionSubject } from '@zhin.js/permission';
|
|
5
5
|
|
|
6
6
|
const ADAPTER = 'discord';
|
|
7
7
|
|
|
@@ -36,13 +36,9 @@ export function normalizeDiscordSenderForPermit(input: {
|
|
|
36
36
|
return { role: 'member', permissions };
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
function
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export function checkDiscordPlatformPermit(perm: string, message: Message<any>): boolean {
|
|
45
|
-
const { role, permissions } = senderPermits(message);
|
|
39
|
+
export function checkDiscordPlatformPermit(perm: string, subject: PermissionSubject): boolean {
|
|
40
|
+
const role = subject.sender?.role?.[0];
|
|
41
|
+
const permissions = subject.sender?.permissions ?? [];
|
|
46
42
|
const has = (t: string) => permissions.includes(t);
|
|
47
43
|
|
|
48
44
|
switch (perm) {
|
|
@@ -59,6 +55,3 @@ export function checkDiscordPlatformPermit(perm: string, message: Message<any>):
|
|
|
59
55
|
}
|
|
60
56
|
}
|
|
61
57
|
|
|
62
|
-
export function registerDiscordPlatformPermitChecker(): () => void {
|
|
63
|
-
return registerPlatformPermitChecker(ADAPTER, checkDiscordPlatformPermit);
|
|
64
|
-
}
|
package/src/protocol.ts
CHANGED
|
@@ -6,13 +6,14 @@
|
|
|
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');
|
|
12
13
|
|
|
13
14
|
/** Plugin Runtime owner config (`plugins.<instanceKey>` / schema.json). */
|
|
14
15
|
export interface DiscordAdapterConfig {
|
|
15
|
-
readonly
|
|
16
|
+
readonly id?: string;
|
|
16
17
|
readonly token?: string;
|
|
17
18
|
/** Default `gateway`. `interactions` uses httpHostToken POST + Ed25519 verify. */
|
|
18
19
|
readonly connection?: 'gateway' | 'interactions';
|
|
@@ -32,7 +33,7 @@ export interface DiscordAdapterConfig {
|
|
|
32
33
|
/** Transitional: legacy root `endpoints[]` with `context: discord`. */
|
|
33
34
|
readonly endpoints?: ReadonlyArray<{
|
|
34
35
|
readonly context?: string;
|
|
35
|
-
readonly
|
|
36
|
+
readonly id?: string;
|
|
36
37
|
readonly token?: string;
|
|
37
38
|
readonly connection?: 'gateway' | 'interactions';
|
|
38
39
|
readonly intents?: readonly number[];
|
|
@@ -49,7 +50,7 @@ export interface DiscordAdapterConfig {
|
|
|
49
50
|
export interface ResolvedDiscordGatewayConfig {
|
|
50
51
|
readonly context: 'discord';
|
|
51
52
|
readonly connection: 'gateway';
|
|
52
|
-
readonly
|
|
53
|
+
readonly id: string;
|
|
53
54
|
readonly token: string;
|
|
54
55
|
readonly intents?: readonly number[];
|
|
55
56
|
readonly enableSlashCommands: boolean;
|
|
@@ -61,7 +62,7 @@ export interface ResolvedDiscordGatewayConfig {
|
|
|
61
62
|
export interface ResolvedDiscordInteractionsConfig {
|
|
62
63
|
readonly context: 'discord';
|
|
63
64
|
readonly connection: 'interactions';
|
|
64
|
-
readonly
|
|
65
|
+
readonly id: string;
|
|
65
66
|
readonly token: string;
|
|
66
67
|
readonly applicationId: string;
|
|
67
68
|
readonly publicKey: string;
|
|
@@ -151,8 +152,8 @@ export function resolveDiscordConfig(config: DiscordAdapterConfig = {}): Resolve
|
|
|
151
152
|
'Discord adapter requires token (plugins.<key>.token or endpoints with context: discord)',
|
|
152
153
|
);
|
|
153
154
|
}
|
|
154
|
-
const
|
|
155
|
-
|| (typeof entry?.
|
|
155
|
+
const id = (typeof config.id === 'string' && config.id)
|
|
156
|
+
|| (typeof entry?.id === 'string' && entry.id)
|
|
156
157
|
|| process.env.DISCORD_BOT_NAME
|
|
157
158
|
|| 'discord-bot';
|
|
158
159
|
const connection = config.connection
|
|
@@ -170,7 +171,7 @@ export function resolveDiscordConfig(config: DiscordAdapterConfig = {}): Resolve
|
|
|
170
171
|
return {
|
|
171
172
|
context: 'discord',
|
|
172
173
|
connection: 'interactions',
|
|
173
|
-
|
|
174
|
+
id,
|
|
174
175
|
token,
|
|
175
176
|
applicationId,
|
|
176
177
|
publicKey,
|
|
@@ -181,7 +182,7 @@ export function resolveDiscordConfig(config: DiscordAdapterConfig = {}): Resolve
|
|
|
181
182
|
return {
|
|
182
183
|
context: 'discord',
|
|
183
184
|
connection: 'gateway',
|
|
184
|
-
|
|
185
|
+
id,
|
|
185
186
|
token,
|
|
186
187
|
intents: config.intents ?? entry?.intents,
|
|
187
188
|
enableSlashCommands: config.enableSlashCommands === true
|
|
@@ -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
|
+
endpointKey: 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: endpointKey, adapter: endpointKey.split('\0')[0] ?? endpointKey },
|
|
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
|