2019-03-16 15:44:20 +02:00
|
|
|
import { decorators as d, IPluginOptions, logger, waitForReaction, waitForReply } from "knub";
|
2019-02-23 21:47:55 +02:00
|
|
|
import { Attachment, Constants as ErisConstants, Guild, Member, Message, TextChannel, User } from "eris";
|
2018-07-12 02:58:34 +03:00
|
|
|
import humanizeDuration from "humanize-duration";
|
2018-07-12 02:53:26 +03:00
|
|
|
import { GuildCases } from "../data/GuildCases";
|
2018-07-09 02:51:34 +03:00
|
|
|
import {
|
2019-04-13 17:35:02 +03:00
|
|
|
asSingleLine,
|
2019-01-12 12:20:05 +02:00
|
|
|
createChunkedMessage,
|
2018-07-09 03:00:10 +03:00
|
|
|
errorMessage,
|
2018-07-29 18:46:49 +03:00
|
|
|
findRelevantAuditLogEntry,
|
2019-04-13 17:35:02 +03:00
|
|
|
INotifyUserResult,
|
|
|
|
notifyUser,
|
|
|
|
NotifyUserStatus,
|
2018-07-09 02:51:34 +03:00
|
|
|
stripObjectToScalars,
|
2018-08-02 00:51:25 +03:00
|
|
|
successMessage,
|
2019-02-15 05:07:28 +02:00
|
|
|
trimLines,
|
2019-04-14 17:03:29 +03:00
|
|
|
ucfirst,
|
2019-04-14 13:30:48 +03:00
|
|
|
unknownUser,
|
2018-07-09 02:51:34 +03:00
|
|
|
} from "../utils";
|
2018-07-08 13:57:27 +03:00
|
|
|
import { GuildMutes } from "../data/GuildMutes";
|
2018-10-26 06:41:20 +03:00
|
|
|
import { CaseTypes } from "../data/CaseTypes";
|
2018-07-29 18:46:49 +03:00
|
|
|
import { GuildLogs } from "../data/GuildLogs";
|
2018-07-09 02:51:34 +03:00
|
|
|
import { LogType } from "../data/LogType";
|
2018-11-25 17:04:26 +02:00
|
|
|
import { ZeppelinPlugin } from "./ZeppelinPlugin";
|
2019-04-13 17:35:02 +03:00
|
|
|
import { GuildActions, MuteActionResult } from "../data/GuildActions";
|
2018-11-25 17:04:26 +02:00
|
|
|
import { Case } from "../data/entities/Case";
|
2019-03-16 15:42:55 +02:00
|
|
|
import { renderTemplate } from "../templateFormatter";
|
2018-07-01 03:35:51 +03:00
|
|
|
|
2018-07-31 04:02:45 +03:00
|
|
|
enum IgnoredEventType {
|
|
|
|
Ban = 1,
|
|
|
|
Unban,
|
2019-02-15 05:07:28 +02:00
|
|
|
Kick,
|
2018-07-31 04:02:45 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
interface IIgnoredEvent {
|
|
|
|
type: IgnoredEventType;
|
|
|
|
userId: string;
|
|
|
|
}
|
|
|
|
|
2019-03-04 21:44:04 +02:00
|
|
|
interface IModActionsPluginConfig {
|
|
|
|
dm_on_warn: boolean;
|
|
|
|
dm_on_kick: boolean;
|
|
|
|
dm_on_ban: boolean;
|
|
|
|
message_on_warn: boolean;
|
|
|
|
message_on_kick: boolean;
|
|
|
|
message_on_ban: boolean;
|
|
|
|
message_channel: string;
|
|
|
|
warn_message: string;
|
|
|
|
kick_message: string;
|
|
|
|
ban_message: string;
|
|
|
|
alert_on_rejoin: boolean;
|
|
|
|
alert_channel: string;
|
|
|
|
|
2019-04-13 01:44:18 +03:00
|
|
|
can_note: boolean;
|
|
|
|
can_warn: boolean;
|
|
|
|
can_mute: boolean;
|
|
|
|
can_kick: boolean;
|
|
|
|
can_ban: boolean;
|
|
|
|
can_view: boolean;
|
|
|
|
can_addcase: boolean;
|
|
|
|
can_massban: boolean;
|
|
|
|
can_hidecase: boolean;
|
|
|
|
can_act_as_other: boolean;
|
2019-03-04 21:44:04 +02:00
|
|
|
}
|
|
|
|
|
2019-04-13 01:44:18 +03:00
|
|
|
export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
2019-01-12 12:20:05 +02:00
|
|
|
public static pluginName = "mod_actions";
|
2019-01-03 06:15:28 +02:00
|
|
|
|
2018-11-25 17:04:26 +02:00
|
|
|
protected actions: GuildActions;
|
|
|
|
protected mutes: GuildMutes;
|
2018-07-12 02:53:26 +03:00
|
|
|
protected cases: GuildCases;
|
2018-07-29 18:46:49 +03:00
|
|
|
protected serverLogs: GuildLogs;
|
2018-07-09 02:51:34 +03:00
|
|
|
|
2018-07-31 04:02:45 +03:00
|
|
|
protected ignoredEvents: IIgnoredEvent[];
|
|
|
|
|
2018-07-01 03:35:51 +03:00
|
|
|
async onLoad() {
|
2018-11-25 17:04:26 +02:00
|
|
|
this.actions = GuildActions.getInstance(this.guildId);
|
2018-10-26 06:41:20 +03:00
|
|
|
this.mutes = GuildMutes.getInstance(this.guildId);
|
2018-11-25 17:04:26 +02:00
|
|
|
this.cases = GuildCases.getInstance(this.guildId);
|
2018-07-29 18:46:49 +03:00
|
|
|
this.serverLogs = new GuildLogs(this.guildId);
|
2018-07-08 13:57:27 +03:00
|
|
|
|
2018-07-31 04:02:45 +03:00
|
|
|
this.ignoredEvents = [];
|
2018-07-01 03:35:51 +03:00
|
|
|
}
|
|
|
|
|
2019-04-13 01:44:18 +03:00
|
|
|
getDefaultOptions(): IPluginOptions<IModActionsPluginConfig> {
|
2018-07-01 03:35:51 +03:00
|
|
|
return {
|
|
|
|
config: {
|
|
|
|
dm_on_warn: true,
|
|
|
|
dm_on_kick: false,
|
|
|
|
dm_on_ban: false,
|
|
|
|
message_on_warn: false,
|
|
|
|
message_on_kick: false,
|
|
|
|
message_on_ban: false,
|
|
|
|
message_channel: null,
|
|
|
|
warn_message: "You have received a warning on {guildName}: {reason}",
|
2018-07-12 01:45:26 +03:00
|
|
|
kick_message: "You have been kicked from {guildName}. Reason given: {reason}",
|
|
|
|
ban_message: "You have been banned from {guildName}. Reason given: {reason}",
|
2018-07-01 03:35:51 +03:00
|
|
|
alert_on_rejoin: false,
|
2019-02-15 05:07:28 +02:00
|
|
|
alert_channel: null,
|
2019-04-13 01:44:18 +03:00
|
|
|
|
|
|
|
can_note: false,
|
|
|
|
can_warn: false,
|
|
|
|
can_mute: false,
|
|
|
|
can_kick: false,
|
|
|
|
can_ban: false,
|
|
|
|
can_view: false,
|
|
|
|
can_addcase: false,
|
|
|
|
can_massban: false,
|
|
|
|
can_hidecase: false,
|
|
|
|
can_act_as_other: false,
|
2018-07-01 03:35:51 +03:00
|
|
|
},
|
|
|
|
overrides: [
|
|
|
|
{
|
|
|
|
level: ">=50",
|
2019-04-13 01:44:18 +03:00
|
|
|
config: {
|
|
|
|
can_note: true,
|
|
|
|
can_warn: true,
|
|
|
|
can_mute: true,
|
|
|
|
can_kick: true,
|
|
|
|
can_ban: true,
|
|
|
|
can_view: true,
|
|
|
|
can_addcase: true,
|
2019-02-15 05:07:28 +02:00
|
|
|
},
|
2018-08-16 20:07:43 +03:00
|
|
|
},
|
|
|
|
{
|
|
|
|
level: ">=100",
|
2019-04-13 01:44:18 +03:00
|
|
|
config: {
|
|
|
|
can_massban: true,
|
|
|
|
can_hidecase: true,
|
|
|
|
can_act_as_other: true,
|
2019-02-15 05:07:28 +02:00
|
|
|
},
|
|
|
|
},
|
|
|
|
],
|
2018-07-01 03:35:51 +03:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2018-08-18 13:34:57 +03:00
|
|
|
ignoreEvent(type: IgnoredEventType, userId: any, timeout: number = null) {
|
2018-07-31 04:02:45 +03:00
|
|
|
this.ignoredEvents.push({ type, userId });
|
|
|
|
|
|
|
|
// Clear after expiry (15sec by default)
|
|
|
|
setTimeout(() => {
|
|
|
|
this.clearIgnoredEvent(type, userId);
|
2018-08-18 13:34:57 +03:00
|
|
|
}, timeout || 1000 * 15);
|
2018-07-31 04:02:45 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
isEventIgnored(type: IgnoredEventType, userId: any) {
|
|
|
|
return this.ignoredEvents.some(info => type === info.type && userId === info.userId);
|
|
|
|
}
|
|
|
|
|
|
|
|
clearIgnoredEvent(type: IgnoredEventType, userId: any) {
|
2018-08-18 13:34:57 +03:00
|
|
|
this.ignoredEvents.splice(this.ignoredEvents.findIndex(info => type === info.type && userId === info.userId), 1);
|
2018-07-31 04:02:45 +03:00
|
|
|
}
|
|
|
|
|
2019-02-23 21:47:55 +02:00
|
|
|
formatReasonWithAttachments(reason: string, attachments: Attachment[]) {
|
|
|
|
const attachmentUrls = attachments.map(a => a.url);
|
2019-02-23 21:54:22 +02:00
|
|
|
return ((reason || "") + " " + attachmentUrls.join(" ")).trim();
|
2019-02-23 21:47:55 +02:00
|
|
|
}
|
|
|
|
|
2019-04-18 08:45:51 +03:00
|
|
|
async resolveMember(userId: string): Promise<{ member: Member; isBanned?: boolean }> {
|
|
|
|
let member = this.guild.members.get(userId);
|
|
|
|
|
|
|
|
if (!member) {
|
|
|
|
try {
|
|
|
|
member = await this.bot.getRESTGuildMember(this.guildId, userId);
|
|
|
|
} catch (e) {} // tslint:disable-line
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!member) {
|
|
|
|
const bans = (await this.guild.getBans()) as any;
|
|
|
|
const isBanned = bans.some(b => b.user.id === userId);
|
|
|
|
return { member, isBanned };
|
|
|
|
}
|
|
|
|
|
|
|
|
return { member };
|
|
|
|
}
|
|
|
|
|
2018-07-01 03:35:51 +03:00
|
|
|
/**
|
|
|
|
* Add a BAN action automatically when a user is banned.
|
|
|
|
* Attempts to find the ban's details in the audit log.
|
|
|
|
*/
|
|
|
|
@d.event("guildBanAdd")
|
|
|
|
async onGuildBanAdd(guild: Guild, user: User) {
|
2018-07-31 04:02:45 +03:00
|
|
|
if (this.isEventIgnored(IgnoredEventType.Ban, user.id)) {
|
|
|
|
this.clearIgnoredEvent(IgnoredEventType.Ban, user.id);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-07-29 18:46:49 +03:00
|
|
|
const relevantAuditLogEntry = await findRelevantAuditLogEntry(
|
2018-07-29 23:30:24 +03:00
|
|
|
this.guild,
|
|
|
|
ErisConstants.AuditLogActions.MEMBER_BAN_ADD,
|
2019-02-15 05:07:28 +02:00
|
|
|
user.id,
|
2018-07-29 18:46:49 +03:00
|
|
|
);
|
2018-07-01 03:35:51 +03:00
|
|
|
|
|
|
|
if (relevantAuditLogEntry) {
|
|
|
|
const modId = relevantAuditLogEntry.user.id;
|
|
|
|
const auditLogId = relevantAuditLogEntry.id;
|
|
|
|
|
2018-11-25 17:04:26 +02:00
|
|
|
this.actions.fire("createCase", {
|
|
|
|
userId: user.id,
|
|
|
|
modId,
|
|
|
|
type: CaseTypes.Ban,
|
|
|
|
auditLogId,
|
|
|
|
reason: relevantAuditLogEntry.reason,
|
2019-02-15 05:07:28 +02:00
|
|
|
automatic: true,
|
2018-11-25 17:04:26 +02:00
|
|
|
});
|
2018-07-01 03:35:51 +03:00
|
|
|
} else {
|
2018-11-25 17:04:26 +02:00
|
|
|
this.actions.fire("createCase", {
|
|
|
|
userId: user.id,
|
2019-02-15 05:07:28 +02:00
|
|
|
type: CaseTypes.Ban,
|
2018-11-25 17:04:26 +02:00
|
|
|
});
|
2018-07-01 03:35:51 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Add an UNBAN mod action automatically when a user is unbanned.
|
|
|
|
* Attempts to find the unban's details in the audit log.
|
|
|
|
*/
|
|
|
|
@d.event("guildBanRemove")
|
|
|
|
async onGuildBanRemove(guild: Guild, user: User) {
|
2018-07-31 04:02:45 +03:00
|
|
|
if (this.isEventIgnored(IgnoredEventType.Unban, user.id)) {
|
|
|
|
this.clearIgnoredEvent(IgnoredEventType.Unban, user.id);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-07-29 18:46:49 +03:00
|
|
|
const relevantAuditLogEntry = await findRelevantAuditLogEntry(
|
2018-07-29 23:30:24 +03:00
|
|
|
this.guild,
|
|
|
|
ErisConstants.AuditLogActions.MEMBER_BAN_REMOVE,
|
2019-02-15 05:07:28 +02:00
|
|
|
user.id,
|
2018-07-01 03:35:51 +03:00
|
|
|
);
|
|
|
|
|
|
|
|
if (relevantAuditLogEntry) {
|
|
|
|
const modId = relevantAuditLogEntry.user.id;
|
|
|
|
const auditLogId = relevantAuditLogEntry.id;
|
|
|
|
|
2018-11-25 17:04:26 +02:00
|
|
|
this.actions.fire("createCase", {
|
|
|
|
userId: user.id,
|
|
|
|
modId,
|
|
|
|
type: CaseTypes.Unban,
|
|
|
|
auditLogId,
|
2019-02-15 05:07:28 +02:00
|
|
|
automatic: true,
|
2018-11-25 17:04:26 +02:00
|
|
|
});
|
2018-07-01 03:35:51 +03:00
|
|
|
} else {
|
2018-11-25 17:04:26 +02:00
|
|
|
this.actions.fire("createCase", {
|
|
|
|
userId: user.id,
|
|
|
|
type: CaseTypes.Unban,
|
2019-02-15 05:07:28 +02:00
|
|
|
automatic: true,
|
2018-11-25 17:04:26 +02:00
|
|
|
});
|
2018-07-01 03:35:51 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Show an alert if a member with prior notes joins the server
|
|
|
|
*/
|
|
|
|
@d.event("guildMemberAdd")
|
2018-07-29 23:30:24 +03:00
|
|
|
async onGuildMemberAdd(_, member: Member) {
|
2019-03-04 21:44:04 +02:00
|
|
|
const config = this.getConfig();
|
2018-07-01 03:35:51 +03:00
|
|
|
|
2019-03-04 21:44:04 +02:00
|
|
|
if (!config.alert_on_rejoin) return;
|
|
|
|
|
|
|
|
const alertChannelId = config.alert_channel;
|
2018-07-01 04:31:24 +03:00
|
|
|
if (!alertChannelId) return;
|
2018-07-01 03:35:51 +03:00
|
|
|
|
2018-07-12 02:53:26 +03:00
|
|
|
const actions = await this.cases.getByUserId(member.id);
|
2018-07-01 03:35:51 +03:00
|
|
|
|
|
|
|
if (actions.length) {
|
|
|
|
const alertChannel: any = this.guild.channels.get(alertChannelId);
|
|
|
|
alertChannel.send(
|
2018-08-18 13:34:57 +03:00
|
|
|
`<@!${member.id}> (${member.user.username}#${member.user.discriminator} \`${member.id}\`) joined with ${
|
|
|
|
actions.length
|
2019-02-15 05:07:28 +02:00
|
|
|
} prior record(s)`,
|
2018-07-01 03:35:51 +03:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-29 23:30:24 +03:00
|
|
|
@d.event("guildMemberRemove")
|
|
|
|
async onGuildMemberRemove(_, member: Member) {
|
2018-07-31 04:02:45 +03:00
|
|
|
if (this.isEventIgnored(IgnoredEventType.Kick, member.id)) {
|
|
|
|
this.clearIgnoredEvent(IgnoredEventType.Kick, member.id);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-07-29 23:30:24 +03:00
|
|
|
const kickAuditLogEntry = await findRelevantAuditLogEntry(
|
|
|
|
this.guild,
|
|
|
|
ErisConstants.AuditLogActions.MEMBER_KICK,
|
2019-02-15 05:07:28 +02:00
|
|
|
member.id,
|
2018-07-29 23:30:24 +03:00
|
|
|
);
|
|
|
|
|
|
|
|
if (kickAuditLogEntry) {
|
2019-04-14 17:05:07 +03:00
|
|
|
const existingCaseForThisEntry = await this.cases.findByAuditLogId(kickAuditLogEntry.id);
|
|
|
|
if (existingCaseForThisEntry) {
|
|
|
|
logger.warn(
|
|
|
|
`Tried to create duplicate case for audit log entry ${kickAuditLogEntry.id}, existing case id ${
|
|
|
|
existingCaseForThisEntry.id
|
|
|
|
}`,
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
this.actions.fire("createCase", {
|
|
|
|
userId: member.id,
|
|
|
|
modId: kickAuditLogEntry.user.id,
|
|
|
|
type: CaseTypes.Kick,
|
|
|
|
auditLogId: kickAuditLogEntry.id,
|
|
|
|
reason: kickAuditLogEntry.reason,
|
|
|
|
automatic: true,
|
|
|
|
});
|
|
|
|
}
|
2018-11-25 17:04:26 +02:00
|
|
|
|
2018-07-29 23:30:24 +03:00
|
|
|
this.serverLogs.log(LogType.MEMBER_KICK, {
|
|
|
|
user: stripObjectToScalars(member.user),
|
2019-02-15 05:07:28 +02:00
|
|
|
mod: stripObjectToScalars(kickAuditLogEntry.user),
|
2018-07-29 23:30:24 +03:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-01 03:35:51 +03:00
|
|
|
/**
|
2019-04-13 03:31:50 +03:00
|
|
|
* Update the specified case (or, if case number is omitted, your latest case) by adding more notes/details to it
|
2018-07-01 03:35:51 +03:00
|
|
|
*/
|
2019-04-13 03:31:50 +03:00
|
|
|
@d.command("update", "<caseNumber:number> <note:string$>", {
|
|
|
|
overloads: ["<note:string$>"],
|
|
|
|
})
|
2019-04-13 01:44:18 +03:00
|
|
|
@d.permission("can_note")
|
2019-04-13 17:35:02 +03:00
|
|
|
async updateCmd(msg: Message, args: { caseNumber?: number; note: string }) {
|
2019-04-13 03:31:50 +03:00
|
|
|
let theCase: Case;
|
|
|
|
if (args.caseNumber != null) {
|
|
|
|
theCase = await this.cases.findByCaseNumber(args.caseNumber);
|
|
|
|
} else {
|
|
|
|
theCase = await this.cases.findLatestByModId(msg.author.id);
|
2018-07-01 03:35:51 +03:00
|
|
|
}
|
|
|
|
|
2019-02-17 15:23:40 +02:00
|
|
|
if (!theCase) {
|
2019-04-13 03:31:50 +03:00
|
|
|
msg.channel.createMessage(errorMessage("Case not found"));
|
2019-02-17 15:23:40 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
await this.actions.fire("createCaseNote", {
|
|
|
|
caseId: theCase.id,
|
|
|
|
modId: msg.author.id,
|
|
|
|
note: args.note,
|
|
|
|
});
|
|
|
|
|
2019-04-13 17:35:02 +03:00
|
|
|
this.serverLogs.log(LogType.CASE_UPDATE, {
|
|
|
|
mod: msg.author,
|
|
|
|
caseNumber: theCase.case_number,
|
|
|
|
caseType: CaseTypes[theCase.type],
|
|
|
|
note: args.note,
|
|
|
|
});
|
|
|
|
|
2018-11-25 17:04:26 +02:00
|
|
|
msg.channel.createMessage(successMessage(`Case \`#${theCase.case_number}\` updated`));
|
2018-07-01 03:35:51 +03:00
|
|
|
}
|
|
|
|
|
2018-08-02 00:51:25 +03:00
|
|
|
@d.command("note", "<userId:userId> <note:string$>")
|
2019-04-13 01:44:18 +03:00
|
|
|
@d.permission("can_note")
|
2018-07-01 03:35:51 +03:00
|
|
|
async noteCmd(msg: Message, args: any) {
|
2018-08-02 00:51:25 +03:00
|
|
|
const user = await this.bot.users.get(args.userId);
|
|
|
|
const userName = user ? `${user.username}#${user.discriminator}` : "member";
|
2019-02-23 21:47:55 +02:00
|
|
|
const reason = this.formatReasonWithAttachments(args.note, msg.attachments);
|
2018-08-02 00:51:25 +03:00
|
|
|
|
2018-12-22 13:22:16 +02:00
|
|
|
const createdCase = await this.actions.fire("createCase", {
|
2018-11-25 17:04:26 +02:00
|
|
|
userId: args.userId,
|
|
|
|
modId: msg.author.id,
|
|
|
|
type: CaseTypes.Note,
|
2019-02-23 21:47:55 +02:00
|
|
|
reason,
|
2018-11-25 17:04:26 +02:00
|
|
|
});
|
|
|
|
|
2018-12-22 13:25:17 +02:00
|
|
|
msg.channel.createMessage(successMessage(`Note added on **${userName}** (Case #${createdCase.case_number})`));
|
2018-07-08 13:57:27 +03:00
|
|
|
}
|
|
|
|
|
2019-04-18 08:45:51 +03:00
|
|
|
@d.command("warn", "<userId:string> <reason:string$>", {
|
2019-02-15 05:07:28 +02:00
|
|
|
options: [{ name: "mod", type: "member" }],
|
2019-02-08 20:04:48 +02:00
|
|
|
})
|
2019-04-13 01:44:18 +03:00
|
|
|
@d.permission("can_warn")
|
2019-04-18 08:45:51 +03:00
|
|
|
async warnCmd(msg: Message, args: { userId: string; reason: string; mod?: Member }) {
|
|
|
|
const { member: memberToWarn, isBanned } = await this.resolveMember(args.userId);
|
|
|
|
|
|
|
|
if (!memberToWarn) {
|
|
|
|
if (isBanned) {
|
|
|
|
this.sendErrorMessage(msg.channel, `User is banned`);
|
|
|
|
} else {
|
|
|
|
this.sendErrorMessage(msg.channel, `User not found on the server`);
|
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-07-30 01:44:03 +03:00
|
|
|
// Make sure we're allowed to warn this member
|
2019-04-18 08:45:51 +03:00
|
|
|
if (!this.canActOn(msg.member, memberToWarn)) {
|
2018-07-30 01:44:03 +03:00
|
|
|
msg.channel.createMessage(errorMessage("Cannot warn: insufficient permissions"));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-02-08 20:04:48 +02:00
|
|
|
// The moderator who did the action is the message author or, if used, the specified --mod
|
|
|
|
let mod = msg.member;
|
|
|
|
if (args.mod) {
|
2019-04-13 03:27:29 +03:00
|
|
|
if (!this.hasPermission("can_act_as_other", { message: msg })) {
|
2019-02-08 20:04:48 +02:00
|
|
|
msg.channel.createMessage(errorMessage("No permission for --mod"));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
mod = args.mod;
|
|
|
|
}
|
|
|
|
|
2019-03-04 21:44:04 +02:00
|
|
|
const config = this.getConfig();
|
2019-02-23 21:47:55 +02:00
|
|
|
const reason = this.formatReasonWithAttachments(args.reason, msg.attachments);
|
|
|
|
|
2019-03-04 21:44:04 +02:00
|
|
|
const warnMessage = config.warn_message.replace("{guildName}", this.guild.name).replace("{reason}", reason);
|
2018-07-01 03:35:51 +03:00
|
|
|
|
2019-04-18 08:45:51 +03:00
|
|
|
const userMessageResult = await notifyUser(this.bot, this.guild, memberToWarn.user, warnMessage, {
|
2019-04-13 17:35:02 +03:00
|
|
|
useDM: config.dm_on_warn,
|
|
|
|
useChannel: config.message_on_warn,
|
|
|
|
});
|
2018-07-08 13:57:27 +03:00
|
|
|
|
2019-04-13 17:35:02 +03:00
|
|
|
if (userMessageResult.status === NotifyUserStatus.Failed) {
|
2018-08-18 13:34:57 +03:00
|
|
|
const failedMsg = await msg.channel.createMessage("Failed to message the user. Log the warning anyway?");
|
2018-07-30 01:44:03 +03:00
|
|
|
const reply = await waitForReaction(this.bot, failedMsg, ["✅", "❌"], msg.author.id);
|
2018-07-09 02:51:34 +03:00
|
|
|
failedMsg.delete();
|
|
|
|
if (!reply || reply.name === "❌") {
|
|
|
|
return;
|
2018-07-08 13:57:27 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-12-22 13:22:16 +02:00
|
|
|
const createdCase: Case = await this.actions.fire("createCase", {
|
2019-04-18 08:45:51 +03:00
|
|
|
userId: memberToWarn.id,
|
2019-02-08 20:04:48 +02:00
|
|
|
modId: mod.id,
|
2018-11-25 17:04:26 +02:00
|
|
|
type: CaseTypes.Warn,
|
2019-02-23 21:47:55 +02:00
|
|
|
reason,
|
2019-02-15 05:07:28 +02:00
|
|
|
ppId: mod.id !== msg.author.id ? msg.author.id : null,
|
2019-04-14 17:03:29 +03:00
|
|
|
noteDetails: userMessageResult.status !== NotifyUserStatus.Ignored ? [ucfirst(userMessageResult.text)] : [],
|
2018-11-25 17:04:26 +02:00
|
|
|
});
|
2018-07-08 13:57:27 +03:00
|
|
|
|
2019-03-07 22:09:14 +02:00
|
|
|
const messageResultText = userMessageResult.text ? ` (${userMessageResult.text})` : "";
|
2019-02-23 22:05:54 +02:00
|
|
|
|
2018-08-02 00:51:25 +03:00
|
|
|
msg.channel.createMessage(
|
2018-12-22 13:22:16 +02:00
|
|
|
successMessage(
|
2019-04-18 08:45:51 +03:00
|
|
|
`Warned **${memberToWarn.user.username}#${memberToWarn.user.discriminator}** (Case #${
|
2019-02-23 22:05:54 +02:00
|
|
|
createdCase.case_number
|
|
|
|
})${messageResultText}`,
|
2019-02-15 05:07:28 +02:00
|
|
|
),
|
2018-08-02 00:51:25 +03:00
|
|
|
);
|
2018-07-09 02:51:34 +03:00
|
|
|
|
|
|
|
this.serverLogs.log(LogType.MEMBER_WARN, {
|
2019-02-08 20:04:48 +02:00
|
|
|
mod: stripObjectToScalars(mod.user),
|
2019-04-18 08:45:51 +03:00
|
|
|
member: stripObjectToScalars(memberToWarn, ["user"]),
|
2018-07-09 02:51:34 +03:00
|
|
|
});
|
2018-07-08 13:57:27 +03:00
|
|
|
}
|
|
|
|
|
2019-04-18 08:45:51 +03:00
|
|
|
@d.command("mute", "<userId:userId> <time:delay> <reason:string$>", {
|
|
|
|
overloads: ["<userId:userId> <time:delay>", "<userId:userId> [reason:string$]"],
|
2019-02-15 05:07:28 +02:00
|
|
|
options: [{ name: "mod", type: "member" }],
|
2019-02-08 20:04:48 +02:00
|
|
|
})
|
2019-04-13 01:44:18 +03:00
|
|
|
@d.permission("can_mute")
|
2019-04-18 08:45:51 +03:00
|
|
|
async muteCmd(msg: Message, args: { userId: string; time?: number; reason?: string; mod: Member }) {
|
|
|
|
const user = this.bot.users.get(args.userId) || { ...unknownUser, id: args.userId };
|
|
|
|
const { member: memberToMute, isBanned } = await this.resolveMember(user.id);
|
|
|
|
|
|
|
|
if (!memberToMute) {
|
|
|
|
const notOnServerMsg = isBanned
|
|
|
|
? await msg.channel.createMessage("User is banned. Apply a mute to them anyway?")
|
|
|
|
: await msg.channel.createMessage("User is not on the server. Apply a mute to them anyway?");
|
|
|
|
|
|
|
|
const reply = await waitForReaction(this.bot, notOnServerMsg, ["✅", "❌"], msg.author.id);
|
|
|
|
notOnServerMsg.delete();
|
|
|
|
if (!reply || reply.name === "❌") {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-08 13:57:27 +03:00
|
|
|
// Make sure we're allowed to mute this member
|
2019-04-18 08:45:51 +03:00
|
|
|
if (memberToMute && !this.canActOn(msg.member, memberToMute)) {
|
2018-07-12 01:42:18 +03:00
|
|
|
msg.channel.createMessage(errorMessage("Cannot mute: insufficient permissions"));
|
|
|
|
return;
|
2018-07-08 13:57:27 +03:00
|
|
|
}
|
|
|
|
|
2019-02-08 20:04:48 +02:00
|
|
|
// The moderator who did the action is the message author or, if used, the specified --mod
|
|
|
|
let mod = msg.member;
|
2019-04-13 17:35:02 +03:00
|
|
|
let pp = null;
|
|
|
|
|
2019-02-08 20:04:48 +02:00
|
|
|
if (args.mod) {
|
2019-04-13 03:27:29 +03:00
|
|
|
if (!this.hasPermission("can_act_as_other", { message: msg })) {
|
2019-02-08 20:04:48 +02:00
|
|
|
msg.channel.createMessage(errorMessage("No permission for --mod"));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
mod = args.mod;
|
2019-04-13 17:35:02 +03:00
|
|
|
pp = msg.author;
|
2019-02-08 20:04:48 +02:00
|
|
|
}
|
|
|
|
|
2019-04-05 20:05:37 +03:00
|
|
|
const timeUntilUnmute = args.time && humanizeDuration(args.time);
|
2019-02-23 21:47:55 +02:00
|
|
|
const reason = this.formatReasonWithAttachments(args.reason, msg.attachments);
|
|
|
|
|
2019-04-13 17:35:02 +03:00
|
|
|
let muteResult: MuteActionResult;
|
2018-07-08 13:57:27 +03:00
|
|
|
|
2019-04-13 17:35:02 +03:00
|
|
|
try {
|
|
|
|
muteResult = await this.actions.fire("mute", {
|
2019-04-18 08:45:51 +03:00
|
|
|
userId: user.id,
|
2019-04-13 17:35:02 +03:00
|
|
|
muteTime: args.time,
|
2019-02-23 21:47:55 +02:00
|
|
|
reason,
|
2019-04-13 17:35:02 +03:00
|
|
|
caseDetails: {
|
|
|
|
modId: mod.id,
|
|
|
|
ppId: pp && pp.id,
|
|
|
|
},
|
2018-07-12 01:45:26 +03:00
|
|
|
});
|
2019-04-13 17:35:02 +03:00
|
|
|
} catch (e) {
|
2019-04-18 08:45:51 +03:00
|
|
|
logger.error(`Failed to mute user ${user.id}: ${e.stack}`);
|
2019-04-13 17:35:02 +03:00
|
|
|
msg.channel.createMessage(errorMessage("Could not mute the user"));
|
|
|
|
return;
|
2018-07-08 13:57:27 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Confirm the action to the moderator
|
2018-07-12 01:20:20 +03:00
|
|
|
let response;
|
2019-04-05 20:05:37 +03:00
|
|
|
if (args.time) {
|
2019-04-13 17:35:02 +03:00
|
|
|
if (muteResult.updatedExistingMute) {
|
2019-03-07 22:35:33 +02:00
|
|
|
response = asSingleLine(`
|
2019-04-18 08:45:51 +03:00
|
|
|
Updated **${user.username}#${user.discriminator}**'s
|
2019-04-13 17:35:02 +03:00
|
|
|
mute to ${timeUntilUnmute} (Case #${muteResult.case.case_number})
|
2019-03-07 22:35:33 +02:00
|
|
|
`);
|
|
|
|
} else {
|
|
|
|
response = asSingleLine(`
|
2019-04-18 08:45:51 +03:00
|
|
|
Muted **${user.username}#${user.discriminator}**
|
2019-04-13 17:35:02 +03:00
|
|
|
for ${timeUntilUnmute} (Case #${muteResult.case.case_number})
|
2019-03-07 22:35:33 +02:00
|
|
|
`);
|
|
|
|
}
|
2018-07-08 13:57:27 +03:00
|
|
|
} else {
|
2019-04-13 17:35:02 +03:00
|
|
|
if (muteResult.updatedExistingMute) {
|
2019-03-07 22:35:33 +02:00
|
|
|
response = asSingleLine(`
|
2019-04-18 08:45:51 +03:00
|
|
|
Updated **${user.username}#${user.discriminator}**'s
|
2019-04-13 17:35:02 +03:00
|
|
|
mute to indefinite (Case #${muteResult.case.case_number})
|
2019-03-07 22:35:33 +02:00
|
|
|
`);
|
|
|
|
} else {
|
|
|
|
response = asSingleLine(`
|
2019-04-18 08:45:51 +03:00
|
|
|
Muted **${user.username}#${user.discriminator}**
|
2019-04-13 17:35:02 +03:00
|
|
|
indefinitely (Case #${muteResult.case.case_number})
|
2019-03-07 22:35:33 +02:00
|
|
|
`);
|
|
|
|
}
|
2018-07-08 13:57:27 +03:00
|
|
|
}
|
2018-07-09 03:13:31 +03:00
|
|
|
|
2019-04-13 17:35:02 +03:00
|
|
|
if (muteResult.notifyResult.text) response += ` (${muteResult.notifyResult.text})`;
|
2018-07-12 01:20:20 +03:00
|
|
|
msg.channel.createMessage(successMessage(response));
|
2018-07-01 03:35:51 +03:00
|
|
|
}
|
|
|
|
|
2019-04-18 08:45:51 +03:00
|
|
|
@d.command("unmute", "<userId:userId> <time:delay> <reason:string$>", {
|
|
|
|
overloads: ["<userId:userId> <time:delay>", "<userId:userId> [reason:string$]"],
|
2019-02-15 05:07:28 +02:00
|
|
|
options: [{ name: "mod", type: "member" }],
|
2019-02-08 20:04:48 +02:00
|
|
|
})
|
2019-04-13 01:44:18 +03:00
|
|
|
@d.permission("can_mute")
|
2019-04-18 08:45:51 +03:00
|
|
|
async unmuteCmd(msg: Message, args: { userId: string; time?: number; reason?: string; mod?: Member }) {
|
|
|
|
// Check if they're muted in the first place
|
|
|
|
if (!(await this.mutes.isMuted(args.userId))) {
|
|
|
|
msg.channel.createMessage(errorMessage("Cannot unmute: member is not muted"));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Find the server member to unmute
|
|
|
|
const user = this.bot.users.get(args.userId) || { ...unknownUser, id: args.userId };
|
|
|
|
const { member: memberToUnmute, isBanned } = await this.resolveMember(user.id);
|
|
|
|
|
|
|
|
if (!memberToUnmute) {
|
|
|
|
const notOnServerMsg = isBanned
|
|
|
|
? await msg.channel.createMessage("User is banned. Unmute them anyway?")
|
|
|
|
: await msg.channel.createMessage("User is not on the server. Unmute them anyway?");
|
|
|
|
|
|
|
|
const reply = await waitForReaction(this.bot, notOnServerMsg, ["✅", "❌"], msg.author.id);
|
|
|
|
notOnServerMsg.delete();
|
|
|
|
if (!reply || reply.name === "❌") {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Make sure we're allowed to unmute this member
|
|
|
|
if (memberToUnmute && !this.canActOn(msg.member, memberToUnmute)) {
|
2018-07-13 00:10:20 +03:00
|
|
|
msg.channel.createMessage(errorMessage("Cannot unmute: insufficient permissions"));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-02-08 20:04:48 +02:00
|
|
|
// The moderator who did the action is the message author or, if used, the specified --mod
|
2019-04-13 17:35:02 +03:00
|
|
|
let mod = msg.author;
|
|
|
|
let pp = null;
|
|
|
|
|
2019-02-08 20:04:48 +02:00
|
|
|
if (args.mod) {
|
2019-04-13 03:27:29 +03:00
|
|
|
if (!this.hasPermission("can_act_as_other", { message: msg })) {
|
2019-02-08 20:04:48 +02:00
|
|
|
msg.channel.createMessage(errorMessage("No permission for --mod"));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-04-13 17:35:02 +03:00
|
|
|
mod = args.mod.user;
|
|
|
|
pp = msg.author;
|
2019-02-08 20:04:48 +02:00
|
|
|
}
|
|
|
|
|
2019-02-23 21:47:55 +02:00
|
|
|
const reason = this.formatReasonWithAttachments(args.reason, msg.attachments);
|
|
|
|
|
2019-04-13 17:35:02 +03:00
|
|
|
const result = await this.actions.fire("unmute", {
|
2019-04-18 08:45:51 +03:00
|
|
|
userId: user.id,
|
2019-04-13 17:35:02 +03:00
|
|
|
unmuteTime: args.time,
|
|
|
|
caseDetails: {
|
|
|
|
modId: mod.id,
|
|
|
|
ppId: pp && pp.id,
|
|
|
|
reason,
|
|
|
|
},
|
2018-12-22 13:22:16 +02:00
|
|
|
});
|
|
|
|
|
2019-04-13 17:35:02 +03:00
|
|
|
// Confirm the action to the moderator
|
2019-04-05 20:05:37 +03:00
|
|
|
if (args.time) {
|
2019-04-13 17:35:02 +03:00
|
|
|
const timeUntilUnmute = args.time && humanizeDuration(args.time);
|
2018-08-05 00:58:03 +03:00
|
|
|
msg.channel.createMessage(
|
|
|
|
successMessage(
|
2019-04-13 17:35:02 +03:00
|
|
|
asSingleLine(`
|
2019-04-18 08:45:51 +03:00
|
|
|
Unmuting **${user.username}#${user.discriminator}**
|
2019-04-13 17:35:02 +03:00
|
|
|
in ${timeUntilUnmute} (Case #${result.case.case_number})
|
|
|
|
`),
|
2019-02-15 05:07:28 +02:00
|
|
|
),
|
2018-08-05 00:58:03 +03:00
|
|
|
);
|
|
|
|
} else {
|
|
|
|
msg.channel.createMessage(
|
2018-12-22 13:22:16 +02:00
|
|
|
successMessage(
|
2019-04-13 17:35:02 +03:00
|
|
|
asSingleLine(`
|
2019-04-18 08:45:51 +03:00
|
|
|
Unmuted **${user.username}#${user.discriminator}**
|
2019-04-13 17:35:02 +03:00
|
|
|
(Case #${result.case.case_number})
|
|
|
|
`),
|
2019-02-15 05:07:28 +02:00
|
|
|
),
|
2018-08-05 00:58:03 +03:00
|
|
|
);
|
2019-01-03 04:37:44 +02:00
|
|
|
}
|
2018-07-13 00:10:20 +03:00
|
|
|
}
|
|
|
|
|
2019-04-18 08:45:51 +03:00
|
|
|
@d.command("kick", "<userId:userId> [reason:string$]", {
|
2019-02-15 05:07:28 +02:00
|
|
|
options: [{ name: "mod", type: "member" }],
|
2019-02-08 20:04:48 +02:00
|
|
|
})
|
2019-04-13 01:44:18 +03:00
|
|
|
@d.permission("can_kick")
|
2019-04-18 08:45:51 +03:00
|
|
|
async kickCmd(msg, args: { userId: string; reason: string; mod: Member }) {
|
|
|
|
const { member: memberToKick, isBanned } = await this.resolveMember(args.userId);
|
|
|
|
|
|
|
|
if (!memberToKick) {
|
|
|
|
if (isBanned) {
|
|
|
|
this.sendErrorMessage(msg.channel, `User is banned`);
|
|
|
|
} else {
|
|
|
|
this.sendErrorMessage(msg.channel, `User not found on the server`);
|
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-07-12 02:03:22 +03:00
|
|
|
// Make sure we're allowed to kick this member
|
2019-04-18 08:45:51 +03:00
|
|
|
if (!this.canActOn(msg.member, memberToKick)) {
|
2018-07-12 02:03:22 +03:00
|
|
|
msg.channel.createMessage(errorMessage("Cannot kick: insufficient permissions"));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-02-08 20:04:48 +02:00
|
|
|
// The moderator who did the action is the message author or, if used, the specified --mod
|
|
|
|
let mod = msg.member;
|
|
|
|
if (args.mod) {
|
2019-04-13 03:27:29 +03:00
|
|
|
if (!this.hasPermission("can_act_as_other", { message: msg })) {
|
2019-02-08 20:04:48 +02:00
|
|
|
msg.channel.createMessage(errorMessage("No permission for --mod"));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
mod = args.mod;
|
|
|
|
}
|
|
|
|
|
2019-03-04 21:44:04 +02:00
|
|
|
const config = this.getConfig();
|
2019-02-23 21:47:55 +02:00
|
|
|
const reason = this.formatReasonWithAttachments(args.reason, msg.attachments);
|
|
|
|
|
2018-07-12 02:03:22 +03:00
|
|
|
// Attempt to message the user *before* kicking them, as doing it after may not be possible
|
2019-04-13 17:35:02 +03:00
|
|
|
let userMessageResult: INotifyUserResult = { status: NotifyUserStatus.Ignored };
|
2018-07-12 02:03:22 +03:00
|
|
|
if (args.reason) {
|
2019-03-16 15:42:55 +02:00
|
|
|
const kickMessage = await renderTemplate(config.kick_message, {
|
2018-07-12 02:03:22 +03:00
|
|
|
guildName: this.guild.name,
|
2019-02-23 21:47:55 +02:00
|
|
|
reason,
|
2018-07-12 02:03:22 +03:00
|
|
|
});
|
|
|
|
|
2019-04-18 08:45:51 +03:00
|
|
|
userMessageResult = await notifyUser(this.bot, this.guild, memberToKick.user, kickMessage, {
|
2019-04-13 17:35:02 +03:00
|
|
|
useDM: config.dm_on_kick,
|
|
|
|
useChannel: config.message_on_kick,
|
|
|
|
channelId: config.message_channel,
|
|
|
|
});
|
2018-07-12 02:03:22 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Kick the user
|
2019-04-18 08:45:51 +03:00
|
|
|
this.serverLogs.ignoreLog(LogType.MEMBER_KICK, memberToKick.id);
|
|
|
|
this.ignoreEvent(IgnoredEventType.Kick, memberToKick.id);
|
|
|
|
memberToKick.kick(reason);
|
2018-07-12 02:03:22 +03:00
|
|
|
|
|
|
|
// Create a case for this action
|
2018-12-22 13:22:16 +02:00
|
|
|
const createdCase = await this.actions.fire("createCase", {
|
2019-04-18 08:45:51 +03:00
|
|
|
userId: memberToKick.id,
|
2019-02-08 20:04:48 +02:00
|
|
|
modId: mod.id,
|
2018-11-25 17:04:26 +02:00
|
|
|
type: CaseTypes.Kick,
|
2019-02-23 21:47:55 +02:00
|
|
|
reason,
|
2019-02-15 05:07:28 +02:00
|
|
|
ppId: mod.id !== msg.author.id ? msg.author.id : null,
|
2019-04-14 17:03:29 +03:00
|
|
|
noteDetails: userMessageResult.status !== NotifyUserStatus.Ignored ? [ucfirst(userMessageResult.text)] : [],
|
2018-11-25 17:04:26 +02:00
|
|
|
});
|
2018-07-12 02:03:22 +03:00
|
|
|
|
|
|
|
// Confirm the action to the moderator
|
2019-04-18 08:45:51 +03:00
|
|
|
let response = `Kicked **${memberToKick.user.username}#${memberToKick.user.discriminator}** (Case #${
|
2018-12-22 13:22:16 +02:00
|
|
|
createdCase.case_number
|
|
|
|
})`;
|
2019-03-07 22:09:14 +02:00
|
|
|
|
|
|
|
if (userMessageResult.text) response += ` (${userMessageResult.text})`;
|
2018-07-12 02:03:22 +03:00
|
|
|
msg.channel.createMessage(successMessage(response));
|
|
|
|
|
|
|
|
// Log the action
|
|
|
|
this.serverLogs.log(LogType.MEMBER_KICK, {
|
2019-02-08 20:04:48 +02:00
|
|
|
mod: stripObjectToScalars(mod.user),
|
2019-04-18 08:45:51 +03:00
|
|
|
user: stripObjectToScalars(memberToKick.user),
|
2018-07-12 02:03:22 +03:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2019-04-18 08:45:51 +03:00
|
|
|
@d.command("ban", "<userId:userId> [reason:string$]", {
|
2019-02-15 05:07:28 +02:00
|
|
|
options: [{ name: "mod", type: "member" }],
|
2019-02-08 20:04:48 +02:00
|
|
|
})
|
2019-04-13 01:44:18 +03:00
|
|
|
@d.permission("can_ban")
|
2019-04-18 08:45:51 +03:00
|
|
|
async banCmd(msg, args: { userId: string; reason?: string; mod?: Member }) {
|
|
|
|
const { member: memberToBan, isBanned } = await this.resolveMember(args.userId);
|
|
|
|
|
|
|
|
if (!memberToBan) {
|
|
|
|
if (isBanned) {
|
|
|
|
this.sendErrorMessage(msg.channel, `User is already banned`);
|
|
|
|
} else {
|
|
|
|
this.sendErrorMessage(msg.channel, `User not found on the server`);
|
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-07-12 02:03:22 +03:00
|
|
|
// Make sure we're allowed to ban this member
|
2019-04-18 08:45:51 +03:00
|
|
|
if (!this.canActOn(msg.member, memberToBan)) {
|
2018-07-12 02:03:22 +03:00
|
|
|
msg.channel.createMessage(errorMessage("Cannot ban: insufficient permissions"));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-02-08 20:04:48 +02:00
|
|
|
// The moderator who did the action is the message author or, if used, the specified --mod
|
|
|
|
let mod = msg.member;
|
|
|
|
if (args.mod) {
|
2019-04-13 03:27:29 +03:00
|
|
|
if (!this.hasPermission("can_act_as_other", { message: msg })) {
|
2019-02-08 20:04:48 +02:00
|
|
|
msg.channel.createMessage(errorMessage("No permission for --mod"));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
mod = args.mod;
|
|
|
|
}
|
|
|
|
|
2019-03-04 21:44:04 +02:00
|
|
|
const config = this.getConfig();
|
2019-02-23 21:47:55 +02:00
|
|
|
const reason = this.formatReasonWithAttachments(args.reason, msg.attachments);
|
|
|
|
|
2018-07-12 02:03:22 +03:00
|
|
|
// Attempt to message the user *before* banning them, as doing it after may not be possible
|
2019-04-13 17:35:02 +03:00
|
|
|
let userMessageResult: INotifyUserResult = { status: NotifyUserStatus.Ignored };
|
2019-02-23 21:47:55 +02:00
|
|
|
if (reason) {
|
2019-03-16 15:42:55 +02:00
|
|
|
const banMessage = await renderTemplate(config.ban_message, {
|
2018-07-12 02:03:22 +03:00
|
|
|
guildName: this.guild.name,
|
2019-02-23 21:47:55 +02:00
|
|
|
reason,
|
2018-07-12 02:03:22 +03:00
|
|
|
});
|
|
|
|
|
2019-04-18 08:45:51 +03:00
|
|
|
userMessageResult = await notifyUser(this.bot, this.guild, memberToBan.user, banMessage, {
|
2019-04-13 17:35:02 +03:00
|
|
|
useDM: config.dm_on_ban,
|
|
|
|
useChannel: config.message_on_ban,
|
|
|
|
channelId: config.message_channel,
|
|
|
|
});
|
2018-07-12 02:03:22 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Ban the user
|
2019-04-18 08:45:51 +03:00
|
|
|
this.serverLogs.ignoreLog(LogType.MEMBER_BAN, memberToBan.id);
|
|
|
|
this.ignoreEvent(IgnoredEventType.Ban, memberToBan.id);
|
|
|
|
memberToBan.ban(1, reason);
|
2018-07-12 02:03:22 +03:00
|
|
|
|
|
|
|
// Create a case for this action
|
2018-12-22 13:22:16 +02:00
|
|
|
const createdCase = await this.actions.fire("createCase", {
|
2019-04-18 08:45:51 +03:00
|
|
|
userId: memberToBan.id,
|
2019-02-08 20:04:48 +02:00
|
|
|
modId: mod.id,
|
2018-11-25 17:04:26 +02:00
|
|
|
type: CaseTypes.Ban,
|
2019-02-23 21:47:55 +02:00
|
|
|
reason,
|
2019-02-15 05:07:28 +02:00
|
|
|
ppId: mod.id !== msg.author.id ? msg.author.id : null,
|
2019-04-14 17:03:29 +03:00
|
|
|
noteDetails: userMessageResult.status !== NotifyUserStatus.Ignored ? [ucfirst(userMessageResult.text)] : [],
|
2018-11-25 17:04:26 +02:00
|
|
|
});
|
2018-07-12 02:03:22 +03:00
|
|
|
|
|
|
|
// Confirm the action to the moderator
|
2019-04-18 08:45:51 +03:00
|
|
|
let response = `Banned **${memberToBan.user.username}#${memberToBan.user.discriminator}** (Case #${
|
2018-12-22 13:22:16 +02:00
|
|
|
createdCase.case_number
|
|
|
|
})`;
|
2019-03-07 22:09:14 +02:00
|
|
|
|
|
|
|
if (userMessageResult.text) response += ` (${userMessageResult.text})`;
|
2018-07-12 02:03:22 +03:00
|
|
|
msg.channel.createMessage(successMessage(response));
|
|
|
|
|
|
|
|
// Log the action
|
|
|
|
this.serverLogs.log(LogType.MEMBER_BAN, {
|
2019-02-08 20:04:48 +02:00
|
|
|
mod: stripObjectToScalars(mod.user),
|
2019-04-18 08:45:51 +03:00
|
|
|
user: stripObjectToScalars(memberToBan.user),
|
2018-07-12 02:03:22 +03:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2019-04-18 08:45:51 +03:00
|
|
|
@d.command("softban", "<userId:userId> [reason:string$]", {
|
2019-02-15 05:07:28 +02:00
|
|
|
options: [{ name: "mod", type: "member" }],
|
2019-02-08 20:04:48 +02:00
|
|
|
})
|
2019-04-13 01:44:18 +03:00
|
|
|
@d.permission("can_ban")
|
2019-04-18 08:45:51 +03:00
|
|
|
async softbanCmd(msg, args: { userId: string; reason: string; mod?: Member }) {
|
|
|
|
const { member: memberToSoftban, isBanned } = await this.resolveMember(args.userId);
|
|
|
|
|
|
|
|
if (!memberToSoftban) {
|
|
|
|
if (isBanned) {
|
|
|
|
this.sendErrorMessage(msg.channel, `User is already banned`);
|
|
|
|
} else {
|
|
|
|
this.sendErrorMessage(msg.channel, `User not found on the server`);
|
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-08-02 00:51:25 +03:00
|
|
|
// Make sure we're allowed to ban this member
|
2019-04-18 08:45:51 +03:00
|
|
|
if (!this.canActOn(msg.member, memberToSoftban)) {
|
2018-08-02 00:51:25 +03:00
|
|
|
msg.channel.createMessage(errorMessage("Cannot ban: insufficient permissions"));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-02-08 20:04:48 +02:00
|
|
|
// The moderator who did the action is the message author or, if used, the specified --mod
|
|
|
|
let mod = msg.member;
|
|
|
|
if (args.mod) {
|
2019-04-13 03:27:29 +03:00
|
|
|
if (!this.hasPermission("can_act_as_other", { message: msg })) {
|
2019-02-08 20:04:48 +02:00
|
|
|
msg.channel.createMessage(errorMessage("No permission for --mod"));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
mod = args.mod;
|
|
|
|
}
|
|
|
|
|
2019-02-23 21:47:55 +02:00
|
|
|
const reason = this.formatReasonWithAttachments(args.reason, msg.attachments);
|
|
|
|
|
2018-08-02 00:51:25 +03:00
|
|
|
// Softban the user = ban, and immediately unban
|
2019-04-18 08:45:51 +03:00
|
|
|
this.serverLogs.ignoreLog(LogType.MEMBER_BAN, memberToSoftban.id);
|
|
|
|
this.serverLogs.ignoreLog(LogType.MEMBER_UNBAN, memberToSoftban.id);
|
|
|
|
this.ignoreEvent(IgnoredEventType.Ban, memberToSoftban.id);
|
|
|
|
this.ignoreEvent(IgnoredEventType.Unban, memberToSoftban.id);
|
2018-08-02 00:51:25 +03:00
|
|
|
|
2019-04-18 08:45:51 +03:00
|
|
|
await memberToSoftban.ban(1, reason);
|
|
|
|
await this.guild.unbanMember(memberToSoftban.id);
|
2018-08-02 00:51:25 +03:00
|
|
|
|
|
|
|
// Create a case for this action
|
2018-12-22 13:22:16 +02:00
|
|
|
const createdCase = await this.actions.fire("createCase", {
|
2019-04-18 08:45:51 +03:00
|
|
|
userId: memberToSoftban.id,
|
2019-02-08 20:04:48 +02:00
|
|
|
modId: mod.id,
|
2018-11-25 17:04:26 +02:00
|
|
|
type: CaseTypes.Softban,
|
2019-02-23 21:47:55 +02:00
|
|
|
reason,
|
2019-02-15 05:07:28 +02:00
|
|
|
ppId: mod.id !== msg.author.id ? msg.author.id : null,
|
2018-11-25 17:04:26 +02:00
|
|
|
});
|
2018-08-02 00:51:25 +03:00
|
|
|
|
|
|
|
// Confirm the action to the moderator
|
|
|
|
msg.channel.createMessage(
|
2018-12-22 13:22:16 +02:00
|
|
|
successMessage(
|
2019-04-18 08:45:51 +03:00
|
|
|
`Softbanned **${memberToSoftban.user.username}#${memberToSoftban.user.discriminator}** (Case #${
|
2018-12-22 13:22:16 +02:00
|
|
|
createdCase.case_number
|
2019-02-15 05:07:28 +02:00
|
|
|
})`,
|
|
|
|
),
|
2018-08-02 00:51:25 +03:00
|
|
|
);
|
|
|
|
|
|
|
|
// Log the action
|
|
|
|
this.serverLogs.log(LogType.MEMBER_SOFTBAN, {
|
2019-02-08 20:04:48 +02:00
|
|
|
mod: stripObjectToScalars(mod.user),
|
2019-04-18 08:45:51 +03:00
|
|
|
member: stripObjectToScalars(memberToSoftban, ["user"]),
|
2018-08-02 00:51:25 +03:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2019-02-08 20:04:48 +02:00
|
|
|
@d.command("unban", "<userId:userId> [reason:string$]", {
|
2019-02-15 05:07:28 +02:00
|
|
|
options: [{ name: "mod", type: "member" }],
|
2019-02-08 20:04:48 +02:00
|
|
|
})
|
2019-04-13 01:44:18 +03:00
|
|
|
@d.permission("can_ban")
|
2019-02-08 20:04:48 +02:00
|
|
|
async unbanCmd(msg: Message, args: { userId: string; reason: string; mod: Member }) {
|
|
|
|
// The moderator who did the action is the message author or, if used, the specified --mod
|
|
|
|
let mod = msg.member;
|
|
|
|
if (args.mod) {
|
2019-04-13 03:27:29 +03:00
|
|
|
if (!this.hasPermission("can_act_as_other", { message: msg })) {
|
2019-02-08 20:04:48 +02:00
|
|
|
msg.channel.createMessage(errorMessage("No permission for --mod"));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
mod = args.mod;
|
|
|
|
}
|
|
|
|
|
2018-07-31 04:02:45 +03:00
|
|
|
this.serverLogs.ignoreLog(LogType.MEMBER_UNBAN, args.userId);
|
2018-07-29 18:46:49 +03:00
|
|
|
|
2018-07-14 20:55:39 +03:00
|
|
|
try {
|
2018-07-31 04:02:45 +03:00
|
|
|
this.ignoreEvent(IgnoredEventType.Unban, args.userId);
|
2018-08-01 00:52:44 +03:00
|
|
|
await this.guild.unbanMember(args.userId);
|
2018-07-14 20:55:39 +03:00
|
|
|
} catch (e) {
|
2019-04-14 13:30:48 +03:00
|
|
|
msg.channel.createMessage(errorMessage("Failed to unban member; are you sure they're banned?"));
|
2018-07-14 20:55:39 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-02-23 21:47:55 +02:00
|
|
|
const reason = this.formatReasonWithAttachments(args.reason, msg.attachments);
|
|
|
|
|
2018-07-14 20:55:39 +03:00
|
|
|
// Create a case
|
2018-12-22 13:22:16 +02:00
|
|
|
const createdCase = await this.actions.fire("createCase", {
|
2018-12-14 08:54:15 +02:00
|
|
|
userId: args.userId,
|
2019-02-08 20:04:48 +02:00
|
|
|
modId: mod.id,
|
2018-11-25 17:04:26 +02:00
|
|
|
type: CaseTypes.Unban,
|
2019-02-23 21:47:55 +02:00
|
|
|
reason,
|
2019-02-15 05:07:28 +02:00
|
|
|
ppId: mod.id !== msg.author.id ? msg.author.id : null,
|
2018-11-25 17:04:26 +02:00
|
|
|
});
|
2018-07-14 20:55:39 +03:00
|
|
|
|
2018-12-22 13:22:16 +02:00
|
|
|
// Confirm the action
|
|
|
|
msg.channel.createMessage(successMessage(`Member unbanned (Case #${createdCase.case_number})`));
|
|
|
|
|
2018-07-14 20:55:39 +03:00
|
|
|
// Log the action
|
|
|
|
this.serverLogs.log(LogType.MEMBER_UNBAN, {
|
2019-02-08 20:04:48 +02:00
|
|
|
mod: stripObjectToScalars(mod.user),
|
2019-02-15 05:07:28 +02:00
|
|
|
userId: args.userId,
|
2018-07-14 20:55:39 +03:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2019-02-08 20:04:48 +02:00
|
|
|
@d.command("forceban", "<userId:userId> [reason:string$]", {
|
2019-02-15 05:07:28 +02:00
|
|
|
options: [{ name: "mod", type: "member" }],
|
2019-02-08 20:04:48 +02:00
|
|
|
})
|
2019-04-13 01:44:18 +03:00
|
|
|
@d.permission("can_ban")
|
2018-07-14 20:55:39 +03:00
|
|
|
async forcebanCmd(msg: Message, args: any) {
|
|
|
|
// If the user exists as a guild member, make sure we can act on them first
|
|
|
|
const member = this.guild.members.get(args.userId);
|
|
|
|
if (member && !this.canActOn(msg.member, member)) {
|
2018-08-18 13:34:57 +03:00
|
|
|
msg.channel.createMessage(errorMessage("Cannot forceban this user: insufficient permissions"));
|
2018-07-14 20:55:39 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-02-08 20:04:48 +02:00
|
|
|
// The moderator who did the action is the message author or, if used, the specified --mod
|
|
|
|
let mod = msg.member;
|
|
|
|
if (args.mod) {
|
2019-04-13 03:27:29 +03:00
|
|
|
if (!this.hasPermission("can_act_as_other", { message: msg })) {
|
2019-02-08 20:04:48 +02:00
|
|
|
msg.channel.createMessage(errorMessage("No permission for --mod"));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
mod = args.mod;
|
|
|
|
}
|
|
|
|
|
2019-02-23 21:47:55 +02:00
|
|
|
const reason = this.formatReasonWithAttachments(args.reason, msg.attachments);
|
|
|
|
|
2018-07-31 04:02:45 +03:00
|
|
|
this.ignoreEvent(IgnoredEventType.Ban, args.userId);
|
|
|
|
this.serverLogs.ignoreLog(LogType.MEMBER_BAN, args.userId);
|
2018-07-29 18:46:49 +03:00
|
|
|
|
2018-07-14 20:55:39 +03:00
|
|
|
try {
|
2019-02-23 21:47:55 +02:00
|
|
|
await this.guild.banMember(args.userId, 1, reason);
|
2018-07-14 20:55:39 +03:00
|
|
|
} catch (e) {
|
|
|
|
msg.channel.createMessage(errorMessage("Failed to forceban member"));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create a case
|
2018-12-22 13:22:16 +02:00
|
|
|
const createdCase = await this.actions.fire("createCase", {
|
2018-11-25 17:04:26 +02:00
|
|
|
userId: args.userId,
|
2019-02-08 20:04:48 +02:00
|
|
|
modId: mod.id,
|
2018-11-25 17:04:26 +02:00
|
|
|
type: CaseTypes.Ban,
|
2019-02-23 21:47:55 +02:00
|
|
|
reason,
|
2019-02-15 05:07:28 +02:00
|
|
|
ppId: mod.id !== msg.author.id ? msg.author.id : null,
|
2018-11-25 17:04:26 +02:00
|
|
|
});
|
2018-07-14 20:55:39 +03:00
|
|
|
|
2018-12-22 13:22:16 +02:00
|
|
|
// Confirm the action
|
|
|
|
msg.channel.createMessage(successMessage(`Member forcebanned (Case #${createdCase.case_number})`));
|
|
|
|
|
2018-07-14 20:55:39 +03:00
|
|
|
// Log the action
|
|
|
|
this.serverLogs.log(LogType.MEMBER_FORCEBAN, {
|
2019-02-08 20:04:48 +02:00
|
|
|
mod: stripObjectToScalars(mod.user),
|
2019-02-15 05:07:28 +02:00
|
|
|
userId: args.userId,
|
2018-07-14 20:55:39 +03:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-08-16 20:07:43 +03:00
|
|
|
@d.command("massban", "<userIds:string...>")
|
2019-04-13 01:44:18 +03:00
|
|
|
@d.permission("can_massban")
|
2018-08-16 20:07:43 +03:00
|
|
|
async massbanCmd(msg: Message, args: { userIds: string[] }) {
|
2018-08-18 13:34:57 +03:00
|
|
|
// Limit to 100 users at once (arbitrary?)
|
|
|
|
if (args.userIds.length > 100) {
|
|
|
|
msg.channel.createMessage(errorMessage(`Can only massban max 100 users at once`));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ask for ban reason (cleaner this way instead of trying to cram it into the args)
|
2018-08-16 20:07:43 +03:00
|
|
|
msg.channel.createMessage("Ban reason? `cancel` to cancel");
|
|
|
|
const banReasonReply = await waitForReply(this.bot, msg.channel as TextChannel, msg.author.id);
|
2018-08-18 13:34:57 +03:00
|
|
|
if (!banReasonReply || !banReasonReply.content || banReasonReply.content.toLowerCase().trim() === "cancel") {
|
2018-08-16 20:07:43 +03:00
|
|
|
msg.channel.createMessage("Cancelled");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-02-23 21:47:55 +02:00
|
|
|
const banReason = this.formatReasonWithAttachments(banReasonReply.content, msg.attachments);
|
2018-08-16 20:07:43 +03:00
|
|
|
|
2018-08-18 13:34:57 +03:00
|
|
|
// Verify we can act on each of the users specified
|
2018-08-16 20:07:43 +03:00
|
|
|
for (const userId of args.userIds) {
|
|
|
|
const member = this.guild.members.get(userId);
|
|
|
|
if (member && !this.canActOn(msg.member, member)) {
|
2018-08-18 13:34:57 +03:00
|
|
|
msg.channel.createMessage(errorMessage("Cannot massban one or more users: insufficient permissions"));
|
2018-08-16 20:07:43 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-18 13:34:57 +03:00
|
|
|
// Ignore automatic ban cases and logs for these users
|
|
|
|
// We'll create our own cases below and post a single "mass banned" log instead
|
2018-08-16 20:07:43 +03:00
|
|
|
args.userIds.forEach(userId => {
|
2018-08-18 13:34:57 +03:00
|
|
|
// Use longer timeouts since this can take a while
|
|
|
|
this.ignoreEvent(IgnoredEventType.Ban, userId, 120 * 1000);
|
|
|
|
this.serverLogs.ignoreLog(LogType.MEMBER_BAN, userId, 120 * 1000);
|
2018-08-16 20:07:43 +03:00
|
|
|
});
|
|
|
|
|
2018-08-18 13:34:57 +03:00
|
|
|
// Show a loading indicator since this can take a while
|
2018-08-16 20:07:43 +03:00
|
|
|
const loadingMsg = await msg.channel.createMessage("Banning...");
|
|
|
|
|
2018-08-18 13:34:57 +03:00
|
|
|
// Ban each user and count failed bans (if any)
|
2018-08-16 20:07:43 +03:00
|
|
|
const failedBans = [];
|
|
|
|
for (const userId of args.userIds) {
|
|
|
|
try {
|
|
|
|
await this.guild.banMember(userId);
|
2018-11-25 17:04:26 +02:00
|
|
|
|
|
|
|
await this.actions.fire("createCase", {
|
|
|
|
userId,
|
|
|
|
modId: msg.author.id,
|
|
|
|
type: CaseTypes.Ban,
|
|
|
|
reason: `Mass ban: ${banReason}`,
|
2019-02-15 05:07:28 +02:00
|
|
|
postInCaseLog: false,
|
2018-11-25 17:04:26 +02:00
|
|
|
});
|
2018-08-16 20:07:43 +03:00
|
|
|
} catch (e) {
|
|
|
|
failedBans.push(userId);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-18 13:34:57 +03:00
|
|
|
// Clear loading indicator
|
2018-08-16 20:07:43 +03:00
|
|
|
loadingMsg.delete();
|
|
|
|
|
|
|
|
const successfulBanCount = args.userIds.length - failedBans.length;
|
|
|
|
if (successfulBanCount === 0) {
|
2018-08-18 13:34:57 +03:00
|
|
|
// All bans failed - don't create a log entry and notify the user
|
2018-08-16 20:07:43 +03:00
|
|
|
msg.channel.createMessage(errorMessage("All bans failed. Make sure the IDs are valid."));
|
|
|
|
} else {
|
2018-08-18 13:34:57 +03:00
|
|
|
// Some or all bans were successful. Create a log entry for the mass ban and notify the user.
|
2018-08-16 20:07:43 +03:00
|
|
|
this.serverLogs.log(LogType.MASSBAN, {
|
|
|
|
mod: stripObjectToScalars(msg.author),
|
2019-02-15 05:07:28 +02:00
|
|
|
count: successfulBanCount,
|
2018-08-16 20:07:43 +03:00
|
|
|
});
|
|
|
|
|
|
|
|
if (failedBans.length) {
|
|
|
|
msg.channel.createMessage(
|
2019-02-15 05:07:28 +02:00
|
|
|
successMessage(`Banned ${successfulBanCount} users, ${failedBans.length} failed: ${failedBans.join(" ")}`),
|
2018-08-16 20:07:43 +03:00
|
|
|
);
|
|
|
|
} else {
|
2018-08-18 13:34:57 +03:00
|
|
|
msg.channel.createMessage(successMessage(`Banned ${successfulBanCount} users successfully`));
|
2018-08-16 20:07:43 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-07 20:36:57 +02:00
|
|
|
@d.command("addcase", "<type:string> <target:userId> [reason:string$]", {
|
2019-02-15 05:07:28 +02:00
|
|
|
options: [{ name: "mod", type: "member" }],
|
2019-02-07 20:36:57 +02:00
|
|
|
})
|
2019-04-13 01:44:18 +03:00
|
|
|
@d.permission("can_addcase")
|
2019-02-23 21:52:36 +02:00
|
|
|
async addcaseCmd(msg: Message, args: { type: string; target: string; reason?: string; mod?: Member }) {
|
2018-07-14 20:55:39 +03:00
|
|
|
// Verify the user id is a valid snowflake-ish
|
2018-07-30 01:44:03 +03:00
|
|
|
if (!args.target.match(/^[0-9]{17,20}$/)) {
|
2018-07-14 20:55:39 +03:00
|
|
|
msg.channel.createMessage(errorMessage("Cannot add case: invalid user id"));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the user exists as a guild member, make sure we can act on them first
|
2019-02-23 21:52:36 +02:00
|
|
|
const member = this.guild.members.get(args.target);
|
2018-07-14 20:55:39 +03:00
|
|
|
if (member && !this.canActOn(msg.member, member)) {
|
2018-08-18 13:34:57 +03:00
|
|
|
msg.channel.createMessage(errorMessage("Cannot add case on this user: insufficient permissions"));
|
2018-07-14 20:55:39 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-02-08 20:04:48 +02:00
|
|
|
// The moderator who did the action is the message author or, if used, the specified --mod
|
|
|
|
let mod = msg.member;
|
2019-02-07 20:36:57 +02:00
|
|
|
if (args.mod) {
|
2019-04-13 03:27:29 +03:00
|
|
|
if (!this.hasPermission("can_act_as_other", { message: msg })) {
|
2019-02-07 20:36:57 +02:00
|
|
|
msg.channel.createMessage(errorMessage("No permission for --mod"));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-02-08 20:04:48 +02:00
|
|
|
mod = args.mod;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Verify the case type is valid
|
|
|
|
const type: string = args.type[0].toUpperCase() + args.type.slice(1).toLowerCase();
|
|
|
|
if (!CaseTypes[type]) {
|
|
|
|
msg.channel.createMessage(errorMessage("Cannot add case: invalid case type"));
|
|
|
|
return;
|
2019-02-07 20:36:57 +02:00
|
|
|
}
|
|
|
|
|
2019-02-23 21:47:55 +02:00
|
|
|
const reason = this.formatReasonWithAttachments(args.reason, msg.attachments);
|
|
|
|
|
2018-07-14 20:55:39 +03:00
|
|
|
// Create the case
|
2018-11-25 17:04:26 +02:00
|
|
|
const theCase: Case = await this.actions.fire("createCase", {
|
|
|
|
userId: args.target,
|
2019-02-08 20:04:48 +02:00
|
|
|
modId: mod.id,
|
2018-11-25 17:04:26 +02:00
|
|
|
type: CaseTypes[type],
|
2019-02-23 21:47:55 +02:00
|
|
|
reason,
|
2019-02-15 05:07:28 +02:00
|
|
|
ppId: mod.id !== msg.author.id ? msg.author.id : null,
|
2018-11-25 17:04:26 +02:00
|
|
|
});
|
2018-07-14 20:55:39 +03:00
|
|
|
|
2019-02-23 21:52:36 +02:00
|
|
|
const user = member ? member.user : this.bot.users.get(args.target);
|
|
|
|
if (user) {
|
|
|
|
msg.channel.createMessage(
|
|
|
|
successMessage(`Case #${theCase.case_number} created for **${user.username}#${user.discriminator}**`),
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
msg.channel.createMessage(successMessage(`Case #${theCase.case_number} created`));
|
|
|
|
}
|
2018-11-25 17:04:26 +02:00
|
|
|
|
|
|
|
// Log the action
|
2018-07-14 20:55:39 +03:00
|
|
|
this.serverLogs.log(LogType.CASE_CREATE, {
|
2019-02-08 20:04:48 +02:00
|
|
|
mod: stripObjectToScalars(mod.user),
|
2019-02-23 21:52:36 +02:00
|
|
|
userId: args.target,
|
2018-07-29 18:46:49 +03:00
|
|
|
caseNum: theCase.case_number,
|
2019-02-15 05:07:28 +02:00
|
|
|
caseType: type.toUpperCase(),
|
2018-07-14 20:55:39 +03:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-07-01 03:35:51 +03:00
|
|
|
/**
|
|
|
|
* Display a case or list of cases
|
|
|
|
* If the argument passed is a case id, display that case
|
|
|
|
* If the argument passed is a user id, show all cases on that user
|
|
|
|
*/
|
2019-02-23 21:32:38 +02:00
|
|
|
@d.command("case", "<caseNumber:number>")
|
2019-04-13 01:44:18 +03:00
|
|
|
@d.permission("can_view")
|
2019-02-23 21:32:38 +02:00
|
|
|
async showCaseCmd(msg: Message, args: { caseNumber: number }) {
|
2018-08-02 00:51:25 +03:00
|
|
|
// Assume case id
|
|
|
|
const theCase = await this.cases.findByCaseNumber(args.caseNumber);
|
2018-07-01 03:35:51 +03:00
|
|
|
|
2018-08-02 00:51:25 +03:00
|
|
|
if (!theCase) {
|
2018-11-25 17:04:26 +02:00
|
|
|
msg.channel.createMessage(errorMessage("Case not found"));
|
2018-08-02 00:51:25 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-11-25 17:04:26 +02:00
|
|
|
await this.actions.fire("postCase", {
|
|
|
|
caseId: theCase.id,
|
2019-02-15 05:07:28 +02:00
|
|
|
channel: msg.channel,
|
2018-11-25 17:04:26 +02:00
|
|
|
});
|
2018-08-02 00:51:25 +03:00
|
|
|
}
|
|
|
|
|
2019-04-13 04:01:37 +03:00
|
|
|
@d.command("cases", "<userId:userId> [opts:string$]", {
|
|
|
|
options: [
|
|
|
|
{
|
|
|
|
name: "expand",
|
|
|
|
type: "boolean",
|
|
|
|
shortcut: "e",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "hidden",
|
|
|
|
type: "boolean",
|
|
|
|
shortcut: "h",
|
|
|
|
},
|
|
|
|
],
|
|
|
|
})
|
2019-04-13 01:44:18 +03:00
|
|
|
@d.permission("can_view")
|
2019-04-13 04:01:37 +03:00
|
|
|
async userCasesCmd(msg: Message, args: { userId: string; opts?: string; expand?: boolean; hidden?: boolean }) {
|
2018-10-26 06:41:20 +03:00
|
|
|
const cases = await this.cases.with("notes").getByUserId(args.userId);
|
2019-01-13 17:56:14 +02:00
|
|
|
const normalCases = cases.filter(c => !c.is_hidden);
|
|
|
|
const hiddenCases = cases.filter(c => c.is_hidden);
|
|
|
|
|
2019-04-14 13:30:48 +03:00
|
|
|
const user = this.bot.users.get(args.userId) || unknownUser;
|
|
|
|
const userName = `${user.username}#${user.discriminator}`;
|
2018-08-02 00:51:25 +03:00
|
|
|
|
|
|
|
if (cases.length === 0) {
|
2019-01-12 12:20:05 +02:00
|
|
|
msg.channel.createMessage(`No cases found for ${user ? `**${userName}**` : "the specified user"}`);
|
2018-08-02 00:51:25 +03:00
|
|
|
} else {
|
2019-04-13 04:01:37 +03:00
|
|
|
const showHidden = args.hidden || (args.opts && args.opts.match(/\bhidden\b/));
|
2019-01-13 17:56:14 +02:00
|
|
|
const casesToDisplay = showHidden ? cases : normalCases;
|
|
|
|
|
2019-04-13 04:01:37 +03:00
|
|
|
if (args.expand || (args.opts && args.opts.match(/\b(expand|e)\b/))) {
|
2019-01-13 17:56:14 +02:00
|
|
|
if (casesToDisplay.length > 8) {
|
2018-11-24 19:14:12 +02:00
|
|
|
msg.channel.createMessage("Too many cases for expanded view. Please use compact view instead.");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-08-02 03:01:22 +03:00
|
|
|
// Expanded view (= individual case embeds)
|
2019-01-13 17:56:14 +02:00
|
|
|
for (const theCase of casesToDisplay) {
|
2018-11-25 17:04:26 +02:00
|
|
|
await this.actions.fire("postCase", {
|
|
|
|
caseId: theCase.id,
|
2019-02-15 05:07:28 +02:00
|
|
|
channel: msg.channel,
|
2018-11-25 17:04:26 +02:00
|
|
|
});
|
2018-08-02 02:46:57 +03:00
|
|
|
}
|
2018-08-02 03:01:22 +03:00
|
|
|
} else {
|
|
|
|
// Compact view (= regular message with a preview of each case)
|
|
|
|
const lines = [];
|
2019-01-13 17:56:14 +02:00
|
|
|
for (const theCase of casesToDisplay) {
|
2018-10-26 06:41:20 +03:00
|
|
|
theCase.notes.sort((a, b) => (a.created_at > b.created_at ? 1 : -1));
|
2019-01-15 04:15:22 +02:00
|
|
|
const caseSummary = this.cases.getSummaryText(theCase);
|
|
|
|
lines.push(caseSummary);
|
2018-08-02 03:01:22 +03:00
|
|
|
}
|
2018-07-01 03:35:51 +03:00
|
|
|
|
2019-01-13 17:56:14 +02:00
|
|
|
if (!showHidden && hiddenCases.length) {
|
|
|
|
if (hiddenCases.length === 1) {
|
2019-04-13 04:01:37 +03:00
|
|
|
lines.push(`*+${hiddenCases.length} hidden case, use "--hidden" to show it*`);
|
2019-01-13 17:56:14 +02:00
|
|
|
} else {
|
2019-04-13 04:01:37 +03:00
|
|
|
lines.push(`*+${hiddenCases.length} hidden cases, use "--hidden" to show them*`);
|
2019-01-13 17:56:14 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-02 03:01:22 +03:00
|
|
|
const finalMessage = trimLines(`
|
2018-08-02 00:51:25 +03:00
|
|
|
Cases for **${userName}**:
|
2018-11-24 19:14:12 +02:00
|
|
|
|
2018-08-02 00:51:25 +03:00
|
|
|
${lines.join("\n")}
|
2018-11-24 19:14:12 +02:00
|
|
|
|
2019-02-23 21:33:57 +02:00
|
|
|
Use the \`case <num>\` command to see more info about individual cases
|
2018-08-02 00:51:25 +03:00
|
|
|
`);
|
|
|
|
|
2019-01-12 12:20:05 +02:00
|
|
|
createChunkedMessage(msg.channel, finalMessage);
|
2018-08-02 03:01:22 +03:00
|
|
|
}
|
2019-04-13 04:01:37 +03:00
|
|
|
|
|
|
|
if ((args.opts && args.opts.match(/\bhidden\b/)) || (args.opts && args.opts.match(/\b(expand|e)\b/))) {
|
|
|
|
msg.channel.createMessage(
|
|
|
|
`<@!${
|
|
|
|
msg.author.id
|
|
|
|
}> **Note:** expand/hidden have been replaced with --expand/--hidden (and -e/-h as shortcuts)`,
|
|
|
|
);
|
|
|
|
}
|
2018-07-01 03:35:51 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-23 22:40:43 +02:00
|
|
|
@d.command("cases", null, {
|
|
|
|
options: [{ name: "mod", type: "Member" }],
|
|
|
|
})
|
2019-04-13 01:44:18 +03:00
|
|
|
@d.permission("can_view")
|
2019-02-23 22:40:43 +02:00
|
|
|
async recentCasesCmd(msg: Message, args: { mod?: Member }) {
|
|
|
|
const modId = args.mod ? args.mod.id : msg.author.id;
|
|
|
|
const recentCases = await this.cases.with("notes").getRecentByModId(modId, 5);
|
|
|
|
|
|
|
|
const mod = this.bot.users.get(modId);
|
|
|
|
const modName = mod ? `${mod.username}#${mod.discriminator}` : modId;
|
|
|
|
|
2019-02-23 21:33:57 +02:00
|
|
|
if (recentCases.length === 0) {
|
2019-02-23 22:40:43 +02:00
|
|
|
msg.channel.createMessage(errorMessage(`No cases by **${modName}**`));
|
2019-02-23 21:33:57 +02:00
|
|
|
} else {
|
|
|
|
const lines = recentCases.map(c => this.cases.getSummaryText(c));
|
|
|
|
const finalMessage = trimLines(`
|
2019-02-23 22:40:43 +02:00
|
|
|
Most recent 5 cases by **${modName}**:
|
2019-02-23 21:33:57 +02:00
|
|
|
|
|
|
|
${lines.join("\n")}
|
|
|
|
|
|
|
|
Use the \`case <num>\` command to see more info about individual cases
|
|
|
|
Use the \`cases <user>\` command to see a specific user's cases
|
|
|
|
`);
|
|
|
|
createChunkedMessage(msg.channel, finalMessage);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-13 17:56:14 +02:00
|
|
|
@d.command("hidecase", "<caseNum:number>")
|
2019-04-13 01:44:18 +03:00
|
|
|
@d.permission("can_hidecase")
|
2019-01-13 17:56:14 +02:00
|
|
|
async hideCaseCmd(msg: Message, args: { caseNum: number }) {
|
|
|
|
const theCase = await this.cases.findByCaseNumber(args.caseNum);
|
|
|
|
if (!theCase) {
|
|
|
|
msg.channel.createMessage(errorMessage("Case not found!"));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
await this.cases.setHidden(theCase.id, true);
|
|
|
|
msg.channel.createMessage(
|
2019-02-15 05:07:28 +02:00
|
|
|
successMessage(`Case #${theCase.case_number} is now hidden! Use \`unhidecase\` to unhide it.`),
|
2019-01-13 17:56:14 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
@d.command("unhidecase", "<caseNum:number>")
|
2019-04-13 01:44:18 +03:00
|
|
|
@d.permission("can_hidecase")
|
2019-01-13 17:56:14 +02:00
|
|
|
async unhideCaseCmd(msg: Message, args: { caseNum: number }) {
|
|
|
|
const theCase = await this.cases.findByCaseNumber(args.caseNum);
|
|
|
|
if (!theCase) {
|
|
|
|
msg.channel.createMessage(errorMessage("Case not found!"));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
await this.cases.setHidden(theCase.id, false);
|
|
|
|
msg.channel.createMessage(successMessage(`Case #${theCase.case_number} is no longer hidden!`));
|
|
|
|
}
|
2018-07-01 03:35:51 +03:00
|
|
|
}
|