pass around a client promise so renderers can await login

This commit is contained in:
itsMapleLeaf
2022-08-05 11:44:23 -05:00
parent f58ec8d776
commit 3bd0b33750
4 changed files with 46 additions and 30 deletions

View 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
}

View File

@@ -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)

View File

@@ -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"

View File

@@ -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`)