privage.js 0.21.0 → 0.25.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 +3 -0
- package/dist/index.d.ts +1 -1
- package/dist/rest.d.ts +5 -5
- package/dist/rest.js +41 -7
- package/dist/structures/Interaction.d.ts +47 -9
- package/dist/structures/Interaction.js +20 -8
- package/dist/structures/Message.d.ts +21 -0
- package/dist/structures/Message.js +16 -0
- package/package.json +1 -1
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/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export { Client } from './Client';
|
|
|
2
2
|
export { Intents } from './intents';
|
|
3
3
|
export type { Intent } from './intents';
|
|
4
4
|
export { Message, TextChannel } from './structures/Message';
|
|
5
|
-
export type { MessageThread, MessageReference, ReferencedMessage, MessageAttachment, MessageEmbed, } from './structures/Message';
|
|
5
|
+
export type { MessageThread, MessageReference, ReferencedMessage, MessageAttachment, MessageEmbed, MessageInteraction, } from './structures/Message';
|
|
6
6
|
export { Member } from './structures/Member';
|
|
7
7
|
export { Server } from './structures/Server';
|
|
8
8
|
export { Channel } from './structures/Channel';
|
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
|
|
123
|
-
* callback must follow), or 'modal' (open a modal
|
|
124
|
-
* only, once). Response: `{ success, message }`
|
|
125
|
-
* ephemeral reply's `message` is a reduced,
|
|
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
|
|
148
|
-
* callback must follow), or 'modal' (open a modal
|
|
149
|
-
* only, once). Response: `{ success, message }`
|
|
150
|
-
* ephemeral reply's `message` is a reduced,
|
|
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
|
-
|
|
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,27 @@ 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
|
+
/**
|
|
24
|
+
* Components on the reply — ephemeral replies only. They are fully
|
|
25
|
+
* interactive: clicks arrive as fresh `interaction` events carrying
|
|
26
|
+
* `ephemeralMessageId`, recipient-locked to the user the ephemeral was
|
|
27
|
+
* delivered to, for 15 minutes after sending.
|
|
28
|
+
*/
|
|
22
29
|
components?: (ActionRow | ActionRowBuilder | Record<string, unknown>)[];
|
|
30
|
+
/**
|
|
31
|
+
* File attachments on the reply — `AttachmentBuilder`s, paths, raw bytes,
|
|
32
|
+
* or Blobs (max 10, 25 MB each). Non-ephemeral replies only; cannot be
|
|
33
|
+
* combined with `embeds` or `components` (v1). Uploads as multipart with
|
|
34
|
+
* an auto-generated `nonce`, so a retried callback never duplicates the
|
|
35
|
+
* message.
|
|
36
|
+
*/
|
|
37
|
+
files?: (AttachmentBuilder | AttachmentSource)[];
|
|
38
|
+
/** Client-dedupe nonce — auto-generated for file replies when omitted. */
|
|
39
|
+
nonce?: string;
|
|
23
40
|
}
|
|
24
41
|
export type CommandOptionType = 'string' | 'integer' | 'number' | 'boolean' | 'user' | 'channel' | 'role';
|
|
25
42
|
/** One resolved option of a slash-command invocation (already validated + coerced by the server). */
|
|
@@ -73,8 +90,18 @@ export declare class Interaction {
|
|
|
73
90
|
readonly command: InteractionCommand | null;
|
|
74
91
|
/** Submitted modal fields — `kind: 'modal_submit'` only, `null` otherwise. */
|
|
75
92
|
readonly fields: ModalSubmitField[] | null;
|
|
76
|
-
/** The source message. `null` for commands
|
|
93
|
+
/** The source message. `null` for commands, command-born modal submits, and ephemeral-born interactions — `update()` is invalid then. */
|
|
77
94
|
readonly messageId: string | null;
|
|
95
|
+
/**
|
|
96
|
+
* Set when this interaction came from a component on an **ephemeral
|
|
97
|
+
* reply** — the synthetic ephemeral message's id. Recipient-locked:
|
|
98
|
+
* only the ephemeral's recipient can click, within the panel's
|
|
99
|
+
* 15-minute window (every click also re-checks membership/timeout).
|
|
100
|
+
* Respond with `update()` to morph the panel in place (the wizard
|
|
101
|
+
* pattern — updating refreshes the window), or `reply()` to post a
|
|
102
|
+
* new message.
|
|
103
|
+
*/
|
|
104
|
+
readonly ephemeralMessageId: string | null;
|
|
78
105
|
readonly channelId: string;
|
|
79
106
|
readonly serverId: string | null;
|
|
80
107
|
readonly user: {
|
|
@@ -103,16 +130,22 @@ export declare class Interaction {
|
|
|
103
130
|
/** `kind === 'modal_submit'`. */
|
|
104
131
|
isModalSubmit(): boolean;
|
|
105
132
|
/**
|
|
106
|
-
* Terminal: edit the
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
133
|
+
* Terminal: edit the message this interaction came from
|
|
134
|
+
* (content/embeds/components). On a persisted message that's a normal
|
|
135
|
+
* edit broadcast. On an interaction born from an **ephemeral reply**
|
|
136
|
+
* (`ephemeralMessageId` set) it morphs the ephemeral panel in place —
|
|
137
|
+
* patch semantics (only the fields you send change), refreshing the
|
|
138
|
+
* panel's 15-minute window; clearing `components: []` ends the panel,
|
|
139
|
+
* and an expired panel rejects with a 404. Throws client-side for
|
|
140
|
+
* `kind: 'command'` and command-born modal submits (no source at all —
|
|
141
|
+
* the server would 400).
|
|
110
142
|
*/
|
|
111
143
|
update(data: InteractionUpdateData): Promise<any>;
|
|
112
144
|
/**
|
|
113
145
|
* Terminal: post a bot message replying to the original (a plain channel
|
|
114
146
|
* message for commands). Pass `{ ephemeral: true }` for an only-you reply
|
|
115
|
-
* that is never persisted
|
|
147
|
+
* that is never persisted, or `{ files: [...] }` to attach uploads
|
|
148
|
+
* (non-ephemeral only; needs `ATTACH_FILES` in the channel).
|
|
116
149
|
*/
|
|
117
150
|
reply(data: string | InteractionReplyData): Promise<any>;
|
|
118
151
|
/**
|
|
@@ -127,7 +160,12 @@ export declare class Interaction {
|
|
|
127
160
|
* deferred reply and replying are the same terminal callback.
|
|
128
161
|
*/
|
|
129
162
|
editReply(data: string | InteractionReplyData): Promise<any>;
|
|
130
|
-
/**
|
|
163
|
+
/**
|
|
164
|
+
* Extend the response window to 15 minutes; must still be followed by
|
|
165
|
+
* `update()` or `reply()`. The invoker's client is told the bot needs
|
|
166
|
+
* more time, so a command's "thinking" row waits patiently instead of
|
|
167
|
+
* timing out at 15 seconds.
|
|
168
|
+
*/
|
|
131
169
|
defer(): Promise<any>;
|
|
132
170
|
/**
|
|
133
171
|
* Open a modal dialog for the interacting user; their submission arrives
|
|
@@ -49,6 +49,7 @@ class Interaction {
|
|
|
49
49
|
? data.modal.fields.map((f) => ({ customId: String(f?.custom_id ?? ''), value: String(f?.value ?? '') }))
|
|
50
50
|
: null;
|
|
51
51
|
this.messageId = data.message_id != null ? String(data.message_id) : null;
|
|
52
|
+
this.ephemeralMessageId = data.ephemeral_message_id != null ? String(data.ephemeral_message_id) : null;
|
|
52
53
|
this.channelId = String(data.channel_id ?? '');
|
|
53
54
|
this.serverId = data.server_id != null ? String(data.server_id) : null;
|
|
54
55
|
this.user = {
|
|
@@ -87,24 +88,30 @@ class Interaction {
|
|
|
87
88
|
return this.kind === 'modal_submit';
|
|
88
89
|
}
|
|
89
90
|
/**
|
|
90
|
-
* Terminal: edit the
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
91
|
+
* Terminal: edit the message this interaction came from
|
|
92
|
+
* (content/embeds/components). On a persisted message that's a normal
|
|
93
|
+
* edit broadcast. On an interaction born from an **ephemeral reply**
|
|
94
|
+
* (`ephemeralMessageId` set) it morphs the ephemeral panel in place —
|
|
95
|
+
* patch semantics (only the fields you send change), refreshing the
|
|
96
|
+
* panel's 15-minute window; clearing `components: []` ends the panel,
|
|
97
|
+
* and an expired panel rejects with a 404. Throws client-side for
|
|
98
|
+
* `kind: 'command'` and command-born modal submits (no source at all —
|
|
99
|
+
* the server would 400).
|
|
94
100
|
*/
|
|
95
101
|
update(data) {
|
|
96
102
|
if (this.kind === 'command') {
|
|
97
103
|
throw new RangeError('update() is not valid for command interactions — there is no source message. Use reply() instead.');
|
|
98
104
|
}
|
|
99
|
-
if (this.
|
|
100
|
-
throw new RangeError('update() is not valid for this
|
|
105
|
+
if (!this.messageId && !this.ephemeralMessageId) {
|
|
106
|
+
throw new RangeError('update() is not valid for this interaction — it has no source message. Use reply() instead.');
|
|
101
107
|
}
|
|
102
108
|
return this.transition('responded', () => this.client.rest.interactionCallback(this.id, this.token, 'update', data));
|
|
103
109
|
}
|
|
104
110
|
/**
|
|
105
111
|
* Terminal: post a bot message replying to the original (a plain channel
|
|
106
112
|
* message for commands). Pass `{ ephemeral: true }` for an only-you reply
|
|
107
|
-
* that is never persisted
|
|
113
|
+
* that is never persisted, or `{ files: [...] }` to attach uploads
|
|
114
|
+
* (non-ephemeral only; needs `ATTACH_FILES` in the channel).
|
|
108
115
|
*/
|
|
109
116
|
reply(data) {
|
|
110
117
|
return this.transition('responded', () => this.client.rest.interactionCallback(this.id, this.token, 'reply', typeof data === 'string' ? { content: data } : data));
|
|
@@ -125,7 +132,12 @@ class Interaction {
|
|
|
125
132
|
editReply(data) {
|
|
126
133
|
return this.reply(data);
|
|
127
134
|
}
|
|
128
|
-
/**
|
|
135
|
+
/**
|
|
136
|
+
* Extend the response window to 15 minutes; must still be followed by
|
|
137
|
+
* `update()` or `reply()`. The invoker's client is told the bot needs
|
|
138
|
+
* more time, so a command's "thinking" row waits patiently instead of
|
|
139
|
+
* timing out at 15 seconds.
|
|
140
|
+
*/
|
|
129
141
|
defer() {
|
|
130
142
|
if (this.state !== 'pending') {
|
|
131
143
|
return this.client.rest.interactionCallback(this.id, this.token, 'defer');
|
|
@@ -76,6 +76,25 @@ export interface MessageEmbed {
|
|
|
76
76
|
timestamp: string | null;
|
|
77
77
|
createdAt: Date | null;
|
|
78
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* Server-verified attribution on an interaction-born bot reply — who
|
|
81
|
+
* invoked the command/component that produced this message ("X used
|
|
82
|
+
* /price"). `user` is a frozen snapshot from interaction time; later
|
|
83
|
+
* renames never rewrite it. Ordinary messages carry `interaction: null`.
|
|
84
|
+
*/
|
|
85
|
+
export interface MessageInteraction {
|
|
86
|
+
kind: 'command' | 'button' | 'select' | 'modal_submit';
|
|
87
|
+
/** Command name — `kind: 'command'` only, `null` otherwise. */
|
|
88
|
+
name: string | null;
|
|
89
|
+
/** Component/modal custom_id — component-born interactions only, `null` otherwise. */
|
|
90
|
+
customId: string | null;
|
|
91
|
+
/** The invoking user, snapshotted at interaction time. */
|
|
92
|
+
user: {
|
|
93
|
+
id: string;
|
|
94
|
+
username: string | null;
|
|
95
|
+
displayName: string | null;
|
|
96
|
+
};
|
|
97
|
+
}
|
|
79
98
|
/** A frozen or live preview of the message a reference points at. */
|
|
80
99
|
export interface ReferencedMessage {
|
|
81
100
|
id: string;
|
|
@@ -144,6 +163,8 @@ export declare class Message {
|
|
|
144
163
|
readonly reference: MessageReference | null;
|
|
145
164
|
/** Thread badge — `null` unless this message has (or had) a thread. */
|
|
146
165
|
readonly thread: MessageThread | null;
|
|
166
|
+
/** Attribution for interaction-born bot replies, or `null` (see {@link MessageInteraction}). */
|
|
167
|
+
readonly interaction: MessageInteraction | null;
|
|
147
168
|
/** The raw wire payload, for anything the typed surface doesn't expose yet. */
|
|
148
169
|
readonly raw: any;
|
|
149
170
|
constructor(client: Client, data: any);
|
|
@@ -123,6 +123,22 @@ class Message {
|
|
|
123
123
|
deletedAt: t.deleted_at ? new Date(t.deleted_at) : null,
|
|
124
124
|
}
|
|
125
125
|
: null;
|
|
126
|
+
const ix = data.interaction;
|
|
127
|
+
this.interaction =
|
|
128
|
+
ix && typeof ix === 'object'
|
|
129
|
+
? {
|
|
130
|
+
kind: ix.kind === 'select' || ix.kind === 'command' || ix.kind === 'modal_submit'
|
|
131
|
+
? ix.kind
|
|
132
|
+
: 'button',
|
|
133
|
+
name: ix.name != null ? String(ix.name) : null,
|
|
134
|
+
customId: ix.custom_id != null ? String(ix.custom_id) : null,
|
|
135
|
+
user: {
|
|
136
|
+
id: String(ix.user?.id ?? ''),
|
|
137
|
+
username: ix.user?.username ?? null,
|
|
138
|
+
displayName: ix.user?.display_name ?? null,
|
|
139
|
+
},
|
|
140
|
+
}
|
|
141
|
+
: null;
|
|
126
142
|
}
|
|
127
143
|
/**
|
|
128
144
|
* Compat alias (discord.js-style): the id of the message this one replies
|