diff --git a/backend/src/data/DefaultLogMessages.json b/backend/src/data/DefaultLogMessages.json index 868318a8..e1e3e3d9 100644 --- a/backend/src/data/DefaultLogMessages.json +++ b/backend/src/data/DefaultLogMessages.json @@ -47,6 +47,7 @@ "CLEAN": "🚿 {userMention(mod)} cleaned **{count}** message(s) in {channelMention(channel)}\n{archiveUrl}", "CASE_CREATE": "✏ {userMention(mod)} manually created new **{caseType}** case (#{caseNum})", + "CASE_DELETE": "✂️ **Case #{case.case_number}** was deleted by {userMention(mod)}", "MASSBAN": "⚒ {userMention(mod)} massbanned {count} users", "MASSMUTE": "📢🚫 {userMention(mod)} massmuted {count} users", @@ -63,10 +64,9 @@ "POSTED_SCHEDULED_MESSAGE": "\uD83D\uDCE8 Posted scheduled message (`{messageId}`) to {channelMention(channel)} as scheduled by {userMention(author)}", "BOT_ALERT": "⚠ **BOT ALERT:** {tmplEval(body)}", + "DM_FAILED": "\uD83D\uDEA7 Failed to send DM ({source}) to {userMention(user)}", + "AUTOMOD_ACTION": "\uD83E\uDD16 Automod rule **{rule}** triggered by {userMention(users)}\n{matchSummary}\nActions taken: **{actionsTaken}**", - "SET_ANTIRAID_USER": "⚔ {userMention(user)} set anti-raid to **{level}**", - "SET_ANTIRAID_AUTO": "⚔ Anti-raid automatically set to **{level}**", - - "CASE_DELETE": "✂️ **Case #{case.case_number}** was deleted by {userMention(mod)}" + "SET_ANTIRAID_AUTO": "⚔ Anti-raid automatically set to **{level}**" } diff --git a/backend/src/data/LogType.ts b/backend/src/data/LogType.ts index 115ec4be..9c40b13d 100644 --- a/backend/src/data/LogType.ts +++ b/backend/src/data/LogType.ts @@ -77,4 +77,6 @@ export enum LogType { MEMBER_NOTE, CASE_DELETE, + + DM_FAILED, } diff --git a/backend/src/plugins/WelcomeMessage/events/SendWelcomeMessageEvt.ts b/backend/src/plugins/WelcomeMessage/events/SendWelcomeMessageEvt.ts index fcf79db0..3e5931c9 100644 --- a/backend/src/plugins/WelcomeMessage/events/SendWelcomeMessageEvt.ts +++ b/backend/src/plugins/WelcomeMessage/events/SendWelcomeMessageEvt.ts @@ -3,6 +3,7 @@ import { renderTemplate } from "src/templateFormatter"; import { createChunkedMessage, stripObjectToScalars } from "src/utils"; import { LogType } from "src/data/LogType"; import { TextChannel } from "eris"; +import { sendDM } from "../../../utils/sendDM"; export const SendWelcomeMessageEvt = welcomeEvent({ event: "guildMemberAdd", @@ -21,14 +22,11 @@ export const SendWelcomeMessageEvt = welcomeEvent({ if (config.send_dm) { try { - console.log(`Sending welcome message to ${member.id}`); - const dmChannel = await member.user.getDMChannel(); - if (!dmChannel) return; - await createChunkedMessage(dmChannel, formatted); + await sendDM(member.user, formatted, "welcome message"); } catch (e) { - pluginData.state.logs.log(LogType.BOT_ALERT, { - body: `Failed send a welcome DM to {userMention(member)}`, - member: stripObjectToScalars(member, ["user"]), + pluginData.state.logs.log(LogType.DM_FAILED, { + source: "welcome message", + user: stripObjectToScalars(member.user), }); } } diff --git a/backend/src/utils.ts b/backend/src/utils.ts index 79c99ace..4e7af66a 100644 --- a/backend/src/utils.ts +++ b/backend/src/utils.ts @@ -38,6 +38,8 @@ import moment from "moment-timezone"; import { SimpleCache } from "./SimpleCache"; import { logger } from "./logger"; import { unsafeCoerce } from "fp-ts/lib/function"; +import { sendDM } from "./utils/sendDM"; +import { LogType } from "./data/LogType"; const fsp = fs.promises; @@ -878,9 +880,7 @@ export async function notifyUser( for (const method of methods) { if (method.type === "dm") { try { - console.log(`Notifying user ${user.id} via DM`); - const dmChannel = await user.getDMChannel(); - await dmChannel.createMessage(body); + await sendDM(user, body, "mod action notification"); return { method, success: true, @@ -1069,8 +1069,6 @@ export async function resolveMember(bot: Client, guild: Guild, value: string): P return null; } - logger.debug(`Fetching unknown member (${userId} in ${guild.name} (${guild.id})) from the API`); - const freshMember = await bot.getRESTGuildMember(guild.id, userId).catch(noop); if (freshMember) { freshMember.id = userId; diff --git a/backend/src/utils/sendDM.ts b/backend/src/utils/sendDM.ts new file mode 100644 index 00000000..ba830e76 --- /dev/null +++ b/backend/src/utils/sendDM.ts @@ -0,0 +1,46 @@ +import { MessageContent, MessageFile, User } from "eris"; +import { createChunkedMessage, HOURS, isDiscordRESTError } from "../utils"; +import { logger } from "../logger"; + +let dmsDisabled = false; +let dmsDisabledTimeout = null; + +function disableDMs(duration) { + dmsDisabled = true; + clearTimeout(dmsDisabledTimeout); + dmsDisabledTimeout = setTimeout(() => (dmsDisabled = false), duration); +} + +export class DMError extends Error {} + +const error20026 = "The bot cannot currently send DMs"; + +export async function sendDM(user: User, content: MessageContent, source: string) { + if (dmsDisabled) { + throw new DMError(error20026); + } + + logger.debug(`Sending ${source} DM to ${user.id}`); + + try { + const dmChannel = await user.getDMChannel(); + if (!dmChannel) { + throw new DMError("Unable to open DM channel"); + } + + if (typeof content === "string") { + await createChunkedMessage(dmChannel, content); + } else { + await dmChannel.createMessage(content); + } + } catch (e) { + if (isDiscordRESTError(e) && e.code === 20026) { + logger.warn(`Received error code 20026: ${e.message}`); + logger.warn("Disabling attempts to send DMs for 1 hour"); + disableDMs(1 * HOURS); + throw new DMError(error20026); + } + + throw e; + } +}