Skip to content

Commit

Permalink
refactor(core): update prefix CommandData` interface, execute method,…
Browse files Browse the repository at this point in the history
… util functions (#191)

- Added 'Utility' category to CommandData interface
- Changed requiredBotPermissions and requiredUserPermissions to be single
  PermissionsBitField
- Updated execute method in BasePrefixCommand to include new functionality
- Refactored Util functions in BasePrefixCommand
- Updated messages in execute method to use emojis and handle errors
  • Loading branch information
dev-737 authored Nov 1, 2024
1 parent d91bc4a commit 117bbf1
Show file tree
Hide file tree
Showing 10 changed files with 239 additions and 91 deletions.
17 changes: 11 additions & 6 deletions src/commands/prefix/deleteMsg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,27 +16,32 @@ export default class DeleteMsgCommand extends BasePrefixCommand {
name: 'deletemsg',
description: 'Delete a message',
category: 'Network',
usage: 'deletemsg <message ID or link>',
usage: 'deletemsg ` message ID or link `',
examples: [
'deletemsg 123456789012345678',
'deletemsg https://discord.com/channels/123456789012345678/123456789012345678/123456789012345678',
],
aliases: ['delmsg', 'dmsg', 'delete', 'del'],
dbPermission: false,
totalArgs: 1,
};

public async execute(message: Message<true>, args: string[]): Promise<void> {
const originalMsgId = message.reference?.messageId ?? getMessageIdFromStr(args[0]);
const originalMsg = originalMsgId ? await this.getOriginalMessage(originalMsgId) : null;
protected async run(message: Message<true>, args: string[]): Promise<void> {
const msgId = message.reference?.messageId ?? getMessageIdFromStr(args[0]);
const originalMsg = msgId ? await this.getOriginalMessage(msgId) : null;

if (!originalMsg) {
await message.channel.send('Please provide a valid message ID or link to delete.');
return;
}

const hub = await fetchHub(originalMsg.hubId);
if (!hub || !isStaffOrHubMod(message.author.id, hub)) {
await message.channel.send('You do not have permission to use this command.');
if (
!hub ||
!isStaffOrHubMod(message.author.id, hub) ||
originalMsg.authorId !== message.author.id
) {
await message.channel.send('You do not have permission to use this command on that message.');
return;
}

Expand Down
11 changes: 5 additions & 6 deletions src/commands/prefix/modpanel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,20 @@ export default class BlacklistPrefixCommand extends BasePrefixCommand {
name: 'modpanel',
description: 'Blacklist a user or server from using the bot',
category: 'Moderation',
usage: 'blacklist <user ID or server ID>',
usage: 'blacklist ` user ID or server ID `',
examples: [
'blacklist 123456789012345678',
'blacklist 123456789012345678',
'> Reply to a message with `blacklist` to blacklist the user who sent the message',
],
aliases: ['bl', 'modactions', 'modpanel', 'mod', 'ban'],
dbPermission: false,
totalArgs: 1,
};

public async execute(message: Message<true>, args: string[]) {
const originalMessageId = message.reference?.messageId ?? getMessageIdFromStr(args[0]);
const originalMessage = originalMessageId
? await this.getOriginalMessage(originalMessageId)
: null;
protected async run(message: Message<true>, args: string[]) {
const msgId = message.reference?.messageId ?? getMessageIdFromStr(args[0]);
const originalMessage = msgId ? await this.getOriginalMessage(msgId) : null;

if (!originalMessage) {
await message.channel.send('Please provide a valid message ID or link.');
Expand Down
63 changes: 63 additions & 0 deletions src/commands/prefix/report.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import BasePrefixCommand, { CommandData } from '#main/core/BasePrefixCommand.js';
import { sendHubReport } from '#main/utils/HubLogger/Report.js';
import {
findOriginalMessage,
getBroadcasts,
getMessageIdFromStr,
getOriginalMessage,
} from '#main/utils/network/messageUtils.js';
import { GuildTextBasedChannel, Message } from 'discord.js';
import ms from 'ms';

export default class ReportPrefixCommand extends BasePrefixCommand {
public readonly data: CommandData = {
name: 'report',
description: 'Report a message',
category: 'Utility',
usage: 'report ` [message ID or link] ` ` reason ` ',
examples: [
'report 123456789012345678',
'report https://discord.com/channels/123456789012345678/123456789012345678/123456789012345678',
'report 123456789012345678 Spamming',
],
aliases: ['r'],
totalArgs: 1,
cooldown: ms('30s'),
};

protected async run(message: Message<true>, args: string[]) {
const msgId = message.reference?.messageId ?? getMessageIdFromStr(args[0] ?? args[1]);
const originalMsg = msgId ? await this.getOriginalMessage(msgId) : null;
const broadcastMsgs = originalMsg
? await getBroadcasts(originalMsg.messageId, originalMsg.hubId)
: null;

if (!broadcastMsgs || !originalMsg) {
await message.channel.send('Please provide a valid message ID or link.');
return;
}

const broadcastMsg = Object.values(broadcastMsgs).find((m) => m.messageId === msgId);
if (!broadcastMsg) {
await message.channel.send('Please provide a valid message ID or link.');
return;
}

const fetchedMsg = await (
message.client.channels.cache.get(broadcastMsg.channelId) as GuildTextBasedChannel
)?.messages
.fetch(broadcastMsg.messageId)
.catch(() => null);

await sendHubReport(originalMsg.hubId, message.client, {
userId: originalMsg.authorId,
serverId: originalMsg.guildId,
reason: message.reference?.messageId ? args[0] : args.slice(1).join(' '),
reportedBy: message.author,
evidence: { messageId: broadcastMsg.messageId, content: fetchedMsg?.content },
});
}
private async getOriginalMessage(messageId: string) {
return (await getOriginalMessage(messageId)) ?? (await findOriginalMessage(messageId)) ?? null;
}
}
61 changes: 57 additions & 4 deletions src/core/BasePrefixCommand.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,73 @@
import { emojis } from '#main/config/Constants.js';
import Logger from '#main/utils/Logger.js';
import { isDev } from '#main/utils/Utils.js';
import { Message, PermissionsBitField } from 'discord.js';

export interface CommandData {
name: string;
description: string;
category: 'Moderation' | 'Network'; // add more categories as needed
category: 'Moderation' | 'Network' | 'Utility'; // add more categories as needed
usage: string;
examples: string[];
aliases: string[];
dbPermission?: boolean;
totalArgs: number;
cooldown?: number;
ownerOnly?: boolean;
requiredBotPermissions?: PermissionsBitField[];
requiredUserPermissions?: PermissionsBitField[];
requiredBotPermissions?: PermissionsBitField;
requiredUserPermissions?: PermissionsBitField;
}

export default abstract class BasePrefixCommand {
public abstract readonly data: CommandData;
public abstract execute(message: Message, args: string[]): Promise<void>;
protected abstract run(message: Message, args: string[]): Promise<void>;
public async execute(message: Message, args: string[]): Promise<void> {
try {
// Check if command is owner-only
if (this.data.ownerOnly && !isDev(message.author.id)) {
await message.reply(`${emojis.botdev} This command can only be used by the bot owner.`);
return;
}

// Check user permissions
const { requiredBotPermissions, requiredUserPermissions } = this.data;

const missingPerms =
requiredUserPermissions &&
message.member?.permissions.missing(requiredUserPermissions, true);
if (missingPerms?.length) {
await message.reply(`${emojis.neutral} You're missing the following permissions: ${missingPerms.join(', ')}`);
return;
}

const botMissingPerms =
requiredBotPermissions &&
message.guild?.members.me?.permissions.missing(requiredBotPermissions, true);
if (botMissingPerms?.length) {
await message.reply(`${emojis.no} I'm missing the following permissions: ${botMissingPerms.join(', ')}`);
return;
}

if (this.data.dbPermission && !message.inGuild()) {
await message.reply(`${emojis.no} This command can only be used in a server.`);
return;
}

if (this.data.totalArgs > args.length) {
const examplesStr =
this.data.examples.length > 0 ? `\n**Examples**: ${this.data.examples.join('\n')}` : '';
await message.reply(
`${emojis.neutral} One or more args missing.\n**Usage**: ${this.data.usage}\n${examplesStr}`,
);
return;
}

// Run command
await this.run(message, args);
}
catch (error) {
Logger.error(error);
await message.reply('There was an error executing this command!');
}
}
}
20 changes: 18 additions & 2 deletions src/events/messageCreate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import BaseEventListener from '#main/core/BaseEventListener.js';
import HubSettingsManager from '#main/managers/HubSettingsManager.js';
import Logger from '#main/utils/Logger.js';
import { checkBlockedWords } from '#main/utils/network/blockwordsRunner.js';
import handlePrefixCommand from '#main/utils/PrefixCmdHandler.js';
import { generateJumpButton as getJumpButton } from '#utils/ComponentUtils.js';
import { getConnectionHubId, getHubConnections } from '#utils/ConnectedListUtils.js';
import db from '#utils/Db.js';
Expand Down Expand Up @@ -40,7 +39,7 @@ export default class MessageCreate extends BaseEventListener<'messageCreate'> {
if (!message.inGuild() || !isHumanMessage(message)) return;

if (message.content.startsWith('c!')) {
await handlePrefixCommand(message, 'c!');
await this.handlePrefixCommand(message, 'c!');
return;
}

Expand All @@ -66,6 +65,23 @@ export default class MessageCreate extends BaseEventListener<'messageCreate'> {
await this.processMessage(message, hub, hubConnections, settings, connection, attachmentURL);
}

private async handlePrefixCommand(message: Message, prefix: string) {
// Split message into command and arguments
const args = message.content.slice(prefix.length).trim().split(/ +/);
const commandName = args.shift()?.toLowerCase();

if (!commandName) return;

// Find command by name or alias
const command =
message.client.prefixCommands.get(commandName) ||
message.client.prefixCommands.find((cmd) => cmd.data.aliases?.includes(commandName));

if (!command) return;

await command.execute(message, args);
}

private async getHub(hubId: string) {
return await db.hub.findFirst({
where: { id: hubId },
Expand Down
40 changes: 40 additions & 0 deletions src/interactions/ShowModPanelButton.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { emojis } from '#main/config/Constants.js';
import { RegisterInteractionHandler } from '#main/decorators/Interaction.js';
import { CustomID } from '#main/utils/CustomID.js';
import { InfoEmbed } from '#main/utils/EmbedUtils.js';
import { fetchHub, isStaffOrHubMod } from '#main/utils/hub/utils.js';
import modActionsPanel from '#main/utils/moderation/modActions/modActionsPanel.js';
import { findOriginalMessage, getOriginalMessage } from '#main/utils/network/messageUtils.js';
import { ButtonInteraction } from 'discord.js';

export default class ModActionsButton {
@RegisterInteractionHandler('showModPanel')
async handler(interaction: ButtonInteraction): Promise<void> {
await interaction.deferUpdate();

const customId = CustomID.parseCustomId(interaction.customId);
const [messageId] = customId.args;

const originalMessage =
(await getOriginalMessage(messageId)) ?? (await findOriginalMessage(messageId));
const hub = originalMessage ? await fetchHub(originalMessage?.hubId) : null;

if (!originalMessage || !hub || !isStaffOrHubMod(interaction.user.id, hub)) {
await interaction.editReply({ components: [] });
await interaction.followUp({
embeds: [new InfoEmbed({ description: `${emojis.slash} Message was deleted.` })],
ephemeral: true,
});
return;
}

if (!isStaffOrHubMod(interaction.user.id, hub)) return;

const panel = await modActionsPanel.buildMessage(interaction, originalMessage);
await interaction.followUp({
embeds: [panel.embed],
components: panel.buttons,
ephemeral: true,
});
}
}
19 changes: 15 additions & 4 deletions src/utils/HubLogger/Default.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import type { ClusterClient } from 'discord-hybrid-sharding';
import type { Channel, Client, EmbedBuilder } from 'discord.js';
import type {
APIActionRowComponent,
APIMessageActionRowComponent,
Channel,
Client,
EmbedBuilder,
} from 'discord.js';

/**
* Sends a log message to the specified channel with the provided embed.
Expand All @@ -10,7 +16,10 @@ export const sendLog = async (
cluster: ClusterClient<Client>,
channelId: string,
embed: EmbedBuilder,
content?: string,
opts?: {
content?: string;
components: APIActionRowComponent<APIMessageActionRowComponent>[];
},
) => {
await cluster.broadcastEval(
async (shardClient, ctx) => {
Expand All @@ -19,9 +28,11 @@ export const sendLog = async (
.catch(() => null)) as Channel | null;

if (channel?.isSendable()) {
await channel.send({ content: ctx.content, embeds: [ctx.embed] }).catch(() => null);
await channel
.send({ content: ctx.content, embeds: [ctx.embed], components: ctx.components })
.catch(() => null);
}
},
{ context: { channelId, embed, content } },
{ context: { channelId, embed, content: opts?.content, components: opts?.components } },
);
};
32 changes: 29 additions & 3 deletions src/utils/HubLogger/Report.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import { getBroadcast, getOriginalMessage } from '#main/utils/network/messageUtils.js';
import { CustomID } from '#main/utils/CustomID.js';
import {
findOriginalMessage,
getBroadcast,
getOriginalMessage,
} from '#main/utils/network/messageUtils.js';
import { stripIndents } from 'common-tags';
import {
ActionRowBuilder,
ButtonBuilder,
ButtonStyle,
EmbedBuilder,
messageLink,
roleMention,
Expand Down Expand Up @@ -43,7 +51,8 @@ const genJumpLink = async (
) => {
if (!messageId) return null;

const originalMsg = await getOriginalMessage(messageId);
const originalMsg =
(await getOriginalMessage(messageId)) ?? (await findOriginalMessage(messageId));
if (!originalMsg) return null;

// fetch the reports server ID from the log channel's ID
Expand Down Expand Up @@ -89,6 +98,8 @@ export const sendHubReport = async (
const hub = await db.hub.findFirst({ where: { id: hubId }, include: { logConfig: true } });
if (!hub?.logConfig[0]?.reports?.channelId) return;

if (!evidence?.messageId) return;

const { channelId: reportsChannelId, roleId: reportsRoleId } = hub.logConfig[0].reports;
const server = await client.fetchGuild(serverId);
const jumpLink = await genJumpLink(hubId, client, evidence?.messageId, reportsChannelId);
Expand Down Expand Up @@ -117,5 +128,20 @@ export const sendHubReport = async (
});

const mentionRole = reportsRoleId ? roleMention(reportsRoleId) : undefined;
await sendLog(client.cluster, reportsChannelId, embed, mentionRole);
const button = new ActionRowBuilder<ButtonBuilder>()
.addComponents(
new ButtonBuilder()
.setCustomId(
new CustomID().setIdentifier('showModPanel').addArgs(evidence.messageId).toString(),
)
.setStyle(ButtonStyle.Danger)
.setLabel('Take Action')
.setEmoji(emojis.blobFastBan),
)
.toJSON();

await sendLog(client.cluster, reportsChannelId, embed, {
content: mentionRole,
components: [button],
});
};
Loading

0 comments on commit 117bbf1

Please sign in to comment.