district.js 0.7.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 CHANGED
@@ -2,9 +2,11 @@ import { EventEmitter } from "node:events";
2
2
  import http from "node:http";
3
3
  import { REST } from "./rest.js";
4
4
  import { Store } from "./store.js";
5
+ import { type EmbedResolvable } from "./embed.js";
6
+ import { type CommandResolvable, type CommandData } from "./commands.js";
5
7
  import { ClientUser, Message, TextChannel, Interaction, User, Space, type ClientContext, type MessageContent } from "./structures.js";
6
8
  import { type EventHeaders } from "./webhook.js";
7
- import type { ClientOptions, ReactionEvent, DeletedMessage } from "./types.js";
9
+ import type { ClientOptions, ReactionEvent, DeletedMessage, MemberEvent, ScheduledEvent, ScheduleInput } from "./types.js";
8
10
  /** The events a Client emits, and their argument tuples. */
9
11
  export interface ClientEvents {
10
12
  ready: [user: ClientUser];
@@ -13,6 +15,9 @@ export interface ClientEvents {
13
15
  messageDelete: [message: DeletedMessage];
14
16
  messageReactionAdd: [reaction: ReactionEvent];
15
17
  messageReactionRemove: [reaction: ReactionEvent];
18
+ memberJoin: [member: MemberEvent];
19
+ memberLeave: [member: MemberEvent];
20
+ scheduled: [event: ScheduledEvent];
16
21
  interactionCreate: [interaction: Interaction];
17
22
  error: [error: Error];
18
23
  }
@@ -36,14 +41,53 @@ export declare class Client extends EventEmitter implements ClientContext {
36
41
  /** Send a message to a channel (text or { content, embeds }). */
37
42
  send: (channelId: string, content: MessageContent) => Promise<Message>;
38
43
  };
39
- /** Fetch users by id. */
44
+ /** Fetch users by id, or DM them. */
40
45
  readonly users: {
41
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
+ }>;
42
54
  };
43
55
  /** Fetch spaces by id. */
44
56
  readonly spaces: {
45
57
  fetch: (id: string) => Promise<Space>;
46
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
+ };
47
91
  /** Validate the token, load the bot's identity, and emit `ready`. */
48
92
  login(): Promise<ClientUser>;
49
93
  /**
package/dist/client.js CHANGED
@@ -4,6 +4,8 @@ import { EventEmitter } from "node:events";
4
4
  import http from "node:http";
5
5
  import { REST } from "./rest.js";
6
6
  import { Store } from "./store.js";
7
+ import { resolveEmbed } from "./embed.js";
8
+ import { resolveCommand } from "./commands.js";
7
9
  import { ClientUser, Message, TextChannel, Interaction, User, Space } from "./structures.js";
8
10
  import { verifyAndParse } from "./webhook.js";
9
11
  export class Client extends EventEmitter {
@@ -45,14 +47,43 @@ export class Client extends EventEmitter {
45
47
  /** Send a message to a channel (text or { content, embeds }). */
46
48
  send: (channelId, content) => new TextChannel(this, channelId).send(content),
47
49
  };
48
- /** Fetch users by id. */
50
+ /** Fetch users by id, or DM them. */
49
51
  users = {
50
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
+ },
51
60
  };
52
61
  /** Fetch spaces by id. */
53
62
  spaces = {
54
63
  fetch: (id) => this.rest.get(`/spaces/${id}`).then((r) => new Space(this, r.space)),
55
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
+ };
56
87
  /** Validate the token, load the bot's identity, and emit `ready`. */
57
88
  async login() {
58
89
  const res = await this.rest.get("/me");
@@ -142,6 +173,15 @@ export class Client extends EventEmitter {
142
173
  case "message.reaction_remove":
143
174
  this.emit("messageReactionRemove", toReaction(evt));
144
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;
145
185
  case "interaction.create":
146
186
  this.emit("interactionCreate", new Interaction(this, evt));
147
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 {};
@@ -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,12 +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
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";
7
8
  export { Collection } from "./collection.js";
8
9
  export { PermissionsBitField, type PermissionResolvable, type PermissionFlagName } from "./permissions.js";
9
10
  export { Formatters } from "./formatters.js";
10
11
  export { ChannelType, type ChannelTypeValue } from "./enums.js";
11
12
  export { verifySignature, parseEvent, verifyAndParse, type EventHeaders } from "./webhook.js";
12
- 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
@@ -10,6 +10,7 @@ export { ClientUser, Message, TextChannel, Interaction, User, Space, SpaceMember
10
10
  export { EmbedBuilder, resolveEmbed } from "./embed.js";
11
11
  export { ButtonBuilder, ActionRowBuilder, StringSelectMenuBuilder, ModalBuilder, TextInputBuilder, resolveComponent, } from "./components.js";
12
12
  export { Store, StoreCollection } from "./store.js";
13
+ export { SlashCommandBuilder, SlashCommandSubcommandBuilder, resolveCommand, } from "./commands.js";
13
14
  export { Collection } from "./collection.js";
14
15
  export { PermissionsBitField } from "./permissions.js";
15
16
  export { Formatters } from "./formatters.js";
@@ -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
- /** Respond to the command by posting a message in its channel. */
250
- reply(content: MessageContent): Promise<Message>;
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
  }
@@ -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
- /** Respond to the command by posting a message in its channel. */
420
- async reply(content) {
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "district.js",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "A powerful Node.js module for building District bots — post, react, run slash commands, and receive events.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",