{"id":20333687,"url":"https://github.com/davidegalilei/piltover","last_synced_at":"2025-08-19T11:32:28.292Z","repository":{"id":111470543,"uuid":"578339620","full_name":"DavideGalilei/piltover","owner":"DavideGalilei","description":"An experimental Telegram Server implementation in Python","archived":false,"fork":false,"pushed_at":"2024-06-14T12:49:11.000Z","size":399,"stargazers_count":61,"open_issues_count":2,"forks_count":6,"subscribers_count":8,"default_branch":"master","last_synced_at":"2024-12-09T03:51:49.253Z","etag":null,"topics":["messaging","mtproto","mtproto-server","python","python3","self-hosted","selfhosted","server","telegram","telegram-server"],"latest_commit_sha":null,"homepage":"","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/DavideGalilei.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-12-14T20:31:17.000Z","updated_at":"2024-12-06T05:10:30.000Z","dependencies_parsed_at":"2024-11-14T20:34:38.358Z","dependency_job_id":"bc1aec2b-3608-44eb-bb16-528be63da651","html_url":"https://github.com/DavideGalilei/piltover","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DavideGalilei%2Fpiltover","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DavideGalilei%2Fpiltover/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DavideGalilei%2Fpiltover/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DavideGalilei%2Fpiltover/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/DavideGalilei","download_url":"https://codeload.github.com/DavideGalilei/piltover/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":230351171,"owners_count":18212789,"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":["messaging","mtproto","mtproto-server","python","python3","self-hosted","selfhosted","server","telegram","telegram-server"],"created_at":"2024-11-14T20:32:53.504Z","updated_at":"2024-12-18T23:09:17.056Z","avatar_url":"https://github.com/DavideGalilei.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# piltover 🐳\n\nAn experimental Telegram server written from scratch in Python. Development\nchat: linked group to [@ChameleonGram](https://t.me/ChameleonGram).\n\n## TODO\n\n- [ ] WebK gets stuck on `sendCode()`. (note to self: inspect the MTProto workers in `chrome://inspect/#workers`)\n- [x] Multiple sessions handling for: ~~Give correct `msg_id`/`seq_no` according\n      to the\n      [Telegram specification](https://core.telegram.org/mtproto/description#message-identifier-msg-id)~~\n- [x] ~~A Websocket proxy for Telegram Web (WebZ / WebK). A work in progress\n      temporary implementation is in `tools/websocket_proxy.js`~~\n- [ ] Updates handling: `pts`, `qts`, etc.\n- [ ] Refactor the TL de/serialization module since the code is messy (e.g. make\n      custom boxed types for list/int/str/bytes).\n- [ ] Refactor the server `authorize()` method.\n- [ ] Support multiple server keys to automatically switch to\n      [RSA_PAD](https://core.telegram.org/mtproto/auth_key#presenting-proof-of-work-server-authentication)\n      for official clients, whilst keeping clients like Pyrogram/Telethon\n      working with the old method. Currently handled manually in `server.py`:\n      `old = False`\n- [ ] Support TL from multiple layers, and layer-based handlers. Add fallbacks\n      eventually.\n- [ ] Add a `tests/` directory with patched assertions from client libraries.\n- [ ] Use custom exceptions instead of Python assertions: `assert` statements\n      are disabled with `python -O`, leading to missing important checks.\n- [ ] Add missing security checks, e.g., checking of `g_a`/`g_b`.\n- [ ] Refactor `piltover/__main__.py`, and use a database for auth\n      keys/messages/users/updates (probably with SQLAlchemy and alambic due to\n      reliable database migrations).\n- [ ] MTProxy support maybe? Obfuscation is already implemented, so why not?\n- [ ] HTTP/UDP support? Probably Telegram itself forgot those also exist.\n- [ ] Switch to hypercorn for the tcp server maybe?\n- [ ] Improve the README.\n\n## Purpose\n\nThis project is currently not meant to be used to host custom Telegram\ninstances, as most **security measures are \u003cu\u003ecurrently\u003c/u\u003e barely in place**.\nFor now, it can be used by MTProto clients developers to understand why their\ncode fails, whereas Telegram just closes the connection with a -404 error code.\n\nThat being said, it is planned in future to make it usable for most basic\nTelegram featues, including but not limited to, sending and receiving text and\nmedia messages, media, search.\n\nThis can be really useful for bots developers that would like to have a testing\nsandbox that doesn't ratelimit their bots.\n\nThe server is meant to be used as a library, providing 100% control of every\nanswer\n\n- TODO: allow the user to override `authorize()`\n\n## Example\n\nAn example quick-start (incomplete) code would look like this:\n\n```python\nimport asyncio\nfrom piltover.server import Server, Client, Request\nfrom piltover.utils import gen_keys\n\nasync def main():\n    pilt = Server(server_keys=gen_keys())\n    # Running on localhost\n    # Port: 4430\n\n    @pilt.on_message(\"ping\")\n    async def pong(client: Client, request: Request):\n        print(\"Received ping:\", request.obj)\n\n        return {\n            \"_\": \"pong\",\n            \"msg_id\": request.msg_id,\n            \"ping_id\": request.obj.ping_id,\n        }\n\n    await pilt.serve()\n\nasyncio.run(main())\n```\n\n```shell\n$ poetry install --no-root\n$ poetry run python -m piltover\n# Server running on 127.0.0.1:4430...\n```\n\nOf course, this minimal setup is far from complete, and will only work for auth\nkey generation and pings.\n\n## Development setup\n\n### **General steps**\n\n#### **1. Clone the repo:**\n\n```shell\n$ git clone https://github.com/DavideGalilei/piltover\n$ cd piltover\n```\n\n#### **2. Install poetry**\n\nFollow instructions at: https://python-poetry.org/docs/#installation\n\n#### **3. Initial setup**\n\n```shell\n$ poetry install --no-root\n$ poetry run python tools/gen_tl.py update\n$ poetry run python -m piltover\n```\n\nNow wait until it loads correctly and fire a Ctrl-C to stop the process.\n\n\u003e **You should see a line looking like this at the beginning**\n\u003e\n\u003e ```yml\n\u003e 2023-11-05 19:52:31.171 | INFO     | __main__:main:49 - Pubkey fingerprint: -6bff292cf4837025 (9400d6d30b7c8fdb)\n\u003e ```\n\n**Get the fingerprint hex string and save it for later (some clients need it)**.\nIn this case, the unsigned fingerprint is `9400d6d30b7c8fdb`, but only for this\nexample. Do not reuse this key fingerprint, as it will be different in your\nsetup.\n\n#### **4. Extract public key number and exponent**\n\nAt this point, two files should have been generated in your directory. Namely,\n`data/secrets/privkey.asc` and `data/secrets/pubkey.asc`. Keep in mind that some\nclients might need the PKCS1 public key in the normal ascii format.\n\nSome others like pyrogram, do not have a RSA key parser and hardcode the\nnumber/exponent. To extract it, you can use\n[this command](https://github.com/pyrogram/pyrogram/blob/b19764d5dc9e2d59a4ccbb7f520f78505800656b/pyrogram/crypto/rsa.py#L26):\n\n```shell\n$ grep -v -- - data/secrets/pubkey.asc | tr -d \\\\n | base64 -d | openssl asn1parse -inform DER -i\n```\n\nAn example output would look like this:\n\n```yml\n  0:d=0  hl=4 l= 266 cons: SEQUENCE          \n  4:d=1  hl=4 l= 257 prim:  INTEGER           :C3AE9457FDB44F47B91B9389401933F2D0B27357FE116ED7640798784829FDBC66295169D1D323AB664FD6920EFBAAC8725DA7EACAA491D1F1EEC8259CA68E4CFE86FC6823C903A323DE46C0E64B8DD5C93A188711C1BF78FCBE0C99904227A66C9135241DD8B92A0AD88AB3A6734BC13B57FA38614BB2AA79F3EF0920D577928F7E689B7B5B0A1A8A48DA9D7E4C28F2A8F1AAEDA22AC4DA05324C1CB67538ADFE1AC3201B34A85189B0765E6C79FF443433837B540D6295BF9EE95B8CDA709868C450BE9730C9FCC7442011129AFB45187C2A1913A4974709E9666865C4F06067E981BF57950A0395B45C3A7322FD36F77D803FF97897BC00D5687A3CB575D1\n265:d=1  hl=2 l=   3 prim:  INTEGER           :010001\n```\n\n**Note the exponent (`010001`) and the prime number: (`C3AE94...B575D1`). Save\nthose values for later.**\n\n### **Pyrogram**\n\n- `git clone --depth=1 https://github.com/pyrogram/pyrogram`\n- Edit this dictionary:\n  https://github.com/pyrogram/pyrogram/blob/b19764d5dc9e2d59a4ccbb7f520f78505800656b/pyrogram/crypto/rsa.py#L33\n  - The key is the **server fingerprint**, the value is formed by this\n    expression: `PublicKey(int(` **prime** `16), int(` **exponent** `, 16))`\n  - Replace those values, (optional: delete the rest of the keys)\n- Edit the datacenters ips in\n  [this file](https://github.com/pyrogram/pyrogram/blob/b19764d5dc9e2d59a4ccbb7f520f78505800656b/pyrogram/session/internals/data_center.py#L22):\n  - Every ip should become `\"127.0.0.1\"` (localhost)\n  - In the `DataCenter.__new__` method below, replace every return with\n    `return (ip,` **4430** `)`, instead of ports 80/443\n- Install in development mode with `python3 -m pip install -e .`\n- Ready to use, run the server and check if `test.py` works\n\n### **Telethon**\n\n- `git clone --depth=1 https://github.com/LonamiWebs/telethon`\n- Edit these variables:\n  https://github.com/LonamiWebs/Telethon/blob/2007c83c9e1b1c85e60e4eca8e8651fcb120ee88/telethon/client/telegrambaseclient.py#L21-L24\n  - ```python\n    DEFAULT_DC_ID = 2\n    DEFAULT_IPV4_IP = '127.0.0.1'\n    DEFAULT_IPV6_IP = '2001:67c:4e8:f002::a'\n    DEFAULT_PORT = 4430\n    ```\n  - Just make sure that the default dc is 2, the ipv4 is localhost, and the\n    default port is 4430. We don't really use ipv6 anyway...\n- Add the rsa public key:\n  - Edit this file:\n    https://github.com/LonamiWebs/Telethon/blob/2007c83c9e1b1c85e60e4eca8e8651fcb120ee88/telethon/crypto/rsa.py#L85\n  - Ideally, delete all the existing keys\n  - Take your server's public key from the `data/secrets/pubkey.asc` file, and\n    add it there with `add_key(\"\"\"` **key here** `\"\"\", old=False)`\n- Install in development mode with `python3 -m pip install -e .`\n- Ready to use, run the server and check if `telethon_test.py` works\n\n### **Telegram Desktop**\n\n- Edit\n  [this file](https://github.com/telegramdesktop/tdesktop/blob/2acedca6b740f6471b6ebe2e0c500bec32a0a94c/Telegram/SourceFiles/mtproto/mtproto_dc_options.cpp#L31-L78):\n  - As always, replace every ip with `127.0.0.1` (localhost), and every port\n    with `4430`\n  - Remove the existing rsa keys, and replace them with your own, taken from the\n    `data/secrets/pubkey.asc` file on your piltover folder. **Important:** check\n    the newlines thoroughly and make sure they are there, or it won't work.\n- Build the program, ideally with GitHub Actions\n- Put the executable in a folder, e.g. `tdesk`\n- Since we don't save the auth keys, you should delete the leftover files from\n  tdesktop at every run. You can do this conveniently and run your custom\n  TDesktop with this command:\n\n```shell\n$ rm -rf tdata/ DebugLogs/ log.txt \u0026\u0026 c \u0026\u0026 ./Telegram\n```\n\n### **Telegram for Android**\n\n- Clone the repo (optional: checkout the commit I used)\n```shell\n$ git clone https://github.com/DrKLO/Telegram.git\n$ cd Telegram\n$ # Optional: checkout the commit I used\n$ git checkout 5bc1c3dce0e9108615c784a565051e54246fe0cb\n```\n- Edit this file:\n  https://github.com/DrKLO/Telegram/blob/5bc1c3dce0e9108615c784a565051e54246fe0cb/TMessagesProj/jni/tgnet/ConnectionsManager.cpp#L1779-L1839\n  - Replace every ip with your local ip address. It can be `127.0.0.1`\n    (localhost) only in the case you're running the app with an emulator on the\n    same machine the server is running. Otherwise, change it with e.g.\n    `192.168.1.35` (the LAN ip address of your machine, which you can obtain by running `ip -c a`).\n  - Replace every port with `4430`\n  - Either delete or change IPv6 addresses to `::1` (localhost). This will break the\n    IPv6 support, but if you know what you're doing, you can change it to your ipv6 address.\n  - In DataCenters like DC2 there are multiple ip addresses, you should delete the extra ones and change only the first one.\n- Edit this file:\n  https://github.com/DrKLO/Telegram/blob/5bc1c3dce0e9108615c784a565051e54246fe0cb/TMessagesProj/jni/tgnet/Handshake.cpp#L366-L385\n  - Remove the existing rsa keys, and replace them with your own, taken from the\n    `data/secrets/pubkey.asc` file on your piltover folder. **Important:** check\n    the newlines thoroughly and make sure they are there, or it won't work. This\n    took me way too much debugging time to realize that the missing newlines was\n    the cause of the app crashes.\n  - Change the unsigned fingerprint with the one you got from the server logs (e.g. `9400d6d30b7c8fdb`)\n- If you haven't already, check the official repo for the build instructions.\nGenerally, remember to edit `TMessagesProj/src/main/java/org/telegram/messenger/BuildVars.java`\n- Build the app, and see if it works.\n\n### **Telegram WebK**\n\n- Clone repo and install dependencies:\n  - ```shell\n    $ git clone https://github.com/morethanwords/tweb\n    $ cd tweb\n    $ npm i -g pnpm\n    $ pnpm install\n    ```\n- Edit the values in this file:\n  https://github.com/morethanwords/tweb/blob/f2827d9c19616a560346bd1662665ca30dc54668/src/lib/mtproto/dcConfigurator.ts#L50\n  - Change `` const chosenServer = `wss://...` `` to:\n  - ```typescript\n    const chosenServer = `ws://127.0.0.1:3000/proxy`;\n    ```\n  - Change every datacenter ip and port below, respectively to `127.0.0.1`\n    (localhost) and `3000` (websocket proxy port)\n    https://github.com/morethanwords/tweb/blob/f2827d9c19616a560346bd1662665ca30dc54668/src/lib/mtproto/dcConfigurator.ts#L58-L70\n- Edit the values in this file:\n  https://github.com/morethanwords/tweb/blob/f2827d9c19616a560346bd1662665ca30dc54668/src/lib/mtproto/rsaKeysManager.ts#L69-L78\n  - Change the `modulus` to the **lowercase** string of `prime` obtained previously\n- Run the websocket proxy from piltover\n  - ```shell\n    $ poetry run python tools/websocket_proxy.py\n    ```\n- Run with `npm start`\n- Wait some time for the app to compile\n- Open the app in your browser (usually `https://0.0.0.0:8080/`)\n\n### **Telegram WebZ**\n\n- #TODO: WebZ instructions\n\n### **Nimgram**\n\n- #TODO: the client is currently under active development and refactoring, so I\n  will wait until a working version is released\n\n### Telegram X/TDLib\n\n- #TODO: add instructions. I haven't figured out how it should be done yet.\n\n#### _Make a pull request if you want to add instructions for your own client._\n\n## How it works\n\n- The client connects with TCP sockets to the server (websockets for web\n  clients)\n- The first bytes sent within a new connection determine the used\n  [transport](https://core.telegram.org/mtproto/mtproto-transports)\n  - `0xef`: Abridged\n  - `0xeeeeeeee`: Intermediate\n  - `0xdddddddd`: Padded Intermediate\n  - `[length: 4 bytes][0x00000000]`: TCP Full, distinguishable by the empty\n    `seq_no` (`0x00000000`)\n  - `[presumably random bytes]`: _Usually_ and\n    [Obfuscated](https://core.telegram.org/mtproto/mtproto-transports#transport-obfuscation)\n    transport\n  - To distinguish between `TCP Full` and `Obfuscated` transports, a buffered\n    reader is needed, to allow for peeking the stream without consuming it.\n- **Type Language** (TL) Data Serialization\n  - In piltover, the TL de/serialization is JIT (Just In Time), allowing for an\n    easy json-like interface at the cost of slow type checking at runtime\n    (#TODO: do something about this) without complex code-generation parsers\n  - The TL parser (`tools/gen_tl.py`) utility uses **`jinja2`** to generate the\n    `api_tl.py` / `mtproto_tl.py` files from the official TDesktop repo. (#TODO\n    retrieve as much old schema layers for multi-layer support)\n- [**Authorization Key**](https://core.telegram.org/mtproto/auth_key) generation\n  - An authorization process starts, done by the `authorize()` method of the\n    piltover's `Server` class.\n  - Generate random prime numbers for `pq` decomposition, a proof of work to\n    avoid clients' DoS to the server\n  - Either use an old algorithm or `RSA_PAD` to encrypt the inner data payload\n  - The server checks the stuff it needs to check, the client too\n  - If everything went correctly, we are authorized\n  - It is worth noting that every auth key has its own id (the 8 lower order\n    bytes of `SHA1(auth_key)`)\n  - Apart from the auth key id, every session has its own arbitrary (client\n    provided) session_id, bound to the auth key. #TODO: Piltover doesn't\n    currently check this value\n- **Sign in / sign up process**\n  - Client sends `invokeWithLayer(initConnection(getConfig(...)))`\n  - Client signs in with number / sms\n  - Run the server and see the logs to find out more...\n\n## Why\n\nOne day, my Telegram account stopped working properly due to an internal server\nerror originating from a supposedly corrupted message I forwarded. Every time\nthe client tried to fetch new messages from private chats, it would face a\n`[500 STORE_INVALID_OBJECT_TYPE]` error. Hopefully, the bug was fixed in ~1/2\ndays after being reported, but the fact that it happened at all motivated me\nenough to try building my own server. In several days, I managed to make it\n_kinda_ work :)\n\n## Miscellaneous\n\nList of other server implementations I found:\n\n- https://github.com/teamgram/teamgram-server\n- https://github.com/aykutalparslan/Telegram-Server, moved to\n  https://github.com/aykutalparslan/Ferrite\n- https://github.com/loyldg/mytelegram\n- https://github.com/nebula-chat/telegramd (now gone, probably moved to\n  teamgram: https://github.com/nebula-chat/chatengine)\n\nVarious applications similar to Telegram (probably using a custom MTProto\nbackend):\n\n- https://nebula.chat/\n- https://potato.im/\n- https://icq.com/ (not sure about this one, but the clients are a copycat of\n  Telegram's)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdavidegalilei%2Fpiltover","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdavidegalilei%2Fpiltover","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdavidegalilei%2Fpiltover/lists"}