2018-07-29 23:30:24 +03:00
|
|
|
import { decorators as d, Plugin, waitForReaction } from "knub";
|
|
|
|
import { Constants as ErisConstants, Guild, Member, Message, TextChannel, User } from "eris";
|
2018-07-12 02:58:34 +03:00
|
|
|
import moment from "moment-timezone";
|
|
|
|
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 {
|
|
|
|
convertDelayStringToMS,
|
2018-08-02 00:51:25 +03:00
|
|
|
disableLinkPreviews,
|
2018-07-09 03:00:10 +03:00
|
|
|
errorMessage,
|
2018-07-29 18:46:49 +03:00
|
|
|
findRelevantAuditLogEntry,
|
2018-07-09 03:00:10 +03:00
|
|
|
formatTemplateString,
|
2018-07-29 23:30:24 +03:00
|
|
|
sleep,
|
2018-07-09 02:51:34 +03:00
|
|
|
stripObjectToScalars,
|
2018-08-02 00:51:25 +03:00
|
|
|
successMessage,
|
|
|
|
trimLines
|
2018-07-09 02:51:34 +03:00
|
|
|
} from "../utils";
|
2018-07-08 13:57:27 +03:00
|
|
|
import { GuildMutes } from "../data/GuildMutes";
|
2018-07-12 02:53:26 +03:00
|
|
|
import Case from "../models/Case";
|
|
|
|
import { CaseType } from "../data/CaseType";
|
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-07-29 23:30:24 +03:00
|
|
|
import Timer = NodeJS.Timer;
|
2018-08-02 01:15:05 +03:00
|
|
|
import { CaseTypeColors } from "../data/CaseTypeColors";
|
2018-07-01 03:35:51 +03:00
|
|
|
|
2018-07-31 04:02:45 +03:00
|
|
|
enum IgnoredEventType {
|
|
|
|
Ban = 1,
|
|
|
|
Unban,
|
|
|
|
Kick
|
|
|
|
}
|
|
|
|
|
|
|
|
interface IIgnoredEvent {
|
|
|
|
type: IgnoredEventType;
|
|
|
|
userId: string;
|
|
|
|
}
|
|
|
|
|
2018-08-02 02:46:57 +03:00
|
|
|
const CASE_LIST_REASON_MAX_LENGTH = 80;
|
|
|
|
|
2018-07-01 03:35:51 +03:00
|
|
|
export class ModActionsPlugin extends Plugin {
|
2018-07-12 02:53:26 +03:00
|
|
|
protected cases: GuildCases;
|
2018-07-08 13:57:27 +03:00
|
|
|
protected mutes: GuildMutes;
|
2018-07-29 18:46:49 +03:00
|
|
|
protected serverLogs: GuildLogs;
|
2018-07-09 02:51:34 +03:00
|
|
|
|
2018-07-08 13:57:27 +03:00
|
|
|
protected muteClearIntervalId: Timer;
|
2018-07-01 03:35:51 +03:00
|
|
|
|
2018-07-31 04:02:45 +03:00
|
|
|
protected ignoredEvents: IIgnoredEvent[];
|
|
|
|
|
2018-07-01 03:35:51 +03:00
|
|
|
async onLoad() {
|
2018-07-12 02:53:26 +03:00
|
|
|
this.cases = new GuildCases(this.guildId);
|
2018-07-08 13:57:27 +03:00
|
|
|
this.mutes = new GuildMutes(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-08 13:57:27 +03:00
|
|
|
// Check for expired mutes every 5s
|
|
|
|
this.clearExpiredMutes();
|
2018-07-12 01:45:26 +03:00
|
|
|
this.muteClearIntervalId = setInterval(() => this.clearExpiredMutes(), 5000);
|
2018-07-08 13:57:27 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
async onUnload() {
|
|
|
|
clearInterval(this.muteClearIntervalId);
|
2018-07-01 03:35:51 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
getDefaultOptions() {
|
|
|
|
return {
|
|
|
|
config: {
|
2018-07-08 13:57:27 +03:00
|
|
|
mute_role: null,
|
2018-07-01 03:35:51 +03:00
|
|
|
dm_on_warn: true,
|
2018-08-02 00:51:25 +03:00
|
|
|
dm_on_mute: false,
|
2018-07-01 03:35:51 +03:00
|
|
|
dm_on_kick: false,
|
|
|
|
dm_on_ban: false,
|
|
|
|
message_on_warn: false,
|
|
|
|
message_on_mute: 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
|
|
|
mute_message: "You have been muted on {guildName}. Reason given: {reason}",
|
|
|
|
timed_mute_message: "You have been muted on {guildName} for {time}. Reason given: {reason}",
|
|
|
|
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
|
|
|
log_automatic_actions: true,
|
2018-07-12 02:53:26 +03:00
|
|
|
case_log_channel: null,
|
2018-07-01 03:35:51 +03:00
|
|
|
alert_on_rejoin: false,
|
|
|
|
alert_channel: null
|
|
|
|
},
|
|
|
|
permissions: {
|
|
|
|
note: false,
|
|
|
|
warn: false,
|
|
|
|
mute: false,
|
|
|
|
kick: false,
|
|
|
|
ban: false,
|
2018-07-14 20:55:39 +03:00
|
|
|
view: false,
|
|
|
|
addcase: false
|
2018-07-01 03:35:51 +03:00
|
|
|
},
|
|
|
|
overrides: [
|
|
|
|
{
|
|
|
|
level: ">=50",
|
|
|
|
permissions: {
|
|
|
|
note: true,
|
|
|
|
warn: true,
|
|
|
|
mute: true,
|
|
|
|
kick: true,
|
|
|
|
ban: true,
|
2018-07-14 20:55:39 +03:00
|
|
|
view: true,
|
|
|
|
addcase: true
|
2018-07-01 03:35:51 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
]
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2018-07-31 04:02:45 +03:00
|
|
|
ignoreEvent(type: IgnoredEventType, userId: any) {
|
|
|
|
this.ignoredEvents.push({ type, userId });
|
|
|
|
|
|
|
|
// Clear after expiry (15sec by default)
|
|
|
|
setTimeout(() => {
|
|
|
|
this.clearIgnoredEvent(type, userId);
|
|
|
|
}, 1000 * 15);
|
|
|
|
}
|
|
|
|
|
|
|
|
isEventIgnored(type: IgnoredEventType, userId: any) {
|
|
|
|
return this.ignoredEvents.some(info => type === info.type && userId === info.userId);
|
|
|
|
}
|
|
|
|
|
|
|
|
clearIgnoredEvent(type: IgnoredEventType, userId: any) {
|
|
|
|
this.ignoredEvents.splice(
|
|
|
|
this.ignoredEvents.findIndex(info => type === info.type && userId === info.userId),
|
|
|
|
1
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
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,
|
2018-07-29 18:46:49 +03:00
|
|
|
user.id
|
|
|
|
);
|
2018-07-01 03:35:51 +03:00
|
|
|
|
|
|
|
if (relevantAuditLogEntry) {
|
|
|
|
const modId = relevantAuditLogEntry.user.id;
|
|
|
|
const auditLogId = relevantAuditLogEntry.id;
|
|
|
|
|
2018-07-12 02:53:26 +03:00
|
|
|
await this.createCase(
|
2018-07-01 03:35:51 +03:00
|
|
|
user.id,
|
|
|
|
modId,
|
2018-07-12 02:53:26 +03:00
|
|
|
CaseType.Ban,
|
2018-07-08 13:57:27 +03:00
|
|
|
auditLogId,
|
2018-07-09 02:51:34 +03:00
|
|
|
relevantAuditLogEntry.reason,
|
|
|
|
true
|
2018-07-01 03:35:51 +03:00
|
|
|
);
|
|
|
|
} else {
|
2018-07-12 02:53:26 +03:00
|
|
|
await this.createCase(user.id, null, CaseType.Ban);
|
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,
|
2018-07-01 03:35:51 +03:00
|
|
|
user.id
|
|
|
|
);
|
|
|
|
|
|
|
|
if (relevantAuditLogEntry) {
|
|
|
|
const modId = relevantAuditLogEntry.user.id;
|
|
|
|
const auditLogId = relevantAuditLogEntry.id;
|
|
|
|
|
2018-07-12 02:53:26 +03:00
|
|
|
await this.createCase(user.id, modId, CaseType.Unban, auditLogId, null, true);
|
2018-07-01 03:35:51 +03:00
|
|
|
} else {
|
2018-07-12 02:53:26 +03:00
|
|
|
await this.createCase(user.id, null, CaseType.Unban);
|
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) {
|
2018-07-01 04:31:24 +03:00
|
|
|
if (!this.configValue("alert_on_rejoin")) return;
|
2018-07-01 03:35:51 +03:00
|
|
|
|
2018-07-01 04:31:24 +03:00
|
|
|
const alertChannelId = this.configValue("alert_channel");
|
|
|
|
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-07-12 01:45:26 +03:00
|
|
|
`<@!${member.id}> (${member.user.username}#${member.user.discriminator} \`${
|
|
|
|
member.id
|
|
|
|
}\`) joined with ${actions.length} 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,
|
|
|
|
member.id
|
|
|
|
);
|
|
|
|
|
|
|
|
if (kickAuditLogEntry) {
|
|
|
|
this.createCase(
|
|
|
|
member.id,
|
|
|
|
kickAuditLogEntry.user.id,
|
|
|
|
CaseType.Kick,
|
|
|
|
kickAuditLogEntry.id,
|
|
|
|
kickAuditLogEntry.reason,
|
|
|
|
true
|
|
|
|
);
|
|
|
|
this.serverLogs.log(LogType.MEMBER_KICK, {
|
|
|
|
user: stripObjectToScalars(member.user),
|
|
|
|
mod: stripObjectToScalars(kickAuditLogEntry.user)
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-01 03:35:51 +03:00
|
|
|
/**
|
2018-07-08 13:57:27 +03:00
|
|
|
* Update the specified case by adding more notes/details to it
|
2018-07-01 03:35:51 +03:00
|
|
|
*/
|
2018-07-08 13:57:27 +03:00
|
|
|
@d.command(/update|updatecase/, "<caseNumber:number> <note:string$>")
|
2018-07-01 03:35:51 +03:00
|
|
|
@d.permission("note")
|
|
|
|
async updateCmd(msg: Message, args: any) {
|
2018-08-02 00:51:25 +03:00
|
|
|
const theCase = await this.cases.findByCaseNumber(args.caseNumber);
|
|
|
|
if (!theCase) {
|
2018-07-01 03:35:51 +03:00
|
|
|
msg.channel.createMessage("Case not found!");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-08-02 00:51:25 +03:00
|
|
|
if (theCase.mod_id === null) {
|
2018-07-01 03:35:51 +03:00
|
|
|
// If the action has no moderator information, assume the first one to update it did the action
|
2018-08-02 00:51:25 +03:00
|
|
|
await this.cases.update(theCase.id, {
|
2018-07-01 03:35:51 +03:00
|
|
|
mod_id: msg.author.id,
|
|
|
|
mod_name: `${msg.author.username}#${msg.author.discriminator}`
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-08-02 00:51:25 +03:00
|
|
|
await this.createCaseNote(theCase.id, msg.author.id, args.note);
|
|
|
|
this.postCaseToCaseLog(theCase.id); // Post updated case to case log
|
|
|
|
|
|
|
|
if (msg.channel.id !== this.configValue("case_log_channel")) {
|
|
|
|
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$>")
|
2018-07-01 03:35:51 +03:00
|
|
|
@d.permission("note")
|
|
|
|
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";
|
|
|
|
|
2018-07-12 02:53:26 +03:00
|
|
|
await this.createCase(args.userId, msg.author.id, CaseType.Note, null, args.note);
|
2018-08-02 00:51:25 +03:00
|
|
|
msg.channel.createMessage(successMessage(`Note added on ${userName}`));
|
2018-07-08 13:57:27 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
@d.command("warn", "<member:Member> <reason:string$>")
|
|
|
|
@d.permission("warn")
|
|
|
|
async warnCmd(msg: Message, args: any) {
|
2018-07-30 01:44:03 +03:00
|
|
|
// Make sure we're allowed to warn this member
|
|
|
|
if (!this.canActOn(msg.member, args.member)) {
|
|
|
|
msg.channel.createMessage(errorMessage("Cannot warn: insufficient permissions"));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-07-08 13:57:27 +03:00
|
|
|
const warnMessage = this.configValue("warn_message")
|
|
|
|
.replace("{guildName}", this.guild.name)
|
|
|
|
.replace("{reason}", args.reason);
|
2018-07-01 03:35:51 +03:00
|
|
|
|
2018-07-12 01:20:20 +03:00
|
|
|
const messageSent = await this.tryToMessageUser(
|
|
|
|
args.member.user,
|
|
|
|
warnMessage,
|
|
|
|
this.configValue("dm_on_warn"),
|
|
|
|
this.configValue("message_on_warn")
|
|
|
|
);
|
2018-07-08 13:57:27 +03:00
|
|
|
|
2018-07-12 01:20:20 +03:00
|
|
|
if (!messageSent) {
|
2018-07-09 02:51:34 +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-07-12 02:53:26 +03:00
|
|
|
await this.createCase(args.member.id, msg.author.id, CaseType.Warn, null, args.reason);
|
2018-07-08 13:57:27 +03:00
|
|
|
|
2018-08-02 00:51:25 +03:00
|
|
|
msg.channel.createMessage(
|
|
|
|
successMessage(`Warned **${args.member.user.username}#${args.member.user.discriminator}**`)
|
|
|
|
);
|
2018-07-09 02:51:34 +03:00
|
|
|
|
|
|
|
this.serverLogs.log(LogType.MEMBER_WARN, {
|
2018-07-29 23:30:24 +03:00
|
|
|
mod: stripObjectToScalars(msg.member.user),
|
2018-07-09 02:51:34 +03:00
|
|
|
member: stripObjectToScalars(args.member, ["user"])
|
|
|
|
});
|
2018-07-08 13:57:27 +03:00
|
|
|
}
|
|
|
|
|
2018-07-31 02:42:45 +03:00
|
|
|
public async muteMember(member: Member, muteTime: number = null, reason: string = null) {
|
2018-08-01 00:52:44 +03:00
|
|
|
await member.addRole(this.configValue("mute_role"));
|
2018-07-31 02:42:45 +03:00
|
|
|
await this.mutes.addOrUpdateMute(member.id, muteTime);
|
|
|
|
}
|
|
|
|
|
2018-07-08 13:57:27 +03:00
|
|
|
@d.command("mute", "<member:Member> [time:string] [reason:string$]")
|
|
|
|
@d.permission("mute")
|
|
|
|
async muteCmd(msg: Message, args: any) {
|
|
|
|
if (!this.configValue("mute_role")) {
|
2018-07-12 01:45:26 +03:00
|
|
|
msg.channel.createMessage(errorMessage("Cannot mute: no mute role specified"));
|
2018-07-08 13:57:27 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Make sure we're allowed to mute this member
|
2018-07-12 01:42:18 +03:00
|
|
|
if (!this.canActOn(msg.member, args.member)) {
|
|
|
|
msg.channel.createMessage(errorMessage("Cannot mute: insufficient permissions"));
|
|
|
|
return;
|
2018-07-08 13:57:27 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Convert mute time from e.g. "2h30m" to milliseconds
|
|
|
|
const muteTime = args.time ? convertDelayStringToMS(args.time) : null;
|
2018-07-12 01:38:58 +03:00
|
|
|
const timeUntilUnmute = muteTime && humanizeDuration(muteTime);
|
|
|
|
|
2018-07-08 13:57:27 +03:00
|
|
|
if (muteTime == null && args.time) {
|
|
|
|
// Invalid muteTime -> assume it's actually part of the reason
|
|
|
|
args.reason = `${args.time} ${args.reason ? args.reason : ""}`.trim();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Apply "muted" role
|
2018-07-29 18:46:49 +03:00
|
|
|
this.serverLogs.ignoreLog(LogType.MEMBER_ROLE_ADD, args.member.id);
|
2018-07-31 02:42:45 +03:00
|
|
|
this.muteMember(args.member, muteTime, args.reason);
|
2018-07-08 13:57:27 +03:00
|
|
|
|
2018-07-13 00:10:20 +03:00
|
|
|
// Create a case
|
2018-07-12 02:53:26 +03:00
|
|
|
await this.createCase(args.member.id, msg.author.id, CaseType.Mute, null, args.reason);
|
2018-07-08 13:57:27 +03:00
|
|
|
|
|
|
|
// Message the user informing them of the mute
|
2018-07-12 01:38:58 +03:00
|
|
|
let messageSent = true;
|
2018-07-08 13:57:27 +03:00
|
|
|
if (args.reason) {
|
2018-07-12 01:38:58 +03:00
|
|
|
const template = muteTime
|
|
|
|
? this.configValue("timed_mute_message")
|
|
|
|
: this.configValue("mute_message");
|
|
|
|
|
2018-07-12 01:45:26 +03:00
|
|
|
const muteMessage = formatTemplateString(template, {
|
|
|
|
guildName: this.guild.name,
|
|
|
|
reason: args.reason,
|
|
|
|
time: timeUntilUnmute
|
|
|
|
});
|
2018-07-08 13:57:27 +03:00
|
|
|
|
2018-07-12 01:20:20 +03:00
|
|
|
messageSent = await this.tryToMessageUser(
|
|
|
|
args.member.user,
|
|
|
|
muteMessage,
|
|
|
|
this.configValue("dm_on_mute"),
|
|
|
|
this.configValue("message_on_mute")
|
|
|
|
);
|
2018-07-08 13:57:27 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Confirm the action to the moderator
|
2018-07-12 01:20:20 +03:00
|
|
|
let response;
|
2018-07-08 13:57:27 +03:00
|
|
|
if (muteTime) {
|
2018-08-02 00:51:25 +03:00
|
|
|
response = `Muted **${args.member.user.username}#${
|
|
|
|
args.member.user.discriminator
|
|
|
|
}** for ${timeUntilUnmute}`;
|
2018-07-08 13:57:27 +03:00
|
|
|
} else {
|
2018-08-02 00:51:25 +03:00
|
|
|
response = `Muted **${args.member.user.username}#${
|
|
|
|
args.member.user.discriminator
|
|
|
|
}** indefinitely`;
|
2018-07-08 13:57:27 +03:00
|
|
|
}
|
2018-07-09 03:13:31 +03:00
|
|
|
|
2018-07-12 01:20:20 +03:00
|
|
|
if (!messageSent) response += " (failed to message user)";
|
|
|
|
msg.channel.createMessage(successMessage(response));
|
|
|
|
|
2018-07-12 02:03:07 +03:00
|
|
|
// Log the action
|
2018-07-09 03:13:31 +03:00
|
|
|
this.serverLogs.log(LogType.MEMBER_MUTE, {
|
2018-07-29 23:30:24 +03:00
|
|
|
mod: stripObjectToScalars(msg.member.user),
|
2018-07-09 03:13:31 +03:00
|
|
|
member: stripObjectToScalars(args.member, ["user"])
|
|
|
|
});
|
2018-07-01 03:35:51 +03:00
|
|
|
}
|
|
|
|
|
2018-07-13 00:10:20 +03:00
|
|
|
@d.command("unmute", "<member:Member> [reason:string$]")
|
|
|
|
@d.permission("mute")
|
|
|
|
async unmuteCmd(msg: Message, args: any) {
|
|
|
|
if (!this.configValue("mute_role")) {
|
|
|
|
msg.channel.createMessage(errorMessage("Cannot unmute: no mute role specified"));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Make sure we're allowed to mute this member
|
|
|
|
if (!this.canActOn(msg.member, args.member)) {
|
|
|
|
msg.channel.createMessage(errorMessage("Cannot unmute: insufficient permissions"));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if they're muted in the first place
|
|
|
|
const mute = await this.mutes.findExistingMuteForUserId(args.member.id);
|
|
|
|
if (!mute) {
|
|
|
|
msg.channel.createMessage(errorMessage("Cannot unmute: member is not muted"));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove "muted" role
|
2018-07-29 18:46:49 +03:00
|
|
|
this.serverLogs.ignoreLog(LogType.MEMBER_ROLE_REMOVE, args.member.id);
|
2018-08-01 00:52:44 +03:00
|
|
|
await args.member.removeRole(this.configValue("mute_role"));
|
2018-07-13 00:10:20 +03:00
|
|
|
await this.mutes.clear(args.member.id);
|
|
|
|
|
|
|
|
// Confirm the action to the moderator
|
2018-08-02 00:51:25 +03:00
|
|
|
msg.channel.createMessage(
|
|
|
|
successMessage(`Unmuted **${args.member.user.username}#${args.member.user.discriminator}**`)
|
|
|
|
);
|
2018-07-13 00:10:20 +03:00
|
|
|
|
|
|
|
// Create a case
|
|
|
|
await this.createCase(args.member.id, msg.author.id, CaseType.Unmute, null, args.reason);
|
|
|
|
|
|
|
|
// Log the action
|
|
|
|
this.serverLogs.log(LogType.MEMBER_UNMUTE, {
|
2018-07-29 23:30:24 +03:00
|
|
|
mod: stripObjectToScalars(msg.member.user),
|
2018-07-13 00:10:20 +03:00
|
|
|
member: stripObjectToScalars(args.member, ["user"])
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-07-12 02:03:22 +03:00
|
|
|
@d.command("kick", "<member:Member> [reason:string$]")
|
|
|
|
@d.permission("kick")
|
|
|
|
async kickCmd(msg, args) {
|
|
|
|
// Make sure we're allowed to kick this member
|
|
|
|
if (!this.canActOn(msg.member, args.member)) {
|
|
|
|
msg.channel.createMessage(errorMessage("Cannot kick: insufficient permissions"));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Attempt to message the user *before* kicking them, as doing it after may not be possible
|
|
|
|
let messageSent = true;
|
|
|
|
if (args.reason) {
|
|
|
|
const kickMessage = formatTemplateString(this.configValue("kick_message"), {
|
|
|
|
guildName: this.guild.name,
|
|
|
|
reason: args.reason
|
|
|
|
});
|
|
|
|
|
|
|
|
messageSent = await this.tryToMessageUser(
|
|
|
|
args.member.user,
|
|
|
|
kickMessage,
|
|
|
|
this.configValue("dm_on_kick"),
|
|
|
|
this.configValue("message_on_kick")
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Kick the user
|
2018-07-29 18:46:49 +03:00
|
|
|
this.serverLogs.ignoreLog(LogType.MEMBER_KICK, args.member.id);
|
2018-07-31 04:02:45 +03:00
|
|
|
this.ignoreEvent(IgnoredEventType.Kick, args.member.id);
|
2018-07-12 02:03:22 +03:00
|
|
|
args.member.kick(args.reason);
|
|
|
|
|
|
|
|
// Create a case for this action
|
2018-07-12 02:53:26 +03:00
|
|
|
await this.createCase(args.member.id, msg.author.id, CaseType.Kick, null, args.reason);
|
2018-07-12 02:03:22 +03:00
|
|
|
|
|
|
|
// Confirm the action to the moderator
|
2018-08-02 00:51:25 +03:00
|
|
|
let response = `Kicked **${args.member.user.username}#${args.member.user.discriminator}**`;
|
2018-07-12 02:03:22 +03:00
|
|
|
if (!messageSent) response += " (failed to message user)";
|
|
|
|
msg.channel.createMessage(successMessage(response));
|
|
|
|
|
|
|
|
// Log the action
|
|
|
|
this.serverLogs.log(LogType.MEMBER_KICK, {
|
2018-07-29 23:30:24 +03:00
|
|
|
mod: stripObjectToScalars(msg.member.user),
|
2018-07-12 02:03:22 +03:00
|
|
|
member: stripObjectToScalars(args.member, ["user"])
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
@d.command("ban", "<member:Member> [reason:string$]")
|
|
|
|
@d.permission("ban")
|
|
|
|
async banCmd(msg, args) {
|
|
|
|
// Make sure we're allowed to ban this member
|
|
|
|
if (!this.canActOn(msg.member, args.member)) {
|
|
|
|
msg.channel.createMessage(errorMessage("Cannot ban: insufficient permissions"));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Attempt to message the user *before* banning them, as doing it after may not be possible
|
|
|
|
let messageSent = true;
|
|
|
|
if (args.reason) {
|
|
|
|
const banMessage = formatTemplateString(this.configValue("ban_message"), {
|
|
|
|
guildName: this.guild.name,
|
|
|
|
reason: args.reason
|
|
|
|
});
|
|
|
|
|
|
|
|
messageSent = await this.tryToMessageUser(
|
|
|
|
args.member.user,
|
|
|
|
banMessage,
|
|
|
|
this.configValue("dm_on_ban"),
|
|
|
|
this.configValue("message_on_ban")
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ban the user
|
2018-07-29 18:46:49 +03:00
|
|
|
this.serverLogs.ignoreLog(LogType.MEMBER_BAN, args.member.id);
|
2018-07-31 04:02:45 +03:00
|
|
|
this.ignoreEvent(IgnoredEventType.Ban, args.member.id);
|
2018-07-12 02:03:22 +03:00
|
|
|
args.member.ban(1, args.reason);
|
|
|
|
|
|
|
|
// Create a case for this action
|
2018-07-12 02:53:26 +03:00
|
|
|
await this.createCase(args.member.id, msg.author.id, CaseType.Ban, null, args.reason);
|
2018-07-12 02:03:22 +03:00
|
|
|
|
|
|
|
// Confirm the action to the moderator
|
2018-08-02 00:51:25 +03:00
|
|
|
let response = `Banned **${args.member.user.username}#${args.member.user.discriminator}**`;
|
2018-07-12 02:03:22 +03:00
|
|
|
if (!messageSent) response += " (failed to message user)";
|
|
|
|
msg.channel.createMessage(successMessage(response));
|
|
|
|
|
|
|
|
// Log the action
|
|
|
|
this.serverLogs.log(LogType.MEMBER_BAN, {
|
2018-07-29 23:30:24 +03:00
|
|
|
mod: stripObjectToScalars(msg.member.user),
|
2018-07-12 02:03:22 +03:00
|
|
|
member: stripObjectToScalars(args.member, ["user"])
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-08-02 00:51:25 +03:00
|
|
|
@d.command("softban", "<member:Member> [reason:string$]")
|
|
|
|
@d.permission("ban")
|
|
|
|
async softbanCmd(msg, args) {
|
|
|
|
// Make sure we're allowed to ban this member
|
|
|
|
if (!this.canActOn(msg.member, args.member)) {
|
|
|
|
msg.channel.createMessage(errorMessage("Cannot ban: insufficient permissions"));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Softban the user = ban, and immediately unban
|
|
|
|
this.serverLogs.ignoreLog(LogType.MEMBER_BAN, args.member.id);
|
|
|
|
this.serverLogs.ignoreLog(LogType.MEMBER_UNBAN, args.member.id);
|
|
|
|
this.ignoreEvent(IgnoredEventType.Ban, args.member.id);
|
|
|
|
this.ignoreEvent(IgnoredEventType.Unban, args.member.id);
|
|
|
|
|
|
|
|
await args.member.ban(1, args.reason);
|
|
|
|
await this.guild.unbanMember(args.member.id);
|
|
|
|
|
|
|
|
// Create a case for this action
|
|
|
|
await this.createCase(args.member.id, msg.author.id, CaseType.Softban, null, args.reason);
|
|
|
|
|
|
|
|
// Confirm the action to the moderator
|
|
|
|
msg.channel.createMessage(
|
|
|
|
successMessage(
|
|
|
|
`Softbanned **${args.member.user.username}#${args.member.user.discriminator}**`
|
|
|
|
)
|
|
|
|
);
|
|
|
|
|
|
|
|
// Log the action
|
|
|
|
this.serverLogs.log(LogType.MEMBER_SOFTBAN, {
|
|
|
|
mod: stripObjectToScalars(msg.member.user),
|
|
|
|
member: stripObjectToScalars(args.member, ["user"])
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
@d.command("unban", "<userId:userId> [reason:string$]")
|
2018-07-14 20:55:39 +03:00
|
|
|
@d.permission("ban")
|
|
|
|
async unbanCmd(msg: Message, args: any) {
|
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) {
|
|
|
|
msg.channel.createMessage(errorMessage("Failed to unban member"));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Confirm the action
|
|
|
|
msg.channel.createMessage(successMessage("Member unbanned!"));
|
|
|
|
|
|
|
|
// Create a case
|
|
|
|
this.createCase(args.userId, msg.author.id, CaseType.Unban, null, args.reason);
|
|
|
|
|
|
|
|
// Log the action
|
|
|
|
this.serverLogs.log(LogType.MEMBER_UNBAN, {
|
2018-07-29 23:30:24 +03:00
|
|
|
mod: stripObjectToScalars(msg.member.user),
|
2018-07-14 20:55:39 +03:00
|
|
|
userId: args.userId
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-08-02 00:51:25 +03:00
|
|
|
@d.command("forceban", "<userId:userId> [reason:string$]")
|
2018-07-14 20:55:39 +03:00
|
|
|
@d.permission("ban")
|
|
|
|
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)) {
|
|
|
|
msg.channel.createMessage(
|
|
|
|
errorMessage("Cannot forceban this user: insufficient permissions")
|
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
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 {
|
|
|
|
await this.guild.banMember(args.userId, 1, args.reason);
|
|
|
|
} catch (e) {
|
|
|
|
msg.channel.createMessage(errorMessage("Failed to forceban member"));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Confirm the action
|
|
|
|
msg.channel.createMessage(successMessage("Member forcebanned!"));
|
|
|
|
|
|
|
|
// Create a case
|
|
|
|
this.createCase(args.userId, msg.author.id, CaseType.Ban, null, args.reason);
|
|
|
|
|
|
|
|
// Log the action
|
|
|
|
this.serverLogs.log(LogType.MEMBER_FORCEBAN, {
|
2018-07-29 23:30:24 +03:00
|
|
|
mod: stripObjectToScalars(msg.member.user),
|
2018-07-14 20:55:39 +03:00
|
|
|
userId: args.userId
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-08-02 00:51:25 +03:00
|
|
|
@d.command("addcase", "<type:string> <target:userId> [reason:string$]")
|
2018-07-14 20:55:39 +03:00
|
|
|
@d.permission("addcase")
|
|
|
|
async addcaseCmd(msg: Message, args: any) {
|
|
|
|
// 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
|
|
|
|
const member = this.guild.members.get(args.userId);
|
|
|
|
if (member && !this.canActOn(msg.member, member)) {
|
|
|
|
msg.channel.createMessage(
|
|
|
|
errorMessage("Cannot add case on this user: insufficient permissions")
|
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Verify the case type is valid
|
|
|
|
const type: string = args.type[0].toUpperCase() + args.type.slice(1).toLowerCase();
|
|
|
|
if (!CaseType[type]) {
|
|
|
|
msg.channel.createMessage(errorMessage("Cannot add case: invalid case type"));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create the case
|
2018-07-29 18:46:49 +03:00
|
|
|
const caseId = await this.createCase(
|
2018-07-30 01:44:03 +03:00
|
|
|
args.target,
|
2018-07-29 18:46:49 +03:00
|
|
|
msg.author.id,
|
|
|
|
CaseType[type],
|
|
|
|
null,
|
|
|
|
args.reason
|
|
|
|
);
|
|
|
|
const theCase = await this.cases.find(caseId);
|
2018-07-14 20:55:39 +03:00
|
|
|
|
|
|
|
// Log the action
|
|
|
|
msg.channel.createMessage(successMessage("Case created!"));
|
|
|
|
this.serverLogs.log(LogType.CASE_CREATE, {
|
2018-07-29 23:30:24 +03:00
|
|
|
mod: stripObjectToScalars(msg.member.user),
|
2018-07-29 18:46:49 +03:00
|
|
|
userId: args.userId,
|
|
|
|
caseNum: theCase.case_number,
|
|
|
|
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
|
|
|
|
*/
|
2018-08-02 00:51:25 +03:00
|
|
|
@d.command(/showcase|case/, "<caseNumber:number>")
|
2018-07-01 03:35:51 +03:00
|
|
|
@d.permission("view")
|
2018-08-02 00:51:25 +03:00
|
|
|
async showcaseCmd(msg: Message, args: { caseNumber: number }) {
|
|
|
|
// 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) {
|
|
|
|
msg.channel.createMessage("Case not found!");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.displayCase(theCase.id, msg.channel.id);
|
|
|
|
}
|
|
|
|
|
|
|
|
@d.command(/cases|usercases/, "<userId:userId>")
|
|
|
|
@d.permission("view")
|
|
|
|
async usercasesCmd(msg: Message, args: { userId: string }) {
|
|
|
|
const cases = await this.cases.getByUserId(args.userId);
|
|
|
|
const user = this.bot.users.get(args.userId);
|
|
|
|
const userName = user ? `${user.username}#${user.discriminator}` : "Unknown#0000";
|
|
|
|
const prefix = this.knub.getGuildData(this.guildId).config.prefix;
|
|
|
|
|
|
|
|
if (cases.length === 0) {
|
|
|
|
msg.channel.createMessage("No cases found for the specified user!");
|
|
|
|
} else {
|
|
|
|
const lines = [];
|
|
|
|
for (const theCase of cases) {
|
|
|
|
const firstNote = await this.cases.findFirstCaseNote(theCase.id);
|
2018-08-02 02:46:57 +03:00
|
|
|
let reason = firstNote ? firstNote.body : "";
|
|
|
|
|
|
|
|
if (reason.length > CASE_LIST_REASON_MAX_LENGTH) {
|
|
|
|
const match = reason.slice(CASE_LIST_REASON_MAX_LENGTH, 20).match(/(?:[.,!?\s]|$)/);
|
|
|
|
const nextWhitespaceIndex = match
|
|
|
|
? CASE_LIST_REASON_MAX_LENGTH + match.index
|
|
|
|
: CASE_LIST_REASON_MAX_LENGTH;
|
|
|
|
if (nextWhitespaceIndex < reason.length) {
|
|
|
|
reason = reason.slice(0, nextWhitespaceIndex - 1) + "...";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
reason = disableLinkPreviews(reason);
|
|
|
|
|
2018-08-02 00:51:25 +03:00
|
|
|
lines.push(`Case \`#${theCase.case_number}\` __${CaseType[theCase.type]}__ ${reason}`);
|
2018-07-01 03:35:51 +03:00
|
|
|
}
|
|
|
|
|
2018-08-02 00:51:25 +03:00
|
|
|
const finalMessage = trimLines(`
|
|
|
|
Cases for **${userName}**:
|
|
|
|
|
|
|
|
${lines.join("\n")}
|
|
|
|
|
|
|
|
Use \`${prefix}case <num>\` to see more info about individual cases
|
|
|
|
`);
|
|
|
|
|
|
|
|
msg.channel.createMessage(finalMessage);
|
2018-07-01 03:35:51 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-12 01:42:18 +03:00
|
|
|
protected canActOn(member1, member2) {
|
|
|
|
if (member1.id === member2.id) {
|
2018-07-12 01:59:13 +03:00
|
|
|
return false;
|
2018-07-12 01:42:18 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
const ourLevel = this.getMemberLevel(member1);
|
|
|
|
const memberLevel = this.getMemberLevel(member2);
|
2018-07-12 01:45:26 +03:00
|
|
|
return ourLevel > memberLevel;
|
2018-07-12 01:42:18 +03:00
|
|
|
}
|
|
|
|
|
2018-07-12 01:20:20 +03:00
|
|
|
/**
|
|
|
|
* Attempts to message the specified user through DMs and/or the message channel.
|
|
|
|
* Returns a promise that resolves to a boolean indicating whether we were able to message them or not.
|
|
|
|
*/
|
|
|
|
protected async tryToMessageUser(
|
|
|
|
user: User,
|
|
|
|
str: string,
|
|
|
|
useDM: boolean,
|
|
|
|
useChannel: boolean
|
|
|
|
): Promise<boolean> {
|
|
|
|
let messageSent = false;
|
|
|
|
|
2018-07-12 01:43:11 +03:00
|
|
|
if (!useDM && !useChannel) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-07-12 01:20:20 +03:00
|
|
|
if (useDM) {
|
|
|
|
try {
|
|
|
|
const dmChannel = await this.bot.getDMChannel(user.id);
|
|
|
|
await dmChannel.createMessage(str);
|
|
|
|
messageSent = true;
|
|
|
|
} catch (e) {} // tslint:disable-line
|
|
|
|
}
|
|
|
|
|
|
|
|
if (useChannel && this.configValue("message_channel")) {
|
|
|
|
try {
|
2018-07-12 01:45:26 +03:00
|
|
|
const channel = this.guild.channels.get(this.configValue("message_channel")) as TextChannel;
|
2018-07-12 01:20:20 +03:00
|
|
|
await channel.createMessage(`<@!${user.id}> ${str}`);
|
|
|
|
messageSent = true;
|
|
|
|
} catch (e) {} // tslint:disable-line
|
|
|
|
}
|
|
|
|
|
|
|
|
return messageSent;
|
|
|
|
}
|
|
|
|
|
2018-07-01 03:35:51 +03:00
|
|
|
/**
|
|
|
|
* Shows information about the specified action in a message embed.
|
|
|
|
* If no channelId is specified, uses the channel id from config.
|
|
|
|
*/
|
2018-07-12 02:53:26 +03:00
|
|
|
protected async displayCase(caseOrCaseId: Case | number, channelId: string) {
|
|
|
|
let theCase: Case;
|
|
|
|
if (typeof caseOrCaseId === "number") {
|
|
|
|
theCase = await this.cases.find(caseOrCaseId);
|
2018-07-01 03:35:51 +03:00
|
|
|
} else {
|
2018-07-12 02:53:26 +03:00
|
|
|
theCase = caseOrCaseId;
|
2018-07-01 03:35:51 +03:00
|
|
|
}
|
|
|
|
|
2018-07-12 02:53:26 +03:00
|
|
|
if (!theCase) return;
|
2018-07-08 13:57:27 +03:00
|
|
|
if (!this.guild.channels.get(channelId)) return;
|
2018-07-01 03:35:51 +03:00
|
|
|
|
2018-07-12 02:53:26 +03:00
|
|
|
const notes = await this.cases.getCaseNotes(theCase.id);
|
2018-07-01 03:35:51 +03:00
|
|
|
|
2018-07-12 02:53:26 +03:00
|
|
|
const createdAt = moment(theCase.created_at);
|
|
|
|
const actionTypeStr = CaseType[theCase.type].toUpperCase();
|
2018-07-01 03:35:51 +03:00
|
|
|
|
|
|
|
const embed: any = {
|
2018-07-12 02:53:26 +03:00
|
|
|
title: `${actionTypeStr} - Case #${theCase.case_number}`,
|
2018-07-01 03:35:51 +03:00
|
|
|
footer: {
|
|
|
|
text: `Case created at ${createdAt.format("YYYY-MM-DD [at] HH:mm")}`
|
|
|
|
},
|
|
|
|
fields: [
|
|
|
|
{
|
|
|
|
name: "User",
|
2018-07-12 02:53:26 +03:00
|
|
|
value: `${theCase.user_name}\n<@!${theCase.user_id}>`,
|
2018-07-01 03:35:51 +03:00
|
|
|
inline: true
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Moderator",
|
2018-07-12 02:53:26 +03:00
|
|
|
value: `${theCase.mod_name}\n<@!${theCase.mod_id}>`,
|
2018-07-01 03:35:51 +03:00
|
|
|
inline: true
|
|
|
|
}
|
|
|
|
]
|
|
|
|
};
|
|
|
|
|
2018-08-02 01:15:05 +03:00
|
|
|
if (CaseTypeColors[theCase.type]) {
|
|
|
|
embed.color = CaseTypeColors[theCase.type];
|
2018-07-01 03:35:51 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if (notes.length) {
|
|
|
|
notes.forEach((note: any) => {
|
|
|
|
const noteDate = moment(note.created_at);
|
2018-07-01 04:31:24 +03:00
|
|
|
embed.fields.push({
|
2018-07-12 01:45:26 +03:00
|
|
|
name: `${note.mod_name} at ${noteDate.format("YYYY-MM-DD [at] HH:mm")}:`,
|
2018-07-01 04:31:24 +03:00
|
|
|
value: note.body
|
|
|
|
});
|
2018-07-01 03:35:51 +03:00
|
|
|
});
|
|
|
|
} else {
|
2018-07-08 13:57:27 +03:00
|
|
|
embed.fields.push({
|
|
|
|
name: "!!! THIS CASE HAS NO NOTES !!!",
|
|
|
|
value: "\u200B"
|
|
|
|
});
|
2018-07-01 03:35:51 +03:00
|
|
|
}
|
|
|
|
|
2018-07-12 02:53:26 +03:00
|
|
|
const channel = this.guild.channels.get(channelId) as TextChannel;
|
|
|
|
channel.createMessage({ embed });
|
2018-07-01 03:35:51 +03:00
|
|
|
}
|
|
|
|
|
2018-07-08 13:57:27 +03:00
|
|
|
/**
|
|
|
|
* Posts the specified mod action to the guild's action log channel
|
|
|
|
*/
|
2018-07-12 02:53:26 +03:00
|
|
|
protected async postCaseToCaseLog(caseOrCaseId: Case | number) {
|
|
|
|
const caseLogChannelId = this.configValue("case_log_channel");
|
|
|
|
if (!caseLogChannelId) return;
|
|
|
|
if (!this.guild.channels.get(caseLogChannelId)) return;
|
2018-07-08 13:57:27 +03:00
|
|
|
|
2018-07-12 02:53:26 +03:00
|
|
|
return this.displayCase(caseOrCaseId, caseLogChannelId);
|
2018-07-08 13:57:27 +03:00
|
|
|
}
|
|
|
|
|
2018-07-31 02:42:45 +03:00
|
|
|
public async createCase(
|
2018-07-01 03:35:51 +03:00
|
|
|
userId: string,
|
|
|
|
modId: string,
|
2018-07-12 02:53:26 +03:00
|
|
|
caseType: CaseType,
|
2018-07-08 13:57:27 +03:00
|
|
|
auditLogId: string = null,
|
2018-07-09 02:51:34 +03:00
|
|
|
reason: string = null,
|
|
|
|
automatic = false
|
2018-07-01 03:35:51 +03:00
|
|
|
): Promise<number> {
|
|
|
|
const user = this.bot.users.get(userId);
|
2018-07-12 01:45:26 +03:00
|
|
|
const userName = user ? `${user.username}#${user.discriminator}` : "Unknown#0000";
|
2018-07-01 03:35:51 +03:00
|
|
|
|
|
|
|
const mod = this.bot.users.get(modId);
|
2018-07-12 01:45:26 +03:00
|
|
|
const modName = mod ? `${mod.username}#${mod.discriminator}` : "Unknown#0000";
|
2018-07-01 03:35:51 +03:00
|
|
|
|
2018-07-12 02:53:26 +03:00
|
|
|
const createdId = await this.cases.create({
|
|
|
|
type: caseType,
|
2018-07-01 03:35:51 +03:00
|
|
|
user_id: userId,
|
|
|
|
user_name: userName,
|
|
|
|
mod_id: modId,
|
|
|
|
mod_name: modName,
|
|
|
|
audit_log_id: auditLogId
|
|
|
|
});
|
2018-07-08 13:57:27 +03:00
|
|
|
|
|
|
|
if (reason) {
|
2018-07-12 02:53:26 +03:00
|
|
|
await this.createCaseNote(createdId, modId, reason);
|
2018-07-08 13:57:27 +03:00
|
|
|
}
|
|
|
|
|
2018-07-09 02:51:34 +03:00
|
|
|
if (
|
2018-07-12 02:53:26 +03:00
|
|
|
this.configValue("case_log_channel") &&
|
2018-07-09 02:51:34 +03:00
|
|
|
(!automatic || this.configValue("log_automatic_actions"))
|
|
|
|
) {
|
2018-07-08 13:57:27 +03:00
|
|
|
try {
|
2018-07-12 02:53:26 +03:00
|
|
|
await this.postCaseToCaseLog(createdId);
|
2018-07-08 13:57:27 +03:00
|
|
|
} catch (e) {} // tslint:disable-line
|
|
|
|
}
|
|
|
|
|
|
|
|
return createdId;
|
2018-07-01 03:35:51 +03:00
|
|
|
}
|
|
|
|
|
2018-07-12 02:53:26 +03:00
|
|
|
protected async createCaseNote(caseId: number, modId: string, body: string) {
|
2018-07-01 03:35:51 +03:00
|
|
|
const mod = this.bot.users.get(modId);
|
2018-07-12 01:45:26 +03:00
|
|
|
const modName = mod ? `${mod.username}#${mod.discriminator}` : "Unknown#0000";
|
2018-07-01 03:35:51 +03:00
|
|
|
|
2018-07-12 02:53:26 +03:00
|
|
|
return this.cases.createNote(caseId, {
|
2018-07-01 03:35:51 +03:00
|
|
|
mod_id: modId,
|
|
|
|
mod_name: modName,
|
2018-07-08 13:57:27 +03:00
|
|
|
body: body || ""
|
2018-07-01 03:35:51 +03:00
|
|
|
});
|
|
|
|
}
|
2018-07-08 13:57:27 +03:00
|
|
|
|
|
|
|
protected async clearExpiredMutes() {
|
|
|
|
const expiredMutes = await this.mutes.getExpiredMutes();
|
|
|
|
for (const mute of expiredMutes) {
|
|
|
|
const member = this.guild.members.get(mute.user_id);
|
|
|
|
if (!member) continue;
|
|
|
|
|
|
|
|
try {
|
2018-07-31 02:42:45 +03:00
|
|
|
this.serverLogs.ignoreLog(LogType.MEMBER_ROLE_REMOVE, member.id);
|
2018-07-08 13:57:27 +03:00
|
|
|
await member.removeRole(this.configValue("mute_role"));
|
|
|
|
} catch (e) {} // tslint:disable-line
|
|
|
|
|
|
|
|
await this.mutes.clear(member.id);
|
2018-07-09 03:13:31 +03:00
|
|
|
|
2018-07-13 00:11:51 +03:00
|
|
|
this.serverLogs.log(LogType.MEMBER_MUTE_EXPIRED, {
|
2018-07-09 03:13:31 +03:00
|
|
|
member: stripObjectToScalars(member, ["user"])
|
|
|
|
});
|
2018-07-08 13:57:27 +03:00
|
|
|
}
|
|
|
|
}
|
2018-07-01 03:35:51 +03:00
|
|
|
}
|