From 17238b75b194e0fb15a57b47e4c66e9019b36b50 Mon Sep 17 00:00:00 2001 From: Enrico Sacchetti Date: Thu, 17 Jul 2025 12:52:20 -0400 Subject: [PATCH 1/4] feat(on_message): audit messages - Provide advise for certain message content - When messages contain links to X, advise to use xcancel for better previews --- src/events/on_message/_advise.ts | 59 +++++++++++++++++++++++++++++ src/events/on_message/on_message.ts | 2 + 2 files changed, 61 insertions(+) create mode 100644 src/events/on_message/_advise.ts diff --git a/src/events/on_message/_advise.ts b/src/events/on_message/_advise.ts new file mode 100644 index 0000000..5f49228 --- /dev/null +++ b/src/events/on_message/_advise.ts @@ -0,0 +1,59 @@ +import { ChannelType, Message } from 'discord.js'; +import { has_link } from './_common.js'; +import urlRegex from 'url-regex'; + +const replacementMap = [ + { test: 'https://x.com', replace: 'https://xcancel.com' }, +]; + +export default async function mutate_content(message: Message) { + if ( + message.channel.type != ChannelType.GuildText || + (!has_link(message) && !message.content.includes('x.com')) + ) + return; + + // Get links + const caughtLinks: string[] | undefined = message.content + .match(urlRegex()) + ?.filter( + (link) => + replacementMap.findIndex((item) => + link.startsWith(item.test), + ) !== -1, + ); + if (!caughtLinks) return; + const hasXLinks: boolean = caughtLinks.some((item) => + item.startsWith('https://x.com'), + ); + if (!hasXLinks) return; + + const updatedPhrase: string = + caughtLinks.length > 1 + ? 'Here are the updated links:' + : 'Here is the updated link:'; + const updatedLinks = caughtLinks + .map((link) => { + const replaceIdx = replacementMap.findIndex((item) => + link.startsWith(item.test), + ); + return link.replace( + replacementMap[replaceIdx].test, + replacementMap[replaceIdx].replace, + ); + }) + .join('\n'); + + const updatedMessage: string = (function () { + let newContent = message.content; + replacementMap.forEach( + (item) => + (newContent = newContent.replace(item.test, item.replace)), + ); + return newContent; + })(); + + await message.author.send( + `Re: ${message.url}\n\nI see you've provided a link to \`x.com\`. Please consider posting a new message having \`x.com\` replaced with \`xcancel.com\`, that way server members may view the message and thread without requiring an account.\n\n${updatedPhrase}\`\`\`${updatedLinks}\`\`\`\n\nHere is your entire message with adjusted links:\n\`\`\`${updatedMessage}\`\`\``, + ); +} diff --git a/src/events/on_message/on_message.ts b/src/events/on_message/on_message.ts index f15ae22..f03cedf 100644 --- a/src/events/on_message/on_message.ts +++ b/src/events/on_message/on_message.ts @@ -2,6 +2,7 @@ import check_links from './_check_links.js'; import spam_filter from './_spam_filter.js'; import autothread from './_autothread.js'; import slow_mode from './_slow_mode.js'; +import advise from './_advise.js'; import { event } from 'jellycommands'; import { STOP } from './_common.js'; @@ -16,6 +17,7 @@ export default event({ check_links, autothread, slow_mode, + advise, ]) { try { await handler(message); From d7d44eaec6c16f9f280c52fea38a1629959a1768 Mon Sep 17 00:00:00 2001 From: Enrico Sacchetti Date: Thu, 17 Jul 2025 15:23:34 -0400 Subject: [PATCH 2/4] fix(on_message): adjust advisor - Attempt a DM first, if that fails then do an inline reply with the converted x.com link --- src/events/on_message/_advise.ts | 45 ++++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/src/events/on_message/_advise.ts b/src/events/on_message/_advise.ts index 5f49228..4aa01e4 100644 --- a/src/events/on_message/_advise.ts +++ b/src/events/on_message/_advise.ts @@ -23,6 +23,7 @@ export default async function mutate_content(message: Message) { ) !== -1, ); if (!caughtLinks) return; + const hasXLinks: boolean = caughtLinks.some((item) => item.startsWith('https://x.com'), ); @@ -32,17 +33,18 @@ export default async function mutate_content(message: Message) { caughtLinks.length > 1 ? 'Here are the updated links:' : 'Here is the updated link:'; - const updatedLinks = caughtLinks - .map((link) => { - const replaceIdx = replacementMap.findIndex((item) => - link.startsWith(item.test), - ); - return link.replace( - replacementMap[replaceIdx].test, - replacementMap[replaceIdx].replace, - ); - }) - .join('\n'); + + const updatedLinks = caughtLinks.map((link) => { + const replaceIdx = replacementMap.findIndex((item) => + link.startsWith(item.test), + ); + return link.replace( + replacementMap[replaceIdx].test, + replacementMap[replaceIdx].replace, + ); + }); + + const updatedLinkList = updatedLinks.map((link) => `- ${link}\n`).join(''); const updatedMessage: string = (function () { let newContent = message.content; @@ -53,7 +55,22 @@ export default async function mutate_content(message: Message) { return newContent; })(); - await message.author.send( - `Re: ${message.url}\n\nI see you've provided a link to \`x.com\`. Please consider posting a new message having \`x.com\` replaced with \`xcancel.com\`, that way server members may view the message and thread without requiring an account.\n\n${updatedPhrase}\`\`\`${updatedLinks}\`\`\`\n\nHere is your entire message with adjusted links:\n\`\`\`${updatedMessage}\`\`\``, - ); + try { + await message.author.send( + `Re: ${message.url}\n\nI see you've provided a link to \`x.com\`. Please consider posting a new message having \`x.com\` replaced with \`xcancel.com\`, that way server members may view the message and thread without requiring an account.\n\n${updatedPhrase}\`\`\`${updatedLinkList}\`\`\`\n\nHere is your entire message with adjusted links:\n\`\`\`${updatedLinkList}\`\`\``, + ); + + return; // mission complete + } catch (e) { + // assume user disabled DMs upon error + } + + // Plan B: inline reply + try { + await message.reply( + `I converted your \`x.com\` links to use \`xcancel.com\` so that server members won't require an account to view content and threads.\n\n${updatedLinkList}`, + ); + } catch (e) { + // don't handle failures + } } From a38510289c9c43a4b3d07ad18ae8332c365fee6e Mon Sep 17 00:00:00 2001 From: Enrico Sacchetti Date: Thu, 17 Jul 2025 15:28:04 -0400 Subject: [PATCH 3/4] fix: appease the linter --- src/events/on_message/_advise.ts | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/src/events/on_message/_advise.ts b/src/events/on_message/_advise.ts index 4aa01e4..46c5579 100644 --- a/src/events/on_message/_advise.ts +++ b/src/events/on_message/_advise.ts @@ -1,4 +1,4 @@ -import { ChannelType, Message } from 'discord.js'; +import { ChannelType, type Message } from 'discord.js'; import { has_link } from './_common.js'; import urlRegex from 'url-regex'; @@ -46,22 +46,13 @@ export default async function mutate_content(message: Message) { const updatedLinkList = updatedLinks.map((link) => `- ${link}\n`).join(''); - const updatedMessage: string = (function () { - let newContent = message.content; - replacementMap.forEach( - (item) => - (newContent = newContent.replace(item.test, item.replace)), - ); - return newContent; - })(); - try { await message.author.send( `Re: ${message.url}\n\nI see you've provided a link to \`x.com\`. Please consider posting a new message having \`x.com\` replaced with \`xcancel.com\`, that way server members may view the message and thread without requiring an account.\n\n${updatedPhrase}\`\`\`${updatedLinkList}\`\`\`\n\nHere is your entire message with adjusted links:\n\`\`\`${updatedLinkList}\`\`\``, ); return; // mission complete - } catch (e) { + } catch (_) { // assume user disabled DMs upon error } @@ -70,7 +61,7 @@ export default async function mutate_content(message: Message) { await message.reply( `I converted your \`x.com\` links to use \`xcancel.com\` so that server members won't require an account to view content and threads.\n\n${updatedLinkList}`, ); - } catch (e) { + } catch (_) { // don't handle failures } } From f6ba448a71c7adf1dc65a918fb3534e144f416ee Mon Sep 17 00:00:00 2001 From: GHOST Date: Sat, 19 Jul 2025 17:11:43 +0100 Subject: [PATCH 4/4] chore: update --- src/events/on_message/_advise.ts | 67 +++++++++++++------------------- 1 file changed, 28 insertions(+), 39 deletions(-) diff --git a/src/events/on_message/_advise.ts b/src/events/on_message/_advise.ts index 46c5579..5cd7327 100644 --- a/src/events/on_message/_advise.ts +++ b/src/events/on_message/_advise.ts @@ -1,67 +1,56 @@ -import { ChannelType, type Message } from 'discord.js'; -import { has_link } from './_common.js'; import urlRegex from 'url-regex'; - -const replacementMap = [ - { test: 'https://x.com', replace: 'https://xcancel.com' }, -]; +import { + type Message, + ChannelType, + hideLinkEmbed, + codeBlock, +} from 'discord.js'; export default async function mutate_content(message: Message) { - if ( - message.channel.type != ChannelType.GuildText || - (!has_link(message) && !message.content.includes('x.com')) - ) - return; + if (message.channel.type != ChannelType.GuildText) return; - // Get links - const caughtLinks: string[] | undefined = message.content + const links = message.content .match(urlRegex()) - ?.filter( - (link) => - replacementMap.findIndex((item) => - link.startsWith(item.test), - ) !== -1, + ?.filter((link) => link.startsWith('https://x.com')) + .map((link) => + link.replace(/^https:\/\/x\.com/, 'https://xcancel.com'), ); - if (!caughtLinks) return; - const hasXLinks: boolean = caughtLinks.some((item) => - item.startsWith('https://x.com'), - ); - if (!hasXLinks) return; + if (!links || links.length === 0) { + return; + } - const updatedPhrase: string = - caughtLinks.length > 1 + const updated_phrase: string = + links.length > 1 ? 'Here are the updated links:' : 'Here is the updated link:'; - const updatedLinks = caughtLinks.map((link) => { - const replaceIdx = replacementMap.findIndex((item) => - link.startsWith(item.test), - ); - return link.replace( - replacementMap[replaceIdx].test, - replacementMap[replaceIdx].replace, - ); - }); + const updated_link_list = links + .map((link) => `- ${hideLinkEmbed(link)}`) + .join('\n'); - const updatedLinkList = updatedLinks.map((link) => `- ${link}\n`).join(''); + const updated_content = message.content.replace(urlRegex(), (match) => { + return match.startsWith('https://x.com') + ? match.replace(/^https:\/\/x\.com/, 'https://xcancel.com') + : match; + }); try { await message.author.send( - `Re: ${message.url}\n\nI see you've provided a link to \`x.com\`. Please consider posting a new message having \`x.com\` replaced with \`xcancel.com\`, that way server members may view the message and thread without requiring an account.\n\n${updatedPhrase}\`\`\`${updatedLinkList}\`\`\`\n\nHere is your entire message with adjusted links:\n\`\`\`${updatedLinkList}\`\`\``, + `Re: ${message.url}\n\nI see you've provided a link to \`x.com\`. Please consider posting a new message having \`x.com\` replaced with \`xcancel.com\`, that way server members may view the message and thread without requiring an account.\n\n${updated_phrase}\n${updated_link_list}\n\nHere is your entire message with adjusted links:\n${codeBlock(updated_content)}`, ); return; // mission complete - } catch (_) { + } catch { // assume user disabled DMs upon error } // Plan B: inline reply try { await message.reply( - `I converted your \`x.com\` links to use \`xcancel.com\` so that server members won't require an account to view content and threads.\n\n${updatedLinkList}`, + `I converted your \`x.com\` links to use \`xcancel.com\` so that server members won't require an account to view content and threads:\n${updated_link_list}`, ); - } catch (_) { + } catch { // don't handle failures } }