diff --git a/packages/reacord/library/create-discord-client.ts b/packages/reacord/library/create-discord-client.ts new file mode 100644 index 0000000..09f966a --- /dev/null +++ b/packages/reacord/library/create-discord-client.ts @@ -0,0 +1,13 @@ +import type { ClientOptions } from "discord.js" +import { Client } from "discord.js" +import { once } from "node:events" + +export async function createDiscordClient( + token: string, + options: ClientOptions, +) { + const client = new Client(options) + await client.login(token) + const [readyClient] = await once(client, "ready") + return readyClient +} diff --git a/packages/reacord/library/reacord-client.ts b/packages/reacord/library/reacord-client.ts index 16a76e4..b60d6e9 100644 --- a/packages/reacord/library/reacord-client.ts +++ b/packages/reacord/library/reacord-client.ts @@ -1,11 +1,11 @@ -import type { APIInteraction } from "discord.js" +import type { APIInteraction, Client } from "discord.js" import { - Client, GatewayDispatchEvents, GatewayIntentBits, InteractionType, } from "discord.js" import * as React from "react" +import { createDiscordClient } from "./create-discord-client" import type { ReacordInstance } from "./reacord-instance.js" import { ReacordInstancePrivate } from "./reacord-instance.js" import { InstanceProvider } from "./react/instance-context" @@ -54,9 +54,9 @@ export type InteractionInfo = { */ export class ReacordClient { private readonly config: Required - private readonly client: Client + private readonly discordClientPromise: Promise> private instances: ReacordInstancePrivate[] = [] - private destroyed = false + destroyed = false constructor(config: ReacordConfig) { this.config = { @@ -64,31 +64,34 @@ export class ReacordClient { maxInstances: config.maxInstances ?? 50, } - this.client = new Client({ intents: [GatewayIntentBits.Guilds] }) - this.client.login(this.config.token).catch(console.error) + this.discordClientPromise = createDiscordClient(this.config.token, { + intents: [GatewayIntentBits.Guilds], + }) - this.client.ws.on( - GatewayDispatchEvents.InteractionCreate, - (interaction: APIInteraction) => { - if (interaction.type !== InteractionType.MessageComponent) return - for (const instance of this.instances) { - instance.handleInteraction(interaction, this) - } - }, - ) + this.discordClientPromise + .then((client) => { + client.ws.on( + GatewayDispatchEvents.InteractionCreate, + (interaction: APIInteraction) => { + if (interaction.type !== InteractionType.MessageComponent) return + for (const instance of this.instances) { + instance.handleInteraction(interaction, this) + } + }, + ) + return client + }) + .catch(console.error) } - send(channelId: string, initialContent?: React.ReactNode): ReacordInstance { + send(channelId: string, initialContent?: React.ReactNode) { return this.createInstance( - new ChannelMessageRenderer(channelId, this.client), + new ChannelMessageRenderer(channelId, this.discordClientPromise), initialContent, ) } - reply( - interaction: InteractionInfo, - initialContent?: React.ReactNode, - ): ReacordInstance { + reply(interaction: InteractionInfo, initialContent?: React.ReactNode) { return this.createInstance( new InteractionReplyRenderer(interaction), initialContent, @@ -98,7 +101,7 @@ export class ReacordClient { ephemeralReply( interaction: InteractionInfo, initialContent?: React.ReactNode, - ): ReacordInstance { + ) { return this.createInstance( new EphemeralInteractionReplyRenderer(interaction), initialContent, @@ -106,14 +109,11 @@ export class ReacordClient { } destroy() { - this.client.destroy() + void this.discordClientPromise.then((client) => client.destroy()) this.destroyed = true } - private createInstance( - renderer: Renderer, - initialContent?: React.ReactNode, - ): ReacordInstance { + private createInstance(renderer: Renderer, initialContent?: React.ReactNode) { if (this.destroyed) throw new Error("ReacordClient is destroyed") const instance = new ReacordInstancePrivate(renderer) diff --git a/packages/reacord/library/reacord-instance.ts b/packages/reacord/library/reacord-instance.ts index 80bb1f9..dca8b75 100644 --- a/packages/reacord/library/reacord-instance.ts +++ b/packages/reacord/library/reacord-instance.ts @@ -4,6 +4,7 @@ import type { APIMessageComponentSelectMenuInteraction, } from "discord.js" import { ComponentType } from "discord.js" +import type * as React from "react" import { Node } from "./node" import type { ReacordClient } from "./reacord-client" import { ButtonNode } from "./react/button" diff --git a/packages/reacord/library/renderer.ts b/packages/reacord/library/renderer.ts index 96efe59..1bab8ee 100644 --- a/packages/reacord/library/renderer.ts +++ b/packages/reacord/library/renderer.ts @@ -19,15 +19,17 @@ export class ChannelMessageRenderer implements Renderer { constructor( private readonly channelId: string, - private readonly client: Client, + private readonly client: Promise>, ) {} private async getChannel(): Promise { if (this.channel) return this.channel + const client = await this.client + const channel = - this.client.channels.cache.get(this.channelId) ?? - (await this.client.channels.fetch(this.channelId)) + client.channels.cache.get(this.channelId) ?? + (await client.channels.fetch(this.channelId)) if (!(channel instanceof TextChannel)) { throw new TypeError(`Channel ${this.channelId} is not a text channel`)