@theholocron/discord-client 1.43.1 → 1.45.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 +19 -9
- package/dist/index.d.mts +247 -1
- package/dist/index.mjs +157 -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,22 @@ 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
|
|
102
|
-
|
|
|
103
|
-
| `client.users`
|
|
104
|
-
| `client.
|
|
105
|
-
| `client.
|
|
106
|
-
|
|
107
|
-
|
|
102
|
+
| Resource | Methods |
|
|
103
|
+
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
104
|
+
| `client.users` | `me()` — `GET /users/@me`, a cheap token check |
|
|
105
|
+
| `client.application` | `get`, `modify` (the bot's own application: description, tags, install settings, URLs) |
|
|
106
|
+
| `client.guilds` | `get`, `modify`, `channels`, `createChannel`, `roles`, `createRole`, `modifyRole`, `welcomeScreen`, `modifyWelcomeScreen`, `onboarding`, `modifyOnboarding` |
|
|
107
|
+
| `client.channels` | `modify`, `webhooks`, `createWebhook`, `createMessage` |
|
|
108
|
+
| `client.webhooks` | `modify` (name, avatar), `delete` |
|
|
109
|
+
|
|
110
|
+
`permissions([...names])` builds the decimal-string bitfield Discord expects, and
|
|
111
|
+
`permissionNames(bitfield)` reads one back; `Permission` holds the bits themselves.
|
|
112
|
+
Welcome screen and onboarding only work once the server has the community
|
|
113
|
+
features on, and `modifyOnboarding` is a `PUT` that replaces the whole config.
|
|
114
|
+
`channels.createMessage` turns every mention off unless `allowed_mentions` says
|
|
115
|
+
otherwise.
|
|
116
|
+
|
|
117
|
+
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
118
|
Discord only returns a webhook's `token`/`url` for webhooks the same
|
|
109
119
|
application created, so a pre-existing webhook can be found by name but its URL
|
|
110
120
|
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,74 @@ 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
|
+
/** How the bot is added to a server or an account. */
|
|
223
|
+
interface DiscordInstallParams {
|
|
224
|
+
/** OAuth scopes, e.g. `bot`, `applications.commands`. */
|
|
225
|
+
scopes: string[];
|
|
226
|
+
/** Permission bitfield the default install link asks for, as a string. */
|
|
227
|
+
permissions: string;
|
|
228
|
+
}
|
|
229
|
+
/** The bot's own application, as `GET /applications/@me` returns it. Fields Discord leaves out are absent or null. */
|
|
230
|
+
interface DiscordApplication {
|
|
231
|
+
id: string;
|
|
232
|
+
name: string;
|
|
233
|
+
description?: string | null;
|
|
234
|
+
/** Up to five short labels shown on the application's page. */
|
|
235
|
+
tags?: string[];
|
|
236
|
+
/** The default install link's scopes and permissions. */
|
|
237
|
+
install_params?: DiscordInstallParams;
|
|
238
|
+
/** Used instead of the default install link when set. */
|
|
239
|
+
custom_install_url?: string | null;
|
|
240
|
+
role_connections_verification_url?: string | null;
|
|
241
|
+
terms_of_service_url?: string | null;
|
|
242
|
+
privacy_policy_url?: string | null;
|
|
243
|
+
interactions_endpoint_url?: string | null;
|
|
244
|
+
/** Whether anyone can add the bot (`true`) or only its owner. */
|
|
245
|
+
bot_public?: boolean;
|
|
246
|
+
bot_require_code_grant?: boolean;
|
|
247
|
+
flags?: number;
|
|
248
|
+
}
|
|
249
|
+
/** What `PATCH /applications/@me` takes; a field left out is left alone, and `null` clears the ones that can be cleared. */
|
|
250
|
+
interface ModifyApplicationInput {
|
|
251
|
+
description?: string;
|
|
252
|
+
tags?: string[];
|
|
253
|
+
install_params?: DiscordInstallParams;
|
|
254
|
+
custom_install_url?: string | null;
|
|
255
|
+
role_connections_verification_url?: string | null;
|
|
256
|
+
terms_of_service_url?: string | null;
|
|
257
|
+
privacy_policy_url?: string | null;
|
|
258
|
+
interactions_endpoint_url?: string | null;
|
|
259
|
+
}
|
|
260
|
+
interface ModifyWebhookInput {
|
|
261
|
+
name?: string;
|
|
262
|
+
/** A data URI (`data:image/png;base64,...`); `null` clears it. */
|
|
263
|
+
avatar?: string | null;
|
|
264
|
+
}
|
|
110
265
|
//#endregion
|
|
111
266
|
//#region ../../../../../src/bot/bot.d.ts
|
|
112
267
|
/**
|
|
@@ -127,21 +282,112 @@ declare function createDiscordBotClient(opts: DiscordBotClientOptions): {
|
|
|
127
282
|
};
|
|
128
283
|
guilds: {
|
|
129
284
|
get: (guildId: string) => Promise<DiscordGuild>;
|
|
285
|
+
/** `PATCH /guilds/:id` — verification, notification defaults, content filter, system channels. */
|
|
286
|
+
modify: (guildId: string, body: ModifyGuildInput) => Promise<DiscordGuild>;
|
|
287
|
+
welcomeScreen: (guildId: string) => Promise<DiscordWelcomeScreen>;
|
|
288
|
+
/** Needs the community features on. */
|
|
289
|
+
modifyWelcomeScreen: (guildId: string, body: ModifyWelcomeScreenInput) => Promise<DiscordWelcomeScreen>;
|
|
290
|
+
onboarding: (guildId: string) => Promise<DiscordOnboarding>;
|
|
291
|
+
/**
|
|
292
|
+
* `PUT` replaces the whole onboarding config, so send every prompt you want to keep. Discord has no
|
|
293
|
+
* conditional write: two callers that read, edit and PUT at once, the later one wins.
|
|
294
|
+
*/
|
|
295
|
+
modifyOnboarding: (guildId: string, body: Omit<DiscordOnboarding, "guild_id">) => Promise<DiscordOnboarding>;
|
|
130
296
|
channels: (guildId: string) => Promise<DiscordChannel[]>;
|
|
131
297
|
createChannel: (guildId: string, body: CreateChannelInput) => Promise<DiscordChannel>;
|
|
132
298
|
roles: (guildId: string) => Promise<DiscordRole[]>;
|
|
133
299
|
createRole: (guildId: string, body: CreateRoleInput) => Promise<DiscordRole>;
|
|
300
|
+
modifyRole: (guildId: string, roleId: string, body: ModifyRoleInput) => Promise<DiscordRole>;
|
|
134
301
|
};
|
|
135
302
|
channels: {
|
|
136
303
|
modify: (channelId: string, body: ModifyChannelInput) => Promise<DiscordChannel>;
|
|
137
304
|
webhooks: (channelId: string) => Promise<DiscordChannelWebhook[]>;
|
|
305
|
+
/** `POST /channels/:id/messages` as the bot. Mentions are off unless `allowed_mentions` asks for them. */
|
|
306
|
+
createMessage: (channelId: string, body: CreateMessageInput) => Promise<DiscordMessage>;
|
|
138
307
|
createWebhook: (channelId: string, body: {
|
|
139
308
|
name: string;
|
|
140
309
|
}) => Promise<DiscordChannelWebhook>;
|
|
141
310
|
};
|
|
311
|
+
application: {
|
|
312
|
+
/** `GET /applications/@me` — the bot's own application: description, tags, install settings, URLs. */
|
|
313
|
+
get: () => Promise<DiscordApplication>;
|
|
314
|
+
/** `PATCH /applications/@me` — only what is in the body changes. */
|
|
315
|
+
modify: (body: ModifyApplicationInput) => Promise<DiscordApplication>;
|
|
316
|
+
};
|
|
317
|
+
webhooks: {
|
|
318
|
+
/** `PATCH /webhooks/:id` — name and avatar of a webhook this bot manages. */
|
|
319
|
+
modify: (webhookId: string, body: ModifyWebhookInput) => Promise<DiscordChannelWebhook>;
|
|
320
|
+
/** `DELETE /webhooks/:id` — e.g. to roll back an orphan whose URL was never stored. */
|
|
321
|
+
/** A 404 counts as done: the webhook is already gone, as when a retry follows a lost response. */
|
|
322
|
+
delete: (webhookId: string) => Promise<void>;
|
|
323
|
+
};
|
|
142
324
|
};
|
|
143
325
|
type DiscordBotClient = ReturnType<typeof createDiscordBotClient>;
|
|
144
326
|
//#endregion
|
|
327
|
+
//#region ../../../../../src/bot/permissions.d.ts
|
|
328
|
+
/**
|
|
329
|
+
* Discord permission bits, by the names the Discord docs use. Permissions travel as
|
|
330
|
+
* decimal strings because they overflow a JS number's safe integer range. Bits Discord
|
|
331
|
+
* adds after this list are not named here: `permissionNames` leaves them out, and
|
|
332
|
+
* `permissions` cannot set them by name.
|
|
333
|
+
*/
|
|
334
|
+
declare const Permission: {
|
|
335
|
+
readonly CreateInstantInvite: bigint;
|
|
336
|
+
readonly KickMembers: bigint;
|
|
337
|
+
readonly BanMembers: bigint;
|
|
338
|
+
readonly Administrator: bigint;
|
|
339
|
+
readonly ManageChannels: bigint;
|
|
340
|
+
readonly ManageGuild: bigint;
|
|
341
|
+
readonly AddReactions: bigint;
|
|
342
|
+
readonly ViewAuditLog: bigint;
|
|
343
|
+
readonly PrioritySpeaker: bigint;
|
|
344
|
+
readonly Stream: bigint;
|
|
345
|
+
readonly ViewChannel: bigint;
|
|
346
|
+
readonly SendMessages: bigint;
|
|
347
|
+
readonly SendTtsMessages: bigint;
|
|
348
|
+
readonly ManageMessages: bigint;
|
|
349
|
+
readonly EmbedLinks: bigint;
|
|
350
|
+
readonly AttachFiles: bigint;
|
|
351
|
+
readonly ReadMessageHistory: bigint;
|
|
352
|
+
readonly MentionEveryone: bigint;
|
|
353
|
+
readonly UseExternalEmojis: bigint;
|
|
354
|
+
readonly ViewGuildInsights: bigint;
|
|
355
|
+
readonly Connect: bigint;
|
|
356
|
+
readonly Speak: bigint;
|
|
357
|
+
readonly MuteMembers: bigint;
|
|
358
|
+
readonly DeafenMembers: bigint;
|
|
359
|
+
readonly MoveMembers: bigint;
|
|
360
|
+
readonly UseVoiceActivity: bigint;
|
|
361
|
+
readonly ChangeNickname: bigint;
|
|
362
|
+
readonly ManageNicknames: bigint;
|
|
363
|
+
readonly ManageRoles: bigint;
|
|
364
|
+
readonly ManageWebhooks: bigint;
|
|
365
|
+
readonly ManageGuildExpressions: bigint;
|
|
366
|
+
readonly UseApplicationCommands: bigint;
|
|
367
|
+
readonly RequestToSpeak: bigint;
|
|
368
|
+
readonly ManageEvents: bigint;
|
|
369
|
+
readonly ManageThreads: bigint;
|
|
370
|
+
readonly CreatePublicThreads: bigint;
|
|
371
|
+
readonly CreatePrivateThreads: bigint;
|
|
372
|
+
readonly UseExternalStickers: bigint;
|
|
373
|
+
readonly SendMessagesInThreads: bigint;
|
|
374
|
+
readonly UseEmbeddedActivities: bigint;
|
|
375
|
+
readonly ModerateMembers: bigint;
|
|
376
|
+
readonly ViewCreatorMonetizationAnalytics: bigint;
|
|
377
|
+
readonly UseSoundboard: bigint;
|
|
378
|
+
readonly CreateGuildExpressions: bigint;
|
|
379
|
+
readonly CreateEvents: bigint;
|
|
380
|
+
readonly UseExternalSounds: bigint;
|
|
381
|
+
readonly SendVoiceMessages: bigint;
|
|
382
|
+
readonly SendPolls: bigint;
|
|
383
|
+
readonly UseExternalApps: bigint;
|
|
384
|
+
};
|
|
385
|
+
type PermissionName = keyof typeof Permission;
|
|
386
|
+
/** The decimal-string bitfield Discord wants for the named permissions (`"0"` for none). */
|
|
387
|
+
declare function permissions(names: readonly PermissionName[]): string;
|
|
388
|
+
/** The permission names set in a bitfield, in declaration order (unknown bits are ignored). */
|
|
389
|
+
declare function permissionNames(bitfield: string): PermissionName[];
|
|
390
|
+
//#endregion
|
|
145
391
|
//#region ../../../../../src/index.d.ts
|
|
146
392
|
declare function createDiscordClient(opts?: DiscordClientOptions): {
|
|
147
393
|
webhooks: {
|
|
@@ -151,4 +397,4 @@ declare function createDiscordClient(opts?: DiscordClientOptions): {
|
|
|
151
397
|
};
|
|
152
398
|
type DiscordClient = ReturnType<typeof createDiscordClient>;
|
|
153
399
|
//#endregion
|
|
154
|
-
export { ChannelType, CreateChannelInput, CreateRoleInput, DiscordBotClient, DiscordBotClientOptions, DiscordChannel, DiscordChannelWebhook, DiscordClient, type DiscordClientOptions, DiscordGuild, DiscordPermissionOverwrite, DiscordRole, DiscordUser, type DiscordWebhookInfo, ModifyChannelInput, createDiscordBotClient, createDiscordClient, parseWebhookUrl };
|
|
400
|
+
export { ChannelType, CreateChannelInput, CreateMessageInput, CreateRoleInput, DiscordApplication, DiscordBotClient, DiscordBotClientOptions, DiscordChannel, DiscordChannelWebhook, DiscordClient, type DiscordClientOptions, DiscordEmoji, DiscordGuild, DiscordInstallParams, DiscordMessage, DiscordOnboarding, DiscordOnboardingOption, DiscordOnboardingPrompt, DiscordPermissionOverwrite, DiscordRole, DiscordUser, type DiscordWebhookInfo, DiscordWelcomeChannel, DiscordWelcomeScreen, ModifyApplicationInput, 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,50 @@ 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
|
+
application: {
|
|
197
|
+
/** `GET /applications/@me` — the bot's own application: description, tags, install settings, URLs. */
|
|
198
|
+
get: () => rest.request("/applications/@me"),
|
|
199
|
+
/** `PATCH /applications/@me` — only what is in the body changes. */
|
|
200
|
+
modify: (body) => rest.request("/applications/@me", {
|
|
201
|
+
method: "PATCH",
|
|
202
|
+
body
|
|
203
|
+
})
|
|
204
|
+
},
|
|
205
|
+
webhooks: {
|
|
206
|
+
/** `PATCH /webhooks/:id` — name and avatar of a webhook this bot manages. */
|
|
207
|
+
modify: async (webhookId, body) => withUrl(await rest.request(`/webhooks/${id(webhookId)}`, {
|
|
208
|
+
method: "PATCH",
|
|
209
|
+
body
|
|
210
|
+
})),
|
|
211
|
+
/** `DELETE /webhooks/:id` — e.g. to roll back an orphan whose URL was never stored. */
|
|
212
|
+
/** A 404 counts as done: the webhook is already gone, as when a retry follows a lost response. */
|
|
213
|
+
delete: async (webhookId) => {
|
|
214
|
+
try {
|
|
215
|
+
await rest.request(`/webhooks/${id(webhookId)}`, {
|
|
216
|
+
method: "DELETE",
|
|
217
|
+
expectNoContent: true
|
|
218
|
+
});
|
|
219
|
+
} catch (err) {
|
|
220
|
+
if (err instanceof ProviderApiError && err.status === 404) return;
|
|
221
|
+
throw err;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
143
224
|
}
|
|
144
225
|
};
|
|
145
226
|
}
|
|
@@ -153,9 +234,84 @@ const ChannelType = {
|
|
|
153
234
|
Forum: 15
|
|
154
235
|
};
|
|
155
236
|
//#endregion
|
|
237
|
+
//#region ../../../../../src/bot/permissions.ts
|
|
238
|
+
/**
|
|
239
|
+
* Discord permission bits, by the names the Discord docs use. Permissions travel as
|
|
240
|
+
* decimal strings because they overflow a JS number's safe integer range. Bits Discord
|
|
241
|
+
* adds after this list are not named here: `permissionNames` leaves them out, and
|
|
242
|
+
* `permissions` cannot set them by name.
|
|
243
|
+
*/
|
|
244
|
+
const Permission = {
|
|
245
|
+
CreateInstantInvite: 1n << 0n,
|
|
246
|
+
KickMembers: 1n << 1n,
|
|
247
|
+
BanMembers: 1n << 2n,
|
|
248
|
+
Administrator: 1n << 3n,
|
|
249
|
+
ManageChannels: 1n << 4n,
|
|
250
|
+
ManageGuild: 1n << 5n,
|
|
251
|
+
AddReactions: 1n << 6n,
|
|
252
|
+
ViewAuditLog: 1n << 7n,
|
|
253
|
+
PrioritySpeaker: 1n << 8n,
|
|
254
|
+
Stream: 1n << 9n,
|
|
255
|
+
ViewChannel: 1n << 10n,
|
|
256
|
+
SendMessages: 1n << 11n,
|
|
257
|
+
SendTtsMessages: 1n << 12n,
|
|
258
|
+
ManageMessages: 1n << 13n,
|
|
259
|
+
EmbedLinks: 1n << 14n,
|
|
260
|
+
AttachFiles: 1n << 15n,
|
|
261
|
+
ReadMessageHistory: 1n << 16n,
|
|
262
|
+
MentionEveryone: 1n << 17n,
|
|
263
|
+
UseExternalEmojis: 1n << 18n,
|
|
264
|
+
ViewGuildInsights: 1n << 19n,
|
|
265
|
+
Connect: 1n << 20n,
|
|
266
|
+
Speak: 1n << 21n,
|
|
267
|
+
MuteMembers: 1n << 22n,
|
|
268
|
+
DeafenMembers: 1n << 23n,
|
|
269
|
+
MoveMembers: 1n << 24n,
|
|
270
|
+
UseVoiceActivity: 1n << 25n,
|
|
271
|
+
ChangeNickname: 1n << 26n,
|
|
272
|
+
ManageNicknames: 1n << 27n,
|
|
273
|
+
ManageRoles: 1n << 28n,
|
|
274
|
+
ManageWebhooks: 1n << 29n,
|
|
275
|
+
ManageGuildExpressions: 1n << 30n,
|
|
276
|
+
UseApplicationCommands: 1n << 31n,
|
|
277
|
+
RequestToSpeak: 1n << 32n,
|
|
278
|
+
ManageEvents: 1n << 33n,
|
|
279
|
+
ManageThreads: 1n << 34n,
|
|
280
|
+
CreatePublicThreads: 1n << 35n,
|
|
281
|
+
CreatePrivateThreads: 1n << 36n,
|
|
282
|
+
UseExternalStickers: 1n << 37n,
|
|
283
|
+
SendMessagesInThreads: 1n << 38n,
|
|
284
|
+
UseEmbeddedActivities: 1n << 39n,
|
|
285
|
+
ModerateMembers: 1n << 40n,
|
|
286
|
+
ViewCreatorMonetizationAnalytics: 1n << 41n,
|
|
287
|
+
UseSoundboard: 1n << 42n,
|
|
288
|
+
CreateGuildExpressions: 1n << 43n,
|
|
289
|
+
CreateEvents: 1n << 44n,
|
|
290
|
+
UseExternalSounds: 1n << 45n,
|
|
291
|
+
SendVoiceMessages: 1n << 46n,
|
|
292
|
+
SendPolls: 1n << 49n,
|
|
293
|
+
UseExternalApps: 1n << 50n
|
|
294
|
+
};
|
|
295
|
+
/** The decimal-string bitfield Discord wants for the named permissions (`"0"` for none). */
|
|
296
|
+
function permissions(names) {
|
|
297
|
+
let bits = 0n;
|
|
298
|
+
for (const name of names) {
|
|
299
|
+
const bit = Permission[name];
|
|
300
|
+
if (bit === void 0) throw new Error(`unknown Discord permission: ${JSON.stringify(name)}`);
|
|
301
|
+
bits |= bit;
|
|
302
|
+
}
|
|
303
|
+
return bits.toString();
|
|
304
|
+
}
|
|
305
|
+
/** The permission names set in a bitfield, in declaration order (unknown bits are ignored). */
|
|
306
|
+
function permissionNames(bitfield) {
|
|
307
|
+
if (!/^\d{1,20}$/.test(bitfield)) throw new Error(`invalid permission bitfield: ${JSON.stringify(bitfield)}`);
|
|
308
|
+
const bits = BigInt(bitfield);
|
|
309
|
+
return Object.keys(Permission).filter((name) => (bits & Permission[name]) !== 0n);
|
|
310
|
+
}
|
|
311
|
+
//#endregion
|
|
156
312
|
//#region ../../../../../src/index.ts
|
|
157
313
|
function createDiscordClient(opts = {}) {
|
|
158
314
|
return { webhooks: webhooks(opts.baseUrl ?? "https://discord.com/api/v10", opts.fetch ?? globalThis.fetch) };
|
|
159
315
|
}
|
|
160
316
|
//#endregion
|
|
161
|
-
export { ChannelType, createDiscordBotClient, createDiscordClient, parseWebhookUrl };
|
|
317
|
+
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.45.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.45.0"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@theholocron/cli": "5.0.0-alpha.79",
|