3
0
Fork 0
mirror of https://github.com/ZeppelinBot/Zeppelin.git synced 2025-05-14 13:55:03 +00:00

WIP: Note Slash Command

This commit is contained in:
Lily Bergonzat 2024-01-22 17:21:05 +01:00
parent 91339bb01e
commit dfb0e2c19d
29 changed files with 319 additions and 178 deletions

View file

@ -3,6 +3,7 @@
*/
import {
ChatInputCommandInteraction,
GuildMember,
Message,
MessageCreateOptions,
@ -100,12 +101,19 @@ export function makeIoTsConfigParser<Schema extends t.Type<any>>(schema: Schema)
};
}
function isContextInteraction(
context: TextBasedChannel | ChatInputCommandInteraction,
): context is ChatInputCommandInteraction {
return "commandId" in context && !!context.commandId;
}
export async function sendSuccessMessage(
pluginData: AnyPluginData<any>,
channel: TextBasedChannel,
context: TextBasedChannel | ChatInputCommandInteraction,
body: string,
allowedMentions?: MessageMentionOptions,
responseInteraction?: ModalSubmitInteraction,
ephemeral = false,
): Promise<Message | undefined> {
const emoji = pluginData.fullConfig.success_emoji || undefined;
const formattedBody = successMessage(body, emoji);
@ -117,23 +125,44 @@ export async function sendSuccessMessage(
await responseInteraction
.editReply({ content: formattedBody, embeds: [], components: [] })
.catch((err) => logger.error(`Interaction reply failed: ${err}`));
} else {
return channel
return;
}
if (!isContextInteraction(context)) {
// noinspection TypeScriptValidateJSTypes
return context
.send({ ...content }) // Force line break
.catch((err) => {
const channelInfo = "guild" in channel ? `${channel.id} (${channel.guild.id})` : channel.id;
const channelInfo = "guild" in context ? `${context.id} (${context.guild.id})` : context.id;
logger.warn(`Failed to send success message to ${channelInfo}): ${err.code} ${err.message}`);
return undefined;
});
}
const replyMethod = context.replied ? "followUp" : "reply";
return context[replyMethod]({
content: formattedBody,
embeds: [],
components: [],
fetchReply: true,
ephemeral,
}).catch((err) => {
logger.error(`Context reply failed: ${err}`);
return undefined;
});
}
export async function sendErrorMessage(
pluginData: AnyPluginData<any>,
channel: TextBasedChannel,
context: TextBasedChannel | ChatInputCommandInteraction,
body: string,
allowedMentions?: MessageMentionOptions,
responseInteraction?: ModalSubmitInteraction,
ephemeral = false,
): Promise<Message | undefined> {
const emoji = pluginData.fullConfig.error_emoji || undefined;
const formattedBody = errorMessage(body, emoji);
@ -145,15 +174,34 @@ export async function sendErrorMessage(
await responseInteraction
.editReply({ content: formattedBody, embeds: [], components: [] })
.catch((err) => logger.error(`Interaction reply failed: ${err}`));
} else {
return channel
return;
}
if (!isContextInteraction(context)) {
// noinspection TypeScriptValidateJSTypes
return context
.send({ ...content }) // Force line break
.catch((err) => {
const channelInfo = "guild" in channel ? `${channel.id} (${channel.guild.id})` : channel.id;
const channelInfo = "guild" in context ? `${context.id} (${context.guild.id})` : context.id;
logger.warn(`Failed to send error message to ${channelInfo}): ${err.code} ${err.message}`);
return undefined;
});
}
const replyMethod = context.replied ? "followUp" : "reply";
return context[replyMethod]({
content: formattedBody,
embeds: [],
components: [],
fetchReply: true,
ephemeral,
}).catch((err) => {
logger.error(`Context reply failed: ${err}`);
return undefined;
});
}
export function getBaseUrl(pluginData: AnyPluginData<any>) {

View file

@ -28,13 +28,14 @@ import { MassbanCmd } from "./commands/MassBanCmd";
import { MassunbanCmd } from "./commands/MassUnbanCmd";
import { MassmuteCmd } from "./commands/MassmuteCmd";
import { MuteCmd } from "./commands/MuteCmd";
import { NoteCmd } from "./commands/NoteCmd";
import { SoftbanCmd } from "./commands/SoftbanCommand";
import { UnbanCmd } from "./commands/UnbanCmd";
import { UnhideCaseCmd } from "./commands/UnhideCaseCmd";
import { UnmuteCmd } from "./commands/UnmuteCmd";
import { UpdateCmd } from "./commands/UpdateCmd";
import { WarnCmd } from "./commands/WarnCmd";
import { NoteMsgCmd } from "./commands/note/NoteMsgCmd";
import { NoteSlashCmd } from "./commands/note/NoteSlashCmd";
import { AuditLogEvents } from "./events/AuditLogEvents";
import { CreateBanCaseOnManualBanEvt } from "./events/CreateBanCaseOnManualBanEvt";
import { CreateUnbanCaseOnManualUnbanEvt } from "./events/CreateUnbanCaseOnManualUnbanEvt";
@ -52,7 +53,14 @@ import { offModActionsEvent } from "./functions/offModActionsEvent";
import { onModActionsEvent } from "./functions/onModActionsEvent";
import { updateCase } from "./functions/updateCase";
import { warnMember } from "./functions/warnMember";
import { BanOptions, ConfigSchema, KickOptions, ModActionsPluginType, WarnOptions } from "./types";
import {
BanOptions,
ConfigSchema,
KickOptions,
ModActionsPluginType,
WarnOptions,
modActionsSlashGroup,
} from "./types";
const defaultOptions = {
config: {
@ -135,9 +143,18 @@ export const ModActionsPlugin = zeppelinGuildPlugin<ModActionsPluginType>()({
events: [CreateBanCaseOnManualBanEvt, CreateUnbanCaseOnManualUnbanEvt, PostAlertOnMemberJoinEvt, AuditLogEvents],
slashCommands: [
modActionsSlashGroup({
name: "mod",
description: "Moderation actions",
defaultMemberPermissions: "0",
subcommands: [{ type: "slash", ...NoteSlashCmd }],
}),
],
messageCommands: [
UpdateCmd,
NoteCmd,
NoteMsgCmd,
WarnCmd,
MuteCmd,
ForcemuteCmd,

View file

@ -6,13 +6,13 @@ import { canActOn, hasPermission, sendErrorMessage, sendSuccessMessage } from ".
import { renderUserUsername, resolveMember, resolveUser } from "../../../utils";
import { LogsPlugin } from "../../Logs/LogsPlugin";
import { formatReasonWithAttachments } from "../functions/formatReasonWithAttachments";
import { modActionsCmd } from "../types";
import { modActionsMsgCmd } from "../types";
const opts = {
mod: ct.member({ option: true }),
};
export const AddCaseCmd = modActionsCmd({
export const AddCaseCmd = modActionsMsgCmd({
trigger: "addcase",
permission: "can_addcase",
description: "Add an arbitrary case to the specified user without taking any action",

View file

@ -13,7 +13,7 @@ import { banUserId } from "../functions/banUserId";
import { formatReasonWithAttachments } from "../functions/formatReasonWithAttachments";
import { isBanned } from "../functions/isBanned";
import { readContactMethodsFromArgs } from "../functions/readContactMethodsFromArgs";
import { modActionsCmd } from "../types";
import { modActionsMsgCmd } from "../types";
const opts = {
mod: ct.member({ option: true }),
@ -22,7 +22,7 @@ const opts = {
"delete-days": ct.number({ option: true, shortcut: "d" }),
};
export const BanCmd = modActionsCmd({
export const BanCmd = modActionsMsgCmd({
trigger: "ban",
permission: "can_ban",
description: "Ban or Tempban the specified member",
@ -45,13 +45,12 @@ export const BanCmd = modActionsCmd({
async run({ pluginData, message: msg, args }) {
const user = await resolveUser(pluginData.client, args.user);
if (!user.id) {
sendErrorMessage(pluginData, msg.channel, `User not found`);
return;
}
const time = args["time"] ? args["time"] : null;
const reason = formatReasonWithAttachments(args.reason, [...msg.attachments.values()]);
const memberToBan = await resolveMember(pluginData.client, pluginData.guild, user.id);
// The moderator who did the action is the message author or, if used, the specified -mod
let mod = msg.member;
@ -64,85 +63,25 @@ export const BanCmd = modActionsCmd({
mod = args.mod;
}
const time = args["time"] ? args["time"] : null;
const reason = formatReasonWithAttachments(args.reason, [...msg.attachments.values()]);
// acquire a lock because of the needed user-inputs below (if banned/not on server)
const lock = await pluginData.locks.acquire(banLock(user));
let forceban = false;
const existingTempban = await pluginData.state.tempbans.findExistingTempbanForUserId(user.id);
if (!memberToBan) {
const banned = await isBanned(pluginData, user.id);
if (banned) {
// Abort if trying to ban user indefinitely if they are already banned indefinitely
if (!existingTempban && !time) {
sendErrorMessage(pluginData, msg.channel, `User is already banned indefinitely.`);
return;
}
// Ask the mod if we should update the existing ban
const reply = await waitForButtonConfirm(
msg.channel,
{ content: "Failed to message the user. Log the warning anyway?" },
{ confirmText: "Yes", cancelText: "No", restrictToId: msg.member.id },
);
if (!reply) {
sendErrorMessage(pluginData, msg.channel, "User already banned, update cancelled by moderator");
lock.unlock();
return;
} else {
// Update or add new tempban / remove old tempban
if (time && time > 0) {
if (existingTempban) {
await pluginData.state.tempbans.updateExpiryTime(user.id, time, mod.id);
} else {
await pluginData.state.tempbans.addTempban(user.id, time, mod.id);
}
const tempban = (await pluginData.state.tempbans.findExistingTempbanForUserId(user.id))!;
registerExpiringTempban(tempban);
} else if (existingTempban) {
clearExpiringTempban(existingTempban);
pluginData.state.tempbans.clear(user.id);
}
// Create a new case for the updated ban since we never stored the old case id and log the action
const casesPlugin = pluginData.getPlugin(CasesPlugin);
const createdCase = await casesPlugin.createCase({
modId: mod.id,
type: CaseTypes.Ban,
userId: user.id,
reason,
noteDetails: [`Ban updated to ${time ? humanizeDuration(time) : "indefinite"}`],
});
if (time) {
pluginData.getPlugin(LogsPlugin).logMemberTimedBan({
mod: mod.user,
user,
caseNumber: createdCase.case_number,
reason,
banTime: humanizeDuration(time),
});
} else {
pluginData.getPlugin(LogsPlugin).logMemberBan({
mod: mod.user,
user,
caseNumber: createdCase.case_number,
reason,
});
}
sendSuccessMessage(
pluginData,
msg.channel,
`Ban updated to ${time ? "expire in " + humanizeDuration(time) + " from now" : "indefinite"}`,
);
lock.unlock();
return;
}
} else {
if (!banned) {
// Ask the mod if we should upgrade to a forceban as the user is not on the server
const reply = await waitForButtonConfirm(
msg.channel,
{ content: "User not on server, forceban instead?" },
{ confirmText: "Yes", cancelText: "No", restrictToId: msg.member.id },
);
if (!reply) {
sendErrorMessage(pluginData, msg.channel, "User not on server, ban cancelled by moderator");
lock.unlock();
@ -151,6 +90,73 @@ export const BanCmd = modActionsCmd({
forceban = true;
}
}
// Abort if trying to ban user indefinitely if they are already banned indefinitely
if (!existingTempban && !time) {
sendErrorMessage(pluginData, msg.channel, `User is already banned indefinitely.`);
return;
}
// Ask the mod if we should update the existing ban
const reply = await waitForButtonConfirm(
msg.channel,
{ content: "Failed to message the user. Log the warning anyway?" },
{ confirmText: "Yes", cancelText: "No", restrictToId: msg.member.id },
);
if (!reply) {
sendErrorMessage(pluginData, msg.channel, "User already banned, update cancelled by moderator");
lock.unlock();
return;
}
// Update or add new tempban / remove old tempban
if (time && time > 0) {
if (existingTempban) {
await pluginData.state.tempbans.updateExpiryTime(user.id, time, mod.id);
} else {
await pluginData.state.tempbans.addTempban(user.id, time, mod.id);
}
const tempban = (await pluginData.state.tempbans.findExistingTempbanForUserId(user.id))!;
registerExpiringTempban(tempban);
} else if (existingTempban) {
clearExpiringTempban(existingTempban);
pluginData.state.tempbans.clear(user.id);
}
// Create a new case for the updated ban since we never stored the old case id and log the action
const casesPlugin = pluginData.getPlugin(CasesPlugin);
const createdCase = await casesPlugin.createCase({
modId: mod.id,
type: CaseTypes.Ban,
userId: user.id,
reason,
noteDetails: [`Ban updated to ${time ? humanizeDuration(time) : "indefinite"}`],
});
if (time) {
pluginData.getPlugin(LogsPlugin).logMemberTimedBan({
mod: mod.user,
user,
caseNumber: createdCase.case_number,
reason,
banTime: humanizeDuration(time),
});
} else {
pluginData.getPlugin(LogsPlugin).logMemberBan({
mod: mod.user,
user,
caseNumber: createdCase.case_number,
reason,
});
}
sendSuccessMessage(
pluginData,
msg.channel,
`Ban updated to ${time ? "expire in " + humanizeDuration(time) + " from now" : "indefinite"}`,
);
lock.unlock();
return;
}
// Make sure we're allowed to ban this member if they are on the server

View file

@ -1,9 +1,9 @@
import { commandTypeHelpers as ct } from "../../../commandTypes";
import { CasesPlugin } from "../../../plugins/Cases/CasesPlugin";
import { sendErrorMessage } from "../../../pluginUtils";
import { modActionsCmd } from "../types";
import { modActionsMsgCmd } from "../types";
export const CaseCmd = modActionsCmd({
export const CaseCmd = modActionsMsgCmd({
trigger: "case",
permission: "can_view",
description: "Show information about a specific case",

View file

@ -7,7 +7,7 @@ import { createPaginatedMessage } from "../../../utils/createPaginatedMessage";
import { getChunkedEmbedFields } from "../../../utils/getChunkedEmbedFields";
import { getGuildPrefix } from "../../../utils/getGuildPrefix";
import { CasesPlugin } from "../../Cases/CasesPlugin";
import { modActionsCmd } from "../types";
import { modActionsMsgCmd } from "../types";
const opts = {
mod: ct.userId({ option: true }),
@ -15,7 +15,7 @@ const opts = {
const casesPerPage = 5;
export const CasesModCmd = modActionsCmd({
export const CasesModCmd = modActionsMsgCmd({
trigger: ["cases", "modlogs", "infractions"],
permission: "can_view",
description: "Show the most recent 5 cases by the specified -mod",

View file

@ -7,7 +7,7 @@ import { UnknownUser, chunkArray, emptyEmbedValue, renderUserUsername, resolveUs
import { asyncMap } from "../../../utils/async";
import { getChunkedEmbedFields } from "../../../utils/getChunkedEmbedFields";
import { getGuildPrefix } from "../../../utils/getGuildPrefix";
import { modActionsCmd } from "../types";
import { modActionsMsgCmd } from "../types";
const opts = {
expand: ct.bool({ option: true, isSwitch: true, shortcut: "e" }),
@ -21,7 +21,7 @@ const opts = {
unbans: ct.switchOption({ def: false, shortcut: "ub" }),
};
export const CasesUserCmd = modActionsCmd({
export const CasesUserCmd = modActionsMsgCmd({
trigger: ["cases", "modlogs"],
permission: "can_view",
description: "Show a list of cases the specified user has",

View file

@ -6,9 +6,9 @@ import { SECONDS, trimLines } from "../../../utils";
import { CasesPlugin } from "../../Cases/CasesPlugin";
import { LogsPlugin } from "../../Logs/LogsPlugin";
import { TimeAndDatePlugin } from "../../TimeAndDate/TimeAndDatePlugin";
import { modActionsCmd } from "../types";
import { modActionsMsgCmd } from "../types";
export const DeleteCaseCmd = modActionsCmd({
export const DeleteCaseCmd = modActionsMsgCmd({
trigger: ["delete_case", "deletecase"],
permission: "can_deletecase",
description: trimLines(`

View file

@ -9,13 +9,13 @@ import { LogsPlugin } from "../../Logs/LogsPlugin";
import { formatReasonWithAttachments } from "../functions/formatReasonWithAttachments";
import { ignoreEvent } from "../functions/ignoreEvent";
import { isBanned } from "../functions/isBanned";
import { IgnoredEventType, modActionsCmd } from "../types";
import { IgnoredEventType, modActionsMsgCmd } from "../types";
const opts = {
mod: ct.member({ option: true }),
};
export const ForcebanCmd = modActionsCmd({
export const ForcebanCmd = modActionsMsgCmd({
trigger: "forceban",
permission: "can_ban",
description: "Force-ban the specified user, even if they aren't on the server",

View file

@ -2,7 +2,7 @@ import { commandTypeHelpers as ct } from "../../../commandTypes";
import { canActOn, sendErrorMessage } from "../../../pluginUtils";
import { resolveMember, resolveUser } from "../../../utils";
import { actualMuteUserCmd } from "../functions/actualMuteUserCmd";
import { modActionsCmd } from "../types";
import { modActionsMsgCmd } from "../types";
const opts = {
mod: ct.member({ option: true }),
@ -10,7 +10,7 @@ const opts = {
"notify-channel": ct.textChannel({ option: true }),
};
export const ForcemuteCmd = modActionsCmd({
export const ForcemuteCmd = modActionsMsgCmd({
trigger: "forcemute",
permission: "can_mute",
description: "Force-mute the specified user, even if they're not on the server",

View file

@ -2,13 +2,13 @@ import { commandTypeHelpers as ct } from "../../../commandTypes";
import { canActOn, sendErrorMessage } from "../../../pluginUtils";
import { resolveMember, resolveUser } from "../../../utils";
import { actualUnmuteCmd } from "../functions/actualUnmuteUserCmd";
import { modActionsCmd } from "../types";
import { modActionsMsgCmd } from "../types";
const opts = {
mod: ct.member({ option: true }),
};
export const ForceUnmuteCmd = modActionsCmd({
export const ForceUnmuteCmd = modActionsMsgCmd({
trigger: "forceunmute",
permission: "can_mute",
description: "Force-unmute the specified user, even if they're not on the server",

View file

@ -1,8 +1,8 @@
import { commandTypeHelpers as ct } from "../../../commandTypes";
import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils";
import { modActionsCmd } from "../types";
import { modActionsMsgCmd } from "../types";
export const HideCaseCmd = modActionsCmd({
export const HideCaseCmd = modActionsMsgCmd({
trigger: ["hide", "hidecase", "hide_case"],
permission: "can_hidecase",
description: "Hide the specified case so it doesn't appear in !cases or !info",

View file

@ -1,6 +1,6 @@
import { commandTypeHelpers as ct } from "../../../commandTypes";
import { actualKickMemberCmd } from "../functions/actualKickMemberCmd";
import { modActionsCmd } from "../types";
import { modActionsMsgCmd } from "../types";
const opts = {
mod: ct.member({ option: true }),
@ -9,7 +9,7 @@ const opts = {
clean: ct.bool({ option: true, isSwitch: true }),
};
export const KickCmd = modActionsCmd({
export const KickCmd = modActionsMsgCmd({
trigger: "kick",
permission: "can_kick",
description: "Kick the specified member",

View file

@ -11,9 +11,9 @@ import { DAYS, MINUTES, SECONDS, noop } from "../../../utils";
import { LogsPlugin } from "../../Logs/LogsPlugin";
import { formatReasonWithAttachments } from "../functions/formatReasonWithAttachments";
import { ignoreEvent } from "../functions/ignoreEvent";
import { IgnoredEventType, modActionsCmd } from "../types";
import { IgnoredEventType, modActionsMsgCmd } from "../types";
export const MassbanCmd = modActionsCmd({
export const MassbanCmd = modActionsMsgCmd({
trigger: "massban",
permission: "can_massban",
description: "Mass-ban a list of user IDs",

View file

@ -9,9 +9,9 @@ import { LogsPlugin } from "../../Logs/LogsPlugin";
import { formatReasonWithAttachments } from "../functions/formatReasonWithAttachments";
import { ignoreEvent } from "../functions/ignoreEvent";
import { isBanned } from "../functions/isBanned";
import { IgnoredEventType, modActionsCmd } from "../types";
import { IgnoredEventType, modActionsMsgCmd } from "../types";
export const MassunbanCmd = modActionsCmd({
export const MassunbanCmd = modActionsMsgCmd({
trigger: "massunban",
permission: "can_massunban",
description: "Mass-unban a list of user IDs",

View file

@ -7,9 +7,9 @@ import { canActOn, sendErrorMessage, sendSuccessMessage } from "../../../pluginU
import { MutesPlugin } from "../../../plugins/Mutes/MutesPlugin";
import { LogsPlugin } from "../../Logs/LogsPlugin";
import { formatReasonWithAttachments } from "../functions/formatReasonWithAttachments";
import { modActionsCmd } from "../types";
import { modActionsMsgCmd } from "../types";
export const MassmuteCmd = modActionsCmd({
export const MassmuteCmd = modActionsMsgCmd({
trigger: "massmute",
permission: "can_massmute",
description: "Mass-mute a list of user IDs",

View file

@ -4,7 +4,7 @@ import { resolveMember, resolveUser } from "../../../utils";
import { waitForButtonConfirm } from "../../../utils/waitForInteraction";
import { actualMuteUserCmd } from "../functions/actualMuteUserCmd";
import { isBanned } from "../functions/isBanned";
import { modActionsCmd } from "../types";
import { modActionsMsgCmd } from "../types";
const opts = {
mod: ct.member({ option: true }),
@ -12,7 +12,7 @@ const opts = {
"notify-channel": ct.textChannel({ option: true }),
};
export const MuteCmd = modActionsCmd({
export const MuteCmd = modActionsMsgCmd({
trigger: "mute",
permission: "can_mute",
description: "Mute the specified member",

View file

@ -1,54 +0,0 @@
import { commandTypeHelpers as ct } from "../../../commandTypes";
import { CaseTypes } from "../../../data/CaseTypes";
import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils";
import { renderUserUsername, resolveUser } from "../../../utils";
import { CasesPlugin } from "../../Cases/CasesPlugin";
import { LogsPlugin } from "../../Logs/LogsPlugin";
import { formatReasonWithAttachments } from "../functions/formatReasonWithAttachments";
import { modActionsCmd } from "../types";
export const NoteCmd = modActionsCmd({
trigger: "note",
permission: "can_note",
description: "Add a note to the specified user",
signature: {
user: ct.string(),
note: ct.string({ required: false, catchAll: true }),
},
async run({ pluginData, message: msg, args }) {
const user = await resolveUser(pluginData.client, args.user);
if (!user.id) {
sendErrorMessage(pluginData, msg.channel, `User not found`);
return;
}
if (!args.note && msg.attachments.size === 0) {
sendErrorMessage(pluginData, msg.channel, "Text or attachment required");
return;
}
const userName = renderUserUsername(user);
const reason = formatReasonWithAttachments(args.note, [...msg.attachments.values()]);
const casesPlugin = pluginData.getPlugin(CasesPlugin);
const createdCase = await casesPlugin.createCase({
userId: user.id,
modId: msg.author.id,
type: CaseTypes.Note,
reason,
});
pluginData.getPlugin(LogsPlugin).logMemberNote({
mod: msg.author,
user,
caseNumber: createdCase.case_number,
reason,
});
sendSuccessMessage(pluginData, msg.channel, `Note added on **${userName}** (Case #${createdCase.case_number})`);
pluginData.state.events.emit("note", user.id, reason);
},
});

View file

@ -1,7 +1,7 @@
import { commandTypeHelpers as ct } from "../../../commandTypes";
import { trimPluginDescription } from "../../../utils";
import { actualKickMemberCmd } from "../functions/actualKickMemberCmd";
import { modActionsCmd } from "../types";
import { modActionsMsgCmd } from "../types";
const opts = {
mod: ct.member({ option: true }),
@ -9,7 +9,7 @@ const opts = {
"notify-channel": ct.textChannel({ option: true }),
};
export const SoftbanCmd = modActionsCmd({
export const SoftbanCmd = modActionsMsgCmd({
trigger: "softban",
permission: "can_kick",
description: trimPluginDescription(`

View file

@ -9,13 +9,13 @@ import { resolveUser } from "../../../utils";
import { LogsPlugin } from "../../Logs/LogsPlugin";
import { formatReasonWithAttachments } from "../functions/formatReasonWithAttachments";
import { ignoreEvent } from "../functions/ignoreEvent";
import { IgnoredEventType, modActionsCmd } from "../types";
import { IgnoredEventType, modActionsMsgCmd } from "../types";
const opts = {
mod: ct.member({ option: true }),
};
export const UnbanCmd = modActionsCmd({
export const UnbanCmd = modActionsMsgCmd({
trigger: "unban",
permission: "can_unban",
description: "Unban the specified member",

View file

@ -1,8 +1,8 @@
import { commandTypeHelpers as ct } from "../../../commandTypes";
import { sendErrorMessage, sendSuccessMessage } from "../../../pluginUtils";
import { modActionsCmd } from "../types";
import { modActionsMsgCmd } from "../types";
export const UnhideCaseCmd = modActionsCmd({
export const UnhideCaseCmd = modActionsMsgCmd({
trigger: ["unhide", "unhidecase", "unhide_case"],
permission: "can_hidecase",
description: "Un-hide the specified case, making it appear in !cases and !info again",

View file

@ -5,13 +5,13 @@ import { resolveMember, resolveUser } from "../../../utils";
import { waitForButtonConfirm } from "../../../utils/waitForInteraction";
import { actualUnmuteCmd } from "../functions/actualUnmuteUserCmd";
import { isBanned } from "../functions/isBanned";
import { modActionsCmd } from "../types";
import { modActionsMsgCmd } from "../types";
const opts = {
mod: ct.member({ option: true }),
};
export const UnmuteCmd = modActionsCmd({
export const UnmuteCmd = modActionsMsgCmd({
trigger: "unmute",
permission: "can_mute",
description: "Unmute the specified member",

View file

@ -1,8 +1,8 @@
import { commandTypeHelpers as ct } from "../../../commandTypes";
import { updateCase } from "../functions/updateCase";
import { modActionsCmd } from "../types";
import { modActionsMsgCmd } from "../types";
export const UpdateCmd = modActionsCmd({
export const UpdateCmd = modActionsMsgCmd({
trigger: ["update", "reason"],
permission: "can_note",
description:

View file

@ -8,9 +8,9 @@ import { formatReasonWithAttachments } from "../functions/formatReasonWithAttach
import { isBanned } from "../functions/isBanned";
import { readContactMethodsFromArgs } from "../functions/readContactMethodsFromArgs";
import { warnMember } from "../functions/warnMember";
import { modActionsCmd } from "../types";
import { modActionsMsgCmd } from "../types";
export const WarnCmd = modActionsCmd({
export const WarnCmd = modActionsMsgCmd({
trigger: "warn",
permission: "can_warn",
description: "Send a warning to the specified user",

View file

@ -0,0 +1,31 @@
import { commandTypeHelpers as ct } from "../../../../commandTypes";
import { sendErrorMessage } from "../../../../pluginUtils";
import { resolveUser } from "../../../../utils";
import { actualNoteCmd } from "../../functions/actualNoteCmd";
import { modActionsMsgCmd } from "../../types";
export const NoteMsgCmd = modActionsMsgCmd({
trigger: "note",
permission: "can_note",
description: "Add a note to the specified user",
signature: {
user: ct.string(),
note: ct.string({ required: false, catchAll: true }),
},
async run({ pluginData, message: msg, args }) {
const user = await resolveUser(pluginData.client, args.user);
if (!user.id) {
sendErrorMessage(pluginData, msg.channel, `User not found`);
return;
}
if (!args.note && msg.attachments.size === 0) {
sendErrorMessage(pluginData, msg.channel, "Text or attachment required");
return;
}
actualNoteCmd(pluginData, msg.channel, msg.author, [...msg.attachments.values()], user, args.note || "");
},
});

View file

@ -0,0 +1,45 @@
import { ApplicationCommandOptionType, ChatInputCommandInteraction } from "discord.js";
import { slashOptions } from "knub";
import { sendErrorMessage } from "../../../../pluginUtils";
import { actualNoteCmd } from "../../functions/actualNoteCmd";
export const NoteSlashCmd = {
name: "note",
description: "Add a note to the specified user",
allowDms: false,
configPermission: "can_note",
signature: [
slashOptions.user({ name: "user", description: "The user to add a note to", required: true }),
slashOptions.string({ name: "note", description: "The note to add to the user", required: false }),
...new Array(10).fill(0).map((_, i) => {
return {
name: `attachment${i + 1}`,
description: "An attachment to add to the note",
type: ApplicationCommandOptionType.Attachment,
required: false,
resolveValue: (interaction: ChatInputCommandInteraction) => {
return interaction.options.getAttachment(`attachment${i + 1}`);
},
getExtraAPIProps: () => ({}),
};
}),
],
async run({ interaction, options, pluginData }) {
const attachments = new Array(10)
.fill(0)
.map((_, i) => {
return options[`attachment${i + 1}`];
})
.filter((a) => a);
if ((!options.note || options.note.trim() === "") && attachments.length < 1) {
sendErrorMessage(pluginData, interaction, "Text or attachment required", undefined, undefined, true);
return;
}
actualNoteCmd(pluginData, interaction, interaction.user, attachments, options.user, options.note || "");
},
};

View file

@ -0,0 +1,47 @@
import { Attachment, ChatInputCommandInteraction, TextBasedChannel, User } from "discord.js";
import { GuildPluginData } from "knub";
import { CaseTypes } from "../../../data/CaseTypes";
import { sendSuccessMessage } from "../../../pluginUtils";
import { UnknownUser, renderUserUsername } from "../../../utils";
import { CasesPlugin } from "../../Cases/CasesPlugin";
import { LogsPlugin } from "../../Logs/LogsPlugin";
import { ModActionsPluginType } from "../types";
import { formatReasonWithAttachments } from "./formatReasonWithAttachments";
export async function actualNoteCmd(
pluginData: GuildPluginData<ModActionsPluginType>,
context: TextBasedChannel | ChatInputCommandInteraction,
author: User,
attachments: Array<Attachment>,
user: User | UnknownUser,
note: string,
) {
const userName = renderUserUsername(user);
const reason = formatReasonWithAttachments(note, attachments);
const casesPlugin = pluginData.getPlugin(CasesPlugin);
const createdCase = await casesPlugin.createCase({
userId: user.id,
modId: author.id,
type: CaseTypes.Note,
reason,
});
pluginData.getPlugin(LogsPlugin).logMemberNote({
mod: author,
user,
caseNumber: createdCase.case_number,
reason,
});
sendSuccessMessage(
pluginData,
context,
`Note added on **${userName}** (Case #${createdCase.case_number})`,
undefined,
undefined,
true,
);
pluginData.state.events.emit("note", user.id, reason);
}

View file

@ -1,7 +1,7 @@
import { GuildTextBasedChannel } from "discord.js";
import { EventEmitter } from "events";
import * as t from "io-ts";
import { BasePluginType, guildPluginEventListener, guildPluginMessageCommand } from "knub";
import { BasePluginType, guildPluginEventListener, guildPluginMessageCommand, guildPluginSlashGroup } from "knub";
import { Queue } from "../../Queue";
import { GuildCases } from "../../data/GuildCases";
import { GuildLogs } from "../../data/GuildLogs";
@ -147,5 +147,6 @@ export interface BanOptions {
export type ModActionType = "note" | "warn" | "mute" | "unmute" | "kick" | "ban" | "unban";
export const modActionsCmd = guildPluginMessageCommand<ModActionsPluginType>();
export const modActionsMsgCmd = guildPluginMessageCommand<ModActionsPluginType>();
export const modActionsSlashGroup = guildPluginSlashGroup<ModActionsPluginType>();
export const modActionsEvt = guildPluginEventListener<ModActionsPluginType>();

View file

@ -53,8 +53,8 @@ export const guildPlugins: Array<ZeppelinGuildPluginBlueprint<any>> = [
PostPlugin,
ReactionRolesPlugin,
MessageSaverPlugin,
// GuildMemberCachePlugin, // FIXME: New caching thing, or fix deadlocks with this plugin
ModActionsPlugin,
// GuildMemberCachePlugin, // FIXME: New caching thing, or fix deadlocks with this plugin
NameHistoryPlugin,
RemindersPlugin,
RolesPlugin,