From 57893b5a93340fdf76a66d12c4411adc84a0f1db Mon Sep 17 00:00:00 2001 From: BenSegal855 Date: Thu, 17 Jun 2021 23:39:49 -0400 Subject: [PATCH 1/2] feat: Make manual mutes count towards rule -1 --- packages/boat/src/commands/mod/mute.ts | 5 +++ packages/boat/src/modules/mod/modlog.ts | 36 +++------------ .../modules/mod/{mutedban.ts => mutedmod.ts} | 45 ++++++++++++++++++- packages/boat/src/util.ts | 44 +++++++++++++++++- 4 files changed, 97 insertions(+), 33 deletions(-) rename packages/boat/src/modules/mod/{mutedban.ts => mutedmod.ts} (59%) diff --git a/packages/boat/src/commands/mod/mute.ts b/packages/boat/src/commands/mod/mute.ts index 216dfd09..b4d502f0 100644 --- a/packages/boat/src/commands/mod/mute.ts +++ b/packages/boat/src/commands/mod/mute.ts @@ -64,5 +64,10 @@ export function executor (msg: Message, args: string[]): v mute(msg.channel.guild, target, msg.author, reason, duration) msg.channel.createMessage('Shut') + msg._client.mongo.collection('enforce').insertOne({ + userId: target, + modId: msg.author.id, + rule: -1, + }) return } diff --git a/packages/boat/src/modules/mod/modlog.ts b/packages/boat/src/modules/mod/modlog.ts index 07f62a68..61c7db24 100644 --- a/packages/boat/src/modules/mod/modlog.ts +++ b/packages/boat/src/modules/mod/modlog.ts @@ -20,9 +20,9 @@ * SOFTWARE. */ -import type { CommandClient, Guild, GuildAuditLogEntry, Role, TextableChannel, User } from 'eris' +import type { CommandClient, Guild, Role, TextableChannel, User } from 'eris' import { Constants } from 'eris' -import { sanitizeMarkdown } from '../../util.js' +import { delayedFunction, extractEntryData, sanitizeMarkdown } from '../../util.js' import config from '../../config.js' const TEMPLATE = `**$type | Case $case** @@ -30,11 +30,6 @@ __User__: $user ($userid) __Moderator__: $moderator ($modid) __Reason__: $reason` -function delayedFunction (fn: Function): () => void { - return function (this: CommandClient, ...args: unknown[]) { - setTimeout(() => fn.apply(this, args), 2e3) - } -} function format (type: string, caseId: string, user: User, modName: string, modId: string, reason: string) { return TEMPLATE @@ -58,27 +53,6 @@ async function computeCaseId (channel: TextableChannel): Promise { return parseInt(match[1], 10) + 1 } -function extractEntryData (entry: GuildAuditLogEntry): [ string, string, string ] { - let modId = '' - let modName = '' - let reason = '' - - if (entry.user.id === config.discord.clientID && entry.reason?.startsWith('[')) { - const splittedReason = entry.reason.split(' ') - modName = splittedReason.shift()!.replace('[', '').replace(']', '') - reason = splittedReason.join(' ') - const [ username, discrim ] = modName.split('#') - const mod = entry.guild.members.find((m) => m.username === username && m.discriminator === discrim) - modId = mod ? mod.id : '' // Should not happen - } else { - modId = entry.user.id - modName = `${entry.user.username}#${entry.user.discriminator}` - reason = entry.reason || 'No reason specified.' - } - - return [ modId, modName, reason ] -} - function processBanFactory (type: 'add' | 'remove'): (guild: Guild, user: User) => Promise { return async function (this: CommandClient, guild: Guild, user: User): Promise { const channel = this.getChannel(config.discord.ids.channelModLogs) @@ -91,7 +65,7 @@ function processBanFactory (type: 'add' | 'remove'): (guild: Guild, user: User) const entry = logs.entries.find((auditEntry) => auditEntry.targetID === user.id) if (!entry) return - let [ modId, modName, reason ] = extractEntryData(entry) + let { modId, modName, reason } = extractEntryData(entry) const soft = reason.startsWith('[soft]') if (soft) { @@ -116,7 +90,7 @@ async function processMemberLeave (this: CommandClient, guild: Guild, user: User const logs = await guild.getAuditLog({ actionType: Constants.AuditLogActions.MEMBER_KICK, limit: 5 }) const entry = logs.entries.find((auditEntry) => auditEntry.targetID === user.id) if (entry && Date.now() - Number((BigInt(entry.id) >> BigInt('22')) + BigInt('1420070400000')) < 5000) { - const [ modId, modName, reason ] = extractEntryData(entry) + const { modId, modName, reason } = extractEntryData(entry) const caseId = await computeCaseId(channel) this.createMessage(config.discord.ids.channelModLogs, { @@ -147,7 +121,7 @@ async function processMemberUpdate (this: CommandClient, guild: Guild, user: Use continue } - const [ modId, modName, reason ] = extractEntryData(entry) + const { modId, modName, reason } = extractEntryData(entry) const caseId = await computeCaseId(channel) this.createMessage(config.discord.ids.channelModLogs, { diff --git a/packages/boat/src/modules/mod/mutedban.ts b/packages/boat/src/modules/mod/mutedmod.ts similarity index 59% rename from packages/boat/src/modules/mod/mutedban.ts rename to packages/boat/src/modules/mod/mutedmod.ts index fb07ba5d..79da455c 100644 --- a/packages/boat/src/modules/mod/mutedban.ts +++ b/packages/boat/src/modules/mod/mutedmod.ts @@ -20,10 +20,12 @@ * SOFTWARE. */ -import type { CommandClient, Guild, Member, MemberPartial } from 'eris' +import { CommandClient, Constants, Guild, Member, MemberPartial, Role, User } from 'eris' import { ban } from '../../mod.js' +import { delayedFunction, extractEntryData } from '../../util.js' import config from '../../config.js' + const MAX_INFRACTIONS = 4 async function memberRemove (this: CommandClient, guild: Guild, member: Member | MemberPartial) { @@ -44,6 +46,47 @@ async function memberRemove (this: CommandClient, guild: Guild, member: Member | } } +async function processMemberUpdate (this: CommandClient, guild: Guild, user: User) { + const channel = this.getChannel(config.discord.ids.channelModLogs) + if (!channel || !('getMessages' in channel)) return + + const logs = await guild.getAuditLog({ actionType: Constants.AuditLogActions.MEMBER_ROLE_UPDATE, limit: 5 }) + + for (const entry of logs.entries) { + if (entry.targetID !== user.id + || entry.user.id === config.discord.clientID + || !entry.after + || Date.now() - Number((BigInt(entry.id) >> BigInt('22')) + BigInt('1420070400000')) > 5000) { + continue + } + + const added = entry.after.$add as Role[] | null + const removed = entry.after.$remove as Role[] | null + + const wasAdded = Boolean(added?.find((r) => r.id === config.discord.ids.roleMuted)) + const wasRemoved = Boolean(removed?.find((r) => r.id === config.discord.ids.roleMuted)) + + if (wasRemoved || wasAdded === wasRemoved) { + continue + } + + const { modId } = extractEntryData(entry) + + if (modId === config.discord.clientID) { + continue + } + + this.mongo.collection('enforce').insertOne({ + userId: user.id, + modId: modId, + rule: -1, + }) + + break + } +} + export default function (bot: CommandClient) { bot.on('guildMemberRemove', memberRemove) + bot.on('guildMemberUpdate', delayedFunction(processMemberUpdate)) } diff --git a/packages/boat/src/util.ts b/packages/boat/src/util.ts index 94a6aab6..77d7c0f6 100644 --- a/packages/boat/src/util.ts +++ b/packages/boat/src/util.ts @@ -20,9 +20,10 @@ * SOFTWARE. */ -import type { Guild, GuildTextableChannel, Member, Message } from 'eris' +import type { CommandClient, Guild, GuildAuditLogEntry, GuildTextableChannel, Member, Message } from 'eris' import { readdir, stat } from 'fs/promises' import { URL } from 'url' +import config from './config.js' const DURATION_MAP = { m: 60e3, h: 3600e3, d: 86400e3 } const BYTE_UNITS = [ 'B', 'KB', 'MB', 'GB', 'TB' ] @@ -168,3 +169,44 @@ export function prettyPrintBytes (bytes: number): string { return `${bytes.toFixed(2)} ${BYTE_UNITS[unitIdx]}` } + +/** + * Data pertaining to an Audit Log Entry. + */ +export type AuditEntryData = { + modId: string, + modName: string, + reason: string; +} + +/** + * Extract data from a GuildAuditLogEntry. + * @param entry The GuildAuditLogEntry from which to extract the data + * @returns A `AuditEntryData` containing data from `entry` + */ +export function extractEntryData (entry: GuildAuditLogEntry): AuditEntryData { + let modId = '' + let modName = '' + let reason = '' + + if (entry.user.id === config.discord.clientID && entry.reason?.startsWith('[')) { + const splittedReason = entry.reason.split(' ') + modName = splittedReason.shift()!.replace('[', '').replace(']', '') + reason = splittedReason.join(' ') + const [ username, discrim ] = modName.split('#') + const mod = entry.guild.members.find((m) => m.username === username && m.discriminator === discrim) + modId = mod ? mod.id : '' // Should not happen + } else { + modId = entry.user.id + modName = `${entry.user.username}#${entry.user.discriminator}` + reason = entry.reason || 'No reason specified.' + } + + return { modId: modId, modName: modName, reason: reason } +} + +export function delayedFunction (fn: Function): () => void { + return function (this: CommandClient, ...args: unknown[]) { + setTimeout(() => fn.apply(this, args), 2e3) + } +} From 0953f25e0cdee7c6ead185ad6f8c669393a36d26 Mon Sep 17 00:00:00 2001 From: BenSegal855 Date: Thu, 17 Jun 2021 23:47:03 -0400 Subject: [PATCH 2/2] tweak: linting --- packages/boat/src/modules/mod/mutedmod.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/boat/src/modules/mod/mutedmod.ts b/packages/boat/src/modules/mod/mutedmod.ts index 79da455c..6bc2f660 100644 --- a/packages/boat/src/modules/mod/mutedmod.ts +++ b/packages/boat/src/modules/mod/mutedmod.ts @@ -25,7 +25,6 @@ import { ban } from '../../mod.js' import { delayedFunction, extractEntryData } from '../../util.js' import config from '../../config.js' - const MAX_INFRACTIONS = 4 async function memberRemove (this: CommandClient, guild: Guild, member: Member | MemberPartial) {