Compare commits

..

35 Commits

Author SHA1 Message Date
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
2ba2cec399 Updated welcome message.
All checks were successful
Main / build-and-push-docker-image (20.x) (push) Successful in 3m9s
2025-10-21 16:57:29 -04:00
20a72ed0f3 Merge pull request 'Updated welcome message to include the requirements for the bot.' (#80) from new-welcome into main
All checks were successful
Main / build-and-push-docker-image (20.x) (push) Successful in 3m13s
Reviewed-on: #80
2025-10-21 19:50:23 +00:00
82476d4e1f Updated welcome message to include the requirements for the bot.
All checks were successful
Main / build-and-push-docker-image (20.x) (pull_request) Successful in 3m2s
2025-10-21 15:46:06 -04:00
845c5cd7df Merge pull request 'Secured command to prevent spam from regular users.' (#79) from secure into main
All checks were successful
Main / build-and-push-docker-image (20.x) (push) Successful in 5m5s
Reviewed-on: #79
2025-10-21 19:12:42 +00:00
d71e736bee Secured command to prevent spam from regular users.
All checks were successful
Main / build-and-push-docker-image (20.x) (pull_request) Successful in 3m1s
2025-10-21 15:09:21 -04:00
10d8f02160 Merge pull request 'Updated grammar. Added new messages. FUCK FASCISM!' (#78) from grammar into main
All checks were successful
Main / build-and-push-docker-image (20.x) (push) Successful in 2m57s
Reviewed-on: #78
2025-10-21 18:42:04 +00:00
e0754cd03b Updated grammar. Added new messages. FUCK FASCISM!
All checks were successful
Main / build-and-push-docker-image (20.x) (pull_request) Successful in 3m8s
2025-10-21 14:38:25 -04:00
10c84e1582 Changed bot features to .hears and used regex to detect commands.
All checks were successful
Main / build-and-push-docker-image (20.x) (push) Successful in 3m10s
2025-10-21 14:13:40 -04:00
b8d966410a Merge pull request 'fix-startup' (#77) from fix-startup into main
All checks were successful
Main / build-and-push-docker-image (20.x) (push) Successful in 3m6s
Reviewed-on: #77
2025-10-21 04:38:49 +00:00
8021a9561e Updated setTimeout
Some checks are pending
Main / build-and-push-docker-image (20.x) (pull_request) Waiting to run
2025-10-21 00:38:18 -04:00
1b38aad76a Created a timeout for starting webhook mode to allow traefik to expose the webhook server and assign an SSL cert.
All checks were successful
Main / build-and-push-docker-image (20.x) (push) Successful in 3m7s
2025-10-21 00:03:21 -04:00
bfc0f1e59a Created a timeout for starting webhook mode to allow traefik to expose the webhook server and assign an SSL cert.
All checks were successful
Main / build-and-push-docker-image (20.x) (push) Successful in 3m11s
2025-10-20 23:53:37 -04:00
57b64e9b5e Created a timeout for starting webhook mode to allow traefik to expose the webhook server and assign an SSL cert.
All checks were successful
Main / build-and-push-docker-image (20.x) (push) Successful in 3m28s
2025-10-20 23:45:20 -04:00
aa0be3b56c Added Docker Hub login 2025-10-20 23:42:33 -04:00
10126edd2b Merge pull request 'public-beta' (#75) from public-beta into main
Some checks failed
Main / build-and-push-docker-image (20.x) (push) Failing after 3m7s
Reviewed-on: #75
2025-10-21 00:12:44 +00:00
f7751b23a8 Updated commands to not check for group IDs.
All checks were successful
Main / build-and-push-docker-image (20.x) (pull_request) Successful in 3m13s
2025-10-20 20:07:28 -04:00
ffad9bfe90 Updated website url. 2025-10-20 20:07:09 -04:00
d7e71600a6 Update .on to .command. Added more information about the bot. 2025-10-20 20:06:56 -04:00
c2ea85acd1 Update .on to .command. Added more information about the bot. 2025-10-20 20:06:40 -04:00
c16c46ceeb Update .hears to .command 2025-10-20 20:06:21 -04:00
1f22a05944 Remove unused commands. 2025-10-20 20:05:45 -04:00
1d7d38927d update bot info 2025-10-20 20:05:31 -04:00
191c1ceb91 Merge pull request 'docker-swarm' (#74) from docker-swarm into main
All checks were successful
Main / build-and-push-docker-image (20.x) (push) Successful in 3m8s
Reviewed-on: #74
2025-10-15 17:34:42 +00:00
98376d93d6 update workflow order
All checks were successful
Main / build-and-push-docker-image (20.x) (pull_request) Successful in 48s
2025-10-15 13:32:18 -04:00
5f55806497 Update compose files to use env variables and the image. 2025-10-15 13:32:08 -04:00
370f601bd8 Merge pull request 'Upgrade dependencies' (#70) from upgrades into main
All checks were successful
Main / build-and-push-docker-image (20.x) (push) Successful in 2m46s
Reviewed-on: lcm/no-twitter-bot#70
2025-09-29 05:15:33 +00:00
3ab71df73f Upgrade dependencies
All checks were successful
Main / build-and-push-docker-image (20.x) (pull_request) Successful in 45s
2025-09-29 01:12:27 -04:00
2a8a054385 Merge pull request 'admin-protections' (#69) from admin-protections into main
All checks were successful
Main / build-and-push-docker-image (20.x) (push) Successful in 45s
Reviewed-on: lcm/no-twitter-bot#69
2025-09-29 05:10:49 +00:00
21 changed files with 877 additions and 747 deletions

View File

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

View File

@@ -8,7 +8,7 @@ on:
env:
REGISTRY: gitea.lucids-cove.duckdns.org
OWNER: lcm
OWNER: wkc
IMAGE_NAME: no-twitter-bot
jobs:
@@ -18,7 +18,6 @@ jobs:
matrix:
node-version: [20.x]
steps:
- uses: actions/checkout@v4
- name: "Base requirements"
run: |
# packages
@@ -26,6 +25,7 @@ jobs:
# ansible collections
ansible-galaxy collection install community.general --force
ansible-galaxy collection install ansible.posix --force
- uses: actions/checkout@v4
- name: Enable Corepack
run: npm install -g corepack && corepack enable
- name: Log into registry ${{ env.REGISTRY }}
@@ -34,6 +34,8 @@ jobs:
registry: ${{ env.REGISTRY }}
username: ${{ env.OWNER }}
password: ${{ secrets.TOKEN }}
- name: Docker Hub Login
run: echo "${{ secrets.DOCKER_HUB_TOKEN }}" | docker login -u "${{ secrets.DOCKER_HUB_USERNAME }}" --password-stdin
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Setup Docker buildx

View File

@@ -1,9 +1,21 @@
services:
bot:
no-twitter-bot:
image: gitea.lucids-cove.duckdns.org/wkc/no-twitter-bot:latest
container_name: no-twitter-bot
environment:
- BOT_TOKEN=${BOT_TOKEN}
- BOT_MODE=${BOT_MODE}
- LOG_LEVEL=${LOG_LEVEL}
- DEBUG=${DEBUG}
- BOT_WEBHOOK=${BOT_WEBHOOK}
- BOT_WEBHOOK_SECRET=${BOT_WEBHOOK_SECRET}
- SERVER_HOST=${SERVER_HOST}
- SERVER_PORT=${SERVER_PORT}
- BOT_ADMINS=${BOT_ADMINS}
- GROUP_IDS=${GROUP_IDS}
- GRAPHQL_URL=${GRAPHQL_URL}
ports:
- "3000:80"
volumes:
- ".:/usr/src"
env_file:
- .env.bot.dev
command: npm run dev

View File

@@ -1,4 +1,19 @@
services:
bot:
env_file:
- .env.bot.prod
no-twitter-bot:
image: gitea.lucids-cove.duckdns.org/wkc/no-twitter-bot:latest
container_name: no-twitter-bot
# env_file: stack.env
environment:
- BOT_TOKEN=${BOT_TOKEN}
- BOT_MODE=${BOT_MODE}
- LOG_LEVEL=${LOG_LEVEL}
- DEBUG=${DEBUG}
- BOT_WEBHOOK=${BOT_WEBHOOK}
- BOT_WEBHOOK_SECRET=${BOT_WEBHOOK_SECRET}
- SERVER_HOST=${SERVER_HOST}
- SERVER_PORT=${SERVER_PORT}
- BOT_ADMINS=${BOT_ADMINS}
- GROUP_IDS=${GROUP_IDS}
- GRAPHQL_URL=${GRAPHQL_URL}
build:
context: .

View File

@@ -1,6 +1,19 @@
services:
bot:
no-twitter-bot:
image: gitea.lucids-cove.duckdns.org/wkc/no-twitter-bot:latest
container_name: no-twitter-bot
env_file: stack.env
# env_file: stack.env
environment:
- BOT_TOKEN=${BOT_TOKEN}
- BOT_MODE=${BOT_MODE}
- LOG_LEVEL=${LOG_LEVEL}
- DEBUG=${DEBUG}
- BOT_WEBHOOK=${BOT_WEBHOOK}
- BOT_WEBHOOK_SECRET=${BOT_WEBHOOK_SECRET}
- SERVER_HOST=${SERVER_HOST}
- SERVER_PORT=${SERVER_PORT}
- BOT_ADMINS=${BOT_ADMINS}
- GROUP_IDS=${GROUP_IDS}
- GRAPHQL_URL=${GRAPHQL_URL}
build:
context: .

View File

@@ -1,14 +1,14 @@
{
"name": "no-twitter-bot",
"type": "module",
"version": "2.0.0",
"version": "3.1.0",
"private": true,
"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.",
"imports": {
"#root/*": "./build/src/*"
},
"author": "Lucid Kobold (Lucid Creations Media) <social@lucidcreations.media>",
"author": "Lucid (Werewolf Kid Creations) <social@werewolfkid.monster>",
"license": "MIT",
"engines": {
"node": ">=20.0.0",
@@ -32,24 +32,25 @@
"@grammyjs/i18n": "1.1.2",
"@grammyjs/parse-mode": "1.11.1",
"@grammyjs/runner": "2.0.3",
"@grammyjs/types": "3.22.1",
"@hono/node-server": "1.19.1",
"@grammyjs/types": "3.22.2",
"@hono/node-server": "1.19.4",
"@urql/core": "^6.0.1",
"callback-data": "1.1.1",
"grammy": "1.38.2",
"hono": "4.9.6",
"hono": "4.9.9",
"iso-639-1": "3.1.5",
"pino": "9.9.1",
"pino": "9.12.0",
"pino-pretty": "13.1.1",
"tsx": "4.20.5",
"valibot": "0.42.1"
"tsx": "4.20.6",
"valibot": "1.1.0"
},
"devDependencies": {
"@antfu/eslint-config": "4.19.0",
"@types/node": "^22.18.0",
"eslint": "^9.34.0",
"@antfu/eslint-config": "5.4.1",
"@types/node": "^24.5.2",
"eslint": "^9.36.0",
"husky": "^9.1.7",
"lint-staged": "^15.5.2",
"tsc-watch": "^6.3.1",
"lint-staged": "^16.2.3",
"tsc-watch": "^7.2.0",
"typescript": "^5.9.2"
},
"lint-staged": {

View File

@@ -1,29 +1,70 @@
import { Composer } from "grammy";
import type { Context } from "#root/bot/context.js";
import { logHandle } from "#root/bot/helpers/logging.js";
import urql from "#root/lib/urql.js";
import increment from "#root/lib/graphql/mutations/incrimentMutation.js";
const composer = new Composer<Context>();
const feature = composer.chatType(["private"]);
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(
"/botInfo",
/^\/botInfo/,
logHandle("bot-info-command"),
async (ctx: Context) => {
await urql.mutation(increment, { trigger: true });
// Checks if the context includes a message property.
if (ctx.msg) {
// Replies to the message.
return await ctx.reply(
`I am a bot designed to delete any Twitter/X and Meta links along with corresponding reformatting services within whitelisted groups\\. I now check embedded links\\! By default I only work with whitelisted group IDs\\.\n\nYou 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 (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 })
.toPromise()
.then(async () => {
if (ctx.msg) {
// 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\\!`,
{
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\\.`,
{
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\\.`,
{
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,6 +3,10 @@ import type { Context } from "#root/bot/context.js";
import { logHandle } from "#root/bot/helpers/logging.js";
import metaLinkCheck from "#root/lib/metaLinkCheck.js";
import twitterLinkCheck from "#root/lib/twitterLinkCheck.js";
import urql from "#root/lib/urql.js";
import increment from "#root/lib/graphql/mutations/incrimentMutation.js";
import addGroup from "#root/lib/graphql/mutations/addGroupMutation.js";
import incrementGroup from "#root/lib/graphql/mutations/incrementGroupMutation.js";
const composer = new Composer<Context>();
@@ -13,122 +17,166 @@ const feature = composer.chatType(["group", "supergroup"]);
* The trigger is anytime an embedded url is detected.
*/
feature.on("message::url", logHandle("embed-check"), async (ctx: Context) => {
await urql.mutation(increment, { trigger: true });
if (ctx.chat && ctx.msg) {
// Pulling the group IDs from the env variables.
const GROUP_IDS = process.env.GROUP_IDS
? process.env.GROUP_IDS.split(",")
: undefined;
const groupName = ctx.chat?.title;
const groupID = ctx.chat?.id;
const groupUsername = ctx.chat?.username;
let deletedLinks = 0;
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;
const username = ctx.msg.from?.username;
if (flag) {
// Filters every message/caption entity that is a url into a new array.
const embeds = ctx.msg.entities
? ctx.msg.entities.filter(e => e.type === "text_link")
: null;
const captionEmbeds = ctx.msg.caption_entities
? ctx.msg.caption_entities.filter(e => e.type === "text_link")
: null;
// Filters every message/caption entity that is a url into a new array.
const embeds = ctx.msg.entities
? ctx.msg.entities.filter(e => e.type === "text_link")
: null;
const captionEmbeds = ctx.msg.caption_entities
? ctx.msg.caption_entities.filter(e => e.type === "text_link")
: null;
// 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) {
const metaLinks = captionEmbeds.filter(({ url }) =>
metaLinkCheck(url)
);
const twitterLinks = captionEmbeds.filter(({ url }) =>
twitterLinkCheck(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) {
const metaLinks = captionEmbeds.filter(({ url }) => metaLinkCheck(url));
const twitterLinks = captionEmbeds.filter(({ url }) =>
twitterLinkCheck(url)
);
// Handle action and response if both meta and Twitter/X links are detected.
if (metaLinks.length && twitterLinks.length) {
// Deletes the offending message.
ctx.msg.delete();
// Handle action and response if both meta and Twitter/X links are detected.
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.
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\\.`,
{ parse_mode: "MarkdownV2" }
);
}
});
}
// Handle action and response if only meta links are detected.
if (metaLinks.length) {
// Deletes the offending message.
ctx.msg.delete();
// 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.
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\\.`,
{ parse_mode: "MarkdownV2" }
);
}
});
}
// Handle action and response if only Twitter/X links are detected.
if (twitterLinks.length) {
// Deletes the offending message.
ctx.msg.delete();
// 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.
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\\.`,
{ parse_mode: "MarkdownV2" }
);
}
}
// If the embeds array isn't empty filter through them to check if any is a Twitter/X or Meta url.
if (embeds !== null && embeds.length) {
const metaLinks = embeds.filter(({ url }) => metaLinkCheck(url));
const twitterLinks = embeds.filter(({ url }) =>
twitterLinkCheck(url)
);
});
}
}
// If 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 (metaLinks.length && twitterLinks.length) {
// Deletes the offending message.
ctx.msg.delete();
// Handle action and response if both meta and Twitter/X links are detected.
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.
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\\.`,
{ parse_mode: "MarkdownV2" }
);
}
});
}
// Handle action and response if only meta links are detected.
if (metaLinks.length) {
// Deletes the offending message.
ctx.msg.delete();
// 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.
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\\.`,
{ parse_mode: "MarkdownV2" }
);
}
});
}
// Handle action and response if only Twitter/X links are detected.
if (twitterLinks.length) {
// Deletes the offending message.
ctx.msg.delete();
// 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.
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\\.`,
{ parse_mode: "MarkdownV2" }
);
}
}
}
});
}
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 }
}
);
if (deletedLinks) {
return await urql
.mutation(addGroup, { groupID, groupName, groupUsername })
.toPromise()
.then(() =>
urql.mutation(incrementGroup, {
groupID,
linksDeleted: deletedLinks
})
);
}
}
}

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.hears(
"/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

@@ -1,6 +1,8 @@
import { Composer } from "grammy";
import type { Context } from "#root/bot/context.js";
import { logHandle } from "#root/bot/helpers/logging.js";
import urql from "#root/lib/urql.js";
import increment from "#root/lib/graphql/mutations/incrimentMutation.js";
const composer = new Composer<Context>();
@@ -10,76 +12,72 @@ const feature = composer.chatType(["group", "supergroup", "private"]);
* What triggers this feature and adds to the log when it has been triggered.
* The trigger is the command "/help"
*/
feature.hears("/help", logHandle("help"), async (ctx: Context) => {
const GROUP_IDS = process.env.GROUP_IDS
? process.env.GROUP_IDS.split(",")
: undefined;
feature.hears(/^\/help/, logHandle("help"), async (ctx: Context) => {
await urql.mutation(increment, { trigger: true });
// const GROUP_IDS = process.env.GROUP_IDS
// ? process.env.GROUP_IDS.split(",")
// : undefined;
// Checks there is a chat and msg property in the context.
if (ctx.chat && ctx.msg) {
// CHecks if the chat is private
if (ctx.chat.type === "private") {
// Responds with the command list.
return await ctx.reply(
`**Here are the available commands you can use:**\n\n/getGroupID _ADMIN ONLY | Only available in groups_\\- 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\\.`,
{
parse_mode: "MarkdownV2",
reply_parameters: { message_id: ctx.msg.message_id }
}
);
}
// Checks if the whitelist is set up.
if (GROUP_IDS !== undefined) {
const groupID = ctx.chat.id;
const flag = GROUP_IDS.includes(`${groupID}`);
// Checks if the chat is in the whitelist.
if (flag) {
// Responds with the command list.
return await ctx.reply(
`**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\\.`,
{
parse_mode: "MarkdownV2",
reply_parameters: { message_id: ctx.msg.message_id }
if (ctx.chat && ctx.msg && ctx.msg.from) {
await urql
.mutation(increment, { command: true })
.toPromise()
.then(async () => {
if (ctx.msg && ctx.chat && ctx.msg.from) {
// Checks if the chat is private
if (ctx.chat.type === "private") {
// 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",
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\\.`,
// Checks if chat is a group.
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(
`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\\.`,
{
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 }
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\\!`,
{
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.hears(
"/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,6 +2,10 @@ import { Composer } from "grammy";
import type { Context } from "#root/bot/context.js";
import { logHandle } from "#root/bot/helpers/logging.js";
import { metaRegex } from "#root/lib/metaLinkCheck.js";
import urql from "#root/lib/urql.js";
import increment from "#root/lib/graphql/mutations/incrimentMutation.js";
import addGroup from "#root/lib/graphql/mutations/addGroupMutation.js";
import incrementGroup from "#root/lib/graphql/mutations/incrementGroupMutation.js";
const composer = new Composer<Context>();
@@ -15,40 +19,36 @@ feature.hears(
metaRegex,
logHandle("blacklist-detection-meta"),
async (ctx: Context) => {
// Pulling the group IDs from the env variables.
const GROUP_IDS = process.env.GROUP_IDS
? process.env.GROUP_IDS.split(",")
: undefined;
await urql.mutation(increment, { trigger: true });
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;
const username = ctx.msg.from?.username;
// Deletes the offending message.
ctx.msg.delete();
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" }
);
}
}
return 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" }
);
// 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 }
const groupName = ctx.chat?.title;
const groupID = ctx.chat?.id;
const groupUsername = ctx.chat?.username;
return await urql
.mutation(addGroup, { groupID, groupName, groupUsername })
.toPromise()
.then(() =>
urql.mutation(incrementGroup, { groupID, linksDeleted: 1 })
);
}
);
}
});
}
}
);

View File

@@ -2,6 +2,10 @@ import { Composer } from "grammy";
import type { Context } from "#root/bot/context.js";
import { logHandle } from "#root/bot/helpers/logging.js";
import { twitterRegex } from "#root/lib/twitterLinkCheck.js";
import urql from "#root/lib/urql.js";
import increment from "#root/lib/graphql/mutations/incrimentMutation.js";
import addGroup from "#root/lib/graphql/mutations/addGroupMutation.js";
import incrementGroup from "#root/lib/graphql/mutations/incrementGroupMutation.js";
const composer = new Composer<Context>();
@@ -15,45 +19,36 @@ feature.hears(
twitterRegex,
logHandle("blacklist-detection-twitter"),
async (ctx: Context) => {
// Pulling the group IDs from the env variables.
const GROUP_IDS = process.env.GROUP_IDS
? process.env.GROUP_IDS.split(",")
: undefined;
await urql.mutation(increment, { trigger: true });
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;
const username = ctx.msg.from?.username;
// Deletes the offending message.
ctx.msg.delete();
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" }
);
}
}
return 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 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 }
const groupName = ctx.chat?.title;
const groupID = ctx.chat?.id;
const groupUsername = ctx.chat?.username;
return await urql
.mutation(addGroup, { groupID, groupName, groupUsername })
.toPromise()
.then(() =>
urql.mutation(incrementGroup, { groupID, linksDeleted: 1 })
);
}
);
}
});
}
}
);

View File

@@ -1,6 +1,8 @@
import { Composer } from "grammy";
import type { Context } from "#root/bot/context.js";
import { logHandle } from "#root/bot/helpers/logging.js";
import urql from "#root/lib/urql.js";
import increment from "#root/lib/graphql/mutations/incrimentMutation.js";
const composer = new Composer<Context>();
@@ -9,12 +11,21 @@ const feature = composer.chatType("private");
* What triggers this feature and adds to the log when it has been triggered.
* The trigger is the command "/start" or "start"
*/
feature.command("start", logHandle("command-start"), ctx => {
// Responds with information about the bot.
return ctx.reply(
`Welcome\\! I am a bot created by Lucid for [Werewolf Kid Creations Media groups\\.](https://community.lucidcreations.media/) I am designed to delete any Twitter/X and Meta links along with corresponding reformatting services within the WKC groups\\. I now check embedded links\\! By default I only work with whitelisted group IDs\\. 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\\!\n\nLucid would consider hosting this bot for public use if crowd\\-funding would cover the hosting cost of the bot\\. Reach out if you would like to help\\.`,
{ parse_mode: "MarkdownV2" }
);
feature.command("start", logHandle("command-start"), async ctx => {
await urql.mutation(increment, { trigger: true });
await urql
.mutation(increment, { command: true })
.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 };

View File

@@ -16,10 +16,8 @@ import { MemorySessionStorage, Bot as TelegramBot } from "grammy";
import { twitterBlacklist } from "./features/twitterBlacklist.js";
import { metaBlacklist } from "./features/metaBlacklist.js";
import { botInfoCommand } from "./features/botInfoCommand.js";
import { getGroupIDCommand } from "./features/getGroupIDCommand.js";
import { helpCommand } from "./features/helpCommand.js";
import { embedCheck } from "./features/embedCheck.js";
import { isWKCGroup } from "./features/isWKCGroup.js";
interface Dependencies {
config: Config;
@@ -71,8 +69,6 @@ export function createBot(
// Commands
protectedBot.use(botInfoCommand);
protectedBot.use(getGroupIDCommand);
protectedBot.use(isWKCGroup);
protectedBot.use(helpCommand);
// Blacklist Feature

View File

@@ -0,0 +1,24 @@
import { gql } from "@urql/core";
const addGroup = gql`
mutation addGroup(
$groupID: BigInt
$groupName: String
$groupUsername: String
) {
addGroup(
groupID: $groupID
groupName: $groupName
groupUsername: $groupUsername
) {
telegramID
name
username
linksDeleted
createdAt
updatedAt
}
}
`;
export default addGroup;

View File

@@ -0,0 +1,16 @@
import { gql } from "@urql/core";
const incrementGroup = gql`
mutation incrementGroup($groupID: BigInt, $linksDeleted: Int) {
incrementGroup(groupID: $groupID, linksDeleted: $linksDeleted) {
telegramID
name
username
linksDeleted
createdAt
updatedAt
}
}
`;
export default incrementGroup;

View File

@@ -0,0 +1,12 @@
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;

13
src/lib/urql.ts Normal file
View File

@@ -0,0 +1,13 @@
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

@@ -64,21 +64,31 @@ async function startWebhook(config: WebhookConfig) {
// to prevent receiving updates before the bot is ready
await bot.init();
// start server
const info = await serverManager.start();
logger.info({
msg: "Server started",
url: info.url
});
const setWebhook = async (): Promise<void> => {
// set webhook
return await bot.api
.setWebhook(config.botWebhook, {
allowed_updates: config.botAllowedUpdates,
secret_token: config.botWebhookSecret
})
.then(() => {
logger.info({
msg: "Webhook was set",
url: config.botWebhook
});
});
};
// set webhook
await bot.api.setWebhook(config.botWebhook, {
allowed_updates: config.botAllowedUpdates,
secret_token: config.botWebhookSecret
});
logger.info({
msg: "Webhook was set",
url: config.botWebhook
// start server
const info = await serverManager.start().then(async info => {
logger.info({
msg: "Server started",
url: info.url
});
setTimeout(async () => {
await setWebhook();
}, 10000);
});
}

771
yarn.lock

File diff suppressed because it is too large Load Diff