@theholocron/discord-client 1.43.1 → 1.44.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 +18 -9
- package/dist/index.d.mts +203 -1
- package/dist/index.mjs +148 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -52,8 +52,9 @@ into `{ id, token }`.
|
|
|
52
52
|
## Bot client — provisioning a server
|
|
53
53
|
|
|
54
54
|
`createDiscordBotClient` authenticates as a bot (`Authorization: Bot <token>`)
|
|
55
|
-
and covers what you need to set a server up.
|
|
56
|
-
|
|
55
|
+
and covers what you need to set a server up. `channels.createMessage` posts as the
|
|
56
|
+
bot (needs View Channel and Send Messages in that channel); to post through a
|
|
57
|
+
channel webhook with no token, use `createDiscordClient().webhooks.execute`.
|
|
57
58
|
|
|
58
59
|
```ts
|
|
59
60
|
import { ChannelType, createDiscordBotClient } from "@theholocron/discord-client";
|
|
@@ -98,13 +99,21 @@ the resource, look it up by name before creating it again. A webhook created
|
|
|
98
99
|
that way cannot have its URL read back, since Discord returns the token only on
|
|
99
100
|
the create response.
|
|
100
101
|
|
|
101
|
-
| Resource | Methods
|
|
102
|
-
| ----------------- |
|
|
103
|
-
| `client.users` | `me()` — `GET /users/@me`, a cheap token check
|
|
104
|
-
| `client.guilds` | `get`, `channels`, `createChannel`, `roles`, `createRole` |
|
|
105
|
-
| `client.channels` | `modify`, `webhooks`, `createWebhook`
|
|
106
|
-
|
|
107
|
-
|
|
102
|
+
| Resource | Methods |
|
|
103
|
+
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
104
|
+
| `client.users` | `me()` — `GET /users/@me`, a cheap token check |
|
|
105
|
+
| `client.guilds` | `get`, `modify`, `channels`, `createChannel`, `roles`, `createRole`, `modifyRole`, `welcomeScreen`, `modifyWelcomeScreen`, `onboarding`, `modifyOnboarding` |
|
|
106
|
+
| `client.channels` | `modify`, `webhooks`, `createWebhook`, `createMessage` |
|
|
107
|
+
| `client.webhooks` | `modify` (name, avatar), `delete` |
|
|
108
|
+
|
|
109
|
+
`permissions([...names])` builds the decimal-string bitfield Discord expects, and
|
|
110
|
+
`permissionNames(bitfield)` reads one back; `Permission` holds the bits themselves.
|
|
111
|
+
Welcome screen and onboarding only work once the server has the community
|
|
112
|
+
features on, and `modifyOnboarding` is a `PUT` that replaces the whole config.
|
|
113
|
+
`channels.createMessage` turns every mention off unless `allowed_mentions` says
|
|
114
|
+
otherwise.
|
|
115
|
+
|
|
116
|
+
The bot needs Manage Channels, Manage Roles, Manage Webhooks and (for server settings, welcome screen and onboarding) Manage Server in the server, plus View Channel and Send Messages in any channel it posts to with `channels.createMessage`.
|
|
108
117
|
Discord only returns a webhook's `token`/`url` for webhooks the same
|
|
109
118
|
application created, so a pre-existing webhook can be found by name but its URL
|
|
110
119
|
cannot be read back.
|
package/dist/index.d.mts
CHANGED
|
@@ -45,9 +45,91 @@ interface DiscordUser {
|
|
|
45
45
|
username: string;
|
|
46
46
|
bot?: boolean;
|
|
47
47
|
}
|
|
48
|
+
/** Guild fields `guilds.modify` can change; the numeric enums are Discord's own. */
|
|
48
49
|
interface DiscordGuild {
|
|
49
50
|
id: string;
|
|
50
51
|
name: string;
|
|
52
|
+
/** 0 none, 1 low, 2 medium, 3 high, 4 very high. */
|
|
53
|
+
verification_level?: number;
|
|
54
|
+
/** 0 all messages, 1 only @mentions. */
|
|
55
|
+
default_message_notifications?: number;
|
|
56
|
+
/** 0 disabled, 1 members without roles, 2 all members. */
|
|
57
|
+
explicit_content_filter?: number;
|
|
58
|
+
system_channel_id?: string | null;
|
|
59
|
+
/** Bitfield of system-channel messages to suppress. */
|
|
60
|
+
system_channel_flags?: number;
|
|
61
|
+
rules_channel_id?: string | null;
|
|
62
|
+
public_updates_channel_id?: string | null;
|
|
63
|
+
preferred_locale?: string;
|
|
64
|
+
description?: string | null;
|
|
65
|
+
/** `COMMUNITY` here means the community features (welcome screen, onboarding) are available. */
|
|
66
|
+
features?: string[];
|
|
67
|
+
}
|
|
68
|
+
interface ModifyGuildInput {
|
|
69
|
+
name?: string;
|
|
70
|
+
verification_level?: number;
|
|
71
|
+
default_message_notifications?: number;
|
|
72
|
+
explicit_content_filter?: number;
|
|
73
|
+
system_channel_id?: string | null;
|
|
74
|
+
system_channel_flags?: number;
|
|
75
|
+
rules_channel_id?: string | null;
|
|
76
|
+
public_updates_channel_id?: string | null;
|
|
77
|
+
preferred_locale?: string;
|
|
78
|
+
description?: string | null;
|
|
79
|
+
/** The full list of mutable guild features (for example `COMMUNITY`); Discord ignores the ones it manages itself. */
|
|
80
|
+
features?: string[];
|
|
81
|
+
}
|
|
82
|
+
interface DiscordWelcomeChannel {
|
|
83
|
+
channel_id: string;
|
|
84
|
+
description: string;
|
|
85
|
+
emoji_id?: string | null;
|
|
86
|
+
emoji_name?: string | null;
|
|
87
|
+
}
|
|
88
|
+
/** Whether the screen is on is the `WELCOME_SCREEN_ENABLED` entry in the guild's `features`; the object has no flag. */
|
|
89
|
+
interface DiscordWelcomeScreen {
|
|
90
|
+
description: string | null;
|
|
91
|
+
welcome_channels: DiscordWelcomeChannel[];
|
|
92
|
+
}
|
|
93
|
+
interface ModifyWelcomeScreenInput {
|
|
94
|
+
enabled?: boolean;
|
|
95
|
+
description?: string | null;
|
|
96
|
+
welcome_channels?: DiscordWelcomeChannel[];
|
|
97
|
+
}
|
|
98
|
+
interface DiscordEmoji {
|
|
99
|
+
id: string | null;
|
|
100
|
+
name: string | null;
|
|
101
|
+
animated?: boolean;
|
|
102
|
+
}
|
|
103
|
+
interface DiscordOnboardingOption {
|
|
104
|
+
id?: string;
|
|
105
|
+
title: string;
|
|
106
|
+
description?: string | null;
|
|
107
|
+
channel_ids?: string[];
|
|
108
|
+
role_ids?: string[];
|
|
109
|
+
/** Discord returns the emoji as an object... */
|
|
110
|
+
emoji?: DiscordEmoji;
|
|
111
|
+
/** ...and takes it as these three fields (a custom emoji has `emoji_id`, a Unicode one only `emoji_name`). */
|
|
112
|
+
emoji_id?: string | null;
|
|
113
|
+
emoji_name?: string | null;
|
|
114
|
+
emoji_animated?: boolean;
|
|
115
|
+
}
|
|
116
|
+
interface DiscordOnboardingPrompt {
|
|
117
|
+
id?: string;
|
|
118
|
+
/** 0 multiple choice, 1 dropdown. */
|
|
119
|
+
type: number;
|
|
120
|
+
title: string;
|
|
121
|
+
single_select: boolean;
|
|
122
|
+
required: boolean;
|
|
123
|
+
in_onboarding: boolean;
|
|
124
|
+
options: DiscordOnboardingOption[];
|
|
125
|
+
}
|
|
126
|
+
interface DiscordOnboarding {
|
|
127
|
+
guild_id?: string;
|
|
128
|
+
prompts: DiscordOnboardingPrompt[];
|
|
129
|
+
default_channel_ids: string[];
|
|
130
|
+
enabled: boolean;
|
|
131
|
+
/** 0 default, 1 advanced. */
|
|
132
|
+
mode?: number;
|
|
51
133
|
}
|
|
52
134
|
interface DiscordPermissionOverwrite {
|
|
53
135
|
/** Role or user id. */
|
|
@@ -87,9 +169,20 @@ interface DiscordRole {
|
|
|
87
169
|
name: string;
|
|
88
170
|
color: number;
|
|
89
171
|
mentionable: boolean;
|
|
172
|
+
hoist?: boolean;
|
|
173
|
+
/** Bitfield of permissions, as a string. */
|
|
174
|
+
permissions?: string;
|
|
90
175
|
managed?: boolean;
|
|
91
176
|
position?: number;
|
|
92
177
|
}
|
|
178
|
+
interface ModifyRoleInput {
|
|
179
|
+
name?: string;
|
|
180
|
+
color?: number;
|
|
181
|
+
hoist?: boolean;
|
|
182
|
+
mentionable?: boolean;
|
|
183
|
+
/** Bitfield of permissions, as a string. */
|
|
184
|
+
permissions?: string;
|
|
185
|
+
}
|
|
93
186
|
interface CreateRoleInput {
|
|
94
187
|
name: string;
|
|
95
188
|
color?: number;
|
|
@@ -101,12 +194,36 @@ interface CreateRoleInput {
|
|
|
101
194
|
interface DiscordChannelWebhook {
|
|
102
195
|
id: string;
|
|
103
196
|
name: string | null;
|
|
197
|
+
/** The avatar hash, or `null` for the default one. */
|
|
198
|
+
avatar?: string | null;
|
|
104
199
|
channel_id: string;
|
|
105
200
|
/** Present only on webhooks this application created; Discord omits it otherwise. */
|
|
106
201
|
token?: string;
|
|
107
202
|
/** Present only on webhooks this application created. */
|
|
108
203
|
url?: string;
|
|
109
204
|
}
|
|
205
|
+
interface CreateMessageInput {
|
|
206
|
+
content: string;
|
|
207
|
+
/** With `enforce_nonce`, Discord drops a repeat of the same nonce, so a retry after a lost response cannot post twice. */
|
|
208
|
+
nonce?: string | number;
|
|
209
|
+
enforce_nonce?: boolean;
|
|
210
|
+
/** Defaults to no mentions, so a pasted `@everyone` cannot ping anyone. */
|
|
211
|
+
allowed_mentions?: {
|
|
212
|
+
parse?: Array<"roles" | "users" | "everyone">;
|
|
213
|
+
roles?: string[];
|
|
214
|
+
users?: string[];
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
interface DiscordMessage {
|
|
218
|
+
id: string;
|
|
219
|
+
channel_id: string;
|
|
220
|
+
content: string;
|
|
221
|
+
}
|
|
222
|
+
interface ModifyWebhookInput {
|
|
223
|
+
name?: string;
|
|
224
|
+
/** A data URI (`data:image/png;base64,...`); `null` clears it. */
|
|
225
|
+
avatar?: string | null;
|
|
226
|
+
}
|
|
110
227
|
//#endregion
|
|
111
228
|
//#region ../../../../../src/bot/bot.d.ts
|
|
112
229
|
/**
|
|
@@ -127,21 +244,106 @@ declare function createDiscordBotClient(opts: DiscordBotClientOptions): {
|
|
|
127
244
|
};
|
|
128
245
|
guilds: {
|
|
129
246
|
get: (guildId: string) => Promise<DiscordGuild>;
|
|
247
|
+
/** `PATCH /guilds/:id` — verification, notification defaults, content filter, system channels. */
|
|
248
|
+
modify: (guildId: string, body: ModifyGuildInput) => Promise<DiscordGuild>;
|
|
249
|
+
welcomeScreen: (guildId: string) => Promise<DiscordWelcomeScreen>;
|
|
250
|
+
/** Needs the community features on. */
|
|
251
|
+
modifyWelcomeScreen: (guildId: string, body: ModifyWelcomeScreenInput) => Promise<DiscordWelcomeScreen>;
|
|
252
|
+
onboarding: (guildId: string) => Promise<DiscordOnboarding>;
|
|
253
|
+
/**
|
|
254
|
+
* `PUT` replaces the whole onboarding config, so send every prompt you want to keep. Discord has no
|
|
255
|
+
* conditional write: two callers that read, edit and PUT at once, the later one wins.
|
|
256
|
+
*/
|
|
257
|
+
modifyOnboarding: (guildId: string, body: Omit<DiscordOnboarding, "guild_id">) => Promise<DiscordOnboarding>;
|
|
130
258
|
channels: (guildId: string) => Promise<DiscordChannel[]>;
|
|
131
259
|
createChannel: (guildId: string, body: CreateChannelInput) => Promise<DiscordChannel>;
|
|
132
260
|
roles: (guildId: string) => Promise<DiscordRole[]>;
|
|
133
261
|
createRole: (guildId: string, body: CreateRoleInput) => Promise<DiscordRole>;
|
|
262
|
+
modifyRole: (guildId: string, roleId: string, body: ModifyRoleInput) => Promise<DiscordRole>;
|
|
134
263
|
};
|
|
135
264
|
channels: {
|
|
136
265
|
modify: (channelId: string, body: ModifyChannelInput) => Promise<DiscordChannel>;
|
|
137
266
|
webhooks: (channelId: string) => Promise<DiscordChannelWebhook[]>;
|
|
267
|
+
/** `POST /channels/:id/messages` as the bot. Mentions are off unless `allowed_mentions` asks for them. */
|
|
268
|
+
createMessage: (channelId: string, body: CreateMessageInput) => Promise<DiscordMessage>;
|
|
138
269
|
createWebhook: (channelId: string, body: {
|
|
139
270
|
name: string;
|
|
140
271
|
}) => Promise<DiscordChannelWebhook>;
|
|
141
272
|
};
|
|
273
|
+
webhooks: {
|
|
274
|
+
/** `PATCH /webhooks/:id` — name and avatar of a webhook this bot manages. */
|
|
275
|
+
modify: (webhookId: string, body: ModifyWebhookInput) => Promise<DiscordChannelWebhook>;
|
|
276
|
+
/** `DELETE /webhooks/:id` — e.g. to roll back an orphan whose URL was never stored. */
|
|
277
|
+
/** A 404 counts as done: the webhook is already gone, as when a retry follows a lost response. */
|
|
278
|
+
delete: (webhookId: string) => Promise<void>;
|
|
279
|
+
};
|
|
142
280
|
};
|
|
143
281
|
type DiscordBotClient = ReturnType<typeof createDiscordBotClient>;
|
|
144
282
|
//#endregion
|
|
283
|
+
//#region ../../../../../src/bot/permissions.d.ts
|
|
284
|
+
/**
|
|
285
|
+
* Discord permission bits, by the names the Discord docs use. Permissions travel as
|
|
286
|
+
* decimal strings because they overflow a JS number's safe integer range. Bits Discord
|
|
287
|
+
* adds after this list are not named here: `permissionNames` leaves them out, and
|
|
288
|
+
* `permissions` cannot set them by name.
|
|
289
|
+
*/
|
|
290
|
+
declare const Permission: {
|
|
291
|
+
readonly CreateInstantInvite: bigint;
|
|
292
|
+
readonly KickMembers: bigint;
|
|
293
|
+
readonly BanMembers: bigint;
|
|
294
|
+
readonly Administrator: bigint;
|
|
295
|
+
readonly ManageChannels: bigint;
|
|
296
|
+
readonly ManageGuild: bigint;
|
|
297
|
+
readonly AddReactions: bigint;
|
|
298
|
+
readonly ViewAuditLog: bigint;
|
|
299
|
+
readonly PrioritySpeaker: bigint;
|
|
300
|
+
readonly Stream: bigint;
|
|
301
|
+
readonly ViewChannel: bigint;
|
|
302
|
+
readonly SendMessages: bigint;
|
|
303
|
+
readonly SendTtsMessages: bigint;
|
|
304
|
+
readonly ManageMessages: bigint;
|
|
305
|
+
readonly EmbedLinks: bigint;
|
|
306
|
+
readonly AttachFiles: bigint;
|
|
307
|
+
readonly ReadMessageHistory: bigint;
|
|
308
|
+
readonly MentionEveryone: bigint;
|
|
309
|
+
readonly UseExternalEmojis: bigint;
|
|
310
|
+
readonly ViewGuildInsights: bigint;
|
|
311
|
+
readonly Connect: bigint;
|
|
312
|
+
readonly Speak: bigint;
|
|
313
|
+
readonly MuteMembers: bigint;
|
|
314
|
+
readonly DeafenMembers: bigint;
|
|
315
|
+
readonly MoveMembers: bigint;
|
|
316
|
+
readonly UseVoiceActivity: bigint;
|
|
317
|
+
readonly ChangeNickname: bigint;
|
|
318
|
+
readonly ManageNicknames: bigint;
|
|
319
|
+
readonly ManageRoles: bigint;
|
|
320
|
+
readonly ManageWebhooks: bigint;
|
|
321
|
+
readonly ManageGuildExpressions: bigint;
|
|
322
|
+
readonly UseApplicationCommands: bigint;
|
|
323
|
+
readonly RequestToSpeak: bigint;
|
|
324
|
+
readonly ManageEvents: bigint;
|
|
325
|
+
readonly ManageThreads: bigint;
|
|
326
|
+
readonly CreatePublicThreads: bigint;
|
|
327
|
+
readonly CreatePrivateThreads: bigint;
|
|
328
|
+
readonly UseExternalStickers: bigint;
|
|
329
|
+
readonly SendMessagesInThreads: bigint;
|
|
330
|
+
readonly UseEmbeddedActivities: bigint;
|
|
331
|
+
readonly ModerateMembers: bigint;
|
|
332
|
+
readonly ViewCreatorMonetizationAnalytics: bigint;
|
|
333
|
+
readonly UseSoundboard: bigint;
|
|
334
|
+
readonly CreateGuildExpressions: bigint;
|
|
335
|
+
readonly CreateEvents: bigint;
|
|
336
|
+
readonly UseExternalSounds: bigint;
|
|
337
|
+
readonly SendVoiceMessages: bigint;
|
|
338
|
+
readonly SendPolls: bigint;
|
|
339
|
+
readonly UseExternalApps: bigint;
|
|
340
|
+
};
|
|
341
|
+
type PermissionName = keyof typeof Permission;
|
|
342
|
+
/** The decimal-string bitfield Discord wants for the named permissions (`"0"` for none). */
|
|
343
|
+
declare function permissions(names: readonly PermissionName[]): string;
|
|
344
|
+
/** The permission names set in a bitfield, in declaration order (unknown bits are ignored). */
|
|
345
|
+
declare function permissionNames(bitfield: string): PermissionName[];
|
|
346
|
+
//#endregion
|
|
145
347
|
//#region ../../../../../src/index.d.ts
|
|
146
348
|
declare function createDiscordClient(opts?: DiscordClientOptions): {
|
|
147
349
|
webhooks: {
|
|
@@ -151,4 +353,4 @@ declare function createDiscordClient(opts?: DiscordClientOptions): {
|
|
|
151
353
|
};
|
|
152
354
|
type DiscordClient = ReturnType<typeof createDiscordClient>;
|
|
153
355
|
//#endregion
|
|
154
|
-
export { ChannelType, CreateChannelInput, CreateRoleInput, DiscordBotClient, DiscordBotClientOptions, DiscordChannel, DiscordChannelWebhook, DiscordClient, type DiscordClientOptions, DiscordGuild, DiscordPermissionOverwrite, DiscordRole, DiscordUser, type DiscordWebhookInfo, ModifyChannelInput, createDiscordBotClient, createDiscordClient, parseWebhookUrl };
|
|
356
|
+
export { ChannelType, CreateChannelInput, CreateMessageInput, CreateRoleInput, DiscordBotClient, DiscordBotClientOptions, DiscordChannel, DiscordChannelWebhook, DiscordClient, type DiscordClientOptions, DiscordEmoji, DiscordGuild, DiscordMessage, DiscordOnboarding, DiscordOnboardingOption, DiscordOnboardingPrompt, DiscordPermissionOverwrite, DiscordRole, DiscordUser, type DiscordWebhookInfo, DiscordWelcomeChannel, DiscordWelcomeScreen, ModifyChannelInput, ModifyGuildInput, ModifyRoleInput, ModifyWebhookInput, ModifyWelcomeScreenInput, Permission, PermissionName, createDiscordBotClient, createDiscordClient, parseWebhookUrl, permissionNames, permissions };
|
package/dist/index.mjs
CHANGED
|
@@ -55,6 +55,13 @@ function id(value) {
|
|
|
55
55
|
if (!/^\d{1,20}$/.test(value)) throw new Error(`invalid Discord id: ${JSON.stringify(value)}`);
|
|
56
56
|
return value;
|
|
57
57
|
}
|
|
58
|
+
/** Validates ids that travel in a request body, so a malformed one fails here and not at Discord. */
|
|
59
|
+
function bodyIds(...values) {
|
|
60
|
+
for (const value of values) {
|
|
61
|
+
if (value === void 0 || value === null) continue;
|
|
62
|
+
for (const one of typeof value === "string" ? [value] : value) id(one);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
58
65
|
/** How long Discord asks us to wait after a 429, in ms (header first, then the JSON body). */
|
|
59
66
|
async function retryDelayMs(res) {
|
|
60
67
|
const header = Number(res.headers.get("retry-after"));
|
|
@@ -119,6 +126,36 @@ function createDiscordBotClient(opts) {
|
|
|
119
126
|
me: () => rest.request("/users/@me") },
|
|
120
127
|
guilds: {
|
|
121
128
|
get: (guildId) => rest.request(`/guilds/${id(guildId)}`),
|
|
129
|
+
/** `PATCH /guilds/:id` — verification, notification defaults, content filter, system channels. */
|
|
130
|
+
modify: (guildId, body) => {
|
|
131
|
+
bodyIds(body.system_channel_id, body.rules_channel_id, body.public_updates_channel_id);
|
|
132
|
+
return rest.request(`/guilds/${id(guildId)}`, {
|
|
133
|
+
method: "PATCH",
|
|
134
|
+
body
|
|
135
|
+
});
|
|
136
|
+
},
|
|
137
|
+
welcomeScreen: (guildId) => rest.request(`/guilds/${id(guildId)}/welcome-screen`),
|
|
138
|
+
/** Needs the community features on. */
|
|
139
|
+
modifyWelcomeScreen: (guildId, body) => {
|
|
140
|
+
for (const c of body.welcome_channels ?? []) bodyIds(c.channel_id, c.emoji_id);
|
|
141
|
+
return rest.request(`/guilds/${id(guildId)}/welcome-screen`, {
|
|
142
|
+
method: "PATCH",
|
|
143
|
+
body
|
|
144
|
+
});
|
|
145
|
+
},
|
|
146
|
+
onboarding: (guildId) => rest.request(`/guilds/${id(guildId)}/onboarding`),
|
|
147
|
+
/**
|
|
148
|
+
* `PUT` replaces the whole onboarding config, so send every prompt you want to keep. Discord has no
|
|
149
|
+
* conditional write: two callers that read, edit and PUT at once, the later one wins.
|
|
150
|
+
*/
|
|
151
|
+
modifyOnboarding: (guildId, body) => {
|
|
152
|
+
bodyIds(body.default_channel_ids);
|
|
153
|
+
for (const prompt of body.prompts) for (const option of prompt.options) bodyIds(option.channel_ids, option.role_ids, option.emoji_id);
|
|
154
|
+
return rest.request(`/guilds/${id(guildId)}/onboarding`, {
|
|
155
|
+
method: "PUT",
|
|
156
|
+
body
|
|
157
|
+
});
|
|
158
|
+
},
|
|
122
159
|
channels: (guildId) => rest.request(`/guilds/${id(guildId)}/channels`),
|
|
123
160
|
createChannel: (guildId, body) => rest.request(`/guilds/${id(guildId)}/channels`, {
|
|
124
161
|
method: "POST",
|
|
@@ -128,6 +165,10 @@ me: () => rest.request("/users/@me") },
|
|
|
128
165
|
createRole: (guildId, body) => rest.request(`/guilds/${id(guildId)}/roles`, {
|
|
129
166
|
method: "POST",
|
|
130
167
|
body
|
|
168
|
+
}),
|
|
169
|
+
modifyRole: (guildId, roleId, body) => rest.request(`/guilds/${id(guildId)}/roles/${id(roleId)}`, {
|
|
170
|
+
method: "PATCH",
|
|
171
|
+
body
|
|
131
172
|
})
|
|
132
173
|
},
|
|
133
174
|
channels: {
|
|
@@ -136,10 +177,41 @@ me: () => rest.request("/users/@me") },
|
|
|
136
177
|
body
|
|
137
178
|
}),
|
|
138
179
|
webhooks: async (channelId) => (await rest.request(`/channels/${id(channelId)}/webhooks`)).map(withUrl),
|
|
180
|
+
/** `POST /channels/:id/messages` as the bot. Mentions are off unless `allowed_mentions` asks for them. */
|
|
181
|
+
createMessage: (channelId, body) => {
|
|
182
|
+
bodyIds(body.allowed_mentions?.roles, body.allowed_mentions?.users);
|
|
183
|
+
return rest.request(`/channels/${id(channelId)}/messages`, {
|
|
184
|
+
method: "POST",
|
|
185
|
+
body: {
|
|
186
|
+
...body,
|
|
187
|
+
allowed_mentions: body.allowed_mentions ?? { parse: [] }
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
},
|
|
139
191
|
createWebhook: async (channelId, body) => withUrl(await rest.request(`/channels/${id(channelId)}/webhooks`, {
|
|
140
192
|
method: "POST",
|
|
141
193
|
body
|
|
142
194
|
}))
|
|
195
|
+
},
|
|
196
|
+
webhooks: {
|
|
197
|
+
/** `PATCH /webhooks/:id` — name and avatar of a webhook this bot manages. */
|
|
198
|
+
modify: async (webhookId, body) => withUrl(await rest.request(`/webhooks/${id(webhookId)}`, {
|
|
199
|
+
method: "PATCH",
|
|
200
|
+
body
|
|
201
|
+
})),
|
|
202
|
+
/** `DELETE /webhooks/:id` — e.g. to roll back an orphan whose URL was never stored. */
|
|
203
|
+
/** A 404 counts as done: the webhook is already gone, as when a retry follows a lost response. */
|
|
204
|
+
delete: async (webhookId) => {
|
|
205
|
+
try {
|
|
206
|
+
await rest.request(`/webhooks/${id(webhookId)}`, {
|
|
207
|
+
method: "DELETE",
|
|
208
|
+
expectNoContent: true
|
|
209
|
+
});
|
|
210
|
+
} catch (err) {
|
|
211
|
+
if (err instanceof ProviderApiError && err.status === 404) return;
|
|
212
|
+
throw err;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
143
215
|
}
|
|
144
216
|
};
|
|
145
217
|
}
|
|
@@ -153,9 +225,84 @@ const ChannelType = {
|
|
|
153
225
|
Forum: 15
|
|
154
226
|
};
|
|
155
227
|
//#endregion
|
|
228
|
+
//#region ../../../../../src/bot/permissions.ts
|
|
229
|
+
/**
|
|
230
|
+
* Discord permission bits, by the names the Discord docs use. Permissions travel as
|
|
231
|
+
* decimal strings because they overflow a JS number's safe integer range. Bits Discord
|
|
232
|
+
* adds after this list are not named here: `permissionNames` leaves them out, and
|
|
233
|
+
* `permissions` cannot set them by name.
|
|
234
|
+
*/
|
|
235
|
+
const Permission = {
|
|
236
|
+
CreateInstantInvite: 1n << 0n,
|
|
237
|
+
KickMembers: 1n << 1n,
|
|
238
|
+
BanMembers: 1n << 2n,
|
|
239
|
+
Administrator: 1n << 3n,
|
|
240
|
+
ManageChannels: 1n << 4n,
|
|
241
|
+
ManageGuild: 1n << 5n,
|
|
242
|
+
AddReactions: 1n << 6n,
|
|
243
|
+
ViewAuditLog: 1n << 7n,
|
|
244
|
+
PrioritySpeaker: 1n << 8n,
|
|
245
|
+
Stream: 1n << 9n,
|
|
246
|
+
ViewChannel: 1n << 10n,
|
|
247
|
+
SendMessages: 1n << 11n,
|
|
248
|
+
SendTtsMessages: 1n << 12n,
|
|
249
|
+
ManageMessages: 1n << 13n,
|
|
250
|
+
EmbedLinks: 1n << 14n,
|
|
251
|
+
AttachFiles: 1n << 15n,
|
|
252
|
+
ReadMessageHistory: 1n << 16n,
|
|
253
|
+
MentionEveryone: 1n << 17n,
|
|
254
|
+
UseExternalEmojis: 1n << 18n,
|
|
255
|
+
ViewGuildInsights: 1n << 19n,
|
|
256
|
+
Connect: 1n << 20n,
|
|
257
|
+
Speak: 1n << 21n,
|
|
258
|
+
MuteMembers: 1n << 22n,
|
|
259
|
+
DeafenMembers: 1n << 23n,
|
|
260
|
+
MoveMembers: 1n << 24n,
|
|
261
|
+
UseVoiceActivity: 1n << 25n,
|
|
262
|
+
ChangeNickname: 1n << 26n,
|
|
263
|
+
ManageNicknames: 1n << 27n,
|
|
264
|
+
ManageRoles: 1n << 28n,
|
|
265
|
+
ManageWebhooks: 1n << 29n,
|
|
266
|
+
ManageGuildExpressions: 1n << 30n,
|
|
267
|
+
UseApplicationCommands: 1n << 31n,
|
|
268
|
+
RequestToSpeak: 1n << 32n,
|
|
269
|
+
ManageEvents: 1n << 33n,
|
|
270
|
+
ManageThreads: 1n << 34n,
|
|
271
|
+
CreatePublicThreads: 1n << 35n,
|
|
272
|
+
CreatePrivateThreads: 1n << 36n,
|
|
273
|
+
UseExternalStickers: 1n << 37n,
|
|
274
|
+
SendMessagesInThreads: 1n << 38n,
|
|
275
|
+
UseEmbeddedActivities: 1n << 39n,
|
|
276
|
+
ModerateMembers: 1n << 40n,
|
|
277
|
+
ViewCreatorMonetizationAnalytics: 1n << 41n,
|
|
278
|
+
UseSoundboard: 1n << 42n,
|
|
279
|
+
CreateGuildExpressions: 1n << 43n,
|
|
280
|
+
CreateEvents: 1n << 44n,
|
|
281
|
+
UseExternalSounds: 1n << 45n,
|
|
282
|
+
SendVoiceMessages: 1n << 46n,
|
|
283
|
+
SendPolls: 1n << 49n,
|
|
284
|
+
UseExternalApps: 1n << 50n
|
|
285
|
+
};
|
|
286
|
+
/** The decimal-string bitfield Discord wants for the named permissions (`"0"` for none). */
|
|
287
|
+
function permissions(names) {
|
|
288
|
+
let bits = 0n;
|
|
289
|
+
for (const name of names) {
|
|
290
|
+
const bit = Permission[name];
|
|
291
|
+
if (bit === void 0) throw new Error(`unknown Discord permission: ${JSON.stringify(name)}`);
|
|
292
|
+
bits |= bit;
|
|
293
|
+
}
|
|
294
|
+
return bits.toString();
|
|
295
|
+
}
|
|
296
|
+
/** The permission names set in a bitfield, in declaration order (unknown bits are ignored). */
|
|
297
|
+
function permissionNames(bitfield) {
|
|
298
|
+
if (!/^\d{1,20}$/.test(bitfield)) throw new Error(`invalid permission bitfield: ${JSON.stringify(bitfield)}`);
|
|
299
|
+
const bits = BigInt(bitfield);
|
|
300
|
+
return Object.keys(Permission).filter((name) => (bits & Permission[name]) !== 0n);
|
|
301
|
+
}
|
|
302
|
+
//#endregion
|
|
156
303
|
//#region ../../../../../src/index.ts
|
|
157
304
|
function createDiscordClient(opts = {}) {
|
|
158
305
|
return { webhooks: webhooks(opts.baseUrl ?? "https://discord.com/api/v10", opts.fetch ?? globalThis.fetch) };
|
|
159
306
|
}
|
|
160
307
|
//#endregion
|
|
161
|
-
export { ChannelType, createDiscordBotClient, createDiscordClient, parseWebhookUrl };
|
|
308
|
+
export { ChannelType, Permission, createDiscordBotClient, createDiscordClient, parseWebhookUrl, permissionNames, permissions };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@theholocron/discord-client",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.44.0",
|
|
4
4
|
"description": "A TypeScript client for the Discord webhooks API",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"discord",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"dist"
|
|
33
33
|
],
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@theholocron/http-client": "^1.
|
|
35
|
+
"@theholocron/http-client": "^1.44.0"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@theholocron/cli": "5.0.0-alpha.79",
|