hidesay-bot 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. hidesay_bot-0.2.0/.gitignore +39 -0
  2. hidesay_bot-0.2.0/CHANGELOG.md +46 -0
  3. hidesay_bot-0.2.0/CONTRIBUTING.md +82 -0
  4. hidesay_bot-0.2.0/LICENSE +21 -0
  5. hidesay_bot-0.2.0/PKG-INFO +273 -0
  6. hidesay_bot-0.2.0/README.md +237 -0
  7. hidesay_bot-0.2.0/SECURITY.md +115 -0
  8. hidesay_bot-0.2.0/docs/API_COVERAGE.md +72 -0
  9. hidesay_bot-0.2.0/docs/CRYPTOGRAPHY.md +383 -0
  10. hidesay_bot-0.2.0/docs/RESEARCH.md +423 -0
  11. hidesay_bot-0.2.0/docs/api/bot.rst +26 -0
  12. hidesay_bot-0.2.0/docs/api/client.rst +92 -0
  13. hidesay_bot-0.2.0/docs/api/crypto.rst +25 -0
  14. hidesay_bot-0.2.0/docs/api/errors.rst +254 -0
  15. hidesay_bot-0.2.0/docs/api/index.rst +50 -0
  16. hidesay_bot-0.2.0/docs/api/keyboard.rst +20 -0
  17. hidesay_bot-0.2.0/docs/api/models.rst +159 -0
  18. hidesay_bot-0.2.0/docs/api/routing.rst +52 -0
  19. hidesay_bot-0.2.0/docs/api/storage.rst +19 -0
  20. hidesay_bot-0.2.0/docs/api/webhook.rst +17 -0
  21. hidesay_bot-0.2.0/docs/architecture/index.md +162 -0
  22. hidesay_bot-0.2.0/docs/conf.py +118 -0
  23. hidesay_bot-0.2.0/docs/deployment/index.md +153 -0
  24. hidesay_bot-0.2.0/docs/examples.md +46 -0
  25. hidesay_bot-0.2.0/docs/getting-started/configuration.md +135 -0
  26. hidesay_bot-0.2.0/docs/getting-started/installation.md +81 -0
  27. hidesay_bot-0.2.0/docs/getting-started/quickstart.md +120 -0
  28. hidesay_bot-0.2.0/docs/guides/async-and-sync.md +124 -0
  29. hidesay_bot-0.2.0/docs/guides/command-line.md +100 -0
  30. hidesay_bot-0.2.0/docs/guides/commands-and-buttons.md +192 -0
  31. hidesay_bot-0.2.0/docs/guides/errors.md +151 -0
  32. hidesay_bot-0.2.0/docs/guides/events.md +326 -0
  33. hidesay_bot-0.2.0/docs/guides/long-polling.md +101 -0
  34. hidesay_bot-0.2.0/docs/guides/messages.md +232 -0
  35. hidesay_bot-0.2.0/docs/guides/openpgp.md +195 -0
  36. hidesay_bot-0.2.0/docs/guides/rate-limiting.md +116 -0
  37. hidesay_bot-0.2.0/docs/guides/storage.md +115 -0
  38. hidesay_bot-0.2.0/docs/guides/webhook.md +203 -0
  39. hidesay_bot-0.2.0/docs/index.rst +133 -0
  40. hidesay_bot-0.2.0/docs/openapi-reference/index.md +18 -0
  41. hidesay_bot-0.2.0/docs/openapi-reference/operations.md +608 -0
  42. hidesay_bot-0.2.0/docs/openapi.json +991 -0
  43. hidesay_bot-0.2.0/docs/project/changelog.md +2 -0
  44. hidesay_bot-0.2.0/docs/project/contributing.md +2 -0
  45. hidesay_bot-0.2.0/docs/project/testing.md +76 -0
  46. hidesay_bot-0.2.0/docs/project/versioning.md +49 -0
  47. hidesay_bot-0.2.0/docs/security/index.md +2 -0
  48. hidesay_bot-0.2.0/docs/troubleshooting/faq.md +85 -0
  49. hidesay_bot-0.2.0/docs/troubleshooting/index.md +94 -0
  50. hidesay_bot-0.2.0/examples/01_hello_world.py +20 -0
  51. hidesay_bot-0.2.0/examples/02_echo_bot.py +33 -0
  52. hidesay_bot-0.2.0/examples/03_commands.py +61 -0
  53. hidesay_bot-0.2.0/examples/04_private_messages.py +54 -0
  54. hidesay_bot-0.2.0/examples/05_group_bot.py +92 -0
  55. hidesay_bot-0.2.0/examples/06_inline_buttons.py +50 -0
  56. hidesay_bot-0.2.0/examples/07_callbacks.py +80 -0
  57. hidesay_bot-0.2.0/examples/08_long_polling.py +45 -0
  58. hidesay_bot-0.2.0/examples/09_fastapi_webhook.py +40 -0
  59. hidesay_bot-0.2.0/examples/10_encrypted_bot.py +49 -0
  60. hidesay_bot-0.2.0/examples/11_generate_pgp_keys.py +43 -0
  61. hidesay_bot-0.2.0/examples/12_send_encrypted_message.py +46 -0
  62. hidesay_bot-0.2.0/examples/13_custom_handlers.py +76 -0
  63. hidesay_bot-0.2.0/examples/14_error_handling.py +78 -0
  64. hidesay_bot-0.2.0/examples/15_rate_limiting.py +66 -0
  65. hidesay_bot-0.2.0/examples/16_async_bot.py +52 -0
  66. hidesay_bot-0.2.0/examples/17_sync_client.py +49 -0
  67. hidesay_bot-0.2.0/examples/18_persistent_updates.py +45 -0
  68. hidesay_bot-0.2.0/examples/19_advanced_configuration.py +58 -0
  69. hidesay_bot-0.2.0/examples/20_complete_bot.py +133 -0
  70. hidesay_bot-0.2.0/pyproject.toml +177 -0
  71. hidesay_bot-0.2.0/src/hidesay_bot/__init__.py +304 -0
  72. hidesay_bot-0.2.0/src/hidesay_bot/__main__.py +319 -0
  73. hidesay_bot-0.2.0/src/hidesay_bot/_http.py +311 -0
  74. hidesay_bot-0.2.0/src/hidesay_bot/_secrets.py +199 -0
  75. hidesay_bot-0.2.0/src/hidesay_bot/_spec.py +385 -0
  76. hidesay_bot-0.2.0/src/hidesay_bot/_validate.py +276 -0
  77. hidesay_bot-0.2.0/src/hidesay_bot/_version.py +1 -0
  78. hidesay_bot-0.2.0/src/hidesay_bot/bot.py +3681 -0
  79. hidesay_bot-0.2.0/src/hidesay_bot/client.py +2450 -0
  80. hidesay_bot-0.2.0/src/hidesay_bot/crypto/__init__.py +33 -0
  81. hidesay_bot-0.2.0/src/hidesay_bot/crypto/_lib.py +326 -0
  82. hidesay_bot-0.2.0/src/hidesay_bot/crypto/keys.py +1093 -0
  83. hidesay_bot-0.2.0/src/hidesay_bot/engine.py +547 -0
  84. hidesay_bot-0.2.0/src/hidesay_bot/errors.py +888 -0
  85. hidesay_bot-0.2.0/src/hidesay_bot/filters.py +661 -0
  86. hidesay_bot-0.2.0/src/hidesay_bot/keyboard.py +457 -0
  87. hidesay_bot-0.2.0/src/hidesay_bot/models.py +2638 -0
  88. hidesay_bot-0.2.0/src/hidesay_bot/py.typed +0 -0
  89. hidesay_bot-0.2.0/src/hidesay_bot/ratelimit.py +246 -0
  90. hidesay_bot-0.2.0/src/hidesay_bot/router.py +776 -0
  91. hidesay_bot-0.2.0/src/hidesay_bot/storage.py +1257 -0
  92. hidesay_bot-0.2.0/src/hidesay_bot/webhook.py +440 -0
  93. hidesay_bot-0.2.0/src/hidesay_bot/wire.py +157 -0
  94. hidesay_bot-0.2.0/tests/__init__.py +0 -0
  95. hidesay_bot-0.2.0/tests/conftest.py +103 -0
  96. hidesay_bot-0.2.0/tests/fakeapi.py +538 -0
  97. hidesay_bot-0.2.0/tests/fixtures/attachment.pgp +0 -0
  98. hidesay_bot-0.2.0/tests/fixtures/bot.pub.asc +15 -0
  99. hidesay_bot-0.2.0/tests/fixtures/bot.sec.asc +14 -0
  100. hidesay_bot-0.2.0/tests/fixtures/bot_locked.sec.asc +16 -0
  101. hidesay_bot-0.2.0/tests/fixtures/bot_old.pub.asc +15 -0
  102. hidesay_bot-0.2.0/tests/fixtures/bot_old.sec.asc +14 -0
  103. hidesay_bot-0.2.0/tests/fixtures/msg_for_old_key.asc +14 -0
  104. hidesay_bot-0.2.0/tests/fixtures/msg_media.asc +16 -0
  105. hidesay_bot-0.2.0/tests/fixtures/msg_not_for_bot.asc +12 -0
  106. hidesay_bot-0.2.0/tests/fixtures/msg_rsa_signed.asc +27 -0
  107. hidesay_bot-0.2.0/tests/fixtures/msg_text_signed.asc +14 -0
  108. hidesay_bot-0.2.0/tests/fixtures/msg_text_unsigned.asc +12 -0
  109. hidesay_bot-0.2.0/tests/fixtures/phone_ed.pub.asc +22 -0
  110. hidesay_bot-0.2.0/tests/fixtures/phone_ed.sec.asc +24 -0
  111. hidesay_bot-0.2.0/tests/fixtures/phone_rsa.pub.asc +70 -0
  112. hidesay_bot-0.2.0/tests/fixtures/phone_rsa.sec.asc +130 -0
  113. hidesay_bot-0.2.0/tests/interop/Interop.java +372 -0
  114. hidesay_bot-0.2.0/tests/interop/__init__.py +0 -0
  115. hidesay_bot-0.2.0/tests/interop/test_pgpainless.py +170 -0
  116. hidesay_bot-0.2.0/tests/test_bot.py +2265 -0
  117. hidesay_bot-0.2.0/tests/test_cli.py +469 -0
  118. hidesay_bot-0.2.0/tests/test_client.py +2035 -0
  119. hidesay_bot-0.2.0/tests/test_crypto.py +1115 -0
  120. hidesay_bot-0.2.0/tests/test_docs.py +235 -0
  121. hidesay_bot-0.2.0/tests/test_engine.py +642 -0
  122. hidesay_bot-0.2.0/tests/test_errors.py +302 -0
  123. hidesay_bot-0.2.0/tests/test_filters.py +487 -0
  124. hidesay_bot-0.2.0/tests/test_http.py +479 -0
  125. hidesay_bot-0.2.0/tests/test_keyboard.py +393 -0
  126. hidesay_bot-0.2.0/tests/test_lifecycle.py +393 -0
  127. hidesay_bot-0.2.0/tests/test_models.py +1229 -0
  128. hidesay_bot-0.2.0/tests/test_parity.py +233 -0
  129. hidesay_bot-0.2.0/tests/test_polling.py +585 -0
  130. hidesay_bot-0.2.0/tests/test_ratelimit.py +312 -0
  131. hidesay_bot-0.2.0/tests/test_router.py +508 -0
  132. hidesay_bot-0.2.0/tests/test_secrets.py +291 -0
  133. hidesay_bot-0.2.0/tests/test_storage.py +709 -0
  134. hidesay_bot-0.2.0/tests/test_syncbot.py +899 -0
  135. hidesay_bot-0.2.0/tests/test_validate.py +528 -0
  136. hidesay_bot-0.2.0/tests/test_webhook.py +900 -0
  137. hidesay_bot-0.2.0/tests/test_wire.py +269 -0
@@ -0,0 +1,39 @@
1
+ # environments
2
+ .venv/
3
+ venv/
4
+ .env
5
+ .env.*
6
+ !.env.example
7
+
8
+ # python
9
+ __pycache__/
10
+ *.py[cod]
11
+ *.egg-info/
12
+ build/
13
+ dist/
14
+
15
+ # tools
16
+ .cache/
17
+ .pytest_cache/
18
+ .mypy_cache/
19
+ .ruff_cache/
20
+ .coverage
21
+ .coverage.*
22
+ htmlcov/
23
+ coverage.xml
24
+ *.log
25
+
26
+ # docs output
27
+ docs/_build/
28
+
29
+ # the tests that drive a real server live with the server, not here
30
+ /tests/integration/
31
+
32
+ # never commit these: bot keys, tokens, local storage
33
+ *.asc
34
+ !tests/fixtures/*.asc
35
+ *.key
36
+ *.db
37
+ *.db-wal
38
+ *.db-shm
39
+ *.lock
@@ -0,0 +1,46 @@
1
+ # Changelog
2
+
3
+ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the version
4
+ numbers follow [Semantic Versioning](https://semver.org/), with the rules in
5
+ `docs/project/versioning.md`.
6
+
7
+ ## [0.2.0] - 2026-11-15
8
+
9
+ First release as hidesay-bot. Versions 0.1.0 and 0.1.1 came out under the app's old name, as
10
+ PGPCbot: this is the same library, and what changes for code written against it is under
11
+ "Changed".
12
+
13
+ ### Added
14
+
15
+ - `AsyncClient` and `Client`: all 22 operations of Bot API 1.0.0 (`openapi.json`), plus media
16
+ downloads, with typed models, client-side checks that match the server's, retries with
17
+ backoff and a stable `sendId`, waiting on `429`, and a client-side rate limiter.
18
+ - `Bot` and `SyncBot`: handlers for messages, commands, buttons, edited and deleted messages,
19
+ and membership changes; filters, routers, middleware, error handlers, automatic answers to
20
+ button presses; startup and shutdown hooks; `request_stop()` from a handler or another thread.
21
+ - `resolve()`, for `GET /resolve/{username}`: the handle somebody writes at a bot, turned into
22
+ an id. One pool of names covers people, groups and channels, so the answer says which it was
23
+ - `Resolved`, with `HandleKind`, a `ChatPeer` for a person or a `ChatCard` for a public group
24
+ or channel. A leading `@` and the case are taken off before the request goes out, and
25
+ `UsernameNotFoundError` (404 `username_not_found`) is what a free handle, a group with no
26
+ public name and a closed account all answer.
27
+ - OpenPGP: making, loading, protecting and rotating the bot's key; automatic encryption and
28
+ signing in protected chats; decryption and signature checks on incoming messages, including
29
+ the compressed messages phones send; reading the app's internal message format; downloading
30
+ and decrypting attachments; pinning keys the first time they are seen.
31
+ - Long polling and an ASGI webhook (FastAPI, Starlette, uvicorn) with "at least once"
32
+ delivery, a durable `SQLiteStorage` queue, and failed updates kept so they can be retried.
33
+ - The `hidesay-bot` command line: `keygen`, `fingerprint`, `whoami`, `publish-key`, `webhook`.
34
+ - The documentation website at <https://vntcore.github.io/hidesay-bot/>, built from the
35
+ docstrings and the pages in `docs/`.
36
+
37
+ ### Changed
38
+
39
+ - The package is `hidesay-bot` on PyPI, it is imported as `hidesay_bot` and the command is
40
+ `hidesay-bot`.
41
+ - The environment variables are `HIDESAY_BOT_TOKEN`, `HIDESAY_API`, `HIDESAY_BOT_KEY` and the
42
+ rest of the family, in place of `PGPCHAT_*`.
43
+ - The default server is `https://api.hidesay.com`.
44
+ - Webhook deliveries carry the secret in `X-Hidesay-Bot-Api-Secret-Token`, and a refused one is
45
+ answered with `WWW-Authenticate: Hidesay-Secret-Token`.
46
+ - `PGPCbotError` is `HidesayBotError`, and the loggers are `hidesay_bot.*`.
@@ -0,0 +1,82 @@
1
+ # Contributing
2
+
3
+ ## Setting up
4
+
5
+ ```bash
6
+ python -m venv .venv
7
+ . .venv/bin/activate # Windows: .venv\Scripts\activate
8
+ pip install -e . --group dev # needs pip 25.1 or newer
9
+ ```
10
+
11
+ ## Before you open a pull request
12
+
13
+ ```bash
14
+ ruff check src tests examples scripts
15
+ ruff format --check src tests examples scripts
16
+ mypy
17
+ pytest
18
+ ```
19
+
20
+ `mypy` runs in `strict` mode on `src/hidesay_bot`. Any warning during the tests is an error
21
+ (`filterwarnings = error`): if a test expects a warning, it has to say so.
22
+
23
+ ### Tests that need more
24
+
25
+ - **Integration** (`-m integration`): the tests that drive a real Hidesay server need a copy
26
+ of the server to start one, so they live with it and not in this repository. The marker stays
27
+ declared here, for a working copy that has both.
28
+ - **Interoperability** (`-m interop`): they check the SDK against PGPainless 1.7.6, the phones'
29
+ library. You need Java 21 or newer and `HIDESAY_BOT_PGPAINLESS_CP` set to a classpath with
30
+ pgpainless-core 1.7.6, Bouncy Castle 1.80 (bcprov, bcpg, bcutil), slf4j-api, jsr305 and
31
+ kotlin-stdlib. The CI job in `.github/workflows/ci.yml` shows how to download them.
32
+
33
+ Without those variables these tests are skipped, they don't fail.
34
+
35
+ ## How the code is organised
36
+
37
+ - `src/hidesay_bot/_spec.py` describes each API operation once. `client.py` has the two shells,
38
+ async and blocking, with the same methods. A new method goes in both:
39
+ `tests/test_parity.py` checks that the signatures match.
40
+ - The client-side checks mirror the server's own rules, so a request that passes them is not
41
+ refused for that reason. When the server changes a rule, change it here too, with a
42
+ test.
43
+ - Everything that touches OpenPGP goes through `src/hidesay_bot/crypto/_lib.py`. A change there
44
+ needs the interoperability tests with PGPainless, not only the unit tests.
45
+ - Comments explain why, not what. Code, comments and documentation are in English; keep the
46
+ documentation simple, for readers whose first language is not English.
47
+
48
+ ## Documentation
49
+
50
+ The API reference is generated from the docstrings, so a docstring is documentation. Write it
51
+ in Google style with reStructuredText markup, like the ones already there: a summary line,
52
+ then `Args:`, `Returns:`, `Raises:`, `Attributes:` and `Example:` sections as needed. Names in
53
+ single backticks become links (`Bot.send_message`); code and values go in double backticks.
54
+ `tests/test_docs.py` checks that everything public has a docstring, that every parameter is
55
+ described and that the `>>>` examples still run.
56
+
57
+ Build the site with Sphinx:
58
+
59
+ ```bash
60
+ pip install -e . --group docs
61
+ sphinx-build -W --keep-going -n -b html docs docs/_build/html
62
+ ```
63
+
64
+ then open `docs/_build/html/index.html`. `-n` reports every link that does not resolve and
65
+ `-W` turns warnings into errors, as in the CI. To read one page without a browser:
66
+ `python -m pydoc hidesay_bot.Bot`.
67
+
68
+ The guides are Markdown pages in `docs/`. The examples in the documentation must work with the
69
+ real code: if you change an API, update the docstrings, the pages and the scripts in
70
+ `examples/` that use it.
71
+
72
+ `docs/openapi.json` is an exact copy of the document the server publishes for its Bot API. When
73
+ the server's spec changes, copy it again and run `python scripts/openapi_pages.py`: it rewrites
74
+ `docs/openapi-reference/operations.md`, and stops if an operation has no SDK method. The CI checks
75
+ that the page is up to date.
76
+
77
+ ## Releases
78
+
79
+ 1. update `src/hidesay_bot/_version.py` and `CHANGELOG.md`;
80
+ 2. create a `vX.Y.Z` tag;
81
+ 3. the `release.yml` workflow builds and checks the packages. Publishing to PyPI needs a manual
82
+ approval of the `pypi` environment.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Hidesay
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,273 @@
1
+ Metadata-Version: 2.4
2
+ Name: hidesay-bot
3
+ Version: 0.2.0
4
+ Summary: Bots for Hidesay: the whole Bot API, end-to-end encryption included.
5
+ Project-URL: Homepage, https://hidesay.com
6
+ Project-URL: Source, https://github.com/vntcore/hidesay-bot
7
+ Project-URL: Documentation, https://vntcore.github.io/hidesay-bot/
8
+ Project-URL: Issues, https://github.com/vntcore/hidesay-bot/issues
9
+ Project-URL: Changelog, https://github.com/vntcore/hidesay-bot/blob/main/CHANGELOG.md
10
+ Author: Hidesay
11
+ License-Expression: MIT
12
+ License-File: LICENSE
13
+ Keywords: asyncio,bot,bot-api,chatbot,end-to-end-encryption,hidesay,openpgp,webhook
14
+ Classifier: Development Status :: 4 - Beta
15
+ Classifier: Framework :: AsyncIO
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3 :: Only
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Programming Language :: Python :: 3.14
24
+ Classifier: Topic :: Communications :: Chat
25
+ Classifier: Topic :: Security :: Cryptography
26
+ Classifier: Typing :: Typed
27
+ Requires-Python: >=3.11
28
+ Requires-Dist: httpx2<3,>=2.13.1
29
+ Requires-Dist: pysequoia<0.2,>=0.1.35
30
+ Requires-Dist: rpgp-py<0.21,>=0.20
31
+ Provides-Extra: http2
32
+ Requires-Dist: httpx2[http2]<3,>=2.13.1; extra == 'http2'
33
+ Provides-Extra: webhook
34
+ Requires-Dist: uvicorn>=0.30; extra == 'webhook'
35
+ Description-Content-Type: text/markdown
36
+
37
+ # hidesay-bot
38
+
39
+ [![ci](https://github.com/vntcore/hidesay-bot/actions/workflows/ci.yml/badge.svg)](https://github.com/vntcore/hidesay-bot/actions/workflows/ci.yml)
40
+
41
+ A Python library for writing bots for [Hidesay](https://hidesay.com), a messenger whose
42
+ private chats are end-to-end encrypted with OpenPGP. It covers the whole Bot API, and it
43
+ handles the encryption for you.
44
+
45
+ **Documentation: <https://vntcore.github.io/hidesay-bot/>** - the guides and the whole API reference.
46
+
47
+ ```python
48
+ from hidesay_bot import Bot
49
+
50
+ bot = Bot() # reads the token from HIDESAY_BOT_TOKEN
51
+
52
+
53
+ @bot.command("start", description="Say hello")
54
+ async def start(message):
55
+ await message.reply("Hello! Send me anything and I will repeat it.")
56
+
57
+
58
+ @bot.on_message()
59
+ async def echo(message):
60
+ await message.reply(f"You said: {message.text}")
61
+
62
+
63
+ bot.run()
64
+ ```
65
+
66
+ If the bot has a published OpenPGP key, its private chats are end-to-end encrypted. The same
67
+ code still works: messages reach your handlers already decrypted, and replies are encrypted
68
+ for the people who should read them. You don't change anything in the handlers.
69
+
70
+ ## What it does
71
+
72
+ - **Every operation of the Bot API**, with typed models, as async calls (`AsyncClient`)
73
+ and as blocking calls (`Client`).
74
+ - **A high-level bot** with handlers for messages, commands (`/start` and `/start@yourbot`),
75
+ button presses, edited and deleted messages, and changes to the bot's membership. Filters you
76
+ can combine, routers to split a bot into modules, middleware, and one place to handle errors.
77
+ - **Automatic OpenPGP**: incoming messages are decrypted and their signatures checked;
78
+ replies are encrypted for every member of the chat and signed. A message that should be
79
+ encrypted is never sent in clear. Tested with real keys and messages from the phones'
80
+ library (PGPainless 1.7.6).
81
+ - **Long polling and webhooks** with "at least once" delivery done right: no update is lost
82
+ when a handler fails, no update is confirmed to the server before it is safe, and a SQLite
83
+ queue survives restarts.
84
+ - **Safe retries**: every send has a `sendId`, so a retry after a timeout never writes the
85
+ message twice. After a `429` the client waits as long as the server asks. A client-side rate
86
+ limiter uses the same algorithm as the server.
87
+ - **Security**: the token never shows up in logs, exceptions or `repr`. Secrets can come from
88
+ environment variables or from files (Docker and Kubernetes secrets too). The webhook checks
89
+ its secret in constant time.
90
+
91
+ ## Install
92
+
93
+ You need Python 3.11 or newer (tested up to 3.14). The OpenPGP engines come as prebuilt
94
+ wheels for Linux and macOS (x86-64 and arm64) and for Windows (x86-64), so there is nothing
95
+ to compile; anywhere else pip builds them and you need a Rust toolchain.
96
+
97
+ ```bash
98
+ pip install hidesay-bot
99
+ ```
100
+
101
+ From a clone of the repository, `pip install .`, or `pip install -e .` to work on the code.
102
+
103
+ Optional extras: `hidesay-bot[webhook]` adds uvicorn to serve the webhook, `hidesay-bot[http2]` adds
104
+ HTTP/2 support. Check that it works:
105
+
106
+ ```bash
107
+ python -c "import hidesay_bot; print(hidesay_bot.__version__)"
108
+ hidesay-bot --version
109
+ ```
110
+
111
+ ## Your first bot
112
+
113
+ 1. Create the bot in the app: Settings → Account → My bots, or write to `@bot`. Copy the
114
+ token when you see it: it is shown only once.
115
+ 2. Put it in an environment variable, not in your code:
116
+
117
+ ```bash
118
+ export HIDESAY_BOT_TOKEN="pgpbot_..." # PowerShell: $env:HIDESAY_BOT_TOKEN="pgpbot_..."
119
+ export HIDESAY_API="https://api.hidesay.com" # only if your server is a different one
120
+ ```
121
+
122
+ 3. Check the token: `hidesay-bot whoami`.
123
+ 4. Save the example at the top of this page as `bot.py`, run `python bot.py`, and write to your
124
+ bot from the app. Stop it with Ctrl+C: it finishes the messages it is working on first.
125
+
126
+ ## Commands and buttons
127
+
128
+ ```python
129
+ from hidesay_bot import Bot, Button, InlineKeyboard
130
+
131
+ bot = Bot()
132
+
133
+
134
+ @bot.command("weather", description="Forecast for a town")
135
+ async def weather(message, command):
136
+ town = command.args or "Rome"
137
+ keyboard = InlineKeyboard.build(
138
+ [Button.callback("Hourly", f"hourly:{town}"), Button.callback("Another town", "ask")],
139
+ [Button.link("On the web", "https://example.org/weather")],
140
+ )
141
+ await message.reply(f"{town}: sun, sun, rain.", markup=keyboard)
142
+
143
+
144
+ @bot.on_callback_query(prefix="hourly:")
145
+ async def hourly(query):
146
+ await query.answer("Here is the hourly view")
147
+ await query.edit_message(f"{query.data[7:]}: 14° at 10, 18° at 14.", markup=None)
148
+
149
+
150
+ bot.run()
151
+ ```
152
+
153
+ Commands registered with a `description` go into the app's command menu with
154
+ `await bot.sync_commands()`.
155
+
156
+ ## An end-to-end encrypted bot
157
+
158
+ ```bash
159
+ hidesay-bot keygen --name Weather --username weatherbot --out bot-key.asc --passphrase-env BOT_KEY_PASSPHRASE
160
+ ```
161
+
162
+ ```python
163
+ import os
164
+ from hidesay_bot import Bot
165
+
166
+ bot = Bot(
167
+ pgp_private_key="bot-key.asc",
168
+ pgp_passphrase=os.environ["BOT_KEY_PASSPHRASE"],
169
+ publish_key=True, # on the first run, publish the key: private chats become encrypted
170
+ )
171
+
172
+
173
+ @bot.on_message()
174
+ async def echo(message):
175
+ # already decrypted; message.verification says if the sender's signature is valid
176
+ await message.reply(f"Got it, privately: {message.text}")
177
+
178
+
179
+ bot.run()
180
+ ```
181
+
182
+ Publishing the key cannot be undone through the API: only the bot's owner can switch the bot
183
+ back to readable chats, from the console in the app. Read [the cryptography page][crypto]
184
+ before you go to production.
185
+
186
+ ## Webhook with FastAPI
187
+
188
+ ```python
189
+ import os
190
+ from fastapi import FastAPI
191
+ from hidesay_bot import Bot, SQLiteStorage
192
+
193
+ bot = Bot(storage=SQLiteStorage("bot.db"))
194
+
195
+
196
+ @bot.on_message()
197
+ async def echo(message):
198
+ await message.reply(message.text or "...")
199
+
200
+
201
+ webhook = bot.create_webhook_app(
202
+ secret=os.environ["WEBHOOK_SECRET"],
203
+ url="https://example.org/hidesay", # registered with the server at start-up
204
+ )
205
+ app = FastAPI(lifespan=bot.lifespan)
206
+ app.add_route("/hidesay", webhook, methods=["POST"])
207
+ ```
208
+
209
+ Run it with `uvicorn app:app`. With `SQLiteStorage` every update is saved before the server
210
+ gets its answer, and handled right after: a crash loses nothing.
211
+
212
+ ## Without asyncio
213
+
214
+ ```python
215
+ from hidesay_bot import SyncBot
216
+
217
+ bot = SyncBot()
218
+
219
+
220
+ @bot.command("start")
221
+ def start(message):
222
+ message.reply("Hello!")
223
+
224
+
225
+ bot.run()
226
+ ```
227
+
228
+ `SyncBot` has the same methods as `Bot`, without `await`. Handlers run in a pool of threads.
229
+ For a script that only needs to send one message:
230
+
231
+ ```python
232
+ from hidesay_bot import Client
233
+
234
+ with Client() as api:
235
+ api.send_message(7781, "Last night's backup finished fine.")
236
+ ```
237
+
238
+ ## Documentation
239
+
240
+ The site is at <https://vntcore.github.io/hidesay-bot/>: the guides, and the API reference built from the
241
+ docstrings. It follows `main`.
242
+
243
+ The same reference without a browser: `help(hidesay_bot.Bot)` in Python, or
244
+ `python -m pydoc hidesay_bot.Bot` in a terminal. The pages are Markdown in [`docs/`][docs], and
245
+ from a clone you can build the site yourself:
246
+
247
+ ```bash
248
+ pip install -e . --group docs
249
+ sphinx-build -b html docs docs/_build/html
250
+ ```
251
+
252
+ Good places to start: [quick start][quickstart], [cryptography][crypto],
253
+ [API coverage][coverage], [research and decisions][research]. Ready-to-run examples are in
254
+ [`examples/`][examples].
255
+
256
+ ## Security
257
+
258
+ See [SECURITY.md][security] for the threat model and for how to report a vulnerability. In
259
+ short: keep the token and the private key out of your code and out of repositories. If the
260
+ token leaks, `await bot.revoke_token()` burns it at once.
261
+
262
+ ## License
263
+
264
+ MIT. The dependencies have their own licenses: httpx2 (BSD-3-Clause), pysequoia (Apache-2.0,
265
+ with Sequoia PGP under LGPL-2.0-or-later inside its wheels), rpgp-py (MIT).
266
+
267
+ [docs]: https://github.com/vntcore/hidesay-bot/tree/main/docs
268
+ [examples]: https://github.com/vntcore/hidesay-bot/tree/main/examples
269
+ [quickstart]: https://github.com/vntcore/hidesay-bot/blob/main/docs/getting-started/quickstart.md
270
+ [crypto]: https://github.com/vntcore/hidesay-bot/blob/main/docs/CRYPTOGRAPHY.md
271
+ [coverage]: https://github.com/vntcore/hidesay-bot/blob/main/docs/API_COVERAGE.md
272
+ [research]: https://github.com/vntcore/hidesay-bot/blob/main/docs/RESEARCH.md
273
+ [security]: https://github.com/vntcore/hidesay-bot/blob/main/SECURITY.md