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:
+
+
+ -
+ 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.
+
+ -
+ Alternatively if you want to update them right away you can log out
+ and back in on sendou.ink.
+
+
+
);
}
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(