diff --git a/app/routes/patrons.tsx b/app/routes/patrons.tsx index 3845a25de..aa7d9094a 100644 --- a/app/routes/patrons.tsx +++ b/app/routes/patrons.tsx @@ -1,7 +1,9 @@ -import type { ActionFunction } from "@remix-run/node"; +import type { ActionFunction, LoaderArgs } from "@remix-run/node"; +import { db } from "~/db"; import { getUser } from "~/modules/auth"; import { updatePatreonData } from "~/modules/patreon"; import { canAccessLohiEndpoint, canPerformAdminActions } from "~/permissions"; +import { validate } from "~/utils/remix"; export const action: ActionFunction = async ({ request }) => { const user = await getUser(request); @@ -14,3 +16,9 @@ export const action: ActionFunction = async ({ request }) => { return null; }; + +export const loader = ({ request }: LoaderArgs) => { + validate(canAccessLohiEndpoint(request), 403); + + return db.users.findAllPatrons(); +}; diff --git a/discord-bot/commands/color.ts b/discord-bot/commands/color.ts new file mode 100644 index 000000000..a63ca8c4f --- /dev/null +++ b/discord-bot/commands/color.ts @@ -0,0 +1,78 @@ +import { SlashCommandBuilder } from "@discordjs/builders"; +import type { GuildMemberRoleManager } from "discord.js"; +import ids from "../ids"; +import type { BotCommand } from "../types"; +import { patronsFiveDollarsAndOver } from "../utils"; + +const COMMAND_NAME = "color"; +const ACTION_ARG = "hex"; +const ADMIN_DISCORD_ID = "79237403620945920"; + +export const colorCommand: BotCommand = { + guilds: [ids.guilds.plusServer, ids.guilds.sendou], + name: COMMAND_NAME, + builder: new SlashCommandBuilder() + .setName(COMMAND_NAME) + .setDescription( + "Get/update color role (Supporter tier on Patreon or higher needed)" + ) + .addStringOption((option) => + option + .setName(ACTION_ARG) + .setDescription("Hex code of the color role") + .setRequired(true) + ), + execute: async ({ interaction, client }) => { + let hexCode = interaction.options.getString(ACTION_ARG); + if (!hexCode.startsWith("#")) { + hexCode = `#${hexCode}`; + } + + if (!validateHexCode(hexCode)) { + return interaction.reply({ + content: + "Hex code is invalid. Google color picker to find a valid one or DM Sendou for help.", + ephemeral: true, + }); + } + + const patrons = await patronsFiveDollarsAndOver(); + + if ( + !patrons.some((p) => p.discordId === interaction.user.id) && + interaction.user.id !== ADMIN_DISCORD_ID + ) { + return interaction.reply({ + content: + "This command is only available to patrons of 'Supporter' tier or higher https://www.patreon.com/sendou", + ephemeral: true, + }); + } + + const memberRoles = interaction.member?.roles as GuildMemberRoleManager; + + const roleToUpdate = memberRoles.cache.find((role) => + role.name.includes("!") + ); + + if (roleToUpdate) { + await roleToUpdate.edit({ color: hexCode }); + } else { + const role = await interaction.guild!.roles.create({ + color: hexCode, + name: `${interaction.user.username}!`, + }); + + await memberRoles.add(role); + } + + return interaction.reply({ + content: roleToUpdate ? "Role updated" : "Role created", + ephemeral: true, + }); + }, +}; + +function validateHexCode(hexCode: string) { + return /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/.test(hexCode); +} diff --git a/discord-bot/commands/index.ts b/discord-bot/commands/index.ts index 07e1bf66b..afed8ea01 100644 --- a/discord-bot/commands/index.ts +++ b/discord-bot/commands/index.ts @@ -4,7 +4,8 @@ import { accessCommand } from "./access"; import { plusCommand } from "./plus"; import { updateAllCommand } from "./updateall"; import { pingRolesCommand } from "./pings"; -import type { BotCommand } from "discord-bot/types"; +import { colorCommand } from "./color"; +import type { BotCommand } from "../types"; export const commands = [ questionCommand, @@ -13,6 +14,7 @@ export const commands = [ plusCommand, updateAllCommand, pingRolesCommand, + colorCommand, ]; export const commandsMap = Object.fromEntries(commands.map((c) => [c.name, c])); diff --git a/discord-bot/utils.ts b/discord-bot/utils.ts index ad0cfa224..e26ae180a 100644 --- a/discord-bot/utils.ts +++ b/discord-bot/utils.ts @@ -25,6 +25,18 @@ export async function usersWithAccess(): Promise { return response.json(); } +export async function patronsFiveDollarsAndOver(): Promise { + const response = await sendouInkFetch("/patrons"); + + if (!response.ok) { + throw new Error( + `Failed to fetch users. Response status was ${response.status}` + ); + } + + return (await response.json()).filter((user) => user.patronTier >= 2); +} + const memberRoles = [ "", ids.roles.plusOne,