@zhin.js/adapter-discord 1.1.27 → 1.1.28
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 +169 -0
- package/LICENSE +21 -0
- package/PERMITS.md +15 -14
- package/README.md +6 -2
- package/adapters/discord/index.js +3 -0
- package/adapters/discord/index.ts +3 -0
- package/agents/discord/conversation.js +70 -0
- package/agents/discord/conversation.ts +77 -0
- package/agents/discord/messages.js +18 -2
- package/agents/discord/messages.ts +18 -2
- package/agents/discord/skills/discord/SKILL.md +1 -1
- package/agents/discord/skills/discord/tools/react/index.js +7 -6
- package/agents/discord/skills/discord/tools/react/index.ts +8 -7
- package/agents/discord/skills/discord/tools/send_embed/index.js +6 -11
- package/agents/discord/skills/discord/tools/send_embed/index.ts +7 -12
- package/agents/discord/skills/discord-guild/tools/add_role/index.js +11 -9
- package/agents/discord/skills/discord-guild/tools/add_role/index.ts +11 -10
- package/agents/discord/skills/discord-guild/tools/create_thread/index.js +5 -5
- package/agents/discord/skills/discord-guild/tools/create_thread/index.ts +6 -6
- package/agents/discord/skills/discord-guild/tools/forum_post/index.js +7 -3
- package/agents/discord/skills/discord-guild/tools/forum_post/index.ts +7 -4
- package/agents/discord/skills/discord-guild/tools/list_roles/index.js +7 -8
- package/agents/discord/skills/discord-guild/tools/list_roles/index.ts +8 -9
- package/agents/discord/skills/discord-guild/tools/remove_role/index.js +11 -9
- package/agents/discord/skills/discord-guild/tools/remove_role/index.ts +11 -10
- package/lib/endpoint.js +71 -12
- package/lib/gateway.d.ts +1 -0
- package/lib/gateway.js +21 -8
- package/lib/index.d.ts +1 -1
- package/lib/index.js +1 -1
- package/lib/platform-permit.d.ts +4 -11
- package/lib/platform-permit.js +7 -40
- package/lib/protocol.d.ts +18 -0
- package/lib/protocol.js +32 -3
- package/lib/sender-standing.d.ts +18 -0
- package/lib/sender-standing.js +91 -0
- package/lib/webhook.d.ts +2 -6
- package/lib/webhook.js +23 -55
- package/package.json +23 -25
- package/plugin.js +0 -6
- package/schema.json +0 -56
- package/src/endpoint.ts +73 -11
- package/src/gateway.ts +22 -5
- package/src/index.ts +1 -6
- package/src/platform-permit.ts +5 -52
- package/src/protocol.ts +43 -3
- package/src/sender-standing.ts +91 -0
- package/src/webhook.ts +21 -58
package/src/webhook.ts
CHANGED
|
@@ -2,9 +2,8 @@ import { endpointSpec, type Endpoint } from 'zhin.js/adapter';
|
|
|
2
2
|
/**
|
|
3
3
|
* Discord interactions HTTP: signature → parse → admit.
|
|
4
4
|
*/
|
|
5
|
-
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
6
5
|
import { createMessages } from 'zhin.js/i18n';
|
|
7
|
-
import type
|
|
6
|
+
import { readTextBody, type HttpHost, type HttpRouteRegistration } from '@zhin.js/host-http';
|
|
8
7
|
import { formatCompact, getLogger } from '@zhin.js/logger';
|
|
9
8
|
import {
|
|
10
9
|
interactionToInboundMessage,
|
|
@@ -40,25 +39,21 @@ export function registerDiscordInteractionRoutes(
|
|
|
40
39
|
): HttpRouteRegistration[] {
|
|
41
40
|
const path = handler.config.interactionsPath;
|
|
42
41
|
return [
|
|
43
|
-
http.route('POST', path,
|
|
44
|
-
await handleDiscordInteractionRequest(request, response, handler);
|
|
45
|
-
}, { summary: 'Discord interactions callback', tags: ['discord'] }),
|
|
42
|
+
http.route('POST', path, (request) => handleDiscordInteractionRequest(request, handler), { summary: 'Discord interactions callback', tags: ['discord'] }),
|
|
46
43
|
];
|
|
47
44
|
}
|
|
48
45
|
|
|
49
46
|
export async function handleDiscordInteractionRequest(
|
|
50
|
-
request:
|
|
51
|
-
response: ServerResponse,
|
|
47
|
+
request: Request,
|
|
52
48
|
handler: DiscordInteractionsHandler,
|
|
53
|
-
): Promise<
|
|
49
|
+
): Promise<Response> {
|
|
54
50
|
try {
|
|
55
|
-
const signature =
|
|
56
|
-
const timestamp =
|
|
57
|
-
|
|
51
|
+
const signature = request.headers.get('x-signature-ed25519') ?? '';
|
|
52
|
+
const timestamp = request.headers.get('x-signature-timestamp') ?? '';
|
|
53
|
+
// Oversize bodies (> 1 MiB) fail and answer 500, as before.
|
|
54
|
+
const rawBody = await readTextBody(request);
|
|
58
55
|
if (!signature || !timestamp) {
|
|
59
|
-
|
|
60
|
-
response.end('Unauthorized');
|
|
61
|
-
return;
|
|
56
|
+
return new Response('Unauthorized', { status: 401, headers: { 'Content-Type': 'text/plain' } });
|
|
62
57
|
}
|
|
63
58
|
if (!verifyDiscordInteractionSignature(
|
|
64
59
|
handler.config.publicKey,
|
|
@@ -66,33 +61,27 @@ export async function handleDiscordInteractionRequest(
|
|
|
66
61
|
signature,
|
|
67
62
|
timestamp,
|
|
68
63
|
)) {
|
|
69
|
-
|
|
70
|
-
response.end('Unauthorized');
|
|
71
|
-
return;
|
|
64
|
+
return new Response('Unauthorized', { status: 401, headers: { 'Content-Type': 'text/plain' } });
|
|
72
65
|
}
|
|
73
66
|
const interaction = JSON.parse(rawBody) as Record<string, unknown>;
|
|
74
67
|
if (!interaction || interaction.application_id !== handler.config.applicationId) {
|
|
75
|
-
|
|
76
|
-
response.end('Unauthorized');
|
|
77
|
-
return;
|
|
68
|
+
return new Response('Unauthorized', { status: 401, headers: { 'Content-Type': 'text/plain' } });
|
|
78
69
|
}
|
|
79
70
|
// A verified protocol challenge is not a business message and keeps its immediate ACK.
|
|
80
71
|
if (interaction.type === INTERACTION_TYPE_PING) {
|
|
81
72
|
if (handler.isOpen) void handler[endpointSpec].events.run(() => handler.admitPlatform(interaction))
|
|
82
73
|
.catch(() => logger.debug('Protocol event dispatch did not complete'));
|
|
83
|
-
writeJson(
|
|
84
|
-
return;
|
|
74
|
+
return writeJson(200, { type: INTERACTION_RESPONSE_PONG });
|
|
85
75
|
}
|
|
86
|
-
await handler[endpointSpec].events.run(async () => {
|
|
76
|
+
return await handler[endpointSpec].events.run(async () => {
|
|
87
77
|
if (handler.isOpen) handler.admitPlatform(interaction);
|
|
88
78
|
if (interaction.type === 3) {
|
|
89
79
|
const data = interaction.data as { component_type?: number; custom_id?: string } | undefined;
|
|
90
80
|
if (data?.component_type === 2 && typeof data.custom_id === 'string') {
|
|
91
|
-
if (!handler.isOpen) {
|
|
81
|
+
if (!handler.isOpen) { return new Response('Unavailable', { status: 503 }); }
|
|
92
82
|
const message = interactionToInboundMessage(interaction);
|
|
93
|
-
handler.admitButton({ event: interaction, id: message.id, customId: data.custom_id, channelId: message.channelId, channelKind: message.channelKind, userId: message.authorId, userName: message.authorName, ...(message.guildId ? { guildId: message.guildId } : {}), sourceMessageId: (interaction.message as { id?: string } | undefined)?.id });
|
|
94
|
-
writeJson(
|
|
95
|
-
return;
|
|
83
|
+
handler.admitButton({ event: interaction, id: message.id, customId: data.custom_id, channelId: message.channelId, channelKind: message.channelKind, userId: message.authorId, userName: message.authorName, ...(message.guildId ? { guildId: message.guildId } : {}), ...(message.threadParentId ? { threadParentId: message.threadParentId } : {}), sourceMessageId: (interaction.message as { id?: string } | undefined)?.id });
|
|
84
|
+
return writeJson(200, { type: 6 });
|
|
96
85
|
}
|
|
97
86
|
}
|
|
98
87
|
if (interaction.type === INTERACTION_TYPE_APPLICATION_COMMAND) {
|
|
@@ -105,48 +94,22 @@ export async function handleDiscordInteractionRequest(
|
|
|
105
94
|
const commandName = String(
|
|
106
95
|
(interaction.data as { name?: unknown } | undefined)?.name ?? '',
|
|
107
96
|
);
|
|
108
|
-
writeJson(
|
|
97
|
+
return writeJson(200, {
|
|
109
98
|
type: INTERACTION_RESPONSE_CHANNEL_MESSAGE_WITH_SOURCE,
|
|
110
99
|
data: {
|
|
111
100
|
content: messages(locale, 'processing', { command: commandName }),
|
|
112
101
|
flags: INTERACTION_FLAG_EPHEMERAL,
|
|
113
102
|
},
|
|
114
103
|
});
|
|
115
|
-
return;
|
|
116
104
|
}
|
|
117
|
-
|
|
118
|
-
response.end('Unsupported interaction type');
|
|
105
|
+
return new Response('Unsupported interaction type', { status: 400, headers: { 'Content-Type': 'text/plain' } });
|
|
119
106
|
});
|
|
120
107
|
} catch (error) {
|
|
121
108
|
logger.error('Discord interactions error:', error);
|
|
122
|
-
|
|
123
|
-
response.writeHead(500, { 'Content-Type': 'text/plain' });
|
|
124
|
-
response.end('Internal Server Error');
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
function headerValue(value: string | string[] | undefined): string {
|
|
130
|
-
if (Array.isArray(value)) return value[0] ?? '';
|
|
131
|
-
return value ?? '';
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
async function readInteractionBody(request: IncomingMessage): Promise<string> {
|
|
135
|
-
const chunks: Buffer[] = [];
|
|
136
|
-
let size = 0;
|
|
137
|
-
for await (const chunk of request) {
|
|
138
|
-
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
139
|
-
size += buffer.length;
|
|
140
|
-
if (size > 1_048_576) {
|
|
141
|
-
request.destroy();
|
|
142
|
-
throw new Error('Request body exceeds 1MB');
|
|
143
|
-
}
|
|
144
|
-
chunks.push(buffer);
|
|
109
|
+
return new Response('Internal Server Error', { status: 500, headers: { 'Content-Type': 'text/plain' } });
|
|
145
110
|
}
|
|
146
|
-
return Buffer.concat(chunks).toString('utf8');
|
|
147
111
|
}
|
|
148
112
|
|
|
149
|
-
function writeJson(
|
|
150
|
-
|
|
151
|
-
response.end(JSON.stringify(body));
|
|
113
|
+
function writeJson(status: number, body: unknown): Response {
|
|
114
|
+
return new Response(JSON.stringify(body), { status, headers: { 'Content-Type': 'application/json' } });
|
|
152
115
|
}
|