@theholocron/discord-client 1.58.0-alpha.3 → 1.58.0-alpha.8

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
@@ -1,3 +1,5 @@
1
+ <!-- alex disable host-hostess -->
2
+
1
3
  # @theholocron/discord-client
2
4
 
3
5
  A TypeScript client for Discord — posts messages through webhooks, and (with a bot token) provisions a server's channels, roles and webhooks.
@@ -99,19 +101,20 @@ the resource, look it up by name before creating it again. A webhook created
99
101
  that way cannot have its URL read back, since Discord returns the token only on
100
102
  the create response.
101
103
 
102
- | Resource | Methods |
103
- | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
104
- | `client.users` | `me()` — `GET /users/@me`, a cheap token check; `get(id)` — a user's name; `guilds({ limit, after })` — `GET /users/@me/guilds`, the servers the bot is in and whether it owns each (look here before retrying `guilds.create`) |
105
- | `client.application` | `get`, `modify` (the bot's own application: description, tags, install settings, URLs, and `flags` for the privileged-intent toggles; `redirect_uris` can be read but not written, so add them in the developer portal) |
106
- | `client.guilds` | `get`, `modify`, `channels`, `createChannel`, `roles`, `createRole`, `modifyRole`, `create`, `delete`, `emojis`, `member`, `members`, `membersWithRole`, `addMemberRole`, `removeMemberRole`, `welcomeScreen`, `modifyWelcomeScreen`, `onboarding`, `modifyOnboarding` |
107
- | `client.channels` | `modify`, `webhooks`, `createWebhook`, `createMessage`, `createInvite`, `messages`, `pin`, `deleteMessage` |
108
- | `client.webhooks` | `modify` (name, avatar), `delete` |
104
+ | Resource | Methods |
105
+ | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
106
+ | `client.users` | `me()` — `GET /users/@me`, a cheap token check; `get(id)` — a user's name; `guilds({ limit, after })` — `GET /users/@me/guilds`, the servers the bot is in and whether it owns each (look here before retrying `guilds.create`) |
107
+ | `client.application` | `get`, `modify` (the bot's own application: description, tags, install settings, URLs, and `flags` for the privileged-intent toggles; `redirect_uris` can be read but not written, so add them in the developer portal) |
108
+ | `client.guilds` | `get`, `modify`, `channels`, `createChannel`, `roles`, `createRole`, `modifyRole`, `create`, `delete`, `emojis`, `member`, `members`, `membersWithRole`, `addMemberRole`, `removeMemberRole`, `welcomeScreen`, `modifyWelcomeScreen`, `onboarding`, `modifyOnboarding` |
109
+ | `client.channels` | `modify`, `webhooks`, `createWebhook`, `createMessage`, `message`, `editMessage`, `createInvite`, `messages`, `pin`, `deleteMessage` |
110
+ | `client.threads` | `createForumPost` — a post in a forum channel with its tags and first message; `modify` — a thread's title, tags, archived and locked state; `active` — the server's active threads; `archived` — one page of a channel's archived threads; `inChannel` — every post of a forum, active and archived |
111
+ | `client.webhooks` | `modify` (name, avatar), `delete` |
109
112
 
110
113
  `permissions([...names])` builds the decimal-string bitfield Discord expects, and
111
114
  `permissionNames(bitfield)` reads one back; `Permission` holds the bits themselves.
112
115
  Welcome screen and onboarding only work once the server has the community
113
116
  features on, and `modifyOnboarding` is a `PUT` that replaces the whole config.
114
- `channels.createMessage` turns every mention off unless `allowed_mentions` says
117
+ `channels.createMessage`, `channels.editMessage` and the first message of `threads.createForumPost` turn every mention off unless `allowed_mentions` says
115
118
  otherwise.
116
119
 
117
120
  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`.
@@ -135,6 +138,10 @@ hands it over, `channels.createInvite` makes the link to join it, and `guilds.de
135
138
  `executeAndWait` posts and returns the message id and `deleteMessage` removes it, so a check can post a line, read it back
136
139
  with `channels.messages`, and clean up. `channels.pin` pins a message.
137
140
 
141
+ ### Forum posts
142
+
143
+ `guilds.createChannel` with `type: ChannelType.Forum` and `available_tags` makes a forum; `channels.modify` replaces its tags (send the whole list, with each kept tag's `id`). `threads.createForumPost(channelId, { name, applied_tags, message })` makes a post, and `message.flags: MessageFlag.SuppressNotifications` posts it without notifying anyone, as a backfill should. `threads.modify` retitles or retags a post, and writing to an archived post unarchives it. Discord hides a post that has been quiet for a while, so `threads.inChannel(guildId, forumId)` reads the archived pages too before you decide a post does not exist. Creating a post is not idempotent: look it up first. Only the bot, which owns its posts, and members with Manage Threads can change a post's tags.
144
+
138
145
  Discord only returns a webhook's `token`/`url` for webhooks the same
139
146
  application created, so a pre-existing webhook can be found by name but its URL
140
147
  cannot be read back.
package/dist/index.d.mts CHANGED
@@ -1,6 +1,10 @@
1
+ /** What fetching a webhook by id and token returns. */
1
2
  interface DiscordWebhookInfo {
3
+ /** The webhook id. */
2
4
  id: string;
5
+ /** The webhook name. */
3
6
  name: string;
7
+ /** Id of the server the webhook belongs to, when Discord includes it. */
4
8
  guild_id?: string;
5
9
  }
6
10
  /** Parse a Discord webhook URL into its id and token parts. */
@@ -8,41 +12,64 @@ export declare function parseWebhookUrl(url: string): {
8
12
  id: string;
9
13
  token: string;
10
14
  };
15
+ /** Options for `createDiscordClient`. */
11
16
  interface DiscordClientOptions {
12
17
  /** Override base URL for testing. Defaults to https://discord.com/api/v10. */
13
18
  baseUrl?: string;
14
19
  /** Override fetch for testing. Defaults to globalThis.fetch. */
15
20
  fetch?: typeof fetch;
16
21
  }
22
+ /** Options for `createDiscordBotClient`. */
17
23
  export interface DiscordBotClientOptions {
18
24
  /** Bot token from the Discord developer portal (Bot → Reset Token). */
19
25
  botToken: string;
20
- /** Override base URL for testing. Defaults to https://discord.com/api/v10. */
26
+ /**
27
+ * Override base URL for testing. Defaults to https://discord.com/api/v10.
28
+ * The bot token is sent to whatever host this names, so never derive it from untrusted input.
29
+ */
21
30
  baseUrl?: string;
22
31
  /** Override fetch for testing. Defaults to globalThis.fetch. */
23
32
  fetch?: typeof fetch;
24
33
  /** Per-request deadline in milliseconds. Defaults to 15000. */
25
34
  timeoutMs?: number;
26
- /** Retries after a 429 (rate limit) before giving up. Defaults to 3. */
35
+ /**
36
+ * Retries after a 429 (rate limit) before giving up. Defaults to 3.
37
+ * Each retry waits for the `Retry-After` Discord sends; a wait longer than 10 seconds fails immediately.
38
+ */
27
39
  maxRetries?: number;
28
40
  /** Wait between rate-limit retries; a test seam. Defaults to `setTimeout`. */
29
41
  sleep?: (ms: number) => Promise<void>;
30
42
  }
31
43
  /** Discord channel `type` values this client creates or reads. */
32
44
  export declare const ChannelType: {
45
+ /** A text channel. */
33
46
  readonly Text: 0;
47
+ /** A category that groups channels. */
34
48
  readonly Category: 4;
49
+ /** An announcement channel. */
35
50
  readonly Announcement: 5;
51
+ /** A forum channel. */
36
52
  readonly Forum: 15;
37
53
  };
54
+ /** Bits of a message's `flags` that a bot may set when it posts. */
55
+ export declare const MessageFlag: {
56
+ /** Posts without notifying anyone, as a backfill of many messages should. */
57
+ readonly SuppressNotifications: number;
58
+ };
59
+ /** A Discord account, as the API returns it. */
38
60
  export interface DiscordUser {
61
+ /** The user id. */
39
62
  id: string;
63
+ /** The account's username. */
40
64
  username: string;
65
+ /** Whether the account is a bot. */
41
66
  bot?: boolean;
42
67
  }
43
68
  /** A server as `GET /users/@me/guilds` lists it: a summary, not the full guild. */
44
69
  export interface DiscordUserGuild {
70
+ /** The server id. */
45
71
  id: string;
72
+ /** The server name. */
46
73
  name: string;
47
74
  /** Whether the caller (here, the bot) owns the server. */
48
75
  owner: boolean;
@@ -51,13 +78,16 @@ export interface DiscordUserGuild {
51
78
  }
52
79
  /** Page options for `users.guilds`; Discord allows a `limit` of 1–200; values outside that are clamped. */
53
80
  export interface ListUserGuildsInput {
81
+ /** How many servers to return, 1 to 200. */
54
82
  limit?: number;
55
83
  /** Return servers after this server id. */
56
84
  after?: string;
57
85
  }
58
86
  /** Guild fields `guilds.modify` can change; the numeric enums are Discord's own. */
59
87
  export interface DiscordGuild {
88
+ /** The server id. */
60
89
  id: string;
90
+ /** The server name. */
61
91
  name: string;
62
92
  /** 0 none, 1 low, 2 medium, 3 high, 4 very high. */
63
93
  verification_level?: number;
@@ -65,84 +95,136 @@ export interface DiscordGuild {
65
95
  default_message_notifications?: number;
66
96
  /** 0 disabled, 1 members without roles, 2 all members. */
67
97
  explicit_content_filter?: number;
98
+ /** Id of the channel for system messages, or `null` for none. */
68
99
  system_channel_id?: string | null;
69
100
  /** Bitfield of system-channel messages to suppress. */
70
101
  system_channel_flags?: number;
102
+ /** Id of the rules channel, or `null` for none. */
71
103
  rules_channel_id?: string | null;
104
+ /** Id of the channel for Discord's community updates, or `null` for none. */
72
105
  public_updates_channel_id?: string | null;
106
+ /** The server's preferred locale, such as `en-US`. */
73
107
  preferred_locale?: string;
108
+ /** The server description, or `null` when unset. */
74
109
  description?: string | null;
75
110
  /** `COMMUNITY` here means the community features (welcome screen, onboarding) are available. */
76
111
  features?: string[];
77
112
  }
113
+ /** What `PATCH /guilds/{id}` takes; a field left out is left alone. */
78
114
  export interface ModifyGuildInput {
115
+ /** A new server name. */
79
116
  name?: string;
80
117
  /** Hands the server to another user; only the current owner (never a bot that does not own it) may. */
81
118
  owner_id?: string;
119
+ /** Verification level: 0 none, 1 low, 2 medium, 3 high, 4 very high. */
82
120
  verification_level?: number;
121
+ /** Notification default: 0 all messages, 1 only @mentions. */
83
122
  default_message_notifications?: number;
123
+ /** Content filter: 0 disabled, 1 members without roles, 2 all members. */
84
124
  explicit_content_filter?: number;
125
+ /** Id of the channel for system messages, or `null` for none. */
85
126
  system_channel_id?: string | null;
127
+ /** Bitfield of system-channel messages to suppress. */
86
128
  system_channel_flags?: number;
129
+ /** Id of the rules channel, or `null` for none. */
87
130
  rules_channel_id?: string | null;
131
+ /** Id of the channel for Discord's community updates, or `null` for none. */
88
132
  public_updates_channel_id?: string | null;
133
+ /** The server's preferred locale, such as `en-US`. */
89
134
  preferred_locale?: string;
135
+ /** The server description, or `null` to clear it. */
90
136
  description?: string | null;
91
137
  /** The full list of mutable guild features (for example `COMMUNITY`); Discord ignores the ones it manages itself. */
92
138
  features?: string[];
93
139
  }
140
+ /** A channel featured on a server's welcome screen. */
94
141
  export interface DiscordWelcomeChannel {
142
+ /** Id of the featured channel. */
95
143
  channel_id: string;
144
+ /** The blurb shown beside the channel. */
96
145
  description: string;
146
+ /** Id of a custom emoji shown beside it, or `null`. */
97
147
  emoji_id?: string | null;
148
+ /** Name of a Unicode emoji shown beside it, or `null`. */
98
149
  emoji_name?: string | null;
99
150
  }
100
151
  /** Whether the screen is on is the `WELCOME_SCREEN_ENABLED` entry in the guild's `features`; the object has no flag. */
101
152
  export interface DiscordWelcomeScreen {
153
+ /** The server description shown on the screen, or `null`. */
102
154
  description: string | null;
155
+ /** The channels featured on the screen. */
103
156
  welcome_channels: DiscordWelcomeChannel[];
104
157
  }
158
+ /** What `PATCH /guilds/{id}/welcome-screen` takes. */
105
159
  export interface ModifyWelcomeScreenInput {
160
+ /** Whether to turn the welcome screen on or off. */
106
161
  enabled?: boolean;
162
+ /** New welcome-screen description, or `null` to clear it. */
107
163
  description?: string | null;
164
+ /** The channels to feature on the welcome screen. */
108
165
  welcome_channels?: DiscordWelcomeChannel[];
109
166
  }
167
+ /** A server emoji; `id` is `null` for a standard Unicode emoji. */
110
168
  export interface DiscordEmoji {
169
+ /** The emoji id, or `null` for a Unicode emoji. */
111
170
  id: string | null;
171
+ /** The emoji name (the Unicode character for a standard emoji), or `null`. */
112
172
  name: string | null;
173
+ /** Whether the emoji is animated. */
113
174
  animated?: boolean;
114
175
  }
176
+ /** One answer a member can pick in an onboarding prompt. */
115
177
  export interface DiscordOnboardingOption {
178
+ /** The option id; absent when creating a new option. */
116
179
  id?: string;
180
+ /** The text of the option. */
117
181
  title: string;
182
+ /** A short explanation shown under the title. */
118
183
  description?: string | null;
184
+ /** Ids of the channels a member who picks this option is shown. */
119
185
  channel_ids?: string[];
186
+ /** Ids of the roles a member who picks this option is given. */
120
187
  role_ids?: string[];
121
188
  /** Discord returns the emoji as an object... */
122
189
  emoji?: DiscordEmoji;
123
190
  /** ...and takes it as these three fields (a custom emoji has `emoji_id`, a Unicode one only `emoji_name`). */
124
191
  emoji_id?: string | null;
192
+ /** Name of a Unicode emoji, or of a custom emoji together with `emoji_id`. */
125
193
  emoji_name?: string | null;
194
+ /** Whether the custom emoji is animated. */
126
195
  emoji_animated?: boolean;
127
196
  }
197
+ /** A question in the server's onboarding flow, with the options a member picks from. */
128
198
  export interface DiscordOnboardingPrompt {
199
+ /** The prompt id; absent when creating a new prompt. */
129
200
  id?: string;
130
201
  /** 0 multiple choice, 1 dropdown. */
131
202
  type: number;
203
+ /** The question text. */
132
204
  title: string;
205
+ /** Whether a member may pick only one option. */
133
206
  single_select: boolean;
207
+ /** Whether a member must answer. */
134
208
  required: boolean;
209
+ /** Whether the prompt appears during onboarding (otherwise only in Channels & Roles). */
135
210
  in_onboarding: boolean;
211
+ /** The answers a member can choose from. */
136
212
  options: DiscordOnboardingOption[];
137
213
  }
214
+ /** A server's onboarding configuration, as `GET /guilds/{id}/onboarding` returns and `PUT` takes it. */
138
215
  export interface DiscordOnboarding {
216
+ /** The server id. */
139
217
  guild_id?: string;
218
+ /** The questions asked during onboarding. */
140
219
  prompts: DiscordOnboardingPrompt[];
220
+ /** Ids of the channels every new member is shown by default. */
141
221
  default_channel_ids: string[];
222
+ /** Whether onboarding is turned on. */
142
223
  enabled: boolean;
143
224
  /** 0 default, 1 advanced. */
144
225
  mode?: number;
145
226
  }
227
+ /** A per-role or per-member override of a channel's permissions. */
146
228
  export interface DiscordPermissionOverwrite {
147
229
  /** Role or user id. */
148
230
  id: string;
@@ -153,92 +235,255 @@ export interface DiscordPermissionOverwrite {
153
235
  /** Bitfield of permissions to deny, as a string. */
154
236
  deny: string;
155
237
  }
238
+ /** A channel or category in a server, as Discord returns it. */
156
239
  export interface DiscordChannel {
240
+ /** The channel id. */
157
241
  id: string;
242
+ /** The channel name. */
158
243
  name: string;
244
+ /** The channel type; see `ChannelType`. */
159
245
  type: number;
246
+ /** Id of the category the channel sits in, or `null` for none. */
160
247
  parent_id?: string | null;
248
+ /** The channel topic, or `null` when unset. */
161
249
  topic?: string | null;
250
+ /** The channel's sort position within its category or the server. */
162
251
  position?: number;
252
+ /** Permission overrides set on this channel. */
163
253
  permission_overwrites?: DiscordPermissionOverwrite[];
254
+ /** The tags a post in this forum channel can carry; present on forum channels only. */
255
+ available_tags?: DiscordForumTag[];
256
+ }
257
+ /** A tag a forum post can carry, with the emoji shown beside its name. */
258
+ export interface DiscordForumTag {
259
+ /** The tag id. */
260
+ id: string;
261
+ /** The tag name, up to 20 characters. */
262
+ name: string;
263
+ /** Whether only members with Manage Threads can apply the tag. */
264
+ moderated: boolean;
265
+ /** Id of the tag's custom emoji, or `null` when it has none. */
266
+ emoji_id: string | null;
267
+ /** Name of the tag's Unicode emoji, or `null` when it has none. */
268
+ emoji_name: string | null;
269
+ }
270
+ /**
271
+ * A tag as sent when a forum is created or its tags are replaced. Send the whole list every time: Discord replaces
272
+ * the tags with it, and a tag keeps its id only when `id` is given.
273
+ */
274
+ export interface ForumTagInput {
275
+ /** The id of a tag that already exists; leave it out to make a new one. */
276
+ id?: string;
277
+ /** The tag name, up to 20 characters. */
278
+ name: string;
279
+ /** Whether only members with Manage Threads can apply the tag. */
280
+ moderated?: boolean;
281
+ /** Id of a custom emoji for the tag. */
282
+ emoji_id?: string | null;
283
+ /** Name of a Unicode emoji for the tag, such as `💬`. */
284
+ emoji_name?: string | null;
285
+ }
286
+ /** What Discord says about a thread's state. */
287
+ export interface DiscordThreadMetadata {
288
+ /** Whether the thread is archived (hidden from the active list until it is used again). */
289
+ archived: boolean;
290
+ /** Minutes of silence after which the thread archives: 60, 1440, 4320 or 10080. */
291
+ auto_archive_duration: number;
292
+ /** When the archived state last changed, as an ISO 8601 timestamp. */
293
+ archive_timestamp: string;
294
+ /** Whether only members with Manage Threads can unarchive and post in the thread. */
295
+ locked: boolean;
296
+ }
297
+ /** A thread, which in a forum channel is a post. */
298
+ export interface DiscordThread extends DiscordChannel {
299
+ /** Id of the forum or text channel the thread belongs to. */
300
+ parent_id: string;
301
+ /** Id of the user who made the thread. */
302
+ owner_id?: string;
303
+ /** Ids of the forum tags applied to the post. */
304
+ applied_tags?: string[];
305
+ /** The thread's archive state. */
306
+ thread_metadata: DiscordThreadMetadata;
307
+ }
308
+ /** What `POST /channels/{id}/threads` takes to make a post in a forum channel. */
309
+ export interface CreateForumPostInput {
310
+ /** The post's title, up to 100 characters. */
311
+ name: string;
312
+ /** Minutes of silence after which the post archives: 60, 1440, 4320 or 10080. */
313
+ auto_archive_duration?: number;
314
+ /** Ids of the forum tags to apply. */
315
+ applied_tags?: string[];
316
+ /** The post's first message. */
317
+ message: CreateMessageInput;
318
+ }
319
+ /** What making a forum post returns: the new thread, with the first message that opens it. */
320
+ export interface DiscordForumPost extends DiscordThread {
321
+ /** The post's first message; its id is the thread's id. */
322
+ message?: DiscordMessage;
323
+ }
324
+ /** What `PATCH /channels/{id}` takes to change a thread. A field left out is left alone. */
325
+ export interface ModifyThreadInput {
326
+ /** A new title, up to 100 characters. */
327
+ name?: string;
328
+ /** Ids of the forum tags to apply, replacing the current ones. */
329
+ applied_tags?: string[];
330
+ /** Whether the thread is archived. Writing to an archived thread unarchives it. */
331
+ archived?: boolean;
332
+ /** Whether only members with Manage Threads can unarchive and post in the thread. */
333
+ locked?: boolean;
334
+ /** Minutes of silence after which the thread archives: 60, 1440, 4320 or 10080. */
335
+ auto_archive_duration?: number;
336
+ }
337
+ /** One page of the archived threads of a channel. */
338
+ export interface DiscordArchivedThreads {
339
+ /** The threads on this page, newest archived first. */
340
+ threads: DiscordThread[];
341
+ /** Whether more archived threads remain after this page. */
342
+ has_more: boolean;
343
+ }
344
+ /** What `GET /channels/{id}/threads/archived/public` takes. */
345
+ export interface ListArchivedThreadsInput {
346
+ /** Only threads archived before this ISO 8601 timestamp. */
347
+ before?: string;
348
+ /** The page size, 1 to 100. Defaults to 100. */
349
+ limit?: number;
164
350
  }
351
+ /** What `PATCH /channels/{id}/messages/{id}` takes. A field left out is left alone. */
352
+ export interface EditMessageInput {
353
+ /** The new message text. */
354
+ content: string;
355
+ /** Defaults to no mentions, so editing in a pasted `@everyone` cannot ping anyone. */
356
+ allowed_mentions?: CreateMessageInput["allowed_mentions"];
357
+ }
358
+ /** What `POST /guilds/{id}/channels` takes. */
165
359
  export interface CreateChannelInput {
360
+ /** The channel name. */
166
361
  name: string;
362
+ /** The channel type; see `ChannelType`. Discord creates a text channel when omitted. */
167
363
  type?: number;
364
+ /** The channel topic. */
168
365
  topic?: string;
366
+ /** Id of the category to create the channel in. */
169
367
  parent_id?: string;
368
+ /** Permission overrides to set on the new channel. */
170
369
  permission_overwrites?: DiscordPermissionOverwrite[];
370
+ /** The tags of a forum channel (`ChannelType.Forum`). */
371
+ available_tags?: ForumTagInput[];
171
372
  }
172
373
  /** `null` clears `topic` or detaches the channel from its category. */
173
374
  export interface ModifyChannelInput {
375
+ /** A new channel name. */
174
376
  name?: string;
377
+ /** A new topic. */
175
378
  topic?: string | null;
379
+ /** Id of the category to move the channel into. */
176
380
  parent_id?: string | null;
381
+ /** The full list of permission overrides, replacing the existing ones. */
177
382
  permission_overwrites?: DiscordPermissionOverwrite[];
383
+ /** The full list of a forum channel's tags, replacing the existing ones; give a tag's `id` to keep it. */
384
+ available_tags?: ForumTagInput[];
178
385
  }
179
386
  /** A member of a server. Only what provisioning needs: who they are and which roles they hold. */
180
387
  export interface DiscordGuildMember {
388
+ /** The member's account; Discord leaves it out in some message contexts. */
181
389
  user?: {
390
+ /** The user id. */
182
391
  id: string;
392
+ /** The account's username. */
183
393
  username?: string;
184
394
  };
185
395
  /** Ids of the roles the member holds. */
186
396
  roles: string[];
397
+ /** The member's nickname in the server, or `null` when none is set. */
187
398
  nick?: string | null;
188
399
  }
400
+ /** A role in a server, as Discord returns it. */
189
401
  export interface DiscordRole {
402
+ /** The role id. */
190
403
  id: string;
404
+ /** The role name. */
191
405
  name: string;
406
+ /** The role colour as an integer RGB value; `0` means no colour. */
192
407
  color: number;
408
+ /** Whether anyone can @mention the role. */
193
409
  mentionable: boolean;
410
+ /** Whether members with the role are listed separately in the member list. */
194
411
  hoist?: boolean;
195
412
  /** Bitfield of permissions, as a string. */
196
413
  permissions?: string;
414
+ /** Whether an integration (such as a bot) manages the role. */
197
415
  managed?: boolean;
416
+ /** The role's position in the role hierarchy. */
198
417
  position?: number;
199
418
  }
419
+ /** Role fields `PATCH /guilds/{id}/roles/{id}` can change; a field left out is left alone. */
200
420
  export interface ModifyRoleInput {
421
+ /** A new role name. */
201
422
  name?: string;
423
+ /** A new colour as an integer RGB value. */
202
424
  color?: number;
425
+ /** Whether to list the role's members separately. */
203
426
  hoist?: boolean;
427
+ /** Whether anyone can @mention the role. */
204
428
  mentionable?: boolean;
205
429
  /** Bitfield of permissions, as a string. */
206
430
  permissions?: string;
207
431
  }
432
+ /** What `POST /guilds/{id}/roles` takes. */
208
433
  export interface CreateRoleInput {
434
+ /** The role name. */
209
435
  name: string;
436
+ /** The colour as an integer RGB value. */
210
437
  color?: number;
438
+ /** Whether anyone can @mention the role. */
211
439
  mentionable?: boolean;
440
+ /** Whether to list the role's members separately. */
212
441
  hoist?: boolean;
213
442
  /** Bitfield of permissions, as a string. */
214
443
  permissions?: string;
215
444
  }
445
+ /** A webhook attached to a channel, as the bot API returns it. */
216
446
  export interface DiscordChannelWebhook {
447
+ /** The webhook id. */
217
448
  id: string;
449
+ /** The webhook name, or `null` when unset. */
218
450
  name: string | null;
219
451
  /** The avatar hash, or `null` for the default one. */
220
452
  avatar?: string | null;
453
+ /** Id of the channel the webhook posts to. */
221
454
  channel_id: string;
222
455
  /** Present only on webhooks this application created; Discord omits it otherwise. */
223
456
  token?: string;
224
457
  /** Present only on webhooks this application created. */
225
458
  url?: string;
226
459
  }
460
+ /** What `POST /channels/{id}/messages` takes. */
227
461
  export interface CreateMessageInput {
462
+ /** The message text. */
228
463
  content: string;
229
464
  /** With `enforce_nonce`, Discord drops a repeat of the same nonce, so a retry after a lost response cannot post twice. */
230
465
  nonce?: string | number;
466
+ /** Turns on the nonce de-duplication for `nonce`. */
231
467
  enforce_nonce?: boolean;
468
+ /** Message flags; see `MessageFlag`. */
469
+ flags?: number;
232
470
  /** Defaults to no mentions, so a pasted `@everyone` cannot ping anyone. */
233
471
  allowed_mentions?: {
472
+ /** Which mention types Discord may resolve into pings. */
234
473
  parse?: Array<"roles" | "users" | "everyone">;
474
+ /** Ids of the roles that may be pinged. */
235
475
  roles?: string[];
476
+ /** Ids of the users that may be pinged. */
236
477
  users?: string[];
237
478
  };
238
479
  }
480
+ /** A message that was posted, as Discord returns it. */
239
481
  export interface DiscordMessage {
482
+ /** The message id. */
240
483
  id: string;
484
+ /** Id of the channel the message is in. */
241
485
  channel_id: string;
486
+ /** The message text. */
242
487
  content: string;
243
488
  }
244
489
  /** How the bot is added to a server or an account. */
@@ -253,17 +498,26 @@ export interface DiscordInstallParams {
253
498
  * that Discord has not verified; once verified, the plain ones take over and the toggle is Discord's to change.
254
499
  */
255
500
  export declare const ApplicationFlag: {
501
+ /** Application may receive presence updates (verified apps). */
256
502
  readonly GatewayPresence: number;
503
+ /** The privileged presence intent toggle for an unverified app. */
257
504
  readonly GatewayPresenceLimited: number;
505
+ /** Application may receive server-member events and the member list (verified apps). */
258
506
  readonly GatewayGuildMembers: number;
507
+ /** The privileged server-members intent toggle for an unverified app. */
259
508
  readonly GatewayGuildMembersLimited: number;
509
+ /** Application may read message content (verified apps). */
260
510
  readonly GatewayMessageContent: number;
511
+ /** The privileged message-content intent toggle for an unverified app. */
261
512
  readonly GatewayMessageContentLimited: number;
262
513
  };
263
514
  /** The bot's own application, as `GET /applications/@me` returns it. Fields Discord leaves out are absent or null. */
264
515
  export interface DiscordApplication {
516
+ /** The application id. */
265
517
  id: string;
518
+ /** The application name. */
266
519
  name: string;
520
+ /** The description shown on the application's page. */
267
521
  description?: string | null;
268
522
  /** Up to five short labels shown on the application's page. */
269
523
  tags?: string[];
@@ -271,13 +525,19 @@ export interface DiscordApplication {
271
525
  install_params?: DiscordInstallParams;
272
526
  /** Used instead of the default install link when set. */
273
527
  custom_install_url?: string | null;
528
+ /** URL Discord uses for linked-role verification. */
274
529
  role_connections_verification_url?: string | null;
530
+ /** URL of the application's terms of service. */
275
531
  terms_of_service_url?: string | null;
532
+ /** URL of the application's privacy policy. */
276
533
  privacy_policy_url?: string | null;
534
+ /** URL Discord sends interaction events to. */
277
535
  interactions_endpoint_url?: string | null;
278
536
  /** Whether anyone can add the bot (`true`) or only its owner. */
279
537
  bot_public?: boolean;
538
+ /** Whether the bot is only added after the full OAuth2 code grant. */
280
539
  bot_require_code_grant?: boolean;
540
+ /** The application flags bitfield; see `ApplicationFlag`. */
281
541
  flags?: number;
282
542
  /**
283
543
  * The OAuth2 redirect URIs registered on the application. Read-only here: Discord accepts a write to this
@@ -287,13 +547,21 @@ export interface DiscordApplication {
287
547
  }
288
548
  /** What `PATCH /applications/@me` takes; a field left out is left alone, and `null` clears the ones that can be cleared. */
289
549
  export interface ModifyApplicationInput {
550
+ /** A new description. */
290
551
  description?: string;
552
+ /** The new tags (at most five). */
291
553
  tags?: string[];
554
+ /** The new default install link scopes and permissions. */
292
555
  install_params?: DiscordInstallParams;
556
+ /** A custom install URL, or `null` to use the default link. */
293
557
  custom_install_url?: string | null;
558
+ /** The linked-role verification URL, or `null` to clear it. */
294
559
  role_connections_verification_url?: string | null;
560
+ /** The terms of service URL, or `null` to clear it. */
295
561
  terms_of_service_url?: string | null;
562
+ /** The privacy policy URL, or `null` to clear it. */
296
563
  privacy_policy_url?: string | null;
564
+ /** The interactions endpoint URL, or `null` to clear it. */
297
565
  interactions_endpoint_url?: string | null;
298
566
  /**
299
567
  * The whole flags value. Discord keeps only the bits an app may change (the `*Limited` privileged-intent
@@ -301,22 +569,29 @@ export interface ModifyApplicationInput {
301
569
  */
302
570
  flags?: number;
303
571
  }
572
+ /** What `PATCH /webhooks/{id}` takes; a field left out is left alone. */
304
573
  export interface ModifyWebhookInput {
574
+ /** A new webhook name. */
305
575
  name?: string;
306
576
  /** A data URI (`data:image/png;base64,...`); `null` clears it. */
307
577
  avatar?: string | null;
308
578
  }
309
579
  /** What `POST /guilds` takes. Only a bot in fewer than ten servers may create one, and it becomes the owner. */
310
580
  export interface CreateGuildInput {
581
+ /** The server name. */
311
582
  name: string;
312
583
  /** A data URI (`data:image/png;base64,...`). */
313
584
  icon?: string;
585
+ /** Verification level, using the same values as `DiscordGuild.verification_level`. */
314
586
  verification_level?: number;
587
+ /** Default notification setting, using the same values as `DiscordGuild.default_message_notifications`. */
315
588
  default_message_notifications?: number;
589
+ /** Content filter level, using the same values as `DiscordGuild.explicit_content_filter`. */
316
590
  explicit_content_filter?: number;
317
591
  }
318
592
  /** Page options for `guilds.members`; Discord caps `limit` at 1000. */
319
593
  export interface ListMembersInput {
594
+ /** How many members to return; Discord caps it at 1000. */
320
595
  limit?: number;
321
596
  /** Return members after this user id. */
322
597
  after?: string;
@@ -325,29 +600,43 @@ export interface DiscordEmoji {
325
600
  id: string | null;
326
601
  name: string | null;
327
602
  animated?: boolean;
603
+ /** Whether the emoji can be used (it may be unavailable when the server lost a boost level). */
328
604
  available?: boolean;
329
605
  }
606
+ /** What `POST /channels/{id}/invites` takes. */
330
607
  export interface CreateInviteInput {
331
608
  /** Seconds until it expires; `0` never. Discord's default is 86400. */
332
609
  max_age?: number;
333
610
  /** Uses before it is revoked; `0` unlimited. */
334
611
  max_uses?: number;
612
+ /** Whether the invite grants temporary membership. */
335
613
  temporary?: boolean;
614
+ /** Whether to always create a new invite instead of reusing a similar one. */
336
615
  unique?: boolean;
337
616
  }
617
+ /** A server invite, as Discord returns it. */
338
618
  export interface DiscordInvite {
619
+ /** The invite code, the part after `discord.gg/`. */
339
620
  code: string;
621
+ /** Id of the channel the invite leads to. */
340
622
  channel_id?: string;
623
+ /** How many times the invite has been used. */
341
624
  uses?: number;
342
625
  }
343
626
  /** A message listed back from a channel, with who wrote it. */
344
627
  export interface DiscordChannelMessage extends DiscordMessage {
628
+ /** Who wrote the message; absent when Discord omits it. */
345
629
  author?: {
630
+ /** The author's user id. */
346
631
  id: string;
632
+ /** The author's username. */
347
633
  username?: string;
634
+ /** Whether the author is a bot. */
348
635
  bot?: boolean;
349
636
  };
637
+ /** Id of the webhook that posted the message, when one did. */
350
638
  webhook_id?: string;
639
+ /** Whether the message is pinned in its channel. */
351
640
  pinned?: boolean;
352
641
  }
353
642
  /**
@@ -416,6 +705,10 @@ export declare function createDiscordBotClient(opts: DiscordBotClientOptions): {
416
705
  webhooks: (channelId: string) => Promise<DiscordChannelWebhook[]>;
417
706
  /** `POST /channels/:id/messages` as the bot. Mentions are off unless `allowed_mentions` asks for them. */
418
707
  createMessage: (channelId: string, body: CreateMessageInput) => Promise<DiscordMessage>;
708
+ /** `GET /channels/:id/messages/:id` — one message, e.g. the first message of a forum post (its id is the post's). */
709
+ message: (channelId: string, messageId: string) => Promise<DiscordChannelMessage>;
710
+ /** `PATCH /channels/:id/messages/:id` — a message this bot posted. Mentions are off unless `allowed_mentions` asks for them. */
711
+ editMessage: (channelId: string, messageId: string, body: EditMessageInput) => Promise<DiscordMessage>;
419
712
  /** `POST /channels/:id/invites` — a join link for the server, e.g. to hand over a newly created one. */
420
713
  createInvite: (channelId: string, body?: CreateInviteInput) => Promise<DiscordInvite>;
421
714
  /** `GET /channels/:id/messages` — the newest messages first, to read a post back. */
@@ -427,6 +720,24 @@ export declare function createDiscordBotClient(opts: DiscordBotClientOptions): {
427
720
  name: string;
428
721
  }) => Promise<DiscordChannelWebhook>;
429
722
  };
723
+ threads: {
724
+ /**
725
+ * `POST /channels/:id/threads` in a forum channel — a new post with its first message. Not idempotent: look
726
+ * the post up (`inChannel`) before retrying. Mentions in the first message are off unless asked for.
727
+ */
728
+ createForumPost: (channelId: string, body: CreateForumPostInput) => Promise<DiscordForumPost>;
729
+ /** `PATCH /channels/:id` on a thread — its title, tags, archived or locked state. Writing to an archived post unarchives it. */
730
+ modify: (threadId: string, body: ModifyThreadInput) => Promise<DiscordThread>;
731
+ /** `GET /guilds/:id/threads/active` — every active thread in the server, across channels. */
732
+ active: (guildId: string) => Promise<DiscordThread[]>;
733
+ /** `GET /channels/:id/threads/archived/public` — one page of a channel's archived threads, newest archived first. */
734
+ archived: (channelId: string, opts?: ListArchivedThreadsInput) => Promise<DiscordArchivedThreads>;
735
+ /**
736
+ * Every post in a forum channel, active and archived: Discord hides a post that has been quiet for a while, so
737
+ * a lookup that read only the active ones would miss it and make it again.
738
+ */
739
+ inChannel: (guildId: string, channelId: string) => Promise<DiscordThread[]>;
740
+ };
430
741
  application: {
431
742
  /** `GET /applications/@me` — the bot's own application: description, tags, install settings, URLs. */
432
743
  get: () => Promise<DiscordApplication>;
@@ -441,6 +752,7 @@ export declare function createDiscordBotClient(opts: DiscordBotClientOptions): {
441
752
  delete: (webhookId: string) => Promise<void>;
442
753
  };
443
754
  };
755
+ /** The bot-token client returned by `createDiscordBotClient`, with its resource groups (guilds, channels, roles, webhooks, messages and so on). */
444
756
  export type DiscordBotClient = ReturnType<typeof createDiscordBotClient>;
445
757
  /**
446
758
  * Discord permission bits, by the names the Discord docs use. Permissions travel as
@@ -449,62 +761,114 @@ export type DiscordBotClient = ReturnType<typeof createDiscordBotClient>;
449
761
  * `permissions` cannot set them by name.
450
762
  */
451
763
  export declare const Permission: {
764
+ /** Create invites to the server. */
452
765
  readonly CreateInstantInvite: bigint;
766
+ /** Kick members. */
453
767
  readonly KickMembers: bigint;
768
+ /** Ban members. */
454
769
  readonly BanMembers: bigint;
770
+ /** Administrator: all permissions, and bypasses channel overrides. */
455
771
  readonly Administrator: bigint;
772
+ /** Create, edit and delete channels. */
456
773
  readonly ManageChannels: bigint;
774
+ /** Change server settings. */
457
775
  readonly ManageGuild: bigint;
776
+ /** Add new reactions to messages. */
458
777
  readonly AddReactions: bigint;
778
+ /** View the audit log. */
459
779
  readonly ViewAuditLog: bigint;
780
+ /** Use priority speaker in voice channels. */
460
781
  readonly PrioritySpeaker: bigint;
782
+ /** Go live (stream) in voice channels. */
461
783
  readonly Stream: bigint;
784
+ /** View channels (and read their messages). */
462
785
  readonly ViewChannel: bigint;
786
+ /** Send messages in text channels. */
463
787
  readonly SendMessages: bigint;
788
+ /** Send text-to-speech messages. */
464
789
  readonly SendTtsMessages: bigint;
790
+ /** Delete other members' messages and pin messages. */
465
791
  readonly ManageMessages: bigint;
792
+ /** Have links in messages embed. */
466
793
  readonly EmbedLinks: bigint;
794
+ /** Upload files and media. */
467
795
  readonly AttachFiles: bigint;
796
+ /** Read message history. */
468
797
  readonly ReadMessageHistory: bigint;
798
+ /** Mention @everyone, @here and all roles. */
469
799
  readonly MentionEveryone: bigint;
800
+ /** Use emojis from other servers. */
470
801
  readonly UseExternalEmojis: bigint;
802
+ /** View server insights. */
471
803
  readonly ViewGuildInsights: bigint;
804
+ /** Join voice channels. */
472
805
  readonly Connect: bigint;
806
+ /** Speak in voice channels. */
473
807
  readonly Speak: bigint;
808
+ /** Mute other members in voice channels. */
474
809
  readonly MuteMembers: bigint;
810
+ /** Deafen other members in voice channels. */
475
811
  readonly DeafenMembers: bigint;
812
+ /** Move members between voice channels. */
476
813
  readonly MoveMembers: bigint;
814
+ /** Use voice activity detection instead of push-to-talk. */
477
815
  readonly UseVoiceActivity: bigint;
816
+ /** Change one's own nickname. */
478
817
  readonly ChangeNickname: bigint;
818
+ /** Change other members' nicknames. */
479
819
  readonly ManageNicknames: bigint;
820
+ /** Create, edit and delete roles lower than the member's own. */
480
821
  readonly ManageRoles: bigint;
822
+ /** Create, edit and delete webhooks. */
481
823
  readonly ManageWebhooks: bigint;
824
+ /** Edit and delete emojis, stickers and soundboard sounds. */
482
825
  readonly ManageGuildExpressions: bigint;
826
+ /** Use application commands (slash commands). */
483
827
  readonly UseApplicationCommands: bigint;
828
+ /** Request to speak in stage channels. */
484
829
  readonly RequestToSpeak: bigint;
830
+ /** Edit and delete scheduled events. */
485
831
  readonly ManageEvents: bigint;
832
+ /** Manage and delete threads. */
486
833
  readonly ManageThreads: bigint;
834
+ /** Create public threads. */
487
835
  readonly CreatePublicThreads: bigint;
836
+ /** Create private threads. */
488
837
  readonly CreatePrivateThreads: bigint;
838
+ /** Use stickers from other servers. */
489
839
  readonly UseExternalStickers: bigint;
840
+ /** Send messages in threads. */
490
841
  readonly SendMessagesInThreads: bigint;
842
+ /** Start activities in voice channels. */
491
843
  readonly UseEmbeddedActivities: bigint;
844
+ /** Time out members. */
492
845
  readonly ModerateMembers: bigint;
846
+ /** View creator monetization analytics. */
493
847
  readonly ViewCreatorMonetizationAnalytics: bigint;
848
+ /** Use soundboard sounds. */
494
849
  readonly UseSoundboard: bigint;
850
+ /** Create emojis, stickers and soundboard sounds. */
495
851
  readonly CreateGuildExpressions: bigint;
852
+ /** Create scheduled events. */
496
853
  readonly CreateEvents: bigint;
854
+ /** Use soundboard sounds from other servers. */
497
855
  readonly UseExternalSounds: bigint;
856
+ /** Send voice messages. */
498
857
  readonly SendVoiceMessages: bigint;
858
+ /** Send polls. */
499
859
  readonly SendPolls: bigint;
860
+ /** Use apps that are not installed in the server. */
500
861
  readonly UseExternalApps: bigint;
862
+ /** Pin and unpin messages. */
501
863
  readonly PinMessages: bigint;
502
864
  };
865
+ /** The name of one entry in `Permission`. */
503
866
  export type PermissionName = keyof typeof Permission;
504
867
  /** The decimal-string bitfield Discord wants for the named permissions (`"0"` for none). */
505
868
  export declare function permissions(names: readonly PermissionName[]): string;
506
869
  /** The permission names set in a bitfield, in declaration order (unknown bits are ignored). */
507
870
  export declare function permissionNames(bitfield: string): PermissionName[];
871
+ /** Options for `createDiscordOAuthClient`. */
508
872
  export interface DiscordOAuthClientOptions {
509
873
  /** The application's client id (the same as its application id). Not a secret. */
510
874
  clientId: string;
@@ -523,11 +887,15 @@ export interface DiscordOAuthClientOptions {
523
887
  }
524
888
  /** What a member's token may do: `identify` reads who they are, `connections` reads their linked accounts. */
525
889
  export type DiscordOAuthScope = "identify" | "connections";
890
+ /** The token Discord returns from the OAuth2 token endpoint. */
526
891
  export interface DiscordOAuthToken {
892
+ /** The access token to send as a bearer. */
527
893
  access_token: string;
894
+ /** The token type, `Bearer`. */
528
895
  token_type: string;
529
896
  /** Seconds until `access_token` stops working. */
530
897
  expires_in: number;
898
+ /** The token that can be exchanged for a new access token. */
531
899
  refresh_token?: string;
532
900
  /** The scopes granted, space separated. */
533
901
  scope: string;
@@ -542,12 +910,16 @@ export interface DiscordConnection {
542
910
  type: string;
543
911
  /** Whether Discord confirmed the member owns the account. */
544
912
  verified: boolean;
913
+ /** Discord's visibility code: 0 only the member, 1 everyone. */
545
914
  visibility?: number;
546
915
  }
547
916
  /** The member who signed in, as `GET /users/@me` returns it with their token. */
548
917
  export interface DiscordOAuthUser {
918
+ /** The Discord user id. */
549
919
  id: string;
920
+ /** The account's username. */
550
921
  username: string;
922
+ /** The display name, or `null` when the member has none. */
551
923
  global_name?: string | null;
552
924
  }
553
925
  /**
@@ -580,6 +952,7 @@ export declare function createDiscordOAuthClient(opts: DiscordOAuthClientOptions
580
952
  /** `GET /users/@me/connections` as the member (needs `connections`). */
581
953
  connections: (accessToken: string) => Promise<DiscordConnection[]>;
582
954
  };
955
+ /** The member-OAuth client returned by `createDiscordOAuthClient`. */
583
956
  export type DiscordOAuthClient = ReturnType<typeof createDiscordOAuthClient>;
584
957
  /**
585
958
  * The verified connection of one service, or `undefined`. Unverified ones are ignored: anyone can type a
@@ -603,6 +976,7 @@ export declare function signOAuthState(secret: string, opts?: {
603
976
  export declare function verifyOAuthState(secret: string, state: string, opts?: {
604
977
  now?: number;
605
978
  }): boolean;
979
+ /** Builds the webhook client: a `webhooks` group that reads, posts to and deletes messages from a webhook by its id and token. */
606
980
  export declare function createDiscordClient(opts?: DiscordClientOptions): {
607
981
  webhooks: {
608
982
  get: (id: string, token: string) => Promise<DiscordWebhookInfo>;
@@ -613,5 +987,6 @@ export declare function createDiscordClient(opts?: DiscordClientOptions): {
613
987
  deleteMessage: (id: string, token: string, messageId: string) => Promise<void>;
614
988
  };
615
989
  };
990
+ /** The client `createDiscordClient` returns. */
616
991
  export type DiscordClient = ReturnType<typeof createDiscordClient>;
617
992
  export type { DiscordClientOptions, DiscordWebhookInfo };
package/dist/index.mjs CHANGED
@@ -81,6 +81,8 @@ const DEFAULT_MAX_RETRIES = 3;
81
81
  const MAX_RETRY_WAIT_MS = 1e4;
82
82
  /** A guard on `membersWithRole`, so a runaway cursor cannot loop forever. */
83
83
  const MAX_MEMBER_PAGES = 50;
84
+ /** A guard on `threads.inChannel`, so a runaway `before` cursor cannot loop forever. */
85
+ const MAX_THREAD_PAGES = 50;
84
86
  /** Discord ids are snowflakes; anything else would be spliced into a URL path. */
85
87
  function id(value) {
86
88
  if (!/^\d{1,20}$/.test(value)) throw new Error(`invalid Discord id: ${JSON.stringify(value)}`);
@@ -93,6 +95,22 @@ function bodyIds(...values) {
93
95
  for (const one of typeof value === "string" ? [value] : value) id(one);
94
96
  }
95
97
  }
98
+ /**
99
+ * A message body with its mention ids checked and mentions switched off unless asked for. The default is applied
100
+ * after the spread, so an explicit `allowed_mentions: undefined` still means "no mentions".
101
+ */
102
+ function withMentionsOff(body) {
103
+ bodyIds(body.allowed_mentions?.roles, body.allowed_mentions?.users);
104
+ return {
105
+ ...body,
106
+ allowed_mentions: body.allowed_mentions ?? { parse: [] }
107
+ };
108
+ }
109
+ /** An ISO 8601 timestamp that travels in a query, so a malformed one fails here and not at Discord. */
110
+ function timestamp(value) {
111
+ if (Number.isNaN(Date.parse(value))) throw new Error(`invalid timestamp: ${JSON.stringify(value)}`);
112
+ return value;
113
+ }
96
114
  /** How long Discord asks us to wait after a 429, in ms (header first, then the JSON body). */
97
115
  async function retryDelayMs(res) {
98
116
  const header = Number(res.headers.get("retry-after"));
@@ -279,16 +297,17 @@ function createDiscordBotClient(opts) {
279
297
  }),
280
298
  webhooks: async (channelId) => (await rest.request(`/channels/${id(channelId)}/webhooks`)).map(withUrl),
281
299
  /** `POST /channels/:id/messages` as the bot. Mentions are off unless `allowed_mentions` asks for them. */
282
- createMessage: (channelId, body) => {
283
- bodyIds(body.allowed_mentions?.roles, body.allowed_mentions?.users);
284
- return rest.request(`/channels/${id(channelId)}/messages`, {
285
- method: "POST",
286
- body: {
287
- ...body,
288
- allowed_mentions: body.allowed_mentions ?? { parse: [] }
289
- }
290
- });
291
- },
300
+ createMessage: (channelId, body) => rest.request(`/channels/${id(channelId)}/messages`, {
301
+ method: "POST",
302
+ body: withMentionsOff(body)
303
+ }),
304
+ /** `GET /channels/:id/messages/:id` — one message, e.g. the first message of a forum post (its id is the post's). */
305
+ message: (channelId, messageId) => rest.request(`/channels/${id(channelId)}/messages/${id(messageId)}`),
306
+ /** `PATCH /channels/:id/messages/:id` — a message this bot posted. Mentions are off unless `allowed_mentions` asks for them. */
307
+ editMessage: (channelId, messageId, body) => rest.request(`/channels/${id(channelId)}/messages/${id(messageId)}`, {
308
+ method: "PATCH",
309
+ body: withMentionsOff(body)
310
+ }),
292
311
  /** `POST /channels/:id/invites` — a join link for the server, e.g. to hand over a newly created one. */
293
312
  createInvite: (channelId, body = {}) => rest.request(`/channels/${id(channelId)}/invites`, {
294
313
  method: "POST",
@@ -310,6 +329,59 @@ function createDiscordBotClient(opts) {
310
329
  body
311
330
  }))
312
331
  },
332
+ threads: {
333
+ /**
334
+ * `POST /channels/:id/threads` in a forum channel — a new post with its first message. Not idempotent: look
335
+ * the post up (`inChannel`) before retrying. Mentions in the first message are off unless asked for.
336
+ */
337
+ createForumPost: (channelId, body) => {
338
+ bodyIds(body.applied_tags);
339
+ return rest.request(`/channels/${id(channelId)}/threads`, {
340
+ method: "POST",
341
+ body: {
342
+ ...body,
343
+ message: withMentionsOff(body.message)
344
+ }
345
+ });
346
+ },
347
+ /** `PATCH /channels/:id` on a thread — its title, tags, archived or locked state. Writing to an archived post unarchives it. */
348
+ modify: (threadId, body) => {
349
+ bodyIds(body.applied_tags);
350
+ return rest.request(`/channels/${id(threadId)}`, {
351
+ method: "PATCH",
352
+ body
353
+ });
354
+ },
355
+ /** `GET /guilds/:id/threads/active` — every active thread in the server, across channels. */
356
+ active: async (guildId) => (await rest.request(`/guilds/${id(guildId)}/threads/active`)).threads,
357
+ /** `GET /channels/:id/threads/archived/public` — one page of a channel's archived threads, newest archived first. */
358
+ archived: (channelId, opts = {}) => {
359
+ const query = { limit: String(Math.min(Math.max(opts.limit ?? 100, 1), 100)) };
360
+ if (opts.before !== void 0) query.before = timestamp(opts.before);
361
+ return rest.request(`/channels/${id(channelId)}/threads/archived/public`, { query });
362
+ },
363
+ /**
364
+ * Every post in a forum channel, active and archived: Discord hides a post that has been quiet for a while, so
365
+ * a lookup that read only the active ones would miss it and make it again.
366
+ */
367
+ inChannel: async (guildId, channelId) => {
368
+ const parent = id(channelId);
369
+ const { threads } = await rest.request(`/guilds/${id(guildId)}/threads/active`);
370
+ const found = threads.filter((t) => t.parent_id === parent);
371
+ let before;
372
+ for (let page = 0; page < MAX_THREAD_PAGES; page++) {
373
+ const batch = await rest.request(`/channels/${parent}/threads/archived/public`, { query: {
374
+ limit: "100",
375
+ ...before === void 0 ? {} : { before: timestamp(before) }
376
+ } });
377
+ found.push(...batch.threads);
378
+ const last = batch.threads[batch.threads.length - 1];
379
+ if (!batch.has_more || last === void 0) return found;
380
+ before = last.thread_metadata.archive_timestamp;
381
+ }
382
+ throw new Error(`more than 5000 archived threads; refusing to read them all`);
383
+ }
384
+ },
313
385
  application: {
314
386
  /** `GET /applications/@me` — the bot's own application: description, tags, install settings, URLs. */
315
387
  get: () => rest.request("/applications/@me"),
@@ -344,21 +416,35 @@ function createDiscordBotClient(opts) {
344
416
  // src/bot/bot.type.ts
345
417
  /** Discord channel `type` values this client creates or reads. */
346
418
  const ChannelType = {
419
+ /** A text channel. */
347
420
  Text: 0,
421
+ /** A category that groups channels. */
348
422
  Category: 4,
423
+ /** An announcement channel. */
349
424
  Announcement: 5,
425
+ /** A forum channel. */
350
426
  Forum: 15
351
427
  };
428
+ /** Bits of a message's `flags` that a bot may set when it posts. */
429
+ const MessageFlag = {
430
+ /** Posts without notifying anyone, as a backfill of many messages should. */
431
+ SuppressNotifications: 4096 };
352
432
  /**
353
433
  * Bits of the application's `flags`. The `*Limited` ones are the portal's privileged-intent toggles for an app
354
434
  * that Discord has not verified; once verified, the plain ones take over and the toggle is Discord's to change.
355
435
  */
356
436
  const ApplicationFlag = {
437
+ /** Application may receive presence updates (verified apps). */
357
438
  GatewayPresence: 4096,
439
+ /** The privileged presence intent toggle for an unverified app. */
358
440
  GatewayPresenceLimited: 8192,
441
+ /** Application may receive server-member events and the member list (verified apps). */
359
442
  GatewayGuildMembers: 16384,
443
+ /** The privileged server-members intent toggle for an unverified app. */
360
444
  GatewayGuildMembersLimited: 32768,
445
+ /** Application may read message content (verified apps). */
361
446
  GatewayMessageContent: 1 << 18,
447
+ /** The privileged message-content intent toggle for an unverified app. */
362
448
  GatewayMessageContentLimited: 1 << 19
363
449
  };
364
450
  // src/bot/permissions.ts
@@ -369,55 +455,105 @@ const ApplicationFlag = {
369
455
  * `permissions` cannot set them by name.
370
456
  */
371
457
  const Permission = {
458
+ /** Create invites to the server. */
372
459
  CreateInstantInvite: 1n << 0n,
460
+ /** Kick members. */
373
461
  KickMembers: 1n << 1n,
462
+ /** Ban members. */
374
463
  BanMembers: 1n << 2n,
464
+ /** Administrator: all permissions, and bypasses channel overrides. */
375
465
  Administrator: 1n << 3n,
466
+ /** Create, edit and delete channels. */
376
467
  ManageChannels: 1n << 4n,
468
+ /** Change server settings. */
377
469
  ManageGuild: 1n << 5n,
470
+ /** Add new reactions to messages. */
378
471
  AddReactions: 1n << 6n,
472
+ /** View the audit log. */
379
473
  ViewAuditLog: 1n << 7n,
474
+ /** Use priority speaker in voice channels. */
380
475
  PrioritySpeaker: 1n << 8n,
476
+ /** Go live (stream) in voice channels. */
381
477
  Stream: 1n << 9n,
478
+ /** View channels (and read their messages). */
382
479
  ViewChannel: 1n << 10n,
480
+ /** Send messages in text channels. */
383
481
  SendMessages: 1n << 11n,
482
+ /** Send text-to-speech messages. */
384
483
  SendTtsMessages: 1n << 12n,
484
+ /** Delete other members' messages and pin messages. */
385
485
  ManageMessages: 1n << 13n,
486
+ /** Have links in messages embed. */
386
487
  EmbedLinks: 1n << 14n,
488
+ /** Upload files and media. */
387
489
  AttachFiles: 1n << 15n,
490
+ /** Read message history. */
388
491
  ReadMessageHistory: 1n << 16n,
492
+ /** Mention @everyone, @here and all roles. */
389
493
  MentionEveryone: 1n << 17n,
494
+ /** Use emojis from other servers. */
390
495
  UseExternalEmojis: 1n << 18n,
496
+ /** View server insights. */
391
497
  ViewGuildInsights: 1n << 19n,
498
+ /** Join voice channels. */
392
499
  Connect: 1n << 20n,
500
+ /** Speak in voice channels. */
393
501
  Speak: 1n << 21n,
502
+ /** Mute other members in voice channels. */
394
503
  MuteMembers: 1n << 22n,
504
+ /** Deafen other members in voice channels. */
395
505
  DeafenMembers: 1n << 23n,
506
+ /** Move members between voice channels. */
396
507
  MoveMembers: 1n << 24n,
508
+ /** Use voice activity detection instead of push-to-talk. */
397
509
  UseVoiceActivity: 1n << 25n,
510
+ /** Change one's own nickname. */
398
511
  ChangeNickname: 1n << 26n,
512
+ /** Change other members' nicknames. */
399
513
  ManageNicknames: 1n << 27n,
514
+ /** Create, edit and delete roles lower than the member's own. */
400
515
  ManageRoles: 1n << 28n,
516
+ /** Create, edit and delete webhooks. */
401
517
  ManageWebhooks: 1n << 29n,
518
+ /** Edit and delete emojis, stickers and soundboard sounds. */
402
519
  ManageGuildExpressions: 1n << 30n,
520
+ /** Use application commands (slash commands). */
403
521
  UseApplicationCommands: 1n << 31n,
522
+ /** Request to speak in stage channels. */
404
523
  RequestToSpeak: 1n << 32n,
524
+ /** Edit and delete scheduled events. */
405
525
  ManageEvents: 1n << 33n,
526
+ /** Manage and delete threads. */
406
527
  ManageThreads: 1n << 34n,
528
+ /** Create public threads. */
407
529
  CreatePublicThreads: 1n << 35n,
530
+ /** Create private threads. */
408
531
  CreatePrivateThreads: 1n << 36n,
532
+ /** Use stickers from other servers. */
409
533
  UseExternalStickers: 1n << 37n,
534
+ /** Send messages in threads. */
410
535
  SendMessagesInThreads: 1n << 38n,
536
+ /** Start activities in voice channels. */
411
537
  UseEmbeddedActivities: 1n << 39n,
538
+ /** Time out members. */
412
539
  ModerateMembers: 1n << 40n,
540
+ /** View creator monetization analytics. */
413
541
  ViewCreatorMonetizationAnalytics: 1n << 41n,
542
+ /** Use soundboard sounds. */
414
543
  UseSoundboard: 1n << 42n,
544
+ /** Create emojis, stickers and soundboard sounds. */
415
545
  CreateGuildExpressions: 1n << 43n,
546
+ /** Create scheduled events. */
416
547
  CreateEvents: 1n << 44n,
548
+ /** Use soundboard sounds from other servers. */
417
549
  UseExternalSounds: 1n << 45n,
550
+ /** Send voice messages. */
418
551
  SendVoiceMessages: 1n << 46n,
552
+ /** Send polls. */
419
553
  SendPolls: 1n << 49n,
554
+ /** Use apps that are not installed in the server. */
420
555
  UseExternalApps: 1n << 50n,
556
+ /** Pin and unpin messages. */
421
557
  PinMessages: 1n << 51n
422
558
  };
423
559
  /** The decimal-string bitfield Discord wants for the named permissions (`"0"` for none). */
@@ -561,7 +697,8 @@ function verifyOAuthState(secret, state, opts = {}) {
561
697
  return Number(expires) >= Math.floor((opts.now ?? Date.now()) / 1e3);
562
698
  }
563
699
  // src/index.ts
700
+ /** Builds the webhook client: a `webhooks` group that reads, posts to and deletes messages from a webhook by its id and token. */
564
701
  function createDiscordClient(opts = {}) {
565
702
  return { webhooks: webhooks(opts.baseUrl ?? "https://discord.com/api/v10", opts.fetch ?? globalThis.fetch) };
566
703
  }
567
- export { ApplicationFlag, ChannelType, DEFAULT_STATE_TTL_SECONDS, Permission, createDiscordBotClient, createDiscordClient, createDiscordOAuthClient, findVerifiedConnection, parseWebhookUrl, permissionNames, permissions, signOAuthState, verifyOAuthState };
704
+ export { ApplicationFlag, ChannelType, DEFAULT_STATE_TTL_SECONDS, MessageFlag, Permission, createDiscordBotClient, createDiscordClient, createDiscordOAuthClient, findVerifiedConnection, parseWebhookUrl, permissionNames, permissions, signOAuthState, verifyOAuthState };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theholocron/discord-client",
3
- "version": "1.58.0-alpha.3",
3
+ "version": "1.58.0-alpha.8",
4
4
  "description": "A TypeScript client for the Discord webhooks API",
5
5
  "keywords": [
6
6
  "discord",
@@ -32,22 +32,22 @@
32
32
  "dist"
33
33
  ],
34
34
  "dependencies": {
35
- "@theholocron/http-client": "^1.58.0-alpha.3"
35
+ "@theholocron/http-client": "^1.58.0-alpha.8"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@types/node": "^26",
39
39
  "@vitest/coverage-v8": "^5.0.5",
40
- "@vitest/eslint-plugin": "^1.6.27",
41
- "eslint": "^10.8.1",
42
- "eslint-plugin-n": "^18.3.0",
43
- "globals": "^17.11.0",
44
- "tsdown": "^0.23.3",
40
+ "@vitest/eslint-plugin": "^1.6.28",
41
+ "eslint": "^10.14.0",
42
+ "eslint-plugin-n": "^18.5.0",
43
+ "globals": "^17.14.0",
44
+ "tsdown": "^0.23.4",
45
45
  "typescript": "^5.9.3",
46
46
  "vitest": "^5.0.5",
47
- "@theholocron/eslint-config": "^8.9.0-alpha.3",
48
- "@theholocron/tsconfig": "^8.9.0-alpha.3",
49
- "@theholocron/vitest-config": "^8.9.0-alpha.3",
50
- "@theholocron/tsdown-config": "^8.9.0-alpha.3"
47
+ "@theholocron/eslint-config": "^8.9.0-alpha.8",
48
+ "@theholocron/tsdown-config": "^8.9.0-alpha.8",
49
+ "@theholocron/tsconfig": "^8.9.0-alpha.8",
50
+ "@theholocron/vitest-config": "^8.9.0-alpha.8"
51
51
  },
52
52
  "engines": {
53
53
  "node": ">=22.0.0"
@@ -56,10 +56,10 @@
56
56
  "access": "public"
57
57
  },
58
58
  "scripts": {
59
- "delivery.build": "tsdown && node ../scripts/check-bundle-size.mjs",
60
- "sourceQuality.staticAnalysis": "eslint .",
59
+ "build": "tsdown && node ../scripts/check-bundle-size.mjs",
60
+ "lint:code": "eslint .",
61
+ "test:unit": "vitest run --coverage",
61
62
  "test:watch": "vitest",
62
- "verification.typeSafety": "tsc --noEmit",
63
- "verification.unitTests": "vitest run"
63
+ "typecheck": "tsc --noEmit"
64
64
  }
65
65
  }