diff --git a/bridge/package.json b/bridge/package.json index 97b3d9e..a0ffe87 100644 --- a/bridge/package.json +++ b/bridge/package.json @@ -21,6 +21,7 @@ "log75": "^2.2.0", "monk": "^7.3.4", "revolt-api": "^0.5.3-rc.8", + "smart-replace": "^1.0.1", "ulid": "^2.3.0" } } diff --git a/bridge/src/discord/events.ts b/bridge/src/discord/events.ts index 2e4f2fe..4d3ee21 100644 --- a/bridge/src/discord/events.ts +++ b/bridge/src/discord/events.ts @@ -8,10 +8,12 @@ import GenericEmbed from "../types/GenericEmbed"; import FormData from 'form-data'; import { discordFetchUser, revoltFetchMessage } from "../util"; import { TextChannel } from "discord.js"; +import { smartReplace } from "smart-replace"; const MAX_BRIDGED_FILE_SIZE = 8_000_000; // 8 MB const RE_MENTION_USER = /<@!*[0-9]+>/g; const RE_MENTION_CHANNEL = /<#[0-9]+>/g; +const RE_EMOJI = /<(a?)?:\w+:\d{18}?>/g; client.on('messageDelete', async message => { try { @@ -198,41 +200,31 @@ client.on('messageCreate', async message => { // Replaces @mentions and #channel mentions async function renderMessageBody(message: string): Promise { - // We don't want users to generate large amounts of database and API queries - let failsafe = 0; - // @mentions - while (failsafe < 10) { - failsafe++; - - const text = message.match(RE_MENTION_USER)?.[0]; - if (!text) break; - - const id = text.replace('<@!', '').replace('<@', '').replace('>', ''); + message = await smartReplace(message, RE_MENTION_USER, async (match: string) => { + const id = match.replace('<@!', '').replace('<@', '').replace('>', ''); const user = await discordFetchUser(id); - - // replaceAll() when - while (message.includes(text)) message = message.replace(text, `@${user?.username || id}`); - } + return `@${user?.username || id}`; + }, { cacheMatchResults: true, maxMatches: 10 }); // #channels - while (failsafe < 10) { - failsafe++; - - const text = message.match(RE_MENTION_CHANNEL)?.[0]; - if (!text) break; - - const id = text.replace('<#', '').replace('>', ''); + message = await smartReplace(message, RE_MENTION_CHANNEL, async (match: string) => { + const id = match.replace('<#', '').replace('>', ''); const channel = client.channels.cache.get(id); const bridgeCfg = channel ? await BRIDGE_CONFIG.findOne({ discord: channel.id }) : undefined; const revoltChannel = bridgeCfg?.revolt ? revoltClient.channels.get(bridgeCfg.revolt) : undefined; - while (message.includes(text)) { - message = message.replace(text, revoltChannel ? `<#${revoltChannel._id}>` : `#${(channel as TextChannel)?.name || id}`); - } - } + return revoltChannel ? `<#${revoltChannel._id}>` : `#${(channel as TextChannel)?.name || id}`; + }, { cacheMatchResults: true, maxMatches: 10 }); + + // :emojis: + message = await smartReplace(message, RE_EMOJI, async (match: string) => { + return match + .replace(/<(a?)?:/, ':\u200b') // We don't want to accidentally send an unrelated emoji, so we add a zero width space here + .replace(/(:\d{18}?>)/, ':'); + }, { cacheMatchResults: true }); return message; } diff --git a/bridge/src/revolt/events.ts b/bridge/src/revolt/events.ts index c450a80..202faaf 100644 --- a/bridge/src/revolt/events.ts +++ b/bridge/src/revolt/events.ts @@ -5,6 +5,7 @@ import { MessageEmbed, MessagePayload, TextChannel, WebhookClient, WebhookMessag import GenericEmbed from "../types/GenericEmbed"; import { SendableEmbed } from "revolt-api"; import { clipText, discordFetchMessage, revoltFetchUser } from "../util"; +import { smartReplace } from "smart-replace"; const RE_MENTION_USER = /<@[0-9A-HJ-KM-NP-TV-Z]{26}>/g; const RE_MENTION_CHANNEL = /<#[0-9A-HJ-KM-NP-TV-Z]{26}>/g; @@ -189,39 +190,26 @@ async function renderMessageBody(message: string): Promise { let failsafe = 0; // @mentions - while (failsafe < 10) { - failsafe++; - - const text = message.match(RE_MENTION_USER)?.[0]; - if (!text) break; - - const id = text.replace('<@', '').replace('>', ''); + message = await smartReplace(message, RE_MENTION_USER, async (match) => { + const id = match.replace('<@', '').replace('>', ''); const user = await revoltFetchUser(id); - - // replaceAll() when - while (message.includes(text)) message = message.replace(text, `@${user?.username || id}`); - } + return `@${user?.username || id}`; + }, { cacheMatchResults: true, maxMatches: 10 }); // #channels - while (failsafe < 10) { - failsafe++; - - const text = message.match(RE_MENTION_CHANNEL)?.[0]; - if (!text) break; - - const id = text.replace('<#', '').replace('>', ''); + message = await smartReplace(message, RE_MENTION_CHANNEL, async (match) => { + const id = match.replace('<#', '').replace('>', ''); const channel = client.channels.get(id); + const bridgeCfg = channel ? await BRIDGE_CONFIG.findOne({ revolt: channel._id }) : undefined; const discordChannel = bridgeCfg?.discord ? discordClient.channels.cache.get(bridgeCfg.discord) : undefined; - while (message.includes(text)) { - message = message.replace(text, discordChannel ? `<#${discordChannel.id}>` : `#${channel?.name || id}`); - } - } + return discordChannel ? `<#${discordChannel.id}>` : `#${channel?.name || id}`; + }, { cacheMatchResults: true, maxMatches: 10 }); - // :emojis: (todo lol) + // TODO: fetch emojis and upload them to Discord or smth return message; } diff --git a/bridge/yarn.lock b/bridge/yarn.lock index 688ea93..794ac0a 100644 --- a/bridge/yarn.lock +++ b/bridge/yarn.lock @@ -479,6 +479,11 @@ saslprep@^1.0.0: dependencies: sparse-bitfield "^3.0.3" +smart-replace@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/smart-replace/-/smart-replace-1.0.1.tgz#4a99193ffc60bd3004370d79e1fd5103fb546ee5" + integrity sha512-2XhBOaa8I+OhjeXk4heFJ6VnFn6qfmT1JA5yFolH1CfGmg5fgV6/7izu8fL5+AFM3bIxFbnPiliFY8lDQ+33WQ== + sparse-bitfield@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz#ff4ae6e68656056ba4b3e792ab3334d38273ca11"