Deprecate GuildActions. Fix double case posting when muting a user for message spam. Update to new mute/case style when muting a user for "other" spam.
GuildActions turned out to be a fairly pointless abstraction in the end. It didn't really solve the problems it was meant to solve (that is, reduce code spaghetti by having all inter-plugin calls go through a single service, and allow easier ways to replace core plugins with alternatives that share the same interface) any better than simply using `this.getPlugin()` when needed, and introduced extra complexity and made static analysis messier.
This commit is contained in:
parent
89ce0555a7
commit
608f17c532
10 changed files with 319 additions and 376 deletions
|
@ -1,81 +0,0 @@
|
|||
import { BaseRepository } from "./BaseRepository";
|
||||
import { Member, TextableChannel } from "eris";
|
||||
import { CaseTypes } from "./CaseTypes";
|
||||
import { ICaseDetails } from "./GuildCases";
|
||||
import { Case } from "./entities/Case";
|
||||
import { INotifyUserResult } from "../utils";
|
||||
|
||||
type KnownActions = "mute" | "unmute";
|
||||
|
||||
// https://github.com/Microsoft/TypeScript/issues/4183#issuecomment-382867018
|
||||
type UnknownAction<T extends string> = T extends KnownActions ? never : T;
|
||||
|
||||
type ActionFn<T> = (args: T) => any | Promise<any>;
|
||||
|
||||
type MuteActionArgs = { userId: string; muteTime?: number; reason?: string; caseDetails?: ICaseDetails };
|
||||
type UnmuteActionArgs = { userId: string; unmuteTime?: number; reason?: string; caseDetails?: ICaseDetails };
|
||||
type CreateCaseActionArgs = ICaseDetails;
|
||||
type CreateCaseNoteActionArgs = {
|
||||
caseId: number;
|
||||
modId: string;
|
||||
note: string;
|
||||
automatic?: boolean;
|
||||
postInCaseLog?: boolean;
|
||||
noteDetails?: string[];
|
||||
};
|
||||
type PostCaseActionArgs = {
|
||||
caseId: number;
|
||||
channel: TextableChannel;
|
||||
};
|
||||
|
||||
export type MuteActionResult = {
|
||||
case: Case;
|
||||
notifyResult: INotifyUserResult;
|
||||
updatedExistingMute: boolean;
|
||||
};
|
||||
|
||||
export type UnmuteActionResult = {
|
||||
case: Case;
|
||||
};
|
||||
|
||||
export class GuildActions extends BaseRepository {
|
||||
private actions: Map<string, ActionFn<any>>;
|
||||
|
||||
constructor(guildId) {
|
||||
super(guildId);
|
||||
this.actions = new Map();
|
||||
}
|
||||
|
||||
public register(actionName: "mute", actionFn: ActionFn<MuteActionArgs>): void;
|
||||
public register(actionName: "unmute", actionFn: ActionFn<UnmuteActionArgs>): void;
|
||||
public register(actionName: "createCase", actionFn: ActionFn<CreateCaseActionArgs>): void;
|
||||
public register(actionName: "createCaseNote", actionFn: ActionFn<CreateCaseNoteActionArgs>): void;
|
||||
public register(actionName: "postCase", actionFn: ActionFn<PostCaseActionArgs>): void;
|
||||
// https://github.com/Microsoft/TypeScript/issues/4183#issuecomment-382867018
|
||||
public register<T extends string & UnknownAction<U>, U extends string = T>(
|
||||
actionName: T,
|
||||
actionFn: ActionFn<any>,
|
||||
): void;
|
||||
public register(actionName, actionFn): void {
|
||||
if (this.actions.has(actionName)) {
|
||||
throw new Error("Action is already registered!");
|
||||
}
|
||||
|
||||
this.actions.set(actionName, actionFn);
|
||||
}
|
||||
|
||||
public unregister(actionName: string): void {
|
||||
this.actions.delete(actionName);
|
||||
}
|
||||
|
||||
public fire(actionName: "mute", args: MuteActionArgs): Promise<MuteActionResult>;
|
||||
public fire(actionName: "unmute", args: UnmuteActionArgs): Promise<UnmuteActionResult>;
|
||||
public fire(actionName: "createCase", args: CreateCaseActionArgs): Promise<any>;
|
||||
public fire(actionName: "createCaseNote", args: CreateCaseNoteActionArgs): Promise<any>;
|
||||
public fire(actionName: "postCase", args: PostCaseActionArgs): Promise<any>;
|
||||
// https://github.com/Microsoft/TypeScript/issues/4183#issuecomment-382867018
|
||||
public fire<T extends string & UnknownAction<U>, U extends string = T>(actionName: T, args: any): Promise<any>;
|
||||
public fire(actionName, args): Promise<any> {
|
||||
return this.actions.has(actionName) ? this.actions.get(actionName)(args) : null;
|
||||
}
|
||||
}
|
|
@ -8,22 +8,6 @@ import moment = require("moment-timezone");
|
|||
|
||||
const CASE_SUMMARY_REASON_MAX_LENGTH = 300;
|
||||
|
||||
/**
|
||||
* Used as a config object for functions that create cases
|
||||
*/
|
||||
export interface ICaseDetails {
|
||||
userId?: string;
|
||||
modId?: string;
|
||||
ppId?: string;
|
||||
type?: CaseTypes;
|
||||
auditLogId?: string;
|
||||
reason?: string;
|
||||
automatic?: boolean;
|
||||
postInCaseLogOverride?: boolean;
|
||||
noteDetails?: string[];
|
||||
extraNotes?: string[];
|
||||
}
|
||||
|
||||
export class GuildCases extends BaseRepository {
|
||||
private cases: Repository<Case>;
|
||||
private caseNotes: Repository<CaseNote>;
|
||||
|
|
30
src/index.ts
30
src/index.ts
|
@ -4,7 +4,7 @@ import yaml from "js-yaml";
|
|||
import fs from "fs";
|
||||
const fsp = fs.promises;
|
||||
|
||||
import { Knub, logger, PluginError, CommandArgumentTypeError } from "knub";
|
||||
import { Knub, logger, PluginError, CommandArgumentTypeError, Plugin } from "knub";
|
||||
import { SimpleError } from "./SimpleError";
|
||||
|
||||
require("dotenv").config();
|
||||
|
@ -75,6 +75,7 @@ import { PingableRolesPlugin } from "./plugins/PingableRolesPlugin";
|
|||
import { SelfGrantableRolesPlugin } from "./plugins/SelfGrantableRolesPlugin";
|
||||
import { RemindersPlugin } from "./plugins/Reminders";
|
||||
import { convertDelayStringToMS, errorMessage, successMessage } from "./utils";
|
||||
import { ZeppelinPlugin } from "./plugins/ZeppelinPlugin";
|
||||
|
||||
// Run latest database migrations
|
||||
logger.info("Running database migrations");
|
||||
|
@ -125,11 +126,30 @@ connect().then(async conn => {
|
|||
|
||||
options: {
|
||||
getEnabledPlugins(guildId, guildConfig): string[] {
|
||||
const plugins = guildConfig.plugins || {};
|
||||
const keys: string[] = Array.from(this.plugins.keys());
|
||||
return keys.filter(pluginName => {
|
||||
return basePlugins.includes(pluginName) || (plugins[pluginName] && plugins[pluginName].enabled !== false);
|
||||
const configuredPlugins = guildConfig.plugins || {};
|
||||
const pluginNames: string[] = Array.from(this.plugins.keys());
|
||||
const plugins: Array<typeof Plugin> = Array.from(this.plugins.values());
|
||||
const zeppelinPlugins: Array<typeof ZeppelinPlugin> = plugins.filter(
|
||||
p => p.prototype instanceof ZeppelinPlugin,
|
||||
) as Array<typeof ZeppelinPlugin>;
|
||||
|
||||
const enabledBasePlugins = pluginNames.filter(n => basePlugins.includes(n));
|
||||
const explicitlyEnabledPlugins = pluginNames.filter(pluginName => {
|
||||
return configuredPlugins[pluginName] && configuredPlugins[pluginName].enabled !== false;
|
||||
});
|
||||
const enabledPlugins = new Set([...enabledBasePlugins, ...explicitlyEnabledPlugins]);
|
||||
|
||||
const pluginsEnabledAsDependencies = zeppelinPlugins.reduce((arr, pluginClass) => {
|
||||
if (!enabledPlugins.has(pluginClass.pluginName)) return arr;
|
||||
return arr.concat(pluginClass.dependencies);
|
||||
}, []);
|
||||
|
||||
const finalEnabledPlugins = new Set([
|
||||
...basePlugins,
|
||||
...pluginsEnabledAsDependencies,
|
||||
...explicitlyEnabledPlugins,
|
||||
]);
|
||||
return Array.from(finalEnabledPlugins.values());
|
||||
},
|
||||
|
||||
async getConfig(id) {
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import { Message, MessageContent, MessageFile, TextableChannel, TextChannel } from "eris";
|
||||
import { GuildCases, ICaseDetails } from "../data/GuildCases";
|
||||
import { Message, MessageContent, MessageFile, TextChannel } from "eris";
|
||||
import { GuildCases } from "../data/GuildCases";
|
||||
import { CaseTypes } from "../data/CaseTypes";
|
||||
import { Case } from "../data/entities/Case";
|
||||
import moment from "moment-timezone";
|
||||
import { CaseTypeColors } from "../data/CaseTypeColors";
|
||||
import { ZeppelinPlugin } from "./ZeppelinPlugin";
|
||||
import { GuildActions } from "../data/GuildActions";
|
||||
import { GuildArchives } from "../data/GuildArchives";
|
||||
import { IPluginOptions } from "knub";
|
||||
|
||||
|
@ -14,10 +13,34 @@ interface ICasesPluginConfig {
|
|||
case_log_channel: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can also be used as a config object for functions that create cases
|
||||
*/
|
||||
export type CaseArgs = {
|
||||
userId: string;
|
||||
modId: string;
|
||||
ppId?: string;
|
||||
type: CaseTypes;
|
||||
auditLogId?: string;
|
||||
reason?: string;
|
||||
automatic?: boolean;
|
||||
postInCaseLogOverride?: boolean;
|
||||
noteDetails?: string[];
|
||||
extraNotes?: string[];
|
||||
};
|
||||
|
||||
export type CaseNoteArgs = {
|
||||
caseId: number;
|
||||
modId: string;
|
||||
body: string;
|
||||
automatic?: boolean;
|
||||
postInCaseLogOverride?: boolean;
|
||||
noteDetails?: string[];
|
||||
};
|
||||
|
||||
export class CasesPlugin extends ZeppelinPlugin<ICasesPluginConfig> {
|
||||
public static pluginName = "cases";
|
||||
|
||||
protected actions: GuildActions;
|
||||
protected cases: GuildCases;
|
||||
protected archives: GuildArchives;
|
||||
|
||||
|
@ -31,35 +54,13 @@ export class CasesPlugin extends ZeppelinPlugin<ICasesPluginConfig> {
|
|||
}
|
||||
|
||||
onLoad() {
|
||||
this.actions = GuildActions.getInstance(this.guildId);
|
||||
this.cases = GuildCases.getInstance(this.guildId);
|
||||
this.archives = GuildArchives.getInstance(this.guildId);
|
||||
|
||||
this.actions.register("createCase", args => {
|
||||
return this.createCase(args);
|
||||
});
|
||||
|
||||
this.actions.register("createCaseNote", args => {
|
||||
return this.createCaseNote(
|
||||
args.caseId,
|
||||
args.modId,
|
||||
args.note,
|
||||
args.automatic,
|
||||
args.postInCaseLog,
|
||||
args.noteDetails,
|
||||
);
|
||||
});
|
||||
|
||||
this.actions.register("postCase", async args => {
|
||||
const embed = await this.getCaseEmbed(args.caseId);
|
||||
return (args.channel as TextableChannel).createMessage(embed);
|
||||
});
|
||||
}
|
||||
|
||||
onUnload() {
|
||||
this.actions.unregister("createCase");
|
||||
this.actions.unregister("createCaseNote");
|
||||
this.actions.unregister("postCase");
|
||||
// this.actions.register("postCase", async args => {
|
||||
// const embed = await this.getCaseEmbed(args.caseId);
|
||||
// return (args.channel as TextableChannel).createMessage(embed);
|
||||
// });
|
||||
}
|
||||
|
||||
protected resolveCaseId(caseOrCaseId: Case | number): number {
|
||||
|
@ -68,39 +69,51 @@ export class CasesPlugin extends ZeppelinPlugin<ICasesPluginConfig> {
|
|||
|
||||
/**
|
||||
* Creates a new case and, depending on config, posts it in the case log channel
|
||||
* @return {Number} The ID of the created case
|
||||
*/
|
||||
public async createCase(opts: ICaseDetails): Promise<Case> {
|
||||
const user = this.bot.users.get(opts.userId);
|
||||
const userName = user ? `${user.username}#${user.discriminator}` : "Unknown#0000";
|
||||
public async createCase(args: CaseArgs): Promise<Case> {
|
||||
const user = await this.resolveUser(args.userId);
|
||||
const userName = `${user.username}#${user.discriminator}`;
|
||||
|
||||
const mod = this.bot.users.get(opts.modId);
|
||||
const modName = mod ? `${mod.username}#${mod.discriminator}` : "Unknown#0000";
|
||||
const mod = await this.resolveUser(args.modId);
|
||||
const modName = `${mod.username}#${mod.discriminator}`;
|
||||
|
||||
let ppName = null;
|
||||
if (opts.ppId) {
|
||||
const pp = this.bot.users.get(opts.ppId);
|
||||
ppName = pp ? `${pp.username}#${pp.discriminator}` : "Unknown#0000";
|
||||
if (args.ppId) {
|
||||
const pp = await this.resolveUser(args.ppId);
|
||||
ppName = `${pp.username}#${pp.discriminator}`;
|
||||
}
|
||||
|
||||
const createdCase = await this.cases.create({
|
||||
type: opts.type,
|
||||
user_id: opts.userId,
|
||||
type: args.type,
|
||||
user_id: args.userId,
|
||||
user_name: userName,
|
||||
mod_id: opts.modId,
|
||||
mod_id: args.modId,
|
||||
mod_name: modName,
|
||||
audit_log_id: opts.auditLogId,
|
||||
pp_id: opts.ppId,
|
||||
audit_log_id: args.auditLogId,
|
||||
pp_id: args.ppId,
|
||||
pp_name: ppName,
|
||||
});
|
||||
|
||||
if (opts.reason || (opts.noteDetails && opts.noteDetails.length)) {
|
||||
await this.createCaseNote(createdCase, opts.modId, opts.reason || "", opts.automatic, false, opts.noteDetails);
|
||||
if (args.reason || (args.noteDetails && args.noteDetails.length)) {
|
||||
await this.createCaseNote({
|
||||
caseId: createdCase.id,
|
||||
modId: args.modId,
|
||||
body: args.reason || "",
|
||||
automatic: args.automatic,
|
||||
postInCaseLogOverride: false,
|
||||
noteDetails: args.noteDetails,
|
||||
});
|
||||
}
|
||||
|
||||
if (opts.extraNotes) {
|
||||
for (const extraNote of opts.extraNotes) {
|
||||
await this.createCaseNote(createdCase, opts.modId, extraNote, opts.automatic, false);
|
||||
if (args.extraNotes) {
|
||||
for (const extraNote of args.extraNotes) {
|
||||
await this.createCaseNote({
|
||||
caseId: createdCase.id,
|
||||
modId: args.modId,
|
||||
body: extraNote,
|
||||
automatic: args.automatic,
|
||||
postInCaseLogOverride: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -108,8 +121,8 @@ export class CasesPlugin extends ZeppelinPlugin<ICasesPluginConfig> {
|
|||
|
||||
if (
|
||||
config.case_log_channel &&
|
||||
(!opts.automatic || config.log_automatic_actions) &&
|
||||
opts.postInCaseLogOverride !== false
|
||||
(!args.automatic || config.log_automatic_actions) &&
|
||||
args.postInCaseLogOverride !== false
|
||||
) {
|
||||
try {
|
||||
await this.postCaseToCaseLogChannel(createdCase);
|
||||
|
@ -122,29 +135,24 @@ export class CasesPlugin extends ZeppelinPlugin<ICasesPluginConfig> {
|
|||
/**
|
||||
* Adds a case note to an existing case and, depending on config, posts the updated case in the case log channel
|
||||
*/
|
||||
public async createCaseNote(
|
||||
caseOrCaseId: Case | number,
|
||||
modId: string,
|
||||
body: string,
|
||||
automatic = false,
|
||||
postInCaseLogOverride = null,
|
||||
noteDetails: string[] = null,
|
||||
): Promise<void> {
|
||||
const mod = this.bot.users.get(modId);
|
||||
const modName = mod ? `${mod.username}#${mod.discriminator}` : "Unknown#0000";
|
||||
|
||||
const theCase = await this.cases.find(this.resolveCaseId(caseOrCaseId));
|
||||
public async createCaseNote(args: CaseNoteArgs): Promise<void> {
|
||||
const theCase = await this.cases.find(this.resolveCaseId(args.caseId));
|
||||
if (!theCase) {
|
||||
this.throwPluginRuntimeError(`Unknown case ID: ${caseOrCaseId}`);
|
||||
this.throwPluginRuntimeError(`Unknown case ID: ${args.caseId}`);
|
||||
}
|
||||
|
||||
const mod = await this.resolveUser(args.modId);
|
||||
const modName = `${mod.username}#${mod.discriminator}`;
|
||||
|
||||
let body = args.body;
|
||||
|
||||
// Add note details to the beginning of the note
|
||||
if (noteDetails && noteDetails.length) {
|
||||
body = noteDetails.map(d => `__[${d}]__`).join(" ") + " " + body;
|
||||
if (args.noteDetails && args.noteDetails.length) {
|
||||
body = args.noteDetails.map(d => `__[${d}]__`).join(" ") + " " + body;
|
||||
}
|
||||
|
||||
await this.cases.createNote(theCase.id, {
|
||||
mod_id: modId,
|
||||
mod_id: mod.id,
|
||||
mod_name: modName,
|
||||
body: body || "",
|
||||
});
|
||||
|
@ -152,7 +160,7 @@ export class CasesPlugin extends ZeppelinPlugin<ICasesPluginConfig> {
|
|||
if (theCase.mod_id == null) {
|
||||
// If the case has no moderator information, assume the first one to add a note to it did the action
|
||||
await this.cases.update(theCase.id, {
|
||||
mod_id: modId,
|
||||
mod_id: mod.id,
|
||||
mod_name: modName,
|
||||
});
|
||||
}
|
||||
|
@ -163,7 +171,7 @@ export class CasesPlugin extends ZeppelinPlugin<ICasesPluginConfig> {
|
|||
this.archives.makePermanent(archiveId);
|
||||
}
|
||||
|
||||
if ((!automatic || this.getConfig().log_automatic_actions) && postInCaseLogOverride !== false) {
|
||||
if ((!args.automatic || this.getConfig().log_automatic_actions) && args.postInCaseLogOverride !== false) {
|
||||
try {
|
||||
await this.postCaseToCaseLogChannel(theCase.id);
|
||||
} catch (e) {} // tslint:disable-line
|
||||
|
|
|
@ -5,7 +5,6 @@ import { GuildCases } from "../data/GuildCases";
|
|||
import {
|
||||
asSingleLine,
|
||||
createChunkedMessage,
|
||||
createUnknownUser,
|
||||
errorMessage,
|
||||
findRelevantAuditLogEntry,
|
||||
INotifyUserResult,
|
||||
|
@ -22,9 +21,10 @@ import { CaseTypes } from "../data/CaseTypes";
|
|||
import { GuildLogs } from "../data/GuildLogs";
|
||||
import { LogType } from "../data/LogType";
|
||||
import { ZeppelinPlugin } from "./ZeppelinPlugin";
|
||||
import { GuildActions, MuteActionResult } from "../data/GuildActions";
|
||||
import { Case } from "../data/entities/Case";
|
||||
import { renderTemplate } from "../templateFormatter";
|
||||
import { CasesPlugin } from "./Cases";
|
||||
import { MuteResult, MutesPlugin } from "./Mutes";
|
||||
|
||||
enum IgnoredEventType {
|
||||
Ban = 1,
|
||||
|
@ -65,8 +65,8 @@ interface IModActionsPluginConfig {
|
|||
|
||||
export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
||||
public static pluginName = "mod_actions";
|
||||
public static dependencies = ["cases", "mutes"];
|
||||
|
||||
protected actions: GuildActions;
|
||||
protected mutes: GuildMutes;
|
||||
protected cases: GuildCases;
|
||||
protected serverLogs: GuildLogs;
|
||||
|
@ -74,7 +74,6 @@ export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
|||
protected ignoredEvents: IIgnoredEvent[];
|
||||
|
||||
async onLoad() {
|
||||
this.actions = GuildActions.getInstance(this.guildId);
|
||||
this.mutes = GuildMutes.getInstance(this.guildId);
|
||||
this.cases = GuildCases.getInstance(this.guildId);
|
||||
this.serverLogs = new GuildLogs(this.guildId);
|
||||
|
@ -156,62 +155,6 @@ export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
|||
return ((reason || "") + " " + attachmentUrls.join(" ")).trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<User | UnknownUser> {
|
||||
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;
|
||||
}
|
||||
|
||||
const cachedUser = this.bot.users.find(u => u.id === userId);
|
||||
if (cachedUser) return cachedUser;
|
||||
|
||||
try {
|
||||
const freshUser = await this.bot.getRESTUser(userId);
|
||||
return freshUser;
|
||||
} catch (e) {} // tslint:disable-line
|
||||
|
||||
return createUnknownUser({ id: userId });
|
||||
}
|
||||
|
||||
async getMember(userId: string): Promise<Member> {
|
||||
// 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
|
||||
}
|
||||
|
||||
return member;
|
||||
}
|
||||
|
||||
async isBanned(userId): Promise<boolean> {
|
||||
const bans = (await this.guild.getBans()) as any;
|
||||
return bans.some(b => b.user.id === userId);
|
||||
|
@ -234,11 +177,12 @@ export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
|||
user.id,
|
||||
);
|
||||
|
||||
const casesPlugin = this.getPlugin<CasesPlugin>("cases");
|
||||
if (relevantAuditLogEntry) {
|
||||
const modId = relevantAuditLogEntry.user.id;
|
||||
const auditLogId = relevantAuditLogEntry.id;
|
||||
|
||||
this.actions.fire("createCase", {
|
||||
casesPlugin.createCase({
|
||||
userId: user.id,
|
||||
modId,
|
||||
type: CaseTypes.Ban,
|
||||
|
@ -247,8 +191,9 @@ export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
|||
automatic: true,
|
||||
});
|
||||
} else {
|
||||
this.actions.fire("createCase", {
|
||||
casesPlugin.createCase({
|
||||
userId: user.id,
|
||||
modId: null,
|
||||
type: CaseTypes.Ban,
|
||||
});
|
||||
}
|
||||
|
@ -271,11 +216,12 @@ export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
|||
user.id,
|
||||
);
|
||||
|
||||
const casesPlugin = this.getPlugin<CasesPlugin>("cases");
|
||||
if (relevantAuditLogEntry) {
|
||||
const modId = relevantAuditLogEntry.user.id;
|
||||
const auditLogId = relevantAuditLogEntry.id;
|
||||
|
||||
this.actions.fire("createCase", {
|
||||
casesPlugin.createCase({
|
||||
userId: user.id,
|
||||
modId,
|
||||
type: CaseTypes.Unban,
|
||||
|
@ -283,8 +229,9 @@ export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
|||
automatic: true,
|
||||
});
|
||||
} else {
|
||||
this.actions.fire("createCase", {
|
||||
casesPlugin.createCase({
|
||||
userId: user.id,
|
||||
modId: null,
|
||||
type: CaseTypes.Unban,
|
||||
automatic: true,
|
||||
});
|
||||
|
@ -337,7 +284,8 @@ export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
|||
}`,
|
||||
);
|
||||
} else {
|
||||
this.actions.fire("createCase", {
|
||||
const casesPlugin = this.getPlugin<CasesPlugin>("cases");
|
||||
casesPlugin.createCase({
|
||||
userId: member.id,
|
||||
modId: kickAuditLogEntry.user.id,
|
||||
type: CaseTypes.Kick,
|
||||
|
@ -374,10 +322,11 @@ export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
|||
return;
|
||||
}
|
||||
|
||||
await this.actions.fire("createCaseNote", {
|
||||
const casesPlugin = this.getPlugin<CasesPlugin>("cases");
|
||||
await casesPlugin.createCaseNote({
|
||||
caseId: theCase.id,
|
||||
modId: msg.author.id,
|
||||
note: args.note,
|
||||
body: args.note,
|
||||
});
|
||||
|
||||
this.serverLogs.log(LogType.CASE_UPDATE, {
|
||||
|
@ -399,7 +348,8 @@ export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
|||
const userName = `${user.username}#${user.discriminator}`;
|
||||
const reason = this.formatReasonWithAttachments(args.note, msg.attachments);
|
||||
|
||||
const createdCase = await this.actions.fire("createCase", {
|
||||
const casesPlugin = this.getPlugin<CasesPlugin>("cases");
|
||||
const createdCase = await casesPlugin.createCase({
|
||||
userId: user.id,
|
||||
modId: msg.author.id,
|
||||
type: CaseTypes.Note,
|
||||
|
@ -466,7 +416,8 @@ export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
|||
}
|
||||
}
|
||||
|
||||
const createdCase: Case = await this.actions.fire("createCase", {
|
||||
const casesPlugin = this.getPlugin<CasesPlugin>("cases");
|
||||
const createdCase = await casesPlugin.createCase({
|
||||
userId: memberToWarn.id,
|
||||
modId: mod.id,
|
||||
type: CaseTypes.Warn,
|
||||
|
@ -513,17 +464,13 @@ export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
|||
const timeUntilUnmute = args.time && humanizeDuration(args.time);
|
||||
const reason = this.formatReasonWithAttachments(args.reason, msg.attachments);
|
||||
|
||||
let muteResult: MuteActionResult;
|
||||
let muteResult: MuteResult;
|
||||
const mutesPlugin = this.getPlugin<MutesPlugin>("mutes");
|
||||
|
||||
try {
|
||||
muteResult = await this.actions.fire("mute", {
|
||||
userId: user.id,
|
||||
muteTime: args.time,
|
||||
reason,
|
||||
caseDetails: {
|
||||
modId: mod.id,
|
||||
ppId: pp && pp.id,
|
||||
},
|
||||
muteResult = await mutesPlugin.muteUser(user.id, args.time, reason, {
|
||||
modId: mod.id,
|
||||
ppId: pp && pp.id,
|
||||
});
|
||||
} catch (e) {
|
||||
logger.error(`Failed to mute user ${user.id}: ${e.stack}`);
|
||||
|
@ -646,14 +593,11 @@ export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
|||
|
||||
const reason = this.formatReasonWithAttachments(args.reason, msg.attachments);
|
||||
|
||||
const result = await this.actions.fire("unmute", {
|
||||
userId: user.id,
|
||||
unmuteTime: args.time,
|
||||
caseDetails: {
|
||||
modId: mod.id,
|
||||
ppId: pp && pp.id,
|
||||
reason,
|
||||
},
|
||||
const mutesPlugin = this.getPlugin<MutesPlugin>("mutes");
|
||||
const result = await mutesPlugin.unmuteUser(user.id, args.time, {
|
||||
modId: mod.id,
|
||||
ppId: pp && pp.id,
|
||||
reason,
|
||||
});
|
||||
|
||||
// Confirm the action to the moderator
|
||||
|
@ -810,7 +754,8 @@ export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
|||
memberToKick.kick(reason);
|
||||
|
||||
// Create a case for this action
|
||||
const createdCase = await this.actions.fire("createCase", {
|
||||
const casesPlugin = this.getPlugin<CasesPlugin>("cases");
|
||||
const createdCase = await casesPlugin.createCase({
|
||||
userId: memberToKick.id,
|
||||
modId: mod.id,
|
||||
type: CaseTypes.Kick,
|
||||
|
@ -896,7 +841,8 @@ export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
|||
memberToBan.ban(1, reason);
|
||||
|
||||
// Create a case for this action
|
||||
const createdCase = await this.actions.fire("createCase", {
|
||||
const casesPlugin = this.getPlugin<CasesPlugin>("cases");
|
||||
const createdCase = await casesPlugin.createCase({
|
||||
userId: memberToBan.id,
|
||||
modId: mod.id,
|
||||
type: CaseTypes.Ban,
|
||||
|
@ -970,7 +916,8 @@ export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
|||
await this.guild.unbanMember(memberToSoftban.id);
|
||||
|
||||
// Create a case for this action
|
||||
const createdCase = await this.actions.fire("createCase", {
|
||||
const casesPlugin = this.getPlugin<CasesPlugin>("cases");
|
||||
const createdCase = await casesPlugin.createCase({
|
||||
userId: memberToSoftban.id,
|
||||
modId: mod.id,
|
||||
type: CaseTypes.Softban,
|
||||
|
@ -1026,7 +973,8 @@ export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
|||
const reason = this.formatReasonWithAttachments(args.reason, msg.attachments);
|
||||
|
||||
// Create a case
|
||||
const createdCase = await this.actions.fire("createCase", {
|
||||
const casesPlugin = this.getPlugin<CasesPlugin>("cases");
|
||||
const createdCase = await casesPlugin.createCase({
|
||||
userId: user.id,
|
||||
modId: mod.id,
|
||||
type: CaseTypes.Unban,
|
||||
|
@ -1083,7 +1031,8 @@ export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
|||
}
|
||||
|
||||
// Create a case
|
||||
const createdCase = await this.actions.fire("createCase", {
|
||||
const casesPlugin = this.getPlugin<CasesPlugin>("cases");
|
||||
const createdCase = await casesPlugin.createCase({
|
||||
userId: user.id,
|
||||
modId: mod.id,
|
||||
type: CaseTypes.Ban,
|
||||
|
@ -1142,16 +1091,17 @@ export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
|||
|
||||
// Ban each user and count failed bans (if any)
|
||||
const failedBans = [];
|
||||
const casesPlugin = this.getPlugin<CasesPlugin>("cases");
|
||||
for (const userId of args.userIds) {
|
||||
try {
|
||||
await this.guild.banMember(userId);
|
||||
|
||||
await this.actions.fire("createCase", {
|
||||
await casesPlugin.createCase({
|
||||
userId,
|
||||
modId: msg.author.id,
|
||||
type: CaseTypes.Ban,
|
||||
reason: `Mass ban: ${banReason}`,
|
||||
postInCaseLog: false,
|
||||
postInCaseLogOverride: false,
|
||||
});
|
||||
} catch (e) {
|
||||
failedBans.push(userId);
|
||||
|
@ -1218,7 +1168,8 @@ export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
|||
const reason = this.formatReasonWithAttachments(args.reason, msg.attachments);
|
||||
|
||||
// Create the case
|
||||
const theCase: Case = await this.actions.fire("createCase", {
|
||||
const casesPlugin = this.getPlugin<CasesPlugin>("cases");
|
||||
const theCase: Case = await casesPlugin.createCase({
|
||||
userId: user.id,
|
||||
modId: mod.id,
|
||||
type: CaseTypes[type],
|
||||
|
@ -1259,10 +1210,9 @@ export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
|||
return;
|
||||
}
|
||||
|
||||
await this.actions.fire("postCase", {
|
||||
caseId: theCase.id,
|
||||
channel: msg.channel,
|
||||
});
|
||||
const casesPlugin = this.getPlugin<CasesPlugin>("cases");
|
||||
const embed = await casesPlugin.getCaseEmbed(theCase.id);
|
||||
msg.channel.createMessage(embed);
|
||||
}
|
||||
|
||||
@d.command("cases", "<user:string> [opts:string$]", {
|
||||
|
@ -1303,11 +1253,10 @@ export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
|||
}
|
||||
|
||||
// Expanded view (= individual case embeds)
|
||||
const casesPlugin = this.getPlugin<CasesPlugin>("cases");
|
||||
for (const theCase of casesToDisplay) {
|
||||
await this.actions.fire("postCase", {
|
||||
caseId: theCase.id,
|
||||
channel: msg.channel,
|
||||
});
|
||||
const embed = await casesPlugin.getCaseEmbed(theCase.id);
|
||||
msg.channel.createMessage(embed);
|
||||
}
|
||||
} else {
|
||||
// Compact view (= regular message with a preview of each case)
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import { Member, Message, User } from "eris";
|
||||
import { GuildCases, ICaseDetails } from "../data/GuildCases";
|
||||
import { GuildCases } from "../data/GuildCases";
|
||||
import moment from "moment-timezone";
|
||||
import { ZeppelinPlugin } from "./ZeppelinPlugin";
|
||||
import { GuildActions } from "../data/GuildActions";
|
||||
import { GuildMutes } from "../data/GuildMutes";
|
||||
import {
|
||||
chunkMessageLines,
|
||||
|
@ -14,7 +13,6 @@ import {
|
|||
stripObjectToScalars,
|
||||
successMessage,
|
||||
ucfirst,
|
||||
unknownUser,
|
||||
} from "../utils";
|
||||
import humanizeDuration from "humanize-duration";
|
||||
import { LogType } from "../data/LogType";
|
||||
|
@ -23,6 +21,8 @@ import { decorators as d, IPluginOptions, logger } from "knub";
|
|||
import { Mute } from "../data/entities/Mute";
|
||||
import { renderTemplate } from "../templateFormatter";
|
||||
import { CaseTypes } from "../data/CaseTypes";
|
||||
import { CaseArgs, CasesPlugin } from "./Cases";
|
||||
import { Case } from "../data/entities/Case";
|
||||
|
||||
interface IMuteWithDetails extends Mute {
|
||||
member?: Member;
|
||||
|
@ -43,10 +43,19 @@ interface IMutesPluginConfig {
|
|||
can_cleanup: boolean;
|
||||
}
|
||||
|
||||
export type MuteResult = {
|
||||
case: Case;
|
||||
notifyResult: INotifyUserResult;
|
||||
updatedExistingMute: boolean;
|
||||
};
|
||||
|
||||
export type UnmuteResult = {
|
||||
case: Case;
|
||||
};
|
||||
|
||||
export class MutesPlugin extends ZeppelinPlugin<IMutesPluginConfig> {
|
||||
public static pluginName = "mutes";
|
||||
|
||||
protected actions: GuildActions;
|
||||
protected mutes: GuildMutes;
|
||||
protected cases: GuildCases;
|
||||
protected serverLogs: GuildLogs;
|
||||
|
@ -84,62 +93,38 @@ export class MutesPlugin extends ZeppelinPlugin<IMutesPluginConfig> {
|
|||
};
|
||||
}
|
||||
|
||||
onLoad() {
|
||||
this.actions = GuildActions.getInstance(this.guildId);
|
||||
protected onLoad() {
|
||||
this.mutes = GuildMutes.getInstance(this.guildId);
|
||||
this.cases = GuildCases.getInstance(this.guildId);
|
||||
this.serverLogs = new GuildLogs(this.guildId);
|
||||
|
||||
this.actions.register("mute", args => {
|
||||
return this.muteUser(args.userId, args.muteTime, args.reason, args.caseDetails);
|
||||
});
|
||||
this.actions.register("unmute", args => {
|
||||
return this.unmuteUser(args.userId, args.unmuteTime, args.caseDetails);
|
||||
});
|
||||
|
||||
// Check for expired mutes every 5s
|
||||
this.clearExpiredMutes();
|
||||
this.muteClearIntervalId = setInterval(() => this.clearExpiredMutes(), 5000);
|
||||
}
|
||||
|
||||
onUnload() {
|
||||
this.actions.unregister("mute");
|
||||
this.actions.unregister("unmute");
|
||||
|
||||
protected onUnload() {
|
||||
clearInterval(this.muteClearIntervalId);
|
||||
}
|
||||
|
||||
async resolveMember(userId: string) {
|
||||
if (this.guild.members.has(userId)) {
|
||||
return this.guild.members.get(userId);
|
||||
}
|
||||
|
||||
try {
|
||||
const member = await this.bot.getRESTGuildMember(this.guildId, userId);
|
||||
return member;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async muteUser(
|
||||
userId: string,
|
||||
muteTime: number = null,
|
||||
reason: string = null,
|
||||
caseDetails: ICaseDetails = {},
|
||||
) {
|
||||
caseArgs: Partial<CaseArgs> = {},
|
||||
): Promise<MuteResult> {
|
||||
const muteRole = this.getConfig().mute_role;
|
||||
if (!muteRole) return;
|
||||
|
||||
const timeUntilUnmute = muteTime && humanizeDuration(muteTime);
|
||||
|
||||
// No mod specified -> mark Zeppelin as the mod
|
||||
if (!caseDetails.modId) {
|
||||
caseDetails.modId = this.bot.user.id;
|
||||
if (!caseArgs.modId) {
|
||||
caseArgs.modId = this.bot.user.id;
|
||||
}
|
||||
|
||||
const user = this.bot.users.get(userId) || { ...unknownUser, id: userId };
|
||||
const member = await this.resolveMember(userId);
|
||||
const user = await this.resolveUser(userId);
|
||||
const member = await this.getMember(user.id);
|
||||
|
||||
if (member) {
|
||||
// Apply mute role if it's missing
|
||||
|
@ -193,17 +178,29 @@ export class MutesPlugin extends ZeppelinPlugin<IMutesPluginConfig> {
|
|||
}
|
||||
|
||||
// Create/update a case
|
||||
const casesPlugin = this.getPlugin<CasesPlugin>("cases");
|
||||
let theCase;
|
||||
|
||||
if (existingMute && existingMute.case_id) {
|
||||
// Update old case
|
||||
// Since mutes can often have multiple notes (extraNotes), we won't post each case note individually,
|
||||
// but instead we'll post the entire case afterwards
|
||||
theCase = await this.cases.find(existingMute.case_id);
|
||||
const noteDetails = [`Mute updated to ${muteTime ? timeUntilUnmute : "indefinite"}`];
|
||||
await this.actions.fire("createCaseNote", {
|
||||
caseId: existingMute.case_id,
|
||||
modId: caseDetails.modId,
|
||||
note: reason,
|
||||
noteDetails,
|
||||
});
|
||||
const reasons = [reason, ...(caseArgs.extraNotes || [])];
|
||||
for (const noteReason of reasons) {
|
||||
await casesPlugin.createCaseNote({
|
||||
caseId: existingMute.case_id,
|
||||
modId: caseArgs.modId,
|
||||
body: noteReason,
|
||||
noteDetails,
|
||||
postInCaseLogOverride: false,
|
||||
});
|
||||
}
|
||||
|
||||
if (caseArgs.postInCaseLogOverride !== false) {
|
||||
casesPlugin.postCaseToCaseLogChannel(existingMute.case_id);
|
||||
}
|
||||
} else {
|
||||
// Create new case
|
||||
const noteDetails = [`Muted ${muteTime ? `for ${timeUntilUnmute}` : "indefinitely"}`];
|
||||
|
@ -211,20 +208,19 @@ export class MutesPlugin extends ZeppelinPlugin<IMutesPluginConfig> {
|
|||
noteDetails.push(ucfirst(notifyResult.text));
|
||||
}
|
||||
|
||||
theCase = await this.actions.fire("createCase", {
|
||||
theCase = await casesPlugin.createCase({
|
||||
...caseArgs,
|
||||
userId,
|
||||
modId: caseDetails.modId,
|
||||
modId: caseArgs.modId,
|
||||
type: CaseTypes.Mute,
|
||||
reason,
|
||||
ppId: caseDetails.ppId,
|
||||
noteDetails,
|
||||
extraNotes: caseDetails.extraNotes,
|
||||
});
|
||||
await this.mutes.setCaseId(user.id, theCase.id);
|
||||
}
|
||||
|
||||
// Log the action
|
||||
const mod = this.bot.users.get(caseDetails.modId) || { ...unknownUser, id: caseDetails.modId };
|
||||
const mod = await this.resolveUser(caseArgs.modId);
|
||||
if (muteTime) {
|
||||
this.serverLogs.log(LogType.MEMBER_TIMED_MUTE, {
|
||||
mod: stripObjectToScalars(mod),
|
||||
|
@ -245,12 +241,16 @@ export class MutesPlugin extends ZeppelinPlugin<IMutesPluginConfig> {
|
|||
};
|
||||
}
|
||||
|
||||
public async unmuteUser(userId: string, unmuteTime: number = null, caseDetails: ICaseDetails = {}) {
|
||||
public async unmuteUser(
|
||||
userId: string,
|
||||
unmuteTime: number = null,
|
||||
caseArgs: Partial<CaseArgs> = {},
|
||||
): Promise<UnmuteResult> {
|
||||
const existingMute = await this.mutes.findExistingMuteForUserId(userId);
|
||||
if (!existingMute) return;
|
||||
|
||||
const user = this.bot.users.get(userId) || { ...unknownUser, id: userId };
|
||||
const member = await this.resolveMember(userId);
|
||||
const user = await this.resolveUser(userId);
|
||||
const member = await this.getMember(userId);
|
||||
|
||||
if (unmuteTime) {
|
||||
// Schedule timed unmute (= just set the mute's duration)
|
||||
|
@ -277,17 +277,17 @@ export class MutesPlugin extends ZeppelinPlugin<IMutesPluginConfig> {
|
|||
noteDetails.push(`Unmuted immediately`);
|
||||
}
|
||||
|
||||
const createdCase = await this.actions.fire("createCase", {
|
||||
const casesPlugin = this.getPlugin<CasesPlugin>("cases");
|
||||
const createdCase = await casesPlugin.createCase({
|
||||
...caseArgs,
|
||||
userId,
|
||||
modId: caseDetails.modId,
|
||||
modId: caseArgs.modId,
|
||||
type: CaseTypes.Unmute,
|
||||
reason: caseDetails.reason,
|
||||
ppId: caseDetails.ppId,
|
||||
noteDetails,
|
||||
});
|
||||
|
||||
// Log the action
|
||||
const mod = this.bot.users.get(caseDetails.modId);
|
||||
const mod = this.bot.users.get(caseArgs.modId);
|
||||
if (unmuteTime) {
|
||||
this.serverLogs.log(LogType.MEMBER_TIMED_UNMUTE, {
|
||||
mod: stripObjectToScalars(mod),
|
||||
|
@ -310,7 +310,7 @@ export class MutesPlugin extends ZeppelinPlugin<IMutesPluginConfig> {
|
|||
options: [{ name: "age", type: "delay" }, { name: "left", type: "boolean" }],
|
||||
})
|
||||
@d.permission("can_view_list")
|
||||
public async muteListCmd(msg: Message, args: { age?: number; left?: boolean }) {
|
||||
protected async muteListCmd(msg: Message, args: { age?: number; left?: boolean }) {
|
||||
const lines = [];
|
||||
|
||||
// Active, logged mutes
|
||||
|
@ -449,7 +449,7 @@ export class MutesPlugin extends ZeppelinPlugin<IMutesPluginConfig> {
|
|||
* Reapply active mutes on join
|
||||
*/
|
||||
@d.event("guildMemberAdd")
|
||||
async onGuildMemberAdd(_, member: Member) {
|
||||
protected async onGuildMemberAdd(_, member: Member) {
|
||||
const mute = await this.mutes.findExistingMuteForUserId(member.id);
|
||||
if (mute) {
|
||||
const muteRole = this.getConfig().mute_role;
|
||||
|
@ -465,7 +465,7 @@ export class MutesPlugin extends ZeppelinPlugin<IMutesPluginConfig> {
|
|||
* Clear active mute from the member if the member is banned
|
||||
*/
|
||||
@d.event("guildBanAdd")
|
||||
async onGuildBanAdd(_, user: User) {
|
||||
protected async onGuildBanAdd(_, user: User) {
|
||||
const mute = await this.mutes.findExistingMuteForUserId(user.id);
|
||||
if (mute) {
|
||||
this.mutes.clear(user.id);
|
||||
|
@ -477,7 +477,7 @@ export class MutesPlugin extends ZeppelinPlugin<IMutesPluginConfig> {
|
|||
*/
|
||||
@d.command("clear_banned_mutes")
|
||||
@d.permission("can_cleanup")
|
||||
async clearBannedMutesCmd(msg: Message) {
|
||||
protected async clearBannedMutesCmd(msg: Message) {
|
||||
await msg.channel.createMessage("Clearing mutes from banned users...");
|
||||
|
||||
const activeMutes = await this.mutes.getActiveMutes();
|
||||
|
@ -505,7 +505,7 @@ export class MutesPlugin extends ZeppelinPlugin<IMutesPluginConfig> {
|
|||
* Clear active mute from the member if the mute role is removed
|
||||
*/
|
||||
@d.event("guildMemberUpdate")
|
||||
async onGuildMemberUpdate(_, member: Member) {
|
||||
protected async onGuildMemberUpdate(_, member: Member) {
|
||||
const muteRole = this.getConfig().mute_role;
|
||||
if (!muteRole) return;
|
||||
|
||||
|
@ -522,7 +522,7 @@ export class MutesPlugin extends ZeppelinPlugin<IMutesPluginConfig> {
|
|||
*/
|
||||
@d.command("clear_mutes_without_role")
|
||||
@d.permission("can_cleanup")
|
||||
async clearMutesWithoutRoleCmd(msg: Message) {
|
||||
protected async clearMutesWithoutRoleCmd(msg: Message) {
|
||||
const activeMutes = await this.mutes.getActiveMutes();
|
||||
const muteRole = this.getConfig().mute_role;
|
||||
if (!muteRole) return;
|
||||
|
@ -545,7 +545,7 @@ export class MutesPlugin extends ZeppelinPlugin<IMutesPluginConfig> {
|
|||
|
||||
@d.command("clear_mute", "<userId:string>")
|
||||
@d.permission("can_cleanup")
|
||||
async clearMuteCmd(msg: Message, args: { userId: string }) {
|
||||
protected async clearMuteCmd(msg: Message, args: { userId: string }) {
|
||||
const mute = await this.mutes.findExistingMuteForUserId(args.userId);
|
||||
if (!mute) {
|
||||
msg.channel.createMessage(errorMessage("No active mutes found for that user id"));
|
||||
|
|
|
@ -8,6 +8,7 @@ import { SavedMessage } from "../data/entities/SavedMessage";
|
|||
import { GuildSavedMessages } from "../data/GuildSavedMessages";
|
||||
|
||||
const NATIVE_SLOWMODE_LIMIT = 6 * 60 * 60; // 6 hours
|
||||
const MAX_SLOWMODE = 60 * 60 * 24 * 365 * 100; // 100 years
|
||||
|
||||
interface ISlowmodePluginConfig {
|
||||
use_native_slowmode: boolean;
|
||||
|
@ -238,6 +239,15 @@ export class SlowmodePlugin extends ZeppelinPlugin<ISlowmodePluginConfig> {
|
|||
const seconds = Math.ceil(convertDelayStringToMS(args.time, "s") / 1000);
|
||||
const useNativeSlowmode = this.getConfigForChannel(channel).use_native_slowmode && seconds <= NATIVE_SLOWMODE_LIMIT;
|
||||
|
||||
if (seconds === 0) {
|
||||
return this.disableSlowmodeCmd(msg, { channel: args.channel });
|
||||
}
|
||||
|
||||
if (seconds > MAX_SLOWMODE) {
|
||||
this.sendErrorMessage(msg.channel, `Sorry, slowmodes can be at most 100 years long. Maybe 99 would be enough?`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (useNativeSlowmode) {
|
||||
// Native slowmode
|
||||
|
||||
|
|
|
@ -17,10 +17,10 @@ import { GuildArchives } from "../data/GuildArchives";
|
|||
import moment from "moment-timezone";
|
||||
import { SavedMessage } from "../data/entities/SavedMessage";
|
||||
import { GuildSavedMessages } from "../data/GuildSavedMessages";
|
||||
import { GuildActions, MuteActionResult } from "../data/GuildActions";
|
||||
import { Case } from "../data/entities/Case";
|
||||
import { GuildMutes } from "../data/GuildMutes";
|
||||
import { ZeppelinPlugin } from "./ZeppelinPlugin";
|
||||
import { MuteResult, MutesPlugin } from "./Mutes";
|
||||
import { CasesPlugin } from "./Cases";
|
||||
|
||||
enum RecentActionType {
|
||||
Message = 1,
|
||||
|
@ -71,7 +71,6 @@ interface ISpamPluginConfig {
|
|||
export class SpamPlugin extends ZeppelinPlugin<ISpamPluginConfig> {
|
||||
public static pluginName = "spam";
|
||||
|
||||
protected actions: GuildActions;
|
||||
protected logs: GuildLogs;
|
||||
protected archives: GuildArchives;
|
||||
protected savedMessages: GuildSavedMessages;
|
||||
|
@ -128,7 +127,6 @@ export class SpamPlugin extends ZeppelinPlugin<ISpamPluginConfig> {
|
|||
}
|
||||
|
||||
onLoad() {
|
||||
this.actions = GuildActions.getInstance(this.guildId);
|
||||
this.logs = new GuildLogs(this.guildId);
|
||||
this.archives = GuildArchives.getInstance(this.guildId);
|
||||
this.savedMessages = GuildSavedMessages.getInstance(this.guildId);
|
||||
|
@ -240,18 +238,15 @@ export class SpamPlugin extends ZeppelinPlugin<ISpamPluginConfig> {
|
|||
const recentActions = this.getRecentActions(type, savedMessage.user_id, savedMessage.channel_id, since);
|
||||
|
||||
// Start by muting them, if enabled
|
||||
let muteResult: MuteActionResult;
|
||||
let muteResult: MuteResult;
|
||||
if (spamConfig.mute && member) {
|
||||
const mutesPlugin = this.getPlugin<MutesPlugin>("mutes");
|
||||
const muteTime = spamConfig.mute_time
|
||||
? convertDelayStringToMS(spamConfig.mute_time.toString())
|
||||
: 120 * 1000;
|
||||
muteResult = await this.actions.fire("mute", {
|
||||
userId: member.id,
|
||||
muteTime,
|
||||
reason: "Automatic spam detection",
|
||||
caseDetails: {
|
||||
modId: this.bot.user.id,
|
||||
},
|
||||
muteResult = await mutesPlugin.muteUser(member.id, muteTime, "Automatic spam detection", {
|
||||
modId: this.bot.user.id,
|
||||
postInCaseLogOverride: false,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -296,18 +291,20 @@ export class SpamPlugin extends ZeppelinPlugin<ISpamPluginConfig> {
|
|||
const archiveUrl = await this.saveSpamArchives(uniqueMessages);
|
||||
|
||||
// Create a case
|
||||
const casesPlugin = this.getPlugin<CasesPlugin>("cases");
|
||||
if (muteResult) {
|
||||
// If the user was muted, the mute already generated a case - in that case, just update the case with extra details
|
||||
// This will also post the case in the case log channel, which we didn't do with the mute initially to avoid
|
||||
// posting the case on the channel twice: once with the initial reason, and then again with the note from here
|
||||
const updateText = trimLines(`
|
||||
Details: ${description} (over ${spamConfig.count} in ${spamConfig.interval}s)
|
||||
${archiveUrl}
|
||||
`);
|
||||
this.actions.fire("createCaseNote", {
|
||||
casesPlugin.createCaseNote({
|
||||
caseId: muteResult.case.id,
|
||||
modId: muteResult.case.mod_id,
|
||||
note: updateText,
|
||||
body: updateText,
|
||||
automatic: true,
|
||||
postInCaseLogOverride: false,
|
||||
});
|
||||
} else {
|
||||
// If the user was not muted, create a note case of the detected spam instead
|
||||
|
@ -316,7 +313,7 @@ export class SpamPlugin extends ZeppelinPlugin<ISpamPluginConfig> {
|
|||
${archiveUrl}
|
||||
`);
|
||||
|
||||
this.actions.fire("createCase", {
|
||||
casesPlugin.createCase({
|
||||
userId: savedMessage.user_id,
|
||||
modId: this.bot.user.id,
|
||||
type: CaseTypes.Note,
|
||||
|
@ -362,42 +359,35 @@ export class SpamPlugin extends ZeppelinPlugin<ISpamPluginConfig> {
|
|||
|
||||
if (recentActionsCount > spamConfig.count) {
|
||||
const member = this.guild.members.get(userId);
|
||||
const details = `${description} (over ${spamConfig.count} in ${spamConfig.interval}s)`;
|
||||
|
||||
// Start by muting them, if enabled
|
||||
if (spamConfig.mute && member) {
|
||||
const muteTime = spamConfig.mute_time ? spamConfig.mute_time * 60 * 1000 : 120 * 1000;
|
||||
this.logs.ignoreLog(LogType.MEMBER_ROLE_ADD, userId);
|
||||
this.actions.fire("mute", { userId: member.id, muteTime, reason: "Automatic spam detection" });
|
||||
const mutesPlugin = this.getPlugin<MutesPlugin>("mutes");
|
||||
const muteTime = spamConfig.mute_time ? convertDelayStringToMS(spamConfig.mute_time.toString()) : 120 * 1000;
|
||||
await mutesPlugin.muteUser(member.id, muteTime, "Automatic spam detection", {
|
||||
modId: this.bot.user.id,
|
||||
extraNotes: [`Details: ${details}`],
|
||||
});
|
||||
} else {
|
||||
// If we're not muting the user, just add a note on them
|
||||
const casesPlugin = this.getPlugin<CasesPlugin>("cases");
|
||||
await casesPlugin.createCase({
|
||||
userId,
|
||||
modId: this.bot.user.id,
|
||||
type: CaseTypes.Note,
|
||||
reason: `Automatic spam detection: ${details}`,
|
||||
});
|
||||
}
|
||||
|
||||
// Clear recent cases
|
||||
this.clearRecentUserActions(RecentActionType.VoiceChannelMove, userId, actionGroupId);
|
||||
|
||||
// Create a case and log the actions taken above
|
||||
const caseType = spamConfig.mute ? CaseTypes.Mute : CaseTypes.Note;
|
||||
const caseText = trimLines(`
|
||||
Automatic spam detection: ${description} (over ${spamConfig.count} in ${spamConfig.interval}s)
|
||||
`);
|
||||
|
||||
this.logs.log(LogType.OTHER_SPAM_DETECTED, {
|
||||
member: stripObjectToScalars(member, ["user"]),
|
||||
description,
|
||||
limit: spamConfig.count,
|
||||
interval: spamConfig.interval,
|
||||
});
|
||||
|
||||
const theCase: Case = await this.actions.fire("createCase", {
|
||||
userId,
|
||||
modId: this.bot.user.id,
|
||||
type: caseType,
|
||||
reason: caseText,
|
||||
automatic: true,
|
||||
});
|
||||
|
||||
// For mutes, also set the mute's case id (for !mutes)
|
||||
if (spamConfig.mute && member) {
|
||||
await this.mutes.setCaseId(userId, theCase.id);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
import { IBasePluginConfig, IPluginOptions, Plugin } from "knub";
|
||||
import { PluginRuntimeError } from "../PluginRuntimeError";
|
||||
import Ajv, { ErrorObject } from "ajv";
|
||||
import { isSnowflake, isUnicodeEmoji } from "../utils";
|
||||
import { createUnknownUser, isSnowflake, isUnicodeEmoji, UnknownUser } from "../utils";
|
||||
import { Member, User } from "eris";
|
||||
|
||||
export class ZeppelinPlugin<TConfig extends {} = IBasePluginConfig> extends Plugin<TConfig> {
|
||||
protected configSchema: any;
|
||||
protected permissionsSchema: any;
|
||||
|
||||
public static dependencies = [];
|
||||
|
||||
protected throwPluginRuntimeError(message: string) {
|
||||
throw new PluginRuntimeError(message, this.runtimePluginName, this.guildId);
|
||||
}
|
||||
|
@ -73,4 +76,64 @@ export class ZeppelinPlugin<TConfig extends {} = IBasePluginConfig> extends Plug
|
|||
public getRegisteredCommands() {
|
||||
return this.commands.commands;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<User | UnknownUser> {
|
||||
if (userResolvable == null) {
|
||||
return createUnknownUser();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
const cachedUser = this.bot.users.find(u => u.id === userId);
|
||||
if (cachedUser) return cachedUser;
|
||||
|
||||
try {
|
||||
const freshUser = await this.bot.getRESTUser(userId);
|
||||
return freshUser;
|
||||
} catch (e) {} // tslint:disable-line
|
||||
|
||||
return createUnknownUser({ id: userId });
|
||||
}
|
||||
|
||||
async getMember(userId: string): Promise<Member> {
|
||||
// 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
|
||||
}
|
||||
|
||||
return member;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -504,7 +504,7 @@ export type UnknownUser = {
|
|||
};
|
||||
|
||||
export const unknownUser: UnknownUser = {
|
||||
id: "0",
|
||||
id: null,
|
||||
username: "Unknown",
|
||||
discriminator: "0000",
|
||||
};
|
||||
|
|
Loading…
Add table
Reference in a new issue