mini-telegram
A small, dependency-light toolkit for building Telegram bots: polling, sending, receiving, media, inline callbacks and access control — without any AI-specific layers (no personas, memory, or LLM integration).
It is an extraction of the generic plumbing that powers the
telegram-ai-bots service
(its core/telegram/* package and the two supporting utilities
retrying_requests_session / defer), cleaned up into a reusable library.
Install
pip install mini-telegram
From a checkout (development install with the dev toolchain):
pip install -e ".[dev]"
Requires Python ≥ 3.10 and requests.
Quick start
Every API function takes the session and bot_token explicitly, so a
single session can be shared between the polling loop and out-of-band jobs,
and everything is easy to test with a fake session.
import time
from minitelegram import get_updates, send_text, telegram_session
TOKEN = "123456:ABC-DEF..."
with telegram_session() as session:
offset = None
while True:
updates = get_updates(session, TOKEN, offset=offset, allowed_updates=["message"])
for update in updates:
offset = update.update_id + 1 # acknowledge
msg = update.message
if msg is None or msg.text is None:
continue
send_text(session, TOKEN, chat_id=msg.chat_id, text=f"You said: {msg.text}")
time.sleep(0.5)
Messages longer than 4096 characters are split automatically (preferring
paragraph, then newline, then hard boundaries). Text is sent as Telegram HTML
after conversion from a small, safe Markdown subset (**bold**, *italic*,
[links](url), `code`, # headings, > quotes); if Telegram rejects
the entities the chunk is retried once as plain text.
Inline keyboards and callbacks
Send a question with buttons, then match button presses back to the pending
question via CallbackManager (persisted to disk when a persist_dir is
given, so presses survive restarts; entries expire after a TTL):
from pathlib import Path
from minitelegram import (
CallbackManager, answer_callback_query, send_question,
telegram_session, get_updates,
)
manager = CallbackManager(persist_dir=Path("/tmp/mybot-callbacks"))
with telegram_session() as session:
# Ask a question with two options.
cb_id = manager.create(chat_id=123, question="Continue?", options=["Yes", "No"])
send_question(
session, TOKEN, chat_id=123,
text="Continue?",
options=["Yes", "No"],
callback_data=[manager.make_callback_data(cb_id, i) for i in range(2)],
)
# In the polling loop:
for update in get_updates(session, TOKEN, offset=offset):
offset = update.update_id + 1
callback = update.callback
if callback is None:
continue
parsed = CallbackManager.parse_callback_data(callback.data) # (cb_id, idx)
if parsed is not None:
pending, chosen = manager.resolve(*parsed) # consumes the entry
answer_callback_query(session, TOKEN, callback_query_id=callback.id)
if chosen is not None:
send_text(session, TOKEN, chat_id=pending.chat_id, text=f"You chose: {chosen}")
Receiving media
from minitelegram import ImageMessage, TextAndImageMessage, download_by_file_id
from minitelegram.parsing import classify_message
incoming = classify_message(update.message) # typed variant
if isinstance(incoming, (ImageMessage, TextAndImageMessage)):
download_by_file_id(
session, TOKEN,
file_id=incoming.photo_file_id,
target_path=f"/tmp/downloads/{update.message.message_id}.jpg",
)
Restricting access
from minitelegram import UserWhitelist, whitelist_filter_update
allowlist = UserWhitelist(allowed={123, 456}) # empty set = allow everyone
for update in get_updates(session, TOKEN, offset=offset):
if not whitelist_filter_update(allowlist, update):
continue # skip disallowed senders / button presses
Low-level escape hatch
Methods that have no dedicated wrapper (e.g. editMessageText,
deleteMessage, setMyCommands) can be called directly:
from minitelegram import api_call
from minitelegram.errors import TelegramAPIError
try:
api_call(session, TOKEN, "editMessageText",
json_payload={"chat_id": ..., "message_id": ..., "text": "edited"})
except TelegramAPIError as exc:
print("Telegram said no:", exc)
Module map
| Module | Purpose |
|---|---|
polling.py |
get_updates long polling with offset |
sending.py |
text (splitting + HTML fallback), question keyboards, media uploads, chat actions |
parsing.py |
raw update JSON → typed models |
types.py |
TelegramUpdate, TelegramMessage, TelegramCallback, content variants |
media.py |
resolve file_id → download to disk |
callbacks.py |
pending inline-keyboard question store (TTL + optional persistence) |
whitelist.py |
thread-safe user allowlist |
markup.py |
inline keyboard / option keyboard builders |
formatting.py |
Markdown-subset → Telegram HTML, tag stripping |
session.py |
retrying telegram_session() context manager |
errors.py |
TelegramError, TelegramRequestError, TelegramAPIError |
Tests
pytest
The tests run against a fake HTTP session — no network or bot token needed.
Releasing (maintainers)
One-time setup: PyPI token
- Create a project-scoped token: https://pypi.org/manage/account/token/ → Add API token → scope Project: mini-telegram.
- Save the
pypi-...value as the GitHub Actions secretPYPI_API_TOKENunder repo Settings → Secrets and variables → Actions.
If the secret is absent, publishing falls back to PyPI trusted publishing (OIDC), which needs a pending publisher configured on https://pypi.org/manage/account/publishing/ instead.
Cutting a release
# 1. bump src/minitelegram/__init__.py __version__ and commit
# 2. build and sanity-check the artifacts
pip install build twine
python -m build
twine check dist/*
# optional: dry-run upload to TestPyPI
# twine upload --repository testpypi dist/* (needs a TestPyPI token)
# 3. push the version tag; .github/workflows/publish.yml uploads to PyPI
git tag v0.1.0
git push origin v0.1.0
Metadata
Release files for mini-telegram 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| mini_telegram-1.0.0.tar.gz | 28.2 kB | Without provenance | Details for mini_telegram-1.0.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| mini_telegram-1.0.0-py3-none-any.whl | Python 3 | none | any | Details for mini_telegram-1.0.0-py3-none-any.whl |
Total release size: 54.9 kB
Release files / mini_telegram-1.0.0.tar.gz
| Download URL | mini_telegram-1.0.0.tar.gz |
|---|---|
| Size | 28.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
52fe7c37fc8e19c6b3bd6dda751faf089616397c8437bb89a03ea535b4716e10
|
|
BLAKE2b-256 checksum How to use checksums |
c8b788110356c3b7579052a8b58218e97e82b8ac3f36a3f8bef02d1483bfe10c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / mini_telegram-1.0.0-py3-none-any.whl
| Download URL | mini_telegram-1.0.0-py3-none-any.whl |
|---|---|
| Size | 26.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
0482e5d3fc03a80b21de4821212622c68db0c61dc5aa0cb40c59e4c402016ea9
|
|
BLAKE2b-256 checksum How to use checksums |
d791b39f87cf0d0bfaea90d002a98389c78016dbc6f27cb3caa1b6b5129918f0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Security for mini-telegram 1.0.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.
No provenance found
None of the files in this release were uploaded with provenance metadata.
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.