diff options
| author | Fuwn <[email protected]> | 2025-10-15 18:01:16 -0700 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2025-10-15 18:01:16 -0700 |
| commit | 020782898fa31724da0d751e575936f79b063d30 (patch) | |
| tree | cd669572feafb346aa36d31cb5b7ab3970bb6dc6 | |
| parent | feat(gateway:rtur): Track additional channel (diff) | |
| download | umabotdiscord-020782898fa31724da0d751e575936f79b063d30.tar.xz umabotdiscord-020782898fa31724da0d751e575936f79b063d30.zip | |
feat(gateway:commands): Add gate toggling command
| -rw-r--r-- | packages/gateway/src/commands/commandHandler.ts | 5 | ||||
| -rw-r--r-- | packages/gateway/src/commands/verbalGates.ts | 165 | ||||
| -rw-r--r-- | packages/gateway/src/listeners/messageCreate/aiCommandHandler/index.ts | 2 | ||||
| -rw-r--r-- | packages/interactions/server.ts | 1 |
4 files changed, 173 insertions, 0 deletions
diff --git a/packages/gateway/src/commands/commandHandler.ts b/packages/gateway/src/commands/commandHandler.ts index a684339..b1247c1 100644 --- a/packages/gateway/src/commands/commandHandler.ts +++ b/packages/gateway/src/commands/commandHandler.ts @@ -6,11 +6,16 @@ import { handleReactCommand } from "./react"; import { handleDeleteCommand } from "./delete"; import { handlePinCommand } from "./pin"; import { handleRoleCommand } from "./role"; +import { handleVerbalGatesCommand } from "./verbalGates"; export const handleCommandHandler = (client: Client) => { client.on(Events.MessageCreate, async (message: Message) => { if (message.author.bot) return; + const verbalGatesHandled = await handleVerbalGatesCommand(message); + + (message as any).verbalGatesHandled = verbalGatesHandled; + await Promise.allSettled([ handleSayCommand(message), handleStartCommand(message), diff --git a/packages/gateway/src/commands/verbalGates.ts b/packages/gateway/src/commands/verbalGates.ts new file mode 100644 index 0000000..1fb655b --- /dev/null +++ b/packages/gateway/src/commands/verbalGates.ts @@ -0,0 +1,165 @@ +import { Message } from "discord.js"; +import { replyWithCleanup } from "../utilities"; +import { ROLEPLAY_GUILD_ID } from "../constants"; + +const RACECOURSES_CATEGORY_ID = "1428169113754402836"; +const RACE_PLANNER_ROLE_ID = "1428173099270148173"; + +export const handleVerbalGatesCommand = async ( + message: Message, +): Promise<boolean> => { + if (message.author.bot) return false; + + const botMentioned = message.mentions.users.has( + message.client.user?.id || "", + ); + + if (!botMentioned) return false; + + if (message.guildId !== ROLEPLAY_GUILD_ID) return false; + + const content = message.content.toLowerCase(); + const openMatch = content.match(/open\s+<#(\d+)>/); + const closeMatch = content.match(/close\s+<#(\d+)>/); + const toggleMatch = content.match(/toggle\s+<#(\d+)>/); + const action = openMatch + ? "open" + : closeMatch + ? "close" + : toggleMatch + ? "toggle" + : null; + const channelId = openMatch?.[1] || closeMatch?.[1] || toggleMatch?.[1]; + + if (!action || !channelId) return false; + + const application = await message.client.application?.fetch(); + const ownerId = application?.owner?.id; + const guildOwnerId = message.guild?.ownerId; + const hasAdminPermission = + message.member?.permissions.has("Administrator") ?? false; + const hasOwnerPermission = + message.member?.permissions.has("CreateInstantInvite") ?? false; + const hasRacePlannerRole = + message.member?.roles.cache.has(RACE_PLANNER_ROLE_ID); + const isBotOwner = message.author.id === ownerId; + const isGuildOwner = message.author.id === guildOwnerId; + + if ( + !hasAdminPermission && + !hasOwnerPermission && + !hasRacePlannerRole && + !isBotOwner && + !isGuildOwner + ) { + await replyWithCleanup( + message, + "❌ You don't have permission to use this command. Only owners, administrators, or Race Planners can use this command.", + ); + + return true; + } + + const targetChannel = message.client.channels.cache.get(channelId); + + if ( + !targetChannel || + !targetChannel.isTextBased() || + targetChannel.isThread() + ) { + await replyWithCleanup( + message, + "❌ Channel not found, is not a text channel, or is a thread.", + ); + + return true; + } + + if ( + !("parentId" in targetChannel) || + targetChannel.parentId !== RACECOURSES_CATEGORY_ID + ) { + await replyWithCleanup( + message, + "❌ The specified channel is not a racecourse.", + ); + + return true; + } + + try { + // eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain + const everyoneRoleId = message.guild?.roles.everyone.id!; + const canCurrentlySendMessages = + targetChannel.permissionsFor(everyoneRoleId)?.has("SendMessages") ?? true; + const canCurrentlySendInThreads = + targetChannel + .permissionsFor(everyoneRoleId) + ?.has("SendMessagesInThreads") ?? true; + const gatesCurrentlyOpen = + canCurrentlySendMessages && canCurrentlySendInThreads; + let actualAction = action; + + if (action === "toggle") + actualAction = gatesCurrentlyOpen ? "close" : "open"; + + if (action !== "toggle") { + const requestedStateMatches = + (action === "open" && gatesCurrentlyOpen) || + (action === "close" && !gatesCurrentlyOpen); + + if (requestedStateMatches) { + const channelMention = `<#${targetChannel.id}>`; + const currentState = gatesCurrentlyOpen ? "open" : "closed"; + + await replyWithCleanup( + message, + `ℹ️ The gates for ${channelMention} are already ${currentState}.`, + ); + + try { + await message.react("✅"); + } catch (error) { + console.error("Error reacting to message:", error); + } + + return true; + } + } + + if (actualAction === "close") { + await targetChannel.permissionOverwrites.edit(everyoneRoleId, { + SendMessages: false, + SendMessagesInThreads: false, + }); + } else if (actualAction === "open") { + await targetChannel.permissionOverwrites.edit(everyoneRoleId, { + SendMessages: true, + SendMessagesInThreads: true, + }); + } + + const channelMention = `<#${targetChannel.id}>`; + + await replyWithCleanup( + message, + `✅ I've successfully ${actualAction === "close" ? "closed" : "opened"} the gates for ${channelMention}.`, + ); + + try { + await message.react("✅"); + } catch (error) { + console.error("Error reacting to message:", error); + } + + return true; + } catch (error) { + console.error("Error in verbal gates command:", error); + await replyWithCleanup( + message, + "❌ An error occurred while managing channel permissions.", + ); + + return true; + } +}; diff --git a/packages/gateway/src/listeners/messageCreate/aiCommandHandler/index.ts b/packages/gateway/src/listeners/messageCreate/aiCommandHandler/index.ts index 111c467..893c3b3 100644 --- a/packages/gateway/src/listeners/messageCreate/aiCommandHandler/index.ts +++ b/packages/gateway/src/listeners/messageCreate/aiCommandHandler/index.ts @@ -22,6 +22,8 @@ export const handleAICommand = async (message: Message) => { if (!message.mentions.has(message.client.user!)) return; + if ((message as any).verbalGatesHandled) return; + if (!message.member) return; const hasModeratorRole = message.member.roles.cache.some((role) => diff --git a/packages/interactions/server.ts b/packages/interactions/server.ts index b1b076a..a6cef92 100644 --- a/packages/interactions/server.ts +++ b/packages/interactions/server.ts @@ -31,6 +31,7 @@ import { JSONResponse } from "./discord/responses.ts"; import { verifyDiscordRequest } from "./discord/verification.ts"; import { CENTRAL_GUILD_ID, + ROLEPLAY_GUILD_ID, COLOR_ROLE_IDS, CENTRAL_STAFF_ROLES, CENTRAL_PRIVILEGED_ACCESS_ROLE_ID, |