pass around a client promise so renderers can await login
This commit is contained in:
13
packages/reacord/library/create-discord-client.ts
Normal file
13
packages/reacord/library/create-discord-client.ts
Normal file
@@ -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
|
||||||
|
}
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
import type { APIInteraction } from "discord.js"
|
import type { APIInteraction, Client } from "discord.js"
|
||||||
import {
|
import {
|
||||||
Client,
|
|
||||||
GatewayDispatchEvents,
|
GatewayDispatchEvents,
|
||||||
GatewayIntentBits,
|
GatewayIntentBits,
|
||||||
InteractionType,
|
InteractionType,
|
||||||
} from "discord.js"
|
} from "discord.js"
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
|
import { createDiscordClient } from "./create-discord-client"
|
||||||
import type { ReacordInstance } from "./reacord-instance.js"
|
import type { ReacordInstance } from "./reacord-instance.js"
|
||||||
import { ReacordInstancePrivate } from "./reacord-instance.js"
|
import { ReacordInstancePrivate } from "./reacord-instance.js"
|
||||||
import { InstanceProvider } from "./react/instance-context"
|
import { InstanceProvider } from "./react/instance-context"
|
||||||
@@ -54,9 +54,9 @@ export type InteractionInfo = {
|
|||||||
*/
|
*/
|
||||||
export class ReacordClient {
|
export class ReacordClient {
|
||||||
private readonly config: Required<ReacordConfig>
|
private readonly config: Required<ReacordConfig>
|
||||||
private readonly client: Client
|
private readonly discordClientPromise: Promise<Client<true>>
|
||||||
private instances: ReacordInstancePrivate[] = []
|
private instances: ReacordInstancePrivate[] = []
|
||||||
private destroyed = false
|
destroyed = false
|
||||||
|
|
||||||
constructor(config: ReacordConfig) {
|
constructor(config: ReacordConfig) {
|
||||||
this.config = {
|
this.config = {
|
||||||
@@ -64,10 +64,13 @@ export class ReacordClient {
|
|||||||
maxInstances: config.maxInstances ?? 50,
|
maxInstances: config.maxInstances ?? 50,
|
||||||
}
|
}
|
||||||
|
|
||||||
this.client = new Client({ intents: [GatewayIntentBits.Guilds] })
|
this.discordClientPromise = createDiscordClient(this.config.token, {
|
||||||
this.client.login(this.config.token).catch(console.error)
|
intents: [GatewayIntentBits.Guilds],
|
||||||
|
})
|
||||||
|
|
||||||
this.client.ws.on(
|
this.discordClientPromise
|
||||||
|
.then((client) => {
|
||||||
|
client.ws.on(
|
||||||
GatewayDispatchEvents.InteractionCreate,
|
GatewayDispatchEvents.InteractionCreate,
|
||||||
(interaction: APIInteraction) => {
|
(interaction: APIInteraction) => {
|
||||||
if (interaction.type !== InteractionType.MessageComponent) return
|
if (interaction.type !== InteractionType.MessageComponent) return
|
||||||
@@ -76,19 +79,19 @@ export class ReacordClient {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
return client
|
||||||
|
})
|
||||||
|
.catch(console.error)
|
||||||
}
|
}
|
||||||
|
|
||||||
send(channelId: string, initialContent?: React.ReactNode): ReacordInstance {
|
send(channelId: string, initialContent?: React.ReactNode) {
|
||||||
return this.createInstance(
|
return this.createInstance(
|
||||||
new ChannelMessageRenderer(channelId, this.client),
|
new ChannelMessageRenderer(channelId, this.discordClientPromise),
|
||||||
initialContent,
|
initialContent,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
reply(
|
reply(interaction: InteractionInfo, initialContent?: React.ReactNode) {
|
||||||
interaction: InteractionInfo,
|
|
||||||
initialContent?: React.ReactNode,
|
|
||||||
): ReacordInstance {
|
|
||||||
return this.createInstance(
|
return this.createInstance(
|
||||||
new InteractionReplyRenderer(interaction),
|
new InteractionReplyRenderer(interaction),
|
||||||
initialContent,
|
initialContent,
|
||||||
@@ -98,7 +101,7 @@ export class ReacordClient {
|
|||||||
ephemeralReply(
|
ephemeralReply(
|
||||||
interaction: InteractionInfo,
|
interaction: InteractionInfo,
|
||||||
initialContent?: React.ReactNode,
|
initialContent?: React.ReactNode,
|
||||||
): ReacordInstance {
|
) {
|
||||||
return this.createInstance(
|
return this.createInstance(
|
||||||
new EphemeralInteractionReplyRenderer(interaction),
|
new EphemeralInteractionReplyRenderer(interaction),
|
||||||
initialContent,
|
initialContent,
|
||||||
@@ -106,14 +109,11 @@ export class ReacordClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
this.client.destroy()
|
void this.discordClientPromise.then((client) => client.destroy())
|
||||||
this.destroyed = true
|
this.destroyed = true
|
||||||
}
|
}
|
||||||
|
|
||||||
private createInstance(
|
private createInstance(renderer: Renderer, initialContent?: React.ReactNode) {
|
||||||
renderer: Renderer,
|
|
||||||
initialContent?: React.ReactNode,
|
|
||||||
): ReacordInstance {
|
|
||||||
if (this.destroyed) throw new Error("ReacordClient is destroyed")
|
if (this.destroyed) throw new Error("ReacordClient is destroyed")
|
||||||
|
|
||||||
const instance = new ReacordInstancePrivate(renderer)
|
const instance = new ReacordInstancePrivate(renderer)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import type {
|
|||||||
APIMessageComponentSelectMenuInteraction,
|
APIMessageComponentSelectMenuInteraction,
|
||||||
} from "discord.js"
|
} from "discord.js"
|
||||||
import { ComponentType } from "discord.js"
|
import { ComponentType } from "discord.js"
|
||||||
|
import type * as React from "react"
|
||||||
import { Node } from "./node"
|
import { Node } from "./node"
|
||||||
import type { ReacordClient } from "./reacord-client"
|
import type { ReacordClient } from "./reacord-client"
|
||||||
import { ButtonNode } from "./react/button"
|
import { ButtonNode } from "./react/button"
|
||||||
|
|||||||
@@ -19,15 +19,17 @@ export class ChannelMessageRenderer implements Renderer {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly channelId: string,
|
private readonly channelId: string,
|
||||||
private readonly client: Client,
|
private readonly client: Promise<Client<true>>,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
private async getChannel(): Promise<TextChannel> {
|
private async getChannel(): Promise<TextChannel> {
|
||||||
if (this.channel) return this.channel
|
if (this.channel) return this.channel
|
||||||
|
|
||||||
|
const client = await this.client
|
||||||
|
|
||||||
const channel =
|
const channel =
|
||||||
this.client.channels.cache.get(this.channelId) ??
|
client.channels.cache.get(this.channelId) ??
|
||||||
(await this.client.channels.fetch(this.channelId))
|
(await client.channels.fetch(this.channelId))
|
||||||
|
|
||||||
if (!(channel instanceof TextChannel)) {
|
if (!(channel instanceof TextChannel)) {
|
||||||
throw new TypeError(`Channel ${this.channelId} is not a text channel`)
|
throw new TypeError(`Channel ${this.channelId} is not a text channel`)
|
||||||
|
|||||||
Reference in New Issue
Block a user