summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFuwn <[email protected]>2025-10-27 22:20:07 -0700
committerFuwn <[email protected]>2025-10-27 22:20:07 -0700
commitfc6278e4cbe00acba0c8bdbe645df85e607af667 (patch)
tree3d6b2d128aca38064911cf8cb2032c019ffb21e9
parentfeat(listeners:autoDeletion): Add additional banned strings (diff)
downloadumabotdiscord-fc6278e4cbe00acba0c8bdbe645df85e607af667.tar.xz
umabotdiscord-fc6278e4cbe00acba0c8bdbe645df85e607af667.zip
feat(gateway:commands): Add timeout command
-rw-r--r--packages/gateway/src/commands/commandHandler.ts2
-rw-r--r--packages/gateway/src/commands/timeout.ts120
2 files changed, 122 insertions, 0 deletions
diff --git a/packages/gateway/src/commands/commandHandler.ts b/packages/gateway/src/commands/commandHandler.ts
index 163568e..7442c47 100644
--- a/packages/gateway/src/commands/commandHandler.ts
+++ b/packages/gateway/src/commands/commandHandler.ts
@@ -11,6 +11,7 @@ import { handleVerbalGatesCommand } from "./verbalGates";
import { handleWebhookCommand } from "./webhook";
import { handleDeleteWebhookCommand } from "./deleteWebhook";
import { handleCharacterClaimUsageCommand } from "./characterClaimUsage";
+import { handleTimeoutCommand } from "./timeout";
export const handleCommandHandler = (client: Client) => {
client.on(Events.MessageCreate, async (message: Message) => {
@@ -33,6 +34,7 @@ export const handleCommandHandler = (client: Client) => {
handleWebhookCommand(message),
handleDeleteWebhookCommand(message),
handleCharacterClaimUsageCommand(message),
+ handleTimeoutCommand(message),
]);
});
};
diff --git a/packages/gateway/src/commands/timeout.ts b/packages/gateway/src/commands/timeout.ts
new file mode 100644
index 0000000..d43d6bd
--- /dev/null
+++ b/packages/gateway/src/commands/timeout.ts
@@ -0,0 +1,120 @@
+import { Message } from "discord.js";
+import { CENTRAL_GUILD_ID, ROLEPLAY_GUILD_ID } from "../constants";
+import { logUnexpectedDiscordAPIError, replyWithCleanup } from "../utilities";
+
+const parseDuration = (duration: string): number | null => {
+ const match = duration.match(/^(\d+)([smhd])$/);
+
+ if (!match) return null;
+
+ const value = parseInt(match[1]);
+ const unit = match[2];
+
+ switch (unit) {
+ case "s":
+ return value * 1000;
+ case "m":
+ return value * 60 * 1000;
+ case "h":
+ return value * 60 * 60 * 1000;
+ case "d":
+ return value * 24 * 60 * 60 * 1000;
+ default:
+ return null;
+ }
+};
+
+const getUserId = (input: string): string | null => {
+ const mentionMatch = input.match(/^<@(\d+)>$/);
+
+ if (mentionMatch) return mentionMatch[1];
+
+ const userIdMatch = input.match(/^\d+$/);
+
+ if (userIdMatch) return input;
+
+ return null;
+};
+
+const getGuild = (client: any, serverId: string) => {
+ if (serverId === "central" || serverId === CENTRAL_GUILD_ID)
+ return client.guilds.cache.get(CENTRAL_GUILD_ID);
+
+ if (serverId === "roleplay" || serverId === ROLEPLAY_GUILD_ID)
+ return client.guilds.cache.get(ROLEPLAY_GUILD_ID);
+
+ return null;
+};
+
+export const handleTimeoutCommand = async (message: Message) => {
+ if (message.author.bot) return;
+
+ if (message.content.toLowerCase().startsWith("uma!timeout")) {
+ const application = await message.client.application?.fetch();
+ const ownerId = application?.owner?.id;
+
+ if (message.author.id !== ownerId) return;
+
+ const parameters = message.content.split(" ").slice(1);
+
+ if (parameters.length < 3) {
+ await replyWithCleanup(
+ message,
+ "❌ Usage: `uma!timeout <server_id|central|roleplay> <user_id|@mention> <duration> [reason]`\n\n**Examples:**\n- `uma!timeout central @user 1h Being toxic`\n- `uma!timeout roleplay 1234567890123456789 6h`\n- `uma!timeout 1406422617724026901 @user 1d Spam`\n\n**Duration format:** Use suffix: `s` (seconds), `m` (minutes), `h` (hours), `d` (days)",
+ );
+
+ return;
+ }
+
+ const [serverInput, userInput, durationInput, ...reasonParts] = parameters;
+ const reason = reasonParts.join(" ") || undefined;
+ const durationMs = parseDuration(durationInput);
+
+ if (!durationMs) {
+ await replyWithCleanup(
+ message,
+ "❌ Invalid duration format. Use: `<number><s|m|h|d>`\nExamples: `1m`, `6h`, `1d`",
+ );
+
+ return;
+ }
+
+ const guild = getGuild(message.client, serverInput);
+
+ if (!guild) {
+ await replyWithCleanup(
+ message,
+ "❌ Invalid server. Use `central`, `roleplay`, or a guild ID.",
+ );
+
+ return;
+ }
+
+ const userId = getUserId(userInput);
+
+ if (!userId) {
+ await replyWithCleanup(
+ message,
+ "❌ Invalid user. Use a user ID or mention: `<@userId>`",
+ );
+
+ return;
+ }
+
+ try {
+ const member = await guild.members.fetch(userId);
+
+ await member.timeout(durationMs, reason);
+ await replyWithCleanup(
+ message,
+ `✅ Successfully timed out ${member.user.tag} in ${guild.name} for ${durationInput}${reason ? ` - ${reason}` : ""}`,
+ );
+ } catch (error) {
+ logUnexpectedDiscordAPIError(error);
+ await replyWithCleanup(
+ message,
+ `❌ Failed to timeout user. ${error instanceof Error ? error.message : "Unknown error"}`,
+ );
+ }
+ }
+};