mirror of
https://github.com/ZeppelinBot/Zeppelin.git
synced 2025-03-15 05:41:51 +00:00
Merge pull request #89 from DarkView/k30_modActions
[K30] Migrated ModActions | Mutes dont work, issue in MutesPlugin->public
This commit is contained in:
commit
6338ee9e8a
29 changed files with 1213 additions and 16 deletions
|
@ -9,6 +9,7 @@ import { Case } from "../../data/entities/Case";
|
|||
import { postCaseToCaseLogChannel } from "./functions/postToCaseLogChannel";
|
||||
import { CaseTypes } from "../../data/CaseTypes";
|
||||
import { getCaseTypeAmountForUserId } from "./functions/getCaseTypeAmountForUserId";
|
||||
import { getCaseEmbed } from "./functions/getCaseEmbed";
|
||||
|
||||
const defaultOptions = {
|
||||
config: {
|
||||
|
@ -45,6 +46,12 @@ export const CasesPlugin = zeppelinPlugin<CasesPluginType>()("cases", {
|
|||
return getCaseTypeAmountForUserId(pluginData, userID, type);
|
||||
};
|
||||
},
|
||||
|
||||
getCaseEmbed(pluginData) {
|
||||
return (caseOrCaseId: Case | number) => {
|
||||
return getCaseEmbed(pluginData, caseOrCaseId);
|
||||
};
|
||||
},
|
||||
},
|
||||
|
||||
onLoad(pluginData) {
|
||||
|
|
|
@ -9,6 +9,25 @@ import { UpdateCmd } from "./commands/UpdateCmd";
|
|||
import { NoteCmd } from "./commands/NoteCmd";
|
||||
import { WarnCmd } from "./commands/WarnCmd";
|
||||
import { MuteCmd } from "./commands/MuteCmd";
|
||||
import { PostAlertOnMemberJoinEvt } from "./events/PostAlertOnMemberJoinEvt";
|
||||
import { ForcemuteCmd } from "./commands/ForcemuteCmd";
|
||||
import { UnmuteCmd } from "./commands/UnmuteCmd";
|
||||
import { KickCmd } from "./commands/KickCmd";
|
||||
import { SoftbanCmd } from "./commands/SoftbanCommand";
|
||||
import { BanCmd } from "./commands/BanCmd";
|
||||
import { UnbanCmd } from "./commands/UnbanCmd";
|
||||
import { ForcebanCmd } from "./commands/ForcebanCmd";
|
||||
import { MassbanCmd } from "./commands/MassBanCmd";
|
||||
import { AddCaseCmd } from "./commands/AddCaseCmd";
|
||||
import { CaseCmd } from "./commands/CaseCmd";
|
||||
import { CasesUserCmd } from "./commands/CasesUserCmd";
|
||||
import { CasesModCmd } from "./commands/CasesModCmd";
|
||||
import { HideCaseCmd } from "./commands/HideCaseCmd";
|
||||
import { UnhideCaseCmd } from "./commands/UnhideCaseCmd";
|
||||
import { GuildMutes } from "src/data/GuildMutes";
|
||||
import { GuildCases } from "src/data/GuildCases";
|
||||
import { GuildLogs } from "src/data/GuildLogs";
|
||||
import { ForceUnmuteCmd } from "./commands/ForceunmuteCmd";
|
||||
|
||||
const defaultOptions = {
|
||||
config: {
|
||||
|
@ -71,7 +90,42 @@ export const ModActionsPlugin = zeppelinPlugin<ModActionsPluginType>()("mod_acti
|
|||
|
||||
dependencies: [CasesPlugin, MutesPlugin],
|
||||
|
||||
events: [CreateBanCaseOnManualBanEvt, CreateUnbanCaseOnManualUnbanEvt, CreateKickCaseOnManualKickEvt],
|
||||
events: [
|
||||
CreateBanCaseOnManualBanEvt,
|
||||
CreateUnbanCaseOnManualUnbanEvt,
|
||||
CreateKickCaseOnManualKickEvt,
|
||||
PostAlertOnMemberJoinEvt,
|
||||
],
|
||||
|
||||
commands: [UpdateCmd, NoteCmd, WarnCmd, MuteCmd],
|
||||
commands: [
|
||||
UpdateCmd,
|
||||
NoteCmd,
|
||||
WarnCmd,
|
||||
MuteCmd,
|
||||
ForcemuteCmd,
|
||||
UnmuteCmd,
|
||||
ForceUnmuteCmd,
|
||||
KickCmd,
|
||||
SoftbanCmd,
|
||||
BanCmd,
|
||||
UnbanCmd,
|
||||
ForcebanCmd,
|
||||
MassbanCmd,
|
||||
AddCaseCmd,
|
||||
CaseCmd,
|
||||
CasesUserCmd,
|
||||
CasesModCmd,
|
||||
HideCaseCmd,
|
||||
UnhideCaseCmd,
|
||||
],
|
||||
|
||||
onLoad(pluginData) {
|
||||
const { state, guild } = pluginData;
|
||||
|
||||
state.mutes = GuildMutes.getGuildInstance(guild.id);
|
||||
state.cases = GuildCases.getGuildInstance(guild.id);
|
||||
state.serverLogs = new GuildLogs(guild.id);
|
||||
|
||||
state.ignoredEvents = [];
|
||||
},
|
||||
});
|
||||
|
|
90
backend/src/plugins/ModActions/commands/AddCaseCmd.ts
Normal file
90
backend/src/plugins/ModActions/commands/AddCaseCmd.ts
Normal file
|
@ -0,0 +1,90 @@
|
|||
import { modActionsCommand } from "../types";
|
||||
import { commandTypeHelpers as ct } from "../../../commandTypes";
|
||||
import { canActOn, sendErrorMessage, hasPermission, sendSuccessMessage } from "../../../pluginUtils";
|
||||
import { resolveUser, resolveMember, stripObjectToScalars } from "../../../utils";
|
||||
import { formatReasonWithAttachments } from "../functions/formatReasonWithAttachments";
|
||||
import { CaseTypes } from "src/data/CaseTypes";
|
||||
import { CasesPlugin } from "src/plugins/Cases/CasesPlugin";
|
||||
import { Case } from "src/data/entities/Case";
|
||||
import { LogType } from "src/data/LogType";
|
||||
|
||||
const opts = {
|
||||
mod: ct.member({ option: true }),
|
||||
};
|
||||
|
||||
export const AddCaseCmd = modActionsCommand({
|
||||
trigger: "addcase",
|
||||
permission: "can_addcase",
|
||||
description: "Add an arbitrary case to the specified user without taking any action",
|
||||
|
||||
signature: [
|
||||
{
|
||||
type: ct.string(),
|
||||
user: ct.string(),
|
||||
reason: ct.string({ required: false, catchAll: true }),
|
||||
|
||||
...opts,
|
||||
},
|
||||
],
|
||||
|
||||
async run({ pluginData, message: msg, args }) {
|
||||
const user = await resolveUser(pluginData.client, args.user);
|
||||
if (!user) return sendErrorMessage(pluginData, msg.channel, `User not found`);
|
||||
|
||||
// If the user exists as a guild member, make sure we can act on them first
|
||||
const member = await resolveMember(pluginData.client, pluginData.guild, user.id);
|
||||
if (member && !canActOn(pluginData, msg.member, member)) {
|
||||
sendErrorMessage(pluginData, msg.channel, "Cannot add case on this user: insufficient permissions");
|
||||
return;
|
||||
}
|
||||
|
||||
// The moderator who did the action is the message author or, if used, the specified -mod
|
||||
let mod = msg.member;
|
||||
if (args.mod) {
|
||||
if (!hasPermission(pluginData, "can_act_as_other", { message: msg })) {
|
||||
sendErrorMessage(pluginData, msg.channel, "No permission for -mod");
|
||||
return;
|
||||
}
|
||||
|
||||
mod = args.mod;
|
||||
}
|
||||
|
||||
// Verify the case type is valid
|
||||
const type: string = args.type[0].toUpperCase() + args.type.slice(1).toLowerCase();
|
||||
if (!CaseTypes[type]) {
|
||||
sendErrorMessage(pluginData, msg.channel, "Cannot add case: invalid case type");
|
||||
return;
|
||||
}
|
||||
|
||||
const reason = formatReasonWithAttachments(args.reason, msg.attachments);
|
||||
|
||||
// Create the case
|
||||
const casesPlugin = pluginData.getPlugin(CasesPlugin);
|
||||
const theCase: Case = await casesPlugin.createCase({
|
||||
userId: user.id,
|
||||
modId: mod.id,
|
||||
type: CaseTypes[type],
|
||||
reason,
|
||||
ppId: mod.id !== msg.author.id ? msg.author.id : null,
|
||||
});
|
||||
|
||||
if (user) {
|
||||
sendSuccessMessage(
|
||||
pluginData,
|
||||
msg.channel,
|
||||
`Case #${theCase.case_number} created for **${user.username}#${user.discriminator}**`,
|
||||
);
|
||||
} else {
|
||||
sendSuccessMessage(pluginData, msg.channel, `Case #${theCase.case_number} created`);
|
||||
}
|
||||
|
||||
// Log the action
|
||||
pluginData.state.serverLogs.log(LogType.CASE_CREATE, {
|
||||
mod: stripObjectToScalars(mod.user),
|
||||
userId: user.id,
|
||||
caseNum: theCase.case_number,
|
||||
caseType: type.toUpperCase(),
|
||||
reason,
|
||||
});
|
||||
},
|
||||
});
|
97
backend/src/plugins/ModActions/commands/BanCmd.ts
Normal file
97
backend/src/plugins/ModActions/commands/BanCmd.ts
Normal file
|
@ -0,0 +1,97 @@
|
|||
import { modActionsCommand, IgnoredEventType } from "../types";
|
||||
import { commandTypeHelpers as ct } from "../../../commandTypes";
|
||||
import { canActOn, sendErrorMessage, hasPermission, sendSuccessMessage } from "../../../pluginUtils";
|
||||
import { resolveUser, resolveMember } from "../../../utils";
|
||||
import { isBanned } from "../functions/isBanned";
|
||||
import { readContactMethodsFromArgs } from "../functions/readContactMethodsFromArgs";
|
||||
import { formatReasonWithAttachments } from "../functions/formatReasonWithAttachments";
|
||||
import { banUserId } from "../functions/banUserId";
|
||||
import { ignoreEvent } from "../functions/ignoreEvent";
|
||||
import { LogType } from "src/data/LogType";
|
||||
|
||||
const opts = {
|
||||
mod: ct.member({ option: true }),
|
||||
notify: ct.string({ option: true }),
|
||||
"notify-channel": ct.textChannel({ option: true }),
|
||||
"delete-deays": ct.number({ option: true, shortcut: "d" }),
|
||||
};
|
||||
|
||||
export const BanCmd = modActionsCommand({
|
||||
trigger: "ban",
|
||||
permission: "can_ban",
|
||||
description: "Ban the specified member",
|
||||
|
||||
signature: [
|
||||
{
|
||||
user: ct.string(),
|
||||
reason: ct.string({ required: false, catchAll: true }),
|
||||
|
||||
...opts,
|
||||
},
|
||||
],
|
||||
|
||||
async run({ pluginData, message: msg, args }) {
|
||||
const user = await resolveUser(pluginData.client, args.user);
|
||||
if (!user) return sendErrorMessage(pluginData, msg.channel, `User not found`);
|
||||
|
||||
const memberToBan = await resolveMember(pluginData.client, pluginData.guild, user.id);
|
||||
|
||||
if (!memberToBan) {
|
||||
const banned = await isBanned(pluginData, user.id);
|
||||
if (banned) {
|
||||
sendErrorMessage(pluginData, msg.channel, `User is already banned`);
|
||||
} else {
|
||||
sendErrorMessage(pluginData, msg.channel, `User not found on the server`);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure we're allowed to ban this member
|
||||
if (!canActOn(pluginData, msg.member, memberToBan)) {
|
||||
sendErrorMessage(pluginData, msg.channel, "Cannot ban: insufficient permissions");
|
||||
return;
|
||||
}
|
||||
|
||||
// The moderator who did the action is the message author or, if used, the specified -mod
|
||||
let mod = msg.member;
|
||||
if (args.mod) {
|
||||
if (!hasPermission(pluginData, "can_act_as_other", { message: msg, channelId: msg.channel.id })) {
|
||||
sendErrorMessage(pluginData, msg.channel, "No permission for -mod");
|
||||
return;
|
||||
}
|
||||
|
||||
mod = args.mod;
|
||||
}
|
||||
|
||||
let contactMethods;
|
||||
try {
|
||||
contactMethods = readContactMethodsFromArgs(args);
|
||||
} catch (e) {
|
||||
sendErrorMessage(pluginData, msg.channel, e.message);
|
||||
return;
|
||||
}
|
||||
|
||||
const deleteMessageDays = args["delete-days"] ?? pluginData.config.getForMessage(msg).ban_delete_message_days;
|
||||
const reason = formatReasonWithAttachments(args.reason, msg.attachments);
|
||||
const banResult = await banUserId(pluginData, memberToBan.id, reason, {
|
||||
contactMethods,
|
||||
caseArgs: {
|
||||
modId: mod.id,
|
||||
ppId: mod.id !== msg.author.id ? msg.author.id : null,
|
||||
},
|
||||
deleteMessageDays,
|
||||
});
|
||||
|
||||
if (banResult.status === "failed") {
|
||||
sendErrorMessage(pluginData, msg.channel, `Failed to ban member`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Confirm the action to the moderator
|
||||
let response = `Banned **${memberToBan.user.username}#${memberToBan.user.discriminator}** (Case #${banResult.case.case_number})`;
|
||||
|
||||
if (banResult.notifyResult.text) response += ` (${banResult.notifyResult.text})`;
|
||||
sendSuccessMessage(pluginData, msg.channel, response);
|
||||
},
|
||||
});
|
29
backend/src/plugins/ModActions/commands/CaseCmd.ts
Normal file
29
backend/src/plugins/ModActions/commands/CaseCmd.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
import { modActionsCommand } from "../types";
|
||||
import { commandTypeHelpers as ct } from "../../../commandTypes";
|
||||
import { sendErrorMessage } from "../../../pluginUtils";
|
||||
import { CasesPlugin } from "src/plugins/Cases/CasesPlugin";
|
||||
|
||||
export const CaseCmd = modActionsCommand({
|
||||
trigger: "case",
|
||||
permission: "can_view",
|
||||
description: "Show information about a specific case",
|
||||
|
||||
signature: [
|
||||
{
|
||||
caseNumber: ct.number(),
|
||||
},
|
||||
],
|
||||
|
||||
async run({ pluginData, message: msg, args }) {
|
||||
const theCase = await pluginData.state.cases.findByCaseNumber(args.caseNumber);
|
||||
|
||||
if (!theCase) {
|
||||
sendErrorMessage(pluginData, msg.channel, "Case not found");
|
||||
return;
|
||||
}
|
||||
|
||||
const casesPlugin = pluginData.getPlugin(CasesPlugin);
|
||||
const embed = await casesPlugin.getCaseEmbed(theCase.id);
|
||||
msg.channel.createMessage(embed);
|
||||
},
|
||||
});
|
43
backend/src/plugins/ModActions/commands/CasesModCmd.ts
Normal file
43
backend/src/plugins/ModActions/commands/CasesModCmd.ts
Normal file
|
@ -0,0 +1,43 @@
|
|||
import { modActionsCommand } from "../types";
|
||||
import { commandTypeHelpers as ct } from "../../../commandTypes";
|
||||
import { sendErrorMessage } from "../../../pluginUtils";
|
||||
import { trimLines, createChunkedMessage } from "src/utils";
|
||||
|
||||
const opts = {
|
||||
mod: ct.member({ option: true }),
|
||||
};
|
||||
|
||||
export const CasesModCmd = modActionsCommand({
|
||||
trigger: "cases",
|
||||
permission: "can_view",
|
||||
description: "Show the most recent 5 cases by the specified -mod",
|
||||
|
||||
signature: [
|
||||
{
|
||||
...opts,
|
||||
},
|
||||
],
|
||||
|
||||
async run({ pluginData, message: msg, args }) {
|
||||
const modId = args.mod ? args.mod.id : msg.author.id;
|
||||
const recentCases = await pluginData.state.cases.with("notes").getRecentByModId(modId, 5);
|
||||
|
||||
const mod = pluginData.client.users.get(modId);
|
||||
const modName = mod ? `${mod.username}#${mod.discriminator}` : modId;
|
||||
|
||||
if (recentCases.length === 0) {
|
||||
sendErrorMessage(pluginData, msg.channel, `No cases by **${modName}**`);
|
||||
} else {
|
||||
const lines = recentCases.map(c => pluginData.state.cases.getSummaryText(c));
|
||||
const finalMessage = trimLines(`
|
||||
Most recent 5 cases by **${modName}**:
|
||||
|
||||
${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);
|
||||
}
|
||||
},
|
||||
});
|
84
backend/src/plugins/ModActions/commands/CasesUserCmd.ts
Normal file
84
backend/src/plugins/ModActions/commands/CasesUserCmd.ts
Normal file
|
@ -0,0 +1,84 @@
|
|||
import { modActionsCommand } from "../types";
|
||||
import { commandTypeHelpers as ct } from "../../../commandTypes";
|
||||
import { sendErrorMessage } from "../../../pluginUtils";
|
||||
import { CasesPlugin } from "src/plugins/Cases/CasesPlugin";
|
||||
import { UnknownUser, multiSorter, trimLines, createChunkedMessage, resolveUser } from "src/utils";
|
||||
|
||||
const opts = {
|
||||
expand: ct.bool({ option: true, isSwitch: true, shortcut: "e" }),
|
||||
hidden: ct.bool({ option: true, isSwitch: true, shortcut: "h" }),
|
||||
};
|
||||
|
||||
export const CasesUserCmd = modActionsCommand({
|
||||
trigger: "cases",
|
||||
permission: "can_view",
|
||||
description: "Show a list of cases the specified user has",
|
||||
|
||||
signature: [
|
||||
{
|
||||
user: ct.string(),
|
||||
|
||||
...opts,
|
||||
},
|
||||
],
|
||||
|
||||
async run({ pluginData, message: msg, args }) {
|
||||
const user = await resolveUser(pluginData.client, args.user);
|
||||
if (!user) return sendErrorMessage(pluginData, msg.channel, `User not found`);
|
||||
|
||||
const cases = await pluginData.state.cases.with("notes").getByUserId(user.id);
|
||||
const normalCases = cases.filter(c => !c.is_hidden);
|
||||
const hiddenCases = cases.filter(c => c.is_hidden);
|
||||
|
||||
const userName =
|
||||
user instanceof UnknownUser && cases.length
|
||||
? cases[cases.length - 1].user_name
|
||||
: `${user.username}#${user.discriminator}`;
|
||||
|
||||
if (cases.length === 0) {
|
||||
msg.channel.createMessage(`No cases found for **${userName}**`);
|
||||
} else {
|
||||
const casesToDisplay = args.hidden ? cases : normalCases;
|
||||
|
||||
if (args.expand) {
|
||||
if (casesToDisplay.length > 8) {
|
||||
msg.channel.createMessage("Too many cases for expanded view. Please use compact view instead.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Expanded view (= individual case embeds)
|
||||
const casesPlugin = pluginData.getPlugin(CasesPlugin);
|
||||
for (const theCase of casesToDisplay) {
|
||||
const embed = await casesPlugin.getCaseEmbed(theCase.id);
|
||||
msg.channel.createMessage(embed);
|
||||
}
|
||||
} else {
|
||||
// Compact view (= regular message with a preview of each case)
|
||||
const lines = [];
|
||||
for (const theCase of casesToDisplay) {
|
||||
theCase.notes.sort(multiSorter(["created_at", "id"]));
|
||||
const caseSummary = pluginData.state.cases.getSummaryText(theCase);
|
||||
lines.push(caseSummary);
|
||||
}
|
||||
|
||||
if (!args.hidden && hiddenCases.length) {
|
||||
if (hiddenCases.length === 1) {
|
||||
lines.push(`*+${hiddenCases.length} hidden case, use "-hidden" to show it*`);
|
||||
} else {
|
||||
lines.push(`*+${hiddenCases.length} hidden cases, use "-hidden" to show them*`);
|
||||
}
|
||||
}
|
||||
|
||||
const finalMessage = trimLines(`
|
||||
Cases for **${userName}**:
|
||||
|
||||
${lines.join("\n")}
|
||||
|
||||
Use the \`case <num>\` command to see more info about individual cases
|
||||
`);
|
||||
|
||||
createChunkedMessage(msg.channel, finalMessage);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
93
backend/src/plugins/ModActions/commands/ForcebanCmd.ts
Normal file
93
backend/src/plugins/ModActions/commands/ForcebanCmd.ts
Normal file
|
@ -0,0 +1,93 @@
|
|||
import { modActionsCommand, IgnoredEventType } from "../types";
|
||||
import { commandTypeHelpers as ct } from "../../../commandTypes";
|
||||
import { canActOn, sendErrorMessage, hasPermission, sendSuccessMessage } from "../../../pluginUtils";
|
||||
import { resolveUser, resolveMember, stripObjectToScalars } from "../../../utils";
|
||||
import { isBanned } from "../functions/isBanned";
|
||||
import { readContactMethodsFromArgs } from "../functions/readContactMethodsFromArgs";
|
||||
import { formatReasonWithAttachments } from "../functions/formatReasonWithAttachments";
|
||||
import { banUserId } from "../functions/banUserId";
|
||||
import { ignoreEvent } from "../functions/ignoreEvent";
|
||||
import { LogType } from "src/data/LogType";
|
||||
import { CaseTypes } from "src/data/CaseTypes";
|
||||
import { CasesPlugin } from "src/plugins/Cases/CasesPlugin";
|
||||
|
||||
const opts = {
|
||||
mod: ct.member({ option: true }),
|
||||
};
|
||||
|
||||
export const ForcebanCmd = modActionsCommand({
|
||||
trigger: "forceban",
|
||||
permission: "can_ban",
|
||||
description: "Force-ban the specified user, even if they aren't on the server",
|
||||
|
||||
signature: [
|
||||
{
|
||||
user: ct.string(),
|
||||
reason: ct.string({ required: false, catchAll: true }),
|
||||
|
||||
...opts,
|
||||
},
|
||||
],
|
||||
|
||||
async run({ pluginData, message: msg, args }) {
|
||||
const user = await resolveUser(pluginData.client, args.user);
|
||||
if (!user) return sendErrorMessage(pluginData, msg.channel, `User not found`);
|
||||
|
||||
// If the user exists as a guild member, make sure we can act on them first
|
||||
const member = await resolveMember(pluginData.client, pluginData.guild, user.id);
|
||||
if (member && !canActOn(pluginData, msg.member, member)) {
|
||||
sendErrorMessage(pluginData, msg.channel, "Cannot forceban this user: insufficient permissions");
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure the user isn't already banned
|
||||
const banned = await isBanned(pluginData, user.id);
|
||||
if (banned) {
|
||||
sendErrorMessage(pluginData, msg.channel, `User is already banned`);
|
||||
return;
|
||||
}
|
||||
|
||||
// The moderator who did the action is the message author or, if used, the specified -mod
|
||||
let mod = msg.member;
|
||||
if (args.mod) {
|
||||
if (!hasPermission(pluginData, "can_act_as_other", { message: msg })) {
|
||||
sendErrorMessage(pluginData, msg.channel, "No permission for -mod");
|
||||
return;
|
||||
}
|
||||
|
||||
mod = args.mod;
|
||||
}
|
||||
|
||||
const reason = formatReasonWithAttachments(args.reason, msg.attachments);
|
||||
|
||||
ignoreEvent(pluginData, IgnoredEventType.Ban, user.id);
|
||||
pluginData.state.serverLogs.ignoreLog(LogType.MEMBER_BAN, user.id);
|
||||
|
||||
try {
|
||||
await pluginData.guild.banMember(user.id, 1);
|
||||
} catch (e) {
|
||||
sendErrorMessage(pluginData, msg.channel, "Failed to forceban member");
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a case
|
||||
const casesPlugin = pluginData.getPlugin(CasesPlugin);
|
||||
const createdCase = await casesPlugin.createCase({
|
||||
userId: user.id,
|
||||
modId: mod.id,
|
||||
type: CaseTypes.Ban,
|
||||
reason,
|
||||
ppId: mod.id !== msg.author.id ? msg.author.id : null,
|
||||
});
|
||||
|
||||
// Confirm the action
|
||||
sendSuccessMessage(pluginData, msg.channel, `Member forcebanned (Case #${createdCase.case_number})`);
|
||||
|
||||
// Log the action
|
||||
pluginData.state.serverLogs.log(LogType.MEMBER_FORCEBAN, {
|
||||
mod: stripObjectToScalars(mod.user),
|
||||
userId: user.id,
|
||||
reason,
|
||||
});
|
||||
},
|
||||
});
|
48
backend/src/plugins/ModActions/commands/ForcemuteCmd.ts
Normal file
48
backend/src/plugins/ModActions/commands/ForcemuteCmd.ts
Normal file
|
@ -0,0 +1,48 @@
|
|||
import { modActionsCommand } from "../types";
|
||||
import { commandTypeHelpers as ct } from "../../../commandTypes";
|
||||
import { canActOn, sendErrorMessage } from "../../../pluginUtils";
|
||||
import { resolveMember, resolveUser } from "../../../utils";
|
||||
import { actualMuteUserCmd } from "../functions/actualMuteUserCmd";
|
||||
|
||||
const opts = {
|
||||
mod: ct.member({ option: true }),
|
||||
notify: ct.string({ option: true }),
|
||||
"notify-channel": ct.textChannel({ option: true }),
|
||||
};
|
||||
|
||||
export const ForcemuteCmd = modActionsCommand({
|
||||
trigger: "forcemute",
|
||||
permission: "can_mute",
|
||||
description: "Force-mute the specified user, even if they're not on the server",
|
||||
|
||||
signature: [
|
||||
{
|
||||
user: ct.string(),
|
||||
time: ct.delay(),
|
||||
reason: ct.string({ required: false, catchAll: true }),
|
||||
|
||||
...opts,
|
||||
},
|
||||
{
|
||||
user: ct.string(),
|
||||
reason: ct.string({ required: false, catchAll: true }),
|
||||
|
||||
...opts,
|
||||
},
|
||||
],
|
||||
|
||||
async run({ pluginData, message: msg, args }) {
|
||||
const user = await resolveUser(pluginData.client, args.user);
|
||||
if (!user) return sendErrorMessage(pluginData, msg.channel, `User not found`);
|
||||
|
||||
const memberToMute = await resolveMember(pluginData.client, pluginData.guild, user.id);
|
||||
|
||||
// Make sure we're allowed to mute this user
|
||||
if (memberToMute && !canActOn(pluginData, msg.member, memberToMute)) {
|
||||
sendErrorMessage(pluginData, msg.channel, "Cannot mute: insufficient permissions");
|
||||
return;
|
||||
}
|
||||
|
||||
actualMuteUserCmd(pluginData, user, msg, args);
|
||||
},
|
||||
});
|
53
backend/src/plugins/ModActions/commands/ForceunmuteCmd.ts
Normal file
53
backend/src/plugins/ModActions/commands/ForceunmuteCmd.ts
Normal file
|
@ -0,0 +1,53 @@
|
|||
import { modActionsCommand } from "../types";
|
||||
import { commandTypeHelpers as ct } from "../../../commandTypes";
|
||||
import { canActOn, sendErrorMessage } from "../../../pluginUtils";
|
||||
import { resolveUser, resolveMember } from "../../../utils";
|
||||
import { actualUnmuteCmd } from "../functions/actualUnmuteUserCmd";
|
||||
|
||||
const opts = {
|
||||
mod: ct.member({ option: true }),
|
||||
};
|
||||
|
||||
export const ForceUnmuteCmd = modActionsCommand({
|
||||
trigger: "forceunmute",
|
||||
permission: "can_mute",
|
||||
description: "Force-unmute the specified user, even if they're not on the server",
|
||||
|
||||
signature: [
|
||||
{
|
||||
user: ct.string(),
|
||||
time: ct.delay(),
|
||||
reason: ct.string({ required: false, catchAll: true }),
|
||||
|
||||
...opts,
|
||||
},
|
||||
{
|
||||
user: ct.string(),
|
||||
reason: ct.string({ required: false, catchAll: true }),
|
||||
|
||||
...opts,
|
||||
},
|
||||
],
|
||||
|
||||
async run({ pluginData, message: msg, args }) {
|
||||
const user = await resolveUser(pluginData.client, args.user);
|
||||
if (!user) return sendErrorMessage(pluginData, msg.channel, `User not found`);
|
||||
|
||||
// Check if they're muted in the first place
|
||||
if (!(await pluginData.state.mutes.isMuted(user.id))) {
|
||||
sendErrorMessage(pluginData, msg.channel, "Cannot unmute: member is not muted");
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the server member to unmute
|
||||
const memberToUnmute = await resolveMember(pluginData.client, pluginData.guild, user.id);
|
||||
|
||||
// Make sure we're allowed to unmute this member
|
||||
if (memberToUnmute && !canActOn(pluginData, msg.member, memberToUnmute)) {
|
||||
sendErrorMessage(pluginData, msg.channel, "Cannot unmute: insufficient permissions");
|
||||
return;
|
||||
}
|
||||
|
||||
actualUnmuteCmd(pluginData, user, msg, args);
|
||||
},
|
||||
});
|
30
backend/src/plugins/ModActions/commands/HideCaseCmd.ts
Normal file
30
backend/src/plugins/ModActions/commands/HideCaseCmd.ts
Normal file
|
@ -0,0 +1,30 @@
|
|||
import { modActionsCommand } from "../types";
|
||||
import { commandTypeHelpers as ct } from "../../../commandTypes";
|
||||
import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils";
|
||||
|
||||
export const HideCaseCmd = modActionsCommand({
|
||||
trigger: ["hide", "hidecase", "hide_case"],
|
||||
permission: "can_hidecase",
|
||||
description: "Hide the specified case so it doesn't appear in !cases or !info",
|
||||
|
||||
signature: [
|
||||
{
|
||||
caseNum: ct.number(),
|
||||
},
|
||||
],
|
||||
|
||||
async run({ pluginData, message: msg, args }) {
|
||||
const theCase = await pluginData.state.cases.findByCaseNumber(args.caseNum);
|
||||
if (!theCase) {
|
||||
sendErrorMessage(pluginData, msg.channel, "Case not found!");
|
||||
return;
|
||||
}
|
||||
|
||||
await pluginData.state.cases.setHidden(theCase.id, true);
|
||||
sendSuccessMessage(
|
||||
pluginData,
|
||||
msg.channel,
|
||||
`Case #${theCase.case_number} is now hidden! Use \`unhidecase\` to unhide it.`,
|
||||
);
|
||||
},
|
||||
});
|
35
backend/src/plugins/ModActions/commands/KickCmd.ts
Normal file
35
backend/src/plugins/ModActions/commands/KickCmd.ts
Normal file
|
@ -0,0 +1,35 @@
|
|||
import { modActionsCommand } from "../types";
|
||||
import { commandTypeHelpers as ct } from "../../../commandTypes";
|
||||
import { canActOn, sendErrorMessage } from "../../../pluginUtils";
|
||||
import { resolveUser, resolveMember } from "../../../utils";
|
||||
import { MutesPlugin } from "src/plugins/Mutes/MutesPlugin";
|
||||
import { actualUnmuteCmd } from "../functions/actualUnmuteUserCmd";
|
||||
import { isBanned } from "../functions/isBanned";
|
||||
import { plugin } from "knub";
|
||||
import { actualKickMemberCmd } from "../functions/actualKickMemberCmd";
|
||||
|
||||
const opts = {
|
||||
mod: ct.member({ option: true }),
|
||||
notify: ct.string({ option: true }),
|
||||
"notify-channel": ct.textChannel({ option: true }),
|
||||
clean: ct.bool({ option: true, isSwitch: true }),
|
||||
};
|
||||
|
||||
export const KickCmd = modActionsCommand({
|
||||
trigger: "kick",
|
||||
permission: "can_kick",
|
||||
description: "Kick the specified member",
|
||||
|
||||
signature: [
|
||||
{
|
||||
user: ct.string(),
|
||||
reason: ct.string({ required: false, catchAll: true }),
|
||||
|
||||
...opts,
|
||||
},
|
||||
],
|
||||
|
||||
async run({ pluginData, message: msg, args }) {
|
||||
actualKickMemberCmd(pluginData, msg, args);
|
||||
},
|
||||
});
|
109
backend/src/plugins/ModActions/commands/MassBanCmd.ts
Normal file
109
backend/src/plugins/ModActions/commands/MassBanCmd.ts
Normal file
|
@ -0,0 +1,109 @@
|
|||
import { modActionsCommand, IgnoredEventType } from "../types";
|
||||
import { commandTypeHelpers as ct } from "../../../commandTypes";
|
||||
import { canActOn, sendErrorMessage, hasPermission, sendSuccessMessage } from "../../../pluginUtils";
|
||||
import { resolveUser, resolveMember, stripObjectToScalars } from "../../../utils";
|
||||
import { isBanned } from "../functions/isBanned";
|
||||
import { readContactMethodsFromArgs } from "../functions/readContactMethodsFromArgs";
|
||||
import { formatReasonWithAttachments } from "../functions/formatReasonWithAttachments";
|
||||
import { banUserId } from "../functions/banUserId";
|
||||
import { CaseTypes } from "src/data/CaseTypes";
|
||||
import { TextChannel } from "eris";
|
||||
import { waitForReply } from "knub/dist/helpers";
|
||||
import { ignoreEvent } from "../functions/ignoreEvent";
|
||||
import { CasesPlugin } from "src/plugins/Cases/CasesPlugin";
|
||||
import { LogType } from "src/data/LogType";
|
||||
|
||||
export const MassbanCmd = modActionsCommand({
|
||||
trigger: "massban",
|
||||
permission: "can_massban",
|
||||
description: "Mass-ban a list of user IDs",
|
||||
|
||||
signature: [
|
||||
{
|
||||
userIds: ct.string({ rest: true }),
|
||||
},
|
||||
],
|
||||
|
||||
async run({ pluginData, message: msg, args }) {
|
||||
// Limit to 100 users at once (arbitrary?)
|
||||
if (args.userIds.length > 100) {
|
||||
sendErrorMessage(pluginData, msg.channel, `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)
|
||||
msg.channel.createMessage("Ban reason? `cancel` to cancel");
|
||||
const banReasonReply = await waitForReply(pluginData.client, msg.channel as TextChannel, msg.author.id);
|
||||
if (!banReasonReply || !banReasonReply.content || banReasonReply.content.toLowerCase().trim() === "cancel") {
|
||||
sendErrorMessage(pluginData, msg.channel, "Cancelled");
|
||||
return;
|
||||
}
|
||||
|
||||
const banReason = formatReasonWithAttachments(banReasonReply.content, msg.attachments);
|
||||
|
||||
// Verify we can act on each of the users specified
|
||||
for (const userId of args.userIds) {
|
||||
const member = pluginData.guild.members.get(userId); // TODO: Get members on demand?
|
||||
if (member && !canActOn(pluginData, msg.member, member)) {
|
||||
sendErrorMessage(pluginData, msg.channel, "Cannot massban one or more users: insufficient permissions");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Ignore automatic ban cases and logs for these users
|
||||
// We'll create our own cases below and post a single "mass banned" log instead
|
||||
args.userIds.forEach(userId => {
|
||||
// Use longer timeouts since this can take a while
|
||||
ignoreEvent(pluginData, IgnoredEventType.Ban, userId, 120 * 1000);
|
||||
pluginData.state.serverLogs.ignoreLog(LogType.MEMBER_BAN, userId, 120 * 1000);
|
||||
});
|
||||
|
||||
// Show a loading indicator since this can take a while
|
||||
const loadingMsg = await msg.channel.createMessage("Banning...");
|
||||
|
||||
// Ban each user and count failed bans (if any)
|
||||
const failedBans = [];
|
||||
const casesPlugin = pluginData.getPlugin(CasesPlugin);
|
||||
for (const userId of args.userIds) {
|
||||
try {
|
||||
await pluginData.guild.banMember(userId, 1);
|
||||
|
||||
await casesPlugin.createCase({
|
||||
userId,
|
||||
modId: msg.author.id,
|
||||
type: CaseTypes.Ban,
|
||||
reason: `Mass ban: ${banReason}`,
|
||||
postInCaseLogOverride: false,
|
||||
});
|
||||
} catch (e) {
|
||||
failedBans.push(userId);
|
||||
}
|
||||
}
|
||||
|
||||
// Clear loading indicator
|
||||
loadingMsg.delete();
|
||||
|
||||
const successfulBanCount = args.userIds.length - failedBans.length;
|
||||
if (successfulBanCount === 0) {
|
||||
// All bans failed - don't create a log entry and notify the user
|
||||
sendErrorMessage(pluginData, msg.channel, "All bans failed. Make sure the IDs are valid.");
|
||||
} else {
|
||||
// Some or all bans were successful. Create a log entry for the mass ban and notify the user.
|
||||
pluginData.state.serverLogs.log(LogType.MASSBAN, {
|
||||
mod: stripObjectToScalars(msg.author),
|
||||
count: successfulBanCount,
|
||||
reason: banReason,
|
||||
});
|
||||
|
||||
if (failedBans.length) {
|
||||
sendSuccessMessage(
|
||||
pluginData,
|
||||
msg.channel,
|
||||
`Banned ${successfulBanCount} users, ${failedBans.length} failed: ${failedBans.join(" ")}`,
|
||||
);
|
||||
} else {
|
||||
sendSuccessMessage(pluginData, msg.channel, `Banned ${successfulBanCount} users successfully`);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
35
backend/src/plugins/ModActions/commands/SoftbanCommand.ts
Normal file
35
backend/src/plugins/ModActions/commands/SoftbanCommand.ts
Normal file
|
@ -0,0 +1,35 @@
|
|||
import { modActionsCommand } from "../types";
|
||||
import { commandTypeHelpers as ct } from "../../../commandTypes";
|
||||
import { trimPluginDescription } from "../../../utils";
|
||||
import { actualKickMemberCmd } from "../functions/actualKickMemberCmd";
|
||||
|
||||
const opts = {
|
||||
mod: ct.member({ option: true }),
|
||||
notify: ct.string({ option: true }),
|
||||
"notify-channel": ct.textChannel({ option: true }),
|
||||
};
|
||||
|
||||
export const SoftbanCmd = modActionsCommand({
|
||||
trigger: "softban",
|
||||
permission: "can_kick",
|
||||
description: trimPluginDescription(`
|
||||
"Softban" the specified user by banning and immediately unbanning them. Effectively a kick with message deletions.
|
||||
This command will be removed in the future, please use kick with the \`- clean\` argument instead
|
||||
`),
|
||||
|
||||
signature: [
|
||||
{
|
||||
user: ct.string(),
|
||||
reason: ct.string({ required: false, catchAll: true }),
|
||||
|
||||
...opts,
|
||||
},
|
||||
],
|
||||
|
||||
async run({ pluginData, message: msg, args }) {
|
||||
await actualKickMemberCmd(pluginData, msg, { clean: true, ...args });
|
||||
await msg.channel.createMessage(
|
||||
"Softban will be removed in the future - please use the kick command with the `-clean` argument instead!",
|
||||
);
|
||||
},
|
||||
});
|
76
backend/src/plugins/ModActions/commands/UnbanCmd.ts
Normal file
76
backend/src/plugins/ModActions/commands/UnbanCmd.ts
Normal file
|
@ -0,0 +1,76 @@
|
|||
import { modActionsCommand, IgnoredEventType } from "../types";
|
||||
import { commandTypeHelpers as ct } from "../../../commandTypes";
|
||||
import { sendErrorMessage, hasPermission, sendSuccessMessage } from "../../../pluginUtils";
|
||||
import { resolveUser, stripObjectToScalars } from "../../../utils";
|
||||
import { formatReasonWithAttachments } from "../functions/formatReasonWithAttachments";
|
||||
import { LogType } from "src/data/LogType";
|
||||
import { ignoreEvent } from "../functions/ignoreEvent";
|
||||
import { CaseTypes } from "src/data/CaseTypes";
|
||||
import { CasesPlugin } from "src/plugins/Cases/CasesPlugin";
|
||||
|
||||
const opts = {
|
||||
mod: ct.member({ option: true }),
|
||||
};
|
||||
|
||||
export const UnbanCmd = modActionsCommand({
|
||||
trigger: "unban",
|
||||
permission: "can_ban",
|
||||
description: "Unban the specified member",
|
||||
|
||||
signature: [
|
||||
{
|
||||
user: ct.string(),
|
||||
reason: ct.string({ required: false, catchAll: true }),
|
||||
|
||||
...opts,
|
||||
},
|
||||
],
|
||||
|
||||
async run({ pluginData, message: msg, args }) {
|
||||
const user = await resolveUser(pluginData.client, args.user);
|
||||
if (!user) return sendErrorMessage(pluginData, msg.channel, `User not found`);
|
||||
|
||||
// The moderator who did the action is the message author or, if used, the specified -mod
|
||||
let mod = msg.member;
|
||||
if (args.mod) {
|
||||
if (!hasPermission(pluginData, "can_act_as_other", { message: msg, channelId: msg.channel.id })) {
|
||||
sendErrorMessage(pluginData, msg.channel, "No permission for -mod");
|
||||
return;
|
||||
}
|
||||
|
||||
mod = args.mod;
|
||||
}
|
||||
|
||||
pluginData.state.serverLogs.ignoreLog(LogType.MEMBER_UNBAN, user.id);
|
||||
|
||||
try {
|
||||
ignoreEvent(pluginData, IgnoredEventType.Unban, user.id);
|
||||
await pluginData.guild.unbanMember(user.id);
|
||||
} catch (e) {
|
||||
sendErrorMessage(pluginData, msg.channel, "Failed to unban member; are you sure they're banned?");
|
||||
return;
|
||||
}
|
||||
|
||||
const reason = formatReasonWithAttachments(args.reason, msg.attachments);
|
||||
|
||||
// Create a case
|
||||
const casesPlugin = pluginData.getPlugin(CasesPlugin);
|
||||
const createdCase = await casesPlugin.createCase({
|
||||
userId: user.id,
|
||||
modId: mod.id,
|
||||
type: CaseTypes.Unban,
|
||||
reason,
|
||||
ppId: mod.id !== msg.author.id ? msg.author.id : null,
|
||||
});
|
||||
|
||||
// Confirm the action
|
||||
sendSuccessMessage(pluginData, msg.channel, `Member unbanned (Case #${createdCase.case_number})`);
|
||||
|
||||
// Log the action
|
||||
pluginData.state.serverLogs.log(LogType.MEMBER_UNBAN, {
|
||||
mod: stripObjectToScalars(mod.user),
|
||||
userId: user.id,
|
||||
reason,
|
||||
});
|
||||
},
|
||||
});
|
26
backend/src/plugins/ModActions/commands/UnhideCaseCmd.ts
Normal file
26
backend/src/plugins/ModActions/commands/UnhideCaseCmd.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
import { modActionsCommand } from "../types";
|
||||
import { commandTypeHelpers as ct } from "../../../commandTypes";
|
||||
import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils";
|
||||
|
||||
export const UnhideCaseCmd = modActionsCommand({
|
||||
trigger: ["unhide", "unhidecase", "unhide_case"],
|
||||
permission: "can_hidecase",
|
||||
description: "Un-hide the specified case, making it appear in !cases and !info again",
|
||||
|
||||
signature: [
|
||||
{
|
||||
caseNum: ct.number(),
|
||||
},
|
||||
],
|
||||
|
||||
async run({ pluginData, message: msg, args }) {
|
||||
const theCase = await pluginData.state.cases.findByCaseNumber(args.caseNum);
|
||||
if (!theCase) {
|
||||
sendErrorMessage(pluginData, msg.channel, "Case not found!");
|
||||
return;
|
||||
}
|
||||
|
||||
await pluginData.state.cases.setHidden(theCase.id, false);
|
||||
sendSuccessMessage(pluginData, msg.channel, `Case #${theCase.case_number} is no longer hidden!`);
|
||||
},
|
||||
});
|
76
backend/src/plugins/ModActions/commands/UnmuteCmd.ts
Normal file
76
backend/src/plugins/ModActions/commands/UnmuteCmd.ts
Normal file
|
@ -0,0 +1,76 @@
|
|||
import { modActionsCommand } from "../types";
|
||||
import { commandTypeHelpers as ct } from "../../../commandTypes";
|
||||
import { canActOn, sendErrorMessage } from "../../../pluginUtils";
|
||||
import { resolveUser, resolveMember } from "../../../utils";
|
||||
import { MutesPlugin } from "src/plugins/Mutes/MutesPlugin";
|
||||
import { actualUnmuteCmd } from "../functions/actualUnmuteUserCmd";
|
||||
import { isBanned } from "../functions/isBanned";
|
||||
import { plugin } from "knub";
|
||||
|
||||
const opts = {
|
||||
mod: ct.member({ option: true }),
|
||||
};
|
||||
|
||||
export const UnmuteCmd = modActionsCommand({
|
||||
trigger: "unmute",
|
||||
permission: "can_mute",
|
||||
description: "Unmute the specified member",
|
||||
|
||||
signature: [
|
||||
{
|
||||
user: ct.string(),
|
||||
time: ct.delay(),
|
||||
reason: ct.string({ required: false, catchAll: true }),
|
||||
|
||||
...opts,
|
||||
},
|
||||
{
|
||||
user: ct.string(),
|
||||
reason: ct.string({ required: false, catchAll: true }),
|
||||
|
||||
...opts,
|
||||
},
|
||||
],
|
||||
|
||||
async run({ pluginData, message: msg, args }) {
|
||||
const user = await resolveUser(pluginData.client, args.user);
|
||||
if (!user) return sendErrorMessage(pluginData, msg.channel, `User not found`);
|
||||
const memberToUnmute = await resolveMember(pluginData.client, pluginData.guild, user.id);
|
||||
const mutesPlugin = pluginData.getPlugin(MutesPlugin);
|
||||
const hasMuteRole = memberToUnmute && mutesPlugin.hasMutedRole(memberToUnmute);
|
||||
|
||||
// Check if they're muted in the first place
|
||||
if (!(await pluginData.state.mutes.isMuted(args.user)) && !hasMuteRole) {
|
||||
sendErrorMessage(pluginData, msg.channel, "Cannot unmute: member is not muted");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!memberToUnmute) {
|
||||
const banned = await isBanned(pluginData, memberToUnmute.id);
|
||||
const prefix = pluginData.guildConfig.prefix;
|
||||
if (banned) {
|
||||
sendErrorMessage(
|
||||
pluginData,
|
||||
msg.channel,
|
||||
`User is banned. Use \`${prefix}forceunmute\` to unmute them anyway.`,
|
||||
);
|
||||
} else {
|
||||
sendErrorMessage(
|
||||
pluginData,
|
||||
msg.channel,
|
||||
`User is not on the server. Use \`${prefix}forceunmute\` to unmute them anyway.`,
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure we're allowed to unmute this member
|
||||
if (memberToUnmute && !canActOn(pluginData, msg.member, memberToUnmute)) {
|
||||
sendErrorMessage(pluginData, msg.channel, "Cannot unmute: insufficient permissions");
|
||||
return;
|
||||
}
|
||||
|
||||
actualUnmuteCmd(pluginData, user, msg, args);
|
||||
},
|
||||
});
|
|
@ -13,10 +13,15 @@ export const UpdateCmd = modActionsCommand({
|
|||
description:
|
||||
"Update the specified case (or, if case number is omitted, your latest case) by adding more notes/details to it",
|
||||
|
||||
signature: {
|
||||
caseNumber: ct.number(),
|
||||
note: ct.string({ required: false, catchAll: true }),
|
||||
},
|
||||
signature: [
|
||||
{
|
||||
caseNumber: ct.number(),
|
||||
note: ct.string({ required: false, catchAll: true }),
|
||||
},
|
||||
{
|
||||
note: ct.string({ catchAll: true }),
|
||||
},
|
||||
],
|
||||
|
||||
async run({ pluginData, message: msg, args }) {
|
||||
let theCase: Case;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { eventListener } from "knub";
|
||||
import { IgnoredEventType, ModActionsPluginType } from "../types";
|
||||
import { isEventIgnored } from "../functions/isEventIgnored";
|
||||
import { clearIgnoredEvent } from "../functions/clearIgnoredEvents";
|
||||
import { clearIgnoredEvents } from "../functions/clearIgnoredEvents";
|
||||
import { Constants as ErisConstants } from "eris";
|
||||
import { safeFindRelevantAuditLogEntry } from "../functions/safeFindRelevantAuditLogEntry";
|
||||
import { CasesPlugin } from "../../Cases/CasesPlugin";
|
||||
|
@ -15,7 +15,7 @@ export const CreateBanCaseOnManualBanEvt = eventListener<ModActionsPluginType>()
|
|||
"guildBanAdd",
|
||||
async ({ pluginData, args: { guild, user } }) => {
|
||||
if (isEventIgnored(pluginData, IgnoredEventType.Ban, user.id)) {
|
||||
clearIgnoredEvent(pluginData, IgnoredEventType.Ban, user.id);
|
||||
clearIgnoredEvents(pluginData, IgnoredEventType.Ban, user.id);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { eventListener } from "knub";
|
||||
import { IgnoredEventType, ModActionsPluginType } from "../types";
|
||||
import { isEventIgnored } from "../functions/isEventIgnored";
|
||||
import { clearIgnoredEvent } from "../functions/clearIgnoredEvents";
|
||||
import { clearIgnoredEvents } from "../functions/clearIgnoredEvents";
|
||||
import { Constants as ErisConstants } from "eris";
|
||||
import { safeFindRelevantAuditLogEntry } from "../functions/safeFindRelevantAuditLogEntry";
|
||||
import { CasesPlugin } from "../../Cases/CasesPlugin";
|
||||
|
@ -18,7 +18,7 @@ export const CreateKickCaseOnManualKickEvt = eventListener<ModActionsPluginType>
|
|||
"guildMemberRemove",
|
||||
async ({ pluginData, args: { member } }) => {
|
||||
if (isEventIgnored(pluginData, IgnoredEventType.Kick, member.id)) {
|
||||
clearIgnoredEvent(pluginData, IgnoredEventType.Kick, member.id);
|
||||
clearIgnoredEvents(pluginData, IgnoredEventType.Kick, member.id);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { eventListener } from "knub";
|
||||
import { IgnoredEventType, ModActionsPluginType } from "../types";
|
||||
import { isEventIgnored } from "../functions/isEventIgnored";
|
||||
import { clearIgnoredEvent } from "../functions/clearIgnoredEvents";
|
||||
import { clearIgnoredEvents } from "../functions/clearIgnoredEvents";
|
||||
import { Constants as ErisConstants } from "eris";
|
||||
import { safeFindRelevantAuditLogEntry } from "../functions/safeFindRelevantAuditLogEntry";
|
||||
import { CasesPlugin } from "../../Cases/CasesPlugin";
|
||||
|
@ -15,7 +15,7 @@ export const CreateUnbanCaseOnManualUnbanEvt = eventListener<ModActionsPluginTyp
|
|||
"guildBanRemove",
|
||||
async ({ pluginData, args: { guild, user } }) => {
|
||||
if (isEventIgnored(pluginData, IgnoredEventType.Unban, user.id)) {
|
||||
clearIgnoredEvent(pluginData, IgnoredEventType.Unban, user.id);
|
||||
clearIgnoredEvents(pluginData, IgnoredEventType.Unban, user.id);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
import { eventListener } from "knub";
|
||||
import { ModActionsPluginType } from "../types";
|
||||
|
||||
/**
|
||||
* Show an alert if a member with prior notes joins the server
|
||||
*/
|
||||
export const PostAlertOnMemberJoinEvt = eventListener<ModActionsPluginType>()(
|
||||
"guildMemberAdd",
|
||||
async ({ pluginData, args: { guild, member } }) => {
|
||||
const config = pluginData.config.get();
|
||||
|
||||
if (!config.alert_on_rejoin) return;
|
||||
|
||||
const alertChannelId = config.alert_channel;
|
||||
if (!alertChannelId) return;
|
||||
|
||||
const actions = await pluginData.state.cases.getByUserId(member.id);
|
||||
|
||||
if (actions.length) {
|
||||
const alertChannel: any = pluginData.guild.channels.get(alertChannelId);
|
||||
alertChannel.send(
|
||||
`<@!${member.id}> (${member.user.username}#${member.user.discriminator} \`${member.id}\`) joined with ${actions.length} prior record(s)`,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
107
backend/src/plugins/ModActions/functions/actualKickMemberCmd.ts
Normal file
107
backend/src/plugins/ModActions/functions/actualKickMemberCmd.ts
Normal file
|
@ -0,0 +1,107 @@
|
|||
import { Member, TextChannel } from "eris";
|
||||
import { LogType } from "src/data/LogType";
|
||||
import { IgnoredEventType, ModActionsPluginType } from "../types";
|
||||
import { errorMessage, resolveUser, resolveMember } from "src/utils";
|
||||
import { PluginData } from "knub";
|
||||
import { sendErrorMessage, canActOn, sendSuccessMessage } from "src/pluginUtils";
|
||||
import { hasPermission } from "knub/dist/helpers";
|
||||
import { readContactMethodsFromArgs } from "./readContactMethodsFromArgs";
|
||||
import { formatReasonWithAttachments } from "./formatReasonWithAttachments";
|
||||
import { kickMember } from "./kickMember";
|
||||
import { ignoreEvent } from "./ignoreEvent";
|
||||
import { isBanned } from "./isBanned";
|
||||
|
||||
export async function actualKickMemberCmd(
|
||||
pluginData: PluginData<ModActionsPluginType>,
|
||||
msg,
|
||||
args: {
|
||||
user: string;
|
||||
reason: string;
|
||||
mod: Member;
|
||||
notify?: string;
|
||||
"notify-channel"?: TextChannel;
|
||||
clean?: boolean;
|
||||
},
|
||||
) {
|
||||
const user = await resolveUser(pluginData.client, args.user);
|
||||
if (!user) return sendErrorMessage(pluginData, msg.channel, `User not found`);
|
||||
|
||||
const memberToKick = await resolveMember(pluginData.client, pluginData.guild, user.id);
|
||||
|
||||
if (!memberToKick) {
|
||||
const banned = await isBanned(pluginData, user.id);
|
||||
if (banned) {
|
||||
sendErrorMessage(pluginData, msg.channel, `User is banned`);
|
||||
} else {
|
||||
sendErrorMessage(pluginData, msg.channel, `User not found on the server`);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure we're allowed to kick this member
|
||||
if (!canActOn(pluginData, msg.member, memberToKick)) {
|
||||
sendErrorMessage(pluginData, msg.channel, "Cannot kick: insufficient permissions");
|
||||
return;
|
||||
}
|
||||
|
||||
// The moderator who did the action is the message author or, if used, the specified -mod
|
||||
let mod = msg.member;
|
||||
if (args.mod) {
|
||||
if (!hasPermission(pluginData.config.getForMessage(msg), "can_act_as_other")) {
|
||||
sendErrorMessage(pluginData, msg.channel, "No permission for -mod");
|
||||
return;
|
||||
}
|
||||
|
||||
mod = args.mod;
|
||||
}
|
||||
|
||||
let contactMethods;
|
||||
try {
|
||||
contactMethods = readContactMethodsFromArgs(args);
|
||||
} catch (e) {
|
||||
sendErrorMessage(pluginData, msg.channel, e.message);
|
||||
return;
|
||||
}
|
||||
|
||||
const reason = formatReasonWithAttachments(args.reason, msg.attachments);
|
||||
|
||||
const kickResult = await kickMember(pluginData, memberToKick, reason, {
|
||||
contactMethods,
|
||||
caseArgs: {
|
||||
modId: mod.id,
|
||||
ppId: mod.id !== msg.author.id ? msg.author.id : null,
|
||||
},
|
||||
});
|
||||
|
||||
if (args.clean) {
|
||||
pluginData.state.serverLogs.ignoreLog(LogType.MEMBER_BAN, memberToKick.id);
|
||||
ignoreEvent(pluginData, IgnoredEventType.Ban, memberToKick.id);
|
||||
|
||||
try {
|
||||
await memberToKick.ban(1);
|
||||
} catch (e) {
|
||||
sendErrorMessage(pluginData, msg.channel, "Failed to ban the user to clean messages (-clean)");
|
||||
}
|
||||
|
||||
pluginData.state.serverLogs.ignoreLog(LogType.MEMBER_UNBAN, memberToKick.id);
|
||||
ignoreEvent(pluginData, IgnoredEventType.Unban, memberToKick.id);
|
||||
|
||||
try {
|
||||
await pluginData.guild.unbanMember(memberToKick.id);
|
||||
} catch (e) {
|
||||
sendErrorMessage(pluginData, msg.channel, "Failed to unban the user after banning them (-clean)");
|
||||
}
|
||||
}
|
||||
|
||||
if (kickResult.status === "failed") {
|
||||
msg.channel.createMessage(errorMessage(`Failed to kick user`));
|
||||
return;
|
||||
}
|
||||
|
||||
// Confirm the action to the moderator
|
||||
let response = `Kicked **${memberToKick.user.username}#${memberToKick.user.discriminator}** (Case #${kickResult.case.case_number})`;
|
||||
|
||||
if (kickResult.notifyResult.text) response += ` (${kickResult.notifyResult.text})`;
|
||||
sendSuccessMessage(pluginData, msg.channel, response);
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
import { PluginData } from "knub";
|
||||
import { ModActionsPluginType } from "../types";
|
||||
import { User, Message, Member } from "eris";
|
||||
import { UnknownUser, asSingleLine } from "src/utils";
|
||||
import { sendErrorMessage, sendSuccessMessage, hasPermission } from "src/pluginUtils";
|
||||
import { formatReasonWithAttachments } from "./formatReasonWithAttachments";
|
||||
import { MutesPlugin } from "src/plugins/Mutes/MutesPlugin";
|
||||
import humanizeDuration from "humanize-duration";
|
||||
|
||||
export async function actualUnmuteCmd(
|
||||
pluginData: PluginData<ModActionsPluginType>,
|
||||
user: User | UnknownUser,
|
||||
msg: Message,
|
||||
args: { time?: number; reason?: string; mod?: Member },
|
||||
) {
|
||||
// The moderator who did the action is the message author or, if used, the specified -mod
|
||||
let mod = msg.author;
|
||||
let pp = null;
|
||||
|
||||
if (args.mod) {
|
||||
if (!hasPermission(pluginData, "can_act_as_other", { message: msg, channelId: msg.channel.id })) {
|
||||
sendErrorMessage(pluginData, msg.channel, "No permission for -mod");
|
||||
return;
|
||||
}
|
||||
|
||||
mod = args.mod.user;
|
||||
pp = msg.author;
|
||||
}
|
||||
|
||||
const reason = formatReasonWithAttachments(args.reason, msg.attachments);
|
||||
|
||||
const mutesPlugin = pluginData.getPlugin(MutesPlugin);
|
||||
const result = await mutesPlugin.unmuteUser(user.id, args.time, {
|
||||
modId: mod.id,
|
||||
ppId: pp && pp.id,
|
||||
reason,
|
||||
});
|
||||
|
||||
// Confirm the action to the moderator
|
||||
if (args.time) {
|
||||
const timeUntilUnmute = args.time && humanizeDuration(args.time);
|
||||
sendSuccessMessage(
|
||||
pluginData,
|
||||
msg.channel,
|
||||
asSingleLine(`
|
||||
Unmuting **${user.username}#${user.discriminator}**
|
||||
in ${timeUntilUnmute} (Case #${result.case.case_number})
|
||||
`),
|
||||
);
|
||||
} else {
|
||||
sendSuccessMessage(
|
||||
pluginData,
|
||||
msg.channel,
|
||||
asSingleLine(`
|
||||
Unmuted **${user.username}#${user.discriminator}**
|
||||
(Case #${result.case.case_number})
|
||||
`),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
import { PluginData } from "knub";
|
||||
import { IgnoredEventType, ModActionsPluginType } from "../types";
|
||||
|
||||
export function clearIgnoredEvent(
|
||||
export function clearIgnoredEvents(
|
||||
pluginData: PluginData<ModActionsPluginType>,
|
||||
type: IgnoredEventType,
|
||||
userId: string,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { PluginData } from "knub";
|
||||
import { IgnoredEventType, ModActionsPluginType } from "../types";
|
||||
import { SECONDS } from "../../../utils";
|
||||
import { clearIgnoredEvent } from "./clearIgnoredEvents";
|
||||
import { clearIgnoredEvents } from "./clearIgnoredEvents";
|
||||
|
||||
const DEFAULT_TIMEOUT = 15 * SECONDS;
|
||||
|
||||
|
@ -15,6 +15,6 @@ export function ignoreEvent(
|
|||
|
||||
// Clear after expiry (15sec by default)
|
||||
setTimeout(() => {
|
||||
clearIgnoredEvent(pluginData, type, userId);
|
||||
clearIgnoredEvents(pluginData, type, userId);
|
||||
}, timeout);
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ import { BasePluginType, command } from "knub";
|
|||
import { GuildMutes } from "../../data/GuildMutes";
|
||||
import { GuildCases } from "../../data/GuildCases";
|
||||
import { GuildLogs } from "../../data/GuildLogs";
|
||||
import { GuildArchives } from "../../data/GuildArchives";
|
||||
import { Case } from "../../data/entities/Case";
|
||||
import { CaseArgs } from "../Cases/types";
|
||||
import { TextChannel } from "eris";
|
||||
|
|
|
@ -12,6 +12,9 @@ import { ClearActiveMuteOnRoleRemovalEvt } from "./events/ClearActiveMuteOnRoleR
|
|||
import { ClearMutesWithoutRoleCmd } from "./commands/ClearMutesWithoutRoleCmd";
|
||||
import { ClearMutesCmd } from "./commands/ClearMutesCmd";
|
||||
import { muteUser } from "./functions/muteUser";
|
||||
import { unmuteUser } from "./functions/unmuteUser";
|
||||
import { CaseArgs } from "../Cases/types";
|
||||
import { Member } from "eris";
|
||||
|
||||
const defaultOptions = {
|
||||
config: {
|
||||
|
@ -75,6 +78,16 @@ export const MutesPlugin = zeppelinPlugin<MutesPluginType>()("mutes", {
|
|||
return muteUser(pluginData, userId, muteTime, reason, muteOptions);
|
||||
};
|
||||
},
|
||||
unmuteUser(pluginData) {
|
||||
return (userId: string, unmuteTime: number = null, args: Partial<CaseArgs>) => {
|
||||
return unmuteUser(pluginData, userId, unmuteTime, args);
|
||||
};
|
||||
},
|
||||
hasMutedRole(pluginData) {
|
||||
return (member: Member) => {
|
||||
return member.roles.includes(pluginData.config.get().mute_role);
|
||||
};
|
||||
},
|
||||
},
|
||||
|
||||
onLoad(pluginData) {
|
||||
|
|
|
@ -13,6 +13,7 @@ import { GuildConfigReloaderPlugin } from "./GuildConfigReloader/GuildConfigRelo
|
|||
import { CasesPlugin } from "./Cases/CasesPlugin";
|
||||
import { MutesPlugin } from "./Mutes/MutesPlugin";
|
||||
import { TagsPlugin } from "./Tags/TagsPlugin";
|
||||
import { ModActionsPlugin } from "./ModActions/ModActionsPlugin";
|
||||
import { PostPlugin } from "./Post/PostPlugin";
|
||||
import { AutoDeletePlugin } from "./AutoDelete/AutoDeletePlugin";
|
||||
import { GuildInfoSaverPlugin } from "./GuildInfoSaver/GuildInfoSaverPlugin";
|
||||
|
@ -34,6 +35,7 @@ export const guildPlugins: Array<ZeppelinPluginBlueprint<any>> = [
|
|||
PingableRolesPlugin,
|
||||
PostPlugin,
|
||||
MessageSaverPlugin,
|
||||
ModActionsPlugin,
|
||||
NameHistoryPlugin,
|
||||
RemindersPlugin,
|
||||
RolesPlugin,
|
||||
|
|
Loading…
Add table
Reference in a new issue