privage.js 0.21.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/LICENSE +21 -0
- package/README.md +503 -0
- package/dist/Client.d.ts +192 -0
- package/dist/Client.js +904 -0
- package/dist/attachments.d.ts +41 -0
- package/dist/attachments.js +63 -0
- package/dist/collectors.d.ts +41 -0
- package/dist/collectors.js +52 -0
- package/dist/commands.d.ts +108 -0
- package/dist/commands.js +216 -0
- package/dist/compat.d.ts +72 -0
- package/dist/compat.js +75 -0
- package/dist/components.d.ts +137 -0
- package/dist/components.js +226 -0
- package/dist/embeds.d.ts +97 -0
- package/dist/embeds.js +125 -0
- package/dist/errors.d.ts +32 -0
- package/dist/errors.js +48 -0
- package/dist/formatters.d.ts +16 -0
- package/dist/formatters.js +43 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.js +101 -0
- package/dist/intents.d.ts +28 -0
- package/dist/intents.js +33 -0
- package/dist/modals.d.ts +96 -0
- package/dist/modals.js +162 -0
- package/dist/rest.d.ts +208 -0
- package/dist/rest.js +356 -0
- package/dist/structures/Channel.d.ts +25 -0
- package/dist/structures/Channel.js +34 -0
- package/dist/structures/Interaction.d.ts +149 -0
- package/dist/structures/Interaction.js +170 -0
- package/dist/structures/Member.d.ts +35 -0
- package/dist/structures/Member.js +55 -0
- package/dist/structures/Message.d.ts +181 -0
- package/dist/structures/Message.js +191 -0
- package/dist/structures/Role.d.ts +17 -0
- package/dist/structures/Role.js +20 -0
- package/dist/structures/Server.d.ts +29 -0
- package/dist/structures/Server.js +36 -0
- package/dist/structures/ServerChannels.d.ts +24 -0
- package/dist/structures/ServerChannels.js +50 -0
- package/dist/types.d.ts +195 -0
- package/dist/types.js +2 -0
- package/package.json +49 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/** A file source for message attachments: a filesystem path, raw bytes, or a Blob. */
|
|
2
|
+
export type AttachmentSource = string | Buffer | Uint8Array | Blob;
|
|
3
|
+
/**
|
|
4
|
+
* Fluent attachment wrapper (discord.js-style). Wraps a file source with the
|
|
5
|
+
* filename (and optional content type) it should carry in the upload.
|
|
6
|
+
*
|
|
7
|
+
* Attachments upload as multipart; the server allows up to 10 per message
|
|
8
|
+
* (25 MB each by default) and blocks executable file types. Note the v1
|
|
9
|
+
* limitation: a message can carry attachments OR embeds, not both.
|
|
10
|
+
*/
|
|
11
|
+
export declare class AttachmentBuilder {
|
|
12
|
+
private readonly source;
|
|
13
|
+
private name?;
|
|
14
|
+
private contentType?;
|
|
15
|
+
private spoilerFlag?;
|
|
16
|
+
constructor(source: AttachmentSource, options?: {
|
|
17
|
+
name?: string;
|
|
18
|
+
contentType?: string;
|
|
19
|
+
spoiler?: boolean;
|
|
20
|
+
});
|
|
21
|
+
/** Filename shown in the client (defaults to the path's basename, or 'file'). */
|
|
22
|
+
setName(name: string): this;
|
|
23
|
+
/** Explicit MIME type for the upload part. */
|
|
24
|
+
setContentType(contentType: string): this;
|
|
25
|
+
/** Mark the attachment as a spoiler — blurred in the client until revealed. */
|
|
26
|
+
setSpoiler(spoiler?: boolean): this;
|
|
27
|
+
/** Whether this attachment uploads as a spoiler (per-file flag; filenames are never involved). */
|
|
28
|
+
get spoiler(): boolean;
|
|
29
|
+
/** Resolve to the `{ blob, name, spoiler }` triple appended to the multipart body. */
|
|
30
|
+
resolve(): Promise<{
|
|
31
|
+
blob: Blob;
|
|
32
|
+
name: string;
|
|
33
|
+
spoiler: boolean;
|
|
34
|
+
}>;
|
|
35
|
+
}
|
|
36
|
+
/** Normalize a `files` array (builders or raw sources) to upload parts. */
|
|
37
|
+
export declare function resolveFiles(files: (AttachmentBuilder | AttachmentSource)[]): Promise<{
|
|
38
|
+
blob: Blob;
|
|
39
|
+
name: string;
|
|
40
|
+
spoiler: boolean;
|
|
41
|
+
}[]>;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AttachmentBuilder = void 0;
|
|
4
|
+
exports.resolveFiles = resolveFiles;
|
|
5
|
+
const promises_1 = require("fs/promises");
|
|
6
|
+
const path_1 = require("path");
|
|
7
|
+
/**
|
|
8
|
+
* Fluent attachment wrapper (discord.js-style). Wraps a file source with the
|
|
9
|
+
* filename (and optional content type) it should carry in the upload.
|
|
10
|
+
*
|
|
11
|
+
* Attachments upload as multipart; the server allows up to 10 per message
|
|
12
|
+
* (25 MB each by default) and blocks executable file types. Note the v1
|
|
13
|
+
* limitation: a message can carry attachments OR embeds, not both.
|
|
14
|
+
*/
|
|
15
|
+
class AttachmentBuilder {
|
|
16
|
+
constructor(source, options = {}) {
|
|
17
|
+
this.source = source;
|
|
18
|
+
this.name = options.name;
|
|
19
|
+
this.contentType = options.contentType;
|
|
20
|
+
this.spoilerFlag = options.spoiler;
|
|
21
|
+
}
|
|
22
|
+
/** Filename shown in the client (defaults to the path's basename, or 'file'). */
|
|
23
|
+
setName(name) {
|
|
24
|
+
this.name = name;
|
|
25
|
+
return this;
|
|
26
|
+
}
|
|
27
|
+
/** Explicit MIME type for the upload part. */
|
|
28
|
+
setContentType(contentType) {
|
|
29
|
+
this.contentType = contentType;
|
|
30
|
+
return this;
|
|
31
|
+
}
|
|
32
|
+
/** Mark the attachment as a spoiler — blurred in the client until revealed. */
|
|
33
|
+
setSpoiler(spoiler = true) {
|
|
34
|
+
this.spoilerFlag = spoiler;
|
|
35
|
+
return this;
|
|
36
|
+
}
|
|
37
|
+
/** Whether this attachment uploads as a spoiler (per-file flag; filenames are never involved). */
|
|
38
|
+
get spoiler() {
|
|
39
|
+
return this.spoilerFlag ?? false;
|
|
40
|
+
}
|
|
41
|
+
/** Resolve to the `{ blob, name, spoiler }` triple appended to the multipart body. */
|
|
42
|
+
async resolve() {
|
|
43
|
+
const name = this.name ?? (typeof this.source === 'string' ? (0, path_1.basename)(this.source) : 'file');
|
|
44
|
+
const spoiler = this.spoiler;
|
|
45
|
+
if (this.source instanceof Blob) {
|
|
46
|
+
const blob = this.contentType
|
|
47
|
+
? new Blob([await this.source.arrayBuffer()], { type: this.contentType })
|
|
48
|
+
: this.source;
|
|
49
|
+
return { blob, name, spoiler };
|
|
50
|
+
}
|
|
51
|
+
const bytes = typeof this.source === 'string' ? await (0, promises_1.readFile)(this.source) : this.source;
|
|
52
|
+
return {
|
|
53
|
+
blob: new Blob([bytes], this.contentType ? { type: this.contentType } : undefined),
|
|
54
|
+
name,
|
|
55
|
+
spoiler,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
exports.AttachmentBuilder = AttachmentBuilder;
|
|
60
|
+
/** Normalize a `files` array (builders or raw sources) to upload parts. */
|
|
61
|
+
function resolveFiles(files) {
|
|
62
|
+
return Promise.all(files.map((f) => (f instanceof AttachmentBuilder ? f : new AttachmentBuilder(f)).resolve()));
|
|
63
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
import type { Message } from './structures/Message';
|
|
3
|
+
import type { Client } from './Client';
|
|
4
|
+
export interface MessageCollectorOptions {
|
|
5
|
+
/** Only collect messages from this channel. */
|
|
6
|
+
channelId?: string;
|
|
7
|
+
/** Predicate a message must pass to be collected. */
|
|
8
|
+
filter?: (message: Message) => boolean;
|
|
9
|
+
/** Stop after collecting this many messages. */
|
|
10
|
+
max?: number;
|
|
11
|
+
/** Stop after this long (default 60_000 ms). */
|
|
12
|
+
timeMs?: number;
|
|
13
|
+
}
|
|
14
|
+
export type CollectorEndReason = 'limit' | 'time' | 'user';
|
|
15
|
+
export interface MessageCollector {
|
|
16
|
+
on(event: 'collect', listener: (message: Message) => void): this;
|
|
17
|
+
on(event: 'end', listener: (collected: Message[], reason: CollectorEndReason) => void): this;
|
|
18
|
+
once(event: 'collect', listener: (message: Message) => void): this;
|
|
19
|
+
once(event: 'end', listener: (collected: Message[], reason: CollectorEndReason) => void): this;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Collects `messageCreate` events matching a filter until a count or time
|
|
23
|
+
* limit — the standard building block for confirmations and wizards.
|
|
24
|
+
* Unsubscribes from the client automatically on end.
|
|
25
|
+
*/
|
|
26
|
+
export declare class MessageCollector extends EventEmitter {
|
|
27
|
+
private readonly client;
|
|
28
|
+
private readonly options;
|
|
29
|
+
readonly collected: Message[];
|
|
30
|
+
private ended;
|
|
31
|
+
private readonly timer;
|
|
32
|
+
private readonly listener;
|
|
33
|
+
constructor(client: Client, options?: MessageCollectorOptions);
|
|
34
|
+
/** End the collector; emits `end` with everything collected and the reason. */
|
|
35
|
+
stop(reason?: CollectorEndReason): void;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Promise form: resolves with the collected messages once `max` is reached
|
|
39
|
+
* or time runs out (resolves with whatever was collected — check `.length`).
|
|
40
|
+
*/
|
|
41
|
+
export declare function awaitMessages(client: Client, options?: MessageCollectorOptions): Promise<Message[]>;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MessageCollector = void 0;
|
|
4
|
+
exports.awaitMessages = awaitMessages;
|
|
5
|
+
const events_1 = require("events");
|
|
6
|
+
/**
|
|
7
|
+
* Collects `messageCreate` events matching a filter until a count or time
|
|
8
|
+
* limit — the standard building block for confirmations and wizards.
|
|
9
|
+
* Unsubscribes from the client automatically on end.
|
|
10
|
+
*/
|
|
11
|
+
class MessageCollector extends events_1.EventEmitter {
|
|
12
|
+
constructor(client, options = {}) {
|
|
13
|
+
super();
|
|
14
|
+
this.client = client;
|
|
15
|
+
this.options = options;
|
|
16
|
+
this.collected = [];
|
|
17
|
+
this.ended = false;
|
|
18
|
+
this.listener = (message) => {
|
|
19
|
+
if (this.options.channelId && message.channelId !== this.options.channelId)
|
|
20
|
+
return;
|
|
21
|
+
if (this.options.filter && !this.options.filter(message))
|
|
22
|
+
return;
|
|
23
|
+
this.collected.push(message);
|
|
24
|
+
this.emit('collect', message);
|
|
25
|
+
if (this.options.max && this.collected.length >= this.options.max)
|
|
26
|
+
this.stop('limit');
|
|
27
|
+
};
|
|
28
|
+
client.on('messageCreate', this.listener);
|
|
29
|
+
this.timer = setTimeout(() => this.stop('time'), this.options.timeMs ?? 60000);
|
|
30
|
+
this.timer.unref?.();
|
|
31
|
+
}
|
|
32
|
+
/** End the collector; emits `end` with everything collected and the reason. */
|
|
33
|
+
stop(reason = 'user') {
|
|
34
|
+
if (this.ended)
|
|
35
|
+
return;
|
|
36
|
+
this.ended = true;
|
|
37
|
+
clearTimeout(this.timer);
|
|
38
|
+
this.client.off('messageCreate', this.listener);
|
|
39
|
+
this.emit('end', this.collected, reason);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
exports.MessageCollector = MessageCollector;
|
|
43
|
+
/**
|
|
44
|
+
* Promise form: resolves with the collected messages once `max` is reached
|
|
45
|
+
* or time runs out (resolves with whatever was collected — check `.length`).
|
|
46
|
+
*/
|
|
47
|
+
function awaitMessages(client, options = {}) {
|
|
48
|
+
return new Promise((resolve) => {
|
|
49
|
+
const collector = new MessageCollector(client, { max: 1, ...options });
|
|
50
|
+
collector.on('end', (collected) => resolve(collected));
|
|
51
|
+
});
|
|
52
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slash-command registry (interactions v2 §3). A bot manages ONLY its own
|
|
3
|
+
* commands — `client.commands.set([...])` bulk-overwrites the whole set
|
|
4
|
+
* (whole-or-400 server-side; the same grammar is validated here first so
|
|
5
|
+
* mistakes fail loud and local), `client.commands.fetch()` lists it.
|
|
6
|
+
*
|
|
7
|
+
* Invocations arrive as `interaction` events with `kind: 'command'` —
|
|
8
|
+
* see `interaction.command` / `interaction.command.getOption(name)`.
|
|
9
|
+
*/
|
|
10
|
+
import type { Client } from './Client';
|
|
11
|
+
import type { CommandOptionType } from './structures/Interaction';
|
|
12
|
+
/** Server-enforced registry limits (mirrored client-side). */
|
|
13
|
+
export declare const CommandLimits: {
|
|
14
|
+
/** Commands per bot (one `set()` payload). */
|
|
15
|
+
readonly Commands: 50;
|
|
16
|
+
/** `^[a-z0-9_-]{1,32}$` for command and option names. */
|
|
17
|
+
readonly NamePattern: RegExp;
|
|
18
|
+
readonly Description: 100;
|
|
19
|
+
readonly Options: 10;
|
|
20
|
+
readonly Choices: 25;
|
|
21
|
+
readonly ChoiceName: 100;
|
|
22
|
+
readonly ChoiceStringValue: 100;
|
|
23
|
+
};
|
|
24
|
+
/** A pre-baked choice for a string/integer/number option. */
|
|
25
|
+
export interface CommandChoice {
|
|
26
|
+
/** ≤100 chars — what the invoker sees. */
|
|
27
|
+
name: string;
|
|
28
|
+
/** Must match the option type: string (≤100 chars) or number. */
|
|
29
|
+
value: string | number;
|
|
30
|
+
}
|
|
31
|
+
export interface CommandOptionData {
|
|
32
|
+
type: CommandOptionType;
|
|
33
|
+
/** `^[a-z0-9_-]{1,32}$`, unique within the command. */
|
|
34
|
+
name: string;
|
|
35
|
+
/** Required, ≤100 chars. */
|
|
36
|
+
description: string;
|
|
37
|
+
/** Required options must precede optional ones. Server default: false. */
|
|
38
|
+
required?: boolean;
|
|
39
|
+
/** string/integer/number options only, 1–25. */
|
|
40
|
+
choices?: CommandChoice[];
|
|
41
|
+
}
|
|
42
|
+
export interface BotCommandData {
|
|
43
|
+
/** `^[a-z0-9_-]{1,32}$`, unique per bot. */
|
|
44
|
+
name: string;
|
|
45
|
+
/** Required, ≤100 chars. */
|
|
46
|
+
description: string;
|
|
47
|
+
/** ≤10 options. */
|
|
48
|
+
options?: CommandOptionData[];
|
|
49
|
+
}
|
|
50
|
+
/** A registered command, as the server returns it. */
|
|
51
|
+
export interface BotCommand extends BotCommandData {
|
|
52
|
+
id: string;
|
|
53
|
+
options: CommandOptionData[];
|
|
54
|
+
}
|
|
55
|
+
/** Mirror of the server's registry grammar (whole-or-400) — throws RangeError with the field path. */
|
|
56
|
+
export declare function validateCommands(commands: BotCommandData[]): void;
|
|
57
|
+
/** Option builder used by {@link SlashCommandBuilder} — mirrors discord.js's shape. */
|
|
58
|
+
export declare class SlashCommandOptionBuilder {
|
|
59
|
+
private readonly data;
|
|
60
|
+
constructor(type: CommandOptionType);
|
|
61
|
+
/** `^[a-z0-9_-]{1,32}$`, unique within the command. */
|
|
62
|
+
setName(name: string): this;
|
|
63
|
+
/** Required, ≤100 chars. */
|
|
64
|
+
setDescription(description: string): this;
|
|
65
|
+
/** Required options must precede optional ones. */
|
|
66
|
+
setRequired(required?: boolean): this;
|
|
67
|
+
/** string/integer/number options only (1–25 choices). */
|
|
68
|
+
addChoices(...choices: CommandChoice[]): this;
|
|
69
|
+
toJSON(): CommandOptionData;
|
|
70
|
+
}
|
|
71
|
+
type OptionInput = SlashCommandOptionBuilder | ((option: SlashCommandOptionBuilder) => SlashCommandOptionBuilder);
|
|
72
|
+
/**
|
|
73
|
+
* discord.js-style command builder — `new SlashCommandBuilder().setName(...)
|
|
74
|
+
* .addStringOption(o => o.setName(...))` ports verbatim and emits plain
|
|
75
|
+
* {@link BotCommandData}. Full validation happens in `client.commands.set()`
|
|
76
|
+
* (same grammar as the server, thrown as `RangeError` with the field path).
|
|
77
|
+
*/
|
|
78
|
+
export declare class SlashCommandBuilder {
|
|
79
|
+
private readonly data;
|
|
80
|
+
private readonly options;
|
|
81
|
+
/** `^[a-z0-9_-]{1,32}$`, unique per bot. */
|
|
82
|
+
setName(name: string): this;
|
|
83
|
+
/** Required, ≤100 chars. */
|
|
84
|
+
setDescription(description: string): this;
|
|
85
|
+
private addOption;
|
|
86
|
+
addStringOption(input: OptionInput): this;
|
|
87
|
+
addIntegerOption(input: OptionInput): this;
|
|
88
|
+
addNumberOption(input: OptionInput): this;
|
|
89
|
+
addBooleanOption(input: OptionInput): this;
|
|
90
|
+
addUserOption(input: OptionInput): this;
|
|
91
|
+
addChannelOption(input: OptionInput): this;
|
|
92
|
+
addRoleOption(input: OptionInput): this;
|
|
93
|
+
toJSON(): BotCommandData;
|
|
94
|
+
}
|
|
95
|
+
/** `client.commands` — the bot's slash-command registry. Available after `login()`. */
|
|
96
|
+
export declare class CommandsManager {
|
|
97
|
+
private readonly client;
|
|
98
|
+
constructor(client: Client);
|
|
99
|
+
/**
|
|
100
|
+
* Bulk-overwrite the bot's whole command set (≤50; `[]` clears it).
|
|
101
|
+
* Validated client-side against the registry grammar before the request —
|
|
102
|
+
* throws RangeError on the first violation. Rate limit: 5/60s.
|
|
103
|
+
*/
|
|
104
|
+
set(commands: (BotCommandData | SlashCommandBuilder)[]): Promise<BotCommand[]>;
|
|
105
|
+
/** List the bot's registered commands. */
|
|
106
|
+
fetch(): Promise<BotCommand[]>;
|
|
107
|
+
}
|
|
108
|
+
export {};
|
package/dist/commands.js
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CommandsManager = exports.SlashCommandBuilder = exports.SlashCommandOptionBuilder = exports.CommandLimits = void 0;
|
|
4
|
+
exports.validateCommands = validateCommands;
|
|
5
|
+
/** Server-enforced registry limits (mirrored client-side). */
|
|
6
|
+
exports.CommandLimits = {
|
|
7
|
+
/** Commands per bot (one `set()` payload). */
|
|
8
|
+
Commands: 50,
|
|
9
|
+
/** `^[a-z0-9_-]{1,32}$` for command and option names. */
|
|
10
|
+
NamePattern: /^[a-z0-9_-]{1,32}$/,
|
|
11
|
+
Description: 100,
|
|
12
|
+
Options: 10,
|
|
13
|
+
Choices: 25,
|
|
14
|
+
ChoiceName: 100,
|
|
15
|
+
ChoiceStringValue: 100,
|
|
16
|
+
};
|
|
17
|
+
const OPTION_TYPES = new Set(['string', 'integer', 'number', 'boolean', 'user', 'channel', 'role']);
|
|
18
|
+
const CHOICE_TYPES = new Set(['string', 'integer', 'number']);
|
|
19
|
+
function fail(message) {
|
|
20
|
+
throw new RangeError(message);
|
|
21
|
+
}
|
|
22
|
+
/** Mirror of the server's registry grammar (whole-or-400) — throws RangeError with the field path. */
|
|
23
|
+
function validateCommands(commands) {
|
|
24
|
+
if (!Array.isArray(commands))
|
|
25
|
+
fail('commands must be an array.');
|
|
26
|
+
if (commands.length > exports.CommandLimits.Commands) {
|
|
27
|
+
fail(`At most ${exports.CommandLimits.Commands} commands per bot (got ${commands.length}).`);
|
|
28
|
+
}
|
|
29
|
+
const seenNames = new Set();
|
|
30
|
+
commands.forEach((command, cmdIdx) => {
|
|
31
|
+
const path = `commands[${cmdIdx}]`;
|
|
32
|
+
if (!command || typeof command !== 'object')
|
|
33
|
+
fail(`${path} must be a command object.`);
|
|
34
|
+
if (typeof command.name !== 'string' || !exports.CommandLimits.NamePattern.test(command.name)) {
|
|
35
|
+
fail(`${path}.name must match ^[a-z0-9_-]{1,32}$.`);
|
|
36
|
+
}
|
|
37
|
+
if (seenNames.has(command.name))
|
|
38
|
+
fail(`${path}.name "${command.name}" is duplicated — command names must be unique per bot.`);
|
|
39
|
+
seenNames.add(command.name);
|
|
40
|
+
if (typeof command.description !== 'string' || command.description.length === 0 || command.description.length > exports.CommandLimits.Description) {
|
|
41
|
+
fail(`${path}.description is required (≤${exports.CommandLimits.Description} characters).`);
|
|
42
|
+
}
|
|
43
|
+
if (command.options === undefined)
|
|
44
|
+
return;
|
|
45
|
+
if (!Array.isArray(command.options) || command.options.length > exports.CommandLimits.Options) {
|
|
46
|
+
fail(`${path}.options must be an array of at most ${exports.CommandLimits.Options} options.`);
|
|
47
|
+
}
|
|
48
|
+
const seenOptions = new Set();
|
|
49
|
+
let sawOptional = false;
|
|
50
|
+
command.options.forEach((option, optIdx) => {
|
|
51
|
+
const optPath = `${path}.options[${optIdx}]`;
|
|
52
|
+
if (!option || typeof option !== 'object')
|
|
53
|
+
fail(`${optPath} must be an option object.`);
|
|
54
|
+
if (typeof option.type !== 'string' || !OPTION_TYPES.has(option.type)) {
|
|
55
|
+
fail(`${optPath}.type must be one of: string, integer, number, boolean, user, channel, role.`);
|
|
56
|
+
}
|
|
57
|
+
if (typeof option.name !== 'string' || !exports.CommandLimits.NamePattern.test(option.name)) {
|
|
58
|
+
fail(`${optPath}.name must match ^[a-z0-9_-]{1,32}$.`);
|
|
59
|
+
}
|
|
60
|
+
if (seenOptions.has(option.name))
|
|
61
|
+
fail(`${optPath}.name "${option.name}" is duplicated within the command.`);
|
|
62
|
+
seenOptions.add(option.name);
|
|
63
|
+
if (typeof option.description !== 'string' || option.description.length === 0 || option.description.length > exports.CommandLimits.Description) {
|
|
64
|
+
fail(`${optPath}.description is required (≤${exports.CommandLimits.Description} characters).`);
|
|
65
|
+
}
|
|
66
|
+
if (option.required !== true) {
|
|
67
|
+
sawOptional = true;
|
|
68
|
+
}
|
|
69
|
+
else if (sawOptional) {
|
|
70
|
+
fail(`${path}.options: required options must precede optional ones.`);
|
|
71
|
+
}
|
|
72
|
+
if (option.choices === undefined)
|
|
73
|
+
return;
|
|
74
|
+
if (!CHOICE_TYPES.has(option.type))
|
|
75
|
+
fail(`${optPath}.choices are only allowed for string, integer, and number options.`);
|
|
76
|
+
if (!Array.isArray(option.choices) || option.choices.length === 0 || option.choices.length > exports.CommandLimits.Choices) {
|
|
77
|
+
fail(`${optPath}.choices must be an array of 1 to ${exports.CommandLimits.Choices} choices.`);
|
|
78
|
+
}
|
|
79
|
+
const seenChoiceNames = new Set();
|
|
80
|
+
option.choices.forEach((choice, choiceIdx) => {
|
|
81
|
+
const choicePath = `${optPath}.choices[${choiceIdx}]`;
|
|
82
|
+
if (!choice || typeof choice !== 'object')
|
|
83
|
+
fail(`${choicePath} must be a choice object.`);
|
|
84
|
+
if (typeof choice.name !== 'string' || choice.name.length === 0 || choice.name.length > exports.CommandLimits.ChoiceName) {
|
|
85
|
+
fail(`${choicePath}.name is required (≤${exports.CommandLimits.ChoiceName} characters).`);
|
|
86
|
+
}
|
|
87
|
+
if (seenChoiceNames.has(choice.name))
|
|
88
|
+
fail(`${choicePath}.name "${choice.name}" is duplicated within the option.`);
|
|
89
|
+
seenChoiceNames.add(choice.name);
|
|
90
|
+
if (option.type === 'string') {
|
|
91
|
+
if (typeof choice.value !== 'string' || choice.value.length === 0 || choice.value.length > exports.CommandLimits.ChoiceStringValue) {
|
|
92
|
+
fail(`${choicePath}.value must be a string of at most ${exports.CommandLimits.ChoiceStringValue} characters for string options.`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
else if (option.type === 'integer') {
|
|
96
|
+
if (typeof choice.value !== 'number' || !Number.isSafeInteger(choice.value)) {
|
|
97
|
+
fail(`${choicePath}.value must be an integer for integer options.`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
else if (typeof choice.value !== 'number' || !Number.isFinite(choice.value)) {
|
|
101
|
+
fail(`${choicePath}.value must be a number for number options.`);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
/** Option builder used by {@link SlashCommandBuilder} — mirrors discord.js's shape. */
|
|
108
|
+
class SlashCommandOptionBuilder {
|
|
109
|
+
constructor(type) {
|
|
110
|
+
this.data = { type };
|
|
111
|
+
}
|
|
112
|
+
/** `^[a-z0-9_-]{1,32}$`, unique within the command. */
|
|
113
|
+
setName(name) {
|
|
114
|
+
this.data.name = name;
|
|
115
|
+
return this;
|
|
116
|
+
}
|
|
117
|
+
/** Required, ≤100 chars. */
|
|
118
|
+
setDescription(description) {
|
|
119
|
+
this.data.description = description;
|
|
120
|
+
return this;
|
|
121
|
+
}
|
|
122
|
+
/** Required options must precede optional ones. */
|
|
123
|
+
setRequired(required = true) {
|
|
124
|
+
this.data.required = required;
|
|
125
|
+
return this;
|
|
126
|
+
}
|
|
127
|
+
/** string/integer/number options only (1–25 choices). */
|
|
128
|
+
addChoices(...choices) {
|
|
129
|
+
this.data.choices = [...(this.data.choices ?? []), ...choices];
|
|
130
|
+
return this;
|
|
131
|
+
}
|
|
132
|
+
toJSON() {
|
|
133
|
+
return { ...this.data };
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
exports.SlashCommandOptionBuilder = SlashCommandOptionBuilder;
|
|
137
|
+
/**
|
|
138
|
+
* discord.js-style command builder — `new SlashCommandBuilder().setName(...)
|
|
139
|
+
* .addStringOption(o => o.setName(...))` ports verbatim and emits plain
|
|
140
|
+
* {@link BotCommandData}. Full validation happens in `client.commands.set()`
|
|
141
|
+
* (same grammar as the server, thrown as `RangeError` with the field path).
|
|
142
|
+
*/
|
|
143
|
+
class SlashCommandBuilder {
|
|
144
|
+
constructor() {
|
|
145
|
+
this.data = {};
|
|
146
|
+
this.options = [];
|
|
147
|
+
}
|
|
148
|
+
/** `^[a-z0-9_-]{1,32}$`, unique per bot. */
|
|
149
|
+
setName(name) {
|
|
150
|
+
this.data.name = name;
|
|
151
|
+
return this;
|
|
152
|
+
}
|
|
153
|
+
/** Required, ≤100 chars. */
|
|
154
|
+
setDescription(description) {
|
|
155
|
+
this.data.description = description;
|
|
156
|
+
return this;
|
|
157
|
+
}
|
|
158
|
+
addOption(type, input) {
|
|
159
|
+
const builder = input instanceof SlashCommandOptionBuilder ? input : input(new SlashCommandOptionBuilder(type));
|
|
160
|
+
const option = builder.toJSON();
|
|
161
|
+
this.options.push({ ...option, type });
|
|
162
|
+
return this;
|
|
163
|
+
}
|
|
164
|
+
addStringOption(input) {
|
|
165
|
+
return this.addOption('string', input);
|
|
166
|
+
}
|
|
167
|
+
addIntegerOption(input) {
|
|
168
|
+
return this.addOption('integer', input);
|
|
169
|
+
}
|
|
170
|
+
addNumberOption(input) {
|
|
171
|
+
return this.addOption('number', input);
|
|
172
|
+
}
|
|
173
|
+
addBooleanOption(input) {
|
|
174
|
+
return this.addOption('boolean', input);
|
|
175
|
+
}
|
|
176
|
+
addUserOption(input) {
|
|
177
|
+
return this.addOption('user', input);
|
|
178
|
+
}
|
|
179
|
+
addChannelOption(input) {
|
|
180
|
+
return this.addOption('channel', input);
|
|
181
|
+
}
|
|
182
|
+
addRoleOption(input) {
|
|
183
|
+
return this.addOption('role', input);
|
|
184
|
+
}
|
|
185
|
+
toJSON() {
|
|
186
|
+
return {
|
|
187
|
+
name: this.data.name,
|
|
188
|
+
description: this.data.description,
|
|
189
|
+
...(this.options.length ? { options: this.options.map((o) => ({ ...o })) } : {}),
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
exports.SlashCommandBuilder = SlashCommandBuilder;
|
|
194
|
+
/** `client.commands` — the bot's slash-command registry. Available after `login()`. */
|
|
195
|
+
class CommandsManager {
|
|
196
|
+
constructor(client) {
|
|
197
|
+
this.client = client;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Bulk-overwrite the bot's whole command set (≤50; `[]` clears it).
|
|
201
|
+
* Validated client-side against the registry grammar before the request —
|
|
202
|
+
* throws RangeError on the first violation. Rate limit: 5/60s.
|
|
203
|
+
*/
|
|
204
|
+
async set(commands) {
|
|
205
|
+
const resolved = commands.map((c) => (c instanceof SlashCommandBuilder ? c.toJSON() : c));
|
|
206
|
+
validateCommands(resolved);
|
|
207
|
+
const res = await this.client.rest.setCommands(resolved);
|
|
208
|
+
return res?.commands ?? [];
|
|
209
|
+
}
|
|
210
|
+
/** List the bot's registered commands. */
|
|
211
|
+
async fetch() {
|
|
212
|
+
const res = await this.client.rest.fetchCommands();
|
|
213
|
+
return res?.commands ?? [];
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
exports.CommandsManager = CommandsManager;
|
package/dist/compat.d.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* discord.js event-name constants mapped to this SDK's events.
|
|
3
|
+
* `client.on(Events.MessageCreate, ...)` ports verbatim.
|
|
4
|
+
*/
|
|
5
|
+
export declare const Events: {
|
|
6
|
+
readonly ClientReady: "ready";
|
|
7
|
+
readonly MessageCreate: "messageCreate";
|
|
8
|
+
readonly MessageUpdate: "messageUpdate";
|
|
9
|
+
readonly MessageDelete: "messageDelete";
|
|
10
|
+
readonly MessageBulkDelete: "messageDeleteBulk";
|
|
11
|
+
readonly MessageReactionAdd: "reactionAdd";
|
|
12
|
+
readonly MessageReactionRemove: "reactionRemove";
|
|
13
|
+
readonly InteractionCreate: "interaction";
|
|
14
|
+
readonly GuildMemberAdd: "memberJoin";
|
|
15
|
+
readonly GuildMemberRemove: "memberLeave";
|
|
16
|
+
readonly GuildMemberUpdate: "memberUpdate";
|
|
17
|
+
readonly GuildBanAdd: "memberBan";
|
|
18
|
+
readonly GuildBanRemove: "memberUnban";
|
|
19
|
+
readonly ChannelCreate: "channelCreate";
|
|
20
|
+
readonly ChannelUpdate: "channelUpdate";
|
|
21
|
+
readonly ChannelDelete: "channelDelete";
|
|
22
|
+
readonly GuildRoleCreate: "roleCreate";
|
|
23
|
+
readonly GuildRoleUpdate: "roleUpdate";
|
|
24
|
+
readonly GuildRoleDelete: "roleDelete";
|
|
25
|
+
readonly TypingStart: "typingStart";
|
|
26
|
+
/** Recoverable transport failures — the SDK reconnects on its own. */
|
|
27
|
+
readonly Error: "connectionError";
|
|
28
|
+
readonly Warn: "warn";
|
|
29
|
+
readonly Debug: "debug";
|
|
30
|
+
};
|
|
31
|
+
/** The discord.js named color palette (same values), plus Privage brand colors. */
|
|
32
|
+
export declare const Colors: {
|
|
33
|
+
readonly Default: 0;
|
|
34
|
+
readonly White: 16777215;
|
|
35
|
+
readonly Aqua: 1752220;
|
|
36
|
+
readonly Green: 5763719;
|
|
37
|
+
readonly Blue: 3447003;
|
|
38
|
+
readonly Yellow: 16705372;
|
|
39
|
+
readonly Purple: 10181046;
|
|
40
|
+
readonly LuminousVividPink: 15277667;
|
|
41
|
+
readonly Fuchsia: 15418782;
|
|
42
|
+
readonly Gold: 15844367;
|
|
43
|
+
readonly Orange: 15105570;
|
|
44
|
+
readonly Red: 15548997;
|
|
45
|
+
readonly Grey: 9807270;
|
|
46
|
+
readonly Navy: 3426654;
|
|
47
|
+
readonly DarkAqua: 1146986;
|
|
48
|
+
readonly DarkGreen: 2067276;
|
|
49
|
+
readonly DarkBlue: 2123412;
|
|
50
|
+
readonly DarkPurple: 7419530;
|
|
51
|
+
readonly DarkVividPink: 11342935;
|
|
52
|
+
readonly DarkGold: 12745742;
|
|
53
|
+
readonly DarkOrange: 11027200;
|
|
54
|
+
readonly DarkRed: 10038562;
|
|
55
|
+
readonly DarkGrey: 9936031;
|
|
56
|
+
readonly DarkerGrey: 8359053;
|
|
57
|
+
readonly LightGrey: 12370112;
|
|
58
|
+
readonly DarkNavy: 2899536;
|
|
59
|
+
/**
|
|
60
|
+
* Alias of {@link Colors.Privage} — NOT Discord's blurple. Ported bots
|
|
61
|
+
* that `setColor(Colors.Blurple)` land on Privage's brand purple
|
|
62
|
+
* automatically (and we don't ship a competitor's brand color).
|
|
63
|
+
*/
|
|
64
|
+
readonly Blurple: 6056644;
|
|
65
|
+
readonly Greyple: 10070709;
|
|
66
|
+
readonly DarkButNotBlack: 2895667;
|
|
67
|
+
readonly NotQuiteBlack: 2303786;
|
|
68
|
+
/** Privage's primary indigo. */
|
|
69
|
+
readonly Privage: 6056644;
|
|
70
|
+
/** Privage's cyan accent. */
|
|
71
|
+
readonly PrivageAccent: 2282478;
|
|
72
|
+
};
|
package/dist/compat.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Colors = exports.Events = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* discord.js event-name constants mapped to this SDK's events.
|
|
6
|
+
* `client.on(Events.MessageCreate, ...)` ports verbatim.
|
|
7
|
+
*/
|
|
8
|
+
exports.Events = {
|
|
9
|
+
ClientReady: 'ready',
|
|
10
|
+
MessageCreate: 'messageCreate',
|
|
11
|
+
MessageUpdate: 'messageUpdate',
|
|
12
|
+
MessageDelete: 'messageDelete',
|
|
13
|
+
MessageBulkDelete: 'messageDeleteBulk',
|
|
14
|
+
MessageReactionAdd: 'reactionAdd',
|
|
15
|
+
MessageReactionRemove: 'reactionRemove',
|
|
16
|
+
InteractionCreate: 'interaction',
|
|
17
|
+
GuildMemberAdd: 'memberJoin',
|
|
18
|
+
GuildMemberRemove: 'memberLeave',
|
|
19
|
+
GuildMemberUpdate: 'memberUpdate',
|
|
20
|
+
GuildBanAdd: 'memberBan',
|
|
21
|
+
GuildBanRemove: 'memberUnban',
|
|
22
|
+
ChannelCreate: 'channelCreate',
|
|
23
|
+
ChannelUpdate: 'channelUpdate',
|
|
24
|
+
ChannelDelete: 'channelDelete',
|
|
25
|
+
GuildRoleCreate: 'roleCreate',
|
|
26
|
+
GuildRoleUpdate: 'roleUpdate',
|
|
27
|
+
GuildRoleDelete: 'roleDelete',
|
|
28
|
+
TypingStart: 'typingStart',
|
|
29
|
+
/** Recoverable transport failures — the SDK reconnects on its own. */
|
|
30
|
+
Error: 'connectionError',
|
|
31
|
+
Warn: 'warn',
|
|
32
|
+
Debug: 'debug',
|
|
33
|
+
};
|
|
34
|
+
/** The discord.js named color palette (same values), plus Privage brand colors. */
|
|
35
|
+
exports.Colors = {
|
|
36
|
+
Default: 0x000000,
|
|
37
|
+
White: 0xffffff,
|
|
38
|
+
Aqua: 0x1abc9c,
|
|
39
|
+
Green: 0x57f287,
|
|
40
|
+
Blue: 0x3498db,
|
|
41
|
+
Yellow: 0xfee75c,
|
|
42
|
+
Purple: 0x9b59b6,
|
|
43
|
+
LuminousVividPink: 0xe91e63,
|
|
44
|
+
Fuchsia: 0xeb459e,
|
|
45
|
+
Gold: 0xf1c40f,
|
|
46
|
+
Orange: 0xe67e22,
|
|
47
|
+
Red: 0xed4245,
|
|
48
|
+
Grey: 0x95a5a6,
|
|
49
|
+
Navy: 0x34495e,
|
|
50
|
+
DarkAqua: 0x11806a,
|
|
51
|
+
DarkGreen: 0x1f8b4c,
|
|
52
|
+
DarkBlue: 0x206694,
|
|
53
|
+
DarkPurple: 0x71368a,
|
|
54
|
+
DarkVividPink: 0xad1457,
|
|
55
|
+
DarkGold: 0xc27c0e,
|
|
56
|
+
DarkOrange: 0xa84300,
|
|
57
|
+
DarkRed: 0x992d22,
|
|
58
|
+
DarkGrey: 0x979c9f,
|
|
59
|
+
DarkerGrey: 0x7f8c8d,
|
|
60
|
+
LightGrey: 0xbcc0c0,
|
|
61
|
+
DarkNavy: 0x2c3e50,
|
|
62
|
+
/**
|
|
63
|
+
* Alias of {@link Colors.Privage} — NOT Discord's blurple. Ported bots
|
|
64
|
+
* that `setColor(Colors.Blurple)` land on Privage's brand purple
|
|
65
|
+
* automatically (and we don't ship a competitor's brand color).
|
|
66
|
+
*/
|
|
67
|
+
Blurple: 0x5c6ac4,
|
|
68
|
+
Greyple: 0x99aab5,
|
|
69
|
+
DarkButNotBlack: 0x2c2f33,
|
|
70
|
+
NotQuiteBlack: 0x23272a,
|
|
71
|
+
/** Privage's primary indigo. */
|
|
72
|
+
Privage: 0x5c6ac4,
|
|
73
|
+
/** Privage's cyan accent. */
|
|
74
|
+
PrivageAccent: 0x22d3ee,
|
|
75
|
+
};
|