chapterjs 0.3.0 → 0.5.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 +5 -3
- package/dist/assets/public.js +4 -4
- package/dist/cli/build.d.ts +6 -2
- package/dist/cli/build.js +17 -4
- package/dist/cli/dev.js +37 -6
- package/dist/cli/env.d.ts +5 -0
- package/dist/cli/env.js +39 -3
- package/dist/cli/project.d.ts +61 -9
- package/dist/cli/project.js +250 -44
- package/dist/cli/redis.d.ts +23 -0
- package/dist/cli/redis.js +69 -0
- package/dist/cli/start.js +54 -18
- package/dist/cli/test.js +112 -18
- package/dist/commands/command.d.ts +140 -5
- package/dist/commands/command.js +17 -2
- package/dist/commands/declaration.d.ts +17 -4
- package/dist/commands/declaration.js +128 -2
- package/dist/commands/router.js +78 -18
- package/dist/commands/tree.d.ts +25 -4
- package/dist/commands/tree.js +95 -26
- package/dist/commands/twins.d.ts +8 -0
- package/dist/commands/twins.js +24 -6
- package/dist/components/button.d.ts +8 -3
- package/dist/components/declaration.d.ts +2 -2
- package/dist/components/declaration.js +16 -12
- package/dist/components/render.js +5 -0
- package/dist/components/router.js +41 -0
- package/dist/components/select.d.ts +12 -5
- package/dist/core/bot.d.ts +7 -0
- package/dist/core/bot.js +44 -0
- package/dist/discord/api.d.ts +2 -0
- package/dist/discord/api.js +2 -0
- package/dist/discord/permissions.d.ts +4 -4
- package/dist/discord/permissions.js +13 -0
- package/dist/events/registry.d.ts +265 -243
- package/dist/events/registry.js +131 -100
- package/dist/index.d.ts +7 -4
- package/dist/index.js +2 -1
- package/dist/interactions/dispatch.js +4 -2
- package/dist/loader/loader.d.ts +16 -0
- package/dist/loader/loader.js +52 -7
- package/dist/loader/usage.d.ts +15 -0
- package/dist/loader/usage.js +40 -0
- package/dist/loader/watch.d.ts +5 -3
- package/dist/loader/watch.js +88 -17
- package/dist/messages/commands.js +3 -0
- package/dist/messages/messages.d.ts +1 -0
- package/dist/messages/translate.js +5 -1
- package/dist/presence/declaration.d.ts +49 -2
- package/dist/presence/declaration.js +122 -39
- package/dist/presence/presence.d.ts +65 -6
- package/dist/presence/presence.js +11 -0
- package/dist/presence/rotation.d.ts +32 -0
- package/dist/presence/rotation.js +167 -0
- package/dist/ranking/declaration.d.ts +12 -0
- package/dist/ranking/declaration.js +48 -0
- package/dist/ranking/ranking.d.ts +102 -0
- package/dist/ranking/ranking.js +129 -0
- package/dist/redis/client.d.ts +36 -0
- package/dist/redis/client.js +252 -0
- package/dist/redis/protocol.d.ts +22 -0
- package/dist/redis/protocol.js +99 -0
- package/dist/redis/url.d.ts +24 -0
- package/dist/redis/url.js +50 -0
- package/dist/rest/errors.d.ts +0 -1
- package/dist/rest/errors.js +15 -1
- package/dist/rest/rest.js +10 -1
- package/dist/store/backend.d.ts +49 -4
- package/dist/store/backend.js +66 -11
- package/dist/store/declaration.d.ts +3 -0
- package/dist/store/declaration.js +8 -3
- package/dist/store/file-backend.d.ts +3 -2
- package/dist/store/file-backend.js +47 -1
- package/dist/store/ipc-backend.d.ts +24 -1
- package/dist/store/ipc-backend.js +49 -0
- package/dist/store/memory-backend.d.ts +4 -2
- package/dist/store/memory-backend.js +27 -1
- package/dist/store/ranking-backend.d.ts +45 -0
- package/dist/store/ranking-backend.js +82 -0
- package/dist/store/redis-backend.d.ts +38 -0
- package/dist/store/redis-backend.js +245 -0
- package/dist/store/redis-ranking.d.ts +15 -0
- package/dist/store/redis-ranking.js +69 -0
- package/dist/store/scope.d.ts +17 -1
- package/dist/store/scope.js +32 -3
- package/dist/store/store.d.ts +39 -5
- package/dist/store/store.js +79 -32
- package/dist/structures/channel.d.ts +13 -0
- package/dist/structures/channel.js +24 -0
- package/dist/structures/context.d.ts +3 -0
- package/dist/structures/entities.js +1 -0
- package/dist/structures/interaction.d.ts +6 -0
- package/dist/structures/interaction.js +20 -0
- package/dist/structures/invite.d.ts +6 -0
- package/dist/structures/invite.js +11 -0
- package/dist/structures/message.d.ts +17 -0
- package/dist/structures/message.js +33 -0
- package/dist/structures/wait.d.ts +105 -0
- package/dist/structures/wait.js +85 -0
- package/dist/testing/harness.d.ts +48 -8
- package/dist/testing/harness.js +156 -36
- package/dist/testing/index.d.ts +1 -1
- package/dist/testing/smoke.js +112 -1
- package/dist/testing/world.js +51 -2
- package/dist/util/call-site.d.ts +4 -0
- package/dist/util/call-site.js +22 -0
- package/dist/voice/connection.d.ts +2 -0
- package/dist/voice/connection.js +15 -1
- package/dist/voice/usage.d.ts +3 -2
- package/dist/voice/usage.js +7 -19
- package/dist/waiting/usage.d.ts +5 -0
- package/dist/waiting/usage.js +19 -0
- package/dist/waiting/waiters.d.ts +48 -0
- package/dist/waiting/waiters.js +107 -0
- package/package.json +1 -1
|
@@ -31,8 +31,12 @@ export interface ButtonLook<Data extends DataShape = DataShape> {
|
|
|
31
31
|
export type ButtonContext<Data extends DataShape = {}, Where extends ComponentWhere = 'guild'> = InteractiveContext<Data> & TranslationContext & PlaceOf<Where, ComponentInteraction>;
|
|
32
32
|
/** What a file gives to `button()`. */
|
|
33
33
|
export interface ButtonConfig<Data extends DataShape = {}, Where extends ComponentWhere = 'guild'> extends ButtonLook<Data>, InteractiveConfig<Data, Where> {
|
|
34
|
-
/**
|
|
35
|
-
|
|
34
|
+
/**
|
|
35
|
+
* What to do when someone clicks the button. Leave it out for a button
|
|
36
|
+
* that is only waited for (`message.waitForClick()`): once nothing waits,
|
|
37
|
+
* a click answers that it is not available any more.
|
|
38
|
+
*/
|
|
39
|
+
run?: (context: ButtonContext<Data, Where>) => unknown;
|
|
36
40
|
}
|
|
37
41
|
/**
|
|
38
42
|
* What `button()` returns: a declaration the framework finds in the exports
|
|
@@ -71,7 +75,8 @@ export interface LoadedButton {
|
|
|
71
75
|
readonly where: ComponentWhere;
|
|
72
76
|
readonly who: 'everyone' | 'author';
|
|
73
77
|
readonly ephemeral: boolean;
|
|
74
|
-
|
|
78
|
+
/** `undefined` for a button that is only waited for. */
|
|
79
|
+
readonly run: ((context: ButtonContext<DataShape, ComponentWhere>) => unknown) | undefined;
|
|
75
80
|
}
|
|
76
81
|
/** Checks what a message gives to change the look of a button. */
|
|
77
82
|
export declare function readLook(what: string, look: unknown): ButtonLook;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type Declaration } from '../loader/loader.js';
|
|
2
2
|
import { type LoadedButton } from './button.js';
|
|
3
3
|
import { isEmbedDeclaration } from './embed.js';
|
|
4
4
|
import { type ComponentKind } from './declared.js';
|
|
@@ -29,7 +29,7 @@ export declare function findDuplicates<T extends {
|
|
|
29
29
|
kind: ComponentKind;
|
|
30
30
|
name: string;
|
|
31
31
|
};
|
|
32
|
-
}>(entries: readonly T[]): {
|
|
32
|
+
}>(entries: readonly T[], running?: ReadonlySet<string>): {
|
|
33
33
|
valid: T[];
|
|
34
34
|
conflicts: {
|
|
35
35
|
file: string;
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// bound to the name of its export, which is its id and what makes its
|
|
5
5
|
// instances.
|
|
6
6
|
import { Limits } from '../discord/api.js';
|
|
7
|
+
import { bySite } from '../loader/loader.js';
|
|
7
8
|
import { checkEmbed } from '../structures/payload.js';
|
|
8
9
|
import { isDynamic } from './component.js';
|
|
9
10
|
import { readLook, renderButton, } from './button.js';
|
|
@@ -93,6 +94,17 @@ function checkRun(what, value, example) {
|
|
|
93
94
|
fail(`${what} has no "run": the function to run when someone uses it, like ${example}`);
|
|
94
95
|
}
|
|
95
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* The `run` of a button or a menu: optional, since one may only be waited
|
|
99
|
+
* for (`message.waitForClick()`); when given, a function.
|
|
100
|
+
*/
|
|
101
|
+
function checkOptionalRun(what, value, example) {
|
|
102
|
+
if (value === undefined)
|
|
103
|
+
return;
|
|
104
|
+
if (typeof value !== 'function') {
|
|
105
|
+
fail(`"run" of ${what} is the function to run when someone uses it, like ${example}. Leave it out for one that is only waited for with message.waitForClick().`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
96
108
|
function readButton(name, config) {
|
|
97
109
|
if (!isRecord(config)) {
|
|
98
110
|
return fail(`button() needs an object: button({ label: '...', run({ interaction }) { ... } })`);
|
|
@@ -118,7 +130,7 @@ function readButton(name, config) {
|
|
|
118
130
|
if (look.label === undefined && look.emoji === undefined) {
|
|
119
131
|
fail('A button needs a label or an emoji.');
|
|
120
132
|
}
|
|
121
|
-
|
|
133
|
+
checkOptionalRun(what, config.run, `async run({ interaction }) { await interaction.update('Done!'); }`);
|
|
122
134
|
return Object.freeze({
|
|
123
135
|
kind: 'button',
|
|
124
136
|
name,
|
|
@@ -168,7 +180,7 @@ function readSelect(name, config) {
|
|
|
168
180
|
}
|
|
169
181
|
const options = type === 'string' ? readOptions(what, config.options) : [];
|
|
170
182
|
const placeholder = readPlaceholder(what, config.placeholder);
|
|
171
|
-
|
|
183
|
+
checkOptionalRun(what, config.run, `async run({ interaction, values }) { ... }`);
|
|
172
184
|
return Object.freeze({
|
|
173
185
|
kind: 'select',
|
|
174
186
|
name,
|
|
@@ -310,18 +322,10 @@ export const componentDeclaration = {
|
|
|
310
322
|
},
|
|
311
323
|
};
|
|
312
324
|
/** Two components of the same kind with the same name: the later one is left out. */
|
|
313
|
-
export function findDuplicates(entries) {
|
|
325
|
+
export function findDuplicates(entries, running) {
|
|
314
326
|
const seen = new Map();
|
|
315
327
|
const conflicts = [];
|
|
316
|
-
const valid =
|
|
317
|
-
.sort((a, b) => a.file === b.file
|
|
318
|
-
? a.export < b.export
|
|
319
|
-
? -1
|
|
320
|
-
: 1
|
|
321
|
-
: a.file < b.file
|
|
322
|
-
? -1
|
|
323
|
-
: 1)
|
|
324
|
-
.filter(entry => {
|
|
328
|
+
const valid = bySite(entries, running).filter(entry => {
|
|
325
329
|
const key = `${entry.component.kind}:${entry.component.name}`;
|
|
326
330
|
const first = seen.get(key);
|
|
327
331
|
if (!first) {
|
|
@@ -46,6 +46,11 @@ export function renderComponents(components, t = missingTranslator(null)) {
|
|
|
46
46
|
const v2 = rows.some(one => one.kind !== 'row');
|
|
47
47
|
// The texts of a piece may be computed now, for who will read the message.
|
|
48
48
|
const raw = rows.map(one => resolve(one, t).raw);
|
|
49
|
+
// Without the pieces of the new layout, a message is rows only, and
|
|
50
|
+
// Discord takes 5 of them.
|
|
51
|
+
if (!v2 && raw.length > Limits.MessageActionRows) {
|
|
52
|
+
throw new RangeError(`The message has ${raw.length} rows of components: Discord accepts ${Limits.MessageActionRows} at most. A menu takes a row by itself and up to 5 buttons share one: send the rest in another message.`);
|
|
53
|
+
}
|
|
49
54
|
const total = count(raw);
|
|
50
55
|
if (total > Limits.MessageComponents) {
|
|
51
56
|
throw new RangeError(`The message has ${total} components once its rows are made: Discord accepts ${Limits.MessageComponents} at most.`);
|
|
@@ -10,6 +10,7 @@ import { ComponentInteraction, ModalInteraction, } from '../structures/interacti
|
|
|
10
10
|
import { remember } from '../structures/known.js';
|
|
11
11
|
import { toCamelCase } from '../util/case.js';
|
|
12
12
|
import { decodeCustomId, readData } from './custom-id.js';
|
|
13
|
+
import { componentStateOf } from './declared.js';
|
|
13
14
|
import { SELECT_TYPES } from './select.js';
|
|
14
15
|
/** What a component is called, for the person and the developer. */
|
|
15
16
|
const WHAT_OF = {
|
|
@@ -130,6 +131,32 @@ export class ComponentRouter {
|
|
|
130
131
|
this.#options.onError(file, error);
|
|
131
132
|
return refuse(interaction, 'failed', { what });
|
|
132
133
|
}
|
|
134
|
+
// A click a function of the files waits for (`message.waitForClick()`)
|
|
135
|
+
// is theirs: the `run` of the component does not run.
|
|
136
|
+
if (component.kind !== 'modal' && ctx.waiters?.wants('click')) {
|
|
137
|
+
const click = Object.freeze({
|
|
138
|
+
messageId: message.id,
|
|
139
|
+
interaction: interaction,
|
|
140
|
+
user,
|
|
141
|
+
member: place.member ?? null,
|
|
142
|
+
name,
|
|
143
|
+
data: Object.freeze(values),
|
|
144
|
+
values: Object.freeze([
|
|
145
|
+
...(data.values ?? []),
|
|
146
|
+
]),
|
|
147
|
+
is: (declared) => {
|
|
148
|
+
const state = componentStateOf(declared);
|
|
149
|
+
return state?.kind === component.kind && state.bound?.name === name;
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
if (ctx.waiters.offer('click', click)) {
|
|
153
|
+
return this.#waited(interaction);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (!component.run) {
|
|
157
|
+
// Only waited for, and nothing waits any more.
|
|
158
|
+
return refuse(interaction, 'gone', { what });
|
|
159
|
+
}
|
|
133
160
|
const context = Object.freeze({
|
|
134
161
|
interaction,
|
|
135
162
|
user,
|
|
@@ -160,6 +187,20 @@ export class ComponentRouter {
|
|
|
160
187
|
run: () => component.run(context),
|
|
161
188
|
});
|
|
162
189
|
}
|
|
190
|
+
/**
|
|
191
|
+
* A click taken by a wait: the files answer it where they waited. When
|
|
192
|
+
* they did not after 2 seconds, nothing changes on screen
|
|
193
|
+
* (`deferUpdate()`), so the person never sees "This interaction failed".
|
|
194
|
+
*/
|
|
195
|
+
#waited(interaction) {
|
|
196
|
+
const timer = setTimeout(() => {
|
|
197
|
+
if (!interaction.answered && !interaction.deferred) {
|
|
198
|
+
interaction.deferUpdate().catch(() => { });
|
|
199
|
+
}
|
|
200
|
+
}, this.#options.deferAfter ?? 2000);
|
|
201
|
+
timer.unref();
|
|
202
|
+
return Promise.resolve();
|
|
203
|
+
}
|
|
163
204
|
/** What the person picked in a menu, as structures where it applies. */
|
|
164
205
|
#readValues(ctx, raw, type, data) {
|
|
165
206
|
const values = resolveValues(ctx, raw, type, data.values ?? [], data.resolved ?? {});
|
|
@@ -67,8 +67,11 @@ export interface StringSelectConfig<Options extends SelectOptions, Data extends
|
|
|
67
67
|
* shown to what your code receives, or a list of `RichOption`.
|
|
68
68
|
*/
|
|
69
69
|
options: Options;
|
|
70
|
-
/**
|
|
71
|
-
|
|
70
|
+
/**
|
|
71
|
+
* What to do when something is picked. Leave it out for a menu that is
|
|
72
|
+
* only waited for (`message.waitForClick()`).
|
|
73
|
+
*/
|
|
74
|
+
run?: (context: SelectContext<OptionValue<Options>, Data, Where>) => unknown;
|
|
72
75
|
}
|
|
73
76
|
/** A menu of users, roles, users and roles, or channels of the server. */
|
|
74
77
|
export interface EntitySelectConfig<Type extends EntitySelectType, Data extends DataShape = {}, Where extends ComponentWhere = 'guild'> extends SelectConfigBase<Data, Where> {
|
|
@@ -76,8 +79,11 @@ export interface EntitySelectConfig<Type extends EntitySelectType, Data extends
|
|
|
76
79
|
type: Type;
|
|
77
80
|
/** For a menu of channels: the kinds of channels listed. All by default. */
|
|
78
81
|
channelTypes?: readonly ChannelType[];
|
|
79
|
-
/**
|
|
80
|
-
|
|
82
|
+
/**
|
|
83
|
+
* What to do when something is picked. Leave it out for a menu that is
|
|
84
|
+
* only waited for (`message.waitForClick()`).
|
|
85
|
+
*/
|
|
86
|
+
run?: (context: SelectContext<EntityValues[Type], Data, Where>) => unknown;
|
|
81
87
|
}
|
|
82
88
|
/** What a select file gives to `select()`: a menu of texts, or of things of the server. */
|
|
83
89
|
export type SelectConfig<Options extends SelectOptions = SelectOptions, Type extends EntitySelectType = EntitySelectType, Data extends DataShape = {}, Where extends ComponentWhere = 'guild'> = StringSelectConfig<Options, Data, Where> | EntitySelectConfig<Type, Data, Where>;
|
|
@@ -126,7 +132,8 @@ export interface LoadedSelect {
|
|
|
126
132
|
readonly where: ComponentWhere;
|
|
127
133
|
readonly who: 'everyone' | 'author';
|
|
128
134
|
readonly ephemeral: boolean;
|
|
129
|
-
|
|
135
|
+
/** `undefined` for a menu that is only waited for. */
|
|
136
|
+
readonly run: ((context: SelectContext<unknown, DataShape, ComponentWhere>) => unknown) | undefined;
|
|
130
137
|
}
|
|
131
138
|
/** Checks `min` and `max` of a menu, a group of checkboxes or a file upload. */
|
|
132
139
|
export declare function readRange(what: string, given: {
|
package/dist/core/bot.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { Gateway, type GatewayOptions } from '../gateway/gateway.js';
|
|
|
5
5
|
import type { ShardEvent } from '../gateway/shard.js';
|
|
6
6
|
import { type RestOptions } from '../rest/rest.js';
|
|
7
7
|
import type { Context } from '../structures/context.js';
|
|
8
|
+
import { type Waiters } from '../waiting/waiters.js';
|
|
8
9
|
/** What a dispatched event is, besides its data. */
|
|
9
10
|
export interface DispatchInfo {
|
|
10
11
|
shardId: number;
|
|
@@ -71,11 +72,17 @@ export interface BotOptions {
|
|
|
71
72
|
* (an outage of Discord). Default: 15000.
|
|
72
73
|
*/
|
|
73
74
|
guildsTimeout?: number;
|
|
75
|
+
/**
|
|
76
|
+
* What the files wait for (`channel.waitForMessage()`...): in memory by
|
|
77
|
+
* default.
|
|
78
|
+
*/
|
|
79
|
+
waiters?: Waiters;
|
|
74
80
|
/** Only tests change these. */
|
|
75
81
|
rest?: Partial<RestOptions>;
|
|
76
82
|
gateway?: Pick<GatewayOptions, 'backoff' | 'identifyInterval'>;
|
|
77
83
|
voice?: {
|
|
78
84
|
secure?: boolean;
|
|
85
|
+
unavailable?: string;
|
|
79
86
|
};
|
|
80
87
|
}
|
|
81
88
|
export interface Bot {
|
package/dist/core/bot.js
CHANGED
|
@@ -9,6 +9,34 @@ import { createContext } from '../structures/entities.js';
|
|
|
9
9
|
import { GatewayOpcode } from '../discord/codes.js';
|
|
10
10
|
import { GatewayIntent } from '../discord/intents.js';
|
|
11
11
|
import { VoiceManager } from '../voice/connection.js';
|
|
12
|
+
import { MemoryWaiters } from '../waiting/waiters.js';
|
|
13
|
+
import { GetUser } from '../discord/endpoints.js';
|
|
14
|
+
import { toCamelCase } from '../util/case.js';
|
|
15
|
+
/**
|
|
16
|
+
* Gives a reaction to what waits for one, with who reacted: the member
|
|
17
|
+
* Discord sends in a server, else the user the bot knows, else asked.
|
|
18
|
+
* @see https://docs.discord.com/developers/events/gateway-events#message-reaction-add
|
|
19
|
+
*/
|
|
20
|
+
function offerReaction(ctx, data) {
|
|
21
|
+
const member = data.member && data.guild_id
|
|
22
|
+
? ctx.entities.member(data.guild_id, data.member)
|
|
23
|
+
: null;
|
|
24
|
+
const known = member?.user ?? ctx.cache.users.get(data.user_id);
|
|
25
|
+
const user = known
|
|
26
|
+
? Promise.resolve(known)
|
|
27
|
+
: ctx.rest
|
|
28
|
+
.request(GetUser, [data.user_id])
|
|
29
|
+
.then(raw => ctx.entities.user(raw));
|
|
30
|
+
user
|
|
31
|
+
.then(user => ctx.waiters?.offer('reaction', {
|
|
32
|
+
messageId: data.message_id,
|
|
33
|
+
emoji: toCamelCase(data.emoji),
|
|
34
|
+
user,
|
|
35
|
+
member,
|
|
36
|
+
}))
|
|
37
|
+
// Who reacted could not be known: nothing to give.
|
|
38
|
+
.catch(() => { });
|
|
39
|
+
}
|
|
12
40
|
/** The server a gateway event belongs to, when it belongs to one. */
|
|
13
41
|
function guildIdOf(event, data) {
|
|
14
42
|
if (typeof data !== 'object' || data === null)
|
|
@@ -38,6 +66,8 @@ export function createBot(options) {
|
|
|
38
66
|
...options.rest,
|
|
39
67
|
});
|
|
40
68
|
const ctx = createContext({ rest, cache: new Cache(options.cache) });
|
|
69
|
+
const waiters = options.waiters ?? new MemoryWaiters();
|
|
70
|
+
ctx.waiters = waiters;
|
|
41
71
|
/** Servers announced by Ready whose data has not come yet, per shard. */
|
|
42
72
|
const pending = new Map();
|
|
43
73
|
let guildsReady = null;
|
|
@@ -129,6 +159,15 @@ export function createBot(options) {
|
|
|
129
159
|
else if (event === 'VOICE_SERVER_UPDATE') {
|
|
130
160
|
voice.onVoiceServer(data);
|
|
131
161
|
}
|
|
162
|
+
// What the files wait for: built only when a wait wants it.
|
|
163
|
+
if (event === 'MESSAGE_CREATE' && waiters.wants('message')) {
|
|
164
|
+
const message = data;
|
|
165
|
+
waiters.offer('message', ctx.entities.message(message));
|
|
166
|
+
}
|
|
167
|
+
else if (event === 'MESSAGE_REACTION_ADD' &&
|
|
168
|
+
waiters.wants('reaction')) {
|
|
169
|
+
offerReaction(ctx, data);
|
|
170
|
+
}
|
|
132
171
|
options.onDispatch?.(event, data, { shardId, joined, before });
|
|
133
172
|
if (awaited)
|
|
134
173
|
checkGuilds();
|
|
@@ -143,6 +182,9 @@ export function createBot(options) {
|
|
|
143
182
|
...(options.voice?.secure === undefined
|
|
144
183
|
? {}
|
|
145
184
|
: { secure: options.voice.secure }),
|
|
185
|
+
...(options.voice?.unavailable === undefined
|
|
186
|
+
? {}
|
|
187
|
+
: { unavailable: options.voice.unavailable }),
|
|
146
188
|
});
|
|
147
189
|
ctx.voice = voice;
|
|
148
190
|
return {
|
|
@@ -167,6 +209,8 @@ export function createBot(options) {
|
|
|
167
209
|
clearTimeout(guildsTimer);
|
|
168
210
|
guildsTimer = null;
|
|
169
211
|
voice.closeAll();
|
|
212
|
+
// What the files waited for will not come: they go on with nothing.
|
|
213
|
+
waiters.close();
|
|
170
214
|
await gateway.close();
|
|
171
215
|
},
|
|
172
216
|
setPresence: presence => gateway.setPresence(presence),
|
package/dist/discord/api.d.ts
CHANGED
|
@@ -74,6 +74,8 @@ export declare const Limits: {
|
|
|
74
74
|
readonly CustomId: 100;
|
|
75
75
|
/** @see https://docs.discord.com/developers/components/reference#component-object */
|
|
76
76
|
readonly MessageComponents: 40;
|
|
77
|
+
/** @see https://docs.discord.com/developers/components/reference#legacy-message-component-behavior */
|
|
78
|
+
readonly MessageActionRows: 5;
|
|
77
79
|
/** @see https://docs.discord.com/developers/components/reference#action-row */
|
|
78
80
|
readonly ActionRowButtons: 5;
|
|
79
81
|
/** @see https://docs.discord.com/developers/components/reference#button-button-structure */
|
package/dist/discord/api.js
CHANGED
|
@@ -77,6 +77,8 @@ export const Limits = {
|
|
|
77
77
|
CustomId: 100,
|
|
78
78
|
/** @see https://docs.discord.com/developers/components/reference#component-object */
|
|
79
79
|
MessageComponents: 40,
|
|
80
|
+
/** @see https://docs.discord.com/developers/components/reference#legacy-message-component-behavior */
|
|
81
|
+
MessageActionRows: 5,
|
|
80
82
|
/** @see https://docs.discord.com/developers/components/reference#action-row */
|
|
81
83
|
ActionRowButtons: 5,
|
|
82
84
|
/** @see https://docs.discord.com/developers/components/reference#button-button-structure */
|
|
@@ -12,11 +12,11 @@ export declare const ALL_PERMISSIONS: bigint;
|
|
|
12
12
|
*/
|
|
13
13
|
export declare const ELEVATED_PERMISSIONS: readonly PermissionName[];
|
|
14
14
|
/**
|
|
15
|
-
* A
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* @see https://docs.discord.com/developers/topics/permissions
|
|
15
|
+
* A permission as people read it: `BanMembers` is "Ban Members". For what
|
|
16
|
+
* the bot says to the people of a server, never to the developer, who
|
|
17
|
+
* writes the names as they are.
|
|
19
18
|
*/
|
|
19
|
+
export declare function permissionWords(name: PermissionName): string;
|
|
20
20
|
export declare class Permissions {
|
|
21
21
|
/** The permissions as one number, each bit being a permission. */
|
|
22
22
|
readonly bits: bigint;
|
|
@@ -42,6 +42,19 @@ function resolve(permission) {
|
|
|
42
42
|
* regular number.
|
|
43
43
|
* @see https://docs.discord.com/developers/topics/permissions
|
|
44
44
|
*/
|
|
45
|
+
/** Letters that are said one by one in the name of a permission. */
|
|
46
|
+
const ACRONYMS = { Tts: 'TTS', Vad: 'VAD' };
|
|
47
|
+
/**
|
|
48
|
+
* A permission as people read it: `BanMembers` is "Ban Members". For what
|
|
49
|
+
* the bot says to the people of a server, never to the developer, who
|
|
50
|
+
* writes the names as they are.
|
|
51
|
+
*/
|
|
52
|
+
export function permissionWords(name) {
|
|
53
|
+
return name
|
|
54
|
+
.split(/(?=[A-Z])/)
|
|
55
|
+
.map(word => ACRONYMS[word] ?? word)
|
|
56
|
+
.join(' ');
|
|
57
|
+
}
|
|
45
58
|
export class Permissions {
|
|
46
59
|
/** The permissions as one number, each bit being a permission. */
|
|
47
60
|
bits;
|