Metadata-Version: 2.1
Name: swiftbot
Version: 1.6.2
Summary: Async Telegram bot framework with typed decorators, composable filters, persistent FSM storage and a typed error hierarchy - Bot API 2026
Home-page: https://github.com/Arjun-M/SwiftBot
Author: Arjun-M
Author-email: arjunm@atomicmail.io
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Communications :: Chat
Classifier: Framework :: AsyncIO
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Provides-Extra: dev
License-File: LICENSE

<img src="banner.jpg" alt="SwiftBot - Ultra-Fast Telegram Bot Framework" width="100%">

[![PyPI](https://img.shields.io/pypi/v/swiftbot.svg)](https://pypi.org/project/swiftbot/)
[![Python](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/)
[![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)

# SwiftBot

SwiftBot is a fast, async-first Telegram bot framework built for simplicity and correctness. One decorator registers a handler, a rich `Context` object does the talking, and the framework quietly handles everything else — HTTP/2 connection pooling, `Retry-After` compliance, persistent state storage, and a fully typed error hierarchy. Everything is typed, everything is optional, and you never need an external database to get started.

```bash
pip install swiftbot
```

## Why SwiftBot

Writing a Telegram bot should not mean wiring polling loops, rate-limit math, and error handling by hand. SwiftBot gives you a clean decorator API on top of a production-grade runtime: HTTP/2 multiplexing over a keep-alive connection pool, a worker pool with backpressure and a dead-letter queue, a trie-based command router, and full Bot API 2026 coverage — including managed bots, guest mode, business messages, and ephemeral messages.

## Quick Start

```python
import asyncio
from swiftbot import SwiftBot, Filters as F
from swiftbot.types import Message

bot = SwiftBot(token="YOUR_BOT_TOKEN")

@bot.on(Message(text="hello"))
async def hello(ctx):
    await ctx.reply("Hi there!")

@bot.on(Message(filters=F.command("start")))
async def start(ctx):
    await ctx.reply("Welcome! Send me hello.")

asyncio.run(bot.run())
```

That is the whole bot. No router setup, no dispatch table, no middleware boilerplate.

## Core Concepts

### Handlers and Filters

Handlers are registered with `@bot.on(Message(...))` and the first matching handler wins. Filters are composable — combine commands, regex patterns, and predicates:

```python
@bot.on(Message(filters=F.command("ban") & F.chat(-100123456789)))
async def ban(ctx):
    ...

@bot.on(Message(pattern=r"^/weather (\w+)"))
async def weather(ctx):
    city = ctx.match.group(1)
    await ctx.reply(f"Weather in {city}: sunny")
```

### Context

Every handler receives a `Context` with typed access to the update, plus shortcuts that cover most use cases: `ctx.reply()`, `ctx.edit()`, `ctx.delete()`, `ctx.answer_callback()`, and `ctx.forward_to()`.

### Buttons and Callbacks

```python
from swiftbot import InlineKeyboard, Button, CallbackData

# Declare the payload shape once
page = CallbackData("page", int)

# Attach packed payloads to buttons
kb = InlineKeyboard([]).add_row(
    Button.inline("1", page.pack(1)),
    Button.inline("2", page.pack(2)),
)
await ctx.reply("Pick a page:", reply_markup=kb.to_dict())

# Filter on valid payloads and decode the typed data
@bot.on(CallbackQuery(pattern=rf"^{page.prefix}:[^:]+:[^:]+$"))
async def on_page(ctx):
    page_num = page.unpack(ctx.callback_query.data)[0]
    await ctx.answer_callback(f"Page {page_num}")
```

### State and Dialogues

State survives restarts with pluggable storage backends — in-memory, JSON file, or Redis — and the dialogue system models multi-step conversations as declared transition graphs with per-state timeouts:

```python
survey = bot.dialogue("survey")

@survey.state("ask_name", next=["ask_age"])
async def ask_name(ctx, prev=None):
    await ctx.reply("What's your name?")
    return Dialogue.next("ask_age", carry=ctx.text)

@survey.state("ask_age", timeout=120.0)
async def ask_age(ctx, prev=None):
    await ctx.reply(f"Hi {prev}, how old are you?")
    return Dialogue.end
```

### Middleware, Pipelines and Scopes

Middleware runs around every handler, and the `Composer` bundles middleware chains with error boundaries. `Pipeline` adds dependency injection so handlers stay pure and testable, while `scope()` attaches middleware to only the updates matching a predicate.

```python
from swiftbot.middleware import Logger, RateLimiter
from swiftbot.pipeline import Pipeline

bot.use(Logger())
bot.use(RateLimiter(rate=20, per=60))

pipe = Pipeline().deps(db=my_db)
async def stats(ctx, db):
    await ctx.reply(f"{await db.count()} users")
pipe.handle(F.command("stats"), stats)
bot.pipeline(pipe)

bot.scope(F.private).use(RateLimiter(rate=1, per=1.0))
```

### Safety Nets

Fallback handlers catch anything that no other handler matched, and outbound throttling keeps your bot under Telegram's limits automatically:

```python
@bot.fallback
async def catch_all(ctx):
    await ctx.reply("Try /help.")

bot.api.config.use(throttle(max_per_second=20.0, per_chat=4.0))
```

## Testing Without Telegram

The built-in harness runs your real handlers with zero network calls — every outgoing API request is captured and every response is scriptable:

```python
from swiftbot.testing import TestClient

async with TestClient(bot) as client:
    await client.send_update({
        "update_id": 1,
        "message": {
            "message_id": 1, "date": 1,
            "chat": {"id": 1, "type": "private"},
            "from": {"id": 2, "is_bot": False, "first_name": "A"},
            "text": "hello",
        },
    })

assert client.outgoing[0]["method"] == "sendMessage"
```

## More

| Topic | What you get |
| --- | --- |
| [Commands](https://github.com/Arjun-M/SwiftBot/tree/main/docs/05_Commands.md) | Declarative command specs with auto-generated `/help` |
| [Transformers](https://github.com/Arjun-M/SwiftBot/tree/main/docs/16_Transformers.md) | Outbound API layer with auto-typing and recording |
| [Deep Linking](https://github.com/Arjun-M/SwiftBot/tree/main/docs/09_DeepLinking.md) | Typed `?start=` payloads for referrals and auth |
| [Webhooks](https://github.com/Arjun-M/SwiftBot/tree/main/docs/22_Webhooks.md) | aiohttp server with secret-token verification and metrics |
| [Storage](https://github.com/Arjun-M/SwiftBot/tree/main/docs/17_StorageAndState.md) | In-memory, JSON-file, and Redis FSM backends |
| [Exceptions](https://github.com/Arjun-M/SwiftBot/tree/main/docs/18_Exceptions.md) | Typed `ChatNotFound`, `TooManyRequests`, `Forbidden`, ... |

The complete documentation lives in the [`docs/`](https://github.com/Arjun-M/SwiftBot/tree/main/docs) folder.

## Contributing

Contributions are welcome — open an issue or submit a pull request.

## License

MIT — Copyright (c) 2026 Arjun-M/SwiftBot
