diff --git a/bun.lockb b/bun.lockb index 276287a..287f7c1 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 3abecd6..cc3ee23 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,8 @@ "dependencies": { "@uwu/configmasher": "latest", "discord.js": "^14.15.3", - "ofetch": "^1.4.1" + "ofetch": "^1.4.1", + "throttle-debounce": "^5.0.2" }, "trustedDependencies": [ "@biomejs/biome" diff --git a/src/commands/util/close.ts b/src/commands/util/close.ts index c29f17f..89a1257 100644 --- a/src/commands/util/close.ts +++ b/src/commands/util/close.ts @@ -17,6 +17,17 @@ import { const getStateWord = (close) => (close ? "closed" : "reopened"); const getStateVerb = (close) => (close ? "close" : "reopen"); +export function getTagsForCloseState(close: boolean) { + return { + tagToAdd: close + ? config.helpChannel.closedTag + : config.helpChannel.openedTag, + tagToRemove: close + ? config.helpChannel.openedTag + : config.helpChannel.closedTag, + }; +} + export async function handleIssueState( interaction: ChatInputCommandInteraction, close = true, @@ -29,12 +40,7 @@ export async function handleIssueState( const stateWord = getStateWord(close); const stateVerb = getStateVerb(close); - const tagToAdd = close - ? config.helpChannel.closedTag - : config.helpChannel.openedTag; - const tagToRemove = close - ? config.helpChannel.openedTag - : config.helpChannel.closedTag; + const { tagToAdd, tagToRemove } = getTagsForCloseState(close); const postTags = threadChannel.appliedTags; diff --git a/src/events/channels.ts b/src/events/channels.ts new file mode 100644 index 0000000..f1e2e1b --- /dev/null +++ b/src/events/channels.ts @@ -0,0 +1,80 @@ +import { config } from "../lib/config.js"; +import { getTagsForCloseState } from "../commands/util/close.js"; +import { isHelpPost } from "../lib/discord/channels.js"; + +import { debounce } from "throttle-debounce"; + +import { type Client, Events, type ThreadChannel } from "discord.js"; + +// Map to store initial thread states +const threadUpdateMap = new Map(); + +// Create a debounced handler for processing thread updates +const handleEvent = debounce( + 1000, + async (threadId: string, newThread: ThreadChannel) => { + const initialThread = threadUpdateMap.get(threadId); + if (!initialThread) return; + + // Remove from map + threadUpdateMap.delete(threadId); + + // Handle tag additions + const addedTags = newThread.appliedTags.filter( + (t) => !initialThread.appliedTags.includes(t), + ); + if (addedTags.length > 0) { + for (const tag of addedTags) { + // If closed/opened tag is added, remove the opposite tag + if ( + tag === config.helpChannel.closedTag || + tag === config.helpChannel.openedTag + ) { + const isClose = tag === config.helpChannel.closedTag; + const { tagToRemove } = getTagsForCloseState(isClose); + if (newThread.appliedTags.includes(tagToRemove)) { + await newThread.setAppliedTags( + newThread.appliedTags.filter((t) => t !== tagToRemove), + ); + } + } + } + } + + // Handle tag removals + const removedTags = initialThread.appliedTags.filter( + (t) => !newThread.appliedTags.includes(t), + ); + if (removedTags.length > 0) { + for (const tag of removedTags) { + // If closed or opened tag is removed, add it back only if its opposite isn't present + if ( + tag === config.helpChannel.closedTag || + tag === config.helpChannel.openedTag + ) { + const isClose = tag === config.helpChannel.closedTag; + const { tagToRemove } = getTagsForCloseState(isClose); + if (!newThread.appliedTags.includes(tagToRemove)) { + await newThread.setAppliedTags([...newThread.appliedTags, tag]); + } + } + } + } + }, +); + +export default function registerEvents(client: Client) { + client.on(Events.ThreadUpdate, async (oldThread, newThread) => { + if (!(await isHelpPost(newThread))) { + return; + } + + // Store the initial state if this is the first update + if (!threadUpdateMap.has(newThread.id)) { + threadUpdateMap.set(newThread.id, oldThread); + } + + // Trigger the debounced handler + handleEvent(newThread.id, newThread); + }); +} diff --git a/src/index.ts b/src/index.ts index 8197d75..140f792 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,6 +3,7 @@ import { config } from "./lib/config.js"; import registerCommandEvents from "./events/commands.js"; import registerWalkthroughEvents from "./events/walkthrough.js"; import registerMessageEvents from "./events/messages.js"; +import registerChannelEvents from "./events/channels.js"; import { Client, Events, GatewayIntentBits, ActivityType } from "discord.js"; @@ -37,6 +38,7 @@ client.once(Events.ClientReady, () => { registerCommandEvents(client); registerWalkthroughEvents(client); registerMessageEvents(client); + registerChannelEvents(client); shufflePresence(); setInterval(shufflePresence, config.presenceDelay);