summaryrefslogtreecommitdiff
path: root/src/server.ts
diff options
context:
space:
mode:
authorFuwn <[email protected]>2025-09-24 18:14:30 -0700
committerFuwn <[email protected]>2025-09-24 18:14:30 -0700
commit2d987046d094cf5eb784c8d79d678bd3efa5eaf9 (patch)
treedd37d395961d9a68e3e1293a89fb46992aab88d1 /src/server.ts
parentstyle: Lint (diff)
downloadumabotdiscord-2d987046d094cf5eb784c8d79d678bd3efa5eaf9.tar.xz
umabotdiscord-2d987046d094cf5eb784c8d79d678bd3efa5eaf9.zip
refactor: Move interactions client to packages directory
Diffstat (limited to 'src/server.ts')
-rw-r--r--src/server.ts741
1 files changed, 0 insertions, 741 deletions
diff --git a/src/server.ts b/src/server.ts
deleted file mode 100644
index a2618fe..0000000
--- a/src/server.ts
+++ /dev/null
@@ -1,741 +0,0 @@
-import { AutoRouter } from "itty-router";
-import { InteractionResponseType, InteractionType } from "discord-interactions";
-import {
- HOT_COMMAND,
- ROLEPLAY_COMMAND,
- NSFW_COMMAND,
- TOP_COMMAND,
- COMPLAIN_COMMAND,
- APPEAL_COMMAND,
- COLOURS_COMMAND,
- ROLEPLAY_SERIOUS_COMMAND,
-} from "./discord/commands.ts";
-import {
- getCutePost,
- getRoleplayPost,
- getNSFWPost,
- getTopPost,
-} from "./reddit.ts";
-import type { TimePeriod } from "./discord/types.ts";
-import type { Environment, DiscordEmbed } from "./discord/interfaces.ts";
-import {
- createPostEmbed,
- createComplaintEmbed,
- createAppealEmbed,
- createRoleDistributionEmbed,
-} from "./discord/embeds.ts";
-import { JSONResponse } from "./discord/responses.ts";
-import { verifyDiscordRequest } from "./discord/verification.ts";
-
-const router = AutoRouter();
-const COMPLAINT_CHANNEL_ID = "1415868433714778204";
-const APPEAL_CHANNEL_ID = "1420340807931531385";
-const SERIOUS_ROLEPLAY_ROLE_ID = "1418311833303122021";
-const ROLE_MANAGER_ROLE_ID = "1410993207608873070";
-const GUILD_ID = "1406422617724026901";
-const COLOR_ROLE_IDS = [
- "1407075059830624406", // Nice Nature Red
- "1407075160250650664", // Taiki Shuttle Green
- "1407075256904187997", // Mejiro McQueen Purple
- "1407075372427640952", // Gold Ship Grey
- "1407075670177091664", // Grass Wonder Gold
- "1407078154555752589", // Agnes Tachyon Dark Purple
- "1407345006108475476", // Special Week Salmon
- "1408246546708959403", // Biwahaya Hide Linen
- "1408247166413176943", // Symboli Rudolf Celeste
- "1411128003924332764", // King Halo Dark Blue
- "1413582797284708474", // Matikanetannhauser Lemon
- "1414435043761324042", // Silence Suzuka Sea Green
- "1414454914138116158", // Haru Urara Pink
- "1414455824524247161", // TM Opera O Orange
- "1414456352167825490", // Oguri Cap Buttermilk
- "1414541675396862012", // Kitasan Black Sable
- "1415083621152460832", // Tokai Teio Royal Blue
- "1415520343690575883", // Aston Machan Sienna
- "1415539100315942962", // Super Creek Baby Blue
- "1415539544232824913", // Sakura Bakushin O Lilac
- "1415567915578818723", // El Condor Pasa Biscotti
- "1415592658906124338", // Still in Love Crimson
- "1415593126273224795", // Mayano Top Gun Navy Blue
- "1415797242845200475", // Mr. C.B. Forest Green
- "1416583306698297354", // Seuin Sky Mint
- "1416583690217328660", // Neo Universe Pastel Yellow
- "1416595046249267364", // Manhattan Cafe Jet Black
-];
-
-const sendComplaintToChannel = async (
- environment: Environment,
- embed: DiscordEmbed,
-): Promise<boolean> => {
- const url = `https://discord.com/api/v10/channels/${COMPLAINT_CHANNEL_ID}/messages`;
-
- try {
- const response = await fetch(url, {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- Authorization: `Bot ${environment.DISCORD_TOKEN}`,
- },
- body: JSON.stringify({
- embeds: [embed],
- }),
- });
-
- return response.ok;
- } catch (error) {
- console.error("Error sending complaint to channel:", error);
-
- return false;
- }
-};
-
-const sendAppealToChannel = async (
- environment: Environment,
- embed: DiscordEmbed,
-): Promise<boolean> => {
- const url = `https://discord.com/api/v10/channels/${APPEAL_CHANNEL_ID}/messages`;
-
- try {
- const response = await fetch(url, {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- Authorization: `Bot ${environment.DISCORD_TOKEN}`,
- },
- body: JSON.stringify({
- embeds: [embed],
- }),
- });
-
- return response.ok;
- } catch (error) {
- console.error("Error sending appeal to channel:", error);
-
- return false;
- }
-};
-
-const fetchRoleDistribution = async (
- environment: Environment,
- guildID: string,
-): Promise<Array<{ name: string; count: number }>> => {
- const roleData: Array<{ name: string; count: number }> = [];
-
- try {
- const guildResponse = await fetch(
- `https://discord.com/api/v10/guilds/${guildID}`,
- {
- headers: {
- Authorization: `Bot ${environment.DISCORD_TOKEN}`,
- },
- },
- );
-
- if (!guildResponse.ok) {
- console.error(
- "Failed to fetch guild data:",
- guildResponse.status,
- guildResponse.statusText,
- );
-
- const errorText = await guildResponse.text();
-
- console.error("Error details:", errorText);
-
- return roleData;
- }
-
- const guild = await guildResponse.json();
-
- for (const roleID of COLOR_ROLE_IDS) {
- const role = guild.roles?.find((r: any) => r.id === roleID);
-
- if (role) {
- roleData.push({
- name: role.name,
- count: 0,
- });
- } else {
- console.log(`Role not found: ${roleID}`);
- }
- }
-
- let after = "";
- let hasMore = true;
- let batchCount = 0;
- const maxBatches = 10;
-
- while (hasMore && batchCount < maxBatches) {
- const membersResponse = await fetch(
- `https://discord.com/api/v10/guilds/${guildID}/members?limit=1000${after ? `&after=${after}` : ""}`,
- {
- headers: {
- Authorization: `Bot ${environment.DISCORD_TOKEN}`,
- },
- },
- );
-
- if (membersResponse.status === 429) {
- const retryAfter = membersResponse.headers.get("Retry-After");
- const resetAfter = membersResponse.headers.get(
- "X-RateLimit-Reset-After",
- );
- const scope = membersResponse.headers.get("X-RateLimit-Scope");
-
- console.log(
- `Rate limited! Scope: ${scope}, Retry-After: ${retryAfter}, Reset-After: ${resetAfter}`,
- );
-
- const delayMs = Math.max(
- retryAfter ? parseFloat(retryAfter) * 1000 : 0,
- resetAfter ? parseFloat(resetAfter) * 1000 : 0,
- );
-
- if (delayMs > 0) {
- console.log(`Waiting ${delayMs}ms before retry ...`);
-
- await new Promise((resolve) => setTimeout(resolve, delayMs));
-
- continue;
- }
- }
-
- if (!membersResponse.ok) {
- console.error(
- "Failed to fetch members:",
- membersResponse.status,
- membersResponse.statusText,
- );
-
- const errorText = await membersResponse.text();
-
- console.error("Members error details:", errorText);
-
- break;
- }
-
- const remaining = membersResponse.headers.get("X-RateLimit-Remaining");
- const resetAfter = membersResponse.headers.get("X-RateLimit-Reset-After");
-
- if (remaining === "0" && resetAfter) {
- console.log(`Rate limit bucket empty, waiting ${resetAfter}s...`);
-
- await new Promise((resolve) =>
- setTimeout(resolve, parseFloat(resetAfter) * 1000),
- );
- }
-
- const members = await membersResponse.json();
-
- for (const member of members)
- for (const roleId of member.roles || []) {
- const roleIndex = COLOR_ROLE_IDS.indexOf(roleId);
-
- if (roleIndex !== -1) roleData[roleIndex].count++;
- }
-
- hasMore = members.length === 1000;
-
- if (hasMore && members.length > 0)
- after = members[members.length - 1].user.id;
-
- batchCount += 1;
- }
-
- roleData.sort((a, b) => b.count - a.count);
- } catch (error) {
- console.error("Error fetching role distribution:", error);
- }
-
- return roleData;
-};
-
-router.get("/", (_request: Request, environment: Environment) => {
- return new Response(`👋 ${environment.DISCORD_APPLICATION_ID}`);
-});
-
-router.post("/", async (request: Request, environment: Environment) => {
- const { isValid, interaction } = await server.verifyDiscordRequest(
- request,
- environment,
- );
-
- if (!isValid || !interaction)
- return new Response("Bad request signature.", { status: 401 });
-
- if (interaction.type === InteractionType.PING)
- return new JSONResponse({
- type: InteractionResponseType.PONG,
- });
-
- if (interaction.type === InteractionType.APPLICATION_COMMAND) {
- switch (interaction.data.name.toLowerCase()) {
- case HOT_COMMAND.name.toLowerCase(): {
- try {
- const post = await getCutePost();
- const embed = createPostEmbed(post);
-
- return new JSONResponse({
- type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
- data: {
- embeds: [embed],
- },
- });
- } catch (error) {
- console.error("Error in hot command:", error);
-
- return new JSONResponse({
- type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
- data: {
- content: "❌ No posts found. Try again later!",
- flags: 64,
- },
- });
- }
- }
-
- case ROLEPLAY_COMMAND.name.toLowerCase(): {
- try {
- const post = await getRoleplayPost();
- const embed = createPostEmbed(post);
-
- return new JSONResponse({
- type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
- data: {
- embeds: [embed],
- },
- });
- } catch (error) {
- console.error("Error in roleplay command:", error);
-
- return new JSONResponse({
- type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
- data: {
- content: "❌ No roleplay posts found. Try again later!",
- flags: 64,
- },
- });
- }
- }
-
- case NSFW_COMMAND.name.toLowerCase(): {
- if (!interaction.channel_id || !interaction.channel?.nsfw) {
- return new JSONResponse({
- type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
- data: {
- content: "❌ This command can only be used in NSFW channels.",
- flags: 64,
- },
- });
- }
-
- try {
- const post = await getNSFWPost();
- const embed = createPostEmbed(post);
-
- return new JSONResponse({
- type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
- data: {
- embeds: [embed],
- },
- });
- } catch (error) {
- console.error("Error in NSFW command:", error);
-
- return new JSONResponse({
- type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
- data: {
- content: "❌ No NSFW posts found. Try again later!",
- flags: 64,
- },
- });
- }
- }
-
- case TOP_COMMAND.name.toLowerCase(): {
- try {
- const time =
- (interaction.data.options?.[0]?.value as TimePeriod) || "day";
- const post = await getTopPost(time);
- const embed = createPostEmbed(post);
-
- return new JSONResponse({
- type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
- data: {
- embeds: [embed],
- },
- });
- } catch (error) {
- console.error("Error in top command:", error);
-
- return new JSONResponse({
- type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
- data: {
- content: "❌ No top posts found. Try again later!",
- flags: 64,
- },
- });
- }
- }
-
- case COMPLAIN_COMMAND.name.toLowerCase(): {
- try {
- const complaintMessage = interaction.data.options?.[0]
- ?.value as string;
-
- if (!complaintMessage)
- return new JSONResponse({
- type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
- data: {
- content: "❌ Please provide a message for your complaint.",
- flags: 64,
- },
- });
-
- const complainant = {
- username:
- interaction.member?.user?.username ||
- interaction.user?.username ||
- "Unknown",
- id:
- interaction.member?.user?.id || interaction.user?.id || "Unknown",
- avatar:
- interaction.member?.user?.avatar || interaction.user?.avatar,
- };
- const isDM = !interaction.guild_id;
- const complaintEmbed = createComplaintEmbed(
- complaintMessage,
- complainant,
- Date.now(),
- isDM,
- );
- const success = await sendComplaintToChannel(
- environment,
- complaintEmbed,
- );
-
- if (success) {
- return new JSONResponse({
- type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
- data: {
- content: "✅ Your complaint has been submitted successfully!",
- flags: 64,
- },
- });
- } else {
- return new JSONResponse({
- type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
- data: {
- content:
- "❌ Failed to submit your complaint. Please try again later.",
- flags: 64,
- },
- });
- }
- } catch (error) {
- console.error("Error in complain command:", error);
-
- return new JSONResponse({
- type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
- data: {
- content: "❌ An error occurred while processing your complaint.",
- flags: 64,
- },
- });
- }
- }
-
- case APPEAL_COMMAND.name.toLowerCase(): {
- try {
- const appealMessage = interaction.data.options?.[0]?.value as string;
-
- if (!appealMessage)
- return new JSONResponse({
- type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
- data: {
- content: "❌ Please provide a message for your appeal.",
- flags: 64,
- },
- });
-
- const appellant = {
- username:
- interaction.member?.user?.username ||
- interaction.user?.username ||
- "Unknown",
- id:
- interaction.member?.user?.id || interaction.user?.id || "Unknown",
- avatar:
- interaction.member?.user?.avatar || interaction.user?.avatar,
- };
- const isDM = !interaction.guild_id;
- const appealEmbed = createAppealEmbed(
- appealMessage,
- appellant,
- Date.now(),
- isDM,
- );
- const success = await sendAppealToChannel(environment, appealEmbed);
-
- if (success) {
- return new JSONResponse({
- type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
- data: {
- content:
- "✅ Your appeal has been submitted successfully! A moderator will follow up with you soon.",
- flags: 64,
- },
- });
- } else {
- return new JSONResponse({
- type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
- data: {
- content:
- "❌ Failed to submit your appeal. Please try again later.",
- flags: 64,
- },
- });
- }
- } catch (error) {
- console.error("Error in appeal command:", error);
-
- return new JSONResponse({
- type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
- data: {
- content: "❌ An error occurred while processing your appeal.",
- flags: 64,
- },
- });
- }
- }
-
- case COLOURS_COMMAND.name.toLowerCase(): {
- try {
- if (!interaction.guild_id)
- return new JSONResponse({
- type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
- data: {
- content: "❌ This command can only be used in server channels.",
- flags: 64,
- },
- });
-
- const roleDistribution = await fetchRoleDistribution(
- environment,
- GUILD_ID,
- );
-
- if (roleDistribution.length === 0)
- return new JSONResponse({
- type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
- data: {
- content:
- "❌ Unable to fetch role distribution data. The bot may not have permission to read member lists or the server may not be accessible.",
- flags: 64,
- },
- });
-
- const embed = createRoleDistributionEmbed(roleDistribution);
-
- return new JSONResponse({
- type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
- data: {
- embeds: [embed],
- },
- });
- } catch (error) {
- console.error("Error in colours command:", error);
-
- return new JSONResponse({
- type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
- data: {
- content: "❌ An error occurred while fetching role distribution.",
- flags: 64,
- },
- });
- }
- }
-
- case ROLEPLAY_SERIOUS_COMMAND.name.toLowerCase(): {
- try {
- const member = interaction.member;
- const hasAdminPermission =
- member?.permissions && (parseInt(member.permissions) & 0x8) === 0x8;
- const hasManagerRole = member?.roles?.includes(ROLE_MANAGER_ROLE_ID);
-
- if (!hasAdminPermission && !hasManagerRole)
- return new JSONResponse({
- type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
- data: {
- content:
- "❌ You don't have permission to use this command. Only administrators and role managers can use this command.",
- flags: 64,
- },
- });
-
- const action = interaction.data.options?.[0]?.value as string;
- const targetUserID = interaction.data.options?.[1]?.value as string;
-
- if (!action || !targetUserID)
- return new JSONResponse({
- type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
- data: {
- content:
- "❌ Missing required parameters. Please provide both action and user.",
- flags: 64,
- },
- });
-
- const guild = await fetch(
- `https://discord.com/api/v10/guilds/${GUILD_ID}/members/${targetUserID}`,
- {
- headers: {
- Authorization: `Bot ${environment.DISCORD_TOKEN}`,
- },
- },
- );
-
- if (!guild.ok)
- return new JSONResponse({
- type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
- data: {
- content:
- "❌ Unable to fetch user information. The user may not be in this server.",
- flags: 64,
- },
- });
-
- const targetMember = await guild.json();
- const currentRoles = targetMember.roles || [];
- const hasRole = currentRoles.includes(SERIOUS_ROLEPLAY_ROLE_ID);
- let newRoles = [...currentRoles];
- let actionTaken = "";
-
- switch (action) {
- case "add":
- if (hasRole)
- return new JSONResponse({
- type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
- data: {
- content:
- "❌ The user already has the serious roleplay role.",
- flags: 64,
- },
- });
-
- newRoles.push(SERIOUS_ROLEPLAY_ROLE_ID);
-
- actionTaken = "added";
-
- break;
-
- case "remove":
- if (!hasRole)
- return new JSONResponse({
- type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
- data: {
- content:
- "❌ The user doesn't have the serious roleplay role.",
- flags: 64,
- },
- });
-
- newRoles = newRoles.filter(
- (roleId) => roleId !== SERIOUS_ROLEPLAY_ROLE_ID,
- );
- actionTaken = "removed";
-
- break;
-
- case "toggle":
- if (hasRole) {
- newRoles = newRoles.filter(
- (roleId) => roleId !== SERIOUS_ROLEPLAY_ROLE_ID,
- );
- actionTaken = "removed";
- } else {
- newRoles.push(SERIOUS_ROLEPLAY_ROLE_ID);
-
- actionTaken = "added";
- }
-
- break;
-
- default:
- return new JSONResponse({
- type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
- data: {
- content:
- "❌ Invalid action. Please use 'add', 'remove', or 'toggle'.",
- flags: 64,
- },
- });
- }
-
- const updateResponse = await fetch(
- `https://discord.com/api/v10/guilds/${GUILD_ID}/members/${targetUserID}`,
- {
- method: "PATCH",
- headers: {
- Authorization: `Bot ${environment.DISCORD_TOKEN}`,
- "Content-Type": "application/json",
- },
- body: JSON.stringify({
- roles: newRoles,
- }),
- },
- );
-
- if (!updateResponse.ok) {
- console.error(
- "Failed to update user roles:",
- await updateResponse.text(),
- );
-
- return new JSONResponse({
- type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
- data: {
- content:
- "❌ Failed to update user roles. The bot may not have sufficient permissions.",
- flags: 64,
- },
- });
- }
-
- return new JSONResponse({
- type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
- data: {
- content: `✅ Successfully ${actionTaken} the serious roleplay role for <@${targetUserID}>.`,
- },
- });
- } catch (error) {
- console.error("Error in roleplay-serious command:", error);
-
- return new JSONResponse({
- type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
- data: {
- content: "❌ An error occurred while managing the role.",
- flags: 64,
- },
- });
- }
- }
-
- default:
- return new JSONResponse({ error: "Unknown Type" }, { status: 400 });
- }
- }
-
- console.error("Unknown Type");
-
- return new JSONResponse({ error: "Unknown Type" }, { status: 400 });
-});
-
-router.all("*", () => new Response("Not Found.", { status: 404 }));
-
-const server = {
- verifyDiscordRequest,
- fetch: router.fetch,
-};
-
-export default server;