From 10bb0b67bc22a3f8dfef1c65809a9035143ba51a Mon Sep 17 00:00:00 2001
From: Tiago R <metal@i0.tf>
Date: Sun, 26 Nov 2023 14:53:54 +0000
Subject: [PATCH] some more patches thanks to ruby

Signed-off-by: GitHub <noreply@github.com>
---
 .../plugins/Automod/triggers/threadArchive.ts |  4 ++--
 .../plugins/Automod/triggers/threadCreate.ts  |  3 ++-
 .../plugins/Automod/triggers/threadDelete.ts  |  3 ++-
 .../Automod/triggers/threadUnarchive.ts       |  4 ++--
 .../plugins/BotControl/commands/ServersCmd.ts |  6 +++--
 .../src/plugins/Cases/functions/createCase.ts |  8 +++----
 .../plugins/Cases/functions/createCaseNote.ts |  4 ++--
 .../InternalPoster/functions/sendMessage.ts   |  2 +-
 .../ModActions/commands/CasesModCmd.ts        | 12 +++++-----
 .../ModActions/commands/CasesUserCmd.ts       | 22 ++++++++++++++-----
 .../src/plugins/Post/util/actualPostCmd.ts    |  4 ++--
 .../util/createStarboardEmbedFromMessage.ts   |  6 ++---
 .../src/plugins/Utility/commands/AboutCmd.ts  |  4 ++--
 .../src/plugins/Utility/commands/AvatarCmd.ts |  8 +++----
 .../Utility/functions/getUserInfoEmbed.ts     |  5 +----
 backend/src/plugins/Utility/search.ts         | 10 ++++-----
 backend/src/utils.ts                          |  9 +++++---
 backend/src/utils/templateSafeObjects.ts      |  7 +++---
 18 files changed, 69 insertions(+), 52 deletions(-)

diff --git a/backend/src/plugins/Automod/triggers/threadArchive.ts b/backend/src/plugins/Automod/triggers/threadArchive.ts
index 0e65b10a..8a692f6d 100644
--- a/backend/src/plugins/Automod/triggers/threadArchive.ts
+++ b/backend/src/plugins/Automod/triggers/threadArchive.ts
@@ -1,6 +1,6 @@
 import { User, escapeBold, type Snowflake } from "discord.js";
 import * as t from "io-ts";
-import { tNullable } from "../../../utils";
+import { renderUsername, tNullable } from "../../../utils";
 import { automodTrigger } from "../helpers";
 
 interface ThreadArchiveResult {
@@ -48,7 +48,7 @@ export const ThreadArchiveTrigger = automodTrigger<ThreadArchiveResult>()({
     const parentName = matchResult.extra.matchedThreadParentName;
     const base = `Thread **#${threadName}** (\`${threadId}\`) has been archived in the **#${parentName}** (\`${parentId}\`) channel`;
     if (threadOwner) {
-      return `${base} by **${escapeBold(threadOwner.tag)}** (\`${threadOwner.id}\`)`;
+      return `${base} by **${escapeBold(renderUsername(threadOwner.tag))}** (\`${threadOwner.id}\`)`;
     }
     return base;
   },
diff --git a/backend/src/plugins/Automod/triggers/threadCreate.ts b/backend/src/plugins/Automod/triggers/threadCreate.ts
index 7b8aca71..dc613068 100644
--- a/backend/src/plugins/Automod/triggers/threadCreate.ts
+++ b/backend/src/plugins/Automod/triggers/threadCreate.ts
@@ -1,5 +1,6 @@
 import { User, escapeBold, type Snowflake } from "discord.js";
 import * as t from "io-ts";
+import { renderUsername } from "../../../utils.js";
 import { automodTrigger } from "../helpers";
 
 interface ThreadCreateResult {
@@ -40,7 +41,7 @@ export const ThreadCreateTrigger = automodTrigger<ThreadCreateResult>()({
     const parentName = matchResult.extra.matchedThreadParentName;
     const base = `Thread **#${threadName}** (\`${threadId}\`) has been created in the **#${parentName}** (\`${parentId}\`) channel`;
     if (threadOwner) {
-      return `${base} by **${escapeBold(threadOwner.tag)}** (\`${threadOwner.id}\`)`;
+      return `${base} by **${escapeBold(renderUsername(threadOwner.tag))}** (\`${threadOwner.id}\`)`;
     }
     return base;
   },
diff --git a/backend/src/plugins/Automod/triggers/threadDelete.ts b/backend/src/plugins/Automod/triggers/threadDelete.ts
index 489b5b4c..f27a1d0b 100644
--- a/backend/src/plugins/Automod/triggers/threadDelete.ts
+++ b/backend/src/plugins/Automod/triggers/threadDelete.ts
@@ -1,5 +1,6 @@
 import { User, escapeBold, type Snowflake } from "discord.js";
 import * as t from "io-ts";
+import { renderUsername } from "../../../utils.js";
 import { automodTrigger } from "../helpers";
 
 interface ThreadDeleteResult {
@@ -40,7 +41,7 @@ export const ThreadDeleteTrigger = automodTrigger<ThreadDeleteResult>()({
     const parentName = matchResult.extra.matchedThreadParentName;
     if (threadOwner) {
       return `Thread **#${threadName ?? "Unknown"}** (\`${threadId}\`) created by **${escapeBold(
-        threadOwner.tag,
+        renderUsername(threadOwner.tag),
       )}** (\`${threadOwner.id}\`) in the **#${parentName}** (\`${parentId}\`) channel has been deleted`;
     }
     return `Thread **#${
diff --git a/backend/src/plugins/Automod/triggers/threadUnarchive.ts b/backend/src/plugins/Automod/triggers/threadUnarchive.ts
index f6047f48..94c69ace 100644
--- a/backend/src/plugins/Automod/triggers/threadUnarchive.ts
+++ b/backend/src/plugins/Automod/triggers/threadUnarchive.ts
@@ -1,6 +1,6 @@
 import { User, escapeBold, type Snowflake } from "discord.js";
 import * as t from "io-ts";
-import { tNullable } from "../../../utils";
+import { renderUsername, tNullable } from "../../../utils";
 import { automodTrigger } from "../helpers";
 
 interface ThreadUnarchiveResult {
@@ -48,7 +48,7 @@ export const ThreadUnarchiveTrigger = automodTrigger<ThreadUnarchiveResult>()({
     const parentName = matchResult.extra.matchedThreadParentName;
     const base = `Thread **#${threadName}** (\`${threadId}\`) has been unarchived in the **#${parentName}** (\`${parentId}\`) channel`;
     if (threadOwner) {
-      return `${base} by **${escapeBold(threadOwner.tag)}** (\`${threadOwner.id}\`)`;
+      return `${base} by **${escapeBold(renderUsername(threadOwner.tag))}** (\`${threadOwner.id}\`)`;
     }
     return base;
   },
diff --git a/backend/src/plugins/BotControl/commands/ServersCmd.ts b/backend/src/plugins/BotControl/commands/ServersCmd.ts
index 23146d21..5cb62433 100644
--- a/backend/src/plugins/BotControl/commands/ServersCmd.ts
+++ b/backend/src/plugins/BotControl/commands/ServersCmd.ts
@@ -1,7 +1,7 @@
 import escapeStringRegexp from "escape-string-regexp";
 import { commandTypeHelpers as ct } from "../../../commandTypes";
 import { isStaffPreFilter } from "../../../pluginUtils";
-import { createChunkedMessage, getUser, sorter } from "../../../utils";
+import { createChunkedMessage, getUser, renderUsername, sorter } from "../../../utils";
 import { botControlCmd } from "../types";
 
 export const ServersCmd = botControlCmd({
@@ -48,7 +48,9 @@ export const ServersCmd = botControlCmd({
         const lines = filteredGuilds.map((g) => {
           const paddedId = g.id.padEnd(longestId, " ");
           const owner = getUser(pluginData.client, g.ownerId);
-          return `\`${paddedId}\` **${g.name}** (${g.memberCount} members) (owner **${owner.tag}** \`${owner.id}\`)`;
+          return `\`${paddedId}\` **${g.name}** (${g.memberCount} members) (owner **${renderUsername(owner.tag)}** \`${
+            owner.id
+          }\`)`;
         });
         createChunkedMessage(msg.channel, lines.join("\n"));
       } else {
diff --git a/backend/src/plugins/Cases/functions/createCase.ts b/backend/src/plugins/Cases/functions/createCase.ts
index c16d937b..70717f51 100644
--- a/backend/src/plugins/Cases/functions/createCase.ts
+++ b/backend/src/plugins/Cases/functions/createCase.ts
@@ -1,23 +1,23 @@
 import type { Snowflake } from "discord.js";
 import { GuildPluginData } from "knub";
 import { logger } from "../../../logger";
-import { renderUserUsername, resolveUser } from "../../../utils";
+import { renderUsername, resolveUser } from "../../../utils";
 import { CaseArgs, CasesPluginType } from "../types";
 import { createCaseNote } from "./createCaseNote";
 import { postCaseToCaseLogChannel } from "./postToCaseLogChannel";
 
 export async function createCase(pluginData: GuildPluginData<CasesPluginType>, args: CaseArgs) {
   const user = await resolveUser(pluginData.client, args.userId);
-  const userName = renderUserUsername(user);
+  const userName = renderUsername(user.username, user.discriminator);
 
   const mod = await resolveUser(pluginData.client, args.modId);
-  const modName = mod.tag;
+  const modName = renderUsername(mod.username, mod.discriminator);
 
   let ppName: string | null = null;
   let ppId: Snowflake | null = null;
   if (args.ppId) {
     const pp = await resolveUser(pluginData.client, args.ppId);
-    ppName = pp.tag;
+    ppName = renderUsername(pp.username, pp.discriminator);
     ppId = pp.id;
   }
 
diff --git a/backend/src/plugins/Cases/functions/createCaseNote.ts b/backend/src/plugins/Cases/functions/createCaseNote.ts
index 92520b7d..71b11302 100644
--- a/backend/src/plugins/Cases/functions/createCaseNote.ts
+++ b/backend/src/plugins/Cases/functions/createCaseNote.ts
@@ -1,6 +1,6 @@
 import { GuildPluginData } from "knub";
 import { ERRORS, RecoverablePluginError } from "../../../RecoverablePluginError";
-import { UnknownUser, resolveUser } from "../../../utils";
+import { UnknownUser, renderUsername, resolveUser } from "../../../utils";
 import { CaseNoteArgs, CasesPluginType } from "../types";
 import { postCaseToCaseLogChannel } from "./postToCaseLogChannel";
 import { resolveCaseId } from "./resolveCaseId";
@@ -16,7 +16,7 @@ export async function createCaseNote(pluginData: GuildPluginData<CasesPluginType
     throw new RecoverablePluginError(ERRORS.INVALID_USER);
   }
 
-  const modName = mod.tag;
+  const modName = renderUsername(mod);
 
   let body = args.body;
 
diff --git a/backend/src/plugins/InternalPoster/functions/sendMessage.ts b/backend/src/plugins/InternalPoster/functions/sendMessage.ts
index ec811240..3d4d6424 100644
--- a/backend/src/plugins/InternalPoster/functions/sendMessage.ts
+++ b/backend/src/plugins/InternalPoster/functions/sendMessage.ts
@@ -48,7 +48,7 @@ export async function sendMessage(
         ...content,
         ...(pluginData.client.user && {
           username: pluginData.client.user.username,
-          avatarURL: pluginData.client.user.avatarURL() || pluginData.client.user.defaultAvatarURL,
+          avatarURL: pluginData.client.user.displayAvatarURL(),
         }),
       })
       .then((apiMessage) => ({
diff --git a/backend/src/plugins/ModActions/commands/CasesModCmd.ts b/backend/src/plugins/ModActions/commands/CasesModCmd.ts
index 5b0e3273..a911091d 100644
--- a/backend/src/plugins/ModActions/commands/CasesModCmd.ts
+++ b/backend/src/plugins/ModActions/commands/CasesModCmd.ts
@@ -1,7 +1,7 @@
-import { APIEmbed, User } from "discord.js";
+import { APIEmbed, GuildMember, User } from "discord.js";
 import { commandTypeHelpers as ct } from "../../../commandTypes";
 import { sendErrorMessage } from "../../../pluginUtils";
-import { emptyEmbedValue, resolveUser, trimLines } from "../../../utils";
+import { emptyEmbedValue, renderUsername, resolveMember, resolveUser, trimLines } from "../../../utils";
 import { asyncMap } from "../../../utils/async";
 import { createPaginatedMessage } from "../../../utils/createPaginatedMessage";
 import { getChunkedEmbedFields } from "../../../utils/getChunkedEmbedFields";
@@ -28,8 +28,10 @@ export const CasesModCmd = modActionsCmd({
 
   async run({ pluginData, message: msg, args }) {
     const modId = args.mod || msg.author.id;
-    const mod = await resolveUser(pluginData.client, modId);
-    const modName = mod instanceof User ? mod.tag : modId;
+    const mod =
+      (await resolveMember(pluginData.client, pluginData.guild, modId)) ||
+      (await resolveUser(pluginData.client, modId));
+    const modName = mod instanceof User ? renderUsername(mod) : modId;
 
     const casesPlugin = pluginData.getPlugin(CasesPlugin);
     const totalCases = await casesPlugin.getTotalCasesByMod(modId);
@@ -57,7 +59,7 @@ export const CasesModCmd = modActionsCmd({
         const embed = {
           author: {
             name: title,
-            icon_url: mod instanceof User ? mod.displayAvatarURL() : undefined,
+            icon_url: mod instanceof User || mod instanceof GuildMember ? mod.displayAvatarURL() : undefined,
           },
           fields: [
             ...getChunkedEmbedFields(emptyEmbedValue, lines.join("\n")),
diff --git a/backend/src/plugins/ModActions/commands/CasesUserCmd.ts b/backend/src/plugins/ModActions/commands/CasesUserCmd.ts
index 069ad31f..05ab65eb 100644
--- a/backend/src/plugins/ModActions/commands/CasesUserCmd.ts
+++ b/backend/src/plugins/ModActions/commands/CasesUserCmd.ts
@@ -1,9 +1,17 @@
-import { APIEmbed, User } from "discord.js";
+import { APIEmbed } from "discord.js";
 import { commandTypeHelpers as ct } from "../../../commandTypes";
 import { CaseTypes } from "../../../data/CaseTypes";
 import { sendErrorMessage } from "../../../pluginUtils";
 import { CasesPlugin } from "../../../plugins/Cases/CasesPlugin";
-import { UnknownUser, chunkArray, emptyEmbedValue, renderUserUsername, resolveUser, trimLines } from "../../../utils";
+import {
+  UnknownUser,
+  chunkArray,
+  emptyEmbedValue,
+  renderUsername,
+  resolveMember,
+  resolveUser,
+  trimLines,
+} from "../../../utils";
 import { asyncMap } from "../../../utils/async";
 import { getChunkedEmbedFields } from "../../../utils/getChunkedEmbedFields";
 import { getGuildPrefix } from "../../../utils/getGuildPrefix";
@@ -35,8 +43,10 @@ export const CasesUserCmd = modActionsCmd({
   ],
 
   async run({ pluginData, message: msg, args }) {
-    const user = await resolveUser(pluginData.client, args.user);
-    if (!user.id) {
+    const user =
+      (await resolveMember(pluginData.client, pluginData.guild, args.user)) ||
+      (await resolveUser(pluginData.client, args.user));
+    if (!user.id || user instanceof UnknownUser) {
       sendErrorMessage(pluginData, msg.channel, `User not found`);
       return;
     }
@@ -62,7 +72,7 @@ export const CasesUserCmd = modActionsCmd({
     const hiddenCases = cases.filter((c) => c.is_hidden);
 
     const userName =
-      user instanceof UnknownUser && cases.length ? cases[cases.length - 1].user_name : renderUserUsername(user);
+      user instanceof UnknownUser && cases.length ? cases[cases.length - 1].user_name : renderUsername(user);
 
     if (cases.length === 0) {
       msg.channel.send(`No cases found for **${userName}**`);
@@ -123,7 +133,7 @@ export const CasesUserCmd = modActionsCmd({
                 lineChunks.length === 1
                   ? `Cases for ${userName} (${lines.length} total)`
                   : `Cases ${chunkStart}–${chunkEnd} of ${lines.length} for ${userName}`,
-              icon_url: user instanceof User ? user.displayAvatarURL() : undefined,
+              icon_url: user.displayAvatarURL(),
             },
             fields: [
               ...getChunkedEmbedFields(emptyEmbedValue, linesInChunk.join("\n")),
diff --git a/backend/src/plugins/Post/util/actualPostCmd.ts b/backend/src/plugins/Post/util/actualPostCmd.ts
index c73a672b..29c3d5ec 100644
--- a/backend/src/plugins/Post/util/actualPostCmd.ts
+++ b/backend/src/plugins/Post/util/actualPostCmd.ts
@@ -4,7 +4,7 @@ import { GuildPluginData } from "knub";
 import moment from "moment-timezone";
 import { registerUpcomingScheduledPost } from "../../../data/loops/upcomingScheduledPostsLoop";
 import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils";
-import { DBDateFormat, MINUTES, StrictMessageContent, errorMessage } from "../../../utils";
+import { DBDateFormat, MINUTES, StrictMessageContent, errorMessage, renderUsername } from "../../../utils";
 import { LogsPlugin } from "../../Logs/LogsPlugin";
 import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin";
 import { PostPluginType } from "../types";
@@ -122,7 +122,7 @@ export async function actualPostCmd(
 
     const post = await pluginData.state.scheduledPosts.create({
       author_id: msg.author.id,
-      author_name: msg.author.tag,
+      author_name: renderUsername(msg.author),
       channel_id: targetChannel.id,
       content,
       attachments: [...msg.attachments.values()],
diff --git a/backend/src/plugins/Starboard/util/createStarboardEmbedFromMessage.ts b/backend/src/plugins/Starboard/util/createStarboardEmbedFromMessage.ts
index 5f028c0a..5126f20f 100644
--- a/backend/src/plugins/Starboard/util/createStarboardEmbedFromMessage.ts
+++ b/backend/src/plugins/Starboard/util/createStarboardEmbedFromMessage.ts
@@ -1,6 +1,6 @@
 import { GuildChannel, Message } from "discord.js";
 import path from "path";
-import { EMPTY_CHAR, EmbedWith } from "../../../utils";
+import { EMPTY_CHAR, EmbedWith, renderUsername } from "../../../utils";
 
 const imageAttachmentExtensions = ["jpeg", "jpg", "png", "gif", "webp"];
 const audioAttachmentExtensions = ["wav", "mp3", "m4a"];
@@ -18,7 +18,7 @@ export function createStarboardEmbedFromMessage(
       text: `#${(msg.channel as GuildChannel).name}`,
     },
     author: {
-      name: msg.author.tag,
+      name: renderUsername(msg.author),
     },
     fields: [],
     timestamp: msg.createdAt.toISOString(),
@@ -28,7 +28,7 @@ export function createStarboardEmbedFromMessage(
     embed.color = color;
   }
 
-  embed.author.icon_url = msg.author.displayAvatarURL();
+  embed.author.icon_url = (msg.member || msg.author).displayAvatarURL();
 
   // The second condition here checks for messages with only an image link that is then embedded.
   // The message content in that case is hidden by the Discord client, so we hide it here too.
diff --git a/backend/src/plugins/Utility/commands/AboutCmd.ts b/backend/src/plugins/Utility/commands/AboutCmd.ts
index 53408d71..d4188ad7 100644
--- a/backend/src/plugins/Utility/commands/AboutCmd.ts
+++ b/backend/src/plugins/Utility/commands/AboutCmd.ts
@@ -100,8 +100,8 @@ export const AboutCmd = utilityCmd({
     }
 
     // Use the bot avatar as the embed image
-    if (pluginData.client.user!.avatarURL()) {
-      aboutEmbed.thumbnail = { url: pluginData.client.user!.avatarURL()! };
+    if (pluginData.client.user!.displayAvatarURL()) {
+      aboutEmbed.thumbnail = { url: pluginData.client.user!.displayAvatarURL()! };
     }
 
     msg.channel.send({ embeds: [aboutEmbed] });
diff --git a/backend/src/plugins/Utility/commands/AvatarCmd.ts b/backend/src/plugins/Utility/commands/AvatarCmd.ts
index ef44a2cb..b1215df6 100644
--- a/backend/src/plugins/Utility/commands/AvatarCmd.ts
+++ b/backend/src/plugins/Utility/commands/AvatarCmd.ts
@@ -1,7 +1,7 @@
 import { APIEmbed, ImageFormat } from "discord.js";
 import { commandTypeHelpers as ct } from "../../../commandTypes";
 import { sendErrorMessage } from "../../../pluginUtils";
-import { UnknownUser, renderUserUsername } from "../../../utils";
+import { UnknownUser, renderUsername } from "../../../utils";
 import { utilityCmd } from "../types";
 
 export const AvatarCmd = utilityCmd({
@@ -10,17 +10,17 @@ export const AvatarCmd = utilityCmd({
   permission: "can_avatar",
 
   signature: {
-    user: ct.resolvedUserLoose({ required: false }),
+    user: ct.resolvedMember({ required: false }) || ct.resolvedUserLoose({ required: false }),
   },
 
   async run({ message: msg, args, pluginData }) {
-    const user = args.user || msg.author;
+    const user = args.user || msg.member || msg.author;
     if (!(user instanceof UnknownUser)) {
       const embed: APIEmbed = {
         image: {
           url: user.displayAvatarURL({ extension: ImageFormat.PNG, size: 2048 }),
         },
-        title: `Avatar of ${renderUserUsername(user)}:`,
+        title: `Avatar of ${renderUsername(user)}:`,
       };
       msg.channel.send({ embeds: [embed] });
     } else {
diff --git a/backend/src/plugins/Utility/functions/getUserInfoEmbed.ts b/backend/src/plugins/Utility/functions/getUserInfoEmbed.ts
index 2de8b4db..58d34d56 100644
--- a/backend/src/plugins/Utility/functions/getUserInfoEmbed.ts
+++ b/backend/src/plugins/Utility/functions/getUserInfoEmbed.ts
@@ -13,7 +13,6 @@ import {
   trimLines,
   UnknownUser,
 } from "../../../utils";
-import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin";
 import { UtilityPluginType } from "../types";
 
 const MAX_ROLES_TO_DISPLAY = 15;
@@ -40,13 +39,11 @@ export async function getUserInfoEmbed(
     fields: [],
   };
 
-  const timeAndDate = pluginData.getPlugin(TimeAndDatePlugin);
-
   embed.author = {
     name: `${user.bot ? "Bot" : "User"}:  ${renderUsername(user)}`,
   };
 
-  const avatarURL = user.displayAvatarURL();
+  const avatarURL = (member || user).displayAvatarURL();
   embed.author.icon_url = avatarURL;
 
   if (compact) {
diff --git a/backend/src/plugins/Utility/search.ts b/backend/src/plugins/Utility/search.ts
index 19710b58..632a85a7 100644
--- a/backend/src/plugins/Utility/search.ts
+++ b/backend/src/plugins/Utility/search.ts
@@ -14,7 +14,7 @@ import { ArgsFromSignatureOrArray, GuildPluginData } from "knub";
 import moment from "moment-timezone";
 import { RegExpRunner, allowTimeout } from "../../RegExpRunner";
 import { getBaseUrl, sendErrorMessage } from "../../pluginUtils";
-import { MINUTES, multiSorter, renderUserUsername, sorter, trimLines } from "../../utils";
+import { MINUTES, multiSorter, renderUsername, sorter, trimLines } from "../../utils";
 import { asyncFilter } from "../../utils/async";
 import { hasDiscordPermissions } from "../../utils/hasDiscordPermissions";
 import { InvalidRegexError, inputPatternToRegExp } from "../../validatorUtils";
@@ -381,7 +381,7 @@ async function performMemberSearch(
         return true;
       }
 
-      const fullUsername = renderUserUsername(member.user);
+      const fullUsername = renderUsername(member.user);
       if (await execRegExp(queryRegex, fullUsername).catch(allowTimeout)) return true;
 
       return false;
@@ -448,7 +448,7 @@ async function performBanSearch(
 
     const execRegExp = getOptimizedRegExpRunner(pluginData, isSafeRegex);
     matchingBans = await asyncFilter(matchingBans, async (user) => {
-      const fullUsername = renderUserUsername(user);
+      const fullUsername = renderUsername(user);
       if (await execRegExp(queryRegex, fullUsername).catch(allowTimeout)) return true;
       return false;
     });
@@ -492,10 +492,10 @@ function formatSearchResultList(members: Array<GuildMember | User>): string {
     const paddedId = member.id.padEnd(longestId, " ");
     let line;
     if (member instanceof GuildMember) {
-      line = `${paddedId} ${renderUserUsername(member.user)}`;
+      line = `${paddedId} ${renderUsername(member.user)}`;
       if (member.nickname) line += ` (${member.nickname})`;
     } else {
-      line = `${paddedId} ${member.tag}`;
+      line = `${paddedId} ${renderUsername(member)}`;
     }
     return line;
   });
diff --git a/backend/src/utils.ts b/backend/src/utils.ts
index ee6b556c..5c795072 100644
--- a/backend/src/utils.ts
+++ b/backend/src/utils.ts
@@ -1603,9 +1603,12 @@ export function isTruthy<T>(value: T): value is Exclude<T, false | null | undefi
 
 export const DBDateFormat = "YYYY-MM-DD HH:mm:ss";
 
-export function renderUsername(username: User): string;
-export function renderUsername(username: string, discriminator?: string): string;
-export function renderUsername(username: string | User, discriminator?: string): string {
+// TODO: Fix overloads
+//export function renderUsername(username: GuildMember): string;
+//export function renderUsername(username: User): string;
+//export function renderUsername(username: string, discriminator?: string): string;
+export function renderUsername(username: string | User | GuildMember, discriminator?: string): string {
+  if (username instanceof GuildMember) return username.user.tag;
   if (username instanceof User) return username.tag;
   if (discriminator === "0" || discriminator === "0000") {
     return username;
diff --git a/backend/src/utils/templateSafeObjects.ts b/backend/src/utils/templateSafeObjects.ts
index fbd27754..33e36971 100644
--- a/backend/src/utils/templateSafeObjects.ts
+++ b/backend/src/utils/templateSafeObjects.ts
@@ -52,7 +52,7 @@ export class TemplateSafeUser extends TemplateSafeValueContainer {
   globalName?: string;
   mention: string;
   tag: string;
-  avatarURL?: string;
+  avatarURL: string;
   bot?: boolean;
   createdAt?: number;
   renderedUsername: string;
@@ -92,7 +92,7 @@ export class TemplateSafeMember extends TemplateSafeUser {
   nick: string;
   roles: TemplateSafeRole[];
   joinedAt?: number;
-  // guildAvatarURL: string, Once DJS supports per-server avatars
+  guildAvatarURL: string;
   guildName: string;
 
   constructor(data: InputProps<TemplateSafeMember>) {
@@ -261,7 +261,7 @@ export function userToTemplateSafeUser(user: User | UnknownUser): TemplateSafeUs
     globalName: user.globalName,
     mention: `<@${user.id}>`,
     tag: user.tag,
-    avatarURL: user.displayAvatarURL?.(),
+    avatarURL: user.displayAvatarURL(),
     bot: user.bot,
     createdAt: user.createdTimestamp,
     renderedUsername: renderUserUsername(user),
@@ -287,6 +287,7 @@ export function memberToTemplateSafeMember(member: GuildMember | PartialGuildMem
     nick: member.nickname ?? "*None*",
     roles: [...member.roles.cache.mapValues((r) => roleToTemplateSafeRole(r)).values()],
     joinedAt: member.joinedTimestamp ?? undefined,
+    guildAvatarURL: member.displayAvatarURL(),
     guildName: member.guild.name,
   });
 }