Skip to main content

hidesay-bot

ci

A Python library for writing bots for Hidesay, a messenger whose private chats are end-to-end encrypted with OpenPGP. It covers the whole Bot API, and it handles the encryption for you.

Documentation: https://vntcore.github.io/hidesay-bot/ - the guides and the whole API reference.

from hidesay_bot import Bot

bot = Bot()  # reads the token from HIDESAY_BOT_TOKEN


@bot.command("start", description="Say hello")
async def start(message):
    await message.reply("Hello! Send me anything and I will repeat it.")


@bot.on_message()
async def echo(message):
    await message.reply(f"You said: {message.text}")


bot.run()

If the bot has a published OpenPGP key, its private chats are end-to-end encrypted. The same code still works: messages reach your handlers already decrypted, and replies are encrypted for the people who should read them. You don't change anything in the handlers.

What it does

  • Every operation of the Bot API, with typed models, as async calls (AsyncClient) and as blocking calls (Client).
  • A high-level bot with handlers for messages, commands (/start and /start@yourbot), button presses, edited and deleted messages, and changes to the bot's membership. Filters you can combine, routers to split a bot into modules, middleware, and one place to handle errors.
  • Automatic OpenPGP: incoming messages are decrypted and their signatures checked; replies are encrypted for every member of the chat and signed. A message that should be encrypted is never sent in clear. Tested with real keys and messages from the phones' library (PGPainless 1.7.6).
  • Long polling and webhooks with "at least once" delivery done right: no update is lost when a handler fails, no update is confirmed to the server before it is safe, and a SQLite queue survives restarts.
  • Safe retries: every send has a sendId, so a retry after a timeout never writes the message twice. After a 429 the client waits as long as the server asks. A client-side rate limiter uses the same algorithm as the server.
  • Security: the token never shows up in logs, exceptions or repr. Secrets can come from environment variables or from files (Docker and Kubernetes secrets too). The webhook checks its secret in constant time.

Install

You need Python 3.11 or newer (tested up to 3.14). The OpenPGP engines come as prebuilt wheels for Linux and macOS (x86-64 and arm64) and for Windows (x86-64), so there is nothing to compile; anywhere else pip builds them and you need a Rust toolchain.

pip install hidesay-bot

From a clone of the repository, pip install ., or pip install -e . to work on the code.

Optional extras: hidesay-bot[webhook] adds uvicorn to serve the webhook, hidesay-bot[http2] adds HTTP/2 support. Check that it works:

python -c "import hidesay_bot; print(hidesay_bot.__version__)"
hidesay-bot --version

Your first bot

  1. Create the bot in the app: Settings → Account → My bots, or write to @bot. Copy the token when you see it: it is shown only once.

  2. Put it in an environment variable, not in your code:

    export HIDESAY_BOT_TOKEN="pgpbot_..."          # PowerShell: $env:HIDESAY_BOT_TOKEN="pgpbot_..."
    export HIDESAY_API="https://api.hidesay.com"   # only if your server is a different one
    
  3. Check the token: hidesay-bot whoami.

  4. Save the example at the top of this page as bot.py, run python bot.py, and write to your bot from the app. Stop it with Ctrl+C: it finishes the messages it is working on first.

Commands and buttons

from hidesay_bot import Bot, Button, InlineKeyboard

bot = Bot()


@bot.command("weather", description="Forecast for a town")
async def weather(message, command):
    town = command.args or "Rome"
    keyboard = InlineKeyboard.build(
        [Button.callback("Hourly", f"hourly:{town}"), Button.callback("Another town", "ask")],
        [Button.link("On the web", "https://example.org/weather")],
    )
    await message.reply(f"{town}: sun, sun, rain.", markup=keyboard)


@bot.on_callback_query(prefix="hourly:")
async def hourly(query):
    await query.answer("Here is the hourly view")
    await query.edit_message(f"{query.data[7:]}: 14° at 10, 18° at 14.", markup=None)


bot.run()

Commands registered with a description go into the app's command menu with await bot.sync_commands().

An end-to-end encrypted bot

hidesay-bot keygen --name Weather --username weatherbot --out bot-key.asc --passphrase-env BOT_KEY_PASSPHRASE
import os
from hidesay_bot import Bot

bot = Bot(
    pgp_private_key="bot-key.asc",
    pgp_passphrase=os.environ["BOT_KEY_PASSPHRASE"],
    publish_key=True,   # on the first run, publish the key: private chats become encrypted
)


@bot.on_message()
async def echo(message):
    # already decrypted; message.verification says if the sender's signature is valid
    await message.reply(f"Got it, privately: {message.text}")


bot.run()

Publishing the key cannot be undone through the API: only the bot's owner can switch the bot back to readable chats, from the console in the app. Read the cryptography page before you go to production.

Webhook with FastAPI

import os
from fastapi import FastAPI
from hidesay_bot import Bot, SQLiteStorage

bot = Bot(storage=SQLiteStorage("bot.db"))


@bot.on_message()
async def echo(message):
    await message.reply(message.text or "...")


webhook = bot.create_webhook_app(
    secret=os.environ["WEBHOOK_SECRET"],
    url="https://example.org/hidesay",   # registered with the server at start-up
)
app = FastAPI(lifespan=bot.lifespan)
app.add_route("/hidesay", webhook, methods=["POST"])

Run it with uvicorn app:app. With SQLiteStorage every update is saved before the server gets its answer, and handled right after: a crash loses nothing.

Without asyncio

from hidesay_bot import SyncBot

bot = SyncBot()


@bot.command("start")
def start(message):
    message.reply("Hello!")


bot.run()

SyncBot has the same methods as Bot, without await. Handlers run in a pool of threads. For a script that only needs to send one message:

from hidesay_bot import Client

with Client() as api:
    api.send_message(7781, "Last night's backup finished fine.")

Documentation

The site is at https://vntcore.github.io/hidesay-bot/: the guides, and the API reference built from the docstrings. It follows main.

The same reference without a browser: help(hidesay_bot.Bot) in Python, or python -m pydoc hidesay_bot.Bot in a terminal. The pages are Markdown in docs/, and from a clone you can build the site yourself:

pip install -e . --group docs
sphinx-build -b html docs docs/_build/html

Good places to start: quick start, cryptography, API coverage, research and decisions. Ready-to-run examples are in examples/.

Security

See SECURITY.md for the threat model and for how to report a vulnerability. In short: keep the token and the private key out of your code and out of repositories. If the token leaks, await bot.revoke_token() burns it at once.

License

MIT. The dependencies have their own licenses: httpx2 (BSD-3-Clause), pysequoia (Apache-2.0, with Sequoia PGP under LGPL-2.0-or-later inside its wheels), rpgp-py (MIT).

Metadata

Release files for hidesay-bot 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for hidesay-bot 0.2.0
File Size Uploaded Provenance Actions
hidesay_bot-0.2.0.tar.gz 453.7 kB With provenance Details for hidesay_bot-0.2.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for hidesay-bot 0.2.0
File Interpreter ABI Platform Actions
hidesay_bot-0.2.0-py3-none-any.whl Python 3 none any Details for hidesay_bot-0.2.0-py3-none-any.whl

Total release size: 661.1 kB

Release files / hidesay_bot-0.2.0.tar.gz

Download URL hidesay_bot-0.2.0.tar.gz
Size 453.7 kB
Tags Source
SHA-256 checksum
How to use checksums
09b1fad8b84f0584591177ff5ce382d7b7082ac9bd89e75560f1b03442d519e3
BLAKE2b-256 checksum
How to use checksums
3dcc9ef85b3f2fbccb643a3cc081b2defde97840d429f90d77d102876c1aae07
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 15, 2026.

Transparency log

Release files / hidesay_bot-0.2.0-py3-none-any.whl

Download URL hidesay_bot-0.2.0-py3-none-any.whl
Size 207.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
79bc1d2b8c6ad80c108c3c25b83e737a87f9876fdfa4b5959b6f5451ac87492b
BLAKE2b-256 checksum
How to use checksums
32661081c2dea23bac44a509a7d15a7cb54381804f98edef91d434ef60a57efe
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 15, 2026.

Transparency log

Security for hidesay-bot 0.2.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for hidesay_bot-0.2.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 15, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page