@theholocron/discord-client 1.58.0-alpha.4 → 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 +15 -8
- package/dist/index.d.mts +136 -0
- package/dist/index.mjs +87 -11
- package/package.json +15 -15
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.
|
|
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`
|
|
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
|
@@ -51,6 +51,11 @@ export declare const ChannelType: {
|
|
|
51
51
|
/** A forum channel. */
|
|
52
52
|
readonly Forum: 15;
|
|
53
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
|
+
};
|
|
54
59
|
/** A Discord account, as the API returns it. */
|
|
55
60
|
export interface DiscordUser {
|
|
56
61
|
/** The user id. */
|
|
@@ -246,6 +251,109 @@ export interface DiscordChannel {
|
|
|
246
251
|
position?: number;
|
|
247
252
|
/** Permission overrides set on this channel. */
|
|
248
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;
|
|
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"];
|
|
249
357
|
}
|
|
250
358
|
/** What `POST /guilds/{id}/channels` takes. */
|
|
251
359
|
export interface CreateChannelInput {
|
|
@@ -259,6 +367,8 @@ export interface CreateChannelInput {
|
|
|
259
367
|
parent_id?: string;
|
|
260
368
|
/** Permission overrides to set on the new channel. */
|
|
261
369
|
permission_overwrites?: DiscordPermissionOverwrite[];
|
|
370
|
+
/** The tags of a forum channel (`ChannelType.Forum`). */
|
|
371
|
+
available_tags?: ForumTagInput[];
|
|
262
372
|
}
|
|
263
373
|
/** `null` clears `topic` or detaches the channel from its category. */
|
|
264
374
|
export interface ModifyChannelInput {
|
|
@@ -270,6 +380,8 @@ export interface ModifyChannelInput {
|
|
|
270
380
|
parent_id?: string | null;
|
|
271
381
|
/** The full list of permission overrides, replacing the existing ones. */
|
|
272
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[];
|
|
273
385
|
}
|
|
274
386
|
/** A member of a server. Only what provisioning needs: who they are and which roles they hold. */
|
|
275
387
|
export interface DiscordGuildMember {
|
|
@@ -353,6 +465,8 @@ export interface CreateMessageInput {
|
|
|
353
465
|
nonce?: string | number;
|
|
354
466
|
/** Turns on the nonce de-duplication for `nonce`. */
|
|
355
467
|
enforce_nonce?: boolean;
|
|
468
|
+
/** Message flags; see `MessageFlag`. */
|
|
469
|
+
flags?: number;
|
|
356
470
|
/** Defaults to no mentions, so a pasted `@everyone` cannot ping anyone. */
|
|
357
471
|
allowed_mentions?: {
|
|
358
472
|
/** Which mention types Discord may resolve into pings. */
|
|
@@ -591,6 +705,10 @@ export declare function createDiscordBotClient(opts: DiscordBotClientOptions): {
|
|
|
591
705
|
webhooks: (channelId: string) => Promise<DiscordChannelWebhook[]>;
|
|
592
706
|
/** `POST /channels/:id/messages` as the bot. Mentions are off unless `allowed_mentions` asks for them. */
|
|
593
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>;
|
|
594
712
|
/** `POST /channels/:id/invites` — a join link for the server, e.g. to hand over a newly created one. */
|
|
595
713
|
createInvite: (channelId: string, body?: CreateInviteInput) => Promise<DiscordInvite>;
|
|
596
714
|
/** `GET /channels/:id/messages` — the newest messages first, to read a post back. */
|
|
@@ -602,6 +720,24 @@ export declare function createDiscordBotClient(opts: DiscordBotClientOptions): {
|
|
|
602
720
|
name: string;
|
|
603
721
|
}) => Promise<DiscordChannelWebhook>;
|
|
604
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
|
+
};
|
|
605
741
|
application: {
|
|
606
742
|
/** `GET /applications/@me` — the bot's own application: description, tags, install settings, URLs. */
|
|
607
743
|
get: () => Promise<DiscordApplication>;
|
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
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
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"),
|
|
@@ -353,6 +425,10 @@ const ChannelType = {
|
|
|
353
425
|
/** A forum channel. */
|
|
354
426
|
Forum: 15
|
|
355
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 };
|
|
356
432
|
/**
|
|
357
433
|
* Bits of the application's `flags`. The `*Limited` ones are the portal's privileged-intent toggles for an app
|
|
358
434
|
* that Discord has not verified; once verified, the plain ones take over and the toggle is Discord's to change.
|
|
@@ -625,4 +701,4 @@ function verifyOAuthState(secret, state, opts = {}) {
|
|
|
625
701
|
function createDiscordClient(opts = {}) {
|
|
626
702
|
return { webhooks: webhooks(opts.baseUrl ?? "https://discord.com/api/v10", opts.fetch ?? globalThis.fetch) };
|
|
627
703
|
}
|
|
628
|
-
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
|
+
"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.
|
|
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.
|
|
41
|
-
"eslint": "^10.
|
|
42
|
-
"eslint-plugin-n": "^18.
|
|
43
|
-
"globals": "^17.
|
|
44
|
-
"tsdown": "^0.23.
|
|
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/
|
|
48
|
-
"@theholocron/
|
|
49
|
-
"@theholocron/
|
|
50
|
-
"@theholocron/vitest-config": "^8.9.0-alpha.
|
|
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
|
-
"
|
|
60
|
-
"
|
|
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
|
-
"
|
|
63
|
-
"verification.unitTests": "vitest run"
|
|
63
|
+
"typecheck": "tsc --noEmit"
|
|
64
64
|
}
|
|
65
65
|
}
|