defer update if there's no component update
This commit is contained in:
@@ -84,6 +84,7 @@ function createReacordComponentInteraction(
|
||||
update: async (options) => {
|
||||
await interaction.update(getDiscordMessageOptions(options))
|
||||
},
|
||||
deferUpdate: () => interaction.deferUpdate(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,6 +98,7 @@ function createReacordComponentInteraction(
|
||||
update: async (options) => {
|
||||
await interaction.update(getDiscordMessageOptions(options))
|
||||
},
|
||||
deferUpdate: () => interaction.deferUpdate(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,13 +108,13 @@ function createReacordComponentInteraction(
|
||||
// TODO: this could be a part of the core library,
|
||||
// and also handle some edge cases, e.g. empty messages
|
||||
function getDiscordMessageOptions(
|
||||
options: MessageOptions,
|
||||
reacordOptions: MessageOptions,
|
||||
): Discord.MessageOptions {
|
||||
return {
|
||||
const options: Discord.MessageOptions = {
|
||||
// eslint-disable-next-line unicorn/no-null
|
||||
content: options.content || null,
|
||||
embeds: options.embeds,
|
||||
components: options.actionRows.map((row) => ({
|
||||
content: reacordOptions.content || null,
|
||||
embeds: reacordOptions.embeds,
|
||||
components: reacordOptions.actionRows.map((row) => ({
|
||||
type: "ACTION_ROW",
|
||||
components: row.map(
|
||||
(component): Discord.MessageActionRowComponentOptions => {
|
||||
@@ -143,6 +145,12 @@ function getDiscordMessageOptions(
|
||||
),
|
||||
})),
|
||||
}
|
||||
|
||||
if (!options.content && !options.embeds?.length) {
|
||||
options.content = "_ _"
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
function createReacordMessage(message: Discord.Message): Message {
|
||||
|
||||
@@ -18,6 +18,7 @@ export type ButtonInteraction = {
|
||||
channelId: string
|
||||
customId: string
|
||||
update(options: MessageOptions): Promise<void>
|
||||
deferUpdate(): Promise<void>
|
||||
}
|
||||
|
||||
export type SelectInteraction = {
|
||||
@@ -27,4 +28,5 @@ export type SelectInteraction = {
|
||||
customId: string
|
||||
values: string[]
|
||||
update(options: MessageOptions): Promise<void>
|
||||
deferUpdate(): Promise<void>
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Container } from "./container.js"
|
||||
import type { ComponentInteraction } from "./interaction"
|
||||
import type { Message, MessageOptions } from "./message"
|
||||
import type { Node } from "./node.js"
|
||||
import { Timeout } from "./timeout"
|
||||
|
||||
type UpdatePayload =
|
||||
| { action: "update" | "deactivate"; options: MessageOptions }
|
||||
@@ -20,6 +21,10 @@ export abstract class Renderer {
|
||||
.pipe(concatMap((payload) => this.updateMessage(payload)))
|
||||
.subscribe({ error: console.error })
|
||||
|
||||
private deferUpdateTimeout = new Timeout(500, () => {
|
||||
this.componentInteraction?.deferUpdate().catch(console.error)
|
||||
})
|
||||
|
||||
render() {
|
||||
if (!this.active) {
|
||||
console.warn("Attempted to update a deactivated message")
|
||||
@@ -47,6 +52,7 @@ export abstract class Renderer {
|
||||
|
||||
handleComponentInteraction(interaction: ComponentInteraction) {
|
||||
this.componentInteraction = interaction
|
||||
this.deferUpdateTimeout.run()
|
||||
for (const node of this.nodes) {
|
||||
if (node.handleComponentInteraction(interaction)) {
|
||||
return true
|
||||
@@ -84,6 +90,7 @@ export abstract class Renderer {
|
||||
if (this.componentInteraction) {
|
||||
const promise = this.componentInteraction.update(payload.options)
|
||||
this.componentInteraction = undefined
|
||||
this.deferUpdateTimeout.cancel()
|
||||
await promise
|
||||
return
|
||||
}
|
||||
|
||||
20
library/internal/timeout.ts
Normal file
20
library/internal/timeout.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
export class Timeout {
|
||||
private timeoutId?: NodeJS.Timeout
|
||||
|
||||
constructor(
|
||||
private readonly time: number,
|
||||
private readonly callback: () => void,
|
||||
) {}
|
||||
|
||||
run() {
|
||||
this.cancel()
|
||||
this.timeoutId = setTimeout(this.callback, this.time)
|
||||
}
|
||||
|
||||
cancel() {
|
||||
if (this.timeoutId) {
|
||||
clearTimeout(this.timeoutId)
|
||||
this.timeoutId = undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Client } from "discord.js"
|
||||
import "dotenv/config"
|
||||
import React from "react"
|
||||
import { ReacordDiscordJs } from "../library/main"
|
||||
import { Button, ReacordDiscordJs } from "../library/main"
|
||||
import { createCommandHandler } from "./command-handler"
|
||||
import { Counter } from "./counter"
|
||||
import { FruitSelect } from "./fruit-select"
|
||||
@@ -36,6 +36,16 @@ client.on("ready", () => {
|
||||
})
|
||||
|
||||
createCommandHandler(client, [
|
||||
{
|
||||
name: "button",
|
||||
description: "it's a button",
|
||||
run: (interaction) => {
|
||||
reacord.reply(
|
||||
interaction,
|
||||
<Button label="clic" onClick={() => console.log("was clic")} />,
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "counter",
|
||||
description: "shows a counter button",
|
||||
|
||||
6
test/renderer.test.ts
Normal file
6
test/renderer.test.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
// test that the interaction update is _eventually_ deferred if there's no component update,
|
||||
// and that update isn't called after the fact
|
||||
// ...somehow
|
||||
test.todo("defer update timeout")
|
||||
|
||||
export {}
|
||||
4
todo.md
4
todo.md
@@ -28,7 +28,7 @@
|
||||
# internal
|
||||
|
||||
- [ ] combine `MessageOptions` and `Message` into a single message object (?)
|
||||
- [ ] consider always calling `deferUpdate` on component interactions
|
||||
- [x] consider always calling `deferUpdate` on component interactions
|
||||
|
||||
# cool ideas / polish
|
||||
|
||||
@@ -45,4 +45,4 @@
|
||||
- [ ] uncontrolled select
|
||||
- [x] single class/helper function for testing `ReacordTester`
|
||||
- [ ] handle deletion outside of reacord
|
||||
- [ ] for more easily writing adapters, address discord API nuances at the reacord level instead of the adapter level. the goal being that adapters can just take the objects and send them to discord
|
||||
- [ ] for more easily writing adapters, address discord API nuances at the reacord level instead of the adapter level. the goal being that adapters can just take the objects and send them to discord. probably make use of discord api types for this
|
||||
|
||||
Reference in New Issue
Block a user