diff --git a/src/index.ts b/src/index.ts index e290fa97..7b70cd5e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -74,7 +74,7 @@ import { AutoReactionsPlugin } from "./plugins/AutoReactionsPlugin"; import { PingableRolesPlugin } from "./plugins/PingableRolesPlugin"; import { SelfGrantableRolesPlugin } from "./plugins/SelfGrantableRolesPlugin"; import { RemindersPlugin } from "./plugins/Reminders"; -import { convertDelayStringToMS } from "./utils"; +import { convertDelayStringToMS, errorMessage, successMessage } from "./utils"; // Run latest database migrations logger.info("Running database migrations"); @@ -168,6 +168,14 @@ connect().then(async conn => { return result; }, }, + + sendSuccessMessageFn(channel, body) { + channel.createMessage(successMessage(body)); + }, + + sendErrorMessageFn(channel, body) { + channel.createMessage(errorMessage(body)); + }, }, }); diff --git a/src/plugins/ModActions.ts b/src/plugins/ModActions.ts index d6256d5f..083f6cff 100644 --- a/src/plugins/ModActions.ts +++ b/src/plugins/ModActions.ts @@ -5,6 +5,7 @@ import { GuildCases } from "../data/GuildCases"; import { asSingleLine, createChunkedMessage, + createUnknownUser, errorMessage, findRelevantAuditLogEntry, INotifyUserResult, @@ -14,7 +15,7 @@ import { successMessage, trimLines, ucfirst, - unknownUser, + UnknownUser, } from "../utils"; import { GuildMutes } from "../data/GuildMutes"; import { CaseTypes } from "../data/CaseTypes"; @@ -155,22 +156,61 @@ export class ModActionsPlugin extends ZeppelinPlugin { return ((reason || "") + " " + attachmentUrls.join(" ")).trim(); } - async resolveMember(userId: string): Promise<{ member: Member; isBanned?: boolean }> { + /** + * Resolves a user from the passed string. The passed string can be a user id, a user mention, a full username (with discrim), etc. + */ + async resolveUser(userResolvable: string): Promise { + let userId; + + // A user mention? + const mentionMatch = userResolvable.match(/^<@!?(\d+)>$/); + if (mentionMatch) { + userId = mentionMatch[1]; + } + + // A non-mention, full username? + if (!userId) { + const usernameMatch = userResolvable.match(/^@?([^#]+)#(\d{4})$/); + if (usernameMatch) { + const user = this.bot.users.find(u => u.username === usernameMatch[1] && u.discriminator === usernameMatch[2]); + userId = user.id; + } + } + + // Just a user ID? + if (!userId) { + const idMatch = userResolvable.match(/^\d+$/); + if (!idMatch) { + return null; + } + + userId = userResolvable; + } + + return ( + this.bot.users.find(u => u.id === userId) || + (await this.bot.getRESTUser(userId)) || + createUnknownUser({ id: userId }) + ); + } + + async getMember(userId: string): Promise { + // See if we have the member cached... let member = this.guild.members.get(userId); + // If not, fetch it from the API if (!member) { try { member = await this.bot.getRESTGuildMember(this.guildId, userId); } catch (e) {} // tslint:disable-line } - if (!member) { - const bans = (await this.guild.getBans()) as any; - const isBanned = bans.some(b => b.user.id === userId); - return { member, isBanned }; - } + return member; + } - return { member }; + async isBanned(userId): Promise { + const bans = (await this.guild.getBans()) as any; + return bans.some(b => b.user.id === userId); } /** @@ -326,7 +366,7 @@ export class ModActionsPlugin extends ZeppelinPlugin { } if (!theCase) { - msg.channel.createMessage(errorMessage("Case not found")); + this.sendErrorMessage(msg.channel, "Case not found"); return; } @@ -346,31 +386,37 @@ export class ModActionsPlugin extends ZeppelinPlugin { msg.channel.createMessage(successMessage(`Case \`#${theCase.case_number}\` updated`)); } - @d.command("note", " ") + @d.command("note", " ") @d.permission("can_note") - async noteCmd(msg: Message, args: any) { - const user = await this.bot.users.get(args.userId); - const userName = user ? `${user.username}#${user.discriminator}` : "member"; + async noteCmd(msg: Message, args: { user: string; note: string }) { + const user = await this.resolveUser(args.user); + if (!user) return this.sendErrorMessage(msg.channel, `User not found`); + + const userName = `${user.username}#${user.discriminator}`; const reason = this.formatReasonWithAttachments(args.note, msg.attachments); const createdCase = await this.actions.fire("createCase", { - userId: args.userId, + userId: user.id, modId: msg.author.id, type: CaseTypes.Note, reason, }); - msg.channel.createMessage(successMessage(`Note added on **${userName}** (Case #${createdCase.case_number})`)); + this.sendSuccessMessage(msg.channel, `Note added on **${userName}** (Case #${createdCase.case_number})`); } - @d.command("warn", " ", { + @d.command("warn", " ", { options: [{ name: "mod", type: "member" }], }) @d.permission("can_warn") - async warnCmd(msg: Message, args: { userId: string; reason: string; mod?: Member }) { - const { member: memberToWarn, isBanned } = await this.resolveMember(args.userId); + async warnCmd(msg: Message, args: { user: string; reason: string; mod?: Member }) { + const user = await this.resolveUser(args.user); + if (!user) return this.sendErrorMessage(msg.channel, `User not found`); + + const memberToWarn = await this.getMember(user.id); if (!memberToWarn) { + const isBanned = await this.isBanned(user.id); if (isBanned) { this.sendErrorMessage(msg.channel, `User is banned`); } else { @@ -441,33 +487,11 @@ export class ModActionsPlugin extends ZeppelinPlugin { }); } - @d.command("mute", " ", { - overloads: [" ", " [reason:string$]"], - options: [{ name: "mod", type: "member" }], - }) - @d.permission("can_mute") - async muteCmd(msg: Message, args: { userId: string; time?: number; reason?: string; mod: Member }) { - const user = this.bot.users.get(args.userId) || { ...unknownUser, id: args.userId }; - const { member: memberToMute, isBanned } = await this.resolveMember(user.id); - - if (!memberToMute) { - const notOnServerMsg = isBanned - ? await msg.channel.createMessage("User is banned. Apply a mute to them anyway?") - : await msg.channel.createMessage("User is not on the server. Apply a mute to them anyway?"); - - const reply = await waitForReaction(this.bot, notOnServerMsg, ["✅", "❌"], msg.author.id); - notOnServerMsg.delete(); - if (!reply || reply.name === "❌") { - return; - } - } - - // Make sure we're allowed to mute this member - if (memberToMute && !this.canActOn(msg.member, memberToMute)) { - msg.channel.createMessage(errorMessage("Cannot mute: insufficient permissions")); - return; - } - + /** + * The actual function run by both !mute and !forcemute. + * The only difference between the two commands is in target member validation. + */ + async actualMuteCmd(user: User | UnknownUser, msg: Message, args: { time?: number; reason?: string; mod: Member }) { // The moderator who did the action is the message author or, if used, the specified --mod let mod = msg.member; let pp = null; @@ -532,50 +556,83 @@ export class ModActionsPlugin extends ZeppelinPlugin { } if (muteResult.notifyResult.text) response += ` (${muteResult.notifyResult.text})`; - msg.channel.createMessage(successMessage(response)); + this.sendSuccessMessage(msg.channel, response); } - @d.command("unmute", " ", { - overloads: [" ", " [reason:string$]"], + @d.command("mute", " ", { + overloads: [" ", " [reason:string$]"], options: [{ name: "mod", type: "member" }], }) @d.permission("can_mute") - async unmuteCmd(msg: Message, args: { userId: string; time?: number; reason?: string; mod?: Member }) { - // Check if they're muted in the first place - if (!(await this.mutes.isMuted(args.userId))) { - msg.channel.createMessage(errorMessage("Cannot unmute: member is not muted")); - return; - } + async muteCmd(msg: Message, args: { user: string; time?: number; reason?: string; mod: Member }) { + const user = await this.resolveUser(args.user); + if (!user) return this.sendErrorMessage(msg.channel, `User not found`); - // Find the server member to unmute - const user = this.bot.users.get(args.userId) || { ...unknownUser, id: args.userId }; - const { member: memberToUnmute, isBanned } = await this.resolveMember(user.id); + const memberToMute = await this.getMember(user.id); - if (!memberToUnmute) { - const notOnServerMsg = isBanned - ? await msg.channel.createMessage("User is banned. Unmute them anyway?") - : await msg.channel.createMessage("User is not on the server. Unmute them anyway?"); - - const reply = await waitForReaction(this.bot, notOnServerMsg, ["✅", "❌"], msg.author.id); - notOnServerMsg.delete(); - if (!reply || reply.name === "❌") { - return; + if (!memberToMute) { + const isBanned = await this.isBanned(user.id); + const prefix = this.guildConfig.prefix; + if (isBanned) { + this.sendErrorMessage( + msg.channel, + `User is banned. Use \`${prefix}forcemute\` if you want to mute them anyway.`, + ); + } else { + this.sendErrorMessage( + msg.channel, + `User is not on the server. Use \`${prefix}forcemute\` if you want to mute them anyway.`, + ); } - } - // Make sure we're allowed to unmute this member - if (memberToUnmute && !this.canActOn(msg.member, memberToUnmute)) { - msg.channel.createMessage(errorMessage("Cannot unmute: insufficient permissions")); return; } + // Make sure we're allowed to mute this member + if (memberToMute && !this.canActOn(msg.member, memberToMute)) { + this.sendErrorMessage(msg.channel, "Cannot mute: insufficient permissions"); + return; + } + + this.actualMuteCmd(user, msg, args); + } + + @d.command("forcemute", " ", { + overloads: [" ", " [reason:string$]"], + options: [{ name: "mod", type: "member" }], + }) + @d.permission("can_mute") + async forcemuteCmd(msg: Message, args: { user: string; time?: number; reason?: string; mod: Member }) { + const user = await this.resolveUser(args.user); + if (!user) return this.sendErrorMessage(msg.channel, `User not found`); + + const memberToMute = await this.getMember(user.id); + + // Make sure we're allowed to mute this user + if (memberToMute && !this.canActOn(msg.member, memberToMute)) { + this.sendErrorMessage(msg.channel, "Cannot mute: insufficient permissions"); + return; + } + + this.actualMuteCmd(user, msg, args); + } + + /** + * The actual function run by both !unmute and !forceunmute. + * The only difference between the two commands is in target member validation. + */ + async actualUnmuteCmd( + user: User | UnknownUser, + msg: Message, + args: { time?: number; reason?: string; mod?: Member }, + ) { // The moderator who did the action is the message author or, if used, the specified --mod let mod = msg.author; let pp = null; if (args.mod) { if (!this.hasPermission("can_act_as_other", { message: msg })) { - msg.channel.createMessage(errorMessage("No permission for --mod")); + this.sendErrorMessage(msg.channel, "No permission for --mod"); return; } @@ -618,14 +675,87 @@ export class ModActionsPlugin extends ZeppelinPlugin { } } - @d.command("kick", " [reason:string$]", { + @d.command("unmute", " ", { + overloads: [" ", " [reason:string$]"], + options: [{ name: "mod", type: "member" }], + }) + @d.permission("can_mute") + async unmuteCmd(msg: Message, args: { user: string; time?: number; reason?: string; mod?: Member }) { + const user = await this.resolveUser(args.user); + if (!user) return this.sendErrorMessage(msg.channel, `User not found`); + + // Check if they're muted in the first place + if (!(await this.mutes.isMuted(args.user))) { + this.sendErrorMessage(msg.channel, "Cannot unmute: member is not muted"); + return; + } + + // Find the server member to unmute + const memberToUnmute = await this.getMember(user.id); + + if (!memberToUnmute) { + const isBanned = await this.isBanned(user.id); + const prefix = this.guildConfig.prefix; + if (isBanned) { + this.sendErrorMessage(msg.channel, `User is banned. Use \`${prefix}forceunmute\` to unmute them anyway.`); + } else { + this.sendErrorMessage( + msg.channel, + `User is not on the server. Use \`${prefix}forceunmute\` to unmute them anyway.`, + ); + } + + return; + } + + // Make sure we're allowed to unmute this member + if (memberToUnmute && !this.canActOn(msg.member, memberToUnmute)) { + this.sendErrorMessage(msg.channel, "Cannot unmute: insufficient permissions"); + return; + } + + this.actualUnmuteCmd(user, msg, args); + } + + @d.command("forceunmute", " ", { + overloads: [" ", " [reason:string$]"], + options: [{ name: "mod", type: "member" }], + }) + @d.permission("can_mute") + async forceunmuteCmd(msg: Message, args: { user: string; time?: number; reason?: string; mod?: Member }) { + const user = await this.resolveUser(args.user); + if (!user) return this.sendErrorMessage(msg.channel, `User not found`); + + // Check if they're muted in the first place + if (!(await this.mutes.isMuted(user.id))) { + this.sendErrorMessage(msg.channel, "Cannot unmute: member is not muted"); + return; + } + + // Find the server member to unmute + const memberToUnmute = await this.getMember(user.id); + + // Make sure we're allowed to unmute this member + if (memberToUnmute && !this.canActOn(msg.member, memberToUnmute)) { + this.sendErrorMessage(msg.channel, "Cannot unmute: insufficient permissions"); + return; + } + + this.actualUnmuteCmd(user, msg, args); + } + + @d.command("kick", " [reason:string$]", { options: [{ name: "mod", type: "member" }], }) @d.permission("can_kick") - async kickCmd(msg, args: { userId: string; reason: string; mod: Member }) { - const { member: memberToKick, isBanned } = await this.resolveMember(args.userId); + async kickCmd(msg, args: { user: string; reason: string; mod: Member }) { + const user = await this.resolveUser(args.user); + if (!user) return this.sendErrorMessage(msg.channel, `User not found`); + + const memberToKick = await this.getMember(user.id); if (!memberToKick) { + const isBanned = await this.isBanned(user.id); if (isBanned) { this.sendErrorMessage(msg.channel, `User is banned`); } else { @@ -637,7 +767,7 @@ export class ModActionsPlugin extends ZeppelinPlugin { // Make sure we're allowed to kick this member if (!this.canActOn(msg.member, memberToKick)) { - msg.channel.createMessage(errorMessage("Cannot kick: insufficient permissions")); + this.sendErrorMessage(msg.channel, "Cannot kick: insufficient permissions"); return; } @@ -645,7 +775,7 @@ export class ModActionsPlugin extends ZeppelinPlugin { let mod = msg.member; if (args.mod) { if (!this.hasPermission("can_act_as_other", { message: msg })) { - msg.channel.createMessage(errorMessage("No permission for --mod")); + this.sendErrorMessage(msg.channel, "No permission for --mod"); return; } @@ -700,14 +830,18 @@ export class ModActionsPlugin extends ZeppelinPlugin { }); } - @d.command("ban", " [reason:string$]", { + @d.command("ban", " [reason:string$]", { options: [{ name: "mod", type: "member" }], }) @d.permission("can_ban") - async banCmd(msg, args: { userId: string; reason?: string; mod?: Member }) { - const { member: memberToBan, isBanned } = await this.resolveMember(args.userId); + async banCmd(msg, args: { user: string; reason?: string; mod?: Member }) { + const user = await this.resolveUser(args.user); + if (!user) return this.sendErrorMessage(msg.channel, `User not found`); + + const memberToBan = await this.getMember(user.id); if (!memberToBan) { + const isBanned = await this.isBanned(user.id); if (isBanned) { this.sendErrorMessage(msg.channel, `User is already banned`); } else { @@ -719,7 +853,7 @@ export class ModActionsPlugin extends ZeppelinPlugin { // Make sure we're allowed to ban this member if (!this.canActOn(msg.member, memberToBan)) { - msg.channel.createMessage(errorMessage("Cannot ban: insufficient permissions")); + this.sendErrorMessage(msg.channel, "Cannot ban: insufficient permissions"); return; } @@ -727,7 +861,7 @@ export class ModActionsPlugin extends ZeppelinPlugin { let mod = msg.member; if (args.mod) { if (!this.hasPermission("can_act_as_other", { message: msg })) { - msg.channel.createMessage(errorMessage("No permission for --mod")); + this.sendErrorMessage(msg.channel, "No permission for --mod"); return; } @@ -782,14 +916,18 @@ export class ModActionsPlugin extends ZeppelinPlugin { }); } - @d.command("softban", " [reason:string$]", { + @d.command("softban", " [reason:string$]", { options: [{ name: "mod", type: "member" }], }) @d.permission("can_ban") - async softbanCmd(msg, args: { userId: string; reason: string; mod?: Member }) { - const { member: memberToSoftban, isBanned } = await this.resolveMember(args.userId); + async softbanCmd(msg, args: { user: string; reason: string; mod?: Member }) { + const user = await this.resolveUser(args.user); + if (!user) return this.sendErrorMessage(msg.channel, `User not found`); + + const memberToSoftban = await this.getMember(user.id); if (!memberToSoftban) { + const isBanned = await this.isBanned(user.id); if (isBanned) { this.sendErrorMessage(msg.channel, `User is already banned`); } else { @@ -801,7 +939,7 @@ export class ModActionsPlugin extends ZeppelinPlugin { // Make sure we're allowed to ban this member if (!this.canActOn(msg.member, memberToSoftban)) { - msg.channel.createMessage(errorMessage("Cannot ban: insufficient permissions")); + this.sendErrorMessage(msg.channel, "Cannot ban: insufficient permissions"); return; } @@ -809,7 +947,7 @@ export class ModActionsPlugin extends ZeppelinPlugin { let mod = msg.member; if (args.mod) { if (!this.hasPermission("can_act_as_other", { message: msg })) { - msg.channel.createMessage(errorMessage("No permission for --mod")); + this.sendErrorMessage(msg.channel, "No permission for --mod"); return; } @@ -852,29 +990,32 @@ export class ModActionsPlugin extends ZeppelinPlugin { }); } - @d.command("unban", " [reason:string$]", { + @d.command("unban", " [reason:string$]", { options: [{ name: "mod", type: "member" }], }) @d.permission("can_ban") - async unbanCmd(msg: Message, args: { userId: string; reason: string; mod: Member }) { + async unbanCmd(msg: Message, args: { user: string; reason: string; mod: Member }) { + const user = await this.resolveUser(args.user); + if (!user) return this.sendErrorMessage(msg.channel, `User not found`); + // The moderator who did the action is the message author or, if used, the specified --mod let mod = msg.member; if (args.mod) { if (!this.hasPermission("can_act_as_other", { message: msg })) { - msg.channel.createMessage(errorMessage("No permission for --mod")); + this.sendErrorMessage(msg.channel, "No permission for --mod"); return; } mod = args.mod; } - this.serverLogs.ignoreLog(LogType.MEMBER_UNBAN, args.userId); + this.serverLogs.ignoreLog(LogType.MEMBER_UNBAN, user.id); try { - this.ignoreEvent(IgnoredEventType.Unban, args.userId); - await this.guild.unbanMember(args.userId); + this.ignoreEvent(IgnoredEventType.Unban, user.id); + await this.guild.unbanMember(user.id); } catch (e) { - msg.channel.createMessage(errorMessage("Failed to unban member; are you sure they're banned?")); + this.sendErrorMessage(msg.channel, "Failed to unban member; are you sure they're banned?"); return; } @@ -882,7 +1023,7 @@ export class ModActionsPlugin extends ZeppelinPlugin { // Create a case const createdCase = await this.actions.fire("createCase", { - userId: args.userId, + userId: user.id, modId: mod.id, type: CaseTypes.Unban, reason, @@ -895,19 +1036,22 @@ export class ModActionsPlugin extends ZeppelinPlugin { // Log the action this.serverLogs.log(LogType.MEMBER_UNBAN, { mod: stripObjectToScalars(mod.user), - userId: args.userId, + userId: user.id, }); } - @d.command("forceban", " [reason:string$]", { + @d.command("forceban", " [reason:string$]", { options: [{ name: "mod", type: "member" }], }) @d.permission("can_ban") - async forcebanCmd(msg: Message, args: any) { + async forcebanCmd(msg: Message, args: { user: string; reason?: string; mod?: Member }) { + const user = await this.resolveUser(args.user); + if (!user) return this.sendErrorMessage(msg.channel, `User not found`); + // If the user exists as a guild member, make sure we can act on them first - const member = this.guild.members.get(args.userId); + const member = await this.getMember(user.id); if (member && !this.canActOn(msg.member, member)) { - msg.channel.createMessage(errorMessage("Cannot forceban this user: insufficient permissions")); + this.sendErrorMessage(msg.channel, "Cannot forceban this user: insufficient permissions"); return; } @@ -915,7 +1059,7 @@ export class ModActionsPlugin extends ZeppelinPlugin { let mod = msg.member; if (args.mod) { if (!this.hasPermission("can_act_as_other", { message: msg })) { - msg.channel.createMessage(errorMessage("No permission for --mod")); + this.sendErrorMessage(msg.channel, "No permission for --mod"); return; } @@ -924,19 +1068,19 @@ export class ModActionsPlugin extends ZeppelinPlugin { const reason = this.formatReasonWithAttachments(args.reason, msg.attachments); - this.ignoreEvent(IgnoredEventType.Ban, args.userId); - this.serverLogs.ignoreLog(LogType.MEMBER_BAN, args.userId); + this.ignoreEvent(IgnoredEventType.Ban, user.id); + this.serverLogs.ignoreLog(LogType.MEMBER_BAN, user.id); try { - await this.guild.banMember(args.userId, 1, reason); + await this.guild.banMember(user.id, 1, reason); } catch (e) { - msg.channel.createMessage(errorMessage("Failed to forceban member")); + this.sendErrorMessage(msg.channel, "Failed to forceban member"); return; } // Create a case const createdCase = await this.actions.fire("createCase", { - userId: args.userId, + userId: user.id, modId: mod.id, type: CaseTypes.Ban, reason, @@ -949,7 +1093,7 @@ export class ModActionsPlugin extends ZeppelinPlugin { // Log the action this.serverLogs.log(LogType.MEMBER_FORCEBAN, { mod: stripObjectToScalars(mod.user), - userId: args.userId, + userId: user.id, }); } @@ -958,7 +1102,7 @@ export class ModActionsPlugin extends ZeppelinPlugin { async massbanCmd(msg: Message, args: { userIds: string[] }) { // Limit to 100 users at once (arbitrary?) if (args.userIds.length > 100) { - msg.channel.createMessage(errorMessage(`Can only massban max 100 users at once`)); + this.sendErrorMessage(msg.channel, `Can only massban max 100 users at once`); return; } @@ -966,7 +1110,7 @@ export class ModActionsPlugin extends ZeppelinPlugin { msg.channel.createMessage("Ban reason? `cancel` to cancel"); const banReasonReply = await waitForReply(this.bot, msg.channel as TextChannel, msg.author.id); if (!banReasonReply || !banReasonReply.content || banReasonReply.content.toLowerCase().trim() === "cancel") { - msg.channel.createMessage("Cancelled"); + this.sendErrorMessage(msg.channel, "Cancelled"); return; } @@ -976,7 +1120,7 @@ export class ModActionsPlugin extends ZeppelinPlugin { for (const userId of args.userIds) { const member = this.guild.members.get(userId); if (member && !this.canActOn(msg.member, member)) { - msg.channel.createMessage(errorMessage("Cannot massban one or more users: insufficient permissions")); + this.sendErrorMessage(msg.channel, "Cannot massban one or more users: insufficient permissions"); return; } } @@ -1016,7 +1160,7 @@ export class ModActionsPlugin extends ZeppelinPlugin { const successfulBanCount = args.userIds.length - failedBans.length; if (successfulBanCount === 0) { // All bans failed - don't create a log entry and notify the user - msg.channel.createMessage(errorMessage("All bans failed. Make sure the IDs are valid.")); + this.sendErrorMessage(msg.channel, "All bans failed. Make sure the IDs are valid."); } else { // Some or all bans were successful. Create a log entry for the mass ban and notify the user. this.serverLogs.log(LogType.MASSBAN, { @@ -1034,21 +1178,18 @@ export class ModActionsPlugin extends ZeppelinPlugin { } } - @d.command("addcase", " [reason:string$]", { + @d.command("addcase", " [reason:string$]", { options: [{ name: "mod", type: "member" }], }) @d.permission("can_addcase") - async addcaseCmd(msg: Message, args: { type: string; target: string; reason?: string; mod?: Member }) { - // Verify the user id is a valid snowflake-ish - if (!args.target.match(/^[0-9]{17,20}$/)) { - msg.channel.createMessage(errorMessage("Cannot add case: invalid user id")); - return; - } + async addcaseCmd(msg: Message, args: { type: string; user: string; reason?: string; mod?: Member }) { + const user = await this.resolveUser(args.user); + if (!user) return this.sendErrorMessage(msg.channel, `User not found`); // If the user exists as a guild member, make sure we can act on them first - const member = this.guild.members.get(args.target); + const member = await this.getMember(user.id); if (member && !this.canActOn(msg.member, member)) { - msg.channel.createMessage(errorMessage("Cannot add case on this user: insufficient permissions")); + this.sendErrorMessage(msg.channel, "Cannot add case on this user: insufficient permissions"); return; } @@ -1056,7 +1197,7 @@ export class ModActionsPlugin extends ZeppelinPlugin { let mod = msg.member; if (args.mod) { if (!this.hasPermission("can_act_as_other", { message: msg })) { - msg.channel.createMessage(errorMessage("No permission for --mod")); + this.sendErrorMessage(msg.channel, "No permission for --mod"); return; } @@ -1066,7 +1207,7 @@ export class ModActionsPlugin extends ZeppelinPlugin { // Verify the case type is valid const type: string = args.type[0].toUpperCase() + args.type.slice(1).toLowerCase(); if (!CaseTypes[type]) { - msg.channel.createMessage(errorMessage("Cannot add case: invalid case type")); + this.sendErrorMessage(msg.channel, "Cannot add case: invalid case type"); return; } @@ -1074,14 +1215,13 @@ export class ModActionsPlugin extends ZeppelinPlugin { // Create the case const theCase: Case = await this.actions.fire("createCase", { - userId: args.target, + userId: user.id, modId: mod.id, type: CaseTypes[type], reason, ppId: mod.id !== msg.author.id ? msg.author.id : null, }); - const user = member ? member.user : this.bot.users.get(args.target); if (user) { msg.channel.createMessage( successMessage(`Case #${theCase.case_number} created for **${user.username}#${user.discriminator}**`), @@ -1093,7 +1233,7 @@ export class ModActionsPlugin extends ZeppelinPlugin { // Log the action this.serverLogs.log(LogType.CASE_CREATE, { mod: stripObjectToScalars(mod.user), - userId: args.target, + userId: user.id, caseNum: theCase.case_number, caseType: type.toUpperCase(), }); @@ -1111,7 +1251,7 @@ export class ModActionsPlugin extends ZeppelinPlugin { const theCase = await this.cases.findByCaseNumber(args.caseNumber); if (!theCase) { - msg.channel.createMessage(errorMessage("Case not found")); + this.sendErrorMessage(msg.channel, "Case not found"); return; } @@ -1121,7 +1261,7 @@ export class ModActionsPlugin extends ZeppelinPlugin { }); } - @d.command("cases", " [opts:string$]", { + @d.command("cases", " [opts:string$]", { options: [ { name: "expand", @@ -1136,12 +1276,14 @@ export class ModActionsPlugin extends ZeppelinPlugin { ], }) @d.permission("can_view") - async userCasesCmd(msg: Message, args: { userId: string; opts?: string; expand?: boolean; hidden?: boolean }) { - const cases = await this.cases.with("notes").getByUserId(args.userId); + async userCasesCmd(msg: Message, args: { user: string; opts?: string; expand?: boolean; hidden?: boolean }) { + const user = await this.resolveUser(args.user); + if (!user) return this.sendErrorMessage(msg.channel, `User not found`); + + const cases = await this.cases.with("notes").getByUserId(user.id); const normalCases = cases.filter(c => !c.is_hidden); const hiddenCases = cases.filter(c => c.is_hidden); - const user = this.bot.users.get(args.userId) || unknownUser; const userName = `${user.username}#${user.discriminator}`; if (cases.length === 0) { @@ -1233,7 +1375,7 @@ export class ModActionsPlugin extends ZeppelinPlugin { async hideCaseCmd(msg: Message, args: { caseNum: number }) { const theCase = await this.cases.findByCaseNumber(args.caseNum); if (!theCase) { - msg.channel.createMessage(errorMessage("Case not found!")); + this.sendErrorMessage(msg.channel, "Case not found!"); return; } @@ -1248,11 +1390,11 @@ export class ModActionsPlugin extends ZeppelinPlugin { async unhideCaseCmd(msg: Message, args: { caseNum: number }) { const theCase = await this.cases.findByCaseNumber(args.caseNum); if (!theCase) { - msg.channel.createMessage(errorMessage("Case not found!")); + this.sendErrorMessage(msg.channel, "Case not found!"); return; } await this.cases.setHidden(theCase.id, false); - msg.channel.createMessage(successMessage(`Case #${theCase.case_number} is no longer hidden!`)); + this.sendSuccessMessage(msg.channel, `Case #${theCase.case_number} is no longer hidden!`); } } diff --git a/src/utils.ts b/src/utils.ts index 5f7367e7..06eeda8d 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -496,8 +496,19 @@ export function ucfirst(str) { return str[0].toUpperCase() + str.slice(1); } -export const unknownUser = { +export type UnknownUser = { + id: string; + username: string; + discriminator: string; + [key: string]: any; +}; + +export const unknownUser: UnknownUser = { id: "0", username: "Unknown", discriminator: "0000", }; + +export function createUnknownUser(props = {}): UnknownUser { + return { ...unknownUser, ...props }; +}