privage.js 0.21.0 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -205,6 +205,9 @@ which have no source message) or `interaction.reply(...)` — optionally after
205
205
  (`reply({ ..., ephemeral: true })`) are visible only to the interacting user
206
206
  and never persisted — great for confirmations and errors; valid for every
207
207
  kind. Selected values arrive on `interaction.values` (selects only).
208
+ Replies can carry uploads — `reply({ files: [...] })`, same limits and
209
+ `ATTACH_FILES` requirement as `client.send` (non-ephemeral only, no
210
+ embeds/components alongside in v1).
208
211
 
209
212
  Limits: 5 rows/message; a row holds either ≤5 buttons or exactly one select
210
213
  (≤25 interactive components/message); label ≤80, `custom_id` ≤100 and unique
package/dist/rest.d.ts CHANGED
@@ -119,11 +119,11 @@ export declare class Rest {
119
119
  /**
120
120
  * Respond to an interaction. `type`: 'update' (terminal — edit the
121
121
  * original message), 'reply' (terminal — post a reply; `data.ephemeral:
122
- * true` for an only-you reply), 'defer' (extend the window; a terminal
123
- * callback must follow), or 'modal' (open a modal — pending interactions
124
- * only, once). Response: `{ success, message }` for update/reply (an
125
- * ephemeral reply's `message` is a reduced, never-persisted object);
126
- * defer/modal are 204s and resolve `null`.
122
+ * true` for an only-you reply, `data.files` for uploads), 'defer' (extend
123
+ * the window; a terminal callback must follow), or 'modal' (open a modal
124
+ * — pending interactions only, once). Response: `{ success, message }`
125
+ * for update/reply (an ephemeral reply's `message` is a reduced,
126
+ * never-persisted object); defer/modal are 204s and resolve `null`.
127
127
  */
128
128
  interactionCallback(interactionId: string, token: string, type: 'update' | 'reply' | 'defer' | 'modal', data?: object): Promise<any>;
129
129
  /** PUT /api/bots/commands — bulk overwrite the bot's slash commands (whole-or-400). Response: `{ success, commands }`. */
package/dist/rest.js CHANGED
@@ -144,14 +144,48 @@ class Rest {
144
144
  /**
145
145
  * Respond to an interaction. `type`: 'update' (terminal — edit the
146
146
  * original message), 'reply' (terminal — post a reply; `data.ephemeral:
147
- * true` for an only-you reply), 'defer' (extend the window; a terminal
148
- * callback must follow), or 'modal' (open a modal — pending interactions
149
- * only, once). Response: `{ success, message }` for update/reply (an
150
- * ephemeral reply's `message` is a reduced, never-persisted object);
151
- * defer/modal are 204s and resolve `null`.
147
+ * true` for an only-you reply, `data.files` for uploads), 'defer' (extend
148
+ * the window; a terminal callback must follow), or 'modal' (open a modal
149
+ * — pending interactions only, once). Response: `{ success, message }`
150
+ * for update/reply (an ephemeral reply's `message` is a reduced,
151
+ * never-persisted object); defer/modal are 204s and resolve `null`.
152
152
  */
153
- interactionCallback(interactionId, token, type, data) {
154
- return this.request(`/interactions/${interactionId}/${encodeURIComponent(token)}/callback`, { method: 'POST', body: data === undefined ? { type } : { type, data } });
153
+ async interactionCallback(interactionId, token, type, data) {
154
+ const path = `/interactions/${interactionId}/${encodeURIComponent(token)}/callback`;
155
+ const d = data;
156
+ if (d?.files?.length) {
157
+ // File replies upload as multipart; the server accepts them for
158
+ // non-ephemeral 'reply' only — fail loud and local on everything else.
159
+ if (type !== 'reply') {
160
+ throw new RangeError(`File attachments are only valid on reply callbacks (got '${type}').`);
161
+ }
162
+ if (d.ephemeral) {
163
+ throw new RangeError('Ephemeral replies cannot carry file attachments — nothing is persisted, so there is nowhere for the files to live.');
164
+ }
165
+ if (d.embeds?.length || d.components?.length) {
166
+ throw new RangeError('Attachments cannot be combined with embeds or components in one reply (v1) — send them separately.');
167
+ }
168
+ if (d.files.length > 10) {
169
+ throw new RangeError(`At most 10 attachments per reply (got ${d.files.length}).`);
170
+ }
171
+ const { files, ephemeral: _e, embeds: _em, components: _c, nonce, ...rest } = d;
172
+ const form = new FormData();
173
+ form.append('type', 'reply');
174
+ // Always carry a nonce: the server commits the message before the
175
+ // terminal state change, so a retried callback dedupes against it
176
+ // instead of inserting a second message.
177
+ form.append('data', JSON.stringify({ ...rest, nonce: nonce ?? (0, crypto_1.randomUUID)() }));
178
+ const parts = await (0, attachments_1.resolveFiles)(files);
179
+ for (const { blob, name } of parts) {
180
+ form.append('files', blob, name);
181
+ }
182
+ if (parts.some((p) => p.spoiler)) {
183
+ form.append('attachment_spoilers', JSON.stringify(parts.map((p) => p.spoiler)));
184
+ }
185
+ // Uploads get a longer budget than ordinary REST calls.
186
+ return this.request(path, { method: 'POST', body: form, timeoutMs: 60000 });
187
+ }
188
+ return this.request(path, { method: 'POST', body: data === undefined ? { type } : { type, data } });
155
189
  }
156
190
  /** PUT /api/bots/commands — bulk overwrite the bot's slash commands (whole-or-400). Response: `{ success, commands }`. */
157
191
  setCommands(commands) {
@@ -1,6 +1,7 @@
1
1
  import type { Client } from '../Client';
2
2
  import type { EmbedBuilder, Embed } from '../embeds';
3
3
  import type { ActionRow, ActionRowBuilder } from '../components';
4
+ import type { AttachmentBuilder, AttachmentSource } from '../attachments';
4
5
  import { ModalBuilder, type Modal } from '../modals';
5
6
  /** Discriminates what the user did. Pre-v2 envelopes (no `kind`) read as `'button'`. */
6
7
  export type InteractionKind = 'button' | 'select' | 'command' | 'modal_submit';
@@ -15,11 +16,22 @@ export interface InteractionReplyData {
15
16
  /**
16
17
  * Only-you reply: never persisted, delivered as an event to the
17
18
  * interacting user alone ("Only you can see this"). Valid for every
18
- * interaction kind. Still terminal.
19
+ * interaction kind. Still terminal. Cannot carry `files` (nothing is
20
+ * persisted, so there is nowhere for the bytes to live).
19
21
  */
20
22
  ephemeral?: boolean;
21
23
  /** Components on the reply — ephemeral replies only (they render, but aren't clickable v1). */
22
24
  components?: (ActionRow | ActionRowBuilder | Record<string, unknown>)[];
25
+ /**
26
+ * File attachments on the reply — `AttachmentBuilder`s, paths, raw bytes,
27
+ * or Blobs (max 10, 25 MB each). Non-ephemeral replies only; cannot be
28
+ * combined with `embeds` or `components` (v1). Uploads as multipart with
29
+ * an auto-generated `nonce`, so a retried callback never duplicates the
30
+ * message.
31
+ */
32
+ files?: (AttachmentBuilder | AttachmentSource)[];
33
+ /** Client-dedupe nonce — auto-generated for file replies when omitted. */
34
+ nonce?: string;
23
35
  }
24
36
  export type CommandOptionType = 'string' | 'integer' | 'number' | 'boolean' | 'user' | 'channel' | 'role';
25
37
  /** One resolved option of a slash-command invocation (already validated + coerced by the server). */
@@ -112,7 +124,8 @@ export declare class Interaction {
112
124
  /**
113
125
  * Terminal: post a bot message replying to the original (a plain channel
114
126
  * message for commands). Pass `{ ephemeral: true }` for an only-you reply
115
- * that is never persisted.
127
+ * that is never persisted, or `{ files: [...] }` to attach uploads
128
+ * (non-ephemeral only; needs `ATTACH_FILES` in the channel).
116
129
  */
117
130
  reply(data: string | InteractionReplyData): Promise<any>;
118
131
  /**
@@ -104,7 +104,8 @@ class Interaction {
104
104
  /**
105
105
  * Terminal: post a bot message replying to the original (a plain channel
106
106
  * message for commands). Pass `{ ephemeral: true }` for an only-you reply
107
- * that is never persisted.
107
+ * that is never persisted, or `{ files: [...] }` to attach uploads
108
+ * (non-ephemeral only; needs `ATTACH_FILES` in the channel).
108
109
  */
109
110
  reply(data) {
110
111
  return this.transition('responded', () => this.client.rest.interactionCallback(this.id, this.token, 'reply', typeof data === 'string' ? { content: data } : data));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "privage.js",
3
- "version": "0.21.0",
3
+ "version": "0.22.0",
4
4
  "description": "Official SDK for building Privage bots.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",