diff --git a/app/db/models/users.server.ts b/app/db/models/users.server.ts index b4b8bdbca..e7df9dc96 100644 --- a/app/db/models/users.server.ts +++ b/app/db/models/users.server.ts @@ -57,6 +57,29 @@ export function updateProfile(args: Pick) { updateProfileStm.run(args); } +const updateByDiscordIdStm = sql.prepare(` + update "User" + set "discordAvatar" = $discordAvatar, + "discordName" = $discordName, + "discordDiscriminator" = $discordDiscriminator + where "discordId" = $discordId +`); + +export const updateMany = sql.transaction( + ( + argsArr: Array< + Pick< + User, + "discordAvatar" | "discordName" | "discordDiscriminator" | "discordId" + > + > + ) => { + for (const updateArgs of argsArr) { + updateByDiscordIdStm.run(updateArgs); + } + } +); + const deleteAllPatronDataStm = sql.prepare(` update "User" set "patronTier" = null, diff --git a/app/routes/faq.tsx b/app/routes/faq.tsx index 36a09ccae..a6fa87d7c 100644 --- a/app/routes/faq.tsx +++ b/app/routes/faq.tsx @@ -33,6 +33,7 @@ export default function FAQPage() { a score below 50% they get demoted a tier or in the case of +3 kicked.

+
How to get a badge prize for my event? @@ -48,6 +49,27 @@ export default function FAQPage() { your idea.

+ +
+ + How to update my avatar or username? + +

+ Updating username or avatar on Discord doesn't right away update + them on sendou.ink. To make that happen you have two options: +

+
    +
  1. + If you are a member of this website's Discord or the Plus + Server you can simply wait. There is a routine that runs once a day + that handles the updating. +
  2. +
  3. + Alternatively if you want to update them right away you can log out + and back in on sendou.ink. +
  4. +
+
); } diff --git a/app/routes/users.tsx b/app/routes/users.tsx index ca2445753..89ecf6530 100644 --- a/app/routes/users.tsx +++ b/app/routes/users.tsx @@ -1,9 +1,21 @@ -import type { LoaderFunction } from "@remix-run/node"; +import type { ActionFunction, LoaderFunction } from "@remix-run/node"; import { json } from "@remix-run/node"; import { db } from "~/db"; import type { UserWithPlusTier } from "~/db/types"; +import { canAccessLohiEndpoint } from "~/permissions"; import { discordFullName } from "~/utils/strings"; +export const action: ActionFunction = async ({ request }) => { + if (!canAccessLohiEndpoint(request)) { + throw new Response(null, { status: 403 }); + } + + // input untyped but we trust Lohi to give us correctly shaped request here + db.users.updateMany(await request.json()); + + return null; +}; + export interface UsersLoaderData { users: ({ discordFullName: string; diff --git a/discord-bot/commands/index.ts b/discord-bot/commands/index.ts index 47a13777a..4149e39d7 100644 --- a/discord-bot/commands/index.ts +++ b/discord-bot/commands/index.ts @@ -2,6 +2,7 @@ import { questionCommand } from "./q"; import { lfgRoleCommand } from "./lfg"; import { accessCommand } from "./access"; import { plusCommand } from "./plus"; +import { updateAllCommand } from "./updateall"; import type { BotCommand } from "discord-bot/types"; export const commands = [ @@ -9,6 +10,7 @@ export const commands = [ lfgRoleCommand, accessCommand, plusCommand, + updateAllCommand, ]; export const commandsMap = Object.fromEntries(commands.map((c) => [c.name, c])); diff --git a/discord-bot/commands/plus.ts b/discord-bot/commands/plus.ts index e35050cb1..8a954a5f4 100644 --- a/discord-bot/commands/plus.ts +++ b/discord-bot/commands/plus.ts @@ -3,12 +3,11 @@ import { Client, GuildMember, Role } from "discord.js"; import invariant from "tiny-invariant"; import ids from "../ids"; import type { BotCommand } from "../types"; -import { isPlusTierRoleId, plusTierToRoleId, usersWithAccess } from "../utils"; +import { plusTierToRoleId, usersWithAccess } from "../utils"; const COMMAND_NAME = "plus"; const ACTION_ARG = "dry"; -// doesn't seem to remove all roles... export const plusCommand: BotCommand = { guilds: [ids.guilds.adminServer], name: COMMAND_NAME, diff --git a/discord-bot/commands/updateall.ts b/discord-bot/commands/updateall.ts new file mode 100644 index 000000000..cf2ce2967 --- /dev/null +++ b/discord-bot/commands/updateall.ts @@ -0,0 +1,66 @@ +import { SlashCommandBuilder } from "@discordjs/builders"; +import { Client } from "discord.js"; +import invariant from "tiny-invariant"; +import type { User } from "../../app/db/types"; +import ids from "../ids"; +import type { BotCommand } from "../types"; +import { sendouInkFetch } from "../utils"; + +const COMMAND_NAME = "updateall"; + +const guildsToCrawlForUpdates = [ids.guilds.plusServer, ids.guilds.sendou]; + +export const updateAllCommand: BotCommand = { + guilds: [ids.guilds.adminServer], + name: COMMAND_NAME, + builder: new SlashCommandBuilder() + .setName(COMMAND_NAME) + .setDescription("Update sendou.ink usernames and avatars"), + // @ts-expect-error TODO: fix. Library doesn't seem to extract API Message type so I could fix this error? + execute: async ({ interaction, client }) => { + await interaction.deferReply({ ephemeral: true }); + + const userUpdates = await getUsersToUpdate(client); + const response = await sendouInkFetch("/users", { + method: "post", + body: JSON.stringify(userUpdates), + }); + + if (!response.ok) { + return interaction.editReply( + `Update failed with status code ${response.status}` + ); + } + + return interaction.editReply(`Sent ${userUpdates.length} users`); + }, +}; + +async function getUsersToUpdate(client: Client) { + const usersSeen = new Set(); + const userUpdates: Array< + Pick< + User, + "discordId" | "discordName" | "discordDiscriminator" | "discordAvatar" + > + > = []; + for (const guildId of guildsToCrawlForUpdates) { + const guild = client.guilds.cache.find((g) => g.id === guildId); + invariant(guild); + + for (const [, { user }] of await guild.members.fetch()) { + if (usersSeen.has(user.id)) continue; + + userUpdates.push({ + discordId: user.id, + discordAvatar: user.avatar, + discordDiscriminator: user.discriminator, + discordName: user.username, + }); + + usersSeen.add(user.id); + } + } + + return userUpdates; +} diff --git a/discord-bot/utils.ts b/discord-bot/utils.ts index 932563671..ad0cfa224 100644 --- a/discord-bot/utils.ts +++ b/discord-bot/utils.ts @@ -3,13 +3,18 @@ import { LOHI_TOKEN_HEADER_NAME } from "~/constants"; import type { PlusListLoaderData } from "~/routes/plus/list"; import ids from "./ids"; -export async function usersWithAccess(): Promise { +export function sendouInkFetch(path: string, init?: RequestInit) { invariant(process.env["SENDOU_INK_URL"], "SENDOU_INK_URL is not set"); invariant(process.env["LOHI_TOKEN"], "LOHI_TOKEN is not set"); - const response = await fetch(`${process.env["SENDOU_INK_URL"]}/plus/list`, { + return fetch(`${process.env["SENDOU_INK_URL"]}${path}`, { headers: [[LOHI_TOKEN_HEADER_NAME, process.env["LOHI_TOKEN"]]], + ...init, }); +} + +export async function usersWithAccess(): Promise { + const response = await sendouInkFetch("/plus/list"); if (!response.ok) { throw new Error(