mirror of
https://github.com/ZeppelinBot/Zeppelin.git
synced 2025-03-14 21:31:50 +00:00
Work on better error messages for mod actions, allow muting users that are not on the server; WIP
This commit is contained in:
parent
3643c319d4
commit
f3e6c05c67
9 changed files with 272 additions and 125 deletions
12
package-lock.json
generated
12
package-lock.json
generated
|
@ -5373,9 +5373,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"knub": {
|
||||
"version": "19.3.0",
|
||||
"resolved": "https://registry.npmjs.org/knub/-/knub-19.3.0.tgz",
|
||||
"integrity": "sha512-/nOE0bE6a/3GtGLpozhiAulmF1j4MLwpK+s0/PbXBacl/MTbFAoTdND8Nv1GzgTj1PNf+jxVULDuKjFYwytrVA==",
|
||||
"version": "19.3.2",
|
||||
"resolved": "https://registry.npmjs.org/knub/-/knub-19.3.2.tgz",
|
||||
"integrity": "sha512-JFN1XvSSkTYOsTyws+RSWE4pas5yLh1uff6k4fTm62P+KVssAVwBdX0Sw/YvYOo413u2ZSgNrnpIvmpPAWV9Rg==",
|
||||
"requires": {
|
||||
"escape-string-regexp": "^1.0.5",
|
||||
"lodash.at": "^4.6.0",
|
||||
|
@ -7768,9 +7768,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"ts-essentials": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-2.0.3.tgz",
|
||||
"integrity": "sha512-cPF62miIiZf5Q+L1YTKBGk/xIKqDPVm6p+NmaYK5LFaR9Y6Y3gMOJoNXCbXnYJOqnmw5jfF9XJLkT9+vXlPe+g=="
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-2.0.4.tgz",
|
||||
"integrity": "sha512-LVtnDkVXrFCPpGG5vShEYXwxO22Snawu7qF7DBKHW9fTQ/NibBg9zLyf4ipeQzNGR/MRuIUisXcdZQD7FGhaKw=="
|
||||
},
|
||||
"ts-node": {
|
||||
"version": "3.3.0",
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
"escape-string-regexp": "^1.0.5",
|
||||
"humanize-duration": "^3.15.0",
|
||||
"js-yaml": "^3.13.1",
|
||||
"knub": "^19.3.0",
|
||||
"knub": "^19.3.2",
|
||||
"lodash.at": "^4.6.0",
|
||||
"lodash.chunk": "^4.2.0",
|
||||
"lodash.difference": "^4.5.0",
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
{
|
||||
"MEMBER_WARN": "⚠️ {userMention(member)} was warned by {userMention(mod)}",
|
||||
"MEMBER_MUTE": "🔇 {userMention(member)} was muted indefinitely by {userMention(mod)}",
|
||||
"MEMBER_TIMED_MUTE": "🔇 {userMention(member)} was muted for **{time}** by {userMention(mod)}",
|
||||
"MEMBER_UNMUTE": "🔊 {userMention(member)} was unmuted by {userMention(mod)}",
|
||||
"MEMBER_MUTE": "🔇 {userMention(user)} was muted indefinitely by {userMention(mod)}",
|
||||
"MEMBER_TIMED_MUTE": "🔇 {userMention(user)} was muted for **{time}** by {userMention(mod)}",
|
||||
"MEMBER_UNMUTE": "🔊 {userMention(user)} was unmuted by {userMention(mod)}",
|
||||
"MEMBER_TIMED_UNMUTE": "🔊 {userMention(member)} was scheduled to be unmuted in **{time}** by {userMention(mod)}",
|
||||
"MEMBER_MUTE_EXPIRED": "🔊 {userMention(member)}'s mute expired",
|
||||
"MEMBER_KICK": "👢 {userMention(user)} was kicked by {userMention(mod)}",
|
||||
|
@ -17,7 +17,7 @@
|
|||
"MEMBER_ROLE_CHANGES": "🔑 {userMention(member)}: roles changed: added **{addedRoles}**, removed **{removedRoles}** by {userMention(mod)}",
|
||||
"MEMBER_NICK_CHANGE": "✏ {userMention(member)}: nickname changed from **{oldNick}** to **{newNick}**",
|
||||
"MEMBER_USERNAME_CHANGE": "✏ {userMention(member)}: username changed from **{oldName}** to **{newName}**",
|
||||
"MEMBER_RESTORE": "💿 {userMention(member)} was restored",
|
||||
"MEMBER_RESTORE": "💿 Restored {restoredData} for {userMention(member)} on rejoin",
|
||||
|
||||
"CHANNEL_CREATE": "🖊 Channel {channelMention(channel)} was created",
|
||||
"CHANNEL_DELETE": "🗑 Channel {channelMention(channel)} was deleted",
|
||||
|
@ -50,5 +50,7 @@
|
|||
|
||||
"MEMBER_JOIN_WITH_PRIOR_RECORDS": "⚠ {userMention(member)} joined with prior records. Recent cases:\n{recentCaseSummary}",
|
||||
|
||||
"CASE_UPDATE": "✏ {userMention(mod)} updated case #{caseNumber} ({caseType}) with note:\n```{note}```"
|
||||
"CASE_UPDATE": "✏ {userMention(mod)} updated case #{caseNumber} ({caseType}) with note:\n```{note}```",
|
||||
|
||||
"MEMBER_MUTE_REJOIN": "⚠ Reapplied active mute for {userMention(member)} on rejoin"
|
||||
}
|
||||
|
|
|
@ -12,8 +12,8 @@ type UnknownAction<T extends string> = T extends KnownActions ? never : T;
|
|||
|
||||
type ActionFn<T> = (args: T) => any | Promise<any>;
|
||||
|
||||
type MuteActionArgs = { member: Member; muteTime?: number; reason?: string; caseDetails?: ICaseDetails };
|
||||
type UnmuteActionArgs = { member: Member; unmuteTime?: number; reason?: string; caseDetails?: ICaseDetails };
|
||||
type MuteActionArgs = { userId: string; muteTime?: number; reason?: string; caseDetails?: ICaseDetails };
|
||||
type UnmuteActionArgs = { userId: string; unmuteTime?: number; reason?: string; caseDetails?: ICaseDetails };
|
||||
type CreateCaseActionArgs = ICaseDetails;
|
||||
type CreateCaseNoteActionArgs = {
|
||||
caseId: number;
|
||||
|
|
|
@ -51,4 +51,6 @@ export enum LogType {
|
|||
VOICE_CHANNEL_FORCE_MOVE,
|
||||
|
||||
CASE_UPDATE,
|
||||
|
||||
MEMBER_MUTE_REJOIN,
|
||||
}
|
||||
|
|
|
@ -155,6 +155,24 @@ export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
|||
return ((reason || "") + " " + attachmentUrls.join(" ")).trim();
|
||||
}
|
||||
|
||||
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 };
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a BAN action automatically when a user is banned.
|
||||
* Attempts to find the ban's details in the audit log.
|
||||
|
@ -345,13 +363,25 @@ export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
|||
msg.channel.createMessage(successMessage(`Note added on **${userName}** (Case #${createdCase.case_number})`));
|
||||
}
|
||||
|
||||
@d.command("warn", "<member:Member> <reason:string$>", {
|
||||
@d.command("warn", "<userId:string> <reason:string$>", {
|
||||
options: [{ name: "mod", type: "member" }],
|
||||
})
|
||||
@d.permission("can_warn")
|
||||
async warnCmd(msg: Message, args: any) {
|
||||
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;
|
||||
}
|
||||
|
||||
// Make sure we're allowed to warn this member
|
||||
if (!this.canActOn(msg.member, args.member)) {
|
||||
if (!this.canActOn(msg.member, memberToWarn)) {
|
||||
msg.channel.createMessage(errorMessage("Cannot warn: insufficient permissions"));
|
||||
return;
|
||||
}
|
||||
|
@ -372,7 +402,7 @@ export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
|||
|
||||
const warnMessage = config.warn_message.replace("{guildName}", this.guild.name).replace("{reason}", reason);
|
||||
|
||||
const userMessageResult = await notifyUser(this.bot, this.guild, args.member.user, warnMessage, {
|
||||
const userMessageResult = await notifyUser(this.bot, this.guild, memberToWarn.user, warnMessage, {
|
||||
useDM: config.dm_on_warn,
|
||||
useChannel: config.message_on_warn,
|
||||
});
|
||||
|
@ -387,7 +417,7 @@ export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
|||
}
|
||||
|
||||
const createdCase: Case = await this.actions.fire("createCase", {
|
||||
userId: args.member.id,
|
||||
userId: memberToWarn.id,
|
||||
modId: mod.id,
|
||||
type: CaseTypes.Warn,
|
||||
reason,
|
||||
|
@ -399,7 +429,7 @@ export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
|||
|
||||
msg.channel.createMessage(
|
||||
successMessage(
|
||||
`Warned **${args.member.user.username}#${args.member.user.discriminator}** (Case #${
|
||||
`Warned **${memberToWarn.user.username}#${memberToWarn.user.discriminator}** (Case #${
|
||||
createdCase.case_number
|
||||
})${messageResultText}`,
|
||||
),
|
||||
|
@ -407,18 +437,33 @@ export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
|||
|
||||
this.serverLogs.log(LogType.MEMBER_WARN, {
|
||||
mod: stripObjectToScalars(mod.user),
|
||||
member: stripObjectToScalars(args.member, ["user"]),
|
||||
member: stripObjectToScalars(memberToWarn, ["user"]),
|
||||
});
|
||||
}
|
||||
|
||||
@d.command("mute", "<member:Member> <time:delay> <reason:string$>", {
|
||||
overloads: ["<member:Member> <time:delay>", "<member:Member> [reason:string$]"],
|
||||
@d.command("mute", "<userId:userId> <time:delay> <reason:string$>", {
|
||||
overloads: ["<userId:userId> <time:delay>", "<userId:userId> [reason:string$]"],
|
||||
options: [{ name: "mod", type: "member" }],
|
||||
})
|
||||
@d.permission("can_mute")
|
||||
async muteCmd(msg: Message, args: { member: Member; time?: number; reason?: string; mod: Member }) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure we're allowed to mute this member
|
||||
if (!this.canActOn(msg.member, args.member)) {
|
||||
if (memberToMute && !this.canActOn(msg.member, memberToMute)) {
|
||||
msg.channel.createMessage(errorMessage("Cannot mute: insufficient permissions"));
|
||||
return;
|
||||
}
|
||||
|
@ -444,7 +489,7 @@ export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
|||
|
||||
try {
|
||||
muteResult = await this.actions.fire("mute", {
|
||||
member: args.member,
|
||||
userId: user.id,
|
||||
muteTime: args.time,
|
||||
reason,
|
||||
caseDetails: {
|
||||
|
@ -453,7 +498,7 @@ export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
|||
},
|
||||
});
|
||||
} catch (e) {
|
||||
logger.error(`Failed to mute user ${args.member.id}: ${e.message}`);
|
||||
logger.error(`Failed to mute user ${user.id}: ${e.stack}`);
|
||||
msg.channel.createMessage(errorMessage("Could not mute the user"));
|
||||
return;
|
||||
}
|
||||
|
@ -463,24 +508,24 @@ export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
|||
if (args.time) {
|
||||
if (muteResult.updatedExistingMute) {
|
||||
response = asSingleLine(`
|
||||
Updated **${args.member.user.username}#${args.member.user.discriminator}**'s
|
||||
Updated **${user.username}#${user.discriminator}**'s
|
||||
mute to ${timeUntilUnmute} (Case #${muteResult.case.case_number})
|
||||
`);
|
||||
} else {
|
||||
response = asSingleLine(`
|
||||
Muted **${args.member.user.username}#${args.member.user.discriminator}**
|
||||
Muted **${user.username}#${user.discriminator}**
|
||||
for ${timeUntilUnmute} (Case #${muteResult.case.case_number})
|
||||
`);
|
||||
}
|
||||
} else {
|
||||
if (muteResult.updatedExistingMute) {
|
||||
response = asSingleLine(`
|
||||
Updated **${args.member.user.username}#${args.member.user.discriminator}**'s
|
||||
Updated **${user.username}#${user.discriminator}**'s
|
||||
mute to indefinite (Case #${muteResult.case.case_number})
|
||||
`);
|
||||
} else {
|
||||
response = asSingleLine(`
|
||||
Muted **${args.member.user.username}#${args.member.user.discriminator}**
|
||||
Muted **${user.username}#${user.discriminator}**
|
||||
indefinitely (Case #${muteResult.case.case_number})
|
||||
`);
|
||||
}
|
||||
|
@ -490,14 +535,36 @@ export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
|||
msg.channel.createMessage(successMessage(response));
|
||||
}
|
||||
|
||||
@d.command("unmute", "<member:Member> <time:delay> <reason:string$>", {
|
||||
overloads: ["<member:Member> <time:delay>", "<member:Member> [reason:string$]"],
|
||||
@d.command("unmute", "<userId:userId> <time:delay> <reason:string$>", {
|
||||
overloads: ["<userId:userId> <time:delay>", "<userId:userId> [reason:string$]"],
|
||||
options: [{ name: "mod", type: "member" }],
|
||||
})
|
||||
@d.permission("can_mute")
|
||||
async unmuteCmd(msg: Message, args: { member: Member; time?: number; reason?: string; mod?: Member }) {
|
||||
// Make sure we're allowed to mute this member
|
||||
if (!this.canActOn(msg.member, args.member)) {
|
||||
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)) {
|
||||
msg.channel.createMessage(errorMessage("Cannot unmute: insufficient permissions"));
|
||||
return;
|
||||
}
|
||||
|
@ -516,16 +583,10 @@ export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
|||
pp = msg.author;
|
||||
}
|
||||
|
||||
// Check if they're muted in the first place
|
||||
if (!(await this.mutes.isMuted(args.member.id))) {
|
||||
msg.channel.createMessage(errorMessage("Cannot unmute: member is not muted"));
|
||||
return;
|
||||
}
|
||||
|
||||
const reason = this.formatReasonWithAttachments(args.reason, msg.attachments);
|
||||
|
||||
const result = await this.actions.fire("unmute", {
|
||||
member: args.member,
|
||||
userId: user.id,
|
||||
unmuteTime: args.time,
|
||||
caseDetails: {
|
||||
modId: mod.id,
|
||||
|
@ -540,7 +601,7 @@ export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
|||
msg.channel.createMessage(
|
||||
successMessage(
|
||||
asSingleLine(`
|
||||
Unmuting **${args.member.user.username}#${args.member.user.discriminator}**
|
||||
Unmuting **${user.username}#${user.discriminator}**
|
||||
in ${timeUntilUnmute} (Case #${result.case.case_number})
|
||||
`),
|
||||
),
|
||||
|
@ -549,7 +610,7 @@ export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
|||
msg.channel.createMessage(
|
||||
successMessage(
|
||||
asSingleLine(`
|
||||
Unmuted **${args.member.user.username}#${args.member.user.discriminator}**
|
||||
Unmuted **${user.username}#${user.discriminator}**
|
||||
(Case #${result.case.case_number})
|
||||
`),
|
||||
),
|
||||
|
@ -557,13 +618,25 @@ export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
|||
}
|
||||
}
|
||||
|
||||
@d.command("kick", "<member:Member> [reason:string$]", {
|
||||
@d.command("kick", "<userId:userId> [reason:string$]", {
|
||||
options: [{ name: "mod", type: "member" }],
|
||||
})
|
||||
@d.permission("can_kick")
|
||||
async kickCmd(msg, args: { member: Member; reason: string; mod: Member }) {
|
||||
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;
|
||||
}
|
||||
|
||||
// Make sure we're allowed to kick this member
|
||||
if (!this.canActOn(msg.member, args.member)) {
|
||||
if (!this.canActOn(msg.member, memberToKick)) {
|
||||
msg.channel.createMessage(errorMessage("Cannot kick: insufficient permissions"));
|
||||
return;
|
||||
}
|
||||
|
@ -590,7 +663,7 @@ export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
|||
reason,
|
||||
});
|
||||
|
||||
userMessageResult = await notifyUser(this.bot, this.guild, args.member.user, kickMessage, {
|
||||
userMessageResult = await notifyUser(this.bot, this.guild, memberToKick.user, kickMessage, {
|
||||
useDM: config.dm_on_kick,
|
||||
useChannel: config.message_on_kick,
|
||||
channelId: config.message_channel,
|
||||
|
@ -598,13 +671,13 @@ export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
|||
}
|
||||
|
||||
// Kick the user
|
||||
this.serverLogs.ignoreLog(LogType.MEMBER_KICK, args.member.id);
|
||||
this.ignoreEvent(IgnoredEventType.Kick, args.member.id);
|
||||
args.member.kick(reason);
|
||||
this.serverLogs.ignoreLog(LogType.MEMBER_KICK, memberToKick.id);
|
||||
this.ignoreEvent(IgnoredEventType.Kick, memberToKick.id);
|
||||
memberToKick.kick(reason);
|
||||
|
||||
// Create a case for this action
|
||||
const createdCase = await this.actions.fire("createCase", {
|
||||
userId: args.member.id,
|
||||
userId: memberToKick.id,
|
||||
modId: mod.id,
|
||||
type: CaseTypes.Kick,
|
||||
reason,
|
||||
|
@ -613,7 +686,7 @@ export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
|||
});
|
||||
|
||||
// Confirm the action to the moderator
|
||||
let response = `Kicked **${args.member.user.username}#${args.member.user.discriminator}** (Case #${
|
||||
let response = `Kicked **${memberToKick.user.username}#${memberToKick.user.discriminator}** (Case #${
|
||||
createdCase.case_number
|
||||
})`;
|
||||
|
||||
|
@ -623,17 +696,29 @@ export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
|||
// Log the action
|
||||
this.serverLogs.log(LogType.MEMBER_KICK, {
|
||||
mod: stripObjectToScalars(mod.user),
|
||||
user: stripObjectToScalars(args.member.user),
|
||||
user: stripObjectToScalars(memberToKick.user),
|
||||
});
|
||||
}
|
||||
|
||||
@d.command("ban", "<member:Member> [reason:string$]", {
|
||||
@d.command("ban", "<userId:userId> [reason:string$]", {
|
||||
options: [{ name: "mod", type: "member" }],
|
||||
})
|
||||
@d.permission("can_ban")
|
||||
async banCmd(msg, args: { member: Member; reason?: string; mod?: Member }) {
|
||||
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;
|
||||
}
|
||||
|
||||
// Make sure we're allowed to ban this member
|
||||
if (!this.canActOn(msg.member, args.member)) {
|
||||
if (!this.canActOn(msg.member, memberToBan)) {
|
||||
msg.channel.createMessage(errorMessage("Cannot ban: insufficient permissions"));
|
||||
return;
|
||||
}
|
||||
|
@ -660,7 +745,7 @@ export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
|||
reason,
|
||||
});
|
||||
|
||||
userMessageResult = await notifyUser(this.bot, this.guild, args.member.user, banMessage, {
|
||||
userMessageResult = await notifyUser(this.bot, this.guild, memberToBan.user, banMessage, {
|
||||
useDM: config.dm_on_ban,
|
||||
useChannel: config.message_on_ban,
|
||||
channelId: config.message_channel,
|
||||
|
@ -668,13 +753,13 @@ export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
|||
}
|
||||
|
||||
// Ban the user
|
||||
this.serverLogs.ignoreLog(LogType.MEMBER_BAN, args.member.id);
|
||||
this.ignoreEvent(IgnoredEventType.Ban, args.member.id);
|
||||
args.member.ban(1, reason);
|
||||
this.serverLogs.ignoreLog(LogType.MEMBER_BAN, memberToBan.id);
|
||||
this.ignoreEvent(IgnoredEventType.Ban, memberToBan.id);
|
||||
memberToBan.ban(1, reason);
|
||||
|
||||
// Create a case for this action
|
||||
const createdCase = await this.actions.fire("createCase", {
|
||||
userId: args.member.id,
|
||||
userId: memberToBan.id,
|
||||
modId: mod.id,
|
||||
type: CaseTypes.Ban,
|
||||
reason,
|
||||
|
@ -683,7 +768,7 @@ export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
|||
});
|
||||
|
||||
// Confirm the action to the moderator
|
||||
let response = `Banned **${args.member.user.username}#${args.member.user.discriminator}** (Case #${
|
||||
let response = `Banned **${memberToBan.user.username}#${memberToBan.user.discriminator}** (Case #${
|
||||
createdCase.case_number
|
||||
})`;
|
||||
|
||||
|
@ -693,17 +778,29 @@ export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
|||
// Log the action
|
||||
this.serverLogs.log(LogType.MEMBER_BAN, {
|
||||
mod: stripObjectToScalars(mod.user),
|
||||
user: stripObjectToScalars(args.member.user),
|
||||
user: stripObjectToScalars(memberToBan.user),
|
||||
});
|
||||
}
|
||||
|
||||
@d.command("softban", "<member:Member> [reason:string$]", {
|
||||
@d.command("softban", "<userId:userId> [reason:string$]", {
|
||||
options: [{ name: "mod", type: "member" }],
|
||||
})
|
||||
@d.permission("can_ban")
|
||||
async softbanCmd(msg, args) {
|
||||
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;
|
||||
}
|
||||
|
||||
// Make sure we're allowed to ban this member
|
||||
if (!this.canActOn(msg.member, args.member)) {
|
||||
if (!this.canActOn(msg.member, memberToSoftban)) {
|
||||
msg.channel.createMessage(errorMessage("Cannot ban: insufficient permissions"));
|
||||
return;
|
||||
}
|
||||
|
@ -722,17 +819,17 @@ export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
|||
const reason = this.formatReasonWithAttachments(args.reason, msg.attachments);
|
||||
|
||||
// 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);
|
||||
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);
|
||||
|
||||
await args.member.ban(1, reason);
|
||||
await this.guild.unbanMember(args.member.id);
|
||||
await memberToSoftban.ban(1, reason);
|
||||
await this.guild.unbanMember(memberToSoftban.id);
|
||||
|
||||
// Create a case for this action
|
||||
const createdCase = await this.actions.fire("createCase", {
|
||||
userId: args.member.id,
|
||||
userId: memberToSoftban.id,
|
||||
modId: mod.id,
|
||||
type: CaseTypes.Softban,
|
||||
reason,
|
||||
|
@ -742,7 +839,7 @@ export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
|||
// Confirm the action to the moderator
|
||||
msg.channel.createMessage(
|
||||
successMessage(
|
||||
`Softbanned **${args.member.user.username}#${args.member.user.discriminator}** (Case #${
|
||||
`Softbanned **${memberToSoftban.user.username}#${memberToSoftban.user.discriminator}** (Case #${
|
||||
createdCase.case_number
|
||||
})`,
|
||||
),
|
||||
|
@ -751,7 +848,7 @@ export class ModActionsPlugin extends ZeppelinPlugin<IModActionsPluginConfig> {
|
|||
// Log the action
|
||||
this.serverLogs.log(LogType.MEMBER_SOFTBAN, {
|
||||
mod: stripObjectToScalars(mod.user),
|
||||
member: stripObjectToScalars(args.member, ["user"]),
|
||||
member: stripObjectToScalars(memberToSoftban, ["user"]),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
stripObjectToScalars,
|
||||
successMessage,
|
||||
ucfirst,
|
||||
unknownUser,
|
||||
} from "../utils";
|
||||
import humanizeDuration from "humanize-duration";
|
||||
import { LogType } from "../data/LogType";
|
||||
|
@ -90,10 +91,10 @@ export class MutesPlugin extends ZeppelinPlugin<IMutesPluginConfig> {
|
|||
this.serverLogs = new GuildLogs(this.guildId);
|
||||
|
||||
this.actions.register("mute", args => {
|
||||
return this.muteMember(args.member, args.muteTime, args.reason, args.caseDetails);
|
||||
return this.muteUser(args.userId, args.muteTime, args.reason, args.caseDetails);
|
||||
});
|
||||
this.actions.register("unmute", args => {
|
||||
return this.unmuteMember(args.member, args.unmuteTime, args.caseDetails);
|
||||
return this.unmuteUser(args.userId, args.unmuteTime, args.caseDetails);
|
||||
});
|
||||
|
||||
// Check for expired mutes every 5s
|
||||
|
@ -108,8 +109,21 @@ export class MutesPlugin extends ZeppelinPlugin<IMutesPluginConfig> {
|
|||
clearInterval(this.muteClearIntervalId);
|
||||
}
|
||||
|
||||
public async muteMember(
|
||||
member: Member,
|
||||
async resolveMember(userId: string) {
|
||||
if (this.guild.members.has(userId)) {
|
||||
return this.guild.members.get(userId);
|
||||
}
|
||||
|
||||
try {
|
||||
const member = await this.bot.getRESTGuildMember(this.guildId, userId);
|
||||
return member;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async muteUser(
|
||||
userId: string,
|
||||
muteTime: number = null,
|
||||
reason: string = null,
|
||||
caseDetails: ICaseDetails = {},
|
||||
|
@ -124,32 +138,37 @@ export class MutesPlugin extends ZeppelinPlugin<IMutesPluginConfig> {
|
|||
caseDetails.modId = this.bot.user.id;
|
||||
}
|
||||
|
||||
// Apply mute role if it's missing
|
||||
if (!member.roles.includes(muteRole)) {
|
||||
await member.addRole(muteRole);
|
||||
}
|
||||
const user = this.bot.users.get(userId) || { ...unknownUser, id: userId };
|
||||
const member = await this.resolveMember(userId);
|
||||
|
||||
// If enabled, move the user to the mute voice channel (e.g. afk - just to apply the voice perms from the mute role)
|
||||
const moveToVoiceChannelId = this.getConfig().move_to_voice_channel;
|
||||
if (moveToVoiceChannelId && member.voiceState.channelID) {
|
||||
try {
|
||||
await member.edit({ channelID: moveToVoiceChannelId });
|
||||
} catch (e) {
|
||||
logger.warn(`Could not move user ${member.id} to voice channel ${moveToVoiceChannelId} when muting`);
|
||||
if (member) {
|
||||
// Apply mute role if it's missing
|
||||
if (!member.roles.includes(muteRole)) {
|
||||
await member.addRole(muteRole);
|
||||
}
|
||||
|
||||
// If enabled, move the user to the mute voice channel (e.g. afk - just to apply the voice perms from the mute role)
|
||||
const moveToVoiceChannelId = this.getConfig().move_to_voice_channel;
|
||||
if (moveToVoiceChannelId && member.voiceState.channelID) {
|
||||
try {
|
||||
await member.edit({ channelID: moveToVoiceChannelId });
|
||||
} catch (e) {
|
||||
logger.warn(`Could not move user ${member.id} to voice channel ${moveToVoiceChannelId} when muting`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the user is already muted, update the duration of their existing mute
|
||||
const existingMute = await this.mutes.findExistingMuteForUserId(member.id);
|
||||
const existingMute = await this.mutes.findExistingMuteForUserId(user.id);
|
||||
let notifyResult: INotifyUserResult = { status: NotifyUserStatus.Ignored };
|
||||
|
||||
if (existingMute) {
|
||||
await this.mutes.updateExpiryTime(member.id, muteTime);
|
||||
await this.mutes.updateExpiryTime(user.id, muteTime);
|
||||
} else {
|
||||
await this.mutes.addMute(member.id, muteTime);
|
||||
await this.mutes.addMute(user.id, muteTime);
|
||||
|
||||
// If it's a new mute, attempt to message the user
|
||||
const config = this.getMatchingConfig({ member });
|
||||
const config = this.getMatchingConfig({ member, userId });
|
||||
const template = muteTime ? config.timed_mute_message : config.mute_message;
|
||||
|
||||
const muteMessage =
|
||||
|
@ -160,12 +179,16 @@ export class MutesPlugin extends ZeppelinPlugin<IMutesPluginConfig> {
|
|||
time: timeUntilUnmute,
|
||||
}));
|
||||
|
||||
if (muteMessage) {
|
||||
notifyResult = await notifyUser(this.bot, this.guild, member.user, muteMessage, {
|
||||
useDM: config.dm_on_mute,
|
||||
useChannel: config.message_on_mute,
|
||||
channelId: config.message_channel,
|
||||
});
|
||||
if (reason && muteMessage) {
|
||||
if (user instanceof User) {
|
||||
notifyResult = await notifyUser(this.bot, this.guild, user, muteMessage, {
|
||||
useDM: config.dm_on_mute,
|
||||
useChannel: config.message_on_mute,
|
||||
channelId: config.message_channel,
|
||||
});
|
||||
} else {
|
||||
notifyResult = { status: NotifyUserStatus.Failed };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -189,7 +212,7 @@ export class MutesPlugin extends ZeppelinPlugin<IMutesPluginConfig> {
|
|||
}
|
||||
|
||||
theCase = await this.actions.fire("createCase", {
|
||||
userId: member.id,
|
||||
userId,
|
||||
modId: caseDetails.modId,
|
||||
type: CaseTypes.Mute,
|
||||
reason,
|
||||
|
@ -197,20 +220,21 @@ export class MutesPlugin extends ZeppelinPlugin<IMutesPluginConfig> {
|
|||
noteDetails,
|
||||
extraNotes: caseDetails.extraNotes,
|
||||
});
|
||||
await this.mutes.setCaseId(member.id, theCase.id);
|
||||
await this.mutes.setCaseId(user.id, theCase.id);
|
||||
}
|
||||
|
||||
// Log the action
|
||||
const mod = this.bot.users.get(caseDetails.modId) || { ...unknownUser, id: caseDetails.modId };
|
||||
if (muteTime) {
|
||||
this.serverLogs.log(LogType.MEMBER_TIMED_MUTE, {
|
||||
mod: stripObjectToScalars(caseDetails.modId),
|
||||
member: stripObjectToScalars(member, ["user"]),
|
||||
mod: stripObjectToScalars(mod),
|
||||
user: stripObjectToScalars(user),
|
||||
time: timeUntilUnmute,
|
||||
});
|
||||
} else {
|
||||
this.serverLogs.log(LogType.MEMBER_MUTE, {
|
||||
mod: stripObjectToScalars(caseDetails.modId),
|
||||
member: stripObjectToScalars(member, ["user"]),
|
||||
mod: stripObjectToScalars(mod),
|
||||
user: stripObjectToScalars(user),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -221,21 +245,26 @@ export class MutesPlugin extends ZeppelinPlugin<IMutesPluginConfig> {
|
|||
};
|
||||
}
|
||||
|
||||
public async unmuteMember(member: Member, unmuteTime: number = null, caseDetails: ICaseDetails = {}) {
|
||||
const existingMute = await this.mutes.findExistingMuteForUserId(member.id);
|
||||
public async unmuteUser(userId: string, unmuteTime: number = null, caseDetails: ICaseDetails = {}) {
|
||||
const existingMute = await this.mutes.findExistingMuteForUserId(userId);
|
||||
if (!existingMute) return;
|
||||
|
||||
const user = this.bot.users.get(userId) || { ...unknownUser, id: userId };
|
||||
const member = await this.resolveMember(userId);
|
||||
|
||||
if (unmuteTime) {
|
||||
// Schedule timed unmute (= just set the mute's duration)
|
||||
await this.mutes.updateExpiryTime(member.id, unmuteTime);
|
||||
await this.mutes.updateExpiryTime(userId, unmuteTime);
|
||||
} else {
|
||||
// Unmute immediately
|
||||
const muteRole = this.getConfig().mute_role;
|
||||
if (member.roles.includes(muteRole)) {
|
||||
await member.removeRole(muteRole);
|
||||
if (member) {
|
||||
const muteRole = this.getConfig().mute_role;
|
||||
if (member.roles.includes(muteRole)) {
|
||||
await member.removeRole(muteRole);
|
||||
}
|
||||
}
|
||||
|
||||
await this.mutes.clear(member.id);
|
||||
await this.mutes.clear(userId);
|
||||
}
|
||||
|
||||
const timeUntilUnmute = unmuteTime && humanizeDuration(unmuteTime);
|
||||
|
@ -249,7 +278,7 @@ export class MutesPlugin extends ZeppelinPlugin<IMutesPluginConfig> {
|
|||
}
|
||||
|
||||
const createdCase = await this.actions.fire("createCase", {
|
||||
userId: member.id,
|
||||
userId,
|
||||
modId: caseDetails.modId,
|
||||
type: CaseTypes.Unmute,
|
||||
reason: caseDetails.reason,
|
||||
|
@ -262,13 +291,13 @@ export class MutesPlugin extends ZeppelinPlugin<IMutesPluginConfig> {
|
|||
if (unmuteTime) {
|
||||
this.serverLogs.log(LogType.MEMBER_TIMED_UNMUTE, {
|
||||
mod: stripObjectToScalars(mod),
|
||||
member: stripObjectToScalars(member, ["user"]),
|
||||
user: stripObjectToScalars(user),
|
||||
time: timeUntilUnmute,
|
||||
});
|
||||
} else {
|
||||
this.serverLogs.log(LogType.MEMBER_UNMUTE, {
|
||||
mod: stripObjectToScalars(mod),
|
||||
member: stripObjectToScalars(member, ["user"]),
|
||||
user: stripObjectToScalars(user),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -416,6 +445,22 @@ export class MutesPlugin extends ZeppelinPlugin<IMutesPluginConfig> {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reapply active mutes on join
|
||||
*/
|
||||
@d.event("guildMemberAdd")
|
||||
async onGuildMemberAdd(_, member: Member) {
|
||||
const mute = await this.mutes.findExistingMuteForUserId(member.id);
|
||||
if (mute) {
|
||||
const muteRole = this.getConfig().mute_role;
|
||||
await member.addRole(muteRole);
|
||||
|
||||
this.serverLogs.log(LogType.MEMBER_MUTE_REJOIN, {
|
||||
member: stripObjectToScalars(member, ["user"]),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear active mute from the member if the member is banned
|
||||
*/
|
||||
|
|
|
@ -69,35 +69,36 @@ export class PersistPlugin extends ZeppelinPlugin<IPersistPluginConfig> {
|
|||
const persistedData = await this.persistedData.find(member.id);
|
||||
if (!persistedData) return;
|
||||
|
||||
let restore = false;
|
||||
const toRestore: MemberOptions = {};
|
||||
const config = this.getConfig();
|
||||
const restoredData = [];
|
||||
|
||||
const persistedRoles = config.persisted_roles;
|
||||
if (persistedRoles.length) {
|
||||
const rolesToRestore = intersection(persistedRoles, persistedData.roles);
|
||||
if (rolesToRestore.length) {
|
||||
restore = true;
|
||||
restoredData.push("roles");
|
||||
toRestore.roles = rolesToRestore;
|
||||
}
|
||||
}
|
||||
|
||||
if (config.persist_nicknames && persistedData.nickname) {
|
||||
restore = true;
|
||||
restoredData.push("nickname");
|
||||
toRestore.nick = persistedData.nickname;
|
||||
}
|
||||
|
||||
if (config.persist_voice_mutes && persistedData.is_voice_muted) {
|
||||
restore = true;
|
||||
restoredData.push("voice mute");
|
||||
toRestore.mute = true;
|
||||
}
|
||||
|
||||
if (restore) {
|
||||
if (restoredData.length) {
|
||||
await member.edit(toRestore, "Restored upon rejoin");
|
||||
await this.persistedData.clear(member.id);
|
||||
|
||||
this.logs.log(LogType.MEMBER_RESTORE, {
|
||||
member: stripObjectToScalars(member, ["user"]),
|
||||
restoredData: restoredData.join(", "),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -245,7 +245,7 @@ export class SpamPlugin extends ZeppelinPlugin<ISpamPluginConfig> {
|
|||
? convertDelayStringToMS(spamConfig.mute_time.toString())
|
||||
: 120 * 1000;
|
||||
muteResult = await this.actions.fire("mute", {
|
||||
member,
|
||||
userId: member.id,
|
||||
muteTime,
|
||||
reason: "Automatic spam detection",
|
||||
caseDetails: {
|
||||
|
@ -366,7 +366,7 @@ export class SpamPlugin extends ZeppelinPlugin<ISpamPluginConfig> {
|
|||
if (spamConfig.mute && member) {
|
||||
const muteTime = spamConfig.mute_time ? spamConfig.mute_time * 60 * 1000 : 120 * 1000;
|
||||
this.logs.ignoreLog(LogType.MEMBER_ROLE_ADD, userId);
|
||||
this.actions.fire("mute", { member, muteTime, reason: "Automatic spam detection" });
|
||||
this.actions.fire("mute", { userId: member.id, muteTime, reason: "Automatic spam detection" });
|
||||
}
|
||||
|
||||
// Clear recent cases
|
||||
|
|
Loading…
Add table
Reference in a new issue