Skip to main content

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

  1. Create a project-scoped token: https://pypi.org/manage/account/token/ → Add API token → scope Project: mini-telegram.
  2. Save the pypi-... value as the GitHub Actions secret PYPI_API_TOKEN under 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)

Source distribution for mini-telegram 1.0.0
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)

Table of built distributions (wheels) for mini-telegram 1.0.0
File Interpreter ABI Platform Actions
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.

Release history Release notifications | RSS feed

This release

1.0.0 This release

2 release files No 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