destroying messages, placeholder for deactivate

This commit is contained in:
itsMapleLeaf
2022-07-23 19:19:13 -05:00
parent 4db32ddbbb
commit 05c940ff52
3 changed files with 120 additions and 25 deletions

View File

@@ -18,13 +18,18 @@ export function createReacordDiscordJs(
const manager = createReacordInstanceManager(options)
return {
send(channelId: string, initialContent?: ReactNode) {
const messageUpdater = createMessageUpdater()
return manager.createInstance(initialContent, async (tree) => {
const handler = createMessageHandler()
return manager.createInstance({
initialContent,
update: async (tree) => {
const messageOptions: MessageOptions & MessageEditOptions = {
content: tree.children.map((child) => child.text).join(""),
}
const channel = await getTextChannel(client, channelId)
await messageUpdater.update(messageOptions, channel)
await handler.update(messageOptions, channel)
},
destroy: () => handler.destroy(),
deactivate: () => handler.deactivate(),
})
},
@@ -34,8 +39,9 @@ export function createReacordDiscordJs(
}
}
function createMessageUpdater() {
function createMessageHandler() {
let message: Message | undefined
let active = true
const queue = createAsyncQueue()
async function update(
@@ -43,6 +49,7 @@ function createMessageUpdater() {
channel: TextBasedChannel,
) {
return queue.add(async () => {
if (!active) return
if (message) {
await message.edit(options)
} else {
@@ -51,10 +58,27 @@ function createMessageUpdater() {
})
}
return { update }
async function destroy() {
return queue.add(async () => {
active = false
await message?.delete()
})
}
async function getTextChannel(client: Client<boolean>, channelId: string) {
async function deactivate() {
return queue.add(async () => {
active = false
// TODO: disable message components
})
}
return { update, destroy, deactivate }
}
async function getTextChannel(
client: Client<boolean>,
channelId: string,
): Promise<TextBasedChannel> {
let channel = client.channels.cache.get(channelId)
if (!channel) {
channel = (await client.channels.fetch(channelId)) ?? undefined

View File

@@ -24,13 +24,31 @@ export type ReacordInstance = {
deactivate: () => void
}
type ReacordInstanceOptions = {
initialContent: ReactNode
update: (tree: MessageTree) => unknown
deactivate: () => unknown
destroy: () => unknown
}
export function createReacordInstanceManager({
maxInstances = 50,
}: ReacordOptions) {
const instances: ReacordInstance[] = []
function createInstance(...args: Parameters<typeof createReacordInstance>) {
const instance = createReacordInstance(...args)
function createInstance(options: ReacordInstanceOptions) {
const instance = createReacordInstance({
...options,
deactivate() {
instances.splice(instances.indexOf(instance), 1)
return options.deactivate()
},
destroy() {
instances.splice(instances.indexOf(instance), 1)
return options.destroy()
},
})
instances.push(instance)
if (instances.length > maxInstances) {
@@ -44,14 +62,13 @@ export function createReacordInstanceManager({
}
function createReacordInstance(
initialContent: ReactNode,
render: (tree: MessageTree) => unknown,
options: ReacordInstanceOptions,
): ReacordInstance {
const tree: MessageTree = {
children: [],
render: async () => {
try {
await render(tree)
await options.update(tree)
} catch (error) {
console.error(
"Reacord encountered an error while updating the message.",
@@ -79,12 +96,30 @@ function createReacordInstance(
render(content: ReactNode) {
reconciler.updateContainer(content, container)
},
destroy() {},
deactivate() {},
async deactivate() {
try {
await options.deactivate()
} catch (error) {
console.error(
"Reacord encountered an error while deactivating an instance.",
error,
)
}
},
async destroy() {
try {
await options.destroy()
} catch (error) {
console.error(
"Reacord encountered an error while destroying an instance.",
error,
)
}
},
}
if (initialContent !== undefined) {
instance.render(initialContent)
if (options.initialContent !== undefined) {
instance.render(options.initialContent)
}
return instance

View File

@@ -3,6 +3,8 @@ import { ChannelType, Client, IntentsBitField } from "discord.js"
import "dotenv/config"
import { kebabCase } from "lodash-es"
import React, { useEffect, useState } from "react"
import { raise } from "../helpers/raise"
import { waitFor } from "../helpers/wait-for"
import { createReacordDiscordJs } from "../library.new/discord-js"
const client = new Client({ intents: IntentsBitField.Flags.Guilds })
@@ -26,6 +28,7 @@ for (const [, channel] of category.children.cache) {
let prefix = 0
const createTest = async (
name: string,
description: string,
block: (channel: TextChannel) => void | Promise<unknown>,
) => {
prefix += 1
@@ -33,10 +36,11 @@ const createTest = async (
type: ChannelType.GuildText,
name: `${String(prefix).padStart(3, "0")}-${kebabCase(name)}`,
})
await channel.edit({ topic: description })
await block(channel)
}
await createTest("basic", (channel) => {
await createTest("basic", "should update over time", (channel) => {
function Timer() {
const [count, setCount] = useState(0)
@@ -53,8 +57,40 @@ await createTest("basic", (channel) => {
reacord.send(channel.id, <Timer />)
})
await createTest("immediate renders", async (channel) => {
await createTest(
"immediate renders",
`should process renders in sequence; this should show "hi moon"`,
async (channel) => {
const instance = reacord.send(channel.id)
instance.render("hi world")
instance.render("hi moon")
},
)
await createTest(
"destroy",
"should remove the message; this channel should be empty",
async (channel) => {
const instance = reacord.send(channel.id)
instance.render("hi world")
instance.render("hi moon")
await waitFor(async () => {
const messages = await channel.messages.fetch({ limit: 1 })
if (messages.first()?.content !== "hi moon") {
raise("not ready")
}
})
instance.destroy()
},
)
await createTest(
"immediate destroy",
"should never show if called immediately; this channel should be empty",
async (channel) => {
const instance = reacord.send(channel.id)
instance.render("hi world")
instance.render("hi moon")
instance.destroy()
},
)