share more logic between renderers
This commit is contained in:
@@ -1,31 +1,91 @@
|
|||||||
import { AsyncQueue } from "@reacord/helpers/async-queue.js"
|
import { AsyncQueue } from "@reacord/helpers/async-queue.js"
|
||||||
import type { Client, Message } from "discord.js"
|
import type { Client, Message } from "discord.js"
|
||||||
import { TextChannel } from "discord.js"
|
import { TextChannel } from "discord.js"
|
||||||
|
import type { MessageUpdatePayload } from "./make-message-update-payload.js"
|
||||||
import { makeMessageUpdatePayload } from "./make-message-update-payload.js"
|
import { makeMessageUpdatePayload } from "./make-message-update-payload.js"
|
||||||
import type { Node } from "./node.js"
|
import type { Node } from "./node.js"
|
||||||
import type { InteractionInfo } from "./reacord-client.js"
|
import type { InteractionInfo } from "./reacord-client.js"
|
||||||
|
|
||||||
export type Renderer = {
|
export abstract class Renderer {
|
||||||
update(tree: Node): Promise<void>
|
private readonly queue = new AsyncQueue()
|
||||||
deactivate(): Promise<void>
|
private active = true
|
||||||
destroy(): Promise<void>
|
private destroyPromise?: Promise<void>
|
||||||
|
|
||||||
|
protected abstract handleUpdate(payload: MessageUpdatePayload): Promise<void>
|
||||||
|
protected abstract handleDestroy(): Promise<void>
|
||||||
|
protected abstract handleDeactivate(): Promise<void>
|
||||||
|
|
||||||
|
update(tree: Node) {
|
||||||
|
const payload = makeMessageUpdatePayload(tree)
|
||||||
|
return this.queue.add(async () => {
|
||||||
|
if (!this.active) return
|
||||||
|
await this.handleUpdate(payload)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ChannelMessageRenderer implements Renderer {
|
destroy() {
|
||||||
private readonly queue = new AsyncQueue()
|
if (this.destroyPromise) return this.destroyPromise
|
||||||
|
this.active = false
|
||||||
|
|
||||||
|
const promise = this.queue.add(() => this.handleDestroy())
|
||||||
|
|
||||||
|
// if it failed, we'll want to try again
|
||||||
|
promise.catch((error) => {
|
||||||
|
console.error("Failed to destroy message:", error)
|
||||||
|
this.destroyPromise = undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
return (this.destroyPromise = promise)
|
||||||
|
}
|
||||||
|
|
||||||
|
deactivate() {
|
||||||
|
return this.queue.add(async () => {
|
||||||
|
if (!this.active) return
|
||||||
|
|
||||||
|
await this.handleDeactivate()
|
||||||
|
|
||||||
|
// set active to false *after* running deactivation,
|
||||||
|
// so that other queued operations run first,
|
||||||
|
// and we can show the correct deactivated state
|
||||||
|
this.active = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ChannelMessageRenderer extends Renderer {
|
||||||
private channel: TextChannel | undefined
|
private channel: TextChannel | undefined
|
||||||
private message: Message | undefined
|
private message: Message | undefined
|
||||||
private active = true
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly channelId: string,
|
private readonly channelId: string,
|
||||||
private readonly client: Promise<Client<true>>,
|
private readonly clientPromise: Promise<Client<true>>,
|
||||||
) {}
|
) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
|
override async handleUpdate(payload: MessageUpdatePayload): Promise<void> {
|
||||||
|
if (this.message) {
|
||||||
|
await this.message.edit(payload)
|
||||||
|
} else {
|
||||||
|
const channel = await this.getChannel()
|
||||||
|
this.message = await channel.send(payload)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override async handleDestroy(): Promise<void> {
|
||||||
|
const message = this.message
|
||||||
|
this.message = undefined
|
||||||
|
await message?.delete()
|
||||||
|
}
|
||||||
|
|
||||||
|
override async handleDeactivate(): Promise<void> {
|
||||||
|
throw new Error("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
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 client = await this.clientPromise
|
||||||
|
|
||||||
const channel =
|
const channel =
|
||||||
client.channels.cache.get(this.channelId) ??
|
client.channels.cache.get(this.channelId) ??
|
||||||
@@ -38,71 +98,40 @@ export class ChannelMessageRenderer implements Renderer {
|
|||||||
this.channel = channel
|
this.channel = channel
|
||||||
return channel
|
return channel
|
||||||
}
|
}
|
||||||
|
|
||||||
update(tree: Node) {
|
|
||||||
const payload = makeMessageUpdatePayload(tree)
|
|
||||||
return this.queue.add(async () => {
|
|
||||||
if (!this.active) return
|
|
||||||
if (this.message) {
|
|
||||||
await this.message.edit(payload)
|
|
||||||
} else {
|
|
||||||
const channel = await this.getChannel()
|
|
||||||
this.message = await channel.send(payload)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
deactivate() {
|
export class InteractionReplyRenderer extends Renderer {
|
||||||
return this.queue.add(async () => {
|
constructor(private readonly interaction: InteractionInfo) {
|
||||||
if (!this.active) return
|
super()
|
||||||
|
|
||||||
// TODO: disable message components
|
|
||||||
|
|
||||||
// set active to false *after* running deactivation,
|
|
||||||
// so that other queued operations run first,
|
|
||||||
// and we can show the correct deactivated state
|
|
||||||
this.active = false
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
handleUpdate(payload: MessageUpdatePayload): Promise<void> {
|
||||||
this.active = false
|
|
||||||
return this.queue.add(async () => {
|
|
||||||
const message = this.message
|
|
||||||
this.message = undefined
|
|
||||||
await message?.delete()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class InteractionReplyRenderer implements Renderer {
|
|
||||||
constructor(private readonly interaction: InteractionInfo) {}
|
|
||||||
|
|
||||||
update(tree: Node): Promise<void> {
|
|
||||||
throw new Error("Method not implemented.")
|
throw new Error("Method not implemented.")
|
||||||
}
|
}
|
||||||
|
|
||||||
deactivate(): Promise<void> {
|
handleDestroy(): Promise<void> {
|
||||||
throw new Error("Method not implemented.")
|
throw new Error("Method not implemented.")
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy(): Promise<void> {
|
handleDeactivate(): Promise<void> {
|
||||||
throw new Error("Method not implemented.")
|
throw new Error("Method not implemented.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class EphemeralInteractionReplyRenderer implements Renderer {
|
export class EphemeralInteractionReplyRenderer extends Renderer {
|
||||||
constructor(private readonly interaction: InteractionInfo) {}
|
constructor(private readonly interaction: InteractionInfo) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
update(tree: Node): Promise<void> {
|
handleUpdate(payload: MessageUpdatePayload): Promise<void> {
|
||||||
throw new Error("Method not implemented.")
|
throw new Error("Method not implemented.")
|
||||||
}
|
}
|
||||||
|
|
||||||
deactivate(): Promise<void> {
|
handleDestroy(): Promise<void> {
|
||||||
throw new Error("Method not implemented.")
|
throw new Error("Method not implemented.")
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy(): Promise<void> {
|
handleDeactivate(): Promise<void> {
|
||||||
throw new Error("Method not implemented.")
|
throw new Error("Method not implemented.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user