{"id":18543188,"url":"https://github.com/kozalosev/textutilsbot","last_synced_at":"2026-04-30T04:01:48.436Z","repository":{"id":54295664,"uuid":"123640218","full_name":"kozalosev/textUtilsBot","owner":"kozalosev","description":"A simple bot for Telegram, that has some useful handlers of inline queries to make text conversions.","archived":false,"fork":false,"pushed_at":"2026-04-28T15:03:09.000Z","size":258,"stargazers_count":5,"open_issues_count":9,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2026-04-28T16:30:39.844Z","etag":null,"topics":["aiotg","bot","inline-queries","telegram","text-conversion"],"latest_commit_sha":null,"homepage":"https://t.me/textUtilsBot","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kozalosev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","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,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2018-03-02T23:06:59.000Z","updated_at":"2026-03-22T23:43:08.000Z","dependencies_parsed_at":"2022-08-13T11:20:09.239Z","dependency_job_id":"61b12105-dbb7-4c64-9b70-76550ef56ea8","html_url":"https://github.com/kozalosev/textUtilsBot","commit_stats":null,"previous_names":[],"tags_count":19,"template":false,"template_full_name":null,"purl":"pkg:github/kozalosev/textUtilsBot","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kozalosev%2FtextUtilsBot","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kozalosev%2FtextUtilsBot/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kozalosev%2FtextUtilsBot/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kozalosev%2FtextUtilsBot/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kozalosev","download_url":"https://codeload.github.com/kozalosev/textUtilsBot/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kozalosev%2FtextUtilsBot/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32454170,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-29T22:27:22.272Z","status":"online","status_checked_at":"2026-04-30T02:00:05.929Z","response_time":57,"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":["aiotg","bot","inline-queries","telegram","text-conversion"],"created_at":"2024-11-06T20:13:00.894Z","updated_at":"2026-04-30T04:01:48.428Z","avatar_url":"https://github.com/kozalosev.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"[@textUtilsBot](https://t.me/textUtilsBot)\n==========================================\n\n[![CI Build](https://github.com/kozalosev/textUtilsBot/actions/workflows/ci-build.yml/badge.svg)](https://github.com/kozalosev/textUtilsBot/actions/workflows/ci-build.yml)\n\nA simple bot for [Telegram](https://telegram.org), that has some useful handlers of inline queries to make text\nconversions.\n\nThe bot supports both ways of communication with Telegram: polling (intended for debugging) and web hooks (for\nproduction use).\n\n**Feel free to suggest additional functionality, new features, and configuration examples!** Just create an issue or\nsend me a pull request.\n\n\nRequirements\n------------\n\n- [Python 3.11+](https://www.python.org) (3.7+ until [v2.2.0](https://github.com/kozalosev/textUtilsBot/releases/tag/v2.2.0))\n- [aiotg](https://pypi.python.org/pypi/aiotg)\n- [aiohttp](https://pypi.python.org/pypi/aiohttp)\n\nTo execute the tests:\n\n- [pytest](https://pypi.python.org/pypi/pytest)\n- [coverage](https://pypi.python.org/pypi/coverage)\n\n\nHow to run the bot in debug mode from PyCharm\n---------------------------------------------\n\nTo run the bot in debug mode from PyCharm, add a new Python configuration:\n- Module name: `app.bot`\n- Working directory: absolute path to the repo directory\n\nAlso, you must create a `.env` file (copy from `.env.example`) and set your `TOKEN`. That's all!\nClick the run button!\n\n\u003e Don't forget to enable VPN if Telegram is banned in your country!\n\n\nHow to deploy\n-------------\n\nAs of [v2.0.0](https://github.com/kozalosev/textUtilsBot/releases/tag/v2.0.0), there are two options to run the bot.\nThe first one is to use a Docker container. To use this option, you need [Docker](https://docs.docker.com/install/#supported-platforms)\nand [Docker Compose](https://docs.docker.com/compose/install/) to be installed on your system. This approach allows you\nto don't care about the version of the Python interpreter installed on your machine.\n\nThe other way is to run the bot within a virtual environment. There is a special [initialization script](init.sh),\nthat can help you on Linux. On Windows, you have to manually run *venv*, install all dependencies using *pip* and create\na `.env` file (copy from `.env.example`) with the required values. Note, however, that there\nis only built-in support for Linux based servers for production use. But it's OK to utilize Windows machines for\ndevelopment and debugging.\n\n\n### Using Docker\n\n1. Clone the repository.\n2. Configure [nginx](http://nginx.org) or any other front-end web server (keep reading for more information).\n3. Run the `./start-container.sh` script.\n4. Edit `.env` according to your environment.\n5. Run `./start-container.sh` again.\n\n\n### Using _venv_\n\n1. Clone the repository.\n2. `./init.sh`\n3. Configure [nginx](http://nginx.org) or any other front-end web server (keep reading for more information).\n4. Edit `.env` according to your environment.\n5. Run `./start.sh` using one of the following ways:\n    - directly (`nohup ./start.sh \u0026\u003e/dev/null \u0026`);\n    - configure [supervisord](http://supervisord.org/) or **systemd** to do it for you (see\n        [exemplary configuration files](examples));\n    - configure any other service manager on your choice (but you have to write configuration by yourself).\n\n\n### Backups (Docker only)\n\nAutomatic backups of `messages.db` to Scaleway Object Storage via [rclone](https://rclone.org/) are built into the\nimage. To enable, mount your `rclone.conf` to `/config/rclone.conf` in the container and set `BACKUP_S3_BUCKET` in\n`.env`.\n\n| Variable | Default | Description |\n|---|---|---|\n| `BACKUP_S3_BUCKET` | *(required to enable)* | Target S3 bucket name |\n| `BACKUP_CRON` | `0 3 * * *` | Cron schedule |\n| `BACKUP_KEEP_DAYS` | `30` | Days to retain old backups |\n\nBackups are stored as `messages_YYYY-MM-DD_HH_MM_SS.db` in the bucket root.\n\n\n### Common notes\n\nI encourage you to use the application as a local server that receives requests from Telegram from an external web\nserver. In such case, you can configure a TLS certificate for all at once. This is especially handy in the case if\nyou're using [Cloudflare](https://www.cloudflare.com/) services.\n\nIf you use *nginx*, look at [the exemplary configuration file](examples/nginx-textUtilsBot.conf). In other cases, you\nhave to write it by yourself (and send me a pull request, of course 😊).\n\nNevertheless, nobody forbids you to use the application as a front-end web server directly. However, to reach this, you\nhave to make some changes in [app/bot.py](app/bot.py):\n\n```python\nimport ssl\n...\nSSL_CERT = \"/your/path/to/ssl/certificate.pem\"\nSSL_PRIV = \"/your/path/to/ssl/private.key\"\n...\ncontext = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)\ncontext.load_cert_chain(SSL_CERT, SSL_PRIV)\nweb.run_app(app, host='0.0.0.0', port=APP_PORT, ssl_context=context)\n```\n\n\nHow to contribute a new feature\n-------------------------------\n\nAs of [v1.1.0](https://github.com/kozalosev/textUtilsBot/releases/tag/v1.1.0), it became much easier to add new text\nprocessors to the bot. If [earlier](https://github.com/kozalosev/textUtilsBot/tree/c25df0e0f246ca9c2143098d4cf7c72535b96591)\nyou had to embed calls of new functions into the entangled and confusing\n[inline_request_handler](https://github.com/kozalosev/textUtilsBot/blob/c25df0e0f246ca9c2143098d4cf7c72535b96591/app/bot.py#L64)\nfunction itself, now this handler is implemented by using dynamic autoloading of modules from the [strconv](app/strconv)\npackage. All you need is to add a new module there, implement the\n[TextProcessor](https://github.com/kozalosev/textUtilsBot/blob/feb637b48fd1aa3f87c04333c08bad3fd7f38024/app/txtproc/abc.py#L12)\ninterface (strictly speaking in terms of Python, extend the ABC class) and append strings with a key\n`hint_{classname_in_snake_case}` to the [localizations.ini](app/localizations.ini) file.\n\nFor more information, you may look at either real-world examples\n([banner](https://github.com/kozalosev/textUtilsBot/blob/master/app/strconv/banner.py),\n[typographer](https://github.com/kozalosev/textUtilsBot/blob/master/app/strconv/typographer.py)) or the fictional ones,\nwritten exclusively for educational purposes, below.\n\n\n### Exemplary text processor\n\nWe're gonna create a very simple and stupid text processor. Let's, for instance, search input queries for the word\n`anime` and block other processors if it's found, suggesting only one message with text `Baka-chan!` (\"baka\" means a\nfool in Japanese), stylized by the bold font.\n\nThere are several ABC and mix-in classes in the [txtproc.abc](app/txtproc/abc.py) module. The most important is the\n`TextProcessor` class, of course. It defines the interface of all text processors and implements the `name` property,\nused by the localization system.\n\nText processors can be exclusive, reversible and universal. \n\nThe exclusivity means here that there is only a subset of processors which should handle the query. If such processors\nare found, the others will be discarded. It's mostly useful for decoders.\n\nThe reversibility means, obviously, that the transformed string may be  transformed back and this reversed\ntransformation has sense. Mostly useful for encoders.\n\nUniversal text processors are just the processors that can handle any text. That's it.\n\n\n#### Implementation\n\nFirstly, let's look at the `TextInteface` as a whole and implement the processor without other mix-in classes.\n\n```python\n# app/strconv/baka.py\n\nimport re\n# In fact, there is a shortcut for TextProcessor in the 'txtproc' module itself,\n# but it's mostly intended for the loading system. Later you'll understand why\n# you almost always should prefer the `abc` submodule.\nfrom ..txtproc.abc import TextProcessor\n\n\nclass BakaDetector(TextProcessor):\n    # Discard all non-exclusive processors if this one is matched.\n    is_exclusive = True\n    # By default, the text will be rendered by Telegram as-is.\n    use_html = True\n    \n    # Let the system determine if the processor is able to handle a query.\n    @classmethod\n    def can_process(cls, query: str) -\u003e bool:\n        return re.search(r\"\\banime\\b\", query, re.IGNORECASE) is not None\n\n    # Return the resulted message.\n    def process(self, query: str) -\u003e str:\n        return \"\u003cb\u003eBaka-chan!\u003c/b\u003e\"\n    \n    # Description of the message mustn't contain HTML tags since they will be rendered as-is.\n    def get_description(self, query: str) -\u003e str:\n        return \"Baka-chan!\"\n```\n\nOK. Let's make the class a bit simpler by using the built-in mix-ins.\n\n```python\nimport re\nfrom ..txtproc.abc import *\n\n\n# Here we describe our text processor in one line.\nclass BakaDetector(Exclusive, HTML, TextProcessor):    \n    @classmethod\n    def can_process(cls, query: str) -\u003e bool:\n        return re.search(r\"\\banime\\b\", query, re.IGNORECASE) is not None\n\n    def process(self, query: str) -\u003e str:\n        return \"\u003cb\u003eBaka-chan!\u003c/b\u003e\"\n    \n    def get_description(self, query: str) -\u003e str:\n        return \"Baka-chan!\"\n```\n\nNote that you're, as a text processor developer, responsible for escaping of the input query! Use the\n`strconv.util.escape_html` function for that. For the same reason, you're not allowed to use Markdown, since Telegram\nflavored Markdown is much harder to escape properly.\n\nWell, let's write another simple text processor. For example, let's convert strings to sequences of their numerical\ncodes and vice versa.\n\n```python\n# app/strconv/char2code.py\n\nfrom typing import *\nfrom ..txtproc.abc import *\n\n\n# The 'Encoder' class is just a shortcut for \"Reversible, TextProcessor\". \nclass CharEncoder(Universal, Encoder):\n    def process(self, query: str) -\u003e str:\n        codes = [str(ord(c)) for c in query]\n        return \" \".join(codes)\n\n\n# The 'Decoder' class is a shortcut for \"Exclusive, TextProcessor\". \nclass CharDecoder(Decoder):\n    @classmethod\n    def can_process(cls, query: str) -\u003e bool:\n        codes = query.split()\n        return all(cls._try_parse_int(code) for code in codes)\n\n    def process(self, query: str) -\u003e str:\n        codes = query.split()\n        chars = [chr(int(code)) for code in codes]\n        return \"\".join(chars)\n        \n    @staticmethod\n    def _try_parse_int(s: str) -\u003e Optional[int]:\n        try:\n            return int(s)\n        except ValueError:\n            return None\n```\n\nUse the `Encoder` and `Decoder` classes for pairs of reversible text processors.\n\n\n#### Titles and localization\n\nDo you know what we forgot to do? Titles for our processors, that will be used by the localization system! Let's fix it.\nGo to the `localizations.ini` file and add the following lines:\n\n```ini\n# Under [DEFAULT] section\nhint_baka_detector = Baka?\nhint_char_encoder = Char codes\nhint_char_decoder = Get the string back\n\n...\n\n# Under [ru] section\nhint_baka_detector = Бака?\nhint_char_encoder = Коды символов\nhint_char_decoder = Вернуть строку обратно\n```\n\nIf you want your processors to be described in the help, don't forget to add the following lines:\n\n```ini\n# Under [DEFAULT] section\nhelp_baka_detector = You're a baka if you watch anime!\nhelp_char_encoder = Converts strings to sequences of their numerical codes.\n\n...\n\n# Under [ru] section\nhelp_baka_detector = Если ты смотришь аниме, то ты бака!\nhelp_char_encoder = Превращает строки в последовательности их числовых кодов.\n```\n\nMuch better! Actually, there is one more thing we need to do. Write tests for our code!\n\n\n#### Testing\n\n\u003e Code without tests is buggy and bad code.  \n\u003e — [Mattias Petter Johansson](https://coub.com/view/ywl7o)\n\nLet's create files `test_baka.py` and `test_char2code.py` files inside the `tests/test_strconv/` directory.\n\n```python\n# tests/test_strconv/test_baka.py\n\nimport pytest\nfrom app.strconv.baka import BakaDetector\n\n\n@pytest.fixture\ndef processor() -\u003e BakaDetector:\n    return BakaDetector()\n\n\ndef test_matcher(processor):\n    assert not processor.can_process(\"hello world\")\n    assert processor.can_process(\"hello anime\")\n    assert processor.can_process(\"Hello Anime!\")\n\n\ndef test_processor(processor):\n    assert processor.process(\"hello anime\") == \"\u003cb\u003eBaka-chan!\u003c/b\u003e\"\n    assert processor.get_description(\"hello anime\") == \"Baka-chan!\"\n    \n    assert processor.process(\"Hello Anime!\") == \"\u003cb\u003eBaka-chan!\u003c/b\u003e\"\n    assert processor.get_description(\"Hello Anime!\") == \"Baka-chan!\"\n```\n\n```python\n# tests/test_strconv/test_baka.py\n\nfrom app.strconv.char2code import CharEncoder, CharDecoder\n\n\ndef test_encoder():\n    assert CharEncoder().process(\"hello world\") == \"104 101 108 108 111 32 119 111 114 108 100\"\n\n\ndef test_decoder():\n    decoder = CharDecoder()\n    assert not decoder.can_process(\"hello world\")\n    assert decoder.can_process(\"104 101 108 108 111 32 119 111 114 108 100\")\n    assert decoder.process(\"104 101 108 108 111 32 119 111 114 108 100\") == \"hello world\"\n```\n\n##### Run tests\n\n```bash\npytest\n```\n\nor\n\n```bash\nuv run pytest\n```\n\nAll tests must finish successfully.\n\n\n#### What's next?\n\nAt this point, our work is done, and we're ready to commit these two features and make a pull request! Surely, we won't\ndo it now, but if you develop a new awesome feature, feel free to send it to the upstream! All text processors have a\nchance to be included in the official hosted version of the bot. Especially if they're not universal and intended for\nsome special cases.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkozalosev%2Ftextutilsbot","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkozalosev%2Ftextutilsbot","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkozalosev%2Ftextutilsbot/lists"}