From aaac8c3aa7e606b148fad743a76725347d1f7a52 Mon Sep 17 00:00:00 2001 From: Lucid Date: Tue, 14 Apr 2026 18:01:44 -0400 Subject: [PATCH 1/2] Added comments. --- package.json | 2 +- src/bot/features/botInfoCommand.ts | 2 +- src/bot/features/botStatsSiteCommand.ts | 2 +- src/bot/features/helpCommand.ts | 3 +++ src/bot/features/metaBanlist.ts | 2 ++ src/bot/features/registerGroupCommand.ts | 1 + src/bot/features/tiktokBanlist.ts | 1 + src/bot/features/welcome.ts | 1 + src/lib/getCommandsList.ts | 7 +++++++ src/lib/metaLinkCheck.ts | 2 +- src/lib/tiktokLinkCheck.ts | 2 +- src/lib/twitterLinkCheck.ts | 2 +- 12 files changed, 21 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 0558cae..3cc3e0f 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "type": "module", "version": "3.2.0", "private": true, - "packageManager": "yarn@4.12.0", + "packageManager": "yarn@4.13.0", "description": "This grammY powered Telegram bot is designed to delete Twitter/X links and reformat services from whitelisted groups. This one is the main bot for the LCM Telegram groups/communities.", "imports": { "#root/*": "./build/src/*" diff --git a/src/bot/features/botInfoCommand.ts b/src/bot/features/botInfoCommand.ts index da23a32..1c6ebb8 100644 --- a/src/bot/features/botInfoCommand.ts +++ b/src/bot/features/botInfoCommand.ts @@ -32,7 +32,7 @@ feature.hears( } await urql - .mutation(increment, { command: true, mutationKey }) + .mutation(increment, { command: true, mutationKey }) // Increments the trigger count .toPromise() .then(async () => { if (ctx.msg) { diff --git a/src/bot/features/botStatsSiteCommand.ts b/src/bot/features/botStatsSiteCommand.ts index cb090c2..f71b47a 100644 --- a/src/bot/features/botStatsSiteCommand.ts +++ b/src/bot/features/botStatsSiteCommand.ts @@ -32,7 +32,7 @@ feature.hears( } await urql - .mutation(increment, { command: true, mutationKey }) + .mutation(increment, { command: true, mutationKey }) // Increments the trigger count .toPromise() .then(async () => { if (ctx.msg) { diff --git a/src/bot/features/helpCommand.ts b/src/bot/features/helpCommand.ts index 9381d82..6ee6a05 100644 --- a/src/bot/features/helpCommand.ts +++ b/src/bot/features/helpCommand.ts @@ -18,7 +18,9 @@ feature.hears(/^\/help/, logHandle("help"), async (ctx: Context) => { const mutationKey = process.env.GRAPHQL_MUTATION_KEY || ""; const statsSite = process.env.STATS_SITE || ""; + // Increments the trigger count await urql.mutation(increment, { trigger: true, mutationKey }); + // Retrieve the command list const commandsListArr = await getCommandsList(`${statsSite}api/bot`, apiKey); // const GROUP_IDS = process.env.GROUP_IDS @@ -67,6 +69,7 @@ feature.hears(/^\/help/, logHandle("help"), async (ctx: Context) => { } } + // Create a readable command list string. const commandsListStr = commandsListArr.reduce((prev, curr) => { const { command, description, groups } = curr; diff --git a/src/bot/features/metaBanlist.ts b/src/bot/features/metaBanlist.ts index 5f597cb..24a1760 100644 --- a/src/bot/features/metaBanlist.ts +++ b/src/bot/features/metaBanlist.ts @@ -21,8 +21,10 @@ feature.hears( async (ctx: Context) => { const mutationKey = process.env.GRAPHQL_MUTATION_KEY || ""; + // Increments the trigger count await urql.mutation(increment, { trigger: true, mutationKey }); + // Checks there is a chat and msg property in the context. if (ctx.chat && ctx.msg) { const username = ctx.msg.from?.username; diff --git a/src/bot/features/registerGroupCommand.ts b/src/bot/features/registerGroupCommand.ts index 9bd9ee8..4a25be8 100644 --- a/src/bot/features/registerGroupCommand.ts +++ b/src/bot/features/registerGroupCommand.ts @@ -19,6 +19,7 @@ feature.hears( async (ctx: Context) => { const mutationKey = process.env.GRAPHQL_MUTATION_KEY || ""; + // Increments the trigger count await urql.mutation(increment, { trigger: true, mutationKey }); // Checks if the context includes a message property. diff --git a/src/bot/features/tiktokBanlist.ts b/src/bot/features/tiktokBanlist.ts index 478a811..7449c66 100644 --- a/src/bot/features/tiktokBanlist.ts +++ b/src/bot/features/tiktokBanlist.ts @@ -21,6 +21,7 @@ feature.hears( async (ctx: Context) => { const mutationKey = process.env.GRAPHQL_MUTATION_KEY || ""; + // Increments the trigger count await urql.mutation(increment, { trigger: true, mutationKey }); if (ctx.chat && ctx.msg) { diff --git a/src/bot/features/welcome.ts b/src/bot/features/welcome.ts index 5dfd401..7e5ade2 100644 --- a/src/bot/features/welcome.ts +++ b/src/bot/features/welcome.ts @@ -15,6 +15,7 @@ const feature = composer.chatType("private"); feature.command("start", logHandle("command-start"), async ctx => { const mutationKey = process.env.GRAPHQL_MUTATION_KEY || ""; + // Increments the trigger count await urql.mutation(increment, { trigger: true, mutationKey }); await urql diff --git a/src/lib/getCommandsList.ts b/src/lib/getCommandsList.ts index c5a3b37..ede5310 100644 --- a/src/lib/getCommandsList.ts +++ b/src/lib/getCommandsList.ts @@ -6,6 +6,13 @@ export interface Commands { groups: boolean; private: boolean; } +/** + * Fetched a list of commands with a provided url and API key. + * @param url The url to query. + * @interface Commands + * @param apiKey The api for the url. + * @returns Array of Commands object. + */ const fetchCommandsList = async ( url: string, diff --git a/src/lib/metaLinkCheck.ts b/src/lib/metaLinkCheck.ts index 9e24bd0..f7e463c 100644 --- a/src/lib/metaLinkCheck.ts +++ b/src/lib/metaLinkCheck.ts @@ -6,7 +6,7 @@ const metaRegex = * This function will check if a url matches Meta services links using regex. * * @param linkUrl representing a suspected url on the banlist - * @returns flag + * @returns @type boolean */ const metaLinkCheck = (linkUrl: string): boolean => { let flag = false; diff --git a/src/lib/tiktokLinkCheck.ts b/src/lib/tiktokLinkCheck.ts index 6e72f56..69a0135 100644 --- a/src/lib/tiktokLinkCheck.ts +++ b/src/lib/tiktokLinkCheck.ts @@ -5,7 +5,7 @@ const tiktokRegex = /(tiktok\.com)/gi; * This function will check if a url matches TikTok services links using regex. * * @param linkUrl representing a suspected url on the banlist - * @return flag + * @returns @type boolean */ const tiktokLinkCheck = (linkUrl: string): boolean => { let flag = false; diff --git a/src/lib/twitterLinkCheck.ts b/src/lib/twitterLinkCheck.ts index 4dabde3..43d7b58 100644 --- a/src/lib/twitterLinkCheck.ts +++ b/src/lib/twitterLinkCheck.ts @@ -5,7 +5,7 @@ const twitterRegex = /(x\.com|twitter\.com)/gi; * This function will check if a url matches Twitter/X services links using regex. * * @param linkUrl representing a suspected url on the banlist - * @return flag + * @returns @type boolean */ const twitterLinkCheck = (linkUrl: string): boolean => { let flag = false; From a3cc0f0711ddc4447358cfaacba8da5e802e1ac4 Mon Sep 17 00:00:00 2001 From: Lucid Date: Tue, 14 Apr 2026 18:08:21 -0400 Subject: [PATCH 2/2] Added function to fetch links to verify the --- src/bot/features/embedCheck.ts | 38 ++++++++++++----------- src/bot/features/twitterBanlist.ts | 9 +++++- src/lib/fetchLink.ts | 49 ++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 18 deletions(-) create mode 100644 src/lib/fetchLink.ts diff --git a/src/bot/features/embedCheck.ts b/src/bot/features/embedCheck.ts index c8fa073..5584287 100644 --- a/src/bot/features/embedCheck.ts +++ b/src/bot/features/embedCheck.ts @@ -2,12 +2,13 @@ import { Composer } from "grammy"; import type { Context } from "#root/bot/context.js"; import { logHandle } from "#root/bot/helpers/logging.js"; import metaLinkCheck from "#root/lib/metaLinkCheck.js"; -import twitterLinkCheck from "#root/lib/twitterLinkCheck.js"; +import twitterLinkCheck, { twitterRegex } from "#root/lib/twitterLinkCheck.js"; import { urql } from "#root/main.js"; import increment from "#root/lib/graphql/mutations/incrementMutation.js"; import addGroup from "#root/lib/graphql/mutations/addGroupMutation.js"; import incrementGroup from "#root/lib/graphql/mutations/incrementGroupMutation.js"; import tiktokLinkCheck from "#root/lib/tiktokLinkCheck.js"; +import fetchLink from "#root/lib/fetchLink.js"; const composer = new Composer(); @@ -20,8 +21,10 @@ const feature = composer.chatType(["group", "supergroup"]); feature.on("message::url", logHandle("embed-check"), async (ctx: Context) => { const mutationKey = process.env.GRAPHQL_MUTATION_KEY || ""; + // Increments the trigger count await urql.mutation(increment, { trigger: true, mutationKey }); + // Checks there is a chat and msg property in the context. if (ctx.chat && ctx.msg) { const groupName = ctx.chat?.title || ""; const groupID = ctx.chat?.id.toString() || ""; @@ -41,24 +44,25 @@ feature.on("message::url", logHandle("embed-check"), async (ctx: Context) => { // If the all embeds array isn't empty filter through them to check for every type of disallowed link. if (allEmbeds.length) { - const metaLinks = allEmbeds.filter(({ url }) => metaLinkCheck(url)); - const twitterLinks = allEmbeds.filter(({ url }) => twitterLinkCheck(url)); - const tiktokLinks = allEmbeds.filter(({ url }) => tiktokLinkCheck(url)); + detectedLinks += + allEmbeds.filter(({ url }) => metaLinkCheck(url)).length || 0; - // Handles Meta & Facebook Links - if (metaLinks.length) { - detectedLinks += metaLinks.length; - } + detectedLinks += ( + await Promise.all( + allEmbeds.map(async ({ url }) => { + // Checks for a false positive. + if (twitterLinkCheck(url)) { + return await fetchLink(url, twitterRegex); + } + return false; + }) + ) + ).reduce((acc, curr) => { + return (acc += curr ? 1 : 0); + }, 0); - // Handles Twitter/X links. - if (twitterLinks.length) { - detectedLinks += twitterLinks.length; - } - - // Handles TikTok links. - if (tiktokLinks.length) { - detectedLinks += tiktokLinks.length; - } + detectedLinks += + allEmbeds.filter(({ url }) => tiktokLinkCheck(url)).length || 0; } if (detectedLinks) { diff --git a/src/bot/features/twitterBanlist.ts b/src/bot/features/twitterBanlist.ts index b7cf5f7..4f6a6c0 100644 --- a/src/bot/features/twitterBanlist.ts +++ b/src/bot/features/twitterBanlist.ts @@ -6,6 +6,7 @@ import { urql } from "#root/main.js"; import increment from "#root/lib/graphql/mutations/incrementMutation.js"; import addGroup from "#root/lib/graphql/mutations/addGroupMutation.js"; import incrementGroup from "#root/lib/graphql/mutations/incrementGroupMutation.js"; +import fetchLink from "#root/lib/fetchLink.js"; const composer = new Composer(); @@ -21,8 +22,10 @@ feature.hears( async (ctx: Context) => { const mutationKey = process.env.GRAPHQL_MUTATION_KEY || ""; + // Increments the trigger count await urql.mutation(increment, { trigger: true, mutationKey }); + // Checks there is a chat and msg property in the context. if (ctx.chat && ctx.msg) { const username = ctx.msg.from?.username; @@ -30,7 +33,11 @@ feature.hears( .mutation(increment, { link: 1, mutationKey }) .toPromise() .then(async () => { - if (ctx.msg && ctx.chat) { + if (ctx.msg && ctx.chat && ctx.msg.text) { + // Checks for a false positive + const truePositive = await fetchLink(ctx.msg.text, twitterRegex); + if (!truePositive) return; + // Replies to the user informing them of the action. await ctx.reply( `@${username} Twitter and X links along with reformatting services for Twitter posts are not allowed here\\. Please consider sharing the media directly or from other social media sources or websites\\. No administration action was taken against you other than the message being deleted\\.`, diff --git a/src/lib/fetchLink.ts b/src/lib/fetchLink.ts new file mode 100644 index 0000000..db5de5d --- /dev/null +++ b/src/lib/fetchLink.ts @@ -0,0 +1,49 @@ +/** + * This function will fetch a url to follow all redirects and compare the base url + * with the RegEx to validate false and true positives. + * + * @param message The message that originally triggered the provided RegEx + * @param banRegEx RegEx to check against + * @returns @type boolean + */ + +const linkChecker = async (message: string, banRegEx: RegExp) => { + const detectedLinks = message.split(" ").map(subStr => { + if (banRegEx.test(subStr)) { + return subStr; + } + }); + + if (!detectedLinks || detectedLinks.length === 0) { + return false; + } + + const resolvedURLs = detectedLinks.map(async string => { + if (string === undefined) { + return false; + } + + const url = + string.startsWith("http://") || string.startsWith("https://") + ? string + : `https://${string}`; + + const res = await fetch(url, { + headers: { + "user-agent": + "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0" + } + }); + + const resolvedURL = new URL(res.url); + const baseURL = resolvedURL.hostname.split(".").slice(-2).join("."); + + if (/^x\.com/i.test(baseURL)) { + return true; + } + }); + + return (await Promise.all(resolvedURLs)).includes(true); +}; + +export default linkChecker;