diff --git a/src/commands/prefix/deleteMsg.ts b/src/commands/prefix/deleteMsg.ts new file mode 100644 index 000000000..3c3180e8f --- /dev/null +++ b/src/commands/prefix/deleteMsg.ts @@ -0,0 +1,71 @@ +import { emojis } from '#main/config/Constants.js'; +import BasePrefixCommand, { CommandData } from '#main/core/BasePrefixCommand.js'; +import { fetchHub, isStaffOrHubMod } from '#main/utils/hub/utils.js'; +import { deleteMessageFromHub } from '#main/utils/moderation/deleteMessage.js'; +import { + findOriginalMessage, + getBroadcasts, + getMessageIdFromStr, + getOriginalMessage, + OriginalMessage, +} from '#main/utils/network/messageUtils.js'; +import { Message } from 'discord.js'; + +export default class DeleteMsgCommand extends BasePrefixCommand { + public readonly data: CommandData = { + name: 'deletemsg', + description: 'Delete a message', + category: 'Network', + usage: 'deletemsg ', + examples: [ + 'deletemsg 123456789012345678', + 'deletemsg https://discord.com/channels/123456789012345678/123456789012345678/123456789012345678', + ], + aliases: ['delmsg', 'dmsg', 'delete', 'del'], + dbPermission: false, + }; + + public async execute(message: Message, args: string[]): Promise { + const originalMsgId = message.reference?.messageId ?? getMessageIdFromStr(args[0]); + const originalMsg = originalMsgId ? await this.getOriginalMessage(originalMsgId) : 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.'); + return; + } + + const reply = await message.reply(`${emojis.loading} Deleting message...`); + + const deleted = await deleteMessageFromHub( + originalMsg.hubId, + originalMsg.messageId, + Object.values(await getBroadcasts(originalMsg.messageId, originalMsg.hubId)), + ).catch(() => null); + + await reply.edit( + `${emojis.delete} Deleted messages from **${deleted?.deletedCount ?? '0'}** servers.`, + ); + } + + private async getOriginalMessage(messageId: string) { + const originalMsg = + (await getOriginalMessage(messageId)) ?? (await findOriginalMessage(messageId)); + return originalMsg; + } + + private async getOriginalMsgs(args: string[]): Promise { + const promises = args.map(async (arg) => { + const messageId = getMessageIdFromStr(arg); + return messageId ? await this.getOriginalMessage(messageId) : null; + }); + + const results = await Promise.all(promises); + return results.filter((id): id is OriginalMessage => id !== null); + } +} diff --git a/src/commands/prefix/modpanel.ts b/src/commands/prefix/modpanel.ts new file mode 100644 index 000000000..bc6dcc6af --- /dev/null +++ b/src/commands/prefix/modpanel.ts @@ -0,0 +1,49 @@ +import BasePrefixCommand, { CommandData } from '#main/core/BasePrefixCommand.js'; +import { fetchHub, isStaffOrHubMod } from '#main/utils/hub/utils.js'; +import modActionsPanel from '#main/utils/moderation/modActions/modActionsPanel.js'; +import { + findOriginalMessage, + getMessageIdFromStr, + getOriginalMessage, +} from '#main/utils/network/messageUtils.js'; +import { Message } from 'discord.js'; + +export default class BlacklistPrefixCommand extends BasePrefixCommand { + public readonly data: CommandData = { + name: 'modpanel', + description: 'Blacklist a user or server from using the bot', + category: 'Moderation', + usage: 'blacklist ', + 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, + }; + + public async execute(message: Message, args: string[]) { + const originalMessageId = message.reference?.messageId ?? getMessageIdFromStr(args[0]); + const originalMessage = originalMessageId + ? await this.getOriginalMessage(originalMessageId) + : null; + + if (!originalMessage) { + await message.channel.send('Please provide a valid message ID or link.'); + return; + } + + const hub = await fetchHub(originalMessage.hubId); + if (!hub || !isStaffOrHubMod(message.author.id, hub)) { + await message.channel.send('You do not have permission to use this command.'); + return; + } + + const modPanel = await modActionsPanel.buildMessage(message, originalMessage); + await message.reply({ embeds: [modPanel.embed], components: modPanel.buttons }); + } + private async getOriginalMessage(messageId: string) { + return (await getOriginalMessage(messageId)) ?? (await findOriginalMessage(messageId)) ?? null; + } +} diff --git a/src/config/Constants.ts b/src/config/Constants.ts index 16b497fbd..f0b2d815a 100644 --- a/src/config/Constants.ts +++ b/src/config/Constants.ts @@ -82,6 +82,7 @@ export default { Hexcode: /^#[0-9A-F]{6}$/i, ChannelMention: /<#|!|>/g, ImgurImage: /https?:\/\/i\.imgur\.com\/[a-zA-Z0-9]+\.((jpg)|(jpeg)|(png)|(gif))/g, + MessageLink: /https:\/\/discord.com\/channels\/(\d{17,19})\/(\d{17,19})\/(\d{17,19})/g, }, Links: { diff --git a/src/core/BaseClient.ts b/src/core/BaseClient.ts index a247fa6bd..2aad80499 100644 --- a/src/core/BaseClient.ts +++ b/src/core/BaseClient.ts @@ -1,5 +1,6 @@ import Constants from '#main/config/Constants.js'; import type BaseCommand from '#main/core/BaseCommand.js'; +import type BasePrefixCommand from '#main/core/BasePrefixCommand.js'; import type { InteractionFunction } from '#main/decorators/Interaction.js'; import AntiSpamManager from '#main/managers/AntiSpamManager.js'; import UserDbManager from '#main/managers/UserDbManager.js'; @@ -7,7 +8,7 @@ import CooldownService from '#main/modules/CooldownService.js'; import EventLoader from '#main/modules/Loaders/EventLoader.js'; import Scheduler from '#main/modules/SchedulerService.js'; import type { RemoveMethods } from '#types/index.d.ts'; -import { loadCommandFiles, loadInteractions } from '#utils/CommandUtils.js'; +import { loadCommands, loadInteractions } from '#utils/CommandUtils.js'; import { loadLocales } from '#utils/Locale.js'; import { resolveEval } from '#utils/Utils.js'; import { ClusterClient, getInfo } from 'discord-hybrid-sharding'; @@ -37,8 +38,11 @@ export default class InterChatClient extends Client { readonly cluster = new ClusterClient(this); readonly eventLoader = new EventLoader(this); readonly commandCooldowns = new CooldownService(); + public readonly commands = new Collection(); public readonly interactions = new Collection(); + public readonly prefixCommands = new Collection(); + public readonly antiSpamManager = new AntiSpamManager({ spamThreshold: 4, timeWindow: 5000, @@ -87,7 +91,7 @@ export default class InterChatClient extends Client { loadLocales('locales'); // load commands - loadCommandFiles(this.commands, this.interactions); + loadCommands(this.commands, this.prefixCommands, this.interactions); loadInteractions(this.interactions); this.eventLoader.load(); diff --git a/src/core/BasePrefixCommand.ts b/src/core/BasePrefixCommand.ts new file mode 100644 index 000000000..34c615c57 --- /dev/null +++ b/src/core/BasePrefixCommand.ts @@ -0,0 +1,20 @@ +import { Message, PermissionsBitField } from 'discord.js'; + +export interface CommandData { + name: string; + description: string; + category: 'Moderation' | 'Network'; // add more categories as needed + usage: string; + examples: string[]; + aliases: string[]; + dbPermission?: boolean; + cooldown?: number; + ownerOnly?: boolean; + requiredBotPermissions?: PermissionsBitField[]; + requiredUserPermissions?: PermissionsBitField[]; +} + +export default abstract class BasePrefixCommand { + public abstract readonly data: CommandData; + public abstract execute(message: Message, args: string[]): Promise; +} diff --git a/src/events/messageCreate.ts b/src/events/messageCreate.ts index 4b2e9d18e..df2e206d1 100644 --- a/src/events/messageCreate.ts +++ b/src/events/messageCreate.ts @@ -1,7 +1,9 @@ import { ConnectionMode } from '#main/config/Constants.js'; 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'; @@ -37,6 +39,11 @@ export default class MessageCreate extends BaseEventListener<'messageCreate'> { async execute(message: Message) { if (!message.inGuild() || !isHumanMessage(message)) return; + if (message.content.startsWith('c!')) { + await handlePrefixCommand(message, 'c!'); + return; + } + const { connection, hubConnections } = await this.getConnectionAndHubConnections(message); if (!connection?.connected || !hubConnections) return; @@ -88,19 +95,6 @@ export default class MessageCreate extends BaseEventListener<'messageCreate'> { embedColor: connection.embedColor as HexColorString, }); - await this.storeMessage(message, sendResult, connection, referredMsgData); - } - - private async fetchReferredMessage(message: Message): Promise { - return message.reference ? await message.fetchReference().catch(() => null) : null; - } - - private async storeMessage( - message: Message, - sendResult: NetworkWebhookSendResult[], - connection: connectedList, - referredMsgData: ReferredMsgData, - ) { await storeMessageData( message, sendResult, @@ -110,6 +104,10 @@ export default class MessageCreate extends BaseEventListener<'messageCreate'> { ); } + private async fetchReferredMessage(message: Message): Promise { + return message.reference ? await message.fetchReference().catch(() => null) : null; + } + private async broadcastMessage( message: Message, hub: Hub, @@ -152,6 +150,7 @@ export default class MessageCreate extends BaseEventListener<'messageCreate'> { return { messageRes, webhookURL: connection.webhookURL, mode }; } catch (e) { + Logger.error(`Failed to send message to ${connection.channelId}`, e); return { error: e.message, webhookURL: connection.webhookURL }; } } diff --git a/src/interactions/InactiveConnectInteraction.ts b/src/interactions/InactiveConnectInteraction.ts index fbc096ae8..736002d11 100644 --- a/src/interactions/InactiveConnectInteraction.ts +++ b/src/interactions/InactiveConnectInteraction.ts @@ -21,7 +21,7 @@ export default class InactiveConnectInteraction { t('connection.channelNotFound', locale, { emoji: emojis.no }), ); - await interaction.reply({ embeds: [notFoundEmbed], ephemeral: true }); + await interaction.followUp({ embeds: [notFoundEmbed], ephemeral: true }); return; } diff --git a/src/modules/HubJoinService.ts b/src/modules/HubJoinService.ts index e89bc6bdc..8a17edbbf 100644 --- a/src/modules/HubJoinService.ts +++ b/src/modules/HubJoinService.ts @@ -17,7 +17,6 @@ import { ChatInputCommandInteraction, GuildTextBasedChannel, MessageComponentInteraction, - Snowflake, } from 'discord.js'; export class HubJoinService { @@ -58,7 +57,9 @@ export class HubJoinService { return false; } - if ((await this.isAlreadyInHub(channel.id)) || (await this.isBlacklisted(hub))) return false; + if ((await this.isAlreadyInHub(channel, hub.id)) || (await this.isBlacklisted(hub))) { + return false; + } const onboardingSuccess = await this.processOnboarding(hub, channel); if (!onboardingSuccess) return false; @@ -119,13 +120,16 @@ export class HubJoinService { }); } - private async isAlreadyInHub(channelId: Snowflake) { - const channelInHub = await db.connectedList.findFirst({ where: { channelId } }); + private async isAlreadyInHub(channel: GuildTextBasedChannel, hubId: string) { + const channelInHub = await db.connectedList.findFirst({ + where: { OR: [{ channelId: channel.id }, { serverId: channel.guildId, hubId }] }, + include: { hub: { select: { name: true } } }, + }); + if (channelInHub) { - const otherHub = await db.hub.findFirst({ where: { id: channelInHub.hubId } }); await this.replyError('hub.alreadyJoined', { - channel: `<#${channelId}>`, - hub: `${otherHub?.name}`, + channel: `<#${channelInHub.channelId}>`, + hub: `${channelInHub.hub?.name}`, emoji: emojis.no, }); return true; diff --git a/src/modules/Loaders/CommandLoader.ts b/src/modules/Loaders/CommandLoader.ts index 76ea0ddba..0d404d266 100644 --- a/src/modules/Loaders/CommandLoader.ts +++ b/src/modules/Loaders/CommandLoader.ts @@ -1,4 +1,5 @@ import BaseCommand from '#main/core/BaseCommand.js'; +import BasePrefixCommand from '#main/core/BasePrefixCommand.js'; import { type Class, FileLoader, type ResourceLoader } from '#main/core/FileLoader.js'; import { InteractionFunction } from '#main/decorators/Interaction.js'; import Logger from '#utils/Logger.js'; @@ -9,15 +10,18 @@ import { fileURLToPath } from 'url'; const __dirname = dirname(fileURLToPath(import.meta.url)); export class CommandLoader implements ResourceLoader { - private readonly map: Collection; + private readonly commandMap: Collection; + private readonly prefixMap: Collection; private readonly interactionsMap: Collection; private readonly fileLoader: FileLoader; constructor( - map: Collection, + commandMap: Collection, + prefixMap: Collection, interactionsMap: Collection, ) { - this.map = map; + this.prefixMap = prefixMap; + this.commandMap = commandMap; this.interactionsMap = interactionsMap; this.fileLoader = new FileLoader(join(__dirname, '..', '..', 'commands'), { recursive: true }); } @@ -28,14 +32,31 @@ export class CommandLoader implements ResourceLoader { private async processFile(filePath: string): Promise { Logger.debug(`Importing command file: ${filePath}`); - const imported = await FileLoader.import<{ default: Class }>(filePath); + const imported = await FileLoader.import<{ default: Class }>( + filePath, + ); const command = new imported.default(); const fileName = filePath.replaceAll('\\', '/').split('/').pop() as string; - command.build(fileName.replace('.js', ''), { - commandsMap: this.map, - interactionsMap: this.interactionsMap, - }); + // FIXME: do smth about this + if (command instanceof BasePrefixCommand) { + this.prefixMap.set(command.data.name, command); + } + else { + command.build(fileName.replace('.js', ''), { + commandsMap: this.commandMap, + interactionsMap: this.interactionsMap, + }); + } Logger.debug(`Finished loading command: ${command.data.name}`); } } + +export interface ICommand { + readonly name: string; + readonly description: string; + readonly category: string; + readonly cooldown?: number; + readonly staffOnly?: boolean; + readonly type: 'prefix' | 'slash' | 'context'; +} diff --git a/src/types/index.d.ts b/src/types/index.d.ts index 2fe058f22..d7055014c 100644 --- a/src/types/index.d.ts +++ b/src/types/index.d.ts @@ -1,4 +1,5 @@ import BaseCommand from '#main/core/BaseCommand.js'; +import BasePrefixCommand from '#main/core/BasePrefixCommand.js'; import { InteractionFunction } from '#main/decorators/Interaction.ts'; import AntiSpamManager from '#main/managers/AntiSpamManager.js'; import UserDbManager from '#main/managers/UserDbManager.js'; @@ -27,6 +28,7 @@ declare module 'discord.js' { readonly description: string; readonly commands: Collection; readonly interactions: Collection; + readonly prefixCommands: Collection; readonly commandCooldowns: CooldownService; readonly reactionCooldowns: Collection; diff --git a/src/utils/CommandUtils.ts b/src/utils/CommandUtils.ts index dea753a38..be25d9724 100644 --- a/src/utils/CommandUtils.ts +++ b/src/utils/CommandUtils.ts @@ -1,4 +1,5 @@ import type BaseCommand from '#main/core/BaseCommand.js'; +import type BasePrefixCommand from '#main/core/BasePrefixCommand.js'; import { type InteractionFunction } from '#main/decorators/Interaction.js'; import { CommandLoader } from '#main/modules/Loaders/CommandLoader.js'; import { InteractionLoader } from '#main/modules/Loaders/InteractionLoader.js'; @@ -23,11 +24,12 @@ export const loadInteractions = async (map: Collection, +export const loadCommands = async ( + commandsMap: Collection, + prefixMap: Collection, interactionsMap: Collection, ) => { - const loader = new CommandLoader(map, interactionsMap); + const loader = new CommandLoader(commandsMap, prefixMap, interactionsMap); await loader.load(); }; diff --git a/src/utils/PrefixCmdHandler.ts b/src/utils/PrefixCmdHandler.ts new file mode 100644 index 000000000..f4f65e9bc --- /dev/null +++ b/src/utils/PrefixCmdHandler.ts @@ -0,0 +1,65 @@ +import type BasePrefixCommand from '#main/core/BasePrefixCommand.js'; +import Logger from '#main/utils/Logger.js'; +import { isDev } from '#main/utils/Utils.js'; +import { Message } from 'discord.js'; + +const handlePrefixCommand = async (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 as BasePrefixCommand).data.aliases?.includes(commandName), + ); + + if (!command) return; + + try { + // Check if command is owner-only + if (command.data.ownerOnly) { + if (!isDev(message.author.id)) { + await message.reply('This command can only be used by the bot owner.'); + return; + } + } + + // Check user permissions + if (command.data.requiredUserPermissions?.length) { + const missingPerms = command.data.requiredUserPermissions.filter( + (perm) => !message.member?.permissions.has(perm), + ); + + if (missingPerms.length) { + await message.reply(`You're missing the following permissions: ${missingPerms.join(', ')}`); + return; + } + } + + // Check bot permissions + if (command.data.requiredBotPermissions?.length) { + const botMember = message.guild?.members.cache.get(message.client.user.id); + const missingPerms = command.data.requiredBotPermissions.filter( + (perm) => !botMember?.permissions.has(perm), + ); + + if (missingPerms.length) { + await message.reply(`I'm missing the following permissions: ${missingPerms.join(', ')}`); + return; + } + } + + // Execute command + await command.execute(message as Message, args); + } + catch (error) { + Logger.error(error); + await message.reply('There was an error executing this command!'); + } +}; + +export default handlePrefixCommand; diff --git a/src/utils/hub/utils.ts b/src/utils/hub/utils.ts index 96d3873cd..a0632d8f1 100644 --- a/src/utils/hub/utils.ts +++ b/src/utils/hub/utils.ts @@ -62,4 +62,4 @@ export const isStaffOrHubMod = (userId: string, hub: Hub) => export const isHubManager = (userId: string, hub: Hub) => hub.ownerId === userId || - hub.moderators.find((mod) => mod.userId === userId && mod.position === 'manager'); + hub.moderators.some((mod) => mod.userId === userId && mod.position === 'manager'); diff --git a/src/utils/moderation/modActions/modActionsPanel.ts b/src/utils/moderation/modActions/modActionsPanel.ts index 90ae79a28..f82950d2d 100644 --- a/src/utils/moderation/modActions/modActionsPanel.ts +++ b/src/utils/moderation/modActions/modActionsPanel.ts @@ -14,6 +14,7 @@ import { ButtonBuilder, ButtonStyle, EmbedBuilder, + Message, } from 'discord.js'; type BuilderOpts = { @@ -23,42 +24,47 @@ type BuilderOpts = { isBanned: boolean; }; -const buildButtons = (interaction: Interaction, messageId: Snowflake, opts: BuilderOpts) => { +const buildButtons = ( + interaction: Interaction | Message, + messageId: Snowflake, + opts: BuilderOpts, +) => { + const author = interaction instanceof Message ? interaction.author : interaction.user; const buttons = new ActionRowBuilder().addComponents( new ButtonBuilder() .setCustomId( - new CustomID('modActions:blacklistUser', [interaction.user.id, messageId]).toString(), + new CustomID('modActions:blacklistUser', [author.id, messageId]).toString(), ) .setStyle(ButtonStyle.Secondary) .setEmoji(emojis.user_icon) .setDisabled(opts.isUserBlacklisted), new ButtonBuilder() .setCustomId( - new CustomID('modActions:blacklistServer', [interaction.user.id, messageId]).toString(), + new CustomID('modActions:blacklistServer', [author.id, messageId]).toString(), ) .setStyle(ButtonStyle.Secondary) .setEmoji(emojis.globe_icon) .setDisabled(opts.isServerBlacklisted), new ButtonBuilder() .setCustomId( - new CustomID('modActions:removeAllReactions', [interaction.user.id, messageId]).toString(), + new CustomID('modActions:removeAllReactions', [author.id, messageId]).toString(), ) .setStyle(ButtonStyle.Secondary) .setEmoji(emojis.add_icon), new ButtonBuilder() .setCustomId( - new CustomID('modActions:deleteMsg', [interaction.user.id, messageId]).toString(), + new CustomID('modActions:deleteMsg', [author.id, messageId]).toString(), ) .setStyle(ButtonStyle.Secondary) .setEmoji(emojis.deleteDanger_icon) .setDisabled(opts.isDeleteInProgress), ); - if (checkIfStaff(interaction.user.id)) { + if (checkIfStaff(author.id)) { buttons.addComponents( new ButtonBuilder() .setCustomId( - new CustomID('modActions:banUser', [interaction.user.id, messageId]).toString(), + new CustomID('modActions:banUser', [author.id, messageId]).toString(), ) .setStyle(ButtonStyle.Secondary) .setEmoji(emojis.blobFastBan) @@ -69,7 +75,7 @@ const buildButtons = (interaction: Interaction, messageId: Snowflake, opts: Buil const extras = new ActionRowBuilder().addComponents( new ButtonBuilder() .setCustomId( - new CustomID('modActions:viewInfractions', [interaction.user.id, messageId]).toString(), + new CustomID('modActions:viewInfractions', [author.id, messageId]).toString(), ) .setLabel('View Infractions') .setStyle(ButtonStyle.Secondary) @@ -108,7 +114,7 @@ const buildInfoEmbed = (username: string, servername: string, opts: BuilderOpts) `); }; -const buildMessage = async (interaction: Interaction, originalMsg: OriginalMessage) => { +const buildMessage = async (interaction: Interaction | Message, originalMsg: OriginalMessage) => { const user = await interaction.client.users.fetch(originalMsg.authorId); const server = await interaction.client.fetchGuild(originalMsg.guildId); const deleteInProgress = await isDeleteInProgress(originalMsg.messageId); diff --git a/src/utils/moderation/modActions/utils.ts b/src/utils/moderation/modActions/utils.ts index 8bdd1d38f..0e18f91df 100644 --- a/src/utils/moderation/modActions/utils.ts +++ b/src/utils/moderation/modActions/utils.ts @@ -1,5 +1,6 @@ import { emojis } from '#main/config/Constants.js'; import { OriginalMessage } from '#main/utils/network/messageUtils.js'; +import { getReplyMethod } from '#main/utils/Utils.js'; import { InfoEmbed } from '#utils/EmbedUtils.js'; import { type supportedLocaleCodes, t } from '#utils/Locale.js'; import type { @@ -31,6 +32,8 @@ export async function replyWithUnknownMessage( t('errors.unknownNetworkMessage', locale, { emoji: emojis.no }), ); + const replyMethod = getReplyMethod(interaction); + if (edit) await interaction.editReply({ embeds: [embed] }); - else await interaction.reply({ embeds: [embed] }); + else await interaction[replyMethod]({ embeds: [embed], ephemeral: true }); } diff --git a/src/utils/network/messageUtils.ts b/src/utils/network/messageUtils.ts index 57c9fe384..266f6ac2d 100644 --- a/src/utils/network/messageUtils.ts +++ b/src/utils/network/messageUtils.ts @@ -1,4 +1,4 @@ -import { RedisKeys } from '#main/config/Constants.js'; +import Constants, { RedisKeys } from '#main/config/Constants.js'; import getRedis from '#main/utils/Redis.js'; import type { Message, Snowflake } from 'discord.js'; import isEmpty from 'lodash/isEmpty.js'; @@ -67,12 +67,9 @@ export const addBroadcasts = async ( ); // Add all broadcasts to the hash in a single operation - await redis - .multi() - .hset(broadcastsKey, broadcastEntries) - .expire(broadcastsKey, 86400) - .mset(reverseLookups) - .exec(); + await redis.hset(broadcastsKey, broadcastEntries); + await redis.expire(broadcastsKey, 86400); + await redis.mset(reverseLookups); reverseLookups .filter((_, i) => i % 2 === 0) @@ -132,3 +129,8 @@ export const deleteMessageCache = async (originalMsgId: Snowflake) => { return count; }; + +export const getMessageIdFromStr = (str: string) => { + const match = str.match(Constants.Regex.MessageLink)?.[3] ?? str.match(/(\d{17,19})/)?.[0]; + return match; +}; diff --git a/src/utils/network/storeMessageData.ts b/src/utils/network/storeMessageData.ts index d6aa8a6c6..49328b1bc 100644 --- a/src/utils/network/storeMessageData.ts +++ b/src/utils/network/storeMessageData.ts @@ -50,6 +50,7 @@ export default async ( const validBroadcasts: Broadcast[] = []; const validErrors = [ 'Unknown Webhook', + 'Unknown Channel', 'Missing Permissions', 'Invalid Webhook Token', 'The provided webhook URL is not valid.', @@ -69,7 +70,7 @@ export default async ( }); }); - await addBroadcasts(hubId, message.id, ...validBroadcasts); + if (validBroadcasts.length > 0) await addBroadcasts(hubId, message.id, ...validBroadcasts); await storeMessageTimestamp(message); // disconnect network if, webhook does not exist/bot cannot access webhook