{"id":20172288,"url":"https://github.com/apostoldevel/apostol-pgtg","last_synced_at":"2026-04-12T23:02:24.843Z","repository":{"id":110915301,"uuid":"564364570","full_name":"apostoldevel/apostol-pgtg","owner":"apostoldevel","description":"Telegram bot in Postgres","archived":false,"fork":false,"pushed_at":"2023-04-28T19:30:08.000Z","size":3676,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2023-04-28T20:27:01.875Z","etag":null,"topics":["postgres","postgresql","telegram","telegram-bot"],"latest_commit_sha":null,"homepage":"","language":"C++","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/apostoldevel.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}},"created_at":"2022-11-10T15:02:37.000Z","updated_at":"2023-04-28T20:27:01.875Z","dependencies_parsed_at":null,"dependency_job_id":"a850e49a-60bb-46ef-8bdb-640698ea540e","html_url":"https://github.com/apostoldevel/apostol-pgtg","commit_stats":null,"previous_names":[],"tags_count":0,"template":null,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/apostoldevel%2Fapostol-pgtg","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/apostoldevel%2Fapostol-pgtg/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/apostoldevel%2Fapostol-pgtg/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/apostoldevel%2Fapostol-pgtg/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/apostoldevel","download_url":"https://codeload.github.com/apostoldevel/apostol-pgtg/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":241607707,"owners_count":19989946,"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","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":["postgres","postgresql","telegram","telegram-bot"],"created_at":"2024-11-14T01:29:31.082Z","updated_at":"2026-04-12T23:02:19.802Z","avatar_url":"https://github.com/apostoldevel.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Telegram bot in Postgres (pgTG)\n\nHere you will find everything you need to implement a telegram bot in the PL/pgSQL programming language.\n\nHow is it possible?\n-\n\nThis was made possible thanks to the [Apostol](https://github.com/apostoldevel/apostol) project and the [PGFetch](https://github.com/apostoldevel/module-PGFetch) (Postgres Fetch) module.\n\nHow it works?\n-\n\nAll messages addressed to your telegram bot through [WebHook](https://core.telegram.org/bots/api#setwebhook) will be redirected to `pgtg` and passed to PostgreSQL for processing.\n\nAll you need to do is to implement a handler function to process messages coming from telegrams, as described below.\n\nSettings\n-\n\n1. Set the [WebHook](https://core.telegram.org/bots/api#setwebhook) in your Telegram bot settings:\n   * [Setting your Telegram Bot WebHook the easy way](https://xabaras.medium.com/setting-your-telegram-bot-webhook-the-easy-way-c7577b2d6f72)\n      * URL format:\n        ~~~\n        https://you.domain.org/api/v1/webhook/00000000-0000-4000-8000-000000000001\n        ~~~\n         * `you.domain.org` - You domain name;\n\n\n1. Configure [Nginx](https://nginx.org) so that telegram requests are redirected to **pgTG** on port `4980`:\n\n    \u003cdetails\u003e\n      \u003csummary\u003eExample\u003c/summary\u003e\n\n      ~~~\n      server {\n        listen 443 ssl;\n        server_name you.domain.org;\n\n        ssl_certificate     /etc/ssl/certs/you.domain.crt;\n        ssl_certificate_key /etc/ssl/private/you.domain.key;\n        ssl_session_cache   shared:SSL:10m;\n        ssl_session_timeout 10m;\n      \n        location / {\n          proxy_pass http://127.0.0.1:4980;\n          proxy_http_version 1.1;\n          proxy_set_header Host $host;\n          proxy_set_header Connection \"keep-alive\";\n          proxy_set_header X-Forwarded-Proto $scheme;\n          proxy_set_header X-Real-IP $remote_addr;\n          proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        }\n      }\n      ~~~\n    \u003c/details\u003e \n\n1. Build and install **pgTG**.\n\n1. Connect to the database `pgtg`:\n   ~~~shell\n   sudo -u postgres psql -d pgtg -U http\n   ~~~\n\n1. Register your bot:\n   ~~~postgresql\n   SELECT bot.add('00000000-0000-4000-8000-000000000001', '\u003cAPI_TOKEN\u003e', '\u003cBOT_USERNAME\u003e', '\u003cBOT_NAME\u003e', null, 'en');\n   ~~~\n   * `API_TOKEN` - Telegram bot API Token. Example: `0000000000:AAxxxXXXxxxXXXxxxXXXxxxXXXxxxXXXxxx`;\n   * `BOT_USERNAME` - Telegram bot username. Example: `BitcoinBalanceDetectorBot`;\n   * `BOT_NAME` - Telegram bot name. Example: `Bitcoin Balance Detector`.\n\n\n1. Create a `Webhook` function in the `bot` schema:\n   * The function name must start with your bot username and end with `_webhook`.\n\n\n1. Create a `Heartbeat` function in the `bot` schema:\n   * The function name must start with your bot username and end with `_heartbeat`.\n\n**Link to** [Bitcoin Balance Detector](http://t.me/BitcoinBalanceDetectorBot).\n\n### Webhook function example:\n\u003cdetails\u003e\n  \u003csummary\u003eBitcoinBalanceDetectorBot_webhook\u003c/summary\u003e\n\n~~~postgresql\nCREATE OR REPLACE FUNCTION bot.BitcoinBalanceDetectorBot_webhook (\n  pBotId    uuid,\n  pBody     jsonb\n) RETURNS   void\nAS $$\nDECLARE\n  r         record;\n  b         record;\n  m         record;\n  c         record;\n  u         record;\n  f         record;\n\n  isCommand bool;\n\n  vParam    text[];\n\n  vCommand  text;\n  vMessage  text;\nBEGIN\n  SELECT * INTO r FROM bot.list WHERE id = pBotId;\n\n  IF NOT FOUND THEN\n    RETURN;\n  END IF;\n\n  SELECT * INTO b FROM jsonb_to_record(pBody) AS x(message jsonb, update_id double precision);\n  SELECT * INTO m FROM jsonb_to_record(b.message) AS x(chat jsonb, date double precision, \"from\" jsonb, text text, entities jsonb, document jsonb, message_id int);\n  SELECT * INTO c FROM jsonb_to_record(m.chat) AS x(id int, type text, username text, last_name text, first_name text);\n  SELECT * INTO u FROM jsonb_to_record(m.\"from\") AS x(id int, is_bot bool, username text, last_name text, first_name text, language_code text);\n  SELECT * INTO f FROM jsonb_to_record(m.document) AS x(file_id text, file_name text, file_size int, mime_type text, file_unique_id text);\n\n  IF m.document IS NOT NULL THEN\n    IF f.mime_type = 'text/csv' THEN\n      IF u.language_code = 'ru' THEN\n        vMessage := format('Загрузка файла: \"%s\".', f.file_name);\n      ELSE\n        vMessage := format('Downloading file: \"%s\".', f.file_name);\n      END IF;\n\n      PERFORM bot.new_file(f.file_id, r.id, c.id, u.id, f.file_name, '/', f.file_size, Now(), null, null, f.file_unique_id, f.mime_type);\n      PERFORM tg.get_file(r.id, f.file_id);\n    ELSE\n      IF u.language_code = 'ru' THEN\n        vMessage := format('Неверный тип файла: %s', f.mime_type);\n      ELSE\n        vMessage := format('Invalid file type: %s', f.mime_type);\n      END IF;\n    END IF;\n  END IF;\n\n  IF m.text IS NOT NULL THEN\n\n    isCommand := SubStr(m.text, 1, 1) = '/';\n\n    IF isCommand THEN\n      vParam := string_to_array(m.text, ' ');\n      vCommand := vParam[1];\n    ELSE\n      SELECT command INTO vCommand FROM bot.context WHERE pBotId = pBotId AND chat_id = c.id AND user_id = u.id;\n    END IF;\n\n    PERFORM bot.context(r.id, c.id, u.id, vCommand, m.text, b.message, to_timestamp(b.update_id));\n\n    CASE vCommand\n    WHEN '/start' THEN\n        IF u.language_code = 'ru' THEN\n\t    vMessage := format('Здравствуйте, Вас приветствует бот %s!', r.full_name);\n      ELSE\n\t    vMessage := format('Hello, you are welcomed by a bot %s!', r.full_name);\n      END IF;\n\n      PERFORM bot.command_start(u.language_code, to_timestamp(b.update_id));\n    WHEN '/help' THEN\n      vMessage := bot.command_help(u.language_code);\n    WHEN '/add' THEN\n      IF isCommand THEN\n        IF u.language_code = 'ru' THEN\n          vMessage := 'Введите, пожалуйста, один или несколько Bitcoin адресов.';\n        ELSE\n          vMessage := 'Please enter one or more Bitcoin addresses.';\n        END IF;\n\n        IF array_length(vParam, 1) \u003e 1 THEN\n          vMessage := bot.command_add(vParam[2:], u.language_code, to_timestamp(b.update_id));\n        END IF;\n      ELSE\n        vMessage := bot.command_add(string_to_array(replace(m.text, E'\\n', ' '), ' '), u.language_code, to_timestamp(b.update_id));\n      END IF;\n    WHEN '/delete' THEN\n      IF isCommand THEN\n        IF u.language_code = 'ru' THEN\n          vMessage := 'Введите, пожалуйста, один или несколько Bitcoin адресов.';\n        ELSE\n          vMessage := 'Please enter one or more Bitcoin addresses.';\n        END IF;\n\n        IF array_length(vParam, 1) \u003e 1 THEN\n          vMessage := bot.command_delete(vParam[2:], u.language_code);\n        END IF;\n      ELSE\n        vMessage := bot.command_delete(string_to_array(replace(m.text, E'\\n', ' '), ' '), u.language_code);\n      END IF;\n    WHEN '/list' THEN\n      IF isCommand THEN\n        vMessage := bot.command_list(u.language_code);\n      END IF;\n    WHEN '/check' THEN\n      IF isCommand THEN\n        vMessage := bot.command_check(u.language_code);\n      END IF;\n    WHEN '/settings' THEN\n      IF isCommand THEN\n        IF u.language_code = 'ru' THEN\n          vMessage := E'Введите одно или несколько настроек в формате:\\r\\n\u003cpre\u003eключ=значение\u003c/pre\u003e';\n          vMessage := concat(vMessage, E'\\r\\n\\r\\nТекущие настройки:\\r\\n\\r\\n');\n        ELSE\n          vMessage := 'Enter one or more settings in the format:\\r\\n\u003cpre\u003ekey=value\u003c/pre\u003e';\n          vMessage := concat(vMessage, E'\\r\\n\\r\\nCurrent settings:\\r\\n\\r\\n');\n        END IF;\n        vMessage := concat(vMessage, bot.command_settings(null, u.language_code));\n      ELSE\n        vMessage := bot.command_settings(string_to_array(m.text, E'\\n'), u.language_code);\n      END IF;\n    ELSE\n      IF u.language_code = 'ru' THEN\n        vMessage := 'Неизвестная команда.';\n      ELSE\n        vMessage := 'Unknown command.';\n      END IF;\n    END CASE;\n\n  END IF;\n\n  IF vMessage IS NOT NULL THEN\n    PERFORM tg.send_message(r.id, c.id, vMessage, 'HTML');\n  END IF;\nEND\n$$ LANGUAGE plpgsql\n  SECURITY DEFINER\n  SET search_path = tg, pg_temp;\n~~~\n\u003c/details\u003e \n\n### Heartbeat function example:\n\u003cdetails\u003e\n  \u003csummary\u003eBitcoinBalanceDetectorBot_heartbeat\u003c/summary\u003e\n\n~~~postgresql\nCREATE OR REPLACE FUNCTION bot.BitcoinBalanceDetectorBot_heartbeat (\n  pBotId        uuid\n) RETURNS       void\nAS $$\nDECLARE\n  r             record;\n  d             record;\n\n  count         int;\n  i             interval;\n\n  address       text;\n\n  vMessage      text;\n  vContext      text;\nBEGIN\n  FOR d IN SELECT bot_id, chat_id, user_id FROM bot.data WHERE bot_id = pBotId GROUP BY bot_id, chat_id, user_id\n  LOOP\n    SELECT make_interval(secs =\u003e value::int) INTO i\n      FROM bot.data\n     WHERE bot_id = d.bot_id\n       AND chat_id = d.chat_id\n       AND user_id = d.user_id\n       AND category = 'settings'\n       AND key = 'interval';\n\n    i := coalesce(i, INTERVAL '1 min');\n    count := 0;\n\n    FOR r IN\n      SELECT key\n        FROM bot.data\n       WHERE bot_id = d.bot_id\n         AND chat_id = d.chat_id\n         AND user_id = d.user_id\n         AND category = 'address'\n         AND updated + i \u003c= Now()\n       ORDER BY updated\n    LOOP\n      address := coalesce(address || '|', '') || r.key;\n      count := count + 1;\n      EXIT WHEN count \u003e= 50;\n    END LOOP;\n\n    IF address IS NOT NULL THEN\n      PERFORM http.fetch(format('https://blockchain.info/multiaddr?active=%s\u0026n=0', address), 'GET', null, null, 'bot.blockchain_done', 'bot.blockchain_fail', 'blockchain', pBotId::text, 'multiaddr');\n      RETURN; -- one circle - one user\n    END IF;\n  END LOOP;\nEXCEPTION\nWHEN others THEN\n  GET STACKED DIAGNOSTICS vMessage = MESSAGE_TEXT, vContext = PG_EXCEPTION_CONTEXT;\n  PERFORM WriteDiagnostics(vMessage, vContext);\nEND\n$$ LANGUAGE plpgsql\n  SECURITY DEFINER\n  SET search_path = bot, pg_temp;\n~~~\n\u003c/details\u003e \n\nBuilding and installation\n-\n\n### Dependencies:\n\n1. C++ compiler;\n2. [CMake](https://cmake.org) or a comprehensive development environment (IDE) with support for [CMake](https://cmake.org);\n3. Library [libpq-dev](https://www.postgresql.org/download) (libraries and headers for C language frontend development);\n4. Library [postgresql-server-dev-all](https://www.postgresql.org/download) (libraries and headers for C language backend development).\n\n### Linux (Debian/Ubuntu)\n\nTo install a C++ compiler and a valid library on Ubuntu:\n~~~shell\nsudo apt-get install build-essential libssl-dev libcurl4-openssl-dev make cmake gcc g++\n~~~\n\n### PostgreSQL\n\nTo install PostgreSQL, follow the instructions at [this](https://www.postgresql.org/download/) link.\n\n### Database\n\nTo install the database you need to run:\n\n1. Write the name of the database in the db/sql/sets.conf file (default: pgtg)\n1. Set passwords for Postgres users [libpq-pgpass](https://postgrespro.ru/docs/postgrespro/14/libpq-pgpass):\n   ~~~shell\n   sudo -iu postgres -H vim .pgpass\n   ~~~\n   ~~~\n   *:*:*:http:http\n   ~~~\n1. Specify in the settings file `/etc/postgresql/14/main/pg_hba.conf`:\n   ~~~\n   # TYPE  DATABASE        USER            ADDRESS                 METHOD\n   local\tpgtg\t\thttp\t\t\t\t\tmd5\n   ~~~\n1. Apply settings:\n   ~~~shell\n   sudo pg_ctlcluster 14 main reload\n   ~~~   \n1. Run:\n   ~~~shell\n   cd db/\n   ./runme.sh --make\n   ~~~\n\n###### The `--make` option is required to install the database for the first time. Further, the installation script can be run either without parameters or with the `--install` parameter.\n\nTo install **pgTG** using Git, run:\n~~~shell\ngit clone https://github.com/apostoldevel/apostol-pgtg.git\n~~~\n\n### Building:\n~~~shell\ncd apostol-pgtg\n./configure\n~~~\n\n### Compilation and installation:\n~~~shell\ncd cmake-build-release\nmake\nsudo make install\n~~~\n\nBy default, the `pgtg` binary will be installed to:\n~~~\n/usr/sbin\n~~~\n\nThe configuration file and the corresponding files for operation, depending on the installation configuration, are installed in:\n~~~\n/etc/pgtg\n~~~\n\nRun\n-\n\n**`pgtg`** is a Linux system service (daemon).\nTo manage **`pgtg`**, use standard service management commands.\n\nTo run `pgtg` run:\n~~~shell\nsudo systemctl start pgtg\n~~~\n\nTo check the status, run:\n~~~shell\nsudo systemctl status pgtg\n~~~\n\nThe result should be **something** like this:\n~~~\n● pgtg.service - Telegram bot in Postgres\n     Loaded: loaded (/etc/systemd/system/pgtg.service; enabled; vendor preset: enabled)\n     Active: active (running) since Thu 2022-11-10 01:02:00 MSK; 4s ago\n    Process: 57641 ExecStartPre=/usr/bin/rm -f /run/pgtg.pid (code=exited, status=0/SUCCESS)\n    Process: 57647 ExecStartPre=/usr/sbin/pgtg -t (code=exited, status=0/SUCCESS)\n    Process: 57648 ExecStart=/usr/sbin/pgtg (code=exited, status=0/SUCCESS)\n   Main PID: 57649 (pgtg)\n      Tasks: 2 (limit: 9528)\n     Memory: 6.6M\n     CGroup: /system.slice/pgtg.service\n             ├─57649 pgtg: master process /usr/sbin/pgtg\n             └─57650 pgtg: worker process (\"pg fetch\", \"web server\")\n~~~\n\nDocker\n-\n\nYou can build the image yourself or get it ready-made from the docker hub:\n\n### Collect\n\n~~~shell\ndocker build -t pgtg .\n~~~\n\n### Get\n\n~~~shell\ndocker pull apostoldevel/pgtg\n~~~\n\n### Run\n\nIf assembled by yourself:\n~~~shell\ndocker run -d -p 4980:4980 --rm --name pgtg pgtg\n~~~\n\nIf you received a finished image:\n~~~shell\ndocker run -d -p 4980:4980 --rm --name pgtg apostoldevel/pgtg\n~~~\n\nSwagger UI will be available at http://localhost:4980 or http://host-ip:4980 in your browser.\n\n### **Management**\n\nYou can control **`pgtg`** with signals.\nThe default master process number is written to the `/run/pgtg.pid` file.\nYou can change the name of this file during build configuration or in `pgtg.conf` section `[daemon]` key `pid`.\n\nThe master process supports the following signals:\n\n| Signal | Action |\n|---------|------------------|\n|TERM, INT|quick completion|\n|QUIT |smooth termination|\n|HUP |change configuration, start new workflows with new configuration, gracefully terminate old workflows|\n|WINCH |smooth shutdown of workflows|\n\nYou do not need to manage workflows separately. However, they also support some signals:\n\n| Signal | Action |\n|---------|------------------|\n|TERM, INT|quick completion|\n|QUIT |smooth termination|","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fapostoldevel%2Fapostol-pgtg","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fapostoldevel%2Fapostol-pgtg","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fapostoldevel%2Fapostol-pgtg/lists"}