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_MUTE": "🔇 **{member.user.username}#{member.user.discriminator}** (`{member.id}`) was muted by {mod.user.username}#{mod.user.discriminator}",
"MEMBER_UNMUTE": "🔉 **{member.user.username}#{member.user.discriminator}** (`{member.id}`) was unmuted 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.username}#{mod.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_KICK": "👢 **{user.username}#{user.discriminator}** (`{user.id}`) was kicked by {mod.user.username}#{mod.user.discriminator}",
"MEMBER_BAN": "🔨 **{user.username}#{user.discriminator}** (`{user.id}`) was banned by {mod.user.username}#{mod.user.discriminator}",
"MEMBER_UNBAN": "🔓 **{user.username}#{user.discriminator}** (`{user.id}`) was unbanned by {mod.user.username}#{mod.user.discriminator}",
"MEMBER_FORCEBAN": "🔨 User `{userId}` was forcebanned 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.username}#{mod.discriminator}",
"MEMBER_UNBAN": "🔓 **{user.username}#{user.discriminator}** (`{user.id}`) was unbanned by {mod.username}#{mod.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_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_REMOVE": "🔑 **{member.user.username}#{member.user.discriminator}** (`{member.id}`) role removed **{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.username}#{mod.discriminator}",
"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_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}**",
"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_LEAVE": "🔸 **{member.user.username}#{member.user.discriminator}** (`{member.id}`) left **{channel.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}**",
"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;
}
const unknownMember = {
const unknownUser = {
id: 0,
user: {
id: 0,
username: "Unknown",
discriminator: "0000"
}
username: "Unknown",
discriminator: "0000"
};
export class LogsPlugin extends Plugin {
@ -62,7 +59,7 @@ export class LogsPlugin extends Plugin {
this.serverLogs.removeListener("log", this.logListener);
}
log(type, data) {
async log(type, data) {
const logChannels: ILogChannelMap = this.configValue("channels");
for (const [channelId, opts] of Object.entries(logChannels)) {
const channel = this.guild.channels.get(channelId);
@ -73,7 +70,7 @@ export class LogsPlugin extends Plugin {
(opts.exclude && !opts.exclude.includes(type))
) {
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")
async onMemberBan(_, user) {
const relevantAuditLogEntry = await findRelevantAuditLogEntry(
this.bot,
this.guild,
ErisConstants.AuditLogActions.MEMBER_BAN_ADD,
user.id
);
const mod = relevantAuditLogEntry ? relevantAuditLogEntry.user : unknownUser;
if (relevantAuditLogEntry) {
this.log(LogType.MEMBER_BAN, {
user: stripObjectToScalars(user),
mod: relevantAuditLogEntry.member
});
} else {
this.log(LogType.MEMBER_BAN, {
user: stripObjectToScalars(user),
mod: unknownMember
});
}
this.log(LogType.MEMBER_BAN, {
user: stripObjectToScalars(user),
mod: stripObjectToScalars(mod)
});
}
@d.event("guildBanRemove")
async onMemberUnban(_, user) {
const relevantAuditLogEntry = await findRelevantAuditLogEntry(
this.bot,
this.guild,
ErisConstants.AuditLogActions.MEMBER_BAN_REMOVE,
user.id
);
const mod = relevantAuditLogEntry ? relevantAuditLogEntry.user : unknownUser;
if (relevantAuditLogEntry) {
this.log(LogType.MEMBER_UNBAN, {
user: stripObjectToScalars(user),
mod: relevantAuditLogEntry.member
});
} else {
this.log(LogType.MEMBER_UNBAN, {
user: stripObjectToScalars(user),
mod: unknownMember
});
}
this.log(LogType.MEMBER_UNBAN, {
user: stripObjectToScalars(user),
mod: stripObjectToScalars(mod)
});
}
@d.event("guildMemberUpdate")
onMemberUpdate(_, member: Member, oldMember: Member) {
async onMemberUpdate(_, member: Member, oldMember: Member) {
if (!oldMember) return;
if (member.nick !== oldMember.nick) {
@ -170,18 +155,27 @@ export class LogsPlugin extends Plugin {
}
if (!isEqual(oldMember.roles, member.roles)) {
const addedRoles = diff(oldMember.roles, member.roles);
const removedRoles = diff(member.roles, oldMember.roles);
const addedRoles = 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) {
this.log(LogType.MEMBER_ROLE_ADD, {
member,
role: this.guild.roles.get(addedRoles[0])
role: this.guild.roles.get(addedRoles[0]),
mod: stripObjectToScalars(mod)
});
} else if (removedRoles.length) {
this.log(LogType.MEMBER_ROLE_REMOVE, {
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")
onRoleCreate(role) {
onRoleCreate(_, role) {
this.log(LogType.ROLE_CREATE, {
role: stripObjectToScalars(role)
});
}
@d.event("guildRoleDelete")
onRoleDelete(role) {
onRoleDelete(_, role) {
this.log(LogType.ROLE_DELETE, {
role: stripObjectToScalars(role)
});
@ -236,8 +230,8 @@ export class LogsPlugin extends Plugin {
this.log(LogType.MESSAGE_EDIT, {
member: stripObjectToScalars(msg.member, ["user"]),
channel: stripObjectToScalars(msg.channel),
before: oldMsg ? oldMsg.cleanContent || "" : "Unavailable due to restart",
after: msg.cleanContent || ""
before: oldMsg ? oldMsg.content || "" : "Unavailable due to restart",
after: msg.content || ""
});
}
@ -275,7 +269,7 @@ export class LogsPlugin extends Plugin {
}
@d.event("voiceChannelSwitch")
onVoiceChannelSwitch(member: Member, oldChannel: Channel, newChannel: Channel) {
onVoiceChannelSwitch(member: Member, newChannel: Channel, oldChannel: Channel) {
this.log(LogType.VOICE_CHANNEL_MOVE, {
member: stripObjectToScalars(member, ["user"]),
oldChannel: stripObjectToScalars(oldChannel),

View file

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

View file

@ -1,5 +1,5 @@
import at = require("lodash.at");
import { GuildAuditLogEntry } from "eris";
import { Guild, GuildAuditLogEntry } from "eris";
/**
* 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);
}
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
*/
export async function findRelevantAuditLogEntry(
bot,
guild: Guild,
actionType: number,
userId: string,
attempts: number = 3,
attemptDelay: number = 1500
attemptDelay: number = 3000
): 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) => {
if (a.createdAt > b.createdAt) return -1;
@ -101,13 +107,14 @@ export async function findRelevantAuditLogEntry(
const cutoffTS = Date.now() - 1000 * 60 * 2;
const relevantEntry = auditLogEntries.entries.find(entry => {
return entry.target.id === userId && entry.createdAt >= cutoffTS;
return entry.targetID === userId && entry.createdAt >= cutoffTS;
});
if (relevantEntry) {
return relevantEntry;
} else if (attempts > 0) {
return findRelevantAuditLogEntry(bot, actionType, userId, attempts - 1, attemptDelay);
await sleep(attemptDelay);
return findRelevantAuditLogEntry(guild, actionType, userId, attempts - 1, attemptDelay);
} else {
return null;
}