Compare commits

...

21 Commits

Author SHA1 Message Date
f72b04d49b Updated the increment mutation to take a number instead of a boolean. For instances when more than 1 group is detected. Like when parsing embeds and captions.
All checks were successful
Main / build-and-push-docker-image (20.x) (push) Successful in 4m23s
2026-01-26 21:28:18 -05:00
8157ca00a8 Updated embed check to include TikTok checks. Consolidated the code and removed unnecessary logic checks. 2026-01-26 21:27:30 -05:00
5c5e2ee5ba updated pronouns
All checks were successful
Main / build-and-push-docker-image (20.x) (push) Successful in 4m24s
2026-01-26 19:53:51 -05:00
cc4abb3385 Added tiktok filter 2026-01-26 19:53:09 -05:00
299530b0dd Updated "blacklist" to be "banlist" 2026-01-26 19:52:57 -05:00
b8db58a71a Upgrade dependencies. 2026-01-26 19:36:52 -05:00
8dce37fe08 Added functionality to fetch command list from bot stats site api. 2026-01-26 19:35:24 -05:00
593083d994 Update file names. 2026-01-26 19:35:01 -05:00
d816568522 Moved env variables into function. 2026-01-26 19:34:25 -05:00
1361fab0ad upgrade dependencies 2026-01-26 19:33:24 -05:00
f6db2d5d7a update gitignore
All checks were successful
Main / build-and-push-docker-image (20.x) (push) Successful in 5m41s
2026-01-02 20:34:21 -05:00
df433c1951 Added cache exchange urq, fixed eslint, added prettier, upgraded yarn, added statsSite command.
All checks were successful
Main / build-and-push-docker-image (20.x) (push) Successful in 4m58s
2026-01-02 10:10:20 -05:00
c088d7c4a5 Added new commands. getGroupStats and registerGroup.
All checks were successful
Main / build-and-push-docker-image (20.x) (push) Successful in 3m3s
2025-12-16 15:25:02 -05:00
66fb9cef85 Remove cache exchange 2025-12-16 15:24:34 -05:00
789803da59 Update imports and group info extraction from context. 2025-12-16 15:24:16 -05:00
8d8b7f4c3a Added new query 2025-12-16 15:23:24 -05:00
0177aae79a Update file name 2025-12-16 15:23:12 -05:00
8076b984f5 Update groupID type for mutations. 2025-12-16 15:23:04 -05:00
2050e61706 Added mutation key to features.
All checks were successful
Main / build-and-push-docker-image (20.x) (push) Successful in 3m32s
2025-12-10 19:39:15 -05:00
465fbf96c9 fixed env variable not working.
All checks were successful
Main / build-and-push-docker-image (20.x) (push) Successful in 5m58s
2025-12-06 23:45:46 -05:00
30546606d7 Merge pull request 'graphql' (#81) from graphql into main
All checks were successful
Main / build-and-push-docker-image (20.x) (push) Successful in 6m0s
Reviewed-on: #81
2025-12-07 03:32:59 +00:00
27 changed files with 1618 additions and 1131 deletions

View File

@@ -9,4 +9,5 @@ SERVER_PORT=3000
BOT_ADMINS=[1] BOT_ADMINS=[1]
GROUP_IDS=- GROUP_IDS=-
GRAPHQL_URL=http://localhost:3000/api/graphql GRAPHQL_URL=http://localhost:3000/api/graphql
GRAPHQL_API_TOKEN="token-here" GRAPHQL_API_TOKEN="token-here"
GRAPHQL_MUTATION_KEY="token-here"

4
.gitignore vendored
View File

@@ -135,4 +135,6 @@ data/
# Ignore SQLite database # Ignore SQLite database
*.db *.db
*.db-journal *.db-journal
build

View File

@@ -1,9 +1,11 @@
import globals from "globals"; import globals from "globals";
import pluginJs from "@eslint/js"; import pluginJs from "@eslint/js";
import { defineConfig, globalIgnores } from "eslint/config";
import tseslint from "typescript-eslint"; import tseslint from "typescript-eslint";
/** @type {import('eslint').Linter.Config[]} */ /** @type {import('eslint').Linter.Config[]} */
export default [ export default defineConfig([
globalIgnores(["build/"]),
{ files: ["**/*.{js,mjs,cjs,ts}"] }, { files: ["**/*.{js,mjs,cjs,ts}"] },
{ languageOptions: { globals: globals.browser } }, { languageOptions: { globals: globals.browser } },
pluginJs.configs.recommended, pluginJs.configs.recommended,
@@ -22,4 +24,4 @@ export default [
] ]
} }
} }
]; ]);

View File

@@ -3,7 +3,7 @@
"type": "module", "type": "module",
"version": "3.1.0", "version": "3.1.0",
"private": true, "private": true,
"packageManager": "yarn@4.9.2", "packageManager": "yarn@4.12.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.", "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": { "imports": {
"#root/*": "./build/src/*" "#root/*": "./build/src/*"
@@ -32,26 +32,30 @@
"@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.22.2", "@grammyjs/types": "3.23.0",
"@hono/node-server": "1.19.4", "@hono/node-server": "1.19.9",
"@urql/core": "^6.0.1", "@urql/core": "^6.0.1",
"axios": "^1.13.3",
"callback-data": "1.1.1", "callback-data": "1.1.1",
"grammy": "1.38.2", "grammy": "1.39.3",
"hono": "4.9.9", "hono": "4.11.6",
"iso-639-1": "3.1.5", "iso-639-1": "3.1.5",
"pino": "9.12.0", "pino": "10.3.0",
"pino-pretty": "13.1.1", "pino-pretty": "13.1.3",
"tsx": "4.20.6", "tsx": "4.21.0",
"valibot": "1.1.0" "valibot": "1.2.0"
}, },
"devDependencies": { "devDependencies": {
"@antfu/eslint-config": "5.4.1", "@antfu/eslint-config": "7.2.0",
"@types/node": "^24.5.2", "@eslint/js": "^9.39.2",
"eslint": "^9.36.0", "@types/node": "^25.0.10",
"eslint": "^9.39.2",
"husky": "^9.1.7", "husky": "^9.1.7",
"lint-staged": "^16.2.3", "lint-staged": "^16.2.7",
"prettier": "3.8.1",
"tsc-watch": "^7.2.0", "tsc-watch": "^7.2.0",
"typescript": "^5.9.2" "typescript": "^5.9.3",
"typescript-eslint": "^8.54.0"
}, },
"lint-staged": { "lint-staged": {
"*.ts": "eslint" "*.ts": "eslint"

View File

@@ -1,8 +1,8 @@
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 urql from "#root/lib/urql.js"; import { urql } from "#root/main.js";
import increment from "#root/lib/graphql/mutations/incrimentMutation.js"; import increment from "#root/lib/graphql/mutations/incrementMutation.js";
const composer = new Composer<Context>(); const composer = new Composer<Context>();
@@ -16,7 +16,9 @@ feature.hears(
/^\/botInfo/, /^\/botInfo/,
logHandle("bot-info-command"), logHandle("bot-info-command"),
async (ctx: Context) => { async (ctx: Context) => {
await urql.mutation(increment, { trigger: true }); const mutationKey = process.env.GRAPHQL_MUTATION_KEY || "";
await urql.mutation(increment, { trigger: true, mutationKey });
// Checks if the context includes a message property. // Checks if the context includes a message property.
if (ctx.msg && ctx.chat && ctx.msg.from) { if (ctx.msg && ctx.chat && ctx.msg.from) {
@@ -30,7 +32,7 @@ feature.hears(
} }
await urql await urql
.mutation(increment, { command: true }) .mutation(increment, { command: true, mutationKey })
.toPromise() .toPromise()
.then(async () => { .then(async () => {
if (ctx.msg) { if (ctx.msg) {
@@ -43,21 +45,21 @@ feature.hears(
} }
); );
await ctx.reply( await ctx.reply(
`[Lucid](https://werewolfkid.monster) made this bot as a protest against the enshittification of Twitter and Nazi\\-fication of it\\ ever since Elon Must did a Nazi salute at a Trump Rally celebrating Trump\\'s second inauguration\\.`, `[Lucid](https://werewolfkid.monster) made this bot as a protest against the enshittification and Nazi\\-fication of Twitter\\ ever since Elon Musk took it over. The final straw aws when he did a Nazi salute at a Trump Rally celebrating Trump\\'s second inauguration\\.`,
{ {
parse_mode: "MarkdownV2", parse_mode: "MarkdownV2",
reply_parameters: { message_id: ctx.msg.message_id } reply_parameters: { message_id: ctx.msg.message_id }
} }
); );
await ctx.reply( await ctx.reply(
`Recently there have been reports of extremist right\\-wing individuals doxxing and harassing anyone left of center\\. The victims have come out to state that they\\'ve had several accounts hacked into\\, including onces with unique passwords\\. It is clear that these assholes are using some kind of script kiddie malware to capture\\/steal credentials and possibly even stored passwords\\.`, `There have been reports of extremist right\\-wing individuals doxxing and harassing anyone left of center\\. The victims have come out to state that they\\'ve had several accounts hacked into\\, including onces with unique passwords\\. It is clear that these assholes are using some kind of script kiddie malware to capture\\/steal credentials and possibly even stored passwords\\.`,
{ {
parse_mode: "MarkdownV2", parse_mode: "MarkdownV2",
reply_parameters: { message_id: ctx.msg.message_id } reply_parameters: { message_id: ctx.msg.message_id }
} }
); );
return await ctx.reply( return await ctx.reply(
`Lucid decided it was time to make a statement\\. Twitter\\/X is not safe anymore\\. The fandom doesn\\'t need it\\. It should be boycotted and not allowed anymore\\. Thus decided to try making this bot public\\-use to test it's viability\\. Feel free to add this bot into your own group\\. All it needs is an admin role with the permission to delete messages\\.`, `Lucid decided it was time to make a statement\\. Twitter\\/X is not safe anymore\\. The fandom doesn\\'t need it\\. It should be boycotted and not allowed anymore\\. Thus they decided to try making this bot public\\-use to test it's viability\\. Feel free to add this bot into your own group\\. All it needs is an admin role with the permission to delete messages\\.`,
{ {
parse_mode: "MarkdownV2", parse_mode: "MarkdownV2",
reply_parameters: { message_id: ctx.msg.message_id } reply_parameters: { message_id: ctx.msg.message_id }

View File

@@ -0,0 +1,53 @@
import { Composer } from "grammy";
import type { Context } from "#root/bot/context.js";
import { logHandle } from "#root/bot/helpers/logging.js";
import { urql } from "#root/main.js";
import increment from "#root/lib/graphql/mutations/incrementMutation.js";
const composer = new Composer<Context>();
const feature = composer.chatType(["private", "group", "supergroup"]);
/**
* What triggers this feature and adds to the log when it has been triggered.
* The trigger is the command "/botInfo"
*/
feature.hears(
/^\/botStats/,
logHandle("site-stats-command"),
async (ctx: Context) => {
const mutationKey = process.env.GRAPHQL_MUTATION_KEY || "";
await urql.mutation(increment, { trigger: true, mutationKey });
// Checks if the context includes a message property.
if (ctx.msg && ctx.chat && ctx.msg.from) {
// Doesn't respond to regular users in groups. This is to prevent users spamming the command.
if (ctx.chat.type !== "private") {
const chatMember = await ctx.getChatMember(ctx.msg.from.id);
if (!["creator", "administrator"].includes(chatMember.status)) {
return;
}
}
await urql
.mutation(increment, { command: true, mutationKey })
.toPromise()
.then(async () => {
if (ctx.msg) {
// Replies to the message.
return await ctx.reply(
`The bot statistics website can be found at https://bot\\-stats\\.werewolfkid\\.monster/\\.`,
{
parse_mode: "MarkdownV2",
reply_parameters: { message_id: ctx.msg.message_id }
}
);
}
});
}
}
);
export { composer as statsSiteCommand };

View File

@@ -3,10 +3,11 @@ 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 metaLinkCheck from "#root/lib/metaLinkCheck.js";
import twitterLinkCheck from "#root/lib/twitterLinkCheck.js"; import twitterLinkCheck from "#root/lib/twitterLinkCheck.js";
import urql from "#root/lib/urql.js"; import { urql } from "#root/main.js";
import increment from "#root/lib/graphql/mutations/incrimentMutation.js"; import increment from "#root/lib/graphql/mutations/incrementMutation.js";
import addGroup from "#root/lib/graphql/mutations/addGroupMutation.js"; import addGroup from "#root/lib/graphql/mutations/addGroupMutation.js";
import incrementGroup from "#root/lib/graphql/mutations/incrementGroupMutation.js"; import incrementGroup from "#root/lib/graphql/mutations/incrementGroupMutation.js";
import tiktokLinkCheck from "#root/lib/tiktokLinkCheck.js";
const composer = new Composer<Context>(); const composer = new Composer<Context>();
@@ -17,12 +18,14 @@ const feature = composer.chatType(["group", "supergroup"]);
* The trigger is anytime an embedded url is detected. * The trigger is anytime an embedded url is detected.
*/ */
feature.on("message::url", logHandle("embed-check"), async (ctx: Context) => { feature.on("message::url", logHandle("embed-check"), async (ctx: Context) => {
await urql.mutation(increment, { trigger: true }); const mutationKey = process.env.GRAPHQL_MUTATION_KEY || "";
await urql.mutation(increment, { trigger: true, mutationKey });
if (ctx.chat && ctx.msg) { if (ctx.chat && ctx.msg) {
const groupName = ctx.chat?.title; const groupName = ctx.chat?.title || "";
const groupID = ctx.chat?.id; const groupID = ctx.chat?.id.toString() || "";
const groupUsername = ctx.chat?.username; const groupUsername = ctx.chat?.username || "";
let deletedLinks = 0; let deletedLinks = 0;
const username = ctx.msg.from?.username; const username = ctx.msg.from?.username;
@@ -30,154 +33,68 @@ feature.on("message::url", logHandle("embed-check"), async (ctx: Context) => {
// Filters every message/caption entity that is a url into a new array. // Filters every message/caption entity that is a url into a new array.
const embeds = ctx.msg.entities const embeds = ctx.msg.entities
? ctx.msg.entities.filter(e => e.type === "text_link") ? ctx.msg.entities.filter(e => e.type === "text_link")
: null; : [];
const captionEmbeds = ctx.msg.caption_entities const captionEmbeds = ctx.msg.caption_entities
? ctx.msg.caption_entities.filter(e => e.type === "text_link") ? ctx.msg.caption_entities.filter(e => e.type === "text_link")
: null; : [];
const allEmbeds = embeds.concat(captionEmbeds);
// If the caption embeds array isn't empty filter through them to check if any is a Twitter/X or Meta url. // If the caption embeds array isn't empty filter through them to check if any is a Twitter/X or Meta url.
if (captionEmbeds !== null && captionEmbeds.length) { if (allEmbeds.length) {
const metaLinks = captionEmbeds.filter(({ url }) => metaLinkCheck(url)); const metaLinks = allEmbeds.filter(({ url }) => metaLinkCheck(url));
const twitterLinks = captionEmbeds.filter(({ url }) => const twitterLinks = allEmbeds.filter(({ url }) => twitterLinkCheck(url));
twitterLinkCheck(url) const tiktokLinks = allEmbeds.filter(({ url }) => tiktokLinkCheck(url));
);
// Handle action and response if both meta and Twitter/X links are detected. // Handles Meta & Facebook Links
if (metaLinks.length && twitterLinks.length) {
// Deletes the offending message.
ctx.msg.delete();
deletedLinks++;
await urql
.mutation(increment, { link: true })
.toPromise()
.then(async () => {
if (ctx.msg) {
// 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\\. 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" }
);
}
});
}
// Handle action and response if only meta links are detected.
if (metaLinks.length) { if (metaLinks.length) {
// Deletes the offending message. deletedLinks += metaLinks.length;
ctx.msg.delete();
deletedLinks++;
await urql
.mutation(increment, { link: true })
.toPromise()
.then(async () => {
if (ctx.msg) {
// Replies to the user informing them of the action.
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" }
);
}
});
} }
// Handle action and response if only Twitter/X links are detected. // Handles Twitter/X links.
if (twitterLinks.length) { if (twitterLinks.length) {
// Deletes the offending message. deletedLinks += twitterLinks.length;
ctx.msg.delete(); }
deletedLinks++;
await urql // Handles TikTok links.
.mutation(increment, { link: true }) if (tiktokLinks.length) {
.toPromise() deletedLinks += tiktokLinks.length;
.then(async () => {
if (ctx.msg) {
// 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\\.\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));
// Handle action and response if both meta and Twitter/X links are detected. if (deletedLinks) {
if (metaLinks.length && twitterLinks.length) { ctx.msg.delete();
// Deletes the offending message.
ctx.msg.delete();
deletedLinks++;
await urql return await urql
.mutation(increment, { link: true }) .mutation(addGroup, {
.toPromise() groupID,
.then(async () => { groupName,
if (ctx.msg) { groupUsername,
// Replies to the user informing them of the action. mutationKey
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\\.`, .toPromise()
{ parse_mode: "MarkdownV2" } .then(() =>
); urql
} .mutation(incrementGroup, {
});
}
// Handle action and response if only meta links are detected.
if (metaLinks.length) {
// Deletes the offending message.
ctx.msg.delete();
deletedLinks++;
await urql
.mutation(increment, { link: true })
.toPromise()
.then(async () => {
if (ctx.msg) {
// Replies to the user informing them of the action.
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();
deletedLinks++;
await urql
.mutation(increment, { link: true })
.toPromise()
.then(async () => {
if (ctx.msg) {
// 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\\.`,
{ parse_mode: "MarkdownV2" }
);
}
});
}
if (deletedLinks) {
return await urql
.mutation(addGroup, { groupID, groupName, groupUsername })
.toPromise()
.then(() =>
urql.mutation(incrementGroup, {
groupID, groupID,
linksDeleted: deletedLinks linksDeleted: deletedLinks,
mutationKey
}) })
); .then(
} async () =>
await urql
.mutation(increment, { link: deletedLinks, mutationKey })
.toPromise()
.then(async () => {
if (ctx.msg) {
// Replies to the user informing them of the action.
await ctx.reply(
`@${username} One or more links in your last message were on the banlist\\. Remember TikTok, Meta & Facebook, and Twitter\\/X links along with any reformatting services for them are not allowed here\\. Please consider sharing the media directly or from other social media sources or websites\\.\n\nNo 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" }
);
}
})
)
);
} }
} }
}); });

View File

@@ -0,0 +1,96 @@
import { Composer } from "grammy";
import type { Context } from "#root/bot/context.js";
import { logHandle } from "#root/bot/helpers/logging.js";
import { urql } from "#root/main.js";
import increment from "#root/lib/graphql/mutations/incrementMutation.js";
import getGroupStats from "#root/lib/graphql/queries/getGroupStatsQuery.js";
const composer = new Composer<Context>();
const feature = composer.chatType(["group", "supergroup"]);
/**
* What triggers this feature and adds to the log when it has been triggered.
* The trigger is the command "/groupStats"
*/
feature.hears(
/^\/groupStats/,
logHandle("groups-stats-command"),
async (ctx: Context) => {
const mutationKey = process.env.GRAPHQL_MUTATION_KEY || "";
await urql.mutation(increment, { trigger: true, mutationKey });
// Checks if the context includes a message property.
if (ctx.msg && ctx.chat && ctx.msg.from) {
// Doesn't respond to regular users in groups. This is to prevent users spamming the command.
const chatMember = await ctx.getChatMember(ctx.msg.from.id);
if (!["creator", "administrator"].includes(chatMember.status)) {
return;
}
// Stringify the groupID
const groupID = ctx.chat?.id.toString() || "";
// Query to get group stats.
await urql
.query(getGroupStats, {
groupID
})
.toPromise()
.then(async res => {
// Replies to the message.
if (ctx.msg) {
// Check if the group has a document in the database and respond accordingly.
if (res.data.getGroupStats !== null) {
const { name, username, linksDeleted } = res.data.getGroupStats;
await ctx.reply(
`Your group is registered in the database as "${name}" ${
username.length
? `with a username of ${name}`
: `without a public username`
}\\.\n\nThe bot has successfully deleted ${linksDeleted} links from your group\\.`,
{
parse_mode: "MarkdownV2",
reply_parameters: { message_id: ctx.msg.message_id }
}
);
return await ctx.reply(
`If you need to update this information you can use the \\/registerGroup command\\.`,
{
parse_mode: "MarkdownV2",
reply_parameters: { message_id: ctx.msg.message_id }
}
);
}
// Default response //
await ctx.reply(
`Your group was not found in the database\\. You can use the \\/registerGroup command to add your group to the database\\.`,
{
parse_mode: "MarkdownV2",
reply_parameters: { message_id: ctx.msg.message_id }
}
);
return await ctx.reply(
`This is optional\\. If the bot ever removes a link in your group then the group information will be added to the database and tracked from then on\\.`,
{
parse_mode: "MarkdownV2",
reply_parameters: { message_id: ctx.msg.message_id }
}
);
}
return await urql
.mutation(increment, { command: true, mutationKey })
.toPromise();
});
}
}
);
export { composer as getGroupStats };

View File

@@ -1,8 +1,9 @@
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 urql from "#root/lib/urql.js"; import { urql } from "#root/main.js";
import increment from "#root/lib/graphql/mutations/incrimentMutation.js"; import increment from "#root/lib/graphql/mutations/incrementMutation.js";
import getCommandsList, { Commands } from "#root/lib/getCommandsList.js";
const composer = new Composer<Context>(); const composer = new Composer<Context>();
@@ -13,7 +14,12 @@ const feature = composer.chatType(["group", "supergroup", "private"]);
* The trigger is the command "/help" * The trigger is the command "/help"
*/ */
feature.hears(/^\/help/, logHandle("help"), async (ctx: Context) => { feature.hears(/^\/help/, logHandle("help"), async (ctx: Context) => {
await urql.mutation(increment, { trigger: true }); const apiKey = process.env.GRAPHQL_API_TOKEN || "";
const mutationKey = process.env.GRAPHQL_MUTATION_KEY || "";
const statsSite = process.env.STATS_SITE || "";
await urql.mutation(increment, { trigger: true, mutationKey });
const commandsListArr = await getCommandsList(`${statsSite}api/bot`, apiKey);
// const GROUP_IDS = process.env.GROUP_IDS // const GROUP_IDS = process.env.GROUP_IDS
// ? process.env.GROUP_IDS.split(",") // ? process.env.GROUP_IDS.split(",")
@@ -22,7 +28,7 @@ feature.hears(/^\/help/, logHandle("help"), async (ctx: Context) => {
// Checks there is a chat and msg property in the context. // Checks there is a chat and msg property in the context.
if (ctx.chat && ctx.msg && ctx.msg.from) { if (ctx.chat && ctx.msg && ctx.msg.from) {
await urql await urql
.mutation(increment, { command: true }) .mutation(increment, { command: true, mutationKey })
.toPromise() .toPromise()
.then(async () => { .then(async () => {
if (ctx.msg && ctx.chat && ctx.msg.from) { if (ctx.msg && ctx.chat && ctx.msg.from) {
@@ -30,7 +36,7 @@ feature.hears(/^\/help/, logHandle("help"), async (ctx: Context) => {
if (ctx.chat.type === "private") { if (ctx.chat.type === "private") {
// Responds with message. // Responds with message.
await ctx.reply( await ctx.reply(
`Currently I have no commands for the public beta\\. I require no configuration\\. Simply add me to a group and make me an admin with the permission to delete messages\\. From there I will start deleting any Twitter\\/X and Meta links I detect\\. I also check embeds and forwards\\. Use /\\botInfo to learn more about my mission and why I was created\\.`, `I require no configuration\\. Simply add me to a group and make me an admin with the permission to delete messages\\. From there I will start deleting any Twitter\\/X and Meta links I detect\\. I also check embeds and forwards\\.`,
{ {
parse_mode: "MarkdownV2", parse_mode: "MarkdownV2",
reply_parameters: { message_id: ctx.msg.message_id } reply_parameters: { message_id: ctx.msg.message_id }
@@ -46,7 +52,7 @@ feature.hears(/^\/help/, logHandle("help"), async (ctx: Context) => {
if (["creator", "administrator"].includes(chatMember.status)) { if (["creator", "administrator"].includes(chatMember.status)) {
// Respond with message. // Respond with message.
await ctx.reply( await ctx.reply(
`Currently I have no commands for the public beta\\. I require no configuration\\. If I am not working\\, make sure I have an admin role with the permission to delete messages\\. I should already be deleting any Twitter\\/X and Meta links I detect\\. I also check embeds and forwards\\. Use /\\botInfo to learn more about my mission and why I was created\\.`, `I require no configuration\\. If I am not working\\, make sure I have an admin role with the permission to delete messages\\. I should already be deleting any Twitter\\/X and Meta links I detect\\. I also check embeds and forwards\\.`,
{ {
parse_mode: "MarkdownV2", parse_mode: "MarkdownV2",
reply_parameters: { message_id: ctx.msg.message_id } reply_parameters: { message_id: ctx.msg.message_id }
@@ -61,6 +67,17 @@ feature.hears(/^\/help/, logHandle("help"), async (ctx: Context) => {
} }
} }
const commandsListStr = commandsListArr.reduce((prev, curr) => {
const { command, description, groups } = curr;
return (prev += `**Command**: ${command} \\- ${description}\\.\nGroups: ${groups ? "✔️" : "❌"} \\| Private: ${curr.private ? "✔️" : "❌"}\n\n`);
}, "Here are a list of my commands:\n\n");
await ctx.reply(commandsListStr, {
parse_mode: "MarkdownV2",
reply_parameters: { message_id: ctx.msg.message_id }
});
await ctx.reply( await ctx.reply(
`In the future a public GitHub repo will be set up with a mirror of the private git that the bot code is stored on\\. That repo will be used to handle bug reports and feature requests\\. Including requests to check for more domains\\.`, `In the future a public GitHub repo will be set up with a mirror of the private git that the bot code is stored on\\. That repo will be used to handle bug reports and feature requests\\. Including requests to check for more domains\\.`,
{ {
@@ -69,13 +86,13 @@ feature.hears(/^\/help/, logHandle("help"), async (ctx: Context) => {
} }
); );
return await ctx.reply( // return await ctx.reply(
`Down with fascism\\! Fuck MAGA\\! Fuck Trump\\! Fuck Nazis\\! Trans rights are human rights\\! Trans women are women\\! Trans men are men\\! Never let them take away your happiness\\!`, // `Down with fascism\\! Fuck MAGA\\! Fuck Trump\\! Fuck Nazis\\! Trans rights are human rights\\! Trans women are women\\! Trans men are men\\! Never let them take away your happiness\\!`,
{ // {
parse_mode: "MarkdownV2", // parse_mode: "MarkdownV2",
reply_parameters: { message_id: ctx.msg.message_id } // reply_parameters: { message_id: ctx.msg.message_id }
} // }
); // );
} }
}); });
} }

View File

@@ -2,8 +2,8 @@ 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"; import { metaRegex } from "#root/lib/metaLinkCheck.js";
import urql from "#root/lib/urql.js"; import { urql } from "#root/main.js";
import increment from "#root/lib/graphql/mutations/incrimentMutation.js"; import increment from "#root/lib/graphql/mutations/incrementMutation.js";
import addGroup from "#root/lib/graphql/mutations/addGroupMutation.js"; import addGroup from "#root/lib/graphql/mutations/addGroupMutation.js";
import incrementGroup from "#root/lib/graphql/mutations/incrementGroupMutation.js"; import incrementGroup from "#root/lib/graphql/mutations/incrementGroupMutation.js";
@@ -17,9 +17,11 @@ const feature = composer.chatType(["group", "supergroup"]);
*/ */
feature.hears( feature.hears(
metaRegex, metaRegex,
logHandle("blacklist-detection-meta"), logHandle("banlist-detection-meta"),
async (ctx: Context) => { async (ctx: Context) => {
await urql.mutation(increment, { trigger: true }); const mutationKey = process.env.GRAPHQL_MUTATION_KEY || "";
await urql.mutation(increment, { trigger: true, mutationKey });
if (ctx.chat && ctx.msg) { if (ctx.chat && ctx.msg) {
const username = ctx.msg.from?.username; const username = ctx.msg.from?.username;
@@ -27,25 +29,34 @@ feature.hears(
ctx.msg.delete(); ctx.msg.delete();
return await urql return await urql
.mutation(increment, { link: true }) .mutation(increment, { link: 1, mutationKey })
.toPromise() .toPromise()
.then(async () => { .then(async () => {
if (ctx.msg) { if (ctx.msg && ctx.chat) {
// Replies to the user informing them of the action. // Replies to the user informing them of the action.
await ctx.reply( 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" }
); );
const groupName = ctx.chat?.title; const groupName = ctx.chat?.title || "";
const groupID = ctx.chat?.id; const groupID = ctx.chat?.id.toString() || "";
const groupUsername = ctx.chat?.username; const groupUsername = ctx.chat?.username || "";
return await urql return await urql
.mutation(addGroup, { groupID, groupName, groupUsername }) .mutation(addGroup, {
groupID,
groupName,
groupUsername,
mutationKey
})
.toPromise() .toPromise()
.then(() => .then(() =>
urql.mutation(incrementGroup, { groupID, linksDeleted: 1 }) urql.mutation(incrementGroup, {
groupID,
linksDeleted: 1,
mutationKey
})
); );
} }
}); });
@@ -53,4 +64,4 @@ feature.hears(
} }
); );
export { composer as metaBlacklist }; export { composer as metaBanlist };

View File

@@ -0,0 +1,65 @@
import { Composer } from "grammy";
import type { Context } from "#root/bot/context.js";
import { logHandle } from "#root/bot/helpers/logging.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";
const composer = new Composer<Context>();
const feature = composer.chatType(["group", "supergroup"]);
/**
* What triggers this feature and adds to the log when it has been triggered.
* The trigger is the command "/registerGroup"
*/
feature.hears(
/^\/registerGroup/,
logHandle("register-group-command"),
async (ctx: Context) => {
const mutationKey = process.env.GRAPHQL_MUTATION_KEY || "";
await urql.mutation(increment, { trigger: true, mutationKey });
// Checks if the context includes a message property.
if (ctx.msg && ctx.chat && ctx.msg.from) {
// Doesn't respond to regular users in groups. This is to prevent users spamming the command.
const chatMember = await ctx.getChatMember(ctx.msg.from.id);
if (!["creator", "administrator"].includes(chatMember.status)) {
return;
}
const groupName = ctx.chat?.title || "";
const groupID = ctx.chat?.id.toString() || ""; // Stringify the groupID
const groupUsername = ctx.chat?.username || "";
// Add or update the group
await urql
.mutation(addGroup, {
groupID,
groupName,
groupUsername,
mutationKey
})
.toPromise()
.then(async () => {
await urql
.mutation(increment, { command: true, mutationKey })
.toPromise();
if (ctx.msg) {
// Replies to the message.
return await ctx.reply(
`Your group has been successfully added to the database\\.`,
{
parse_mode: "MarkdownV2",
reply_parameters: { message_id: ctx.msg.message_id }
}
);
}
});
}
}
);
export { composer as registerGroup };

View File

@@ -0,0 +1,67 @@
import { Composer } from "grammy";
import type { Context } from "#root/bot/context.js";
import { logHandle } from "#root/bot/helpers/logging.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 { tiktokRegex } from "#root/lib/tiktokLinkCheck.js";
const composer = new Composer<Context>();
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 TikTok links within messages.
*/
feature.hears(
tiktokRegex,
logHandle("banlist-detection-tiktok"),
async (ctx: Context) => {
const mutationKey = process.env.GRAPHQL_MUTATION_KEY || "";
await urql.mutation(increment, { trigger: true, mutationKey });
if (ctx.chat && ctx.msg) {
const username = ctx.msg.from?.username;
// Deletes the offending message.
ctx.msg.delete();
return await urql
.mutation(increment, { link: 1, mutationKey })
.toPromise()
.then(async () => {
if (ctx.msg && ctx.chat) {
// Replies to the user informing them of the action.
await ctx.reply(
`@${username} TikTok links 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" }
);
const groupName = ctx.chat?.title || "";
const groupID = ctx.chat?.id.toString() || "";
const groupUsername = ctx.chat?.username || "";
return await urql
.mutation(addGroup, {
groupID,
groupName,
groupUsername,
mutationKey
})
.toPromise()
.then(() =>
urql.mutation(incrementGroup, {
groupID,
linksDeleted: 1,
mutationKey
})
);
}
});
}
}
);
export { composer as tiktokBanlist };

View File

@@ -2,8 +2,8 @@ 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"; import { twitterRegex } from "#root/lib/twitterLinkCheck.js";
import urql from "#root/lib/urql.js"; import { urql } from "#root/main.js";
import increment from "#root/lib/graphql/mutations/incrimentMutation.js"; import increment from "#root/lib/graphql/mutations/incrementMutation.js";
import addGroup from "#root/lib/graphql/mutations/addGroupMutation.js"; import addGroup from "#root/lib/graphql/mutations/addGroupMutation.js";
import incrementGroup from "#root/lib/graphql/mutations/incrementGroupMutation.js"; import incrementGroup from "#root/lib/graphql/mutations/incrementGroupMutation.js";
@@ -17,9 +17,11 @@ const feature = composer.chatType(["group", "supergroup"]);
*/ */
feature.hears( feature.hears(
twitterRegex, twitterRegex,
logHandle("blacklist-detection-twitter"), logHandle("banlist-detection-twitter"),
async (ctx: Context) => { async (ctx: Context) => {
await urql.mutation(increment, { trigger: true }); const mutationKey = process.env.GRAPHQL_MUTATION_KEY || "";
await urql.mutation(increment, { trigger: true, mutationKey });
if (ctx.chat && ctx.msg) { if (ctx.chat && ctx.msg) {
const username = ctx.msg.from?.username; const username = ctx.msg.from?.username;
@@ -27,25 +29,34 @@ feature.hears(
ctx.msg.delete(); ctx.msg.delete();
return await urql return await urql
.mutation(increment, { link: true }) .mutation(increment, { link: 1, mutationKey })
.toPromise() .toPromise()
.then(async () => { .then(async () => {
if (ctx.msg) { if (ctx.msg && ctx.chat) {
// Replies to the user informing them of the action. // Replies to the user informing them of the action.
await ctx.reply( 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" }
); );
const groupName = ctx.chat?.title; const groupName = ctx.chat?.title || "";
const groupID = ctx.chat?.id; const groupID = ctx.chat?.id.toString() || "";
const groupUsername = ctx.chat?.username; const groupUsername = ctx.chat?.username || "";
return await urql return await urql
.mutation(addGroup, { groupID, groupName, groupUsername }) .mutation(addGroup, {
groupID,
groupName,
groupUsername,
mutationKey
})
.toPromise() .toPromise()
.then(() => .then(() =>
urql.mutation(incrementGroup, { groupID, linksDeleted: 1 }) urql.mutation(incrementGroup, {
groupID,
linksDeleted: 1,
mutationKey
})
); );
} }
}); });
@@ -53,4 +64,4 @@ feature.hears(
} }
); );
export { composer as twitterBlacklist }; export { composer as twitterBanlist };

View File

@@ -1,21 +1,24 @@
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 urql from "#root/lib/urql.js"; import { urql } from "#root/main.js";
import increment from "#root/lib/graphql/mutations/incrimentMutation.js"; import increment from "#root/lib/graphql/mutations/incrementMutation.js";
const composer = new Composer<Context>(); const composer = new Composer<Context>();
const feature = composer.chatType("private"); const feature = composer.chatType("private");
/** /**
* What triggers this feature and adds to the log when it has been triggered. * What triggers this feature and adds to the log when it has been triggered.
* The trigger is the command "/start" or "start" * The trigger is the command "/start" or "start"
*/ */
feature.command("start", logHandle("command-start"), async ctx => { feature.command("start", logHandle("command-start"), async ctx => {
await urql.mutation(increment, { trigger: true }); const mutationKey = process.env.GRAPHQL_MUTATION_KEY || "";
await urql.mutation(increment, { trigger: true, mutationKey });
await urql await urql
.mutation(increment, { command: true }) .mutation(increment, { command: true, mutationKey })
.toPromise() .toPromise()
.then(async () => { .then(async () => {
if (ctx.msg) { if (ctx.msg) {

View File

@@ -5,7 +5,7 @@ import type { BotConfig } from "grammy";
import { unhandledFeature } from "#root/bot/features/unhandled.js"; import { unhandledFeature } from "#root/bot/features/unhandled.js";
import { welcomeFeature } from "#root/bot/features/welcome.js"; import { welcomeFeature } from "#root/bot/features/welcome.js";
import { errorHandler } from "#root/bot/handlers/error.js"; import { errorHandler } from "#root/bot/handlers/error.js";
import { i18n, isMultipleLocales } from "#root/bot/i18n.js"; import { i18n } from "#root/bot/i18n.js";
import { session } from "#root/bot/middlewares/session.js"; import { session } from "#root/bot/middlewares/session.js";
import { updateLogger } from "#root/bot/middlewares/update-logger.js"; import { updateLogger } from "#root/bot/middlewares/update-logger.js";
import { autoChatAction } from "@grammyjs/auto-chat-action"; import { autoChatAction } from "@grammyjs/auto-chat-action";
@@ -13,11 +13,15 @@ import { hydrate } from "@grammyjs/hydrate";
import { hydrateReply, parseMode } from "@grammyjs/parse-mode"; import { hydrateReply, parseMode } from "@grammyjs/parse-mode";
import { sequentialize } from "@grammyjs/runner"; import { sequentialize } from "@grammyjs/runner";
import { MemorySessionStorage, Bot as TelegramBot } from "grammy"; import { MemorySessionStorage, Bot as TelegramBot } from "grammy";
import { twitterBlacklist } from "./features/twitterBlacklist.js"; import { twitterBanlist } from "./features/twitterBanlist.js";
import { metaBlacklist } from "./features/metaBlacklist.js"; import { metaBanlist } from "./features/metaBanlist.js";
import { tiktokBanlist } from "./features/tiktokBanlist.js";
import { botInfoCommand } from "./features/botInfoCommand.js"; import { botInfoCommand } from "./features/botInfoCommand.js";
import { helpCommand } from "./features/helpCommand.js"; import { helpCommand } from "./features/helpCommand.js";
import { embedCheck } from "./features/embedCheck.js"; import { embedCheck } from "./features/embedCheck.js";
import { registerGroup } from "./features/registerGroupCommand.js";
import { getGroupStats } from "./features/getGroupStatsCommand.js";
import { statsSiteCommand } from "./features/botStatsSiteCommand.js";
interface Dependencies { interface Dependencies {
config: Config; config: Config;
@@ -70,10 +74,14 @@ export function createBot(
// Commands // Commands
protectedBot.use(botInfoCommand); protectedBot.use(botInfoCommand);
protectedBot.use(helpCommand); protectedBot.use(helpCommand);
protectedBot.use(registerGroup);
protectedBot.use(getGroupStats);
protectedBot.use(statsSiteCommand);
// Blacklist Feature // banlist Feature
protectedBot.use(twitterBlacklist); protectedBot.use(twitterBanlist);
protectedBot.use(metaBlacklist); protectedBot.use(metaBanlist);
protectedBot.use(tiktokBanlist);
protectedBot.use(embedCheck); protectedBot.use(embedCheck);
// must be the last handler // must be the last handler

View File

@@ -0,0 +1,30 @@
import axios from "axios";
export interface Commands {
command: String;
description: String;
groups: boolean;
private: boolean;
}
const fetchCommandsList = async (
url: string,
apiKey: string
): Promise<Commands[]> => {
let commandsList = [] as Commands[];
await axios
.get(url, {
headers: { "x-api-key": apiKey }
})
.then(res => {
commandsList = res.data;
})
.catch(res => {
console.error(res);
});
return commandsList;
};
export default fetchCommandsList;

View File

@@ -2,21 +2,19 @@ import { gql } from "@urql/core";
const addGroup = gql` const addGroup = gql`
mutation addGroup( mutation addGroup(
$groupID: BigInt $groupID: String!
$groupName: String $groupName: String!
$groupUsername: String $groupUsername: String
$mutationKey: String
) { ) {
addGroup( addGroup(
groupID: $groupID groupID: $groupID
groupName: $groupName groupName: $groupName
groupUsername: $groupUsername groupUsername: $groupUsername
mutationKey: $mutationKey
) { ) {
telegramID
name name
username username
linksDeleted
createdAt
updatedAt
} }
} }
`; `;

View File

@@ -1,14 +1,19 @@
import { gql } from "@urql/core"; import { gql } from "@urql/core";
const incrementGroup = gql` const incrementGroup = gql`
mutation incrementGroup($groupID: BigInt, $linksDeleted: Int) { mutation incrementGroup(
incrementGroup(groupID: $groupID, linksDeleted: $linksDeleted) { $groupID: String!
telegramID $linksDeleted: Int!
$mutationKey: String
) {
incrementGroup(
groupID: $groupID
linksDeleted: $linksDeleted
mutationKey: $mutationKey
) {
name name
username username
linksDeleted linksDeleted
createdAt
updatedAt
} }
} }
`; `;

View File

@@ -0,0 +1,22 @@
import { gql } from "@urql/core";
const increment = gql`
mutation increment(
$command: Boolean
$link: Int
$trigger: Boolean
$mutationKey: String
) {
increment(
command: $command
link: $link
trigger: $trigger
mutationKey: $mutationKey
) {
createdAt
updatedAt
}
}
`;
export default increment;

View File

@@ -1,12 +0,0 @@
import { gql } from "@urql/core";
const increment = gql`
mutation increment($command: Boolean, $link: Boolean, $trigger: Boolean) {
increment(command: $command, link: $link, trigger: $trigger) {
createdAt
updatedAt
}
}
`;
export default increment;

View File

@@ -0,0 +1,13 @@
import { gql } from "@urql/core";
const getGroupStats = gql`
query getGroupStats($groupID: String!) {
getGroupStats(groupID: $groupID) {
name
username
linksDeleted
}
}
`;
export default getGroupStats;

View File

@@ -5,7 +5,7 @@ const metaRegex =
/** /**
* This function will check if a url matches Meta services links using regex. * This function will check if a url matches Meta services links using regex.
* *
* @param linkUrl representing a suspected blacklisted url * @param linkUrl representing a suspected url on the banlist
* @returns flag * @returns flag
*/ */
const metaLinkCheck = (linkUrl: string): boolean => { const metaLinkCheck = (linkUrl: string): boolean => {

View File

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

View File

@@ -4,7 +4,7 @@ const twitterRegex = /(x\.com|twitter\.com)/gi;
/** /**
* This function will check if a url matches Twitter/X services links using regex. * This function will check if a url matches Twitter/X services links using regex.
* *
* @param linkUrl representing a suspected blacklisted url * @param linkUrl representing a suspected url on the banlist
* @return flag * @return flag
*/ */
const twitterLinkCheck = (linkUrl: string): boolean => { const twitterLinkCheck = (linkUrl: string): boolean => {

View File

@@ -1,13 +0,0 @@
import { Client, cacheExchange, fetchExchange } from "@urql/core";
const urql = new Client({
url: process.env.GRAPHQL_URL || "",
exchanges: [cacheExchange, fetchExchange],
fetchOptions: {
headers: {
"x-api-key": process.env?.GRAPHQL_API_TOKEN || ""
}
}
});
export default urql;

View File

@@ -8,6 +8,7 @@ import { config } from "#root/config.js";
import { logger } from "#root/logger.js"; import { logger } from "#root/logger.js";
import { createServer, createServerManager } from "#root/server/index.js"; import { createServer, createServerManager } from "#root/server/index.js";
import { run } from "@grammyjs/runner"; import { run } from "@grammyjs/runner";
import { cacheExchange, Client, fetchExchange } from "@urql/core";
async function startPolling(config: PollingConfig) { async function startPolling(config: PollingConfig) {
const bot = createBot(config.botToken, { const bot = createBot(config.botToken, {
@@ -112,3 +113,13 @@ function onShutdown(cleanUp: () => Promise<void>) {
process.on("SIGINT", handleShutdown); process.on("SIGINT", handleShutdown);
process.on("SIGTERM", handleShutdown); process.on("SIGTERM", handleShutdown);
} }
export const urql = new Client({
url: process.env.GRAPHQL_URL || "",
exchanges: [fetchExchange, cacheExchange],
fetchOptions: {
headers: {
"x-api-key": process.env?.GRAPHQL_API_TOKEN || ""
}
}
});

1919
yarn.lock

File diff suppressed because it is too large Load Diff