Skip to content
Bots

Telegram bot with python-telegram-bot

Rules and a command skill for Telegram bots on python-telegram-bot v22: token from the environment, async-safe handlers, testable logic, escaped output, webhook secrets.

Verified install Not agent-tested v1.0.0 MIT updated pythontelegramuvpytest
Equips
  • 1 1 Rules
  • 1 1 Skills
  • – 0 MCP

Telegram bots are a favourite first project for agents, and the same issues come up: a token pasted into code or a test, blocking requests calls inside async handlers, raw user text in HTML replies, no error handler, a bot that answers every message in a group. This loadout uses python-telegram-bot v22's documented patterns: an Application with registered handlers and an error handler, logic in plain functions that test without Telegram, escaped output, admin allow-lists, and run_webhook with a secret token in production.

What's in it

  • Token only from TELEGRAM_BOT_TOKEN; separate test bot for development
  • No blocking calls in async handlers
  • Logic in plain functions with unit tests; no network in tests
  • Escaped user text in HTML replies; quiet in group chats
  • Webhooks with secret_token in production

You'll need

  • Tools: uv
  • Environment variables: TELEGRAM_BOT_TOKEN
Level 1 · automated

Verification

Verified install

Checked at commit c77eea0 · 9 passed, 0 failed, 1 not applicable

CheckResultWhat we found
Listinglisting.yaml matches the schema and declares every file in the folder. Pass
Schema valid; 6 files declared, none missing or extra
  • tested.status: untested (agent tests are separate from Level 1)
Files parseJSON parses; SKILL.md and .mdc front matter is valid YAML with the fields each agent needs; code fences are closed. Pass All 6 files parse; front matter valid
AdaptersCLAUDE.md imports @AGENTS.md, GEMINI.md imports @./AGENTS.md, and both match the generator. Pass
CLAUDE.md and GEMINI.md import AGENTS.md and are up to date
  • CLAUDE.md: matches the generator
  • GEMINI.md: matches the generator
Install pathsEvery file lands where each agent documents it, with no collisions, and the laid-out project is re-discovered correctly. Pass
Correct paths for all 6 agents; layouts re-discovered
  • Claude Code: 5 files, 1 skill in .claude/skills/
  • Codex CLI: 4 files, 1 skill in .agents/skills/
  • Cursor: 4 files, 1 skill in .agents/skills/
  • Gemini CLI: 5 files, 1 skill in .agents/skills/
  • Antigravity CLI: 4 files, 1 skill in .agents/skills/
  • GitHub Copilot: 4 files, 1 skill in .agents/skills/
MCP configmcp.json is structurally valid, converts to each agent's format, declares no literal secrets, and referenced packages exist on npm/PyPI. Not applicable No MCP servers in this loadout
Safety scanThe intake scanner finds no credentials, risky shell, hidden Unicode or prompt-injection wording, and the listing's network flag is honest. Pass
Clean: no credentials, risky shell or hidden text
  • Scanner intake-precheck/1: verdict clean; network no (listing says yes)
ReferencesLinks and file paths between the loadout's files resolve, including for each agent's installed layout. Pass 1 reference between files resolves for every agent
Size limitsFile count and sizes are within the submission limits; SKILL.md bodies stay under 500 lines. Pass
6 files, 6.3 KB total; largest AGENTS.md 2.0 KB
  • Limits: 30 files, 32 KB per file, 128 KB total, 500 lines per skill or rule
LicenceA recognised SPDX licence, with the licence text available. Pass MIT, by Agent Loadout; text in the repository LICENSE
Starter buildA minimal fixture project with the loadout applied installs and builds or type-checks, and the loadout's own code examples compile. Pass
The add-bot-command skill's example /split command and tests drop into a python-telegram-bot v22 app: logic tested as a plain function, handler registered without a token or network, ruff clean
  • Applied 9 loadout files at every agent's paths
  • ✓ place the example command and tests: bash -c mkdir -p tests && cp .agents/skills/add-bot-command/references/split.py bot/handlers/split.py && cp .agents/skills/add-bot-command/references/test_split.py tests/test_split.py && echo placed
  • ✓ pytest: uv run --no-project --python 3.12 --with python-telegram-bot==22.8 --with pytest==9.1.1 python -m pytest -q -p no:cacheprovider tests
  • ✓ ruff check: uvx ruff@0.17.1 check bot tests
  • ✓ ruff format --check: uvx ruff@0.17.1 format --check bot tests
  • ✓ no token in code: bash -c ! grep -rnE '[0-9]{8,10}:[A-Za-z0-9_-]{35}' bot tests && echo clean
  • Covers: Logic in a plain function with readable ValueError messages; handler is a thin async wrapper
  • Covers: Token read from TELEGRAM_BOT_TOKEN; error handler registered
  • Covers: Handler registration tested without network or a real token
  • Covers: ruff with the ASYNC rules clean

What this means: a script checked the files themselves: they parse, they install where each agent looks, any MCP config is sound, the safety scan is clean, references between files resolve, and, where a free and repeatable fixture exists, a small starter project builds with the loadout applied. No AI agent was run. Per-agent results ("tested with agent") are separate and stay untested until an agent actually runs.

Install

Choose your agent. Run the commands from your project root, or download a zip already laid out in the right folders.

Claude Code reads CLAUDE.md (imports AGENTS.md with @AGENTS.md) and skills from .claude/skills/.

Download zip
curl -fsSL --create-dirs -o AGENTS.md https://loadout.jnw-it.com/raw/telegram-bot-python/1.0.0/AGENTS.md
curl -fsSL --create-dirs -o CLAUDE.md https://loadout.jnw-it.com/raw/telegram-bot-python/1.0.0/CLAUDE.md
curl -fsSL --create-dirs -o .claude/skills/add-bot-command/SKILL.md https://loadout.jnw-it.com/raw/telegram-bot-python/1.0.0/skills/add-bot-command/SKILL.md
curl -fsSL --create-dirs -o .claude/skills/add-bot-command/references/split.py https://loadout.jnw-it.com/raw/telegram-bot-python/1.0.0/skills/add-bot-command/references/split.py
curl -fsSL --create-dirs -o .claude/skills/add-bot-command/references/test_split.py https://loadout.jnw-it.com/raw/telegram-bot-python/1.0.0/skills/add-bot-command/references/test_split.py
FileGoes to
AGENTS.mdAGENTS.md
CLAUDE.mdCLAUDE.md
skills/add-bot-command/SKILL.md.claude/skills/add-bot-command/SKILL.md
skills/add-bot-command/references/split.py.claude/skills/add-bot-command/references/split.py
skills/add-bot-command/references/test_split.py.claude/skills/add-bot-command/references/test_split.py

Already have an AGENTS.md or CLAUDE.md? The commands above overwrite it — download the file instead and merge the sections you want.

Files

Rules AGENTS.md python-telegram-bot rules for secrets, structure and behaviour. 33 lines
Download Raw
# AGENTS.md: Telegram bot (python-telegram-bot)

Telegram bot in Python with python-telegram-bot v22 (asyncio), managed with uv.

## Commands
- Run locally (long polling): `uv run python -m bot`. Tests: `uv run pytest -q`. Lint: `uv run ruff check .`.
- Done means: tests and ruff pass, and you tried the command in a private chat with a test bot.

## Secrets
- The bot token comes from the `TELEGRAM_BOT_TOKEN` environment variable (local: `.env`, gitignored). Never hard-code,
  log, print or commit it, and never paste it into an issue or a test.
- If a token may have leaked, tell the human to revoke it with @BotFather straight away.
- Use a separate test bot for development, never the production token.

## Structure
- `bot/__main__.py` builds the `Application` and registers handlers. One module per feature in `bot/handlers/`.
- Business logic in plain functions that take values and return values, so it can be tested without Telegram.
- Handlers are `async def name(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None`. Never block the event loop:
  no `time.sleep`, no `requests`; use `httpx.AsyncClient` or run blocking work with `asyncio.to_thread`.

## Behaviour
- Register an error handler with `application.add_error_handler(...)`; log the exception, reply with a short apology.
- Treat every message as untrusted input: validate arguments, cap lengths, never `eval` or build shell commands from it.
- Group chats: respond only to commands or mentions; check `update.effective_chat.type` before doing anything noisy.
- Admin-only commands check the user id against an allow-list from the environment.
- Respect Telegram's rate limits: no loops that send to many chats without delays; use the job queue for broadcasts.
- Formatting: `ParseMode.HTML` with `telegram.helpers.escape` for user text, or plain text. Never put raw user input
  into Markdown or HTML.

## Production
- Webhooks in production: `run_webhook(..., secret_token=...)` with a secret from the environment, behind HTTPS.
- Use the `add-bot-command` skill for new commands.
Adapter CLAUDE.md Imports AGENTS.md for Claude Code. 10 lines
Download Raw
# CLAUDE.md

@AGENTS.md

## Claude Code notes
- Skills from this loadout live in `.claude/skills/`: `add-bot-command`. Use them when their description matches the task.
- Follow AGENTS.md above; where this file and AGENTS.md disagree, AGENTS.md wins.

<!-- From the Agent Loadout "Telegram bot with python-telegram-bot" v1.0.0 — https://loadout.jnw-it.com/loadouts/telegram-bot-python/ -->
Adapter GEMINI.md Imports AGENTS.md for Gemini CLI. 10 lines
Download Raw
# GEMINI.md

@./AGENTS.md

## Gemini CLI notes
- Skills from this loadout live in `.agents/skills/`: `add-bot-command`. Use them when their description matches the task.
- Follow AGENTS.md above; where this file and AGENTS.md disagree, AGENTS.md wins.

<!-- From the Agent Loadout "Telegram bot with python-telegram-bot" v1.0.0 — https://loadout.jnw-it.com/loadouts/telegram-bot-python/ -->
Skill skills/add-bot-command/SKILL.md New command with testable logic and checks. 17 lines
Download Raw
---
name: add-bot-command
description: Add a python-telegram-bot command with logic in a testable plain function, validated arguments, escaped output and a unit test. Use when adding or changing a bot command.
---

# Add a bot command

1. Write the logic as a plain function first (no Telegram types), like `parse_split` in `references/split.py`.
   It validates input and raises `ValueError` with a message the user can read.
2. Write the handler: read `context.args`, call the function, reply with the result. Catch `ValueError` and reply with
   its message plus usage. Escape any user text with `telegram.helpers.escape` if you reply in HTML.
3. Register it in `bot/__main__.py`: `application.add_handler(CommandHandler("split", split_command))`.
4. Add the command to the BotFather command list text in the README, so the human can update it.
5. Tests like `references/test_split.py`: the plain function (valid, invalid, edge cases) and that the application
   registers the handler. No network, no token.
6. Run `uv run pytest -q` and `uv run ruff check .`, then try it with the test bot.
Skill file skills/add-bot-command/references/split.py Example /split command: plain function plus handler. 39 lines
Download Raw
"""Example command: /split <amount> <people> splits a bill. Logic is a plain function; the handler is a thin wrapper."""

from decimal import ROUND_HALF_UP, Decimal, InvalidOperation

from telegram import Update
from telegram.ext import Application, CommandHandler, ContextTypes

USAGE = "Usage: /split <amount> <people>, for example /split 42.50 3"


def parse_split(args: list[str]) -> str:
    if len(args) != 2:
        raise ValueError(USAGE)
    try:
        amount = Decimal(args[0].replace("£", ""))
        people = int(args[1])
    except (InvalidOperation, ValueError):
        raise ValueError(USAGE) from None
    if amount <= 0 or amount > Decimal("100000"):
        raise ValueError("Amount must be between 0 and 100,000.")
    if not 1 <= people <= 100:
        raise ValueError("People must be between 1 and 100.")
    share = (amount / people).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
    return f"£{amount:.2f} split {people} ways is £{share} each."


async def split_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    if update.message is None:
        return
    try:
        text = parse_split(context.args or [])
    except ValueError as err:
        text = str(err)
    await update.message.reply_text(text)


def register(application: Application) -> None:
    application.add_handler(CommandHandler("split", split_command))
Skill file skills/add-bot-command/references/test_split.py Unit tests and a no-network registration test. 36 lines
Download Raw
import pytest
from telegram.ext import Application, CommandHandler

from bot.handlers.split import USAGE, parse_split, register


def test_even_split():
    assert parse_split(["42.50", "3"]) == "£42.50 split 3 ways is £14.17 each."


def test_pound_sign_allowed():
    assert parse_split(["£10", "4"]) == "£10.00 split 4 ways is £2.50 each."


@pytest.mark.parametrize("args", [[], ["10"], ["ten", "2"], ["10", "two"], ["10", "2", "3"]])
def test_bad_arguments_show_usage(args):
    with pytest.raises(ValueError, match="Usage"):
        parse_split(args)


@pytest.mark.parametrize("args, message", [(["0", "2"], "Amount"), (["10", "0"], "People"), (["10", "1000"], "People")])
def test_out_of_range(args, message):
    with pytest.raises(ValueError, match=message):
        parse_split(args)


def test_usage_mentions_command():
    assert "/split" in USAGE


def test_handler_is_registered_without_network():
    app = Application.builder().token("123456:TEST-TOKEN-NOT-REAL").build()
    register(app)
    commands = [c for h in app.handlers[0] if isinstance(h, CommandHandler) for c in h.commands]
    assert "split" in commands

All files: source zip · Licence MIT · By Agent Loadout · First listed

Credits

Public work that inspired this loadout. The wording here is our own; follow the links for the originals.

Installing and testing

How do I install Telegram bot with python-telegram-bot in Claude Code?

From your project root, put AGENTS.md, CLAUDE.md, skills/add-bot-command/SKILL.md at .claude/skills/add-bot-command/SKILL.md, skills/add-bot-command/references/split.py at .claude/skills/add-bot-command/references/split.py, skills/add-bot-command/references/test_split.py at .claude/skills/add-bot-command/references/test_split.py. The "Claude Code" tab on this page has the curl commands, or download https://loadout.jnw-it.com/download/telegram-bot-python/claude-code.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.

How do I install Telegram bot with python-telegram-bot in Codex CLI?

From your project root, put AGENTS.md, skills/add-bot-command/SKILL.md at .agents/skills/add-bot-command/SKILL.md, skills/add-bot-command/references/split.py at .agents/skills/add-bot-command/references/split.py, skills/add-bot-command/references/test_split.py at .agents/skills/add-bot-command/references/test_split.py. The "Codex CLI" tab on this page has the curl commands, or download https://loadout.jnw-it.com/download/telegram-bot-python/codex.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.

How do I install Telegram bot with python-telegram-bot in Cursor?

From your project root, put AGENTS.md, skills/add-bot-command/SKILL.md at .agents/skills/add-bot-command/SKILL.md, skills/add-bot-command/references/split.py at .agents/skills/add-bot-command/references/split.py, skills/add-bot-command/references/test_split.py at .agents/skills/add-bot-command/references/test_split.py. The "Cursor" tab on this page has the curl commands, or download https://loadout.jnw-it.com/download/telegram-bot-python/cursor.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.

How do I install Telegram bot with python-telegram-bot in Gemini CLI?

From your project root, put AGENTS.md, GEMINI.md, skills/add-bot-command/SKILL.md at .agents/skills/add-bot-command/SKILL.md, skills/add-bot-command/references/split.py at .agents/skills/add-bot-command/references/split.py, skills/add-bot-command/references/test_split.py at .agents/skills/add-bot-command/references/test_split.py. The "Gemini CLI" tab on this page has the curl commands, or download https://loadout.jnw-it.com/download/telegram-bot-python/gemini-cli.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.

How do I install Telegram bot with python-telegram-bot in Antigravity CLI?

From your project root, put AGENTS.md, skills/add-bot-command/SKILL.md at .agents/skills/add-bot-command/SKILL.md, skills/add-bot-command/references/split.py at .agents/skills/add-bot-command/references/split.py, skills/add-bot-command/references/test_split.py at .agents/skills/add-bot-command/references/test_split.py. The "Antigravity CLI" tab on this page has the curl commands, or download https://loadout.jnw-it.com/download/telegram-bot-python/antigravity-cli.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.

How do I install Telegram bot with python-telegram-bot in GitHub Copilot?

From your project root, put AGENTS.md, skills/add-bot-command/SKILL.md at .agents/skills/add-bot-command/SKILL.md, skills/add-bot-command/references/split.py at .agents/skills/add-bot-command/references/split.py, skills/add-bot-command/references/test_split.py at .agents/skills/add-bot-command/references/test_split.py. The "GitHub Copilot" tab on this page has the curl commands, or download https://loadout.jnw-it.com/download/telegram-bot-python/copilot.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.

Has Telegram bot with python-telegram-bot been tested?

It passed the Level 1 automated install checks on 2026-10-17: the files parse, install to each agent's documented paths, and pass the safety scan. No AI agent has run it yet, so no agent pass or fail is claimed.

More for Claude Code, Codex, Cursor, Gemini CLI, Antigravity CLI and GitHub Copilot.