{"id":19293945,"url":"https://github.com/igrishaev/teleward","last_synced_at":"2026-03-12T00:03:06.347Z","repository":{"id":45786373,"uuid":"514508908","full_name":"igrishaev/teleward","owner":"igrishaev","description":"Captcha bot for Telegram in Clojure + GraalVM","archived":false,"fork":false,"pushed_at":"2023-08-31T19:23:42.000Z","size":183,"stargazers_count":15,"open_issues_count":0,"forks_count":4,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-06-30T18:19:29.089Z","etag":null,"topics":["clojure","graalvm","telegram"],"latest_commit_sha":null,"homepage":"https://github.com/igrishaev/teleward","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/igrishaev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-07-16T07:24:43.000Z","updated_at":"2025-01-28T20:24:20.000Z","dependencies_parsed_at":"2024-11-09T22:36:46.874Z","dependency_job_id":"34f7447a-5332-4242-93ba-21274be76e61","html_url":"https://github.com/igrishaev/teleward","commit_stats":{"total_commits":115,"total_committers":3,"mean_commits":"38.333333333333336","dds":"0.017391304347826098","last_synced_commit":"2720e35868455c89a6853cc4c860336975cfe47b"},"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"purl":"pkg:github/igrishaev/teleward","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/igrishaev%2Fteleward","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/igrishaev%2Fteleward/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/igrishaev%2Fteleward/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/igrishaev%2Fteleward/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/igrishaev","download_url":"https://codeload.github.com/igrishaev/teleward/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/igrishaev%2Fteleward/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":272929995,"owners_count":25017057,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-30T02:00:09.474Z","response_time":77,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["clojure","graalvm","telegram"],"created_at":"2024-11-09T22:36:39.167Z","updated_at":"2026-03-12T00:03:06.314Z","avatar_url":"https://github.com/igrishaev.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Teleward\n\nA Telegram captcha bot written in Clojure and compiled with GraalVM native\nimage. Runs on bare Linux/MacOS with no requirements. Fast and robust.\n\n## Table of Contents\n\n\u003c!-- toc --\u003e\n\n- [Why](#why)\n- [Features](#features)\n- [Algorithm](#algorithm)\n- [Java version](#java-version)\n- [Binary version, Linux](#binary-version-linux)\n- [Binary version, MacOS](#binary-version-macos)\n- [Setting Up Your Bot](#setting-up-your-bot)\n- [Configuration](#configuration)\n- [Env vars](#env-vars)\n- [Deploying on bare Ubuntu](#deploying-on-bare-ubuntu)\n- [Webhook mode](#webhook-mode)\n- [AWS/Yandex Cloud deployment](#awsyandex-cloud-deployment)\n- [Health check](#health-check)\n- [Further work](#further-work)\n\n\u003c!-- tocstop --\u003e\n\n## Why\n\nTelegram chats suffer from spammers who are pretty smart nowadays. They don't\nuse bots; instead, they register ordinary accounts and automate them with\nSelenium + web-version of Telegram. Personally, I found Shieldy and other bots\nuseless when dealing with such kind of spammers. This project aims the goal to\nfinish that mess.\n\nAnother reason I opened Teleward for is to try my skills in developing Clojure\napplications with GraalVM. Binary applications are nice: they are fast, and they\ndon't need installing JDK. At the same time, they're are still Clojure: REPL is\nhere, and that's amazing.\n\n## Features\n\n- This is Clojure, so you have REPL! During development, you call Telegram API\n  directly from REPL and see what's going on.\n- The bot can be delivered either as a Jar file or a binary file (with Graal).\n- When Graal-compiled, needs no requirements (Java SDK, etc). The binary size is\n  about 30 Mb.\n- Supports both long polling and webhook modes to obtain messages.\n- Keeps all the state in memory and thus doesn't need any kind of a\n  database. The only exception is the current offset value which is tracked in a\n  file.\n- Supports English and Russian languages.\n- Two captcha styles: normal \"1 + 2\" and Lisp captcha \"(+ 1 2)\".\n- The `+`, `-`, and `*` operators are corresponding Unicode characters that\n  prevent captcha from naive evaluation.\n\n## Algorithm\n\nThe bot listens for all the messages in a group. Once a new pack of messages\narrives, the bot applies the following procedure to each message:\n\n- Mark new members as locked.\n- Send a captcha message to all members.\n- Unless an author of a message is locked, delete that message.\n- If a message is short and matches the captcha's solution, unlock a user and\n  delete the catpcha message.\n- If a locked user has posted three messages with no solution, ban them.\n- If a locked user hasn't solved captcha in time, ban them as well.\n\n*Please note:* the bot processes only messages no older than two minutes from\nnow. In other words, the bot is interested in what is happening now (with a\nslight delay), but not in the far past. This is to prevent a sutuation what a\nbot had been inactive and then has started to consume messages. Without this\ncondition, it will send captcha to chat members who have already joined and\nconfuse them.\n\n## Java version\n\nTo make a Jar artefact, run:\n\n```bash\nmake uberjar\n```\n\nThe `uberjar` target calls `lein uberjar` and also injects the `VERSION` file\ninto it. The output file is `./target/teleward.jar`.\n\n## Binary version, Linux\n\nLinux version is built inside a Docker image, namely the\n`ghcr.io/graalvm/graalvm-ce` one with `native-image` extension preinstalled. Run\nthe following command:\n\n```bash\nmake build-binary-docker\n```\n\nThe output binary file appears at `./builds/teleward-Linux-x86_64`.\n\n## Binary version, MacOS\n\n- [Install GraalVM](https://www.graalvm.org/docs/getting-started/) locally.\n\n- Install the \"native image\" extension:\n\n```bash\ngu install native-image\n```\n\n- Then `make` the project:\n\n```bash\nmake\n# or\nmake build-binary-local\n```\n\nThe output will be `./builds/teleward-Darwin-x86_64`.\n\n## Setting Up Your Bot\n\n- To run the bot, first you need a token. Contact `@BotFather` in Telegram to\n  create a new bot. Copy the token and don't share it.\n\n- Add your new bot into a Telegram group. Promote it to admins. At least the bot\n  must be able to 1) send messages, 2) delete messages, and 3) ban users.\n\n- Run the bot locally:\n\n```bash\nteleward -t \u003ctelegram-token\u003e -l debug\n```\n\nIf everything is fine, the bot will start consuming messages and print them in\nconsole.\n\n## Configuration\n\nSee the version with `-v`, and help with `-h`. The bot takes into account plenty\nof settings, yet not all of them are available for configuration for now. Below,\nwe name the most important parameters you will need.\n\n- `-t, --telegram.token`: the Telegram token you obtain from\n  BotFather. Required, can be set via an env variable `TELEGRAM_TOKEN`.\n\n- `-m, --mode`: Working mode. Either `polling` or `webhook`, default is polling.\n\n- `--webhook.path`: Webhook path, default is `/telegram/webhook`.\n\n- `--webhook.server.host`: Hostname of the webhook server, default is\n  `localhost`.\n\n- `-p, --webhook.server.port`: Port to listen in webhook mode, default is 8090.\n\n- `-l, --logging.level`: the logging level. Can be `debug, info, error`. Default\n  is `info`. In production, most likely you will set `error`.\n\n- `--telegram.offset-file`: where to store offset number for the next\n  `getUpdates` call. Default is `TELEGRAM_OFFSET` in the current working\n  directory.\n\n- `--language`: the language for messages. Can be `en, ru`, default is `ru`.\n\n- `--captcha.style`: a type of captcha. When `lisp`, the captcha looks like `(+\n  4 3)`. Any other value type will produce `4 + 3`. The operator is taken\n  randomly.\n\nExample:\n\n```bash\n./target/teleward -t \u003c...\u003e -l debug \\\n  --language=en --telegram.offset-file=mybot.offset \\\n  --captcha.style=normal\n```\n\nFor the rest of the config, see the `src/teleward/config.clj` file.\n\n[cprop]: https://github.com/tolitius/cprop\n\nUnder the hood, Teleward uses [Cprop][cprop] for configuration. This library\ntakes into account env vars to override default values. Set the `DEBUG=y`\nvariable to see the log of configuration startup.\n\n## Env vars\n\n```\nTELEGRAM__TOKEN\nLANGUAGE\nLOGGING__LEVEL\nPOLLING__UPDATE_TIMEOUT\nPOLLING__USER_TRAIL_PERIOD\nPOLLING__USER_TRAIL_ATTEMPTS\nPOLLING__MESSAGE_EXPIRES\nWEBHOOK__PATH\nWEBHOOK__SERVER__HOST\nWEBHOOK__SERVER__PORT\nPROCESSING__BAN_MODE\nDEBUG=y/Y\n```\n\n## Deploying on bare Ubuntu\n\n- Buy the cheapest VPS machine and SSH to it.\n\n- Create a user:\n\n```bash\nsudo useradd -s /bin/bash -d /home/ivan/ -m -G sudo ivan\nsudo passwd ivan\nmkdir /home/ivan/teleward\n```\n\n- Compile the file locally and copy it to the machine:\n\n```bash\nscp ./builds/teleward-Linux-x86_64 ivan@hostname:/home/ivan/teleward/\n```\n\n- Create a new `systemctl` service:\n\n```bash\nsudo mcedit /etc/systemd/system/teleward.service\n```\n\n- Paste the following config:\n\n```\n[Unit]\nDescription = Teleward bot\nAfter = network.target\n\n[Service]\nType = simple\nRestart = always\nRestartSec = 1\nUser = ivan\nWorkingDirectory = /home/ivan/teleward/\nExecStart = /home/ivan/teleward/teleward-Linux-x86_64 -l debug\nEnvironment = TELEGRAM__TOKEN=xxxxxxxxxxxxxx\n\n[Install]\nWantedBy = multi-user.target\n```\n\n- Enable autoload:\n\n```bash\nsudo systemctl enable teleward\n```\n\n- Manage the service with commands:\n\n```bash\nsudo systemctl stop teleward\nsudo systemctl start teleward\nsudo systemctl status teleward\n```\n\nFor Jar, the config file would be almost the same except the `ExecStart`\nsection. There, you specify something like `java -jar teleward.jar ...`.\n\n## Webhook mode\n\nIn the `teleward.service` file, specify the `-m webhook` parameter:\n\n```\nExecStart = .../teleward-Linux-x86_64 -m webhook -p 8090 ...\n```\n\nInstall Caddy server for SSL. Modify its service config:\n\n```\n# sudo mcedit /lib/systemd/system/caddy.service\n\n[Service]\nExecStart=caddy reverse-proxy --from \u003cDOMAIN\u003e --to localhost:8090\n```\n\nSee the `/conf` directory for configuration.\n\n## AWS/Yandex Cloud deployment\n\nCompile uberjar with with a special profile:\n\n```bash\nmake yc-jar\n```\n\nIn Dynamo DB or Yandex Db, create a table with `(chat_id, user_id)` pair for the\nprimary key (both integers).\n\nZip and upload this jar into S3/YC bucket. Create a lambda/function with these\nsettings:\n\n| Setting | Value |\n| ------- | ----- |\n| environment | Java 11                |\n| bucket      | the name of the bucket |\n| object      | path to the zip file   |\n| entrypoint  | `teleward.YCHandler`   |\n| timeout     | minimum 5 seconds      |\n| memory      | 128 is enough          |\n\n\nSetup the env vars:\n\n| Variable | Value |\n| -------- | ----- |\n| `TELEGRAM__TOKEN`       | your telegram token       |\n| `LOGGING__LEVEL`        | debug/info/error          |\n| `DYNAMODB_TABLE`        | table to store the state  |\n| `AWS_ACCESS_KEY_ID`     | aws public key            |\n| `AWS_SECRET_ACCESS_KEY` | aws secret key            |\n| `DYNAMODB_ENDPOINT`     | HTTPS URL to DynamoDB/YDB |\n\nMake you lambda/function public. Use its URL as a webhook for your bot.\n\n## Health check\n\nThe bot accepts the `/health` command which it replies to \"OK\".\n\n## Further work\n\n- Add tests.\n- Report uptime for `/health`.\n- More config parameters via CLI args.\n- Better config handling.\n- Widnows build.\n\n\u0026copy; 2022 Ivan Grishaev\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Figrishaev%2Fteleward","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Figrishaev%2Fteleward","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Figrishaev%2Fteleward/lists"}