Compare commits

..

16 Commits

Author SHA1 Message Date
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
98f954aa82 update api token
All checks were successful
Main / build-and-push-docker-image (20.x) (pull_request) Successful in 7m57s
2025-12-06 22:22:33 -05:00
a0107eeb27 added api token to urql and env example 2025-12-06 20:32:09 -05:00
74d32d518e added new env variable 2025-12-05 17:11:04 -05:00
926df56972 Added graphql calls every time a feature is triggered, the bot responds to a command, and deletes a link 2025-11-27 18:17:34 -05:00
59ff831b45 Added graphql queries 2025-11-27 18:16:48 -05:00
f2aa778722 Removed unused features 2025-11-27 18:16:03 -05:00
3a03bbc47b Added urql and client 2025-11-27 18:15:48 -05:00
22 changed files with 625 additions and 434 deletions

View File

@@ -7,4 +7,6 @@ BOT_WEBHOOK_SECRET=RANDOM_SECRET_VALUE
SERVER_HOST=localhost SERVER_HOST=localhost
SERVER_PORT=3000 SERVER_PORT=3000
BOT_ADMINS=[1] BOT_ADMINS=[1]
GROUP_IDS=- GROUP_IDS=-
GRAPHQL_URL=http://localhost:3000/api/graphql
GRAPHQL_API_TOKEN="token-here"

View File

@@ -13,6 +13,7 @@ services:
- SERVER_PORT=${SERVER_PORT} - SERVER_PORT=${SERVER_PORT}
- BOT_ADMINS=${BOT_ADMINS} - BOT_ADMINS=${BOT_ADMINS}
- GROUP_IDS=${GROUP_IDS} - GROUP_IDS=${GROUP_IDS}
- GRAPHQL_URL=${GRAPHQL_URL}
ports: ports:
- "3000:80" - "3000:80"
volumes: volumes:

View File

@@ -14,5 +14,6 @@ services:
- SERVER_PORT=${SERVER_PORT} - SERVER_PORT=${SERVER_PORT}
- BOT_ADMINS=${BOT_ADMINS} - BOT_ADMINS=${BOT_ADMINS}
- GROUP_IDS=${GROUP_IDS} - GROUP_IDS=${GROUP_IDS}
- GRAPHQL_URL=${GRAPHQL_URL}
build: build:
context: . context: .

View File

@@ -14,5 +14,6 @@ services:
- SERVER_PORT=${SERVER_PORT} - SERVER_PORT=${SERVER_PORT}
- BOT_ADMINS=${BOT_ADMINS} - BOT_ADMINS=${BOT_ADMINS}
- GROUP_IDS=${GROUP_IDS} - GROUP_IDS=${GROUP_IDS}
- GRAPHQL_URL=${GRAPHQL_URL}
build: build:
context: . context: .

View File

@@ -1,7 +1,7 @@
{ {
"name": "no-twitter-bot", "name": "no-twitter-bot",
"type": "module", "type": "module",
"version": "3.0.0", "version": "3.1.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.",
@@ -34,6 +34,7 @@
"@grammyjs/runner": "2.0.3", "@grammyjs/runner": "2.0.3",
"@grammyjs/types": "3.22.2", "@grammyjs/types": "3.22.2",
"@hono/node-server": "1.19.4", "@hono/node-server": "1.19.4",
"@urql/core": "^6.0.1",
"callback-data": "1.1.1", "callback-data": "1.1.1",
"grammy": "1.38.2", "grammy": "1.38.2",
"hono": "4.9.9", "hono": "4.9.9",

View File

@@ -1,11 +1,15 @@
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/main.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", "group", "supergroup"]); const feature = composer.chatType(["private", "group", "supergroup"]);
const mutationKey = process.env.GRAPHQL_MUTATION_KEY || "";
/** /**
* 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 "/botInfo" * The trigger is the command "/botInfo"
@@ -14,6 +18,8 @@ feature.hears(
/^\/botInfo/, /^\/botInfo/,
logHandle("bot-info-command"), logHandle("bot-info-command"),
async (ctx: Context) => { async (ctx: Context) => {
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) {
// Doesn't respond to regular users in groups. This is to prevent users spamming the command. // Doesn't respond to regular users in groups. This is to prevent users spamming the command.
@@ -25,35 +31,42 @@ feature.hears(
} }
} }
// Replies to the message. await urql
await ctx.reply( .mutation(increment, { command: true, mutationKey })
`I am a bot designed to delete any Twitter/X and Meta links along with corresponding reformatting services\\. I now check embedded links\\!`, .toPromise()
{ .then(async () => {
parse_mode: "MarkdownV2", if (ctx.msg) {
reply_parameters: { message_id: ctx.msg.message_id } // Replies to the message.
} await ctx.reply(
); `I am a bot designed to delete any Twitter/X and Meta links along with corresponding reformatting services\\. I now check embedded links\\!`,
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\\.`, parse_mode: "MarkdownV2",
{ reply_parameters: { message_id: ctx.msg.message_id }
parse_mode: "MarkdownV2", }
reply_parameters: { message_id: ctx.msg.message_id } );
} 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\\.`,
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\\.`, parse_mode: "MarkdownV2",
{ reply_parameters: { message_id: ctx.msg.message_id }
parse_mode: "MarkdownV2", }
reply_parameters: { message_id: ctx.msg.message_id } );
} 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\\.`,
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\\.`, parse_mode: "MarkdownV2",
{ reply_parameters: { message_id: ctx.msg.message_id }
parse_mode: "MarkdownV2", }
reply_parameters: { message_id: ctx.msg.message_id } );
} 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\\.`,
{
parse_mode: "MarkdownV2",
reply_parameters: { message_id: ctx.msg.message_id }
}
);
}
});
} }
} }
); );

View File

@@ -3,132 +3,188 @@ 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/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";
const composer = new Composer<Context>(); const composer = new Composer<Context>();
const feature = composer.chatType(["group", "supergroup"]); const feature = composer.chatType(["group", "supergroup"]);
const mutationKey = process.env.GRAPHQL_MUTATION_KEY || "";
/** /**
* 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 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, mutationKey });
if (ctx.chat && ctx.msg) { if (ctx.chat && ctx.msg) {
// Pulling the group IDs from the env variables. const groupName = ctx.chat?.title || "";
const GROUP_IDS = process.env.GROUP_IDS const groupID = ctx.chat?.id.toString() || "";
? process.env.GROUP_IDS.split(",") const groupUsername = ctx.chat?.username || "";
: undefined; let deletedLinks = 0;
if (ctx.chat && ctx.msg) { const username = ctx.msg.from?.username;
if (GROUP_IDS !== undefined) {
// Checking if the message is from a whitelisted group.
const groupID = ctx.chat.id;
const flag = GROUP_IDS.includes(`${groupID}`);
const username = ctx.msg.from?.username;
if (flag) { // 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;
: 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;
: null;
// 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 (captionEmbeds !== null && captionEmbeds.length) {
const metaLinks = captionEmbeds.filter(({ url }) => const metaLinks = captionEmbeds.filter(({ url }) => metaLinkCheck(url));
metaLinkCheck(url) const twitterLinks = captionEmbeds.filter(({ url }) =>
); twitterLinkCheck(url)
const twitterLinks = captionEmbeds.filter(({ url }) => );
twitterLinkCheck(url)
);
// Handle action and response if both meta and Twitter/X links are detected. // Handle action and response if both meta and Twitter/X links are detected.
if (metaLinks.length && twitterLinks.length) { if (metaLinks.length && twitterLinks.length) {
// Deletes the offending message. // Deletes the offending message.
ctx.msg.delete(); ctx.msg.delete();
deletedLinks++;
await urql
.mutation(increment, { link: true, mutationKey })
.toPromise()
.then(async () => {
if (ctx.msg) {
// Replies to the user informing them of the action. // Replies to the user informing them of the action.
return await ctx.reply( 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\\.`, `@${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" } { parse_mode: "MarkdownV2" }
); );
} }
});
}
// Handle action and response if only meta links are detected. // Handle action and response if only meta links are detected.
if (metaLinks.length) { if (metaLinks.length) {
// Deletes the offending message. // Deletes the offending message.
ctx.msg.delete(); ctx.msg.delete();
deletedLinks++;
await urql
.mutation(increment, { link: true, mutationKey })
.toPromise()
.then(async () => {
if (ctx.msg) {
// Replies to the user informing them of the action. // Replies to the user informing them of the action.
return 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\\\n\nIf this was forwarded from a channel consider forwarding without the caption so the media isn't 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\\\n\nIf this was forwarded from a channel consider forwarding without the caption so the media isn't deleted\\.`,
{ parse_mode: "MarkdownV2" } { parse_mode: "MarkdownV2" }
); );
} }
});
}
// Handle action and response if only Twitter/X links are detected. // Handle action and response if only Twitter/X links are detected.
if (twitterLinks.length) { if (twitterLinks.length) {
// Deletes the offending message. // Deletes the offending message.
ctx.msg.delete(); ctx.msg.delete();
deletedLinks++;
await urql
.mutation(increment, { link: true, mutationKey })
.toPromise()
.then(async () => {
if (ctx.msg) {
// Replies to the user informing them of the action. // Replies to the user informing them of the action.
return 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\\.\n\nIf this was forwarded from a channel consider forwarding without the caption so the media isn't 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\\.\n\nIf this was forwarded from a channel consider forwarding without the caption so the media isn't deleted\\.`,
{ parse_mode: "MarkdownV2" } { 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)); // If the embeds array isn't empty filter through them to check if any is a Twitter/X or Meta url.
const twitterLinks = embeds.filter(({ url }) => if (embeds !== null && embeds.length) {
twitterLinkCheck(url) 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. // Handle action and response if both meta and Twitter/X links are detected.
if (metaLinks.length && twitterLinks.length) { if (metaLinks.length && twitterLinks.length) {
// Deletes the offending message. // Deletes the offending message.
ctx.msg.delete(); ctx.msg.delete();
deletedLinks++;
await urql
.mutation(increment, { link: true, mutationKey })
.toPromise()
.then(async () => {
if (ctx.msg) {
// Replies to the user informing them of the action. // Replies to the user informing them of the action.
return await ctx.reply( 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\\.`, `@${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" } { parse_mode: "MarkdownV2" }
); );
} }
});
}
// Handle action and response if only meta links are detected. // Handle action and response if only meta links are detected.
if (metaLinks.length) { if (metaLinks.length) {
// Deletes the offending message. // Deletes the offending message.
ctx.msg.delete(); ctx.msg.delete();
deletedLinks++;
await urql
.mutation(increment, { link: true, mutationKey })
.toPromise()
.then(async () => {
if (ctx.msg) {
// Replies to the user informing them of the action. // Replies to the user informing them of the action.
return 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" }
); );
} }
});
}
// Handle action and response if only Twitter/X links are detected. // Handle action and response if only Twitter/X links are detected.
if (twitterLinks.length) { if (twitterLinks.length) {
// Deletes the offending message. // Deletes the offending message.
ctx.msg.delete(); ctx.msg.delete();
deletedLinks++;
await urql
.mutation(increment, { link: true, mutationKey })
.toPromise()
.then(async () => {
if (ctx.msg) {
// Replies to the user informing them of the action. // Replies to the user informing them of the action.
return 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" }
); );
} }
} });
}
} }
if (!GROUP_IDS) { if (deletedLinks) {
console.info("Group IDS:", process.env.GROUP_IDS); return await urql
return await ctx.reply( .mutation(addGroup, {
`There was a problem retrieving the whitelist\\. Check the env variables and try again\\.`, groupID,
{ groupName,
parse_mode: "MarkdownV2", groupUsername,
reply_parameters: { message_id: ctx.msg.message_id } mutationKey
} })
); .toPromise()
.then(() =>
urql.mutation(incrementGroup, {
groupID,
linksDeleted: deletedLinks,
mutationKey
})
);
} }
} }
} }

View File

@@ -1,54 +0,0 @@
import { Composer } from "grammy";
import type { Context } from "#root/bot/context.js";
import { logHandle } from "#root/bot/helpers/logging.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 "/botInfo"
*/
feature.command(
"getGroupID",
logHandle("get-group-id"),
async (ctx: Context) => {
// Pulling the group IDs from the env variables.
const GROUP_IDS = process.env.GROUP_IDS
? process.env.GROUP_IDS.split(",")
: undefined;
// Checks if the context has a chat, msg, and from property.
if (ctx.chat && ctx.msg && ctx.msg.from) {
if (GROUP_IDS !== undefined) {
const groupID = ctx.chat.id;
const flag = GROUP_IDS.includes(`${groupID}`);
// Checks if the group is whitelisted
if (flag) {
const chatMember = await ctx.getChatMember(ctx.msg.from.id);
// Checks if the user is an admin
if (["creator", "administrator"].includes(chatMember.status)) {
return await ctx.reply(`The group id is: \`${groupID}\``, {
parse_mode: "MarkdownV2",
reply_parameters: { message_id: ctx.msg.message_id }
});
}
// Send a default message if the user is not an admin
return await ctx.reply(
`You have to be an admin to use this command\\!`,
{
parse_mode: "MarkdownV2",
reply_parameters: { message_id: ctx.msg.message_id }
}
);
}
}
}
}
);
export { composer as getGroupIDCommand };

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"]);
const mutationKey = process.env.GRAPHQL_MUTATION_KEY || "";
/**
* 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) => {
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,124 +1,85 @@
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/main.js";
import increment from "#root/lib/graphql/mutations/incrementMutation.js";
const composer = new Composer<Context>(); const composer = new Composer<Context>();
const feature = composer.chatType(["group", "supergroup", "private"]); const feature = composer.chatType(["group", "supergroup", "private"]);
const mutationKey = process.env.GRAPHQL_MUTATION_KEY || "";
/** /**
* 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 "/help" * The trigger is the command "/help"
*/ */
feature.hears(/^\/help/, logHandle("help"), async (ctx: Context) => { feature.hears(/^\/help/, logHandle("help"), async (ctx: Context) => {
const GROUP_IDS = process.env.GROUP_IDS await urql.mutation(increment, { trigger: true, mutationKey });
? process.env.GROUP_IDS.split(",")
: undefined; // const GROUP_IDS = process.env.GROUP_IDS
// ? process.env.GROUP_IDS.split(",")
// : undefined;
// 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) {
// Checks if the chat is private await urql
if (ctx.chat.type === "private") { .mutation(increment, { command: true, mutationKey })
// Responds with message. .toPromise()
await ctx.reply( .then(async () => {
`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\\.`, if (ctx.msg && ctx.chat && ctx.msg.from) {
{ // Checks if the chat is private
parse_mode: "MarkdownV2", if (ctx.chat.type === "private") {
reply_parameters: { message_id: ctx.msg.message_id } // Responds with message.
} 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\\.`,
} {
parse_mode: "MarkdownV2",
// Checks if chat is a group. reply_parameters: { message_id: ctx.msg.message_id }
if (ctx.chat.type !== "private") { }
const chatMember = await ctx.getChatMember(ctx.msg.from.id); );
// Checks if the user is an admin.
if (["creator", "administrator"].includes(chatMember.status)) {
// Respond with message.
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\\.`,
{
parse_mode: "MarkdownV2",
reply_parameters: { message_id: ctx.msg.message_id }
} }
);
} else {
/**
* Doesn't respond to regular users in groups. This is to prevent users spamming the command.
* Especially since this one includes some political opinions/messages.
*/
return;
}
}
await ctx.reply( // Checks if chat is a group.
`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\\.`, if (ctx.chat.type !== "private") {
{ const chatMember = await ctx.getChatMember(ctx.msg.from.id);
parse_mode: "MarkdownV2",
reply_parameters: { message_id: ctx.msg.message_id }
}
);
return await ctx.reply( // Checks if the user is an admin.
`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\\!`, if (["creator", "administrator"].includes(chatMember.status)) {
{ // Respond with message.
parse_mode: "MarkdownV2", await ctx.reply(
reply_parameters: { message_id: ctx.msg.message_id } `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\\.`,
} {
); parse_mode: "MarkdownV2",
reply_parameters: { message_id: ctx.msg.message_id }
}
);
} else {
/**
* Doesn't respond to regular users in groups. This is to prevent users spamming the command.
* Especially since this one includes some political opinions/messages.
*/
return;
}
}
// Checks if the whitelist is set up. await ctx.reply(
// if (GROUP_IDS !== undefined) { `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\\.`,
// const groupID = ctx.chat.id; {
// const flag = GROUP_IDS.includes(`${groupID}`); parse_mode: "MarkdownV2",
reply_parameters: { message_id: ctx.msg.message_id }
}
);
// // Checks if the chat is in the whitelist. return await ctx.reply(
// if (flag) { `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\\!`,
// // Responds with the command list. {
// return await ctx.reply( parse_mode: "MarkdownV2",
// `**Here are the available commands you can use:**\n\n/getGroupID _ADMIN ONLY_ \\- Replies with the ID of the group I am in\\.\n\n/isWKCGRoup _ADMIN ONLY_ \\- Checks if this group's ID is on the whitelist and responds accordingly\\.\n\n/botInfo _Private Command_\\- Info about me and how to fork me to deploy for your own use\\.\n\n/help\\- Displays this help message\\.`, reply_parameters: { message_id: ctx.msg.message_id }
// { }
// parse_mode: "MarkdownV2", );
// reply_parameters: { message_id: ctx.msg.message_id } }
// } });
// );
// }
// Checks if the chat is not in the whitelist.
// if (!flag) {
// await ctx
// // Responds with the command list with a warning that the available commands are limited.
// .reply(
// `**Since this is not a whitelisted group the features are limited\\!\\!**\n\nHere are the available commands you can use:\n\n/isWKCGRoup _ADMIN ONLY_\\- Checks if this group's ID is on the whitelist and responds accordingly\\.\n\n/botInfo _Private Command_\\- Info about me and how to fork me to deploy for your own use\\.\n\n/help \\- Displays this help message\\.`,
// {
// parse_mode: "MarkdownV2",
// reply_parameters: { message_id: ctx.msg.message_id }
// }
// )
// .then(() => {});
// // Sends a follow-up message with information about the bot.
// return await ctx.reply(
// `This group is NOT in the whitelisted and is NOT a part of the WKC Telegram groups/communities\\. I am a bot designed to delete any Twitter/X and Meta links along with corresponding reformatting services within whitelisted groups\\. You can fork me from this link: https://github\\.com/lucid\\-creations\\-media/no\\-twitter\\-bot and deploy me for use in your own groups\\!`,
// {
// parse_mode: "MarkdownV2",
// reply_parameters: { message_id: ctx.msg.message_id }
// }
// );
// }
// }
// Checks if the whitelist is not set up.
// if (!GROUP_IDS) {
// // Sends a warning that the whitelist is not set up or the bot cannot access it.
// return await ctx.reply(
// `There was a problem retrieving the whitelist\\. Check the env variables and try again\\.`,
// {
// parse_mode: "MarkdownV2",
// reply_parameters: { message_id: ctx.msg.message_id }
// }
// );
// }
} }
}); });

View File

@@ -1,80 +0,0 @@
import { Composer } from "grammy";
import type { Context } from "#root/bot/context.js";
import { logHandle } from "#root/bot/helpers/logging.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 "/isWKCGroup"
*/
feature.command(
"isWKCGroup",
logHandle("is-WKC-group"),
async (ctx: Context) => {
// Pulling the group IDs from the env variables.
const GROUP_IDS = process.env.GROUP_IDS
? process.env.GROUP_IDS.split(",")
: undefined;
// Checking that context has chat, msg, and msg.from properties
if (ctx.chat && ctx.msg && ctx.msg.from) {
const groupID = ctx.chat.id;
const chatMember = await ctx.getChatMember(ctx.msg.from.id);
// Checking if the user is an admin.
if (["creator", "administrator"].includes(chatMember.status)) {
if (GROUP_IDS !== undefined) {
const flag = GROUP_IDS.includes(`${groupID}`);
// Checking if the group is whitelisted.
if (flag) {
// Confirming that the group is whitelisted and should be deleting detected messages.
await ctx.reply(
`This group is in the whitelisted and is a part of the WKC Telegram groups/communities\\. I should be deleting any Twitter/X and Meta links along with corresponding reformatting services within this group\\.`,
{
parse_mode: "MarkdownV2",
reply_parameters: { message_id: ctx.msg.message_id }
}
);
}
if (!flag) {
// Informing the user that features are blocked because the group is not in the whitelist.
await ctx.reply(
`This group is NOT in the whitelisted and is NOT a part of the WKC Telegram groups/communities\\. I am a bot designed to delete any Twitter/X and Meta links along with corresponding reformatting services within groups\\. You can fork me from this link: https://github\\.com/lucid\\-creations\\-media/no\\-twitter\\-bot and deploy me for use in your own groups\\!`,
{
parse_mode: "MarkdownV2",
reply_parameters: { message_id: ctx.msg.message_id }
}
);
}
}
if (!GROUP_IDS) {
// Altering that the whitelist is not set or the bot cannot access it.
await ctx.reply(
`There was a problem retrieving the whitelist\\. Check the env variables and try again\\.`,
{
parse_mode: "MarkdownV2",
reply_parameters: { message_id: ctx.msg.message_id }
}
);
}
} else {
// Informing the user that they need to be an admin to use this command.
await ctx.reply(
`You have to be an admin of this group to use this command\\!`,
{
parse_mode: "MarkdownV2",
reply_parameters: { message_id: ctx.msg.message_id }
}
);
}
}
}
);
export { composer as isWKCGroup };

View File

@@ -2,11 +2,17 @@ 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/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";
const composer = new Composer<Context>(); const composer = new Composer<Context>();
const feature = composer.chatType(["group", "supergroup"]); const feature = composer.chatType(["group", "supergroup"]);
const mutationKey = process.env.GRAPHQL_MUTATION_KEY || "";
/** /**
* 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 uses the global Twitter regex to detect Twitter and X links within messages. * The trigger uses the global Twitter regex to detect Twitter and X links within messages.
@@ -15,52 +21,46 @@ feature.hears(
metaRegex, metaRegex,
logHandle("blacklist-detection-meta"), logHandle("blacklist-detection-meta"),
async (ctx: Context) => { async (ctx: Context) => {
// Pulling the group IDs from the env variables. await urql.mutation(increment, { trigger: true, mutationKey });
const GROUP_IDS = process.env.GROUP_IDS
? process.env.GROUP_IDS.split(",")
: undefined;
if (ctx.chat && ctx.msg) { if (ctx.chat && ctx.msg) {
const username = ctx.msg.from?.username; const username = ctx.msg.from?.username;
// Deletes the offending message. // Deletes the offending message.
ctx.msg.delete(); ctx.msg.delete();
// Replies to the user informing them of the action.
return await ctx.reply( return await urql
`@${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\\.`, .mutation(increment, { link: true, mutationKey })
{ parse_mode: "MarkdownV2" } .toPromise()
); .then(async () => {
if (ctx.msg && ctx.chat) {
// 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" }
);
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
})
);
}
});
} }
// if (ctx.chat && ctx.msg) {
// if (GROUP_IDS !== undefined) {
// // Checking if the message is from a whitelisted group.
// const groupID = ctx.chat.id;
// const flag = GROUP_IDS.includes(`${groupID}`);
// const username = ctx.msg.from?.username;
// if (flag) {
// // 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\\.`,
// { parse_mode: "MarkdownV2" }
// );
// }
// }
// // If the env variables are misconfigured an error is sent to the group.
// if (!GROUP_IDS) {
// console.info("Group IDS:", process.env.GROUP_IDS);
// return await ctx.reply(
// `There was a problem retrieving the whitelist\\. Check the env variables and try again\\.`,
// {
// parse_mode: "MarkdownV2",
// reply_parameters: { message_id: ctx.msg.message_id }
// }
// );
// }
// }
} }
); );

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"]);
const mutationKey = process.env.GRAPHQL_MUTATION_KEY || "";
/**
* 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) => {
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

@@ -2,11 +2,17 @@ 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/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";
const composer = new Composer<Context>(); const composer = new Composer<Context>();
const feature = composer.chatType(["group", "supergroup"]); const feature = composer.chatType(["group", "supergroup"]);
const mutationKey = process.env.GRAPHQL_MUTATION_KEY || "";
/** /**
* 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 uses the global Twitter regex to detect Twitter and X links within messages. * The trigger uses the global Twitter regex to detect Twitter and X links within messages.
@@ -15,57 +21,46 @@ feature.hears(
twitterRegex, twitterRegex,
logHandle("blacklist-detection-twitter"), logHandle("blacklist-detection-twitter"),
async (ctx: Context) => { async (ctx: Context) => {
// Pulling the group IDs from the env variables. await urql.mutation(increment, { trigger: true, mutationKey });
const GROUP_IDS = process.env.GROUP_IDS
? process.env.GROUP_IDS.split(",")
: undefined;
if (ctx.chat && ctx.msg) { if (ctx.chat && ctx.msg) {
const username = ctx.msg.from?.username; const username = ctx.msg.from?.username;
// Deletes the offending message. // Deletes the offending message.
ctx.msg.delete(); ctx.msg.delete();
// Replies to the user informing them of the action.
return await ctx.reply( return await urql
`@${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\\.`, .mutation(increment, { link: true, mutationKey })
{ parse_mode: "MarkdownV2" } .toPromise()
); .then(async () => {
if (ctx.msg && ctx.chat) {
// 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" }
);
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
})
);
}
});
} }
// if (ctx.chat && ctx.msg) {
// if (GROUP_IDS !== undefined) {
// // Checking if the message is from a whitelisted group.
// const groupID = ctx.chat.id;
// const flag = GROUP_IDS.includes(`${groupID}`);
// const username = ctx.msg.from?.username;
// if (flag) {
// // 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 the env variables are misconfigured an error is sent to the group.
// if (!GROUP_IDS) {
// console.info(
// "Group IDS:",
// process.env.GROUP_IDS,
// GROUP_IDS,
// GROUP_IDS !== undefined
// );
// return await ctx.reply(
// `There was a problem retrieving the whitelist\\. Check the env variables and try again\\.`,
// {
// parse_mode: "MarkdownV2",
// reply_parameters: { message_id: ctx.msg.message_id }
// }
// );
// }
// }
} }
); );

View File

@@ -1,20 +1,34 @@
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/main.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");
const mutationKey = process.env.GRAPHQL_MUTATION_KEY || "";
/** /**
* 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"), ctx => { feature.command("start", logHandle("command-start"), async ctx => {
// Responds with information about the bot. await urql.mutation(increment, { trigger: true, mutationKey });
return ctx.reply(
`Welcome\\! I am a bot created by Lucid for [Werewolf Kid Creations](https://werewolfkid.monster/) I am designed to delete any Twitter/X and Meta links along with corresponding reformatting services within groups\\. I also check embedded links and forwarded messages\\! I am currently in a public beta mode\\! 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\\.`, await urql
{ parse_mode: "MarkdownV2" } .mutation(increment, { command: true, mutationKey })
); .toPromise()
.then(async () => {
if (ctx.msg) {
// Responds with information about the bot.
return ctx.reply(
`Welcome\\! I am a bot created by Lucid for [Werewolf Kid Creations](https://werewolfkid.monster/) I am designed to delete any Twitter/X and Meta links along with corresponding reformatting services within groups\\. I also check embedded links and forwarded messages\\! I am currently in a public beta mode\\! 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\\.`,
{ parse_mode: "MarkdownV2" }
);
}
});
}); });
export { composer as welcomeFeature }; export { composer as welcomeFeature };

View File

@@ -16,10 +16,10 @@ import { MemorySessionStorage, Bot as TelegramBot } from "grammy";
import { twitterBlacklist } from "./features/twitterBlacklist.js"; import { twitterBlacklist } from "./features/twitterBlacklist.js";
import { metaBlacklist } from "./features/metaBlacklist.js"; import { metaBlacklist } from "./features/metaBlacklist.js";
import { botInfoCommand } from "./features/botInfoCommand.js"; import { botInfoCommand } from "./features/botInfoCommand.js";
import { getGroupIDCommand } from "./features/getGroupIDCommand.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 { isWKCGroup } from "./features/isWKCGroup.js"; import { registerGroup } from "./features/registerBotCommand.js";
import { getGroupStats } from "./features/getGroupStatsCommand.js";
interface Dependencies { interface Dependencies {
config: Config; config: Config;
@@ -71,9 +71,9 @@ export function createBot(
// Commands // Commands
protectedBot.use(botInfoCommand); protectedBot.use(botInfoCommand);
// protectedBot.use(getGroupIDCommand);
// protectedBot.use(isWKCGroup);
protectedBot.use(helpCommand); protectedBot.use(helpCommand);
protectedBot.use(registerGroup);
protectedBot.use(getGroupStats);
// Blacklist Feature // Blacklist Feature
protectedBot.use(twitterBlacklist); protectedBot.use(twitterBlacklist);

View File

@@ -0,0 +1,22 @@
import { gql } from "@urql/core";
const addGroup = gql`
mutation addGroup(
$groupID: String!
$groupName: String!
$groupUsername: String
$mutationKey: String
) {
addGroup(
groupID: $groupID
groupName: $groupName
groupUsername: $groupUsername
mutationKey: $mutationKey
) {
name
username
}
}
`;
export default addGroup;

View File

@@ -0,0 +1,21 @@
import { gql } from "@urql/core";
const incrementGroup = gql`
mutation incrementGroup(
$groupID: String!
$linksDeleted: Int!
$mutationKey: String
) {
incrementGroup(
groupID: $groupID
linksDeleted: $linksDeleted
mutationKey: $mutationKey
) {
name
username
linksDeleted
}
}
`;
export default incrementGroup;

View File

@@ -0,0 +1,22 @@
import { gql } from "@urql/core";
const increment = gql`
mutation increment(
$command: Boolean
$link: Boolean
$trigger: Boolean
$mutationKey: String
) {
increment(
command: $command
link: $link
trigger: $trigger
mutationKey: $mutationKey
) {
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

@@ -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 { 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],
fetchOptions: {
headers: {
"x-api-key": process.env?.GRAPHQL_API_TOKEN || ""
}
}
});

View File

@@ -5,6 +5,18 @@ __metadata:
version: 8 version: 8
cacheKey: 10c0 cacheKey: 10c0
"@0no-co/graphql.web@npm:^1.0.13":
version: 1.2.0
resolution: "@0no-co/graphql.web@npm:1.2.0"
peerDependencies:
graphql: ^14.0.0 || ^15.0.0 || ^16.0.0
peerDependenciesMeta:
graphql:
optional: true
checksum: 10c0/4eed600962bfab42afb49cddcfb31a47b00502f59707609cf160559920ce0f5cf8874791e4cafc465ede30ae291992f3f892bc757b2a989e80e50e358f71c518
languageName: node
linkType: hard
"@antfu/eslint-config@npm:5.4.1": "@antfu/eslint-config@npm:5.4.1":
version: 5.4.1 version: 5.4.1
resolution: "@antfu/eslint-config@npm:5.4.1" resolution: "@antfu/eslint-config@npm:5.4.1"
@@ -1063,6 +1075,16 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@urql/core@npm:^6.0.1":
version: 6.0.1
resolution: "@urql/core@npm:6.0.1"
dependencies:
"@0no-co/graphql.web": "npm:^1.0.13"
wonka: "npm:^6.3.2"
checksum: 10c0/44ff0d12dcef1e47338a9ff1217759d1124fa66eec1eec21ff9622e44c179b9d66fa78f462f195bfd8b790b04609abbe5a0674cbfcb0bc6d9c6fe6223d7d7b5b
languageName: node
linkType: hard
"@vitest/eslint-plugin@npm:^1.3.12": "@vitest/eslint-plugin@npm:^1.3.12":
version: 1.3.13 version: 1.3.13
resolution: "@vitest/eslint-plugin@npm:1.3.13" resolution: "@vitest/eslint-plugin@npm:1.3.13"
@@ -3744,6 +3766,7 @@ __metadata:
"@grammyjs/types": "npm:3.22.2" "@grammyjs/types": "npm:3.22.2"
"@hono/node-server": "npm:1.19.4" "@hono/node-server": "npm:1.19.4"
"@types/node": "npm:^24.5.2" "@types/node": "npm:^24.5.2"
"@urql/core": "npm:^6.0.1"
callback-data: "npm:1.1.1" callback-data: "npm:1.1.1"
eslint: "npm:^9.36.0" eslint: "npm:^9.36.0"
grammy: "npm:1.38.2" grammy: "npm:1.38.2"
@@ -4976,6 +4999,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"wonka@npm:^6.3.2":
version: 6.3.5
resolution: "wonka@npm:6.3.5"
checksum: 10c0/044fe5ae26c0a32b0a1603cc0ed71ede8c9febe5bb3adab4fad5e088ceee600a84a08d0deb95a72189bbaf0d510282d183b6fb7b6e9837e7a1c9b209f788dd07
languageName: node
linkType: hard
"word-wrap@npm:^1.2.5": "word-wrap@npm:^1.2.5":
version: 1.2.5 version: 1.2.5
resolution: "word-wrap@npm:1.2.5" resolution: "word-wrap@npm:1.2.5"