district.js 0.6.0 → 0.8.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/dist/client.d.ts +49 -2
- package/dist/client.js +45 -1
- package/dist/commands.d.ts +73 -0
- package/dist/commands.js +70 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +2 -0
- package/dist/store.d.ts +41 -0
- package/dist/store.js +90 -0
- package/dist/structures.d.ts +38 -2
- package/dist/structures.js +40 -2
- package/dist/types.d.ts +39 -0
- package/package.json +1 -1
package/dist/client.d.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { EventEmitter } from "node:events";
|
|
2
2
|
import http from "node:http";
|
|
3
3
|
import { REST } from "./rest.js";
|
|
4
|
+
import { Store } from "./store.js";
|
|
5
|
+
import { type EmbedResolvable } from "./embed.js";
|
|
6
|
+
import { type CommandResolvable, type CommandData } from "./commands.js";
|
|
4
7
|
import { ClientUser, Message, TextChannel, Interaction, User, Space, type ClientContext, type MessageContent } from "./structures.js";
|
|
5
8
|
import { type EventHeaders } from "./webhook.js";
|
|
6
|
-
import type { ClientOptions, ReactionEvent, DeletedMessage } from "./types.js";
|
|
9
|
+
import type { ClientOptions, ReactionEvent, DeletedMessage, MemberEvent, ScheduledEvent, ScheduleInput } from "./types.js";
|
|
7
10
|
/** The events a Client emits, and their argument tuples. */
|
|
8
11
|
export interface ClientEvents {
|
|
9
12
|
ready: [user: ClientUser];
|
|
@@ -12,11 +15,16 @@ export interface ClientEvents {
|
|
|
12
15
|
messageDelete: [message: DeletedMessage];
|
|
13
16
|
messageReactionAdd: [reaction: ReactionEvent];
|
|
14
17
|
messageReactionRemove: [reaction: ReactionEvent];
|
|
18
|
+
memberJoin: [member: MemberEvent];
|
|
19
|
+
memberLeave: [member: MemberEvent];
|
|
20
|
+
scheduled: [event: ScheduledEvent];
|
|
15
21
|
interactionCreate: [interaction: Interaction];
|
|
16
22
|
error: [error: Error];
|
|
17
23
|
}
|
|
18
24
|
export declare class Client extends EventEmitter implements ClientContext {
|
|
19
25
|
readonly rest: REST;
|
|
26
|
+
/** The bot's persistent document store (`client.store.collection("users")…`). */
|
|
27
|
+
readonly store: Store;
|
|
20
28
|
/** The bot's own identity — populated after `login()`. */
|
|
21
29
|
user: ClientUser | null;
|
|
22
30
|
private signingSecret?;
|
|
@@ -33,14 +41,53 @@ export declare class Client extends EventEmitter implements ClientContext {
|
|
|
33
41
|
/** Send a message to a channel (text or { content, embeds }). */
|
|
34
42
|
send: (channelId: string, content: MessageContent) => Promise<Message>;
|
|
35
43
|
};
|
|
36
|
-
/** Fetch users by id. */
|
|
44
|
+
/** Fetch users by id, or DM them. */
|
|
37
45
|
readonly users: {
|
|
38
46
|
fetch: (id: string) => Promise<User>;
|
|
47
|
+
/** Open (or reuse) a 1-on-1 DM with a user and send them a message. */
|
|
48
|
+
send: (userId: string, content: string | {
|
|
49
|
+
content?: string;
|
|
50
|
+
embeds?: EmbedResolvable[];
|
|
51
|
+
}) => Promise<{
|
|
52
|
+
channelId: string;
|
|
53
|
+
}>;
|
|
39
54
|
};
|
|
40
55
|
/** Fetch spaces by id. */
|
|
41
56
|
readonly spaces: {
|
|
42
57
|
fetch: (id: string) => Promise<Space>;
|
|
43
58
|
};
|
|
59
|
+
/** Register / list / delete this bot's slash commands (they show in the `/`
|
|
60
|
+
* picker). `set` replaces the entire command set, discord.js-style. */
|
|
61
|
+
readonly commands: {
|
|
62
|
+
/** Replace all of the bot's commands with this set. `handlerUrl` defaults to
|
|
63
|
+
* the app's active event-subscription endpoint. */
|
|
64
|
+
set: (commands: CommandResolvable[], opts?: {
|
|
65
|
+
handlerUrl?: string;
|
|
66
|
+
}) => Promise<{
|
|
67
|
+
ok: boolean;
|
|
68
|
+
count: number;
|
|
69
|
+
}>;
|
|
70
|
+
/** List the bot's currently-registered commands. */
|
|
71
|
+
list: () => Promise<CommandData[]>;
|
|
72
|
+
/** Delete one command by name. */
|
|
73
|
+
delete: (name: string) => Promise<void>;
|
|
74
|
+
};
|
|
75
|
+
/** Register / list / delete recurring scheduled jobs. When one is due District
|
|
76
|
+
* POSTs a `scheduled` event to the bot — listen with `client.on("scheduled", …)`. */
|
|
77
|
+
readonly schedules: {
|
|
78
|
+
/** Replace all schedules with this set (min interval 60s). `handlerUrl`
|
|
79
|
+
* defaults to the app's active event-subscription endpoint. */
|
|
80
|
+
set: (schedules: ScheduleInput[], opts?: {
|
|
81
|
+
handlerUrl?: string;
|
|
82
|
+
}) => Promise<{
|
|
83
|
+
ok: boolean;
|
|
84
|
+
count: number;
|
|
85
|
+
}>;
|
|
86
|
+
/** List the bot's registered schedules (with next/last run times). */
|
|
87
|
+
list: () => Promise<any[]>;
|
|
88
|
+
/** Delete one schedule by name. */
|
|
89
|
+
delete: (name: string) => Promise<void>;
|
|
90
|
+
};
|
|
44
91
|
/** Validate the token, load the bot's identity, and emit `ready`. */
|
|
45
92
|
login(): Promise<ClientUser>;
|
|
46
93
|
/**
|
package/dist/client.js
CHANGED
|
@@ -3,10 +3,15 @@
|
|
|
3
3
|
import { EventEmitter } from "node:events";
|
|
4
4
|
import http from "node:http";
|
|
5
5
|
import { REST } from "./rest.js";
|
|
6
|
+
import { Store } from "./store.js";
|
|
7
|
+
import { resolveEmbed } from "./embed.js";
|
|
8
|
+
import { resolveCommand } from "./commands.js";
|
|
6
9
|
import { ClientUser, Message, TextChannel, Interaction, User, Space } from "./structures.js";
|
|
7
10
|
import { verifyAndParse } from "./webhook.js";
|
|
8
11
|
export class Client extends EventEmitter {
|
|
9
12
|
rest;
|
|
13
|
+
/** The bot's persistent document store (`client.store.collection("users")…`). */
|
|
14
|
+
store;
|
|
10
15
|
/** The bot's own identity — populated after `login()`. */
|
|
11
16
|
user = null;
|
|
12
17
|
signingSecret;
|
|
@@ -15,6 +20,7 @@ export class Client extends EventEmitter {
|
|
|
15
20
|
if (!options?.token)
|
|
16
21
|
throw new Error("district.js: `token` is required");
|
|
17
22
|
this.rest = new REST(options.token, options.apiBase);
|
|
23
|
+
this.store = new Store(this.rest);
|
|
18
24
|
this.signingSecret = options.signingSecret;
|
|
19
25
|
}
|
|
20
26
|
// ── typed emitter surface ──────────────────────────────────────────────────
|
|
@@ -41,14 +47,43 @@ export class Client extends EventEmitter {
|
|
|
41
47
|
/** Send a message to a channel (text or { content, embeds }). */
|
|
42
48
|
send: (channelId, content) => new TextChannel(this, channelId).send(content),
|
|
43
49
|
};
|
|
44
|
-
/** Fetch users by id. */
|
|
50
|
+
/** Fetch users by id, or DM them. */
|
|
45
51
|
users = {
|
|
46
52
|
fetch: (id) => this.rest.get(`/users/${id}`).then((r) => new User(r.user)),
|
|
53
|
+
/** Open (or reuse) a 1-on-1 DM with a user and send them a message. */
|
|
54
|
+
send: (userId, content) => {
|
|
55
|
+
const body = typeof content === "string"
|
|
56
|
+
? { content }
|
|
57
|
+
: { content: content.content, embeds: content.embeds?.map(resolveEmbed) };
|
|
58
|
+
return this.rest.post(`/users/${userId}/dm`, body);
|
|
59
|
+
},
|
|
47
60
|
};
|
|
48
61
|
/** Fetch spaces by id. */
|
|
49
62
|
spaces = {
|
|
50
63
|
fetch: (id) => this.rest.get(`/spaces/${id}`).then((r) => new Space(this, r.space)),
|
|
51
64
|
};
|
|
65
|
+
/** Register / list / delete this bot's slash commands (they show in the `/`
|
|
66
|
+
* picker). `set` replaces the entire command set, discord.js-style. */
|
|
67
|
+
commands = {
|
|
68
|
+
/** Replace all of the bot's commands with this set. `handlerUrl` defaults to
|
|
69
|
+
* the app's active event-subscription endpoint. */
|
|
70
|
+
set: (commands, opts = {}) => this.rest.put("/commands", { commands: commands.map(resolveCommand), ...(opts.handlerUrl ? { handlerUrl: opts.handlerUrl } : {}) }),
|
|
71
|
+
/** List the bot's currently-registered commands. */
|
|
72
|
+
list: () => this.rest.get("/commands").then((r) => r.commands),
|
|
73
|
+
/** Delete one command by name. */
|
|
74
|
+
delete: (name) => this.rest.delete(`/commands/${encodeURIComponent(name)}`).then(() => undefined),
|
|
75
|
+
};
|
|
76
|
+
/** Register / list / delete recurring scheduled jobs. When one is due District
|
|
77
|
+
* POSTs a `scheduled` event to the bot — listen with `client.on("scheduled", …)`. */
|
|
78
|
+
schedules = {
|
|
79
|
+
/** Replace all schedules with this set (min interval 60s). `handlerUrl`
|
|
80
|
+
* defaults to the app's active event-subscription endpoint. */
|
|
81
|
+
set: (schedules, opts = {}) => this.rest.put("/schedules", { schedules, ...(opts.handlerUrl ? { handlerUrl: opts.handlerUrl } : {}) }),
|
|
82
|
+
/** List the bot's registered schedules (with next/last run times). */
|
|
83
|
+
list: () => this.rest.get("/schedules").then((r) => r.schedules),
|
|
84
|
+
/** Delete one schedule by name. */
|
|
85
|
+
delete: (name) => this.rest.delete(`/schedules/${encodeURIComponent(name)}`).then(() => undefined),
|
|
86
|
+
};
|
|
52
87
|
/** Validate the token, load the bot's identity, and emit `ready`. */
|
|
53
88
|
async login() {
|
|
54
89
|
const res = await this.rest.get("/me");
|
|
@@ -138,6 +173,15 @@ export class Client extends EventEmitter {
|
|
|
138
173
|
case "message.reaction_remove":
|
|
139
174
|
this.emit("messageReactionRemove", toReaction(evt));
|
|
140
175
|
break;
|
|
176
|
+
case "member.join":
|
|
177
|
+
this.emit("memberJoin", { spaceId: evt.space_id, userId: evt.member.user_id, joinedAt: evt.member.joined_at ?? null });
|
|
178
|
+
break;
|
|
179
|
+
case "member.leave":
|
|
180
|
+
this.emit("memberLeave", { spaceId: evt.space_id, userId: evt.member.user_id, joinedAt: evt.member.joined_at ?? null });
|
|
181
|
+
break;
|
|
182
|
+
case "scheduled":
|
|
183
|
+
this.emit("scheduled", { schedule: evt.schedule, data: evt.data ?? {} });
|
|
184
|
+
break;
|
|
141
185
|
case "interaction.create":
|
|
142
186
|
this.emit("interactionCreate", new Interaction(this, evt));
|
|
143
187
|
break;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/** Option value types a command argument can declare. */
|
|
2
|
+
export type CommandOptionType = "string" | "integer" | "number" | "boolean" | "user" | "channel" | "role" | "subcommand";
|
|
3
|
+
/** A fixed choice for an option (what the picker offers). */
|
|
4
|
+
export interface CommandOptionChoice {
|
|
5
|
+
name: string;
|
|
6
|
+
value: string | number;
|
|
7
|
+
}
|
|
8
|
+
/** One declared command argument (or a subcommand, when `type` is "subcommand"). */
|
|
9
|
+
export interface CommandOption {
|
|
10
|
+
type: CommandOptionType;
|
|
11
|
+
name: string;
|
|
12
|
+
description: string;
|
|
13
|
+
required?: boolean;
|
|
14
|
+
choices?: CommandOptionChoice[];
|
|
15
|
+
/** Numeric bounds (integer/number options). */
|
|
16
|
+
minValue?: number;
|
|
17
|
+
maxValue?: number;
|
|
18
|
+
/** Nested options — present when `type` is "subcommand". */
|
|
19
|
+
options?: CommandOption[];
|
|
20
|
+
}
|
|
21
|
+
/** The serialized command payload sent to the API. */
|
|
22
|
+
export interface CommandData {
|
|
23
|
+
name: string;
|
|
24
|
+
description: string;
|
|
25
|
+
options?: CommandOption[];
|
|
26
|
+
/** Permission bits required to invoke (0 = anyone). */
|
|
27
|
+
defaultPerms?: number;
|
|
28
|
+
}
|
|
29
|
+
type AddOpts = {
|
|
30
|
+
required?: boolean;
|
|
31
|
+
choices?: CommandOptionChoice[];
|
|
32
|
+
minValue?: number;
|
|
33
|
+
maxValue?: number;
|
|
34
|
+
};
|
|
35
|
+
/** Shared option-adding surface for both commands and subcommands. */
|
|
36
|
+
declare class OptionsBuilder {
|
|
37
|
+
options: CommandOption[];
|
|
38
|
+
protected addOption(type: CommandOptionType, name: string, description: string, opts?: AddOpts): this;
|
|
39
|
+
addStringOption(name: string, description: string, opts?: AddOpts): this;
|
|
40
|
+
addIntegerOption(name: string, description: string, opts?: AddOpts): this;
|
|
41
|
+
addNumberOption(name: string, description: string, opts?: AddOpts): this;
|
|
42
|
+
addBooleanOption(name: string, description: string, opts?: AddOpts): this;
|
|
43
|
+
addUserOption(name: string, description: string, opts?: AddOpts): this;
|
|
44
|
+
addChannelOption(name: string, description: string, opts?: AddOpts): this;
|
|
45
|
+
addRoleOption(name: string, description: string, opts?: AddOpts): this;
|
|
46
|
+
}
|
|
47
|
+
/** A subcommand under a top-level command (e.g. `/setup channels`). */
|
|
48
|
+
export declare class SlashCommandSubcommandBuilder extends OptionsBuilder {
|
|
49
|
+
name: string;
|
|
50
|
+
description: string;
|
|
51
|
+
setName(name: string): this;
|
|
52
|
+
setDescription(description: string): this;
|
|
53
|
+
toJSON(): CommandOption;
|
|
54
|
+
}
|
|
55
|
+
/** Fluent builder for a slash command. */
|
|
56
|
+
export declare class SlashCommandBuilder extends OptionsBuilder {
|
|
57
|
+
name: string;
|
|
58
|
+
description: string;
|
|
59
|
+
defaultPerms: number;
|
|
60
|
+
setName(name: string): this;
|
|
61
|
+
setDescription(description: string): this;
|
|
62
|
+
/** Permission bits required to run this command (see PermissionsBitField). */
|
|
63
|
+
setDefaultPerms(bits: number): this;
|
|
64
|
+
/** Add a subcommand (e.g. `/setup channels`). A command with subcommands is
|
|
65
|
+
* invoked as `/name subcommand …`; read the chosen one with
|
|
66
|
+
* `interaction.options.getSubcommand()`. */
|
|
67
|
+
addSubcommand(fn: (sub: SlashCommandSubcommandBuilder) => SlashCommandSubcommandBuilder): this;
|
|
68
|
+
toJSON(): CommandData;
|
|
69
|
+
}
|
|
70
|
+
/** Accept either a builder or a plain object anywhere a command is expected. */
|
|
71
|
+
export type CommandResolvable = SlashCommandBuilder | CommandData;
|
|
72
|
+
export declare function resolveCommand(c: CommandResolvable): CommandData;
|
|
73
|
+
export {};
|
package/dist/commands.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// Slash command definitions — the district.js equivalent of Discord's
|
|
2
|
+
// SlashCommandBuilder. Build a command with typed options (and optional
|
|
3
|
+
// subcommands), then register your whole set with `client.commands.set([...])`
|
|
4
|
+
// so they show in the `/` picker.
|
|
5
|
+
//
|
|
6
|
+
// const roll = new SlashCommandBuilder()
|
|
7
|
+
// .setName("roll").setDescription("Roll a die")
|
|
8
|
+
// .addIntegerOption("sides", "How many sides", { minValue: 2, maxValue: 100 });
|
|
9
|
+
//
|
|
10
|
+
// const setup = new SlashCommandBuilder()
|
|
11
|
+
// .setName("setup").setDescription("Configure the bot")
|
|
12
|
+
// .addSubcommand(s => s.setName("channels").setDescription("Set channels")
|
|
13
|
+
// .addChannelOption("giveaway", "Where giveaways post", { required: true }));
|
|
14
|
+
/** Shared option-adding surface for both commands and subcommands. */
|
|
15
|
+
class OptionsBuilder {
|
|
16
|
+
options = [];
|
|
17
|
+
addOption(type, name, description, opts = {}) {
|
|
18
|
+
const o = { type, name, description, required: !!opts.required };
|
|
19
|
+
if (opts.choices && opts.choices.length)
|
|
20
|
+
o.choices = opts.choices;
|
|
21
|
+
if (opts.minValue != null)
|
|
22
|
+
o.minValue = opts.minValue;
|
|
23
|
+
if (opts.maxValue != null)
|
|
24
|
+
o.maxValue = opts.maxValue;
|
|
25
|
+
this.options.push(o);
|
|
26
|
+
return this;
|
|
27
|
+
}
|
|
28
|
+
addStringOption(name, description, opts) { return this.addOption("string", name, description, opts); }
|
|
29
|
+
addIntegerOption(name, description, opts) { return this.addOption("integer", name, description, opts); }
|
|
30
|
+
addNumberOption(name, description, opts) { return this.addOption("number", name, description, opts); }
|
|
31
|
+
addBooleanOption(name, description, opts) { return this.addOption("boolean", name, description, opts); }
|
|
32
|
+
addUserOption(name, description, opts) { return this.addOption("user", name, description, opts); }
|
|
33
|
+
addChannelOption(name, description, opts) { return this.addOption("channel", name, description, opts); }
|
|
34
|
+
addRoleOption(name, description, opts) { return this.addOption("role", name, description, opts); }
|
|
35
|
+
}
|
|
36
|
+
/** A subcommand under a top-level command (e.g. `/setup channels`). */
|
|
37
|
+
export class SlashCommandSubcommandBuilder extends OptionsBuilder {
|
|
38
|
+
name = "";
|
|
39
|
+
description = "";
|
|
40
|
+
setName(name) { this.name = name; return this; }
|
|
41
|
+
setDescription(description) { this.description = description; return this; }
|
|
42
|
+
toJSON() {
|
|
43
|
+
return { type: "subcommand", name: this.name, description: this.description, options: this.options };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/** Fluent builder for a slash command. */
|
|
47
|
+
export class SlashCommandBuilder extends OptionsBuilder {
|
|
48
|
+
name = "";
|
|
49
|
+
description = "";
|
|
50
|
+
defaultPerms = 0;
|
|
51
|
+
setName(name) { this.name = name; return this; }
|
|
52
|
+
setDescription(description) { this.description = description; return this; }
|
|
53
|
+
/** Permission bits required to run this command (see PermissionsBitField). */
|
|
54
|
+
setDefaultPerms(bits) { this.defaultPerms = bits; return this; }
|
|
55
|
+
/** Add a subcommand (e.g. `/setup channels`). A command with subcommands is
|
|
56
|
+
* invoked as `/name subcommand …`; read the chosen one with
|
|
57
|
+
* `interaction.options.getSubcommand()`. */
|
|
58
|
+
addSubcommand(fn) {
|
|
59
|
+
const sub = new SlashCommandSubcommandBuilder();
|
|
60
|
+
fn(sub);
|
|
61
|
+
this.options.push(sub.toJSON());
|
|
62
|
+
return this;
|
|
63
|
+
}
|
|
64
|
+
toJSON() {
|
|
65
|
+
return { name: this.name, description: this.description, options: this.options, defaultPerms: this.defaultPerms };
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
export function resolveCommand(c) {
|
|
69
|
+
return c instanceof SlashCommandBuilder ? c.toJSON() : c;
|
|
70
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
export { Client, type ClientEvents } from "./client.js";
|
|
2
2
|
export { REST, DistrictAPIError, DEFAULT_API_BASE } from "./rest.js";
|
|
3
|
-
export { ClientUser, Message, TextChannel, Interaction, User, Space, SpaceMember, Role, Attachment, Ban, Invite, Webhook, type ClientContext, type MessageContent, } from "./structures.js";
|
|
3
|
+
export { ClientUser, Message, TextChannel, Interaction, User, Space, SpaceMember, Role, Attachment, Ban, Invite, Webhook, type ClientContext, type MessageContent, type MessageFile, } from "./structures.js";
|
|
4
4
|
export { EmbedBuilder, resolveEmbed, type EmbedData, type EmbedField, type EmbedResolvable } from "./embed.js";
|
|
5
5
|
export { ButtonBuilder, ActionRowBuilder, StringSelectMenuBuilder, ModalBuilder, TextInputBuilder, resolveComponent, type ButtonStyle, type ButtonData, type SelectData, type SelectOption, type ComponentData, type ModalData, type TextInputData, type ActionRowData, type ComponentResolvable, } from "./components.js";
|
|
6
|
+
export { Store, StoreCollection, type StoreDocument } from "./store.js";
|
|
7
|
+
export { SlashCommandBuilder, SlashCommandSubcommandBuilder, resolveCommand, type CommandData, type CommandOption, type CommandOptionType, type CommandOptionChoice, type CommandResolvable, } from "./commands.js";
|
|
6
8
|
export { Collection } from "./collection.js";
|
|
7
9
|
export { PermissionsBitField, type PermissionResolvable, type PermissionFlagName } from "./permissions.js";
|
|
8
10
|
export { Formatters } from "./formatters.js";
|
|
9
11
|
export { ChannelType, type ChannelTypeValue } from "./enums.js";
|
|
10
12
|
export { verifySignature, parseEvent, verifyAndParse, type EventHeaders } from "./webhook.js";
|
|
11
|
-
export type { ClientOptions, RawMessage, RawSelfUser, RawUser, RawSpace, RawChannel, RawRole, RawMember, RawAttachment, RawBan, RawInvite, RawWebhook, DistrictEvent, ReactionEvent, DeletedMessage, } from "./types.js";
|
|
13
|
+
export type { ClientOptions, RawMessage, RawSelfUser, RawUser, RawSpace, RawChannel, RawRole, RawMember, RawAttachment, RawBan, RawInvite, RawWebhook, DistrictEvent, ReactionEvent, DeletedMessage, MemberEvent, ScheduledEvent, ScheduleInput, } from "./types.js";
|
package/dist/index.js
CHANGED
|
@@ -9,6 +9,8 @@ export { REST, DistrictAPIError, DEFAULT_API_BASE } from "./rest.js";
|
|
|
9
9
|
export { ClientUser, Message, TextChannel, Interaction, User, Space, SpaceMember, Role, Attachment, Ban, Invite, Webhook, } from "./structures.js";
|
|
10
10
|
export { EmbedBuilder, resolveEmbed } from "./embed.js";
|
|
11
11
|
export { ButtonBuilder, ActionRowBuilder, StringSelectMenuBuilder, ModalBuilder, TextInputBuilder, resolveComponent, } from "./components.js";
|
|
12
|
+
export { Store, StoreCollection } from "./store.js";
|
|
13
|
+
export { SlashCommandBuilder, SlashCommandSubcommandBuilder, resolveCommand, } from "./commands.js";
|
|
12
14
|
export { Collection } from "./collection.js";
|
|
13
15
|
export { PermissionsBitField } from "./permissions.js";
|
|
14
16
|
export { Formatters } from "./formatters.js";
|
package/dist/store.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { REST } from "./rest.js";
|
|
2
|
+
/** One stored document: its `key` and JSON `value` (+ last-updated time on reads). */
|
|
3
|
+
export interface StoreDocument<T = any> {
|
|
4
|
+
key: string;
|
|
5
|
+
value: T;
|
|
6
|
+
updatedAt?: string;
|
|
7
|
+
}
|
|
8
|
+
/** A handle to one collection of documents (e.g. "users", "guilds"). */
|
|
9
|
+
export declare class StoreCollection {
|
|
10
|
+
private readonly rest;
|
|
11
|
+
readonly name: string;
|
|
12
|
+
constructor(rest: REST, name: string);
|
|
13
|
+
/** Read one document's value, or `null` if it doesn't exist. */
|
|
14
|
+
get<T = any>(key: string): Promise<T | null>;
|
|
15
|
+
/** Create or fully replace a document. Returns the stored value. */
|
|
16
|
+
set<T = any>(key: string, value: T): Promise<T>;
|
|
17
|
+
/** Shallow-merge `partial` into an existing document (creates it if absent).
|
|
18
|
+
* Returns the merged value. */
|
|
19
|
+
update<T = any>(key: string, partial: Partial<T>): Promise<T>;
|
|
20
|
+
/** Delete a document. No-op if it doesn't exist. */
|
|
21
|
+
delete(key: string): Promise<void>;
|
|
22
|
+
/** Atomically add `amount` to a numeric field (default field: `value`). Safe
|
|
23
|
+
* under concurrency — two simultaneous increments never lose a write. Pass an
|
|
24
|
+
* array for a nested path, e.g. `increment(id, ["stats", "wins"], 1)`. Returns
|
|
25
|
+
* the whole updated document value. */
|
|
26
|
+
increment<T = any>(key: string, field?: string | string[], amount?: number): Promise<T>;
|
|
27
|
+
/** List documents in this collection (newest-updated first by default). */
|
|
28
|
+
all<T = any>(opts?: {
|
|
29
|
+
limit?: number;
|
|
30
|
+
offset?: number;
|
|
31
|
+
order?: "asc" | "desc";
|
|
32
|
+
}): Promise<StoreDocument<T>[]>;
|
|
33
|
+
}
|
|
34
|
+
/** The bot's persistent store. Get a collection with `.collection(name)`. */
|
|
35
|
+
export declare class Store {
|
|
36
|
+
private readonly rest;
|
|
37
|
+
private readonly cache;
|
|
38
|
+
constructor(rest: REST);
|
|
39
|
+
/** A handle to a named collection (memoized). */
|
|
40
|
+
collection(name: string): StoreCollection;
|
|
41
|
+
}
|
package/dist/store.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// Persistent bot storage — the district.js equivalent of "bring your own
|
|
2
|
+
// database". Each bot gets an isolated document store, addressed as
|
|
3
|
+
// `collection` → `key` → JSON `value`, so you can model users, balances,
|
|
4
|
+
// transactions, leaderboards, config… anything a data-heavy bot needs.
|
|
5
|
+
//
|
|
6
|
+
// const users = client.store.collection("users");
|
|
7
|
+
// await users.set("42", { balance: 100, streak: 0 });
|
|
8
|
+
// const me = await users.get<{ balance: number }>("42");
|
|
9
|
+
// await users.increment("42", "balance", 250); // atomic — safe under load
|
|
10
|
+
// const leaderboard = await users.all({ limit: 10 });
|
|
11
|
+
//
|
|
12
|
+
// Everything is scoped to your application automatically; a bot can only ever
|
|
13
|
+
// see its own data.
|
|
14
|
+
import { DistrictAPIError } from "./rest.js";
|
|
15
|
+
const enc = encodeURIComponent;
|
|
16
|
+
/** A handle to one collection of documents (e.g. "users", "guilds"). */
|
|
17
|
+
export class StoreCollection {
|
|
18
|
+
rest;
|
|
19
|
+
name;
|
|
20
|
+
constructor(rest, name) {
|
|
21
|
+
this.rest = rest;
|
|
22
|
+
this.name = name;
|
|
23
|
+
}
|
|
24
|
+
/** Read one document's value, or `null` if it doesn't exist. */
|
|
25
|
+
async get(key) {
|
|
26
|
+
try {
|
|
27
|
+
const r = await this.rest.get(`/storage/${enc(this.name)}/${enc(key)}`);
|
|
28
|
+
return r.value;
|
|
29
|
+
}
|
|
30
|
+
catch (e) {
|
|
31
|
+
if (e instanceof DistrictAPIError && e.status === 404)
|
|
32
|
+
return null;
|
|
33
|
+
throw e;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/** Create or fully replace a document. Returns the stored value. */
|
|
37
|
+
async set(key, value) {
|
|
38
|
+
const r = await this.rest.put(`/storage/${enc(this.name)}/${enc(key)}`, value);
|
|
39
|
+
return r.value;
|
|
40
|
+
}
|
|
41
|
+
/** Shallow-merge `partial` into an existing document (creates it if absent).
|
|
42
|
+
* Returns the merged value. */
|
|
43
|
+
async update(key, partial) {
|
|
44
|
+
const r = await this.rest.patch(`/storage/${enc(this.name)}/${enc(key)}`, partial);
|
|
45
|
+
return r.value;
|
|
46
|
+
}
|
|
47
|
+
/** Delete a document. No-op if it doesn't exist. */
|
|
48
|
+
async delete(key) {
|
|
49
|
+
await this.rest.delete(`/storage/${enc(this.name)}/${enc(key)}`);
|
|
50
|
+
}
|
|
51
|
+
/** Atomically add `amount` to a numeric field (default field: `value`). Safe
|
|
52
|
+
* under concurrency — two simultaneous increments never lose a write. Pass an
|
|
53
|
+
* array for a nested path, e.g. `increment(id, ["stats", "wins"], 1)`. Returns
|
|
54
|
+
* the whole updated document value. */
|
|
55
|
+
async increment(key, field = "value", amount = 1) {
|
|
56
|
+
const path = Array.isArray(field) ? field : [field];
|
|
57
|
+
const r = await this.rest.post(`/storage/${enc(this.name)}/${enc(key)}/increment`, { path, amount });
|
|
58
|
+
return r.value;
|
|
59
|
+
}
|
|
60
|
+
/** List documents in this collection (newest-updated first by default). */
|
|
61
|
+
async all(opts = {}) {
|
|
62
|
+
const q = new URLSearchParams();
|
|
63
|
+
if (opts.limit != null)
|
|
64
|
+
q.set("limit", String(opts.limit));
|
|
65
|
+
if (opts.offset != null)
|
|
66
|
+
q.set("offset", String(opts.offset));
|
|
67
|
+
if (opts.order)
|
|
68
|
+
q.set("order", opts.order);
|
|
69
|
+
const qs = q.toString();
|
|
70
|
+
const r = await this.rest.get(`/storage/${enc(this.name)}${qs ? `?${qs}` : ""}`);
|
|
71
|
+
return r.items ?? [];
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/** The bot's persistent store. Get a collection with `.collection(name)`. */
|
|
75
|
+
export class Store {
|
|
76
|
+
rest;
|
|
77
|
+
cache = new Map();
|
|
78
|
+
constructor(rest) {
|
|
79
|
+
this.rest = rest;
|
|
80
|
+
}
|
|
81
|
+
/** A handle to a named collection (memoized). */
|
|
82
|
+
collection(name) {
|
|
83
|
+
let c = this.cache.get(name);
|
|
84
|
+
if (!c) {
|
|
85
|
+
c = new StoreCollection(this.rest, name);
|
|
86
|
+
this.cache.set(name, c);
|
|
87
|
+
}
|
|
88
|
+
return c;
|
|
89
|
+
}
|
|
90
|
+
}
|
package/dist/structures.d.ts
CHANGED
|
@@ -5,10 +5,19 @@ import { PermissionsBitField } from "./permissions.js";
|
|
|
5
5
|
import { type EmbedResolvable } from "./embed.js";
|
|
6
6
|
import { type ComponentResolvable } from "./components.js";
|
|
7
7
|
/** What you can pass to send/reply: a string, or content + embeds + components. */
|
|
8
|
+
/** A file to attach to a message. `data` is base64-encoded bytes. */
|
|
9
|
+
export interface MessageFile {
|
|
10
|
+
filename: string;
|
|
11
|
+
/** Base64-encoded file contents. */
|
|
12
|
+
data: string;
|
|
13
|
+
/** MIME type (e.g. "image/png"). Defaults to application/octet-stream. */
|
|
14
|
+
contentType?: string;
|
|
15
|
+
}
|
|
8
16
|
export type MessageContent = string | {
|
|
9
17
|
content?: string;
|
|
10
18
|
embeds?: EmbedResolvable[];
|
|
11
19
|
components?: ComponentResolvable[];
|
|
20
|
+
files?: MessageFile[];
|
|
12
21
|
};
|
|
13
22
|
/** Minimal client surface the structures need (avoids a circular import). */
|
|
14
23
|
export interface ClientContext {
|
|
@@ -110,6 +119,13 @@ export declare class Space {
|
|
|
110
119
|
members(limit?: number): Promise<Collection<string, SpaceMember>>;
|
|
111
120
|
/** The space's roles. */
|
|
112
121
|
roles(): Promise<Collection<string, Role>>;
|
|
122
|
+
/** A member's roles in this space — use to gate on "has role X". */
|
|
123
|
+
memberRoles(userId: string): Promise<Array<{
|
|
124
|
+
id: string;
|
|
125
|
+
name: string | null;
|
|
126
|
+
color: string | null;
|
|
127
|
+
position: number;
|
|
128
|
+
}>>;
|
|
113
129
|
/** The space owner's profile. */
|
|
114
130
|
fetchOwner(): Promise<User>;
|
|
115
131
|
/** Banned users. */
|
|
@@ -246,6 +262,26 @@ export declare class Interaction {
|
|
|
246
262
|
isModalSubmit(): boolean;
|
|
247
263
|
/** Get a submitted modal field value by its input customId. */
|
|
248
264
|
field(customId: string): string | undefined;
|
|
249
|
-
/**
|
|
250
|
-
|
|
265
|
+
/** Typed accessors for slash-command option values (read from `args`), à la
|
|
266
|
+
* discord.js: `interaction.options.getInteger("amount")`. Returns null when
|
|
267
|
+
* the option was not provided. `getUser` returns the user id. */
|
|
268
|
+
get options(): {
|
|
269
|
+
get: (name: string) => unknown;
|
|
270
|
+
getString: (name: string) => string | null;
|
|
271
|
+
getInteger: (name: string) => number | null;
|
|
272
|
+
getNumber: (name: string) => number | null;
|
|
273
|
+
getBoolean: (name: string) => boolean | null;
|
|
274
|
+
getUser: (name: string) => string | null;
|
|
275
|
+
getChannel: (name: string) => string | null;
|
|
276
|
+
getRole: (name: string) => string | null;
|
|
277
|
+
/** The chosen subcommand name (for commands with subcommands), or null. */
|
|
278
|
+
getSubcommand: () => string | null;
|
|
279
|
+
};
|
|
280
|
+
/** Respond to the command by posting a message in its channel. Pass
|
|
281
|
+
* `{ ephemeral: true }` to send a private reply only the invoking user sees
|
|
282
|
+
* (content + embeds only) — this resolves to null since no public message is
|
|
283
|
+
* created. */
|
|
284
|
+
reply(content: MessageContent, opts?: {
|
|
285
|
+
ephemeral?: boolean;
|
|
286
|
+
}): Promise<Message | null>;
|
|
251
287
|
}
|
package/dist/structures.js
CHANGED
|
@@ -11,6 +11,7 @@ function toPayload(input) {
|
|
|
11
11
|
content: input.content,
|
|
12
12
|
embeds: input.embeds?.map(resolveEmbed),
|
|
13
13
|
components: input.components?.map(resolveComponent),
|
|
14
|
+
files: input.files,
|
|
14
15
|
};
|
|
15
16
|
}
|
|
16
17
|
export class ClientUser {
|
|
@@ -211,6 +212,11 @@ export class Space {
|
|
|
211
212
|
out.set(r.id, new Role(r));
|
|
212
213
|
return out;
|
|
213
214
|
}
|
|
215
|
+
/** A member's roles in this space — use to gate on "has role X". */
|
|
216
|
+
async memberRoles(userId) {
|
|
217
|
+
const r = await this.client.rest.get(`/spaces/${this.id}/members/${userId}/roles`);
|
|
218
|
+
return r.roles ?? [];
|
|
219
|
+
}
|
|
214
220
|
/** The space owner's profile. */
|
|
215
221
|
async fetchOwner() {
|
|
216
222
|
const { user } = await this.client.rest.get(`/users/${this.ownerId}`);
|
|
@@ -416,9 +422,41 @@ export class Interaction {
|
|
|
416
422
|
isModalSubmit() { return this.type === "modal_submit"; }
|
|
417
423
|
/** Get a submitted modal field value by its input customId. */
|
|
418
424
|
field(customId) { return this.fields[customId]; }
|
|
419
|
-
/**
|
|
420
|
-
|
|
425
|
+
/** Typed accessors for slash-command option values (read from `args`), à la
|
|
426
|
+
* discord.js: `interaction.options.getInteger("amount")`. Returns null when
|
|
427
|
+
* the option was not provided. `getUser` returns the user id. */
|
|
428
|
+
get options() {
|
|
429
|
+
const args = this.args;
|
|
430
|
+
return {
|
|
431
|
+
get: (name) => args[name] ?? null,
|
|
432
|
+
getString: (name) => { const v = args[name]; return v == null ? null : String(v); },
|
|
433
|
+
getInteger: (name) => { const v = Number(args[name]); return Number.isFinite(v) ? Math.trunc(v) : null; },
|
|
434
|
+
getNumber: (name) => { const v = Number(args[name]); return Number.isFinite(v) ? v : null; },
|
|
435
|
+
getBoolean: (name) => {
|
|
436
|
+
const v = args[name];
|
|
437
|
+
if (typeof v === "boolean")
|
|
438
|
+
return v;
|
|
439
|
+
if (v == null)
|
|
440
|
+
return null;
|
|
441
|
+
return v === "true" || v === 1 || v === "1";
|
|
442
|
+
},
|
|
443
|
+
getUser: (name) => { const v = args[name]; return v == null ? null : String(v); },
|
|
444
|
+
getChannel: (name) => { const v = args[name]; return v == null ? null : String(v); },
|
|
445
|
+
getRole: (name) => { const v = args[name]; return v == null ? null : String(v); },
|
|
446
|
+
/** The chosen subcommand name (for commands with subcommands), or null. */
|
|
447
|
+
getSubcommand: () => { const v = args.subcommand; return v == null ? null : String(v); },
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
/** Respond to the command by posting a message in its channel. Pass
|
|
451
|
+
* `{ ephemeral: true }` to send a private reply only the invoking user sees
|
|
452
|
+
* (content + embeds only) — this resolves to null since no public message is
|
|
453
|
+
* created. */
|
|
454
|
+
async reply(content, opts) {
|
|
421
455
|
const p = toPayload(content);
|
|
456
|
+
if (opts?.ephemeral) {
|
|
457
|
+
await this.client.rest.post(`/channels/${this.channelId}/messages`, { ...p, ephemeral: true, userId: this.userId });
|
|
458
|
+
return null;
|
|
459
|
+
}
|
|
422
460
|
const { message } = await this.client.rest.post(`/channels/${this.channelId}/messages`, p);
|
|
423
461
|
return new Message(this.client, { ...message, content: p.content ?? "", author_id: this.client.user?.id ?? "" });
|
|
424
462
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -144,6 +144,24 @@ export type DistrictEvent = {
|
|
|
144
144
|
message_id: string;
|
|
145
145
|
user_id: string;
|
|
146
146
|
emoji: string;
|
|
147
|
+
} | {
|
|
148
|
+
event: "member.join";
|
|
149
|
+
space_id: string;
|
|
150
|
+
member: {
|
|
151
|
+
user_id: string;
|
|
152
|
+
joined_at?: string | null;
|
|
153
|
+
};
|
|
154
|
+
} | {
|
|
155
|
+
event: "member.leave";
|
|
156
|
+
space_id: string;
|
|
157
|
+
member: {
|
|
158
|
+
user_id: string;
|
|
159
|
+
joined_at?: string | null;
|
|
160
|
+
};
|
|
161
|
+
} | {
|
|
162
|
+
event: "scheduled";
|
|
163
|
+
schedule: string;
|
|
164
|
+
data?: Record<string, unknown>;
|
|
147
165
|
} | {
|
|
148
166
|
event: "interaction.create";
|
|
149
167
|
type?: "command" | "component" | "modal_submit";
|
|
@@ -167,6 +185,27 @@ export interface ReactionEvent {
|
|
|
167
185
|
userId: string;
|
|
168
186
|
emoji: string;
|
|
169
187
|
}
|
|
188
|
+
/** A member joining or leaving a space (`memberJoin` / `memberLeave`). */
|
|
189
|
+
export interface MemberEvent {
|
|
190
|
+
spaceId: string;
|
|
191
|
+
userId: string;
|
|
192
|
+
joinedAt: string | null;
|
|
193
|
+
}
|
|
194
|
+
/** A due scheduled job firing (`scheduled` event). */
|
|
195
|
+
export interface ScheduledEvent {
|
|
196
|
+
/** The schedule name you registered. */
|
|
197
|
+
schedule: string;
|
|
198
|
+
/** The arbitrary data you attached when registering it. */
|
|
199
|
+
data: Record<string, unknown>;
|
|
200
|
+
}
|
|
201
|
+
/** One schedule to register with `client.schedules.set([...])`. */
|
|
202
|
+
export interface ScheduleInput {
|
|
203
|
+
name: string;
|
|
204
|
+
/** How often to fire, in seconds (minimum 60). */
|
|
205
|
+
intervalSeconds: number;
|
|
206
|
+
/** Arbitrary data delivered back on every tick. */
|
|
207
|
+
data?: Record<string, unknown>;
|
|
208
|
+
}
|
|
170
209
|
export interface DeletedMessage {
|
|
171
210
|
id: string;
|
|
172
211
|
channelId: string;
|
package/package.json
CHANGED