Configuration¶
Everything has a sensible default. Most bots only need a token, and an encrypted bot also needs its key.
Environment variables¶
The library reads these when you don’t pass the value in code. Every variable that holds a
secret also has a _FILE form: the variable then holds the path of a file with the value,
which is how Docker and Kubernetes secrets work.
Variable |
What it holds |
Used by |
|---|---|---|
|
the bot’s token |
|
|
the API server, without |
the same |
|
the bot’s private key: the armored text, or a path to it |
|
|
the key’s passphrase |
the same |
A value passed in code always wins over the environment.
Keep secrets out of your code
Don’t write the token or the passphrase in a .py file. Files end up in repositories,
backups and screenshots. Use the variables above, a .env file that is never committed, or
a secrets manager. The project has a .env.example to copy.
Bot and SyncBot options¶
Bot and SyncBot take the same options. All of them are keyword arguments, except the token.
from pgpcbot import Bot, SQLiteStorage
bot = Bot(
token=None, # None: read PGPCHAT_BOT_TOKEN
api_url=None, # None: read PGPCHAT_API, else the public server
pgp_private_key="bot-key.asc",
pgp_passphrase=lambda: read_from_vault("bot-key"),
storage=SQLiteStorage("bot.db"),
concurrency=8,
)
Connection¶
Option |
Type |
Default |
Meaning |
|---|---|---|---|
|
|
|
the bot’s token. |
|
|
|
the server’s address, like |
|
|
30 s (10 s to connect) |
how long one HTTP request may take. Long polls get extra time on their own |
|
|
|
how many times to try again, see below |
|
|
|
paces calls like the server does, see Rate limits |
|
|
|
allows an |
|
an httpx2 transport |
|
advanced: a custom transport (a proxy setup, a test double) |
OpenPGP¶
Option |
Type |
Default |
Meaning |
|---|---|---|---|
|
|
|
the bot’s private key. |
|
|
|
the key’s passphrase. |
|
list of |
|
older keys, still tried to decrypt messages after a rotation |
|
|
|
at start-up, publish the key if the server has a different one or none. One-way: see OpenPGP |
|
|
|
never send anything in clear, anywhere: a send into a readable chat raises |
|
|
|
|
|
|
|
remember each person’s key fingerprint and refuse a changed key, for sending and for receiving (trusting keys) |
Running¶
Option |
Type |
Default |
Meaning |
|---|---|---|---|
|
|
|
where updates wait to be handled. A path means |
|
|
|
how many chats are handled at the same time. Updates of one chat are always handled one at a time, in order. For |
|
|
|
an update whose handlers were started this many times without ever finishing (the process crashed or was stopped half way) goes to the failed list instead of running again |
|
|
|
at stop, how many seconds running handlers get to finish |
|
|
|
how long chat details, modes and member keys are cached, in seconds |
|
|
|
stop the spinner of a pressed button if the handler did not answer it |
|
|
|
the name of the bot’s root router |
AsyncClient and Client options¶
The low-level clients take the connection options above (token, api_url, timeout,
retry, allow_insecure_http, transport) and a few more:
Option |
Type |
Default |
Meaning |
|---|---|---|---|
|
|
|
|
|
|
|
the |
|
|
|
your own HTTP client. The library does not close it |
|
|
|
the size of the connection pool |
A bot gives you its client as bot.api: the same connection, the same limits.
Retries¶
from pgpcbot import Bot, RetryPolicy
bot = Bot(retry=RetryPolicy(attempts=6, backoff_max=30.0))
Field |
Default |
Meaning |
|---|---|---|
|
|
tries in total, when the network fails or the server answers |
|
|
the first wait, in seconds; it doubles at each try |
|
|
the longest wait between two tries |
|
|
how many |
|
|
a |
The wait is random between 0 and min(backoff_max, backoff_base * 2^n) (“full jitter”), so
many bots that fail together don’t all come back at the same moment.
RetryPolicy(attempts=1, rate_limit_retries=0) turns retries off.
Logging¶
The library logs under the pgpcbot logger (pgpcbot.bot, pgpcbot.client,
pgpcbot.crypto, pgpcbot.engine, pgpcbot.webhook). It never logs the token, passphrases,
private keys or message text.
bot.run() sets up basic logging at INFO level if your program has not set up logging
itself. To do it yourself:
import logging
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s: %(message)s")
logging.getLogger("pgpcbot").setLevel(logging.DEBUG) # more detail from the library only
bot.run(configure_logging=False)
At DEBUG level, other libraries (httpx2, for example) may log request URLs and timings.
They never see the token in a URL, because it travels in a header.