summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFuwn <[email protected]>2025-10-15 18:01:16 -0700
committerFuwn <[email protected]>2025-10-15 18:01:16 -0700
commit020782898fa31724da0d751e575936f79b063d30 (patch)
treecd669572feafb346aa36d31cb5b7ab3970bb6dc6
parentfeat(gateway:rtur): Track additional channel (diff)
downloadumabotdiscord-020782898fa31724da0d751e575936f79b063d30.tar.xz
umabotdiscord-020782898fa31724da0d751e575936f79b063d30.zip
feat(gateway:commands): Add gate toggling command
-rw-r--r--packages/gateway/src/commands/commandHandler.ts5
-rw-r--r--packages/gateway/src/commands/verbalGates.ts165
-rw-r--r--packages/gateway/src/listeners/messageCreate/aiCommandHandler/index.ts2
-rw-r--r--packages/interactions/server.ts1
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,