Metadata-Version: 2.4
Name: telegram-bot-lite-py
Version: 1.0.0
Summary: Lightweight Telegram Bot API client - send messages, keyboards, webhooks, async, zero deps
Project-URL: Homepage, https://sarmkadan.com
Project-URL: Repository, https://github.com/Sarmkadan/telegram-bot-lite-py
Project-URL: Bug Tracker, https://github.com/Sarmkadan/telegram-bot-lite-py/issues
Author-email: Vladyslav Zaiets <rutova2@gmail.com>
License: MIT
License-File: LICENSE
Keywords: api,async,bot,client,messenger,notifications,telegram
Classifier: Development Status :: 5 - Production/Stable
Classifier: Framework :: AsyncIO
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Communications :: Chat
Classifier: Topic :: Internet
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.9
Description-Content-Type: text/markdown

# telegram-bot-lite-py

**Lightweight Telegram Bot API client** — send messages, build keyboards, handle webhooks, all async with **zero dependencies**.

Pure Python ≥ 3.9. No `httpx`, no `aiohttp`, no `requests`. Just the stdlib.

```
pip install telegram-bot-lite-py
```

---

## Features

- Async-first — built on `asyncio` + stdlib SSL
- Full Bot API coverage: messages, media, keyboards, commands, callbacks
- Composable update filters (`&`, `|`, `~`)
- Long-polling and webhook modes
- Fluent keyboard builders
- Typed dataclasses for every API object
- Zero third-party dependencies

---

## Quick Start

### Echo bot (long polling)

```python
import asyncio
from telegram_bot_lite_py import TelegramBot, Dispatcher
from telegram_bot_lite_py.filters import Filters

bot = TelegramBot("YOUR_BOT_TOKEN")
dp = Dispatcher(bot)

@dp.on_command("start")
async def on_start(bot, update):
    await bot.reply(update.message, "Hello! I'm alive 🤖")

@dp.on_message(Filters.text)
async def on_text(bot, update):
    await bot.reply(update.message, f"You said: {update.message.text}")

dp.run_polling()
```

### One-shot notification (no dispatcher needed)

```python
import asyncio
from telegram_bot_lite_py import TelegramBot

async def notify():
    bot = TelegramBot("YOUR_BOT_TOKEN")
    await bot.send_message(CHAT_ID, "<b>Deploy complete!</b>", parse_mode="HTML")

asyncio.run(notify())
```

---

## Installation

```bash
pip install telegram-bot-lite-py
```

Requires **Python 3.9+**. No other packages needed.

---

## API Reference

### `TelegramBot`

```python
from telegram_bot_lite_py import TelegramBot

bot = TelegramBot(
    token="YOUR_BOT_TOKEN",
    timeout=30.0,        # per-request timeout in seconds
    parse_mode="HTML",   # default parse mode: "HTML", "Markdown", "MarkdownV2", or None
)
```

#### Sending messages

| Method | Description |
|--------|-------------|
| `await bot.send_message(chat_id, text, *, parse_mode, reply_markup, ...)` | Send a text message |
| `await bot.reply(message, text, *, parse_mode, reply_markup)` | Reply to a specific message |
| `await bot.notify(chat_id, text)` | Shorthand alias for `send_message` |
| `await bot.send_photo(chat_id, photo, *, caption, ...)` | Send photo by file_id, URL, or bytes |
| `await bot.send_document(chat_id, document, *, caption, ...)` | Send document |
| `await bot.send_audio(chat_id, audio, *, caption, performer, title, ...)` | Send audio |
| `await bot.send_video(chat_id, video, *, caption, duration, ...)` | Send video |
| `await bot.send_location(chat_id, lat, lon, ...)` | Send a map pin |
| `await bot.send_contact(chat_id, phone, first_name, ...)` | Send a contact |
| `await bot.send_chat_action(chat_id, action)` | Show "typing…" or other indicators |
| `await bot.forward_message(chat_id, from_chat_id, message_id)` | Forward a message |
| `await bot.copy_message(chat_id, from_chat_id, message_id)` | Copy (without forward tag) |

#### Editing & deleting

| Method | Description |
|--------|-------------|
| `await bot.edit_message_text(text, *, chat_id, message_id, ...)` | Edit message text |
| `await bot.edit_message_reply_markup(*, chat_id, message_id, reply_markup)` | Update keyboard only |
| `await bot.delete_message(chat_id, message_id)` | Delete a message |

#### Callback queries

| Method | Description |
|--------|-------------|
| `await bot.answer_callback_query(callback_query_id, *, text, show_alert, url)` | Answer an inline button press |

#### Chat management

| Method | Description |
|--------|-------------|
| `await bot.get_chat(chat_id)` | Fetch `Chat` info |
| `await bot.leave_chat(chat_id)` | Leave a group/channel |
| `await bot.ban_chat_member(chat_id, user_id, ...)` | Ban a user |
| `await bot.unban_chat_member(chat_id, user_id)` | Unban a user |
| `await bot.get_chat_member_count(chat_id)` | Member count |

#### Bot setup

| Method | Description |
|--------|-------------|
| `await bot.get_me()` | Fetch the bot's `User` object |
| `await bot.set_my_commands(commands)` | Set the command list shown in clients |
| `await bot.get_my_commands()` | Get current commands |
| `await bot.delete_my_commands()` | Remove all commands |

#### Webhook

| Method | Description |
|--------|-------------|
| `await bot.set_webhook(url, *, secret_token, max_connections, ...)` | Register a webhook |
| `await bot.delete_webhook(*, drop_pending_updates)` | Remove the webhook |
| `await bot.get_webhook_info()` | Fetch current webhook status |

#### Files

| Method | Description |
|--------|-------------|
| `await bot.get_file(file_id)` | Fetch `File` metadata |
| `bot.get_file_url(file_path)` | Build the download URL for a `File` |

---

### `Dispatcher`

```python
from telegram_bot_lite_py import Dispatcher

dp = Dispatcher(bot, workers=4)
```

#### Decorators

```python
@dp.on_command("start", "help")
async def cmd_handler(bot, update): ...

@dp.on_message(Filters.text & ~Filters.command("start"))
async def text_handler(bot, update): ...

@dp.on_callback("confirm", "cancel")
async def callback_handler(bot, update): ...

@dp.on_callback(prefix="page:")
async def paginate(bot, update): ...

@dp.on_error()
async def error_handler(bot, update, exc): ...
```

#### Low-level registration

```python
from telegram_bot_lite_py.filters import Filters

dp.add_handler(my_fn, Filters.photo, priority=10)
```

#### Running

```python
# Blocking (runs asyncio event loop internally)
dp.run_polling(timeout=30, drop_pending=True)

# Inside an existing event loop
await dp.start_polling(timeout=30)
```

---

### Filters

```python
from telegram_bot_lite_py.filters import Filters

Filters.message          # any message update
Filters.edited_message   # edited message
Filters.callback_query   # callback query
Filters.channel_post     # channel post

Filters.text             # message has text
Filters.photo            # message has photo
Filters.document         # message has document
Filters.audio
Filters.video
Filters.location
Filters.contact
Filters.sticker

Filters.private          # private chat
Filters.group            # group or supergroup
Filters.channel          # channel

Filters.command("start", "help")
Filters.regex(r"order #\d+")
Filters.text_equals("yes", "no")
Filters.text_contains("hello")
Filters.user(123456, 789012)     # whitelist by user_id
Filters.chat(-100123456789)      # whitelist by chat_id
Filters.callback_data("ok", "cancel")
Filters.callback_prefix("page:")
Filters.custom_filter(lambda u: u.effective_user.is_premium)
```

Combine with `&`, `|`, `~`:

```python
private_text = Filters.text & Filters.private
not_bot      = ~Filters.user(bot_id)
media        = Filters.photo | Filters.video | Filters.document
```

---

### Keyboard builders

#### Inline keyboards

```python
from telegram_bot_lite_py.keyboards import InlineKeyboard, inline_keyboard

# Fluent builder
kb = (
    InlineKeyboard()
    .button("Yes", callback_data="yes")
    .button("No", callback_data="no")
    .row()
    .button("Visit", url="https://sarmkadan.com")
    .build()
)

# Compact factory
kb = inline_keyboard(
    [("Option A", "a"), ("Option B", "b")],
    [("Cancel", "cancel")],
)

await bot.send_message(chat_id, "Choose:", reply_markup=kb)
```

#### Reply keyboards

```python
from telegram_bot_lite_py.keyboards import ReplyKeyboard, reply_keyboard

# Fluent builder
kb = (
    ReplyKeyboard(resize=True, one_time=True)
    .button("Share Location", request_location=True)
    .row()
    .button("Share Contact", request_contact=True)
    .build()
)

# Compact factory
kb = reply_keyboard(["Yes", "No"], ["Maybe", "Cancel"], resize=True)

await bot.send_message(chat_id, "Choose:", reply_markup=kb)
```

#### Remove / force reply

```python
from telegram_bot_lite_py.keyboards import remove_keyboard, force_reply

await bot.send_message(chat_id, "Keyboard removed", reply_markup=remove_keyboard())
await bot.send_message(chat_id, "Enter your name:", reply_markup=force_reply(placeholder="Your name"))
```

---

### Webhook server

```python
import ssl
from telegram_bot_lite_py import TelegramBot, Dispatcher, WebhookServer

bot = TelegramBot("YOUR_BOT_TOKEN")
dp  = Dispatcher(bot)

@dp.on_command("start")
async def start(bot, update):
    await bot.reply(update.message, "Hello from webhook!")

# Optional TLS termination
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
ctx.load_cert_chain("cert.pem", "key.pem")

server = WebhookServer(
    dp,
    host="0.0.0.0",
    port=8443,
    path="/webhook",
    secret_token="my-secret",
    ssl_context=ctx,
)

# Register webhook then start
import asyncio

async def main():
    await bot.set_webhook(
        "https://your-domain.com/webhook",
        secret_token="my-secret",
        max_connections=40,
    )
    await server.start()
    # keep running
    await asyncio.Event().wait()

asyncio.run(main())
```

---

### Types

All Telegram API objects are plain Python dataclasses.

```python
from telegram_bot_lite_py import Update, Message, User, Chat, CallbackQuery

update: Update
update.update_id
update.message           # Message | None
update.edited_message    # Message | None
update.callback_query    # CallbackQuery | None
update.effective_message # first non-None of message / edited_message / channel_post
update.effective_chat    # Chat | None
update.effective_user    # User | None

message: Message
message.message_id
message.chat             # Chat
message.from_user        # User | None
message.text             # str | None
message.content_type     # "text" | "photo" | "document" | ...
message.get_command()    # "/start" | None
message.get_args()       # "arg1 arg2" – everything after the command

user: User
user.id
user.full_name           # "First Last"
user.mention             # "@username" or full name

chat: Chat
chat.id
chat.type                # "private" | "group" | "supergroup" | "channel"
chat.is_private          # bool
chat.is_group            # bool
chat.display_name        # title or first_name
```

---

### Error handling

```python
from telegram_bot_lite_py.exceptions import (
    TelegramAPIError,
    RetryAfterError,
    ForbiddenError,
    NetworkError,
)

@dp.on_error()
async def handle_error(bot, update, exc):
    if isinstance(exc, RetryAfterError):
        print(f"Flood limit. Retry after {exc.retry_after}s")
    elif isinstance(exc, ForbiddenError):
        print("Bot was blocked by user")
    elif isinstance(exc, NetworkError):
        print(f"Network problem: {exc}")
    else:
        print(f"Unhandled error: {exc}")
```

---

## Examples

### Notification bot

```python
import asyncio
from telegram_bot_lite_py import TelegramBot

async def alert(chat_id: int, message: str):
    bot = TelegramBot("YOUR_TOKEN")
    await bot.send_message(chat_id, f"<b>Alert</b>\n{message}", parse_mode="HTML")

asyncio.run(alert(-100123456789, "Server CPU above 90%"))
```

### Paginated inline buttons

```python
@dp.on_command("list")
async def cmd_list(bot, update):
    kb = inline_keyboard(
        [("Item 1", "item:1"), ("Item 2", "item:2")],
        [("< Prev", "page:0"), ("Next >", "page:2")],
    )
    await bot.reply(update.message, "Choose an item:", reply_markup=kb)

@dp.on_callback(prefix="item:")
async def on_item(bot, update):
    item_id = update.callback_query.data.split(":")[1]
    await bot.answer_callback_query(update.callback_query.id, text=f"Selected item {item_id}")

@dp.on_callback(prefix="page:")
async def on_page(bot, update):
    page = int(update.callback_query.data.split(":")[1])
    await bot.answer_callback_query(update.callback_query.id, text=f"Page {page}")
```

### Admin-only commands

```python
ADMIN_IDS = [123456789]

@dp.on_command("ban", priority=100)
async def cmd_ban(bot, update):
    if update.effective_user.id not in ADMIN_IDS:
        await bot.reply(update.message, "Access denied.")
        return
    # ... ban logic
```

---

## License

MIT — see [LICENSE](LICENSE).

© 2024 Vladyslav Zaiets — [sarmkadan.com](https://sarmkadan.com)
