From 52e07f14cd849fe26ba839335c27533c5f1ce5bb Mon Sep 17 00:00:00 2001 From: dev-737 <73829355+dev-737@users.noreply.github.com> Date: Fri, 10 May 2024 21:28:42 +0530 Subject: [PATCH 1/8] refactor(network): another scary refactor of the broadcasting part --- src/cluster.ts | 12 +- src/decorators/GatewayEvent.ts | 2 +- src/index.ts | 1 - src/managers/EventManager.ts | 174 ++++-------------------- src/scripts/hub/manage.ts | 6 +- src/scripts/network/antiSpam.ts | 24 ++-- src/scripts/network/broadcast.ts | 122 +++++++++++++++++ src/scripts/network/helpers.ts | 64 ++++++++- src/scripts/network/runChecks.ts | 36 +++-- src/scripts/network/storeMessageData.ts | 9 +- src/scripts/reaction/sortReactions.ts | 24 ++-- src/typings/index.d.ts | 1 - src/utils/ReactionUpdater.ts | 5 +- src/utils/Utils.ts | 31 ++--- 14 files changed, 285 insertions(+), 226 deletions(-) create mode 100644 src/scripts/network/broadcast.ts diff --git a/src/cluster.ts b/src/cluster.ts index d7fcac166..435d66991 100644 --- a/src/cluster.ts +++ b/src/cluster.ts @@ -24,11 +24,11 @@ clusterManager.on('clusterCreate', async (cluster) => { // remove expired blacklists or set new timers for them const serverQuery = { where: { hubs: { some: { expires: { isSet: true } } } } }; const userQuery = { where: { blacklistedFrom: { some: { expires: { isSet: true } } } } }; - updateBlacklists(await db.blacklistedServers.findMany(serverQuery), scheduler) - .catch(Logger.error); + updateBlacklists(await db.blacklistedServers.findMany(serverQuery), scheduler).catch( + Logger.error, + ); - updateBlacklists(await db.userData.findMany(userQuery), scheduler) - .catch(Logger.error); + updateBlacklists(await db.userData.findMany(userQuery), scheduler).catch(Logger.error); // code must be in production to run these tasks if (isDevBuild) return; @@ -57,7 +57,7 @@ voteManager.on('vote', async (vote) => { }); // spawn clusters and start the api that handles nsfw filter and votes -clusterManager.spawn({ timeout: -1 }) +clusterManager + .spawn({ timeout: -1 }) .then(() => startApi({ voteManager })) .catch(Logger.error); - diff --git a/src/decorators/GatewayEvent.ts b/src/decorators/GatewayEvent.ts index 2e21557da..400595481 100644 --- a/src/decorators/GatewayEvent.ts +++ b/src/decorators/GatewayEvent.ts @@ -17,4 +17,4 @@ export default function GatewayEvent(eventName: keyof ClientEvents): MethodDecor eventMethods.set(eventName, values); }; -} \ No newline at end of file +} diff --git a/src/index.ts b/src/index.ts index 06d4d6d18..6d23f432e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -15,5 +15,4 @@ client.on('debug', (debug) => { Logger.debug(debug); }); - client.start(); diff --git a/src/managers/EventManager.ts b/src/managers/EventManager.ts index f1aa789ef..027fca191 100644 --- a/src/managers/EventManager.ts +++ b/src/managers/EventManager.ts @@ -1,4 +1,3 @@ -/* eslint-disable complexity */ import { ActionRowBuilder, ButtonBuilder, @@ -12,7 +11,6 @@ import { Message, MessageReaction, PartialUser, - WebhookMessageCreateOptions, Interaction, Client, } from 'discord.js'; @@ -22,20 +20,20 @@ import GatewayEvent from '../decorators/GatewayEvent.js'; import { stripIndents } from 'common-tags'; import getWelcomeTargets from '../scripts/guilds/getWelcomeTarget.js'; import { logGuildJoin, logGuildLeave } from '../scripts/guilds/goals.js'; -import { channels, emojis, colors, LINKS, REGEX } from '../utils/Constants.js'; -import { censor, check } from '../utils/Profanity.js'; +import { channels, emojis, colors, LINKS } from '../utils/Constants.js'; +import { check } from '../utils/Profanity.js'; import db from '../utils/Db.js'; import { t } from '../utils/Locale.js'; -import storeMessageData, { NetworkWebhookSendResult } from '../scripts/network/storeMessageData.js'; -import { buildNetworkEmbed, getReferredContent } from '../scripts/network/helpers.js'; -import sendMessage from '../scripts/network/sendMessage.js'; +import storeMessageData from '../scripts/network/storeMessageData.js'; +import { getReferredMsgData } from '../scripts/network/helpers.js'; import { HubSettingsBitField } from '../utils/BitFields.js'; import { getAttachmentURL, handleError, simpleEmbed, wait } from '../utils/Utils.js'; import { runChecks } from '../scripts/network/runChecks.js'; -import SuperClient from '../core/Client.js'; import { addReaction, updateReactions } from '../scripts/reaction/actions.js'; import { checkBlacklists } from '../scripts/reaction/helpers.js'; import { CustomID } from '../utils/CustomID.js'; +import SuperClient from '../core/Client.js'; +import broadcast from '../scripts/network/broadcast.js'; export default abstract class EventManager { @GatewayEvent('ready') @@ -55,7 +53,9 @@ export default abstract class EventManager { @GatewayEvent('messageReactionAdd') async onReactionAdd(reaction: MessageReaction, user: User | PartialUser) { - Logger.info(`${user.tag} reacted with ${reaction.emoji.name} in channel ${reaction.message.channelId} and guild ${reaction.message.guildId}.`); + Logger.info( + `${user.tag} reacted with ${reaction.emoji.name} in channel ${reaction.message.channelId} and guild ${reaction.message.guildId}.`, + ); if (user.bot || !reaction.message.inGuild()) return; @@ -239,14 +239,14 @@ export default abstract class EventManager { async onMessageCreate(message: Message): Promise { if (message.author?.bot || message.system || message.webhookId) return; - const { connectionCache, cachePopulated } = message.client; + const { connectionCache, cachePopulated, getUserLocale } = message.client; while (!cachePopulated) { Logger.debug('[InterChat]: Cache not populated, retrying in 5 seconds...'); await wait(5000); } - const locale = await message.client.getUserLocale(message.author.id); + const locale = await getUserLocale(message.author.id); message.author.locale = locale; // check if the message was sent in a network channel @@ -267,141 +267,6 @@ export default abstract class EventManager { return; } - const censoredContent = censor(message.content); - - // fetch the referred message (message being replied to) from discord - const referredMessage = message.reference - ? await message.fetchReference().catch(() => undefined) - : undefined; - - // check if it was sent in the network - const referenceInDb = referredMessage - ? ( - await db.broadcastedMessages.findFirst({ - where: { messageId: referredMessage?.id }, - include: { originalMsg: { include: { broadcastMsgs: true } } }, - }) - )?.originalMsg - : undefined; - - let referredContent: string | undefined = undefined; - let referredAuthor: User | null = null; - - // only assign to this variable if one of these two conditions are true, not always - if (referredMessage) { - if (referredMessage?.author.id === message.client.user.id) { - referredContent = getReferredContent(referredMessage); - referredAuthor = message.client.user; - } - else if (referenceInDb) { - referredContent = getReferredContent(referredMessage); - referredAuthor = await message.client.users.fetch(referenceInDb.authorId).catch(() => null); - } - } - - const username = ( - settings.has('UseNicknames') - ? message.member?.displayName || message.author.displayName - : message.author.username - ) - .slice(0, 35) - .replace(REGEX.BANNED_WEBHOOK_WORDS, '[censored]'); - - const servername = message.guild?.name - .slice(0, 35) - .replace(REGEX.BANNED_WEBHOOK_WORDS, '[censored]'); - - // embeds for the normal mode - const { embed, censoredEmbed } = buildNetworkEmbed(message, username, censoredContent, { - attachmentURL, - referredContent, - embedCol: (connection.embedColor as HexColorString) ?? undefined, - }); - - // ---------- Broadcasting --------- - const sendResult = hubConnections.map(async (otherConnection) => { - try { - const reply = referenceInDb?.broadcastMsgs.find( - (msg) => msg.channelId === otherConnection.channelId, - ); - // create a jump to reply button - const jumpButton = - reply && referredMessage?.author - ? new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setStyle(ButtonStyle.Link) - .setEmoji(emojis.reply) - .setURL( - `https://discord.com/channels/${otherConnection.serverId}/${reply.channelId}/${reply.messageId}`, - ) - .setLabel( - referredAuthor?.username && referredAuthor.username.length >= 80 - ? `@${referredAuthor.username.slice(0, 76)}...` - : `@${referredAuthor?.username}`, - ), - ) - : null; - - // embed format - let messageFormat: WebhookMessageCreateOptions = { - components: jumpButton ? [jumpButton] : undefined, - embeds: [otherConnection.profFilter ? censoredEmbed : embed], - username: `${hub.name}`, - avatarURL: hub.iconUrl, - threadId: otherConnection.parentId ? otherConnection.channelId : undefined, - allowedMentions: { parse: [] }, - }; - - if (otherConnection.compact) { - const replyContent = - otherConnection.profFilter && referredContent - ? censor(referredContent) - : referredContent; - - // preview embed for the message being replied to - const replyEmbed = replyContent - ? new EmbedBuilder({ - description: replyContent, - author: { - name: `${referredAuthor?.username?.slice(0, 30)}`, - icon_url: referredAuthor?.displayAvatarURL(), - }, - }).setColor('Random') - : undefined; - - // compact format (no embeds, only content) - messageFormat = { - username: `@${username} • ${servername}`, - avatarURL: message.author.displayAvatarURL(), - embeds: replyEmbed ? [replyEmbed] : undefined, - components: jumpButton ? [jumpButton] : undefined, - content: - (otherConnection.profFilter ? censoredContent : message.content) + - // append the attachment url if there is one - `${attachment ? `\n${attachmentURL}` : ''}`, - // username is already limited to 50 characters, server name is limited to 40 (char limit is 100) - threadId: otherConnection.parentId ? otherConnection.channelId : undefined, - allowedMentions: { parse: [] }, - }; - } - // send the message - const messageOrError = await sendMessage(messageFormat, otherConnection.webhookURL); - - // return the message and webhook URL to store the message in the db - return { - messageOrError: messageOrError, - webhookURL: otherConnection.webhookURL, - } as NetworkWebhookSendResult; - } - catch (e) { - // return the error and webhook URL to store the message in the db - return { - messageOrError: e.message, - webhookURL: otherConnection.webhookURL, - } as NetworkWebhookSendResult; - } - }); - const userData = await db.userData.findFirst({ where: { userId: message.author.id, viewedNetworkWelcome: true }, }); @@ -453,12 +318,27 @@ export default abstract class EventManager { .catch(() => null); } + // fetch the referred message (message being replied to) from discord + const referredMessage = message.reference + ? await message.fetchReference().catch(() => null) + : null; + + const { dbReferrence, referredAuthor } = await getReferredMsgData(referredMessage); + + const sendResult = await broadcast(message, hub, hubConnections, settings, { + attachmentURL, + dbReferrence, + referredAuthor, + referredMessage, + embedColor: connection.embedColor as HexColorString, + }); + // only delete the message if there is no attachment or if the user has already viewed the welcome message // deleting attachments will make the image not show up in the embed (discord removes it from its cdn) if (!attachment) message.delete().catch(() => null); // store the message in the db - await storeMessageData(message, await Promise.all(sendResult), connection.hubId, referenceInDb); + await storeMessageData(message, await Promise.all(sendResult), connection.hubId, dbReferrence); } @GatewayEvent('interactionCreate') diff --git a/src/scripts/hub/manage.ts b/src/scripts/hub/manage.ts index a3354937c..03e21ceac 100644 --- a/src/scripts/hub/manage.ts +++ b/src/scripts/hub/manage.ts @@ -6,7 +6,11 @@ import { CustomID } from '../../utils/CustomID.js'; import db from '../../utils/Db.js'; import { supportedLocaleCodes, t } from '../../utils/Locale.js'; -export const actionsSelect = (hubId: string, userId: string, locale: supportedLocaleCodes = 'en') => { +export const actionsSelect = ( + hubId: string, + userId: string, + locale: supportedLocaleCodes = 'en', +) => { return new ActionRowBuilder().addComponents( new StringSelectMenuBuilder() .setCustomId( diff --git a/src/scripts/network/antiSpam.ts b/src/scripts/network/antiSpam.ts index c80469997..05f49e207 100644 --- a/src/scripts/network/antiSpam.ts +++ b/src/scripts/network/antiSpam.ts @@ -5,18 +5,17 @@ interface AntiSpamUserOpts { infractions: number; } -export const antiSpamMap = new Collection; +export const antiSpamMap = new Collection(); const WINDOW_SIZE = 5000; const MAX_STORE = 3; - /** - * Runs the anti-spam mechanism for a given user. - * @param author - The user to run the anti-spam mechanism for. - * @param maxInfractions - The maximum number of infractions before the user is blacklisted. - * @returns The user's anti-spam data if they have reached the maximum number of infractions, otherwise undefined. - */ + * Runs the anti-spam mechanism for a given user. + * @param author - The user to run the anti-spam mechanism for. + * @param maxInfractions - The maximum number of infractions before the user is blacklisted. + * @returns The user's anti-spam data if they have reached the maximum number of infractions, otherwise undefined. + */ export const runAntiSpam = (author: User, maxInfractions = MAX_STORE) => { const userInCol = antiSpamMap.get(author.id); const currentTimestamp = Date.now(); @@ -58,12 +57,11 @@ export const runAntiSpam = (author: User, maxInfractions = MAX_STORE) => { } }; - /** - * Sets spam timers for a given user. - * @param userId - The ID of the user to set spam timers for. - * @returns void - */ + * Sets spam timers for a given user. + * @param userId - The ID of the user to set spam timers for. + * @returns void + */ export const setSpamTimers = (user: User) => { const five_min = 60 * 5000; const userInCol = antiSpamMap.get(user.id); @@ -77,4 +75,4 @@ export const setSpamTimers = (user: User) => { scheduler.addRecurringTask(`removeFromCol_${user.id}`, new Date(Date.now() + five_min), () => { antiSpamMap.delete(user.id); }); -}; \ No newline at end of file +}; diff --git a/src/scripts/network/broadcast.ts b/src/scripts/network/broadcast.ts new file mode 100644 index 000000000..2e53224ba --- /dev/null +++ b/src/scripts/network/broadcast.ts @@ -0,0 +1,122 @@ +import { + Collection, + EmbedBuilder, + HexColorString, + Message, + User, + WebhookMessageCreateOptions, +} from 'discord.js'; +import { + buildNetworkEmbed, + generateJumpButton, + getReferredContent, + trimAndCensorBannedWebhookWords, +} from './helpers.js'; +import { censor } from '../../utils/Profanity.js'; +import { broadcastedMessages, connectedList, hubs, originalMessages } from '@prisma/client'; +import { HubSettingsBitField } from '../../utils/BitFields.js'; +import sendMessage from './sendMessage.js'; +import { NetworkWebhookSendResult } from './storeMessageData.js'; + +type BroadcastOpts = { + embedColor?: HexColorString | null; + attachmentURL?: string | null; + referredMessage: Message | null; + dbReferrence: (originalMessages & { broadcastMsgs: broadcastedMessages[] }) | undefined; + referredAuthor: User | undefined; +}; + +export default async ( + message: Message, + hub: hubs, + allConnected: Collection, + settings: HubSettingsBitField, + opts: BroadcastOpts, +) => { + if (!message.guild) return Promise<[]>; + + const censoredContent = censor(message.content); + const referredContent = opts.referredMessage + ? getReferredContent(opts.referredMessage) + : undefined; + const servername = trimAndCensorBannedWebhookWords(message.guild.name); + const username = trimAndCensorBannedWebhookWords( + settings.has('UseNicknames') + ? message.member?.displayName || message.author.displayName + : message.author.username, + ); + // embeds for the normal mode + const { embed, censoredEmbed } = buildNetworkEmbed(message, username, censoredContent, { + attachmentURL: opts.attachmentURL, + referredContent, + embedCol: opts.embedColor ?? undefined, + }); + + return allConnected.map(async (connection) => { + try { + const reply = opts.dbReferrence?.broadcastMsgs.find( + (msg) => msg.channelId === connection.channelId, + ); + + const jumpButton = reply + ? generateJumpButton(reply, opts.referredAuthor?.username, connection.serverId) + : undefined; + + // embed format + let messageFormat: WebhookMessageCreateOptions = { + components: jumpButton ? [jumpButton] : undefined, + embeds: [connection.profFilter ? censoredEmbed : embed], + username: `${hub.name}`, + avatarURL: hub.iconUrl, + threadId: connection.parentId ? connection.channelId : undefined, + allowedMentions: { parse: [] }, + }; + + if (connection.compact) { + const replyContent = + connection.profFilter && referredContent ? censor(referredContent) : referredContent; + + // preview embed for the message being replied to + const replyEmbed = replyContent + ? new EmbedBuilder({ + description: replyContent, + author: { + name: `${opts.referredAuthor?.username?.slice(0, 30)}`, + icon_url: opts.referredAuthor?.displayAvatarURL(), + }, + }).setColor('Random') + : undefined; + + // compact format (no embeds, only content) + messageFormat = { + username: `@${username} • ${servername}`, + avatarURL: message.author.displayAvatarURL(), + embeds: replyEmbed ? [replyEmbed] : undefined, + components: jumpButton ? [jumpButton] : undefined, + content: + (connection.profFilter ? censoredContent : message.content) + + // append the attachment url if there is one + `${opts.attachmentURL ? `\n${opts.attachmentURL}` : ''}`, + // username is already limited to 50 characters, server name is limited to 40 (char limit is 100) + threadId: connection.parentId ? connection.channelId : undefined, + allowedMentions: { parse: [] }, + }; + } + + const messageOrError = await sendMessage(messageFormat, connection.webhookURL); + + // return the message and webhook URL to store the message in the db + return { + messageOrError, + webhookURL: connection.webhookURL, + } as NetworkWebhookSendResult; + } + catch (e) { + // return the error and webhook URL to store the message in the db + return { + messageOrError: e.message, + webhookURL: connection.webhookURL, + } as NetworkWebhookSendResult; + } + }); +}; diff --git a/src/scripts/network/helpers.ts b/src/scripts/network/helpers.ts index ed4df6722..c5d90f2a7 100644 --- a/src/scripts/network/helpers.ts +++ b/src/scripts/network/helpers.ts @@ -1,6 +1,15 @@ -import { Message, HexColorString, EmbedBuilder } from 'discord.js'; -import { REGEX } from '../../utils/Constants.js'; +import { + Message, + HexColorString, + EmbedBuilder, + ButtonStyle, + ButtonBuilder, + ActionRowBuilder, +} from 'discord.js'; +import { REGEX, emojis } from '../../utils/Constants.js'; import { censor } from '../../utils/Profanity.js'; +import db from '../../utils/Db.js'; +import { broadcastedMessages } from '@prisma/client'; /** * Retrieves the content of a referred message, which can be either the message's text content or the description of its first embed. @@ -22,6 +31,31 @@ export const getReferredContent = (referredMessage: Message) => { return referredContent; }; +export const getReferredMsgData = async (referredMessage: Message | null) => { + if (!referredMessage) return { dbReferrence: undefined, referredAuthor: undefined }; + + const { client } = referredMessage; + + // check if it was sent in the network + const dbReferrence = referredMessage + ? ( + await db.broadcastedMessages.findFirst({ + where: { messageId: referredMessage?.id }, + include: { originalMsg: { include: { broadcastMsgs: true } } }, + }) + )?.originalMsg + : undefined; + + if (!dbReferrence) return { dbReferrence: undefined, referredAuthor: undefined }; + + const referredAuthor = + referredMessage.author.id === client.user.id + ? client.user + : await client.users.fetch(dbReferrence.authorId).catch(() => undefined); + + return { dbReferrence, referredAuthor }; +}; + /** * Builds an embed for a network message. * @param message The network message to build the embed for. @@ -76,3 +110,29 @@ export const buildNetworkEmbed = ( return { embed, censoredEmbed }; }; + +export const trimAndCensorBannedWebhookWords = (content: t) => + content?.slice(0, 35).replace(REGEX.BANNED_WEBHOOK_WORDS, '[censored]'); + +export const generateJumpButton = ( + replyMsg: broadcastedMessages, + referredAuthorUsername: string | undefined, + serverId: string, +) => { + // create a jump to reply button + return replyMsg && referredAuthorUsername + ? new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setStyle(ButtonStyle.Link) + .setEmoji(emojis.reply) + .setURL( + `https://discord.com/channels/${serverId}/${replyMsg.channelId}/${replyMsg.messageId}`, + ) + .setLabel( + referredAuthorUsername.length >= 80 + ? `@${referredAuthorUsername.slice(0, 76)}...` + : `@${referredAuthorUsername}`, + ), + ) + : null; +}; diff --git a/src/scripts/network/runChecks.ts b/src/scripts/network/runChecks.ts index b910ca220..811429f14 100644 --- a/src/scripts/network/runChecks.ts +++ b/src/scripts/network/runChecks.ts @@ -23,7 +23,9 @@ export const isUserBlacklisted = async (message: Message, hubId: string) => { export const replyToMsg = async (message: Message, content: string) => { const reply = await message.reply(content).catch(() => null); - if (!reply) await message.channel.send(`${message.author.toString()} ${content}`).catch(() => null); + if (!reply) { + await message.channel.send(`${message.author.toString()} ${content}`).catch(() => null); + } }; export const containsStickers = (message: Message) => { return message.stickers.size > 0 && !message.content; @@ -33,11 +35,13 @@ export const containsInviteLinks = (message: Message, settings: HubSettingsBitFi const inviteLinks = ['discord.gg', 'discord.com/invite', 'dsc.gg']; // check if message contains invite links from the array - return ( - settings.has('BlockInvites') && inviteLinks.some((link) => message.content.includes(link)) - ); + return settings.has('BlockInvites') && inviteLinks.some((link) => message.content.includes(link)); }; -export const isCaughtSpam = async (message: Message, settings: HubSettingsBitField, hubId: string) => { +export const isCaughtSpam = async ( + message: Message, + settings: HubSettingsBitField, + hubId: string, +) => { const antiSpamResult = runAntiSpam(message.author, 3); if (!antiSpamResult) return false; const { blacklistManager } = message.client; @@ -101,12 +105,12 @@ export const attachmentTooLarge = (message: Message) => { return (attachment && attachment.size > 1024 * 1024 * 8) === true; }; /** - * Runs various checks on a message to determine if it can be sent in the network. - * @param message - The message to check. - * @param settings - The settings for the network. - * @param hubId - The ID of the hub the message is being sent in. - * @returns A boolean indicating whether the message passed all checks. - */ + * Runs various checks on a message to determine if it can be sent in the network. + * @param message - The message to check. + * @param settings - The settings for the network. + * @param hubId - The ID of the hub the message is being sent in. + * @returns A boolean indicating whether the message passed all checks. + */ export const runChecks = async ( message: Message, @@ -143,10 +147,7 @@ export const runChecks = async ( } if (message.content.length > 1000) { - await replyToMsg( - message, - 'Your message is too long! Please keep it under 1000 characters.', - ); + await replyToMsg(message, 'Your message is too long! Please keep it under 1000 characters.'); return false; } if (containsStickers(message)) { @@ -164,10 +165,7 @@ export const runChecks = async ( return false; } if (unsupportedAttachment(message)) { - await replyToMsg( - message, - 'Only images and gifs are allowed to be sent within the network.', - ); + await replyToMsg(message, 'Only images and gifs are allowed to be sent within the network.'); return false; } diff --git a/src/scripts/network/storeMessageData.ts b/src/scripts/network/storeMessageData.ts index 96f75ab93..eca4a96e6 100644 --- a/src/scripts/network/storeMessageData.ts +++ b/src/scripts/network/storeMessageData.ts @@ -3,17 +3,16 @@ import { APIMessage, Message } from 'discord.js'; import { parseTimestampFromId } from '../../utils/Utils.js'; import db from '../../utils/Db.js'; - export interface NetworkWebhookSendResult { messageOrError: APIMessage | string; webhookURL: string; } /** - * Stores message data in the database and updates the connectedList based on the webhook status. - * @param channelAndMessageIds The result of sending the message to multiple channels. - * @param hubId The ID of the hub to connect the message data to. - */ + * Stores message data in the database and updates the connectedList based on the webhook status. + * @param channelAndMessageIds The result of sending the message to multiple channels. + * @param hubId The ID of the hub to connect the message data to. + */ export default async ( message: Message, channelAndMessageIds: NetworkWebhookSendResult[], diff --git a/src/scripts/reaction/sortReactions.ts b/src/scripts/reaction/sortReactions.ts index b84b6a97d..2721efab8 100644 --- a/src/scripts/reaction/sortReactions.ts +++ b/src/scripts/reaction/sortReactions.ts @@ -1,15 +1,15 @@ /** - * Sorts the reactions object based on the reaction counts. - * @param reactions - The reactions object to be sorted. - * @returns The sorted reactions object in the form of an array. - * The array is sorted in descending order based on the length of the reaction arrays. - * Each element of the array is a tuple containing the reaction and its corresponding array of user IDs. - * - * ### Example: - * ```js - * [ [ '👎', ['1020193019332334'] ], [ '👍', ['1020193019332334'] ] ] - * ``` - */ + * Sorts the reactions object based on the reaction counts. + * @param reactions - The reactions object to be sorted. + * @returns The sorted reactions object in the form of an array. + * The array is sorted in descending order based on the length of the reaction arrays. + * Each element of the array is a tuple containing the reaction and its corresponding array of user IDs. + * + * ### Example: + * ```js + * [ [ '👎', ['1020193019332334'] ], [ '👍', ['1020193019332334'] ] ] + * ``` + */ export default (reactions: { [key: string]: string[] }) => { return Object.entries(reactions).sort((a, b) => b[1].length - a[1].length); -}; \ No newline at end of file +}; diff --git a/src/typings/index.d.ts b/src/typings/index.d.ts index 707f3e7a5..5fb4aefaa 100644 --- a/src/typings/index.d.ts +++ b/src/typings/index.d.ts @@ -53,4 +53,3 @@ declare module 'discord.js' { locale?: supportedLocaleCodes; } } - diff --git a/src/utils/ReactionUpdater.ts b/src/utils/ReactionUpdater.ts index c18ca2c09..b48a1bb81 100644 --- a/src/utils/ReactionUpdater.ts +++ b/src/utils/ReactionUpdater.ts @@ -46,8 +46,9 @@ export default class ReactionUpdater { if ( !messageInDb?.originalMsg.hub || !new HubSettingsBitField(messageInDb.originalMsg.hub.settings).has('Reactions') - ) return; - + ) { + return; + } const { userBlacklisted, serverBlacklisted } = await checkBlacklists( messageInDb.originalMsg.hub.id, diff --git a/src/utils/Utils.ts b/src/utils/Utils.ts index 90977172d..783abd98e 100644 --- a/src/utils/Utils.ts +++ b/src/utils/Utils.ts @@ -73,14 +73,14 @@ export const sortReactions = (reactions: { [key: string]: string[] }): [string, export const hasVoted = async (userId: Snowflake): Promise => { if (!process.env.TOPGG_API_KEY) throw new TypeError('Missing TOPGG_API_KEY environment variable'); - const res = await ( + const res = (await ( await fetch(`${LINKS.TOPGG_API}/check?userId=${userId}`, { method: 'GET', headers: { Authorization: process.env.TOPGG_API_KEY, }, }) - ).json() as { voted: boolean }; + ).json()) as { voted: boolean }; return Boolean(res.voted); }; @@ -244,7 +244,7 @@ export const calculateAverageRating = (ratings: number[]): number => { return Math.round(average * 10) / 10; }; -type ImgurResponse = { data: { link: string; nsfw: boolean, cover: string } }; +type ImgurResponse = { data: { link: string; nsfw: boolean; cover: string } }; export const checkAndFetchImgurUrl = async (url: string): Promise => { const regex = REGEX.IMGUR_LINKS; @@ -259,7 +259,7 @@ export const checkAndFetchImgurUrl = async (url: string): Promise null) as ImgurResponse; + const data = (await response.json().catch(() => null)) as ImgurResponse; if (!data || data?.data?.nsfw) { return false; } @@ -419,11 +419,11 @@ export const modifyUserRole = async ( }; /** - * Sends a message to all connections in a hub's network. - * @param hubId The ID of the hub to send the message to. - * @param message The message to send. Can be a string or a MessageCreateOptions object. - * @returns A array of the responses from each connection's webhook. - */ + * Sends a message to all connections in a hub's network. + * @param hubId The ID of the hub to send the message to. + * @param message The message to send. Can be a string or a MessageCreateOptions object. + * @returns A array of the responses from each connection's webhook. + */ export const sendToHub = async (hubId: string, message: string | WebhookMessageCreateOptions) => { const connections = await db.connectedList.findMany({ where: { hubId } }); @@ -432,7 +432,7 @@ export const sendToHub = async (hubId: string, message: string | WebhookMessageC .map(async (connection) => { const threadId = connection.parentId ? connection.channelId : undefined; const payload = - typeof message === 'string' ? { content: message, threadId } : { ...message, threadId }; + typeof message === 'string' ? { content: message, threadId } : { ...message, threadId }; const webhook = new WebhookClient({ url: connection.webhookURL }); return await webhook.send(payload).catch(() => null); @@ -441,12 +441,11 @@ export const sendToHub = async (hubId: string, message: string | WebhookMessageC return await Promise.all(res); }; - /** - * Returns the URL of an attachment in a message, if it exists. - * @param message The message to search for an attachment URL. - * @returns The URL of the attachment, or null if no attachment is found. - */ + * Returns the URL of an attachment in a message, if it exists. + * @param message The message to search for an attachment URL. + * @returns The URL of the attachment, or null if no attachment is found. + */ export const getAttachmentURL = async (string: string) => { // Tenor Gifs / Image URLs const URLMatch = string.match(REGEX.IMAGE_URL); @@ -465,4 +464,4 @@ export const getAttachmentURL = async (string: string) => { return gifJSON.results.at(0)?.media.at(0)?.gif.url as string | null; } return null; -}; \ No newline at end of file +}; From efa47d679dc194a02d5dddaedacfcf6f7a799f88 Mon Sep 17 00:00:00 2001 From: dev-737 <73829355+dev-737@users.noreply.github.com> Date: Fri, 10 May 2024 16:30:10 +0000 Subject: [PATCH 2/8] fix some small stuff --- src/managers/EventManager.ts | 4 ++-- src/scripts/network/broadcast.ts | 15 +++++---------- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/src/managers/EventManager.ts b/src/managers/EventManager.ts index 027fca191..b7de84263 100644 --- a/src/managers/EventManager.ts +++ b/src/managers/EventManager.ts @@ -237,7 +237,7 @@ export default abstract class EventManager { @GatewayEvent('messageCreate') async onMessageCreate(message: Message): Promise { - if (message.author?.bot || message.system || message.webhookId) return; + if (message.author?.bot || message.system || message.webhookId || !message.inGuild()) return; const { connectionCache, cachePopulated, getUserLocale } = message.client; @@ -325,7 +325,7 @@ export default abstract class EventManager { const { dbReferrence, referredAuthor } = await getReferredMsgData(referredMessage); - const sendResult = await broadcast(message, hub, hubConnections, settings, { + const sendResult = broadcast(message, hub, hubConnections, settings, { attachmentURL, dbReferrence, referredAuthor, diff --git a/src/scripts/network/broadcast.ts b/src/scripts/network/broadcast.ts index 2e53224ba..2755cc27e 100644 --- a/src/scripts/network/broadcast.ts +++ b/src/scripts/network/broadcast.ts @@ -26,15 +26,13 @@ type BroadcastOpts = { referredAuthor: User | undefined; }; -export default async ( - message: Message, +export default ( + message: Message, hub: hubs, allConnected: Collection, settings: HubSettingsBitField, opts: BroadcastOpts, ) => { - if (!message.guild) return Promise<[]>; - const censoredContent = censor(message.content); const referredContent = opts.referredMessage ? getReferredContent(opts.referredMessage) @@ -42,9 +40,10 @@ export default async ( const servername = trimAndCensorBannedWebhookWords(message.guild.name); const username = trimAndCensorBannedWebhookWords( settings.has('UseNicknames') - ? message.member?.displayName || message.author.displayName + ? message.member?.displayName ?? message.author.displayName : message.author.username, ); + // embeds for the normal mode const { embed, censoredEmbed } = buildNetworkEmbed(message, username, censoredContent, { attachmentURL: opts.attachmentURL, @@ -93,11 +92,7 @@ export default async ( avatarURL: message.author.displayAvatarURL(), embeds: replyEmbed ? [replyEmbed] : undefined, components: jumpButton ? [jumpButton] : undefined, - content: - (connection.profFilter ? censoredContent : message.content) + - // append the attachment url if there is one - `${opts.attachmentURL ? `\n${opts.attachmentURL}` : ''}`, - // username is already limited to 50 characters, server name is limited to 40 (char limit is 100) + content: `${connection.profFilter ? censoredContent : message.content} ${opts.attachmentURL ? `\n${opts.attachmentURL}` : ''}`, threadId: connection.parentId ? connection.channelId : undefined, allowedMentions: { parse: [] }, }; From 110070150268b895e013d136a023184404804c01 Mon Sep 17 00:00:00 2001 From: dev-737 <73829355+dev-737@users.noreply.github.com> Date: Fri, 10 May 2024 16:34:17 +0000 Subject: [PATCH 3/8] more fixy --- src/managers/EventManager.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/managers/EventManager.ts b/src/managers/EventManager.ts index b7de84263..234e06b94 100644 --- a/src/managers/EventManager.ts +++ b/src/managers/EventManager.ts @@ -42,7 +42,7 @@ export default abstract class EventManager { } @GatewayEvent('shardReady') - async onShardReady(s: number, u: Set) { + onShardReady(s: number, u: Set) { if (u) { Logger.warn(`Shard ${s} is ready but ${u.size} guilds are unavailable.`); } @@ -241,7 +241,7 @@ export default abstract class EventManager { const { connectionCache, cachePopulated, getUserLocale } = message.client; - while (!cachePopulated) { + while (cachePopulated !== true) { Logger.debug('[InterChat]: Cache not populated, retrying in 5 seconds...'); await wait(5000); } From 74a0774cd5c797d99b3676918ed56c1906f4ff3f Mon Sep 17 00:00:00 2001 From: dev-737 <73829355+dev-737@users.noreply.github.com> Date: Fri, 10 May 2024 16:35:00 +0000 Subject: [PATCH 4/8] uh more ig? --- src/utils/Locale.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/utils/Locale.ts b/src/utils/Locale.ts index d872fd84b..35be46f30 100644 --- a/src/utils/Locale.ts +++ b/src/utils/Locale.ts @@ -55,8 +55,7 @@ export const loadLocales = (localesDirectory: string) => { localesMap.set(localeKey, parsedContent); } catch (error) { - Logger.error(`Error reading/parsing ${file}: ${error.message}`); - process.exit(0); + throw new Error(`Error reading/parsing ${file}: ${error.message}`); } }); From 45513d358859b6c234228f64e011b9ad1ea812e8 Mon Sep 17 00:00:00 2001 From: dev-737 <73829355+dev-737@users.noreply.github.com> Date: Fri, 10 May 2024 16:37:55 +0000 Subject: [PATCH 5/8] e --- src/managers/EventManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/managers/EventManager.ts b/src/managers/EventManager.ts index 234e06b94..0d7eb02bf 100644 --- a/src/managers/EventManager.ts +++ b/src/managers/EventManager.ts @@ -241,7 +241,7 @@ export default abstract class EventManager { const { connectionCache, cachePopulated, getUserLocale } = message.client; - while (cachePopulated !== true) { + while (!cachePopulated) { Logger.debug('[InterChat]: Cache not populated, retrying in 5 seconds...'); await wait(5000); } From 41f0853f48a025fe4eff5a901858c2b91b399e03 Mon Sep 17 00:00:00 2001 From: dev-737 <73829355+dev-737@users.noreply.github.com> Date: Sat, 11 May 2024 01:47:32 +0000 Subject: [PATCH 6/8] move welcome embed/message to helpers.ts --- src/managers/EventManager.ts | 63 +++---------------- .../{broadcast.ts => broadcastMessage.ts} | 1 + src/scripts/network/helpers.ts | 53 +++++++++++++++- 3 files changed, 61 insertions(+), 56 deletions(-) rename src/scripts/network/{broadcast.ts => broadcastMessage.ts} (99%) diff --git a/src/managers/EventManager.ts b/src/managers/EventManager.ts index 0d7eb02bf..3bb5dd198 100644 --- a/src/managers/EventManager.ts +++ b/src/managers/EventManager.ts @@ -25,7 +25,7 @@ import { check } from '../utils/Profanity.js'; import db from '../utils/Db.js'; import { t } from '../utils/Locale.js'; import storeMessageData from '../scripts/network/storeMessageData.js'; -import { getReferredMsgData } from '../scripts/network/helpers.js'; +import { getReferredMsgData, sendWelcomeMsg } from '../scripts/network/helpers.js'; import { HubSettingsBitField } from '../utils/BitFields.js'; import { getAttachmentURL, handleError, simpleEmbed, wait } from '../utils/Utils.js'; import { runChecks } from '../scripts/network/runChecks.js'; @@ -33,7 +33,7 @@ import { addReaction, updateReactions } from '../scripts/reaction/actions.js'; import { checkBlacklists } from '../scripts/reaction/helpers.js'; import { CustomID } from '../utils/CustomID.js'; import SuperClient from '../core/Client.js'; -import broadcast from '../scripts/network/broadcast.js'; +import broadcastMessage from '../scripts/network/broadcastMessage.js'; export default abstract class EventManager { @GatewayEvent('ready') @@ -267,57 +267,6 @@ export default abstract class EventManager { return; } - const userData = await db.userData.findFirst({ - where: { userId: message.author.id, viewedNetworkWelcome: true }, - }); - - if (!userData) { - await db.userData.upsert({ - where: { userId: message.author.id }, - create: { - userId: message.author.id, - username: message.author.username, - viewedNetworkWelcome: true, - }, - update: { viewedNetworkWelcome: true }, - }); - - const linkButtons = new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setStyle(ButtonStyle.Link) - .setEmoji(emojis.add_icon) - .setLabel('Invite Me!') - .setURL(LINKS.APP_DIRECTORY), - new ButtonBuilder() - .setStyle(ButtonStyle.Link) - .setEmoji(emojis.code_icon) - .setLabel('Support Server') - .setURL(LINKS.SUPPORT_INVITE), - new ButtonBuilder() - .setStyle(ButtonStyle.Link) - .setEmoji(emojis.docs_icon) - .setLabel('How-To Guide') - .setURL(LINKS.DOCS), - ); - - await message.channel - .send({ - content: t( - { phrase: 'network.welcome', locale }, - { - user: message.author.toString(), - hub: hub.name, - channel: message.channel.toString(), - totalServers: hubConnections.size.toString(), - emoji: emojis.wave_anim, - rules_command: '', - }, - ), - components: [linkButtons], - }) - .catch(() => null); - } - // fetch the referred message (message being replied to) from discord const referredMessage = message.reference ? await message.fetchReference().catch(() => null) @@ -325,7 +274,7 @@ export default abstract class EventManager { const { dbReferrence, referredAuthor } = await getReferredMsgData(referredMessage); - const sendResult = broadcast(message, hub, hubConnections, settings, { + const sendResult = broadcastMessage(message, hub, hubConnections, settings, { attachmentURL, dbReferrence, referredAuthor, @@ -337,6 +286,12 @@ export default abstract class EventManager { // deleting attachments will make the image not show up in the embed (discord removes it from its cdn) if (!attachment) message.delete().catch(() => null); + const userData = await db.userData.findFirst({ + where: { userId: message.author.id, viewedNetworkWelcome: true }, + }); + + if (!userData) await sendWelcomeMsg(message, hubConnections.size.toString()); + // store the message in the db await storeMessageData(message, await Promise.all(sendResult), connection.hubId, dbReferrence); } diff --git a/src/scripts/network/broadcast.ts b/src/scripts/network/broadcastMessage.ts similarity index 99% rename from src/scripts/network/broadcast.ts rename to src/scripts/network/broadcastMessage.ts index 2755cc27e..45e98428d 100644 --- a/src/scripts/network/broadcast.ts +++ b/src/scripts/network/broadcastMessage.ts @@ -37,6 +37,7 @@ export default ( const referredContent = opts.referredMessage ? getReferredContent(opts.referredMessage) : undefined; + const servername = trimAndCensorBannedWebhookWords(message.guild.name); const username = trimAndCensorBannedWebhookWords( settings.has('UseNicknames') diff --git a/src/scripts/network/helpers.ts b/src/scripts/network/helpers.ts index c5d90f2a7..4739d43c3 100644 --- a/src/scripts/network/helpers.ts +++ b/src/scripts/network/helpers.ts @@ -6,10 +6,12 @@ import { ButtonBuilder, ActionRowBuilder, } from 'discord.js'; -import { REGEX, emojis } from '../../utils/Constants.js'; +import { LINKS, REGEX, emojis } from '../../utils/Constants.js'; import { censor } from '../../utils/Profanity.js'; import db from '../../utils/Db.js'; import { broadcastedMessages } from '@prisma/client'; +import hub from '../../commands/slash/Main/hub/index.js'; +import { t } from '../../utils/Locale.js'; /** * Retrieves the content of a referred message, which can be either the message's text content or the description of its first embed. @@ -111,7 +113,7 @@ export const buildNetworkEmbed = ( return { embed, censoredEmbed }; }; -export const trimAndCensorBannedWebhookWords = (content: t) => +export const trimAndCensorBannedWebhookWords = (content: string) => content?.slice(0, 35).replace(REGEX.BANNED_WEBHOOK_WORDS, '[censored]'); export const generateJumpButton = ( @@ -136,3 +138,50 @@ export const generateJumpButton = ( ) : null; }; + +export const sendWelcomeMsg = async (message: Message, totalServers: string) => { + await db.userData.upsert({ + where: { userId: message.author.id }, + create: { + userId: message.author.id, + username: message.author.username, + viewedNetworkWelcome: true, + }, + update: { viewedNetworkWelcome: true }, + }); + + const linkButtons = new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setStyle(ButtonStyle.Link) + .setEmoji(emojis.add_icon) + .setLabel('Invite Me!') + .setURL(LINKS.APP_DIRECTORY), + new ButtonBuilder() + .setStyle(ButtonStyle.Link) + .setEmoji(emojis.code_icon) + .setLabel('Support Server') + .setURL(LINKS.SUPPORT_INVITE), + new ButtonBuilder() + .setStyle(ButtonStyle.Link) + .setEmoji(emojis.docs_icon) + .setLabel('How-To Guide') + .setURL(LINKS.DOCS), + ); + + await message.channel + .send({ + content: t( + { phrase: 'network.welcome', locale: message.author.locale ?? 'en' }, + { + user: message.author.toString(), + hub: hub.name, + channel: message.channel.toString(), + emoji: emojis.wave_anim, + rules_command: '', + totalServers, + }, + ), + components: [linkButtons], + }) + .catch(() => null); +}; \ No newline at end of file From dbfeb4f40ac2107b07cda512a9471308c8f29491 Mon Sep 17 00:00:00 2001 From: dev-737 <73829355+dev-737@users.noreply.github.com> Date: Sat, 11 May 2024 02:14:56 +0000 Subject: [PATCH 7/8] fix something else --- src/scripts/network/broadcastMessage.ts | 6 +++++- src/utils/Locale.ts | 11 +++-------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/scripts/network/broadcastMessage.ts b/src/scripts/network/broadcastMessage.ts index 45e98428d..62562e1a3 100644 --- a/src/scripts/network/broadcastMessage.ts +++ b/src/scripts/network/broadcastMessage.ts @@ -87,13 +87,17 @@ export default ( }).setColor('Random') : undefined; + // compact mode doesn't need new attachment url for tenor and direct image links + // we can just slap them right in there without any problems + const attachmentUrlNeeded = message.attachments.size > 0; + // compact format (no embeds, only content) messageFormat = { username: `@${username} • ${servername}`, avatarURL: message.author.displayAvatarURL(), embeds: replyEmbed ? [replyEmbed] : undefined, components: jumpButton ? [jumpButton] : undefined, - content: `${connection.profFilter ? censoredContent : message.content} ${opts.attachmentURL ? `\n${opts.attachmentURL}` : ''}`, + content: `${connection.profFilter ? censoredContent : message.content} ${attachmentUrlNeeded ? `\n${opts.attachmentURL}` : ''}`, threadId: connection.parentId ? connection.channelId : undefined, allowedMentions: { parse: [] }, }; diff --git a/src/utils/Locale.ts b/src/utils/Locale.ts index 35be46f30..2d0f84588 100644 --- a/src/utils/Locale.ts +++ b/src/utils/Locale.ts @@ -48,15 +48,10 @@ export const loadLocales = (localesDirectory: string) => { const filePath = path.join(localesDirectory, file); const localeKey = path.basename(file, '.yml'); - try { - const content = fs.readFileSync(filePath, 'utf8'); - const parsedContent = yaml.load(content); + const content = fs.readFileSync(filePath, 'utf8'); + const parsedContent = yaml.load(content); - localesMap.set(localeKey, parsedContent); - } - catch (error) { - throw new Error(`Error reading/parsing ${file}: ${error.message}`); - } + localesMap.set(localeKey, parsedContent); }); Logger.info(`${localesMap.size} Locales loaded successfully.`); From aa7dd6cd5f7ff96aee90465b0700cee77e57b828 Mon Sep 17 00:00:00 2001 From: dev-737 <73829355+dev-737@users.noreply.github.com> Date: Sat, 11 May 2024 02:36:01 +0000 Subject: [PATCH 8/8] fix another ting --- locales | 2 +- src/commands/slash/Main/hub/browse.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/locales b/locales index a275eb0ce..72ceb9174 160000 --- a/locales +++ b/locales @@ -1 +1 @@ -Subproject commit a275eb0cec50f65ed85a511e68ff8daef2213123 +Subproject commit 72ceb917485d2cff5342bad953b9055136dda54f diff --git a/src/commands/slash/Main/hub/browse.ts b/src/commands/slash/Main/hub/browse.ts index 3759b8904..0bb3e460d 100644 --- a/src/commands/slash/Main/hub/browse.ts +++ b/src/commands/slash/Main/hub/browse.ts @@ -209,7 +209,7 @@ export default class Browse extends Hub { const phrase = userBlacklisted ? 'errors.userBlacklisted' : 'errors.serverBlacklisted'; await interaction.reply({ - embeds: [simpleEmbed(t({ phrase, locale }, { hub: hubDetails.name, emoji: emojis.no }))], + embeds: [simpleEmbed(t({ phrase, locale }, { emoji: emojis.no }))], ephemeral: true, }); return;