@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.
Files changed (48) hide show
  1. package/CHANGELOG.md +169 -0
  2. package/LICENSE +21 -0
  3. package/PERMITS.md +15 -14
  4. package/README.md +6 -2
  5. package/adapters/discord/index.js +3 -0
  6. package/adapters/discord/index.ts +3 -0
  7. package/agents/discord/conversation.js +70 -0
  8. package/agents/discord/conversation.ts +77 -0
  9. package/agents/discord/messages.js +18 -2
  10. package/agents/discord/messages.ts +18 -2
  11. package/agents/discord/skills/discord/SKILL.md +1 -1
  12. package/agents/discord/skills/discord/tools/react/index.js +7 -6
  13. package/agents/discord/skills/discord/tools/react/index.ts +8 -7
  14. package/agents/discord/skills/discord/tools/send_embed/index.js +6 -11
  15. package/agents/discord/skills/discord/tools/send_embed/index.ts +7 -12
  16. package/agents/discord/skills/discord-guild/tools/add_role/index.js +11 -9
  17. package/agents/discord/skills/discord-guild/tools/add_role/index.ts +11 -10
  18. package/agents/discord/skills/discord-guild/tools/create_thread/index.js +5 -5
  19. package/agents/discord/skills/discord-guild/tools/create_thread/index.ts +6 -6
  20. package/agents/discord/skills/discord-guild/tools/forum_post/index.js +7 -3
  21. package/agents/discord/skills/discord-guild/tools/forum_post/index.ts +7 -4
  22. package/agents/discord/skills/discord-guild/tools/list_roles/index.js +7 -8
  23. package/agents/discord/skills/discord-guild/tools/list_roles/index.ts +8 -9
  24. package/agents/discord/skills/discord-guild/tools/remove_role/index.js +11 -9
  25. package/agents/discord/skills/discord-guild/tools/remove_role/index.ts +11 -10
  26. package/lib/endpoint.js +71 -12
  27. package/lib/gateway.d.ts +1 -0
  28. package/lib/gateway.js +21 -8
  29. package/lib/index.d.ts +1 -1
  30. package/lib/index.js +1 -1
  31. package/lib/platform-permit.d.ts +4 -11
  32. package/lib/platform-permit.js +7 -40
  33. package/lib/protocol.d.ts +18 -0
  34. package/lib/protocol.js +32 -3
  35. package/lib/sender-standing.d.ts +18 -0
  36. package/lib/sender-standing.js +91 -0
  37. package/lib/webhook.d.ts +2 -6
  38. package/lib/webhook.js +23 -55
  39. package/package.json +23 -25
  40. package/plugin.js +0 -6
  41. package/schema.json +0 -56
  42. package/src/endpoint.ts +73 -11
  43. package/src/gateway.ts +22 -5
  44. package/src/index.ts +1 -6
  45. package/src/platform-permit.ts +5 -52
  46. package/src/protocol.ts +43 -3
  47. package/src/sender-standing.ts +91 -0
  48. 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 { HttpHost, HttpRouteRegistration } from '@zhin.js/host-http';
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, async (request, response) => {
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: IncomingMessage,
51
- response: ServerResponse,
47
+ request: Request,
52
48
  handler: DiscordInteractionsHandler,
53
- ): Promise<void> {
49
+ ): Promise<Response> {
54
50
  try {
55
- const signature = headerValue(request.headers['x-signature-ed25519']);
56
- const timestamp = headerValue(request.headers['x-signature-timestamp']);
57
- const rawBody = await readInteractionBody(request);
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
- response.writeHead(401, { 'Content-Type': 'text/plain' });
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
- response.writeHead(401, { 'Content-Type': 'text/plain' });
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
- response.writeHead(401, { 'Content-Type': 'text/plain' });
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(response, 200, { type: INTERACTION_RESPONSE_PONG });
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) { response.writeHead(503); response.end('Unavailable'); return; }
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(response, 200, { type: 6 });
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(response, 200, {
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
- response.writeHead(400, { 'Content-Type': 'text/plain' });
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
- if (!response.headersSent) {
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(response: ServerResponse, status: number, body: unknown): void {
150
- response.writeHead(status, { 'Content-Type': 'application/json' });
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
  }