More work on logging and automatic mod actions

This commit is contained in:
Dragory 2018-07-29 23:30:24 +03:00
parent 724c30703f
commit 0c806f32fd
4 changed files with 101 additions and 81 deletions

View file

@ -1,16 +1,16 @@
{ {
"MEMBER_WARN": "⚠️ **{member.user.username}#{member.user.discriminator}** (`{member.id}`) was warned by {mod.user.username}#{mod.user.discriminator}", "MEMBER_WARN": "⚠️ **{member.user.username}#{member.user.discriminator}** (`{member.id}`) was warned by {mod.username}#{mod.discriminator}",
"MEMBER_MUTE": "🔇 **{member.user.username}#{member.user.discriminator}** (`{member.id}`) was muted by {mod.user.username}#{mod.user.discriminator}", "MEMBER_MUTE": "🔇 **{member.user.username}#{member.user.discriminator}** (`{member.id}`) was muted by {mod.username}#{mod.discriminator}",
"MEMBER_UNMUTE": "🔉 **{member.user.username}#{member.user.discriminator}** (`{member.id}`) was unmuted by {mod.user.username}#{mod.user.discriminator}", "MEMBER_UNMUTE": "🔉 **{member.user.username}#{member.user.discriminator}** (`{member.id}`) was unmuted by {mod.username}#{mod.discriminator}",
"MEMBER_MUTE_EXPIRED": "🔉 **{member.user.username}#{member.user.discriminator}**'s mute expired", "MEMBER_MUTE_EXPIRED": "🔉 **{member.user.username}#{member.user.discriminator}**'s mute expired",
"MEMBER_KICK": "👢 **{user.username}#{user.discriminator}** (`{user.id}`) was kicked by {mod.user.username}#{mod.user.discriminator}", "MEMBER_KICK": "👢 **{user.username}#{user.discriminator}** (`{user.id}`) was kicked by {mod.username}#{mod.discriminator}",
"MEMBER_BAN": "🔨 **{user.username}#{user.discriminator}** (`{user.id}`) was banned by {mod.user.username}#{mod.user.discriminator}", "MEMBER_BAN": "🔨 **{user.username}#{user.discriminator}** (`{user.id}`) was banned by {mod.username}#{mod.discriminator}",
"MEMBER_UNBAN": "🔓 **{user.username}#{user.discriminator}** (`{user.id}`) was unbanned by {mod.user.username}#{mod.user.discriminator}", "MEMBER_UNBAN": "🔓 **{user.username}#{user.discriminator}** (`{user.id}`) was unbanned by {mod.username}#{mod.discriminator}",
"MEMBER_FORCEBAN": "🔨 User `{userId}` was forcebanned by {mod.user.username}#{mod.user.discriminator}", "MEMBER_FORCEBAN": "🔨 User `{userId}` was forcebanned by {mod.username}#{mod.discriminator}",
"MEMBER_JOIN": "📥 **{member.user.username}#{member.user.discriminator}** (`{member.id}`) joined{new} (created {account_age} ago)", "MEMBER_JOIN": "📥 **{member.user.username}#{member.user.discriminator}** (`{member.id}`) joined{new} (created {account_age} ago)",
"MEMBER_LEAVE": "📤 **{member.user.username}#{member.user.discriminator}** (`{member.id}`) left the server", "MEMBER_LEAVE": "📤 **{member.user.username}#{member.user.discriminator}** (`{member.id}`) left the server",
"MEMBER_ROLE_ADD": "🔑 **{member.user.username}#{member.user.discriminator}** (`{member.id}`) role added **{role.name}** by {mod.user.username}#{mod.user.discriminator}", "MEMBER_ROLE_ADD": "🔑 **{member.user.username}#{member.user.discriminator}** (`{member.id}`) role added **{role.name}** by {mod.username}#{mod.discriminator}",
"MEMBER_ROLE_REMOVE": "🔑 **{member.user.username}#{member.user.discriminator}** (`{member.id}`) role removed **{role.name}** by {mod.user.username}#{mod.user.discriminator}", "MEMBER_ROLE_REMOVE": "🔑 **{member.user.username}#{member.user.discriminator}** (`{member.id}`) role removed **{role.name}** by {mod.username}#{mod.discriminator}",
"MEMBER_NICK_CHANGE": "✏ **{member.user.username}#{member.user.discriminator}** (`{member.id}`) changed their nickname from **{oldNick}** to **{newNick}**", "MEMBER_NICK_CHANGE": "✏ **{member.user.username}#{member.user.discriminator}** (`{member.id}`) changed their nickname from **{oldNick}** to **{newNick}**",
"MEMBER_USERNAME_CHANGE": "✏ **{member.user.username}#{member.user.discriminator}** (`{member.id}`) changed their username from **{oldName}** to **{newName}**", "MEMBER_USERNAME_CHANGE": "✏ **{member.user.username}#{member.user.discriminator}** (`{member.id}`) changed their username from **{oldName}** to **{newName}**",
"MEMBER_ROLES_RESTORE": "💿 **{member.user.username}#{member.user.discriminator}** (`{member.id}`) roles were restored", "MEMBER_ROLES_RESTORE": "💿 **{member.user.username}#{member.user.discriminator}** (`{member.id}`) roles were restored",
@ -28,8 +28,8 @@
"MESSAGE_DELETE_BULK": "🗑 **{count}** messages deleted in **{channel.name}**", "MESSAGE_DELETE_BULK": "🗑 **{count}** messages deleted in **{channel.name}**",
"VOICE_CHANNEL_JOIN": "🔸 **{member.user.username}#{member.user.discriminator}** (`{member.id}`) joined **{channel.name}**", "VOICE_CHANNEL_JOIN": "🔸 **{member.user.username}#{member.user.discriminator}** (`{member.id}`) joined **{channel.name}**",
"VOICE_CHANNEL_MOVE": "🔸 **{member.user.username}#{member.user.discriminator}** (`{member.id}`) moved **{oldChannel.name}** ➞ **{newChannel.name}**", "VOICE_CHANNEL_MOVE": "🔹 **{member.user.username}#{member.user.discriminator}** (`{member.id}`) moved **{oldChannel.name}** ➞ **{newChannel.name}**",
"VOICE_CHANNEL_LEAVE": "🔸 **{member.user.username}#{member.user.discriminator}** (`{member.id}`) left **{channel.name}**", "VOICE_CHANNEL_LEAVE": " **{member.user.username}#{member.user.discriminator}** (`{member.id}`) left **{channel.name}**",
"COMMAND": "🤖 **{member.user.username}#{member.user.discriminator}** (`{member.id}`) used command in **{channel.name}**:\n`{command}`", "COMMAND": "🤖 **{member.user.username}#{member.user.discriminator}** (`{member.id}`) used command in **{channel.name}**:\n`{command}`",

View file

@ -26,13 +26,10 @@ interface ILogChannelMap {
[channelId: string]: ILogChannel; [channelId: string]: ILogChannel;
} }
const unknownMember = { const unknownUser = {
id: 0, id: 0,
user: { username: "Unknown",
id: 0, discriminator: "0000"
username: "Unknown",
discriminator: "0000"
}
}; };
export class LogsPlugin extends Plugin { export class LogsPlugin extends Plugin {
@ -62,7 +59,7 @@ export class LogsPlugin extends Plugin {
this.serverLogs.removeListener("log", this.logListener); this.serverLogs.removeListener("log", this.logListener);
} }
log(type, data) { async log(type, data) {
const logChannels: ILogChannelMap = this.configValue("channels"); const logChannels: ILogChannelMap = this.configValue("channels");
for (const [channelId, opts] of Object.entries(logChannels)) { for (const [channelId, opts] of Object.entries(logChannels)) {
const channel = this.guild.channels.get(channelId); const channel = this.guild.channels.get(channelId);
@ -73,7 +70,7 @@ export class LogsPlugin extends Plugin {
(opts.exclude && !opts.exclude.includes(type)) (opts.exclude && !opts.exclude.includes(type))
) { ) {
const message = this.getLogMessage(type, data); const message = this.getLogMessage(type, data);
if (message) channel.createMessage(message); if (message) await channel.createMessage(message);
} }
} }
} }
@ -118,47 +115,35 @@ export class LogsPlugin extends Plugin {
@d.event("guildBanAdd") @d.event("guildBanAdd")
async onMemberBan(_, user) { async onMemberBan(_, user) {
const relevantAuditLogEntry = await findRelevantAuditLogEntry( const relevantAuditLogEntry = await findRelevantAuditLogEntry(
this.bot, this.guild,
ErisConstants.AuditLogActions.MEMBER_BAN_ADD, ErisConstants.AuditLogActions.MEMBER_BAN_ADD,
user.id user.id
); );
const mod = relevantAuditLogEntry ? relevantAuditLogEntry.user : unknownUser;
if (relevantAuditLogEntry) { this.log(LogType.MEMBER_BAN, {
this.log(LogType.MEMBER_BAN, { user: stripObjectToScalars(user),
user: stripObjectToScalars(user), mod: stripObjectToScalars(mod)
mod: relevantAuditLogEntry.member });
});
} else {
this.log(LogType.MEMBER_BAN, {
user: stripObjectToScalars(user),
mod: unknownMember
});
}
} }
@d.event("guildBanRemove") @d.event("guildBanRemove")
async onMemberUnban(_, user) { async onMemberUnban(_, user) {
const relevantAuditLogEntry = await findRelevantAuditLogEntry( const relevantAuditLogEntry = await findRelevantAuditLogEntry(
this.bot, this.guild,
ErisConstants.AuditLogActions.MEMBER_BAN_REMOVE, ErisConstants.AuditLogActions.MEMBER_BAN_REMOVE,
user.id user.id
); );
const mod = relevantAuditLogEntry ? relevantAuditLogEntry.user : unknownUser;
if (relevantAuditLogEntry) { this.log(LogType.MEMBER_UNBAN, {
this.log(LogType.MEMBER_UNBAN, { user: stripObjectToScalars(user),
user: stripObjectToScalars(user), mod: stripObjectToScalars(mod)
mod: relevantAuditLogEntry.member });
});
} else {
this.log(LogType.MEMBER_UNBAN, {
user: stripObjectToScalars(user),
mod: unknownMember
});
}
} }
@d.event("guildMemberUpdate") @d.event("guildMemberUpdate")
onMemberUpdate(_, member: Member, oldMember: Member) { async onMemberUpdate(_, member: Member, oldMember: Member) {
if (!oldMember) return; if (!oldMember) return;
if (member.nick !== oldMember.nick) { if (member.nick !== oldMember.nick) {
@ -170,18 +155,27 @@ export class LogsPlugin extends Plugin {
} }
if (!isEqual(oldMember.roles, member.roles)) { if (!isEqual(oldMember.roles, member.roles)) {
const addedRoles = diff(oldMember.roles, member.roles); const addedRoles = diff(member.roles, oldMember.roles);
const removedRoles = diff(member.roles, oldMember.roles); const removedRoles = diff(oldMember.roles, member.roles);
const relevantAuditLogEntry = await findRelevantAuditLogEntry(
this.guild,
ErisConstants.AuditLogActions.MEMBER_ROLE_UPDATE,
member.id
);
const mod = relevantAuditLogEntry ? relevantAuditLogEntry.user : unknownUser;
if (addedRoles.length) { if (addedRoles.length) {
this.log(LogType.MEMBER_ROLE_ADD, { this.log(LogType.MEMBER_ROLE_ADD, {
member, member,
role: this.guild.roles.get(addedRoles[0]) role: this.guild.roles.get(addedRoles[0]),
mod: stripObjectToScalars(mod)
}); });
} else if (removedRoles.length) { } else if (removedRoles.length) {
this.log(LogType.MEMBER_ROLE_REMOVE, { this.log(LogType.MEMBER_ROLE_REMOVE, {
member, member,
role: this.guild.roles.get(removedRoles[0]) role: this.guild.roles.get(removedRoles[0]),
mod: stripObjectToScalars(mod)
}); });
} }
} }
@ -216,14 +210,14 @@ export class LogsPlugin extends Plugin {
} }
@d.event("guildRoleCreate") @d.event("guildRoleCreate")
onRoleCreate(role) { onRoleCreate(_, role) {
this.log(LogType.ROLE_CREATE, { this.log(LogType.ROLE_CREATE, {
role: stripObjectToScalars(role) role: stripObjectToScalars(role)
}); });
} }
@d.event("guildRoleDelete") @d.event("guildRoleDelete")
onRoleDelete(role) { onRoleDelete(_, role) {
this.log(LogType.ROLE_DELETE, { this.log(LogType.ROLE_DELETE, {
role: stripObjectToScalars(role) role: stripObjectToScalars(role)
}); });
@ -236,8 +230,8 @@ export class LogsPlugin extends Plugin {
this.log(LogType.MESSAGE_EDIT, { this.log(LogType.MESSAGE_EDIT, {
member: stripObjectToScalars(msg.member, ["user"]), member: stripObjectToScalars(msg.member, ["user"]),
channel: stripObjectToScalars(msg.channel), channel: stripObjectToScalars(msg.channel),
before: oldMsg ? oldMsg.cleanContent || "" : "Unavailable due to restart", before: oldMsg ? oldMsg.content || "" : "Unavailable due to restart",
after: msg.cleanContent || "" after: msg.content || ""
}); });
} }
@ -275,7 +269,7 @@ export class LogsPlugin extends Plugin {
} }
@d.event("voiceChannelSwitch") @d.event("voiceChannelSwitch")
onVoiceChannelSwitch(member: Member, oldChannel: Channel, newChannel: Channel) { onVoiceChannelSwitch(member: Member, newChannel: Channel, oldChannel: Channel) {
this.log(LogType.VOICE_CHANNEL_MOVE, { this.log(LogType.VOICE_CHANNEL_MOVE, {
member: stripObjectToScalars(member, ["user"]), member: stripObjectToScalars(member, ["user"]),
oldChannel: stripObjectToScalars(oldChannel), oldChannel: stripObjectToScalars(oldChannel),

View file

@ -1,5 +1,5 @@
import { Plugin, decorators as d, waitForReaction } from "knub"; import { decorators as d, Plugin, waitForReaction } from "knub";
import { Guild, GuildAuditLogEntry, Member, Message, TextChannel, User } from "eris"; import { Constants as ErisConstants, Guild, Member, Message, TextChannel, User } from "eris";
import moment from "moment-timezone"; import moment from "moment-timezone";
import humanizeDuration from "humanize-duration"; import humanizeDuration from "humanize-duration";
import { GuildCases } from "../data/GuildCases"; import { GuildCases } from "../data/GuildCases";
@ -8,21 +8,16 @@ import {
errorMessage, errorMessage,
findRelevantAuditLogEntry, findRelevantAuditLogEntry,
formatTemplateString, formatTemplateString,
sleep,
stripObjectToScalars, stripObjectToScalars,
successMessage successMessage
} from "../utils"; } from "../utils";
import { GuildMutes } from "../data/GuildMutes"; import { GuildMutes } from "../data/GuildMutes";
import Timer = NodeJS.Timer;
import Case from "../models/Case"; import Case from "../models/Case";
import { CaseType } from "../data/CaseType"; import { CaseType } from "../data/CaseType";
import { GuildLogs } from "../data/GuildLogs"; import { GuildLogs } from "../data/GuildLogs";
import { LogType } from "../data/LogType"; import { LogType } from "../data/LogType";
import Timer = NodeJS.Timer;
const sleep = (ms: number): Promise<void> => {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
};
export class ModActionsPlugin extends Plugin { export class ModActionsPlugin extends Plugin {
protected cases: GuildCases; protected cases: GuildCases;
@ -102,8 +97,8 @@ export class ModActionsPlugin extends Plugin {
async onGuildBanAdd(guild: Guild, user: User) { async onGuildBanAdd(guild: Guild, user: User) {
await sleep(1000); // Wait a moment for the audit log to update await sleep(1000); // Wait a moment for the audit log to update
const relevantAuditLogEntry = await findRelevantAuditLogEntry( const relevantAuditLogEntry = await findRelevantAuditLogEntry(
this.bot, this.guild,
"MEMBER_BAN_ADD", ErisConstants.AuditLogActions.MEMBER_BAN_ADD,
user.id user.id
); );
@ -131,8 +126,8 @@ export class ModActionsPlugin extends Plugin {
@d.event("guildBanRemove") @d.event("guildBanRemove")
async onGuildBanRemove(guild: Guild, user: User) { async onGuildBanRemove(guild: Guild, user: User) {
const relevantAuditLogEntry = await findRelevantAuditLogEntry( const relevantAuditLogEntry = await findRelevantAuditLogEntry(
this.bot, this.guild,
"MEMBER_BAN_REMOVE", ErisConstants.AuditLogActions.MEMBER_BAN_REMOVE,
user.id user.id
); );
@ -150,7 +145,7 @@ export class ModActionsPlugin extends Plugin {
* Show an alert if a member with prior notes joins the server * Show an alert if a member with prior notes joins the server
*/ */
@d.event("guildMemberAdd") @d.event("guildMemberAdd")
async onGuildMemberAdd(member: Member) { async onGuildMemberAdd(_, member: Member) {
if (!this.configValue("alert_on_rejoin")) return; if (!this.configValue("alert_on_rejoin")) return;
const alertChannelId = this.configValue("alert_channel"); const alertChannelId = this.configValue("alert_channel");
@ -168,6 +163,30 @@ export class ModActionsPlugin extends Plugin {
} }
} }
@d.event("guildMemberRemove")
async onGuildMemberRemove(_, member: Member) {
const kickAuditLogEntry = await findRelevantAuditLogEntry(
this.guild,
ErisConstants.AuditLogActions.MEMBER_KICK,
member.id
);
if (kickAuditLogEntry) {
this.createCase(
member.id,
kickAuditLogEntry.user.id,
CaseType.Kick,
kickAuditLogEntry.id,
kickAuditLogEntry.reason,
true
);
this.serverLogs.log(LogType.MEMBER_KICK, {
user: stripObjectToScalars(member.user),
mod: stripObjectToScalars(kickAuditLogEntry.user)
});
}
}
/** /**
* Update the specified case by adding more notes/details to it * Update the specified case by adding more notes/details to it
*/ */
@ -228,7 +247,7 @@ export class ModActionsPlugin extends Plugin {
msg.channel.createMessage(successMessage("Member warned")); msg.channel.createMessage(successMessage("Member warned"));
this.serverLogs.log(LogType.MEMBER_WARN, { this.serverLogs.log(LogType.MEMBER_WARN, {
mod: stripObjectToScalars(msg.member, ["user"]), mod: stripObjectToScalars(msg.member.user),
member: stripObjectToScalars(args.member, ["user"]) member: stripObjectToScalars(args.member, ["user"])
}); });
} }
@ -298,7 +317,7 @@ export class ModActionsPlugin extends Plugin {
// Log the action // Log the action
this.serverLogs.log(LogType.MEMBER_MUTE, { this.serverLogs.log(LogType.MEMBER_MUTE, {
mod: stripObjectToScalars(msg.member, ["user"]), mod: stripObjectToScalars(msg.member.user),
member: stripObjectToScalars(args.member, ["user"]) member: stripObjectToScalars(args.member, ["user"])
}); });
} }
@ -337,7 +356,7 @@ export class ModActionsPlugin extends Plugin {
// Log the action // Log the action
this.serverLogs.log(LogType.MEMBER_UNMUTE, { this.serverLogs.log(LogType.MEMBER_UNMUTE, {
mod: stripObjectToScalars(msg.member, ["user"]), mod: stripObjectToScalars(msg.member.user),
member: stripObjectToScalars(args.member, ["user"]) member: stripObjectToScalars(args.member, ["user"])
}); });
} }
@ -381,7 +400,7 @@ export class ModActionsPlugin extends Plugin {
// Log the action // Log the action
this.serverLogs.log(LogType.MEMBER_KICK, { this.serverLogs.log(LogType.MEMBER_KICK, {
mod: stripObjectToScalars(msg.member, ["user"]), mod: stripObjectToScalars(msg.member.user),
member: stripObjectToScalars(args.member, ["user"]) member: stripObjectToScalars(args.member, ["user"])
}); });
} }
@ -425,7 +444,7 @@ export class ModActionsPlugin extends Plugin {
// Log the action // Log the action
this.serverLogs.log(LogType.MEMBER_BAN, { this.serverLogs.log(LogType.MEMBER_BAN, {
mod: stripObjectToScalars(msg.member, ["user"]), mod: stripObjectToScalars(msg.member.user),
member: stripObjectToScalars(args.member, ["user"]) member: stripObjectToScalars(args.member, ["user"])
}); });
} }
@ -450,7 +469,7 @@ export class ModActionsPlugin extends Plugin {
// Log the action // Log the action
this.serverLogs.log(LogType.MEMBER_UNBAN, { this.serverLogs.log(LogType.MEMBER_UNBAN, {
mod: stripObjectToScalars(msg.member, ["user"]), mod: stripObjectToScalars(msg.member.user),
userId: args.userId userId: args.userId
}); });
} }
@ -484,7 +503,7 @@ export class ModActionsPlugin extends Plugin {
// Log the action // Log the action
this.serverLogs.log(LogType.MEMBER_FORCEBAN, { this.serverLogs.log(LogType.MEMBER_FORCEBAN, {
mod: stripObjectToScalars(msg.member, ["user"]), mod: stripObjectToScalars(msg.member.user),
userId: args.userId userId: args.userId
}); });
} }
@ -527,7 +546,7 @@ export class ModActionsPlugin extends Plugin {
// Log the action // Log the action
msg.channel.createMessage(successMessage("Case created!")); msg.channel.createMessage(successMessage("Case created!"));
this.serverLogs.log(LogType.CASE_CREATE, { this.serverLogs.log(LogType.CASE_CREATE, {
mod: stripObjectToScalars(msg.member, ["user"]), mod: stripObjectToScalars(msg.member.user),
userId: args.userId, userId: args.userId,
caseNum: theCase.case_number, caseNum: theCase.case_number,
caseType: type.toUpperCase() caseType: type.toUpperCase()

View file

@ -1,5 +1,5 @@
import at = require("lodash.at"); import at = require("lodash.at");
import { GuildAuditLogEntry } from "eris"; import { Guild, GuildAuditLogEntry } from "eris";
/** /**
* Turns a "delay string" such as "1h30m" to milliseconds * Turns a "delay string" such as "1h30m" to milliseconds
@ -80,17 +80,23 @@ export function isSnowflake(v: string): boolean {
return /^\d{17,20}$/.test(v); return /^\d{17,20}$/.test(v);
} }
export function sleep(ms: number): Promise<void> {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
}
/** /**
* Attempts to find a relevant audit log entry for the given user and action * Attempts to find a relevant audit log entry for the given user and action
*/ */
export async function findRelevantAuditLogEntry( export async function findRelevantAuditLogEntry(
bot, guild: Guild,
actionType: number, actionType: number,
userId: string, userId: string,
attempts: number = 3, attempts: number = 3,
attemptDelay: number = 1500 attemptDelay: number = 3000
): Promise<GuildAuditLogEntry> { ): Promise<GuildAuditLogEntry> {
const auditLogEntries = await this.bot.getGuildAuditLogs(this.guildId, 5, null, actionType); const auditLogEntries = await guild.getAuditLogs(5, null, actionType);
auditLogEntries.entries.sort((a, b) => { auditLogEntries.entries.sort((a, b) => {
if (a.createdAt > b.createdAt) return -1; if (a.createdAt > b.createdAt) return -1;
@ -101,13 +107,14 @@ export async function findRelevantAuditLogEntry(
const cutoffTS = Date.now() - 1000 * 60 * 2; const cutoffTS = Date.now() - 1000 * 60 * 2;
const relevantEntry = auditLogEntries.entries.find(entry => { const relevantEntry = auditLogEntries.entries.find(entry => {
return entry.target.id === userId && entry.createdAt >= cutoffTS; return entry.targetID === userId && entry.createdAt >= cutoffTS;
}); });
if (relevantEntry) { if (relevantEntry) {
return relevantEntry; return relevantEntry;
} else if (attempts > 0) { } else if (attempts > 0) {
return findRelevantAuditLogEntry(bot, actionType, userId, attempts - 1, attemptDelay); await sleep(attemptDelay);
return findRelevantAuditLogEntry(guild, actionType, userId, attempts - 1, attemptDelay);
} else { } else {
return null; return null;
} }