@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 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. It does not post messages; use
56
- `createDiscordClient().webhooks.execute` for that.
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
- The bot needs Manage Channels, Manage Roles and Manage Webhooks in the server.
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.43.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.43.1"
35
+ "@theholocron/http-client": "^1.44.0"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@theholocron/cli": "5.0.0-alpha.79",