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

feat: add clean message context menu command

This commit is contained in:
Obliie 2023-08-05 21:31:24 +01:00
parent 6689f91a6a
commit 740aa39cd5
No known key found for this signature in database
GPG key ID: 9189A18F0D5B547E
5 changed files with 107 additions and 32 deletions

View file

@ -7,6 +7,7 @@ import {
Message, Message,
MessageCreateOptions, MessageCreateOptions,
MessageMentionOptions, MessageMentionOptions,
ModalSubmitInteraction,
PermissionsBitField, PermissionsBitField,
TextBasedChannel, TextBasedChannel,
} from "discord.js"; } from "discord.js";
@ -104,6 +105,7 @@ export async function sendSuccessMessage(
channel: TextBasedChannel, channel: TextBasedChannel,
body: string, body: string,
allowedMentions?: MessageMentionOptions, allowedMentions?: MessageMentionOptions,
responseInteraction?: ModalSubmitInteraction,
): Promise<Message | undefined> { ): Promise<Message | undefined> {
const emoji = pluginData.fullConfig.success_emoji || undefined; const emoji = pluginData.fullConfig.success_emoji || undefined;
const formattedBody = successMessage(body, emoji); const formattedBody = successMessage(body, emoji);
@ -111,6 +113,11 @@ export async function sendSuccessMessage(
? { content: formattedBody, allowedMentions } ? { content: formattedBody, allowedMentions }
: { content: formattedBody }; : { content: formattedBody };
if (responseInteraction) {
await responseInteraction
.editReply({ content: formattedBody, embeds: [], components: [] })
.catch((err) => logger.error(`Interaction reply failed: ${err}`));
} else {
return channel return channel
.send({ ...content }) // Force line break .send({ ...content }) // Force line break
.catch((err) => { .catch((err) => {
@ -118,6 +125,7 @@ export async function sendSuccessMessage(
logger.warn(`Failed to send success message to ${channelInfo}): ${err.code} ${err.message}`); logger.warn(`Failed to send success message to ${channelInfo}): ${err.code} ${err.message}`);
return undefined; return undefined;
}); });
}
} }
export async function sendErrorMessage( export async function sendErrorMessage(
@ -125,6 +133,7 @@ export async function sendErrorMessage(
channel: TextBasedChannel, channel: TextBasedChannel,
body: string, body: string,
allowedMentions?: MessageMentionOptions, allowedMentions?: MessageMentionOptions,
responseInteraction?: ModalSubmitInteraction,
): Promise<Message | undefined> { ): Promise<Message | undefined> {
const emoji = pluginData.fullConfig.error_emoji || undefined; const emoji = pluginData.fullConfig.error_emoji || undefined;
const formattedBody = errorMessage(body, emoji); const formattedBody = errorMessage(body, emoji);
@ -132,6 +141,11 @@ export async function sendErrorMessage(
? { content: formattedBody, allowedMentions } ? { content: formattedBody, allowedMentions }
: { content: formattedBody }; : { content: formattedBody };
if (responseInteraction) {
await responseInteraction
.editReply({ content: formattedBody, embeds: [], components: [] })
.catch((err) => logger.error(`Interaction reply failed: ${err}`));
} else {
return channel return channel
.send({ ...content }) // Force line break .send({ ...content }) // Force line break
.catch((err) => { .catch((err) => {
@ -139,6 +153,7 @@ export async function sendErrorMessage(
logger.warn(`Failed to send error message to ${channelInfo}): ${err.code} ${err.message}`); logger.warn(`Failed to send error message to ${channelInfo}): ${err.code} ${err.message}`);
return undefined; return undefined;
}); });
}
} }
export function getBaseUrl(pluginData: AnyPluginData<any>) { export function getBaseUrl(pluginData: AnyPluginData<any>) {

View file

@ -9,6 +9,7 @@ import { MutesPlugin } from "../Mutes/MutesPlugin";
import { UtilityPlugin } from "../Utility/UtilityPlugin"; import { UtilityPlugin } from "../Utility/UtilityPlugin";
import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint";
import { BanCmd } from "./commands/BanUserCtxCmd"; import { BanCmd } from "./commands/BanUserCtxCmd";
import { CleanCmd } from "./commands/CleanMessageCtxCmd";
import { ModMenuCmd } from "./commands/ModMenuUserCtxCmd"; import { ModMenuCmd } from "./commands/ModMenuUserCtxCmd";
import { MuteCmd } from "./commands/MuteUserCtxCmd"; import { MuteCmd } from "./commands/MuteUserCtxCmd";
import { NoteCmd } from "./commands/NoteUserCtxCmd"; import { NoteCmd } from "./commands/NoteUserCtxCmd";
@ -49,7 +50,7 @@ export const ContextMenuPlugin = zeppelinGuildPlugin<ContextMenuPluginType>()({
defaultOptions, defaultOptions,
contextMenuCommands: [ModMenuCmd, NoteCmd, WarnCmd, MuteCmd, BanCmd], contextMenuCommands: [ModMenuCmd, NoteCmd, WarnCmd, MuteCmd, BanCmd, CleanCmd],
beforeLoad(pluginData) { beforeLoad(pluginData) {
const { state, guild } = pluginData; const { state, guild } = pluginData;

View file

@ -1,4 +1,12 @@
import { ActionRowBuilder, ButtonInteraction, ModalBuilder, TextInputBuilder, TextInputStyle } from "discord.js"; import {
ActionRowBuilder,
Message,
MessageContextMenuCommandInteraction,
ModalBuilder,
ModalSubmitInteraction,
TextInputBuilder,
TextInputStyle,
} from "discord.js";
import { GuildPluginData } from "knub"; import { GuildPluginData } from "knub";
import { logger } from "../../../logger"; import { logger } from "../../../logger";
import { UtilityPlugin } from "../../../plugins/Utility/UtilityPlugin"; import { UtilityPlugin } from "../../../plugins/Utility/UtilityPlugin";
@ -9,7 +17,9 @@ export async function cleanAction(
pluginData: GuildPluginData<ContextMenuPluginType>, pluginData: GuildPluginData<ContextMenuPluginType>,
amount: number, amount: number,
target: string, target: string,
interaction: ButtonInteraction, targetMessage: Message,
targetChannel: string,
interaction: ModalSubmitInteraction,
) { ) {
const executingMember = await pluginData.guild.members.fetch(interaction.user.id); const executingMember = await pluginData.guild.members.fetch(interaction.user.id);
const userCfg = await pluginData.config.getMatchingConfig({ const userCfg = await pluginData.config.getMatchingConfig({
@ -18,26 +28,27 @@ export async function cleanAction(
}); });
const utility = pluginData.getPlugin(UtilityPlugin); const utility = pluginData.getPlugin(UtilityPlugin);
if (!userCfg.can_use || !(await utility.hasPermission(executingMember, interaction.channelId, "can_clean"))) { if (!userCfg.can_use || !(await utility.hasPermission(executingMember, targetChannel, "can_clean"))) {
await interaction await interaction
.editReply({ content: "Cannot clean: insufficient permissions", embeds: [], components: [] }) .editReply({ content: "Cannot clean: insufficient permissions", embeds: [], components: [] })
.catch((err) => logger.error(`Clean interaction reply failed: ${err}`)); .catch((err) => logger.error(`Clean interaction reply failed: ${err}`));
return; return;
} }
// TODO: Implement message cleaning
await interaction await interaction
.editReply({ .editReply({
content: `TODO: Implementation incomplete`, content: `Cleaning ${amount} messages from ${target}...`,
embeds: [], embeds: [],
components: [], components: [],
}) })
.catch((err) => logger.error(`Clean interaction reply failed: ${err}`)); .catch((err) => logger.error(`Clean interaction reply failed: ${err}`));
await utility.clean({ count: amount, channel: targetChannel, "response-interaction": interaction }, targetMessage);
} }
export async function launchCleanActionModal( export async function launchCleanActionModal(
pluginData: GuildPluginData<ContextMenuPluginType>, pluginData: GuildPluginData<ContextMenuPluginType>,
interaction: ButtonInteraction, interaction: MessageContextMenuCommandInteraction,
target: string, target: string,
) { ) {
const modalId = `${ModMenuActionType.CLEAN}:${interaction.id}`; const modalId = `${ModMenuActionType.CLEAN}:${interaction.id}`;
@ -50,7 +61,9 @@ export async function launchCleanActionModal(
await interaction await interaction
.awaitModalSubmit({ time: MODAL_TIMEOUT, filter: (i) => i.customId == modalId }) .awaitModalSubmit({ time: MODAL_TIMEOUT, filter: (i) => i.customId == modalId })
.then(async (submitted) => { .then(async (submitted) => {
await submitted.deferUpdate().catch((err) => logger.error(`Clean interaction defer failed: ${err}`)); await submitted
.deferReply({ ephemeral: true })
.catch((err) => logger.error(`Clean interaction defer failed: ${err}`));
const amount = submitted.fields.getTextInputValue("amount"); const amount = submitted.fields.getTextInputValue("amount");
if (isNaN(Number(amount))) { if (isNaN(Number(amount))) {
@ -60,7 +73,14 @@ export async function launchCleanActionModal(
return; return;
} }
await cleanAction(pluginData, Number(amount), target, interaction); await cleanAction(
pluginData,
Number(amount),
target,
interaction.targetMessage,
interaction.channelId,
submitted,
);
}) })
.catch((err) => logger.error(`Clean modal interaction failed: ${err}`)); .catch((err) => logger.error(`Clean modal interaction failed: ${err}`));
} }

View file

@ -0,0 +1,11 @@
import { PermissionFlagsBits } from "discord.js";
import { guildPluginMessageContextMenuCommand } from "knub";
import { launchCleanActionModal } from "../actions/clean";
export const CleanCmd = guildPluginMessageContextMenuCommand({
name: "Clean",
defaultMemberPermissions: PermissionFlagsBits.ManageMessages.toString(),
async run({ pluginData, interaction }) {
await launchCleanActionModal(pluginData, interaction, interaction.targetId);
},
});

View file

@ -1,4 +1,4 @@
import { Message, Snowflake, TextChannel, User } from "discord.js"; import { Message, ModalSubmitInteraction, Snowflake, TextChannel, User } from "discord.js";
import { GuildPluginData } from "knub"; import { GuildPluginData } from "knub";
import { allowTimeout } from "../../../RegExpRunner"; import { allowTimeout } from "../../../RegExpRunner";
import { commandTypeHelpers as ct } from "../../../commandTypes"; import { commandTypeHelpers as ct } from "../../../commandTypes";
@ -77,17 +77,24 @@ export interface CleanArgs {
"has-invites"?: boolean; "has-invites"?: boolean;
match?: RegExp; match?: RegExp;
"to-id"?: string; "to-id"?: string;
"response-interaction"?: ModalSubmitInteraction;
} }
export async function cleanCmd(pluginData: GuildPluginData<UtilityPluginType>, args: CleanArgs | any, msg) { export async function cleanCmd(pluginData: GuildPluginData<UtilityPluginType>, args: CleanArgs | any, msg) {
if (args.count > MAX_CLEAN_COUNT || args.count <= 0) { if (args.count > MAX_CLEAN_COUNT || args.count <= 0) {
sendErrorMessage(pluginData, msg.channel, `Clean count must be between 1 and ${MAX_CLEAN_COUNT}`); sendErrorMessage(
pluginData,
msg.channel,
`Clean count must be between 1 and ${MAX_CLEAN_COUNT}`,
undefined,
args["response-interaction"],
);
return; return;
} }
const targetChannel = args.channel ? pluginData.guild.channels.cache.get(args.channel as Snowflake) : msg.channel; const targetChannel = args.channel ? pluginData.guild.channels.cache.get(args.channel as Snowflake) : msg.channel;
if (!targetChannel?.isTextBased()) { if (!targetChannel?.isTextBased()) {
sendErrorMessage(pluginData, msg.channel, `Invalid channel specified`); sendErrorMessage(pluginData, msg.channel, `Invalid channel specified`, undefined, args["response-interaction"]);
return; return;
} }
@ -99,12 +106,21 @@ export async function cleanCmd(pluginData: GuildPluginData<UtilityPluginType>, a
categoryId: targetChannel.parentId, categoryId: targetChannel.parentId,
}); });
if (configForTargetChannel.can_clean !== true) { if (configForTargetChannel.can_clean !== true) {
sendErrorMessage(pluginData, msg.channel, `Missing permissions to use clean on that channel`); sendErrorMessage(
pluginData,
msg.channel,
`Missing permissions to use clean on that channel`,
undefined,
args["response-interaction"],
);
return; return;
} }
} }
const cleaningMessage = msg.channel.send("Cleaning..."); let cleaningMessage: Message | undefined = undefined;
if (!args["response-interaction"]) {
cleaningMessage = await msg.channel.send("Cleaning...");
}
const messagesToClean: Message[] = []; const messagesToClean: Message[] = [];
let beforeId = msg.id; let beforeId = msg.id;
@ -202,19 +218,31 @@ export async function cleanCmd(pluginData: GuildPluginData<UtilityPluginType>, a
} }
} }
responseMsg = await sendSuccessMessage(pluginData, msg.channel, responseText); responseMsg = await sendSuccessMessage(
pluginData,
msg.channel,
responseText,
undefined,
args["response-interaction"],
);
} else { } else {
const responseText = `Found no messages to clean${note ? ` (${note})` : ""}!`; const responseText = `Found no messages to clean${note ? ` (${note})` : ""}!`;
responseMsg = await sendErrorMessage(pluginData, msg.channel, responseText); responseMsg = await sendErrorMessage(
pluginData,
msg.channel,
responseText,
undefined,
args["response-interaction"],
);
} }
await (await cleaningMessage).delete(); cleaningMessage?.delete();
if (targetChannel.id === msg.channel.id) { if (targetChannel.id === msg.channel.id) {
// Delete the !clean command and the bot response if a different channel wasn't specified // Delete the !clean command and the bot response if a different channel wasn't specified
// (so as not to spam the cleaned channel with the command itself) // (so as not to spam the cleaned channel with the command itself)
setTimeout(() => {
msg.delete().catch(noop); msg.delete().catch(noop);
setTimeout(() => {
responseMsg?.delete().catch(noop); responseMsg?.delete().catch(noop);
}, CLEAN_COMMAND_DELETE_DELAY); }, CLEAN_COMMAND_DELETE_DELAY);
} }