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;
|
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 {
|
export class GuildCases extends BaseRepository {
|
||||||
private cases: Repository<Case>;
|
private cases: Repository<Case>;
|
||||||
private caseNotes: Repository<CaseNote>;
|
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";
|
import fs from "fs";
|
||||||
const fsp = fs.promises;
|
const fsp = fs.promises;
|
||||||
|
|
||||||
import { Knub, logger, PluginError, CommandArgumentTypeError } from "knub";
|
import { Knub, logger, PluginError, CommandArgumentTypeError, Plugin } from "knub";
|
||||||
import { SimpleError } from "./SimpleError";
|
import { SimpleError } from "./SimpleError";
|
||||||
|
|
||||||
require("dotenv").config();
|
require("dotenv").config();
|
||||||
|
@ -75,6 +75,7 @@ import { PingableRolesPlugin } from "./plugins/PingableRolesPlugin";
|
||||||
import { SelfGrantableRolesPlugin } from "./plugins/SelfGrantableRolesPlugin";
|
import { SelfGrantableRolesPlugin } from "./plugins/SelfGrantableRolesPlugin";
|
||||||
import { RemindersPlugin } from "./plugins/Reminders";
|
import { RemindersPlugin } from "./plugins/Reminders";
|
||||||
import { convertDelayStringToMS, errorMessage, successMessage } from "./utils";
|
import { convertDelayStringToMS, errorMessage, successMessage } from "./utils";
|
||||||
|
import { ZeppelinPlugin } from "./plugins/ZeppelinPlugin";
|
||||||
|
|
||||||
// Run latest database migrations
|
// Run latest database migrations
|
||||||
logger.info("Running database migrations");
|
logger.info("Running database migrations");
|
||||||
|
@ -125,11 +126,30 @@ connect().then(async conn => {
|
||||||
|
|
||||||
options: {
|
options: {
|
||||||
getEnabledPlugins(guildId, guildConfig): string[] {
|
getEnabledPlugins(guildId, guildConfig): string[] {
|
||||||
const plugins = guildConfig.plugins || {};
|
const configuredPlugins = guildConfig.plugins || {};
|
||||||
const keys: string[] = Array.from(this.plugins.keys());
|
const pluginNames: string[] = Array.from(this.plugins.keys());
|
||||||
return keys.filter(pluginName => {
|
const plugins: Array<typeof Plugin> = Array.from(this.plugins.values());
|
||||||
return basePlugins.includes(pluginName) || (plugins[pluginName] && plugins[pluginName].enabled !== false);
|
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) {
|
async getConfig(id) {
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
import { Message, MessageContent, MessageFile, TextableChannel, TextChannel } from "eris";
|
import { Message, MessageContent, MessageFile, TextChannel } from "eris";
|
||||||
import { GuildCases, ICaseDetails } from "../data/GuildCases";
|
import { GuildCases } from "../data/GuildCases";
|
||||||
import { CaseTypes } from "../data/CaseTypes";
|
import { CaseTypes } from "../data/CaseTypes";
|
||||||
import { Case } from "../data/entities/Case";
|
import { Case } from "../data/entities/Case";
|
||||||
import moment from "moment-timezone";
|
import moment from "moment-timezone";
|
||||||
import { CaseTypeColors } from "../data/CaseTypeColors";
|
import { CaseTypeColors } from "../data/CaseTypeColors";
|
||||||
import { ZeppelinPlugin } from "./ZeppelinPlugin";
|
import { ZeppelinPlugin } from "./ZeppelinPlugin";
|
||||||
import { GuildActions } from "../data/GuildActions";
|
|
||||||
import { GuildArchives } from "../data/GuildArchives";
|
import { GuildArchives } from "../data/GuildArchives";
|
||||||
import { IPluginOptions } from "knub";
|
import { IPluginOptions } from "knub";
|
||||||
|
|
||||||
|
@ -14,10 +13,34 @@ interface ICasesPluginConfig {
|
||||||
case_log_channel: string;
|
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> {
|
export class CasesPlugin extends ZeppelinPlugin<ICasesPluginConfig> {
|
||||||
public static pluginName = "cases";
|
public static pluginName = "cases";
|
||||||
|
|
||||||
protected actions: GuildActions;
|
|
||||||
protected cases: GuildCases;
|
protected cases: GuildCases;
|
||||||
protected archives: GuildArchives;
|
protected archives: GuildArchives;
|
||||||
|
|
||||||
|
@ -31,35 +54,13 @@ export class CasesPlugin extends ZeppelinPlugin<ICasesPluginConfig> {
|
||||||
}
|
}
|
||||||
|
|
||||||
onLoad() {
|
onLoad() {
|
||||||
this.actions = GuildActions.getInstance(this.guildId);
|
|
||||||
this.cases = GuildCases.getInstance(this.guildId);
|
this.cases = GuildCases.getInstance(this.guildId);
|
||||||
this.archives = GuildArchives.getInstance(this.guildId);
|
this.archives = GuildArchives.getInstance(this.guildId);
|
||||||
|
|
||||||
this.actions.register("createCase", args => {
|
// this.actions.register("postCase", async args => {
|
||||||
return this.createCase(args);
|
// const embed = await this.getCaseEmbed(args.caseId);
|
||||||
});
|
// return (args.channel as TextableChannel).createMessage(embed);
|
||||||
|
// });
|
||||||
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");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected resolveCaseId(caseOrCaseId: Case | number): number {
|
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
|
* 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> {
|
public async createCase(args: CaseArgs): Promise<Case> {
|
||||||
const user = this.bot.users.get(opts.userId);
|
const user = await this.resolveUser(args.userId);
|
||||||
const userName = user ? `${user.username}#${user.discriminator}` : "Unknown#0000";
|
const userName = `${user.username}#${user.discriminator}`;
|
||||||
|
|
||||||
const mod = this.bot.users.get(opts.modId);
|
const mod = await this.resolveUser(args.modId);
|
||||||
const modName = mod ? `${mod.username}#${mod.discriminator}` : "Unknown#0000";
|
const modName = `${mod.username}#${mod.discriminator}`;
|
||||||
|
|
||||||
let ppName = null;
|
let ppName = null;
|
||||||
if (opts.ppId) {
|
if (args.ppId) {
|
||||||
const pp = this.bot.users.get(opts.ppId);
|
const pp = await this.resolveUser(args.ppId);
|
||||||
ppName = pp ? `${pp.username}#${pp.discriminator}` : "Unknown#0000";
|
ppName = `${pp.username}#${pp.discriminator}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const createdCase = await this.cases.create({
|
const createdCase = await this.cases.create({
|
||||||
type: opts.type,
|
type: args.type,
|
||||||
user_id: opts.userId,
|
user_id: args.userId,
|
||||||
user_name: userName,
|
user_name: userName,
|
||||||
mod_id: opts.modId,
|
mod_id: args.modId,
|
||||||
mod_name: modName,
|
mod_name: modName,
|
||||||
audit_log_id: opts.auditLogId,
|
audit_log_id: args.auditLogId,
|
||||||
pp_id: opts.ppId,
|
pp_id: args.ppId,
|
||||||
pp_name: ppName,
|
pp_name: ppName,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (opts.reason || (opts.noteDetails && opts.noteDetails.length)) {
|
if (args.reason || (args.noteDetails && args.noteDetails.length)) {
|
||||||
await this.createCaseNote(createdCase, opts.modId, opts.reason || "", opts.automatic, false, opts.noteDetails);
|
await this.createCaseNote({
|
||||||
|
caseId: createdCase.id,
|
||||||
|
modId: args.modId,
|
||||||
|
body: args.reason || "",
|
||||||
|
automatic: args.automatic,
|
||||||
|
postInCaseLogOverride: false,
|
||||||
|
noteDetails: args.noteDetails,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opts.extraNotes) {
|
if (args.extraNotes) {
|
||||||
for (const extraNote of opts.extraNotes) {
|
for (const extraNote of args.extraNotes) {
|
||||||
await this.createCaseNote(createdCase, opts.modId, extraNote, opts.automatic, false);
|
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 (
|
if (
|
||||||
config.case_log_channel &&
|
config.case_log_channel &&
|
||||||
(!opts.automatic || config.log_automatic_actions) &&
|
(!args.automatic || config.log_automatic_actions) &&
|
||||||
opts.postInCaseLogOverride !== false
|
args.postInCaseLogOverride !== false
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
await this.postCaseToCaseLogChannel(createdCase);
|
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
|
* Adds a case note to an existing case and, depending on config, posts the updated case in the case log channel
|
||||||
*/
|
*/
|
||||||
public async createCaseNote(
|
public async createCaseNote(args: CaseNoteArgs): Promise<void> {
|
||||||
caseOrCaseId: Case | number,
|
const theCase = await this.cases.find(this.resolveCaseId(args.caseId));
|
||||||
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));
|
|
||||||
if (!theCase) {
|
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
|
// Add note details to the beginning of the note
|
||||||
if (noteDetails && noteDetails.length) {
|
if (args.noteDetails && args.noteDetails.length) {
|
||||||
body = noteDetails.map(d => `__[${d}]__`).join(" ") + " " + body;
|
body = args.noteDetails.map(d => `__[${d}]__`).join(" ") + " " + body;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.cases.createNote(theCase.id, {
|
await this.cases.createNote(theCase.id, {
|
||||||
mod_id: modId,
|
mod_id: mod.id,
|
||||||
mod_name: modName,
|
mod_name: modName,
|
||||||
body: body || "",
|
body: body || "",
|
||||||
});
|
});
|
||||||
|
@ -152,7 +160,7 @@ export class CasesPlugin extends ZeppelinPlugin<ICasesPluginConfig> {
|
||||||
if (theCase.mod_id == null) {
|
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
|
// 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, {
|
await this.cases.update(theCase.id, {
|
||||||
mod_id: modId,
|
mod_id: mod.id,
|
||||||
mod_name: modName,
|
mod_name: modName,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -163,7 +171,7 @@ export class CasesPlugin extends ZeppelinPlugin<ICasesPluginConfig> {
|
||||||
this.archives.makePermanent(archiveId);
|
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 {
|
try {
|
||||||
await this.postCaseToCaseLogChannel(theCase.id);
|
await this.postCaseToCaseLogChannel(theCase.id);
|
||||||
} catch (e) {} // tslint:disable-line
|
} catch (e) {} // tslint:disable-line
|
||||||
|
|
|
@ -5,7 +5,6 @@ import { GuildCases } from "../data/GuildCases";
|
||||||
import {
|
import {
|
||||||
asSingleLine,
|
asSingleLine,
|
||||||
createChunkedMessage,
|
createChunkedMessage,
|
||||||
createUnknownUser,
|
|
||||||
errorMessage,
|
errorMessage,
|
||||||
findRelevantAuditLogEntry,
|
findRelevantAuditLogEntry,
|
||||||
INotifyUserResult,
|
INotifyUserResult,
|
||||||
|
@ -22,9 +21,10 @@ import { CaseTypes } from "../data/CaseTypes";
|
||||||
import { GuildLogs } from "../data/GuildLogs";
|
import { GuildLogs } from "../data/GuildLogs";
|
||||||
import { LogType } from "../data/LogType";
|
import { LogType } from "../data/LogType";
|
||||||
import { ZeppelinPlugin } from "./ZeppelinPlugin";
|
import { ZeppelinPlugin } from "./ZeppelinPlugin";
|
||||||
import { GuildActions, MuteActionResult } from "../data/GuildActions";
|
|
||||||
import { Case } from "../data/entities/Case";
|
import { Case } from "../data/entities/Case";
|
||||||
import { renderTemplate } from "../templateFormatter";
|
import { renderTemplate } from "../templateFormatter";
|
||||||
|
import { CasesPlugin } from "./Cases";
|
||||||
|
import { MuteResult, MutesPlugin } from "./Mutes";
|
||||||
|
|
||||||
enum IgnoredEventType {
|
enum IgnoredEventType {
|
||||||
Ban = 1,
|
Ban = 1,
|
||||||
|
@ -65,8 +65,8 @@ interface IModActionsPluginConfig {
|
||||||
|
|
||||||
export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
||||||
public static pluginName = "mod_actions";
|
public static pluginName = "mod_actions";
|
||||||
|
public static dependencies = ["cases", "mutes"];
|
||||||
|
|
||||||
protected actions: GuildActions;
|
|
||||||
protected mutes: GuildMutes;
|
protected mutes: GuildMutes;
|
||||||
protected cases: GuildCases;
|
protected cases: GuildCases;
|
||||||
protected serverLogs: GuildLogs;
|
protected serverLogs: GuildLogs;
|
||||||
|
@ -74,7 +74,6 @@ export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
||||||
protected ignoredEvents: IIgnoredEvent[];
|
protected ignoredEvents: IIgnoredEvent[];
|
||||||
|
|
||||||
async onLoad() {
|
async onLoad() {
|
||||||
this.actions = GuildActions.getInstance(this.guildId);
|
|
||||||
this.mutes = GuildMutes.getInstance(this.guildId);
|
this.mutes = GuildMutes.getInstance(this.guildId);
|
||||||
this.cases = GuildCases.getInstance(this.guildId);
|
this.cases = GuildCases.getInstance(this.guildId);
|
||||||
this.serverLogs = new GuildLogs(this.guildId);
|
this.serverLogs = new GuildLogs(this.guildId);
|
||||||
|
@ -156,62 +155,6 @@ export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
||||||
return ((reason || "") + " " + attachmentUrls.join(" ")).trim();
|
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> {
|
async isBanned(userId): Promise<boolean> {
|
||||||
const bans = (await this.guild.getBans()) as any;
|
const bans = (await this.guild.getBans()) as any;
|
||||||
return bans.some(b => b.user.id === userId);
|
return bans.some(b => b.user.id === userId);
|
||||||
|
@ -234,11 +177,12 @@ export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
||||||
user.id,
|
user.id,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const casesPlugin = this.getPlugin<CasesPlugin>("cases");
|
||||||
if (relevantAuditLogEntry) {
|
if (relevantAuditLogEntry) {
|
||||||
const modId = relevantAuditLogEntry.user.id;
|
const modId = relevantAuditLogEntry.user.id;
|
||||||
const auditLogId = relevantAuditLogEntry.id;
|
const auditLogId = relevantAuditLogEntry.id;
|
||||||
|
|
||||||
this.actions.fire("createCase", {
|
casesPlugin.createCase({
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
modId,
|
modId,
|
||||||
type: CaseTypes.Ban,
|
type: CaseTypes.Ban,
|
||||||
|
@ -247,8 +191,9 @@ export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
||||||
automatic: true,
|
automatic: true,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.actions.fire("createCase", {
|
casesPlugin.createCase({
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
|
modId: null,
|
||||||
type: CaseTypes.Ban,
|
type: CaseTypes.Ban,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -271,11 +216,12 @@ export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
||||||
user.id,
|
user.id,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const casesPlugin = this.getPlugin<CasesPlugin>("cases");
|
||||||
if (relevantAuditLogEntry) {
|
if (relevantAuditLogEntry) {
|
||||||
const modId = relevantAuditLogEntry.user.id;
|
const modId = relevantAuditLogEntry.user.id;
|
||||||
const auditLogId = relevantAuditLogEntry.id;
|
const auditLogId = relevantAuditLogEntry.id;
|
||||||
|
|
||||||
this.actions.fire("createCase", {
|
casesPlugin.createCase({
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
modId,
|
modId,
|
||||||
type: CaseTypes.Unban,
|
type: CaseTypes.Unban,
|
||||||
|
@ -283,8 +229,9 @@ export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
||||||
automatic: true,
|
automatic: true,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.actions.fire("createCase", {
|
casesPlugin.createCase({
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
|
modId: null,
|
||||||
type: CaseTypes.Unban,
|
type: CaseTypes.Unban,
|
||||||
automatic: true,
|
automatic: true,
|
||||||
});
|
});
|
||||||
|
@ -337,7 +284,8 @@ export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
||||||
}`,
|
}`,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
this.actions.fire("createCase", {
|
const casesPlugin = this.getPlugin<CasesPlugin>("cases");
|
||||||
|
casesPlugin.createCase({
|
||||||
userId: member.id,
|
userId: member.id,
|
||||||
modId: kickAuditLogEntry.user.id,
|
modId: kickAuditLogEntry.user.id,
|
||||||
type: CaseTypes.Kick,
|
type: CaseTypes.Kick,
|
||||||
|
@ -374,10 +322,11 @@ export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.actions.fire("createCaseNote", {
|
const casesPlugin = this.getPlugin<CasesPlugin>("cases");
|
||||||
|
await casesPlugin.createCaseNote({
|
||||||
caseId: theCase.id,
|
caseId: theCase.id,
|
||||||
modId: msg.author.id,
|
modId: msg.author.id,
|
||||||
note: args.note,
|
body: args.note,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.serverLogs.log(LogType.CASE_UPDATE, {
|
this.serverLogs.log(LogType.CASE_UPDATE, {
|
||||||
|
@ -399,7 +348,8 @@ export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
||||||
const userName = `${user.username}#${user.discriminator}`;
|
const userName = `${user.username}#${user.discriminator}`;
|
||||||
const reason = this.formatReasonWithAttachments(args.note, msg.attachments);
|
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,
|
userId: user.id,
|
||||||
modId: msg.author.id,
|
modId: msg.author.id,
|
||||||
type: CaseTypes.Note,
|
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,
|
userId: memberToWarn.id,
|
||||||
modId: mod.id,
|
modId: mod.id,
|
||||||
type: CaseTypes.Warn,
|
type: CaseTypes.Warn,
|
||||||
|
@ -513,17 +464,13 @@ export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
||||||
const timeUntilUnmute = args.time && humanizeDuration(args.time);
|
const timeUntilUnmute = args.time && humanizeDuration(args.time);
|
||||||
const reason = this.formatReasonWithAttachments(args.reason, msg.attachments);
|
const reason = this.formatReasonWithAttachments(args.reason, msg.attachments);
|
||||||
|
|
||||||
let muteResult: MuteActionResult;
|
let muteResult: MuteResult;
|
||||||
|
const mutesPlugin = this.getPlugin<MutesPlugin>("mutes");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
muteResult = await this.actions.fire("mute", {
|
muteResult = await mutesPlugin.muteUser(user.id, args.time, reason, {
|
||||||
userId: user.id,
|
modId: mod.id,
|
||||||
muteTime: args.time,
|
ppId: pp && pp.id,
|
||||||
reason,
|
|
||||||
caseDetails: {
|
|
||||||
modId: mod.id,
|
|
||||||
ppId: pp && pp.id,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error(`Failed to mute user ${user.id}: ${e.stack}`);
|
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 reason = this.formatReasonWithAttachments(args.reason, msg.attachments);
|
||||||
|
|
||||||
const result = await this.actions.fire("unmute", {
|
const mutesPlugin = this.getPlugin<MutesPlugin>("mutes");
|
||||||
userId: user.id,
|
const result = await mutesPlugin.unmuteUser(user.id, args.time, {
|
||||||
unmuteTime: args.time,
|
modId: mod.id,
|
||||||
caseDetails: {
|
ppId: pp && pp.id,
|
||||||
modId: mod.id,
|
reason,
|
||||||
ppId: pp && pp.id,
|
|
||||||
reason,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Confirm the action to the moderator
|
// Confirm the action to the moderator
|
||||||
|
@ -810,7 +754,8 @@ export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
||||||
memberToKick.kick(reason);
|
memberToKick.kick(reason);
|
||||||
|
|
||||||
// Create a case for this action
|
// 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,
|
userId: memberToKick.id,
|
||||||
modId: mod.id,
|
modId: mod.id,
|
||||||
type: CaseTypes.Kick,
|
type: CaseTypes.Kick,
|
||||||
|
@ -896,7 +841,8 @@ export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
||||||
memberToBan.ban(1, reason);
|
memberToBan.ban(1, reason);
|
||||||
|
|
||||||
// Create a case for this action
|
// 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,
|
userId: memberToBan.id,
|
||||||
modId: mod.id,
|
modId: mod.id,
|
||||||
type: CaseTypes.Ban,
|
type: CaseTypes.Ban,
|
||||||
|
@ -970,7 +916,8 @@ export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
||||||
await this.guild.unbanMember(memberToSoftban.id);
|
await this.guild.unbanMember(memberToSoftban.id);
|
||||||
|
|
||||||
// Create a case for this action
|
// 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,
|
userId: memberToSoftban.id,
|
||||||
modId: mod.id,
|
modId: mod.id,
|
||||||
type: CaseTypes.Softban,
|
type: CaseTypes.Softban,
|
||||||
|
@ -1026,7 +973,8 @@ export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
||||||
const reason = this.formatReasonWithAttachments(args.reason, msg.attachments);
|
const reason = this.formatReasonWithAttachments(args.reason, msg.attachments);
|
||||||
|
|
||||||
// Create a case
|
// Create a case
|
||||||
const createdCase = await this.actions.fire("createCase", {
|
const casesPlugin = this.getPlugin<CasesPlugin>("cases");
|
||||||
|
const createdCase = await casesPlugin.createCase({
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
modId: mod.id,
|
modId: mod.id,
|
||||||
type: CaseTypes.Unban,
|
type: CaseTypes.Unban,
|
||||||
|
@ -1083,7 +1031,8 @@ export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a case
|
// Create a case
|
||||||
const createdCase = await this.actions.fire("createCase", {
|
const casesPlugin = this.getPlugin<CasesPlugin>("cases");
|
||||||
|
const createdCase = await casesPlugin.createCase({
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
modId: mod.id,
|
modId: mod.id,
|
||||||
type: CaseTypes.Ban,
|
type: CaseTypes.Ban,
|
||||||
|
@ -1142,16 +1091,17 @@ export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
||||||
|
|
||||||
// Ban each user and count failed bans (if any)
|
// Ban each user and count failed bans (if any)
|
||||||
const failedBans = [];
|
const failedBans = [];
|
||||||
|
const casesPlugin = this.getPlugin<CasesPlugin>("cases");
|
||||||
for (const userId of args.userIds) {
|
for (const userId of args.userIds) {
|
||||||
try {
|
try {
|
||||||
await this.guild.banMember(userId);
|
await this.guild.banMember(userId);
|
||||||
|
|
||||||
await this.actions.fire("createCase", {
|
await casesPlugin.createCase({
|
||||||
userId,
|
userId,
|
||||||
modId: msg.author.id,
|
modId: msg.author.id,
|
||||||
type: CaseTypes.Ban,
|
type: CaseTypes.Ban,
|
||||||
reason: `Mass ban: ${banReason}`,
|
reason: `Mass ban: ${banReason}`,
|
||||||
postInCaseLog: false,
|
postInCaseLogOverride: false,
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
failedBans.push(userId);
|
failedBans.push(userId);
|
||||||
|
@ -1218,7 +1168,8 @@ export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
||||||
const reason = this.formatReasonWithAttachments(args.reason, msg.attachments);
|
const reason = this.formatReasonWithAttachments(args.reason, msg.attachments);
|
||||||
|
|
||||||
// Create the case
|
// 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,
|
userId: user.id,
|
||||||
modId: mod.id,
|
modId: mod.id,
|
||||||
type: CaseTypes[type],
|
type: CaseTypes[type],
|
||||||
|
@ -1259,10 +1210,9 @@ export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.actions.fire("postCase", {
|
const casesPlugin = this.getPlugin<CasesPlugin>("cases");
|
||||||
caseId: theCase.id,
|
const embed = await casesPlugin.getCaseEmbed(theCase.id);
|
||||||
channel: msg.channel,
|
msg.channel.createMessage(embed);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@d.command("cases", "<user:string> [opts:string$]", {
|
@d.command("cases", "<user:string> [opts:string$]", {
|
||||||
|
@ -1303,11 +1253,10 @@ export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expanded view (= individual case embeds)
|
// Expanded view (= individual case embeds)
|
||||||
|
const casesPlugin = this.getPlugin<CasesPlugin>("cases");
|
||||||
for (const theCase of casesToDisplay) {
|
for (const theCase of casesToDisplay) {
|
||||||
await this.actions.fire("postCase", {
|
const embed = await casesPlugin.getCaseEmbed(theCase.id);
|
||||||
caseId: theCase.id,
|
msg.channel.createMessage(embed);
|
||||||
channel: msg.channel,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Compact view (= regular message with a preview of each case)
|
// Compact view (= regular message with a preview of each case)
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import { Member, Message, User } from "eris";
|
import { Member, Message, User } from "eris";
|
||||||
import { GuildCases, ICaseDetails } from "../data/GuildCases";
|
import { GuildCases } from "../data/GuildCases";
|
||||||
import moment from "moment-timezone";
|
import moment from "moment-timezone";
|
||||||
import { ZeppelinPlugin } from "./ZeppelinPlugin";
|
import { ZeppelinPlugin } from "./ZeppelinPlugin";
|
||||||
import { GuildActions } from "../data/GuildActions";
|
|
||||||
import { GuildMutes } from "../data/GuildMutes";
|
import { GuildMutes } from "../data/GuildMutes";
|
||||||
import {
|
import {
|
||||||
chunkMessageLines,
|
chunkMessageLines,
|
||||||
|
@ -14,7 +13,6 @@ import {
|
||||||
stripObjectToScalars,
|
stripObjectToScalars,
|
||||||
successMessage,
|
successMessage,
|
||||||
ucfirst,
|
ucfirst,
|
||||||
unknownUser,
|
|
||||||
} from "../utils";
|
} from "../utils";
|
||||||
import humanizeDuration from "humanize-duration";
|
import humanizeDuration from "humanize-duration";
|
||||||
import { LogType } from "../data/LogType";
|
import { LogType } from "../data/LogType";
|
||||||
|
@ -23,6 +21,8 @@ import { decorators as d, IPluginOptions, logger } from "knub";
|
||||||
import { Mute } from "../data/entities/Mute";
|
import { Mute } from "../data/entities/Mute";
|
||||||
import { renderTemplate } from "../templateFormatter";
|
import { renderTemplate } from "../templateFormatter";
|
||||||
import { CaseTypes } from "../data/CaseTypes";
|
import { CaseTypes } from "../data/CaseTypes";
|
||||||
|
import { CaseArgs, CasesPlugin } from "./Cases";
|
||||||
|
import { Case } from "../data/entities/Case";
|
||||||
|
|
||||||
interface IMuteWithDetails extends Mute {
|
interface IMuteWithDetails extends Mute {
|
||||||
member?: Member;
|
member?: Member;
|
||||||
|
@ -43,10 +43,19 @@ interface IMutesPluginConfig {
|
||||||
can_cleanup: boolean;
|
can_cleanup: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type MuteResult = {
|
||||||
|
case: Case;
|
||||||
|
notifyResult: INotifyUserResult;
|
||||||
|
updatedExistingMute: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type UnmuteResult = {
|
||||||
|
case: Case;
|
||||||
|
};
|
||||||
|
|
||||||
export class MutesPlugin extends ZeppelinPlugin<IMutesPluginConfig> {
|
export class MutesPlugin extends ZeppelinPlugin<IMutesPluginConfig> {
|
||||||
public static pluginName = "mutes";
|
public static pluginName = "mutes";
|
||||||
|
|
||||||
protected actions: GuildActions;
|
|
||||||
protected mutes: GuildMutes;
|
protected mutes: GuildMutes;
|
||||||
protected cases: GuildCases;
|
protected cases: GuildCases;
|
||||||
protected serverLogs: GuildLogs;
|
protected serverLogs: GuildLogs;
|
||||||
|
@ -84,62 +93,38 @@ export class MutesPlugin extends ZeppelinPlugin<IMutesPluginConfig> {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
onLoad() {
|
protected onLoad() {
|
||||||
this.actions = GuildActions.getInstance(this.guildId);
|
|
||||||
this.mutes = GuildMutes.getInstance(this.guildId);
|
this.mutes = GuildMutes.getInstance(this.guildId);
|
||||||
this.cases = GuildCases.getInstance(this.guildId);
|
this.cases = GuildCases.getInstance(this.guildId);
|
||||||
this.serverLogs = new GuildLogs(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
|
// Check for expired mutes every 5s
|
||||||
this.clearExpiredMutes();
|
this.clearExpiredMutes();
|
||||||
this.muteClearIntervalId = setInterval(() => this.clearExpiredMutes(), 5000);
|
this.muteClearIntervalId = setInterval(() => this.clearExpiredMutes(), 5000);
|
||||||
}
|
}
|
||||||
|
|
||||||
onUnload() {
|
protected onUnload() {
|
||||||
this.actions.unregister("mute");
|
|
||||||
this.actions.unregister("unmute");
|
|
||||||
|
|
||||||
clearInterval(this.muteClearIntervalId);
|
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(
|
public async muteUser(
|
||||||
userId: string,
|
userId: string,
|
||||||
muteTime: number = null,
|
muteTime: number = null,
|
||||||
reason: string = null,
|
reason: string = null,
|
||||||
caseDetails: ICaseDetails = {},
|
caseArgs: Partial<CaseArgs> = {},
|
||||||
) {
|
): Promise<MuteResult> {
|
||||||
const muteRole = this.getConfig().mute_role;
|
const muteRole = this.getConfig().mute_role;
|
||||||
if (!muteRole) return;
|
if (!muteRole) return;
|
||||||
|
|
||||||
const timeUntilUnmute = muteTime && humanizeDuration(muteTime);
|
const timeUntilUnmute = muteTime && humanizeDuration(muteTime);
|
||||||
|
|
||||||
// No mod specified -> mark Zeppelin as the mod
|
// No mod specified -> mark Zeppelin as the mod
|
||||||
if (!caseDetails.modId) {
|
if (!caseArgs.modId) {
|
||||||
caseDetails.modId = this.bot.user.id;
|
caseArgs.modId = this.bot.user.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = this.bot.users.get(userId) || { ...unknownUser, id: userId };
|
const user = await this.resolveUser(userId);
|
||||||
const member = await this.resolveMember(userId);
|
const member = await this.getMember(user.id);
|
||||||
|
|
||||||
if (member) {
|
if (member) {
|
||||||
// Apply mute role if it's missing
|
// Apply mute role if it's missing
|
||||||
|
@ -193,17 +178,29 @@ export class MutesPlugin extends ZeppelinPlugin<IMutesPluginConfig> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create/update a case
|
// Create/update a case
|
||||||
|
const casesPlugin = this.getPlugin<CasesPlugin>("cases");
|
||||||
let theCase;
|
let theCase;
|
||||||
|
|
||||||
if (existingMute && existingMute.case_id) {
|
if (existingMute && existingMute.case_id) {
|
||||||
// Update old case
|
// 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);
|
theCase = await this.cases.find(existingMute.case_id);
|
||||||
const noteDetails = [`Mute updated to ${muteTime ? timeUntilUnmute : "indefinite"}`];
|
const noteDetails = [`Mute updated to ${muteTime ? timeUntilUnmute : "indefinite"}`];
|
||||||
await this.actions.fire("createCaseNote", {
|
const reasons = [reason, ...(caseArgs.extraNotes || [])];
|
||||||
caseId: existingMute.case_id,
|
for (const noteReason of reasons) {
|
||||||
modId: caseDetails.modId,
|
await casesPlugin.createCaseNote({
|
||||||
note: reason,
|
caseId: existingMute.case_id,
|
||||||
noteDetails,
|
modId: caseArgs.modId,
|
||||||
});
|
body: noteReason,
|
||||||
|
noteDetails,
|
||||||
|
postInCaseLogOverride: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (caseArgs.postInCaseLogOverride !== false) {
|
||||||
|
casesPlugin.postCaseToCaseLogChannel(existingMute.case_id);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Create new case
|
// Create new case
|
||||||
const noteDetails = [`Muted ${muteTime ? `for ${timeUntilUnmute}` : "indefinitely"}`];
|
const noteDetails = [`Muted ${muteTime ? `for ${timeUntilUnmute}` : "indefinitely"}`];
|
||||||
|
@ -211,20 +208,19 @@ export class MutesPlugin extends ZeppelinPlugin<IMutesPluginConfig> {
|
||||||
noteDetails.push(ucfirst(notifyResult.text));
|
noteDetails.push(ucfirst(notifyResult.text));
|
||||||
}
|
}
|
||||||
|
|
||||||
theCase = await this.actions.fire("createCase", {
|
theCase = await casesPlugin.createCase({
|
||||||
|
...caseArgs,
|
||||||
userId,
|
userId,
|
||||||
modId: caseDetails.modId,
|
modId: caseArgs.modId,
|
||||||
type: CaseTypes.Mute,
|
type: CaseTypes.Mute,
|
||||||
reason,
|
reason,
|
||||||
ppId: caseDetails.ppId,
|
|
||||||
noteDetails,
|
noteDetails,
|
||||||
extraNotes: caseDetails.extraNotes,
|
|
||||||
});
|
});
|
||||||
await this.mutes.setCaseId(user.id, theCase.id);
|
await this.mutes.setCaseId(user.id, theCase.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log the action
|
// Log the action
|
||||||
const mod = this.bot.users.get(caseDetails.modId) || { ...unknownUser, id: caseDetails.modId };
|
const mod = await this.resolveUser(caseArgs.modId);
|
||||||
if (muteTime) {
|
if (muteTime) {
|
||||||
this.serverLogs.log(LogType.MEMBER_TIMED_MUTE, {
|
this.serverLogs.log(LogType.MEMBER_TIMED_MUTE, {
|
||||||
mod: stripObjectToScalars(mod),
|
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);
|
const existingMute = await this.mutes.findExistingMuteForUserId(userId);
|
||||||
if (!existingMute) return;
|
if (!existingMute) return;
|
||||||
|
|
||||||
const user = this.bot.users.get(userId) || { ...unknownUser, id: userId };
|
const user = await this.resolveUser(userId);
|
||||||
const member = await this.resolveMember(userId);
|
const member = await this.getMember(userId);
|
||||||
|
|
||||||
if (unmuteTime) {
|
if (unmuteTime) {
|
||||||
// Schedule timed unmute (= just set the mute's duration)
|
// Schedule timed unmute (= just set the mute's duration)
|
||||||
|
@ -277,17 +277,17 @@ export class MutesPlugin extends ZeppelinPlugin<IMutesPluginConfig> {
|
||||||
noteDetails.push(`Unmuted immediately`);
|
noteDetails.push(`Unmuted immediately`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const createdCase = await this.actions.fire("createCase", {
|
const casesPlugin = this.getPlugin<CasesPlugin>("cases");
|
||||||
|
const createdCase = await casesPlugin.createCase({
|
||||||
|
...caseArgs,
|
||||||
userId,
|
userId,
|
||||||
modId: caseDetails.modId,
|
modId: caseArgs.modId,
|
||||||
type: CaseTypes.Unmute,
|
type: CaseTypes.Unmute,
|
||||||
reason: caseDetails.reason,
|
|
||||||
ppId: caseDetails.ppId,
|
|
||||||
noteDetails,
|
noteDetails,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Log the action
|
// Log the action
|
||||||
const mod = this.bot.users.get(caseDetails.modId);
|
const mod = this.bot.users.get(caseArgs.modId);
|
||||||
if (unmuteTime) {
|
if (unmuteTime) {
|
||||||
this.serverLogs.log(LogType.MEMBER_TIMED_UNMUTE, {
|
this.serverLogs.log(LogType.MEMBER_TIMED_UNMUTE, {
|
||||||
mod: stripObjectToScalars(mod),
|
mod: stripObjectToScalars(mod),
|
||||||
|
@ -310,7 +310,7 @@ export class MutesPlugin extends ZeppelinPlugin<IMutesPluginConfig> {
|
||||||
options: [{ name: "age", type: "delay" }, { name: "left", type: "boolean" }],
|
options: [{ name: "age", type: "delay" }, { name: "left", type: "boolean" }],
|
||||||
})
|
})
|
||||||
@d.permission("can_view_list")
|
@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 = [];
|
const lines = [];
|
||||||
|
|
||||||
// Active, logged mutes
|
// Active, logged mutes
|
||||||
|
@ -449,7 +449,7 @@ export class MutesPlugin extends ZeppelinPlugin<IMutesPluginConfig> {
|
||||||
* Reapply active mutes on join
|
* Reapply active mutes on join
|
||||||
*/
|
*/
|
||||||
@d.event("guildMemberAdd")
|
@d.event("guildMemberAdd")
|
||||||
async onGuildMemberAdd(_, member: Member) {
|
protected async onGuildMemberAdd(_, member: Member) {
|
||||||
const mute = await this.mutes.findExistingMuteForUserId(member.id);
|
const mute = await this.mutes.findExistingMuteForUserId(member.id);
|
||||||
if (mute) {
|
if (mute) {
|
||||||
const muteRole = this.getConfig().mute_role;
|
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
|
* Clear active mute from the member if the member is banned
|
||||||
*/
|
*/
|
||||||
@d.event("guildBanAdd")
|
@d.event("guildBanAdd")
|
||||||
async onGuildBanAdd(_, user: User) {
|
protected async onGuildBanAdd(_, user: User) {
|
||||||
const mute = await this.mutes.findExistingMuteForUserId(user.id);
|
const mute = await this.mutes.findExistingMuteForUserId(user.id);
|
||||||
if (mute) {
|
if (mute) {
|
||||||
this.mutes.clear(user.id);
|
this.mutes.clear(user.id);
|
||||||
|
@ -477,7 +477,7 @@ export class MutesPlugin extends ZeppelinPlugin<IMutesPluginConfig> {
|
||||||
*/
|
*/
|
||||||
@d.command("clear_banned_mutes")
|
@d.command("clear_banned_mutes")
|
||||||
@d.permission("can_cleanup")
|
@d.permission("can_cleanup")
|
||||||
async clearBannedMutesCmd(msg: Message) {
|
protected async clearBannedMutesCmd(msg: Message) {
|
||||||
await msg.channel.createMessage("Clearing mutes from banned users...");
|
await msg.channel.createMessage("Clearing mutes from banned users...");
|
||||||
|
|
||||||
const activeMutes = await this.mutes.getActiveMutes();
|
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
|
* Clear active mute from the member if the mute role is removed
|
||||||
*/
|
*/
|
||||||
@d.event("guildMemberUpdate")
|
@d.event("guildMemberUpdate")
|
||||||
async onGuildMemberUpdate(_, member: Member) {
|
protected async onGuildMemberUpdate(_, member: Member) {
|
||||||
const muteRole = this.getConfig().mute_role;
|
const muteRole = this.getConfig().mute_role;
|
||||||
if (!muteRole) return;
|
if (!muteRole) return;
|
||||||
|
|
||||||
|
@ -522,7 +522,7 @@ export class MutesPlugin extends ZeppelinPlugin<IMutesPluginConfig> {
|
||||||
*/
|
*/
|
||||||
@d.command("clear_mutes_without_role")
|
@d.command("clear_mutes_without_role")
|
||||||
@d.permission("can_cleanup")
|
@d.permission("can_cleanup")
|
||||||
async clearMutesWithoutRoleCmd(msg: Message) {
|
protected async clearMutesWithoutRoleCmd(msg: Message) {
|
||||||
const activeMutes = await this.mutes.getActiveMutes();
|
const activeMutes = await this.mutes.getActiveMutes();
|
||||||
const muteRole = this.getConfig().mute_role;
|
const muteRole = this.getConfig().mute_role;
|
||||||
if (!muteRole) return;
|
if (!muteRole) return;
|
||||||
|
@ -545,7 +545,7 @@ export class MutesPlugin extends ZeppelinPlugin<IMutesPluginConfig> {
|
||||||
|
|
||||||
@d.command("clear_mute", "<userId:string>")
|
@d.command("clear_mute", "<userId:string>")
|
||||||
@d.permission("can_cleanup")
|
@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);
|
const mute = await this.mutes.findExistingMuteForUserId(args.userId);
|
||||||
if (!mute) {
|
if (!mute) {
|
||||||
msg.channel.createMessage(errorMessage("No active mutes found for that user id"));
|
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";
|
import { GuildSavedMessages } from "../data/GuildSavedMessages";
|
||||||
|
|
||||||
const NATIVE_SLOWMODE_LIMIT = 6 * 60 * 60; // 6 hours
|
const NATIVE_SLOWMODE_LIMIT = 6 * 60 * 60; // 6 hours
|
||||||
|
const MAX_SLOWMODE = 60 * 60 * 24 * 365 * 100; // 100 years
|
||||||
|
|
||||||
interface ISlowmodePluginConfig {
|
interface ISlowmodePluginConfig {
|
||||||
use_native_slowmode: boolean;
|
use_native_slowmode: boolean;
|
||||||
|
@ -238,6 +239,15 @@ export class SlowmodePlugin extends ZeppelinPlugin<ISlowmodePluginConfig> {
|
||||||
const seconds = Math.ceil(convertDelayStringToMS(args.time, "s") / 1000);
|
const seconds = Math.ceil(convertDelayStringToMS(args.time, "s") / 1000);
|
||||||
const useNativeSlowmode = this.getConfigForChannel(channel).use_native_slowmode && seconds <= NATIVE_SLOWMODE_LIMIT;
|
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) {
|
if (useNativeSlowmode) {
|
||||||
// Native slowmode
|
// Native slowmode
|
||||||
|
|
||||||
|
|
|
@ -17,10 +17,10 @@ import { GuildArchives } from "../data/GuildArchives";
|
||||||
import moment from "moment-timezone";
|
import moment from "moment-timezone";
|
||||||
import { SavedMessage } from "../data/entities/SavedMessage";
|
import { SavedMessage } from "../data/entities/SavedMessage";
|
||||||
import { GuildSavedMessages } from "../data/GuildSavedMessages";
|
import { GuildSavedMessages } from "../data/GuildSavedMessages";
|
||||||
import { GuildActions, MuteActionResult } from "../data/GuildActions";
|
|
||||||
import { Case } from "../data/entities/Case";
|
|
||||||
import { GuildMutes } from "../data/GuildMutes";
|
import { GuildMutes } from "../data/GuildMutes";
|
||||||
import { ZeppelinPlugin } from "./ZeppelinPlugin";
|
import { ZeppelinPlugin } from "./ZeppelinPlugin";
|
||||||
|
import { MuteResult, MutesPlugin } from "./Mutes";
|
||||||
|
import { CasesPlugin } from "./Cases";
|
||||||
|
|
||||||
enum RecentActionType {
|
enum RecentActionType {
|
||||||
Message = 1,
|
Message = 1,
|
||||||
|
@ -71,7 +71,6 @@ interface ISpamPluginConfig {
|
||||||
export class SpamPlugin extends ZeppelinPlugin<ISpamPluginConfig> {
|
export class SpamPlugin extends ZeppelinPlugin<ISpamPluginConfig> {
|
||||||
public static pluginName = "spam";
|
public static pluginName = "spam";
|
||||||
|
|
||||||
protected actions: GuildActions;
|
|
||||||
protected logs: GuildLogs;
|
protected logs: GuildLogs;
|
||||||
protected archives: GuildArchives;
|
protected archives: GuildArchives;
|
||||||
protected savedMessages: GuildSavedMessages;
|
protected savedMessages: GuildSavedMessages;
|
||||||
|
@ -128,7 +127,6 @@ export class SpamPlugin extends ZeppelinPlugin<ISpamPluginConfig> {
|
||||||
}
|
}
|
||||||
|
|
||||||
onLoad() {
|
onLoad() {
|
||||||
this.actions = GuildActions.getInstance(this.guildId);
|
|
||||||
this.logs = new GuildLogs(this.guildId);
|
this.logs = new GuildLogs(this.guildId);
|
||||||
this.archives = GuildArchives.getInstance(this.guildId);
|
this.archives = GuildArchives.getInstance(this.guildId);
|
||||||
this.savedMessages = GuildSavedMessages.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);
|
const recentActions = this.getRecentActions(type, savedMessage.user_id, savedMessage.channel_id, since);
|
||||||
|
|
||||||
// Start by muting them, if enabled
|
// Start by muting them, if enabled
|
||||||
let muteResult: MuteActionResult;
|
let muteResult: MuteResult;
|
||||||
if (spamConfig.mute && member) {
|
if (spamConfig.mute && member) {
|
||||||
|
const mutesPlugin = this.getPlugin<MutesPlugin>("mutes");
|
||||||
const muteTime = spamConfig.mute_time
|
const muteTime = spamConfig.mute_time
|
||||||
? convertDelayStringToMS(spamConfig.mute_time.toString())
|
? convertDelayStringToMS(spamConfig.mute_time.toString())
|
||||||
: 120 * 1000;
|
: 120 * 1000;
|
||||||
muteResult = await this.actions.fire("mute", {
|
muteResult = await mutesPlugin.muteUser(member.id, muteTime, "Automatic spam detection", {
|
||||||
userId: member.id,
|
modId: this.bot.user.id,
|
||||||
muteTime,
|
postInCaseLogOverride: false,
|
||||||
reason: "Automatic spam detection",
|
|
||||||
caseDetails: {
|
|
||||||
modId: this.bot.user.id,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -296,18 +291,20 @@ export class SpamPlugin extends ZeppelinPlugin<ISpamPluginConfig> {
|
||||||
const archiveUrl = await this.saveSpamArchives(uniqueMessages);
|
const archiveUrl = await this.saveSpamArchives(uniqueMessages);
|
||||||
|
|
||||||
// Create a case
|
// Create a case
|
||||||
|
const casesPlugin = this.getPlugin<CasesPlugin>("cases");
|
||||||
if (muteResult) {
|
if (muteResult) {
|
||||||
// If the user was muted, the mute already generated a case - in that case, just update the case with extra details
|
// 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(`
|
const updateText = trimLines(`
|
||||||
Details: ${description} (over ${spamConfig.count} in ${spamConfig.interval}s)
|
Details: ${description} (over ${spamConfig.count} in ${spamConfig.interval}s)
|
||||||
${archiveUrl}
|
${archiveUrl}
|
||||||
`);
|
`);
|
||||||
this.actions.fire("createCaseNote", {
|
casesPlugin.createCaseNote({
|
||||||
caseId: muteResult.case.id,
|
caseId: muteResult.case.id,
|
||||||
modId: muteResult.case.mod_id,
|
modId: muteResult.case.mod_id,
|
||||||
note: updateText,
|
body: updateText,
|
||||||
automatic: true,
|
automatic: true,
|
||||||
postInCaseLogOverride: false,
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// If the user was not muted, create a note case of the detected spam instead
|
// 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}
|
${archiveUrl}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
this.actions.fire("createCase", {
|
casesPlugin.createCase({
|
||||||
userId: savedMessage.user_id,
|
userId: savedMessage.user_id,
|
||||||
modId: this.bot.user.id,
|
modId: this.bot.user.id,
|
||||||
type: CaseTypes.Note,
|
type: CaseTypes.Note,
|
||||||
|
@ -362,42 +359,35 @@ export class SpamPlugin extends ZeppelinPlugin<ISpamPluginConfig> {
|
||||||
|
|
||||||
if (recentActionsCount > spamConfig.count) {
|
if (recentActionsCount > spamConfig.count) {
|
||||||
const member = this.guild.members.get(userId);
|
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) {
|
if (spamConfig.mute && member) {
|
||||||
const muteTime = spamConfig.mute_time ? spamConfig.mute_time * 60 * 1000 : 120 * 1000;
|
const mutesPlugin = this.getPlugin<MutesPlugin>("mutes");
|
||||||
this.logs.ignoreLog(LogType.MEMBER_ROLE_ADD, userId);
|
const muteTime = spamConfig.mute_time ? convertDelayStringToMS(spamConfig.mute_time.toString()) : 120 * 1000;
|
||||||
this.actions.fire("mute", { userId: member.id, muteTime, reason: "Automatic spam detection" });
|
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
|
// Clear recent cases
|
||||||
this.clearRecentUserActions(RecentActionType.VoiceChannelMove, userId, actionGroupId);
|
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, {
|
this.logs.log(LogType.OTHER_SPAM_DETECTED, {
|
||||||
member: stripObjectToScalars(member, ["user"]),
|
member: stripObjectToScalars(member, ["user"]),
|
||||||
description,
|
description,
|
||||||
limit: spamConfig.count,
|
limit: spamConfig.count,
|
||||||
interval: spamConfig.interval,
|
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 { IBasePluginConfig, IPluginOptions, Plugin } from "knub";
|
||||||
import { PluginRuntimeError } from "../PluginRuntimeError";
|
import { PluginRuntimeError } from "../PluginRuntimeError";
|
||||||
import Ajv, { ErrorObject } from "ajv";
|
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> {
|
export class ZeppelinPlugin<TConfig extends {} = IBasePluginConfig> extends Plugin<TConfig> {
|
||||||
protected configSchema: any;
|
protected configSchema: any;
|
||||||
protected permissionsSchema: any;
|
protected permissionsSchema: any;
|
||||||
|
|
||||||
|
public static dependencies = [];
|
||||||
|
|
||||||
protected throwPluginRuntimeError(message: string) {
|
protected throwPluginRuntimeError(message: string) {
|
||||||
throw new PluginRuntimeError(message, this.runtimePluginName, this.guildId);
|
throw new PluginRuntimeError(message, this.runtimePluginName, this.guildId);
|
||||||
}
|
}
|
||||||
|
@ -73,4 +76,64 @@ export class ZeppelinPlugin<TConfig extends {} = IBasePluginConfig> extends Plug
|
||||||
public getRegisteredCommands() {
|
public getRegisteredCommands() {
|
||||||
return this.commands.commands;
|
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 = {
|
export const unknownUser: UnknownUser = {
|
||||||
id: "0",
|
id: null,
|
||||||
username: "Unknown",
|
username: "Unknown",
|
||||||
discriminator: "0000",
|
discriminator: "0000",
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Reference in a new issue