Caption url #56

Merged
LucidKobold merged 3 commits from caption-url into main 2025-07-04 19:05:37 -04:00
7 changed files with 1042 additions and 769 deletions

View File

@@ -1,7 +1,7 @@
{ {
"name": "no-twitter-bot", "name": "no-twitter-bot",
"type": "module", "type": "module",
"version": "1.4.0", "version": "2.0.0",
"private": true, "private": true,
"packageManager": "yarn@4.9.2", "packageManager": "yarn@4.9.2",
"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.", "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.",
@@ -27,34 +27,30 @@
}, },
"dependencies": { "dependencies": {
"@grammyjs/auto-chat-action": "0.1.1", "@grammyjs/auto-chat-action": "0.1.1",
"@grammyjs/commands": "1.0.5", "@grammyjs/commands": "1.0.8",
"@grammyjs/hydrate": "1.4.1", "@grammyjs/hydrate": "1.4.1",
"@grammyjs/i18n": "1.1.2", "@grammyjs/i18n": "1.1.2",
"@grammyjs/parse-mode": "1.11.1", "@grammyjs/parse-mode": "1.11.1",
"@grammyjs/runner": "2.0.3", "@grammyjs/runner": "2.0.3",
"@grammyjs/types": "3.19.0", "@grammyjs/types": "3.20.0",
"@hono/node-server": "1.13.8", "@hono/node-server": "1.14.2",
"callback-data": "1.1.1", "callback-data": "1.1.1",
"grammy": "1.35.0", "grammy": "1.36.1",
"hono": "4.7.2", "hono": "4.7.9",
"iso-639-1": "3.1.5", "iso-639-1": "3.1.5",
"pino": "9.6.0", "pino": "9.6.0",
"pino-pretty": "13.0.0", "pino-pretty": "13.0.0",
"tsx": "4.19.3", "tsx": "4.19.4",
"valibot": "0.42.1" "valibot": "0.42.1"
}, },
"devDependencies": { "devDependencies": {
"@antfu/eslint-config": "4.3.0", "@antfu/eslint-config": "4.12.0",
"@eslint/js": "^9.20.0", "@types/node": "^22.15.21",
"@types/node": "^22.13.4", "eslint": "^9.27.0",
"eslint": "^9.20.1",
"globals": "^15.15.0",
"husky": "^9.1.7", "husky": "^9.1.7",
"lint-staged": "^15.4.3", "lint-staged": "^15.5.1",
"prettier": "^3.5.1", "tsc-watch": "^6.3.1",
"tsc-watch": "^6.2.1", "typescript": "^5.8.3"
"typescript": "^5.7.3",
"typescript-eslint": "^8.24.1"
}, },
"lint-staged": { "lint-staged": {
"*.ts": "eslint" "*.ts": "eslint"

View File

@@ -1,79 +1,137 @@
import { Composer } from "grammy"; import { Composer } from "grammy";
import type { Context } from "#root/bot/context.js"; import type { Context } from "#root/bot/context.js";
import { logHandle } from "#root/bot/helpers/logging.js"; import { logHandle } from "#root/bot/helpers/logging.js";
import metaLinkCheck from "#root/lib/metaLinkCheck.js";
import twitterLinkCheck from "#root/lib/twitterLinkCheck.js";
const composer = new Composer<Context>(); const composer = new Composer<Context>();
const feature = composer.chatType(["group", "supergroup"]); const feature = composer.chatType(["group", "supergroup"]);
feature.on( /**
"message:entities:url", * What triggers this feature and adds to the log when it has been triggered.
logHandle("embed-check"), * The trigger is anytime an embedded url is detected.
async (ctx: Context) => { */
feature.on("message::url", logHandle("embed-check"), async (ctx: Context) => {
if (ctx.chat && ctx.msg) {
// Pulling the group IDs from the env variables.
const GROUP_IDS = process.env.GROUP_IDS
? process.env.GROUP_IDS.split(",")
: undefined;
if (ctx.chat && ctx.msg) { if (ctx.chat && ctx.msg) {
const GROUP_IDS = process.env.GROUP_IDS if (GROUP_IDS !== undefined) {
? process.env.GROUP_IDS.split(",") // Checking if the message is from a whitelisted group.
: undefined; const groupID = ctx.chat.id;
const flag = GROUP_IDS.includes(`${groupID}`);
const username = ctx.msg.from?.username;
if (ctx.chat && ctx.msg) { if (flag) {
if (GROUP_IDS !== undefined) { // Filters every message/caption entity that is a url into a new array.
const groupID = ctx.chat.id; const embeds = ctx.msg.entities
const flag = GROUP_IDS.includes(`${groupID}`); ? ctx.msg.entities.filter(e => e.type === "text_link")
const username = ctx.msg.from?.username; : null;
const captionEmbeds = ctx.msg.caption_entities
? ctx.msg.caption_entities.filter(e => e.type === "text_link")
: null;
if (flag && ctx.msg.entities) { // If the caption embeds array isn't empty filter through them to check if any is a Twitter/X or Meta url.
const embeds = ctx.msg.entities.filter(e => e.type === "text_link"); if (captionEmbeds !== null && captionEmbeds.length) {
const metaLinks = captionEmbeds.filter(({ url }) =>
metaLinkCheck(url)
);
const twitterLinks = captionEmbeds.filter(({ url }) =>
twitterLinkCheck(url)
);
if (embeds.length) { // Handle action and response if both meta and Twitter/X links are detected.
const metaLinks = embeds.filter(({ url }) => if (metaLinks.length && twitterLinks.length) {
url.match( // Deletes the offending message.
/(facebook\.com|meta\.com|instagram\.com|threads\.net|whatsapp\.com)/gi ctx.msg.delete();
) // Replies to the user informing them of the action.
return await ctx.reply(
`@${username} Twitter and X links along with reformatting services for Twitter posts are not allowed here\\. Also Facebook and meta links along with with links to meta\\-owned services 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\\.\n\nIf this was forwarded from a channel consider forwarding without the caption so the media isn't deleted.`,
{ parse_mode: "MarkdownV2" }
); );
const twitterLinks = embeds.filter(({ url }) => }
url.match(/(x\.com|twitter\.com)/gi)
// Handle action and response if only meta links are detected.
if (metaLinks.length) {
// Deletes the offending message.
ctx.msg.delete();
// Replies to the user informing them of the action.
return await ctx.reply(
`@${username} Facebook and meta links along with with links to meta\\-owned services 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\\\n\nIf this was forwarded from a channel consider forwarding without the caption so the media isn't deleted..`,
{ parse_mode: "MarkdownV2" }
); );
}
if (metaLinks.length && twitterLinks.length) { // Handle action and response if only Twitter/X links are detected.
ctx.msg.delete(); if (twitterLinks.length) {
return await ctx.reply( // Deletes the offending message.
`@${username} Twitter and X links along with reformatting services for Twitter posts are not allowed here\\. Also Facebook and meta links along with with links to meta\\-owned services 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\\.`, ctx.msg.delete();
{ parse_mode: "MarkdownV2" } // Replies to the user informing them of the action.
); return 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\\.\n\nIf this was forwarded from a channel consider forwarding without the caption so the media isn't deleted.`,
{ parse_mode: "MarkdownV2" }
);
}
}
// If the embeds array isn't empty filter through them to check if any is a Twitter/X or Meta url.
if (embeds !== null && embeds.length) {
const metaLinks = embeds.filter(({ url }) => metaLinkCheck(url));
const twitterLinks = embeds.filter(({ url }) =>
twitterLinkCheck(url)
);
if (metaLinks.length) { // Handle action and response if both meta and Twitter/X links are detected.
ctx.msg.delete(); if (metaLinks.length && twitterLinks.length) {
return await ctx.reply( // Deletes the offending message.
`@${username} Facebook and meta links along with with links to meta\\-owned services 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\\.`, ctx.msg.delete();
{ parse_mode: "MarkdownV2" } // Replies to the user informing them of the action.
); return await ctx.reply(
} `@${username} Twitter and X links along with reformatting services for Twitter posts are not allowed here\\. Also Facebook and meta links along with with links to meta\\-owned services 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\\.`,
{ parse_mode: "MarkdownV2" }
);
}
if (twitterLinks.length) { // Handle action and response if only meta links are detected.
ctx.msg.delete(); if (metaLinks.length) {
return await ctx.reply( // Deletes the offending message.
`@${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\\.`, ctx.msg.delete();
{ parse_mode: "MarkdownV2" } // Replies to the user informing them of the action.
); return await ctx.reply(
} `@${username} Facebook and meta links along with with links to meta\\-owned services 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\\.`,
{ parse_mode: "MarkdownV2" }
);
}
// Handle action and response if only Twitter/X links are detected.
if (twitterLinks.length) {
// Deletes the offending message.
ctx.msg.delete();
// Replies to the user informing them of the action.
return 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\\.`,
{ parse_mode: "MarkdownV2" }
);
} }
} }
} }
}
if (!GROUP_IDS) { if (!GROUP_IDS) {
console.info("Group IDS:", process.env.GROUP_IDS); console.info("Group IDS:", process.env.GROUP_IDS);
await ctx.reply( await ctx.reply(
`There was a problem retrieving the whitelist\\. Check the env variables and try again\\.`, `There was a problem retrieving the whitelist\\. Check the env variables and try again\\.`,
{ {
parse_mode: "MarkdownV2", parse_mode: "MarkdownV2",
reply_parameters: { message_id: ctx.msg.message_id } reply_parameters: { message_id: ctx.msg.message_id }
} }
); );
}
} }
} }
} }
); });
export { composer as embedCheck }; export { composer as embedCheck };

View File

@@ -1,37 +1,47 @@
import { Composer } from "grammy"; import { Composer } from "grammy";
import type { Context } from "#root/bot/context.js"; import type { Context } from "#root/bot/context.js";
import { logHandle } from "#root/bot/helpers/logging.js"; import { logHandle } from "#root/bot/helpers/logging.js";
import { metaRegex } from "#root/lib/metaLinkCheck.js";
const composer = new Composer<Context>(); const composer = new Composer<Context>();
const feature = composer.chatType(["group", "supergroup"]); const feature = composer.chatType(["group", "supergroup"]);
/**
* What triggers this feature and adds to the log when it has been triggered.
* The trigger uses the global Twitter regex to detect Twitter and X links within messages.
*/
feature.hears( feature.hears(
/(facebook\.com|meta\.com|instagram\.com|threads\.net|whatsapp\.com)/gi, metaRegex,
logHandle("blacklist-detection-meta"), logHandle("blacklist-detection-meta"),
async (ctx: Context) => { async (ctx: Context) => {
// Pulling the group IDs from the env variables.
const GROUP_IDS = process.env.GROUP_IDS const GROUP_IDS = process.env.GROUP_IDS
? process.env.GROUP_IDS.split(",") ? process.env.GROUP_IDS.split(",")
: undefined; : undefined;
if (ctx.chat && ctx.msg) { if (ctx.chat && ctx.msg) {
if (GROUP_IDS !== undefined) { if (GROUP_IDS !== undefined) {
// Checking if the message is from a whitelisted group.
const groupID = ctx.chat.id; const groupID = ctx.chat.id;
const flag = GROUP_IDS.includes(`${groupID}`); const flag = GROUP_IDS.includes(`${groupID}`);
const username = ctx.msg.from?.username; const username = ctx.msg.from?.username;
if (flag) { if (flag) {
// Deletes the offending message.
ctx.msg.delete(); ctx.msg.delete();
await ctx.reply( // Replies to the user informing them of the action.
return await ctx.reply(
`@${username} Facebook and meta links along with with links to meta\\-owned services 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\\.`, `@${username} Facebook and meta links along with with links to meta\\-owned services 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\\.`,
{ parse_mode: "MarkdownV2" } { parse_mode: "MarkdownV2" }
); );
} }
} }
// If the env variables are misconfigured an error is sent to the group.
if (!GROUP_IDS) { if (!GROUP_IDS) {
console.info("Group IDS:", process.env.GROUP_IDS); console.info("Group IDS:", process.env.GROUP_IDS);
await ctx.reply( return await ctx.reply(
`There was a problem retrieving the whitelist\\. Check the env variables and try again\\.`, `There was a problem retrieving the whitelist\\. Check the env variables and try again\\.`,
{ {
parse_mode: "MarkdownV2", parse_mode: "MarkdownV2",

View File

@@ -1,34 +1,44 @@
import { Composer } from "grammy"; import { Composer } from "grammy";
import type { Context } from "#root/bot/context.js"; import type { Context } from "#root/bot/context.js";
import { logHandle } from "#root/bot/helpers/logging.js"; import { logHandle } from "#root/bot/helpers/logging.js";
import { twitterRegex } from "#root/lib/twitterLinkCheck.js";
const composer = new Composer<Context>(); const composer = new Composer<Context>();
const feature = composer.chatType(["group", "supergroup"]); const feature = composer.chatType(["group", "supergroup"]);
/**
* What triggers this feature and adds to the log when it has been triggered.
* The trigger uses the global Twitter regex to detect Twitter and X links within messages.
*/
feature.hears( feature.hears(
/(x\.com|twitter\.com)/gi, twitterRegex,
logHandle("blacklist-detection-twitter"), logHandle("blacklist-detection-twitter"),
async (ctx: Context) => { async (ctx: Context) => {
// Pulling the group IDs from the env variables.
const GROUP_IDS = process.env.GROUP_IDS const GROUP_IDS = process.env.GROUP_IDS
? process.env.GROUP_IDS.split(",") ? process.env.GROUP_IDS.split(",")
: undefined; : undefined;
if (ctx.chat && ctx.msg) { if (ctx.chat && ctx.msg) {
if (GROUP_IDS !== undefined) { if (GROUP_IDS !== undefined) {
// Checking if the message is from a whitelisted group.
const groupID = ctx.chat.id; const groupID = ctx.chat.id;
const flag = GROUP_IDS.includes(`${groupID}`); const flag = GROUP_IDS.includes(`${groupID}`);
const username = ctx.msg.from?.username; const username = ctx.msg.from?.username;
if (flag) { if (flag) {
// Deletes the offending message.
ctx.msg.delete(); ctx.msg.delete();
await ctx.reply( // Replies to the user informing them of the action.
return 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\\.`, `@${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\\.`,
{ parse_mode: "MarkdownV2" } { parse_mode: "MarkdownV2" }
); );
} }
} }
// If the env variables are misconfigured an error is sent to the group.
if (!GROUP_IDS) { if (!GROUP_IDS) {
console.info( console.info(
"Group IDS:", "Group IDS:",
@@ -36,7 +46,7 @@ feature.hears(
GROUP_IDS, GROUP_IDS,
GROUP_IDS !== undefined GROUP_IDS !== undefined
); );
await ctx.reply( return await ctx.reply(
`There was a problem retrieving the whitelist\\. Check the env variables and try again\\.`, `There was a problem retrieving the whitelist\\. Check the env variables and try again\\.`,
{ {
parse_mode: "MarkdownV2", parse_mode: "MarkdownV2",

22
src/lib/metaLinkCheck.ts Normal file
View File

@@ -0,0 +1,22 @@
// Global regex used to detect all meta services.
const metaRegex =
/(facebook\.com|meta\.com|instagram\.com|threads\.net|whatsapp\.com)/gi;
/**
* This function will check if a url matches Meta services links using regex.
*
* @param linkUrl representing a suspected blacklisted url
* @returns flag
*/
const metaLinkCheck = (linkUrl: string): boolean => {
let flag = false;
if (linkUrl.match(metaRegex)) {
flag = true;
}
return flag;
};
export { metaRegex };
export default metaLinkCheck;

View File

@@ -0,0 +1,21 @@
// Global regex used to detect Twitter and X links.
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 blacklisted url
* @return flag
*/
const twitterLinkCheck = (linkUrl: string): boolean => {
let flag = false;
if (linkUrl.match(twitterRegex)) {
flag = true;
}
return flag;
};
export { twitterRegex };
export default twitterLinkCheck;

1542
yarn.lock

File diff suppressed because it is too large Load Diff