Metadata-Version: 2.4
Name: sorosh
Version: 1.0.0
Summary: Official Python SDK for the Sorosh Plus Bot Platform. Enterprise-grade toolkit for building intelligent, scalable bots with messaging, media, keyboards, group management, authentication, monetization, and webhook/polling support. Type-safe and production-ready.
Home-page: https://github.com/Mahdy-Ahmadi/sorosh
Download-URL: https://github.com/Mahdy-Ahmadi/sorosh/
Author: Mahdi Ahmadi
Author-email: mahdiahmadi.1208@gmail.com
Maintainer: Mahdi Ahmadi
Maintainer-email: mahdiahmadi.1208@gmail.com
License: sorosh Exclusive License (REL) v1.0
Project-URL: Bug Tracker, https://t.me/Dev_servers
Project-URL: Documentation, https://github.com/Mahdy-Ahmadi/sorosh/blob/main/README.md
Project-URL: Source Code, https://github.com/Mahdy-Ahmadi/sorosh
Project-URL: Web Site, http://sorosh.ir
Project-URL: Discord/Telegram, https://sorosh.ir/sorosh_info
Keywords: sorosh bot api library chat messaging
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
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: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Topic :: Communications :: Chat
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Natural Language :: Persian
Classifier: Natural Language :: English
Requires-Python: >=3.8
Description-Content-Type: text/markdown
Requires-Dist: aiohttp>=3.9.0
Requires-Dist: httpx>=0.27.0
Dynamic: author
Dynamic: author-email
Dynamic: classifier
Dynamic: description
Dynamic: description-content-type
Dynamic: download-url
Dynamic: home-page
Dynamic: keywords
Dynamic: license
Dynamic: maintainer
Dynamic: maintainer-email
Dynamic: project-url
Dynamic: requires-dist
Dynamic: requires-python
Dynamic: summary

# Soroush Bot
**Official Soroush Plus Bot Library**

This is the official library for developing bots on the Soroush Plus platform, developed by the Soroush Plus team with programming by **Mahdi Ahmadi**.
A comprehensive Python library for the Soroush Plus Bot API, designed with a clean and intuitive interface inspired by `aiogram` and `rubka`.
---

[![Python Version](https://img.shields.io/badge/python-3.8%2B-blue)](https://www.python.org/downloads/)
[![License](https://img.shields.io/badge/license-MIT-green)](LICENSE)
[![Version](https://img.shields.io/badge/version-1.0.0-orange)](https://github.com/Mahdy-Ahmadi/sorosh)

---

## Features

- 🚀 **Full API Coverage** - All methods from the official Soroush Plus Bot API documentation
- 🎨 **Intuitive Design** - Clean and Pythonic interface similar to `aiogram` and `rubka`
- ⌨️ **Rich Keyboard Support** - Both inline and reply keyboards with easy builders
- 🔍 **Powerful Filters** - Command, text, media, chat type, and custom filters
- 📦 **Complete Type System** - Full type hints and data classes for all API objects
- 🔄 **Long Polling & Webhooks** - Support for both update retrieval methods
- 📁 **File Upload Support** - Upload photos, documents, audio, video, and more
- 🛡️ **Error Handling** - Comprehensive exception handling and error management

## Installation

```bash
pip install aiohttp
```

Or install directly from source:

```bash
git clone https://github.com/yourusername/soroush-bot.git
cd soroush-bot
pip install -e .
```

## Quick Start

```python
import asyncio
from sorosh import Robot
from sorosh.filters import CommandFilter
from sorosh.types import ChatKeypadBuilder, InlineKeyboardBuilder

# Initialize your bot
TOKEN = "YOUR_BOT_TOKEN"
bot = Robot(TOKEN, show_progress=True)

# Build a keyboard
def make_main_menu():
    kb = ChatKeypadBuilder(resize_keyboard=True)
    kb.button("📸 Photo", id="photo")
    kb.button("🎲 Dice", id="dice")
    kb.row()
    kb.button("ℹ️ Info", id="info")
    return kb.build()

# Handle /start command
@bot.on_message(CommandFilter(["start", "help"]))
async def start_handler(bot, message):
    inline_kb = InlineKeyboardBuilder()
    inline_kb.callback("About", "about")
    inline_kb.url("Website", "https://splus.ir")
    
    await message.reply(
        "👋 Welcome to the Soroush Plus Bot!\n\n"
        "Use the buttons below to get started:",
        chat_keypad=make_main_menu(),
        inline_keypad=inline_kb.build()
    )

# Handle callback queries
@bot.on_callback("about")
async def about_callback(bot, message):
    await message.answer(
        "🤖 Soroush Plus Bot\n"
        "Built with Soroush Bot Library",
        show_alert=True
    )

# Default handler for unknown messages
@bot.default
async def default_handler(bot, message):
    await message.reply(
        "❌ I didn't understand that.\n"
        "Use /help to see available commands.",
        chat_keypad=make_main_menu()
    )

# Run the bot
async def main():
    await bot.run()

if __name__ == "__main__":
    asyncio.run(main())
```

## Table of Contents

- [Installation](#installation)
- [Quick Start](#quick-start)
- [Core Components](#core-components)
  - [Bot / Robot](#bot--robot)
  - [Message](#message)
  - [Filters](#filters)
  - [Keyboards](#keyboards)
- [API Methods](#api-methods)
  - [Send Messages](#send-messages)
  - [Send Media](#send-media)
  - [Send Files](#send-files)
  - [Edit Messages](#edit-messages)
  - [Delete Messages](#delete-messages)
  - [Reply Methods](#reply-methods)
- [Working with Updates](#working-with-updates)
  - [Long Polling](#long-polling)
  - [Webhooks](#webhooks)
- [Keyboards](#keyboards)
  - [Reply Keyboard (ChatKeypadBuilder)](#reply-keyboard-chatkeypadbuilder)
  - [Inline Keyboard (InlineKeyboardBuilder)](#inline-keyboard-inlinekeyboardbuilder)
- [Filters](#filters-1)
  - [Command Filter](#command-filter)
  - [Text Filter](#text-filter)
  - [Media Filters](#media-filters)
  - [Chat Type Filters](#chat-type-filters)
  - [Combining Filters](#combining-filters)
- [Types Reference](#types-reference)
- [Error Handling](#error-handling)
- [Examples](#examples)
- [Contributing](#contributing)
- [License](#license)

## Core Components

### Bot / Robot

The `Robot` class (alias for `Bot`) is the main entry point for your bot.

```python
from sorosh import Robot

bot = Robot(TOKEN, show_progress=True)
```

**Parameters:**
- `token` (str): Your bot token from @botfather
- `show_progress` (bool): Show progress messages in console (default: False)
- `session` (aiohttp.ClientSession): Custom session (optional)

### Message

The `Message` class represents a message received from the API. It provides rich properties and methods for interacting with messages.

**Key Properties:**
- `text` - Message text
- `caption` - Media caption
- `chat_id` - Chat identifier
- `sender_id` - Sender identifier
- `message_id` - Message identifier
- `from_user` - Sender user object
- `chat` - Chat object
- `is_command` - Whether the message is a command
- `is_private` - Whether the chat is private
- `is_group` - Whether the chat is a group
- `is_photo` - Whether the message contains a photo
- `is_video` - Whether the message contains a video
- `is_audio` - Whether the message contains audio
- `is_document` - Whether the message contains a document
- `is_sticker` - Whether the message contains a sticker
- `is_voice` - Whether the message contains a voice message
- `is_animation` - Whether the message contains an animation (GIF)
- `is_location` - Whether the message contains a location
- `is_contact` - Whether the message contains a contact
- `is_dice` - Whether the message contains a dice
- `has_media` - Whether the message has any media
- `is_reply` - Whether the message is a reply
- `is_forwarded` - Whether the message is forwarded
- `is_edited` - Whether the message is edited
- `has_link` - Whether the text contains a link
- `is_hashtag` - Whether the text contains a hashtag
- `is_emoji` - Whether the text contains emojis
- `args` - Command arguments as a list

### Filters

Filters are used to handle specific types of messages.

```python
from sorosh.filters import CommandFilter, TextFilter, PrivateFilter, HasPhotoFilter

@bot.on_message(CommandFilter(["start"]))
async def start_handler(bot, message):
    # Handle /start command
    pass

@bot.on_message(TextFilter(r"^hello$"))
async def hello_handler(bot, message):
    # Handle "hello" text
    pass

@bot.on_message(PrivateFilter())
async def private_handler(bot, message):
    # Handle messages from private chats
    pass

@bot.on_message(HasPhotoFilter())
async def photo_handler(bot, message):
    # Handle messages with photos
    pass
```

### Keyboards

#### Reply Keyboard (ChatKeypadBuilder)

Builds reply keyboards (buttons below the input field).

```python
from sorosh.types import ChatKeypadBuilder

def make_keyboard():
    kb = ChatKeypadBuilder(resize_keyboard=True)
    kb.button("Button 1", id="btn1")
    kb.button("Button 2", id="btn2")
    kb.row()
    kb.button("Button 3", id="btn3")
    return kb.build()

# Usage
await message.reply("Choose an option:", chat_keypad=make_keyboard())
```

#### Inline Keyboard (InlineKeyboardBuilder)

Builds inline keyboards (buttons below the message).

```python
from sorosh.types import InlineKeyboardBuilder

def make_inline_keyboard():
    kb = InlineKeyboardBuilder()
    kb.callback("Option 1", "opt1")
    kb.callback("Option 2", "opt2")
    kb.row()
    kb.url("Website", "https://example.com")
    return kb.build()

# Usage
await message.reply("Choose an option:", inline_keypad=make_inline_keyboard())
```

## API Methods

### Send Messages

#### `send_message()`

Send a text message.

```python
await bot.send_message(
    chat_id=message.chat_id,
    text="Hello, world!",
    parse_mode=None,  # Optional: "HTML" or "Markdown"
    chat_keypad=keyboard,  # Reply keyboard
    inline_keypad=inline_keyboard,  # Inline keyboard
    reply_to_message_id=message.message_id  # Reply to specific message
)
```

### Send Media

#### `send_photo()`

Send a photo from URL, bytes, or file_id.

```python
# From URL
await bot.send_photo(
    chat_id=message.chat_id,
    photo="https://example.com/image.jpg",
    caption="Beautiful photo!",
    inline_keypad=keyboard
)

# From bytes
with open("image.jpg", "rb") as f:
    image_data = f.read()
    await bot.send_photo(
        chat_id=message.chat_id,
        photo=image_data,
        caption="Uploaded photo!"
    )
```

#### `send_audio()`

Send an audio file.

```python
await bot.send_audio(
    chat_id=message.chat_id,
    audio="https://example.com/song.mp3",
    caption="Check out this song!",
    performer="Artist Name",
    title="Song Title",
    duration=180
)
```

#### `send_document()`

Send a document file.

```python
await bot.send_document(
    chat_id=message.chat_id,
    document="https://example.com/document.pdf",
    caption="Important document!"
)
```

#### `send_video()`

Send a video file.

```python
await bot.send_video(
    chat_id=message.chat_id,
    video="https://example.com/video.mp4",
    caption="Watch this video!",
    duration=120,
    width=1280,
    height=720
)
```

#### `send_animation()`

Send an animation (GIF).

```python
await bot.send_animation(
    chat_id=message.chat_id,
    animation="https://example.com/animation.gif",
    caption="Funny GIF!"
)
```

#### `send_voice()`

Send a voice message.

```python
await bot.send_voice(
    chat_id=message.chat_id,
    voice="https://example.com/voice.ogg",
    caption="Voice message",
    duration=30
)
```

#### `send_video_note()`

Send a video note (round video).

```python
await bot.send_video_note(
    chat_id=message.chat_id,
    video_note="https://example.com/video_note.mp4",
    duration=10,
    length=240
)
```

### Send Special Messages

#### `send_location()`

Send a location.

```python
await bot.send_location(
    chat_id=message.chat_id,
    latitude=35.6892,
    longitude=51.3890
)
```

#### `send_contact()`

Send a contact.

```python
await bot.send_contact(
    chat_id=message.chat_id,
    phone_number="+989123456789",
    first_name="John",
    last_name="Doe"
)
```

#### `send_dice()`

Send a dice.

```python
await bot.send_dice(
    chat_id=message.chat_id,
    emoji="🎲"  # Optional: 🎲, 🎯, 🏀, ⚽, 🎳
)
```

#### `send_sticker()`

Send a sticker.

```python
await bot.send_sticker(
    chat_id=message.chat_id,
    sticker="https://example.com/sticker.webp"
)
```

#### `send_poll()`

Send a poll.

```python
await bot.send_poll(
    chat_id=message.chat_id,
    question="What is your favorite color?",
    options=["Red", "Blue", "Green", "Yellow"],
    type="Regular",  # or "Quiz"
    allows_multiple_answers=True,
    is_anonymous=True,
    correct_option_index=0,  # For Quiz type
    hint="Hint for the quiz"
)
```

### Edit Messages

#### `edit_message_text()`

Edit a message's text.

```python
await bot.edit_message_text(
    text="New text",
    chat_id=message.chat_id,
    message_id=message.message_id
)
```

#### `edit_message_caption()`

Edit a message's caption.

```python
await bot.edit_message_caption(
    chat_id=message.chat_id,
    message_id=message.message_id,
    caption="New caption"
)
```

#### `edit_message_reply_markup()`

Edit a message's keyboard.

```python
await bot.edit_message_reply_markup(
    chat_id=message.chat_id,
    message_id=message.message_id,
    reply_markup=new_keyboard.to_dict()
)
```

### Delete Messages

#### `delete_message()`

Delete a message.

```python
await bot.delete_message(
    chat_id=message.chat_id,
    message_id=message.message_id
)
```

### Reply Methods

The `Message` object provides convenient reply methods:

```python
# Reply with text
await message.reply("Hello!")

# Reply with photo
await message.reply_image("https://example.com/image.jpg", caption="Photo!")

# Reply with location
await message.reply_location(35.6892, 51.3890)

# Reply with dice
await message.reply_dice("🎲")

# Reply with contact
await message.reply_contact("+989123456789", "John", "Doe")

# Reply with audio
await message.reply_audio("https://example.com/song.mp3")

# Reply with document
await message.reply_document("https://example.com/file.pdf")

# Reply with video
await message.reply_video("https://example.com/video.mp4")

# Reply with animation
await message.reply_animation("https://example.com/animation.gif")

# Reply with voice
await message.reply_voice("https://example.com/voice.ogg")

# Reply with sticker
await message.reply_sticker("https://example.com/sticker.webp")

# Edit the message
await message.edit("New text")

# Delete the message
await message.delete()
```

## Working with Updates

### Long Polling

Start the bot with long polling:

```python
async def main():
    await bot.run(timeout=30, limit=100)

if __name__ == "__main__":
    asyncio.run(main())
```

**Parameters:**
- `timeout` (int): Long polling timeout in seconds (default: 30)
- `limit` (int): Maximum number of updates to fetch (default: 100)

### Webhooks

Set up a webhook:

```python
await bot.set_webhook(
    url="https://example.com/webhook",
    max_connections=40,
    allowed_updates=["message", "callback_query"]
)
```

Delete a webhook:

```python
await bot.delete_webhook()
```

Get webhook info:

```python
info = await bot.get_webhook_info()
print(info.url)
print(info.pending_update_count)
```

## Filters

### Command Filter

Handle specific commands:

```python
@bot.on_message(CommandFilter(["start", "help"]))
async def handler(bot, message):
    # Handles /start and /help
    pass
```

### Text Filter

Handle text matching a regex pattern:

```python
@bot.on_message(TextFilter(r"^hello$"))
async def handler(bot, message):
    # Handles exact "hello" text
    pass

@bot.on_message(TextFilter(r"(hello|hi|hey)"))
async def handler(bot, message):
    # Handles hello, hi, or hey
    pass
```

### Media Filters

Handle messages containing specific media types:

```python
from sorosh.filters import (
    HasPhotoFilter,
    HasAudioFilter,
    HasDocumentFilter,
    HasVideoFilter,
    HasAnimationFilter,
    HasVoiceFilter,
    HasStickerFilter,
    HasLocationFilter,
    HasContactFilter,
)

@bot.on_message(HasPhotoFilter())
async def photo_handler(bot, message):
    # Handles messages with photos
    pass

@bot.on_message(HasVideoFilter())
async def video_handler(bot, message):
    # Handles messages with videos
    pass
```

### Chat Type Filters

Handle messages from specific chat types:

```python
from sorosh.filters import PrivateFilter, GroupFilter, ChannelFilter

@bot.on_message(PrivateFilter())
async def private_handler(bot, message):
    # Handles private chat messages
    pass

@bot.on_message(GroupFilter())
async def group_handler(bot, message):
    # Handles group messages
    pass
```

### Combining Filters

Combine multiple filters using `AndFilter` and `OrFilter`:

```python
from sorosh.filters import AndFilter, OrFilter, CommandFilter, PrivateFilter

# Both conditions must be true
@bot.on_message(AndFilter(CommandFilter(["start"]), PrivateFilter()))
async def handler(bot, message):
    # Handles /start in private chats only
    pass

# At least one condition must be true
@bot.on_message(OrFilter(CommandFilter(["help"]), TextFilter(r"^help$")))
async def handler(bot, message):
    # Handles /help or "help" text
    pass
```

## Types Reference

### User

Represents a user or bot.

**Properties:**
- `id` (int): Unique identifier
- `is_bot` (bool): Whether the user is a bot
- `first_name` (str): First name
- `last_name` (str, optional): Last name
- `username` (str, optional): Username
- `language_code` (str, optional): Language code
- `full_name` (str): Full name

### Chat

Represents a chat.

**Properties:**
- `id` (int): Unique identifier
- `type` (str): Chat type ("private", "group", "supergroup", "channel")
- `title` (str, optional): Chat title
- `username` (str, optional): Username
- `first_name` (str, optional): First name (private chats)
- `last_name` (str, optional): Last name (private chats)

### Update

Represents an incoming update.

**Properties:**
- `update_id` (int): Unique update identifier
- `message` (Message, optional): New message
- `edited_message` (Message, optional): Edited message
- `callback_query` (CallbackQuery, optional): Callback query
- `has_message` (bool): Whether update contains a message
- `has_callback_query` (bool): Whether update contains a callback query

### CallbackQuery

Represents a callback query from an inline button.

**Properties:**
- `id` (str): Unique identifier
- `from_user` (User): Sender
- `message` (Message, optional): Message with the inline button
- `data` (str, optional): Callback data

### File

Represents a file ready for download.

**Properties:**
- `file_id` (str): File identifier
- `file_unique_id` (str): Unique file identifier
- `file_size` (int, optional): File size in bytes
- `file_path` (str, optional): File path

**Methods:**
- `get_download_url(token)` → Get download URL

## Error Handling

The library provides specific exception types:

```python
from sorosh.exceptions import SoroushAPIError, SoroushError, ValidationError, NetworkError

try:
    await bot.send_message(chat_id, text)
except SoroushAPIError as e:
    print(f"API Error: {e.message} (Code: {e.error_code})")
except SoroushError as e:
    print(f"General Error: {e}")
except ValidationError as e:
    print(f"Validation Error: {e}")
except NetworkError as e:
    print(f"Network Error: {e}")
```

### Common Error Codes

| Code | Description |
|------|-------------|
| 400 | Bad Request |
| 401 | Unauthorized (invalid token) |
| 403 | Forbidden |
| 404 | Not Found |
| 429 | Too Many Requests |
| 500 | Internal Server Error |

## Examples

### Basic Echo Bot

```python
import asyncio
from sorosh import Robot
from sorosh.filters import CommandFilter, TextFilter

TOKEN = "YOUR_TOKEN"
bot = Robot(TOKEN, show_progress=True)

@bot.on_message(CommandFilter(["start"]))
async def start_handler(bot, message):
    await message.reply("👋 Hello! Send me any message and I'll echo it back.")

@bot.on_message(TextFilter(r".*"))
async def echo_handler(bot, message):
    await message.reply(f"🔊 {message.text}")

async def main():
    await bot.run()

if __name__ == "__main__":
    asyncio.run(main())
```

### Admin Panel with Statistics

```python
import asyncio
from sorosh import Robot
from sorosh.filters import CommandFilter, PrivateFilter, TextFilter
from sorosh.types import ChatKeypadBuilder

TOKEN = "YOUR_TOKEN"
ADMIN_IDS = ["your_admin_id_here"]
bot = Robot(TOKEN, show_progress=True)

user_stats = {}

def admin_keyboard():
    kb = ChatKeypadBuilder(resize_keyboard=True)
    kb.button("📊 Stats", id="stats")
    kb.button("📢 Broadcast", id="broadcast")
    kb.row()
    kb.button("👥 Users", id="users")
    kb.button("🗑️ Clear", id="clear")
    return kb.build()

@bot.on_message(CommandFilter(["admin"]), PrivateFilter())
async def admin_handler(bot, message):
    if message.sender_id not in ADMIN_IDS:
        await message.reply("❌ Unauthorized!")
        return
    
    await message.reply(
        "🔐 Admin Panel\n\n"
        "Choose an option:",
        chat_keypad=admin_keyboard()
    )

@bot.on_message(TextFilter(r"^📊 Stats$"))
async def stats_handler(bot, message):
    if message.sender_id not in ADMIN_IDS:
        return
    
    total_users = len(user_stats)
    total_messages = sum(data.get("count", 0) for data in user_stats.values())
    
    await message.reply(
        f"📊 Statistics\n\n"
        f"👥 Total Users: {total_users}\n"
        f"📨 Total Messages: {total_messages}"
    )

@bot.default
async def track_messages(bot, message):
    if message.sender_id:
        if message.sender_id not in user_stats:
            user_stats[message.sender_id] = {"count": 0}
        user_stats[message.sender_id]["count"] += 1

async def main():
    await bot.run()

if __name__ == "__main__":
    asyncio.run(main())
```

### Poll Bot

```python
import asyncio
from sorosh import Robot
from sorosh.filters import CommandFilter
from sorosh.types import InlineKeyboardBuilder

TOKEN = "YOUR_TOKEN"
bot = Robot(TOKEN, show_progress=True)

@bot.on_message(CommandFilter(["poll"]))
async def poll_handler(bot, message):
    kb = InlineKeyboardBuilder()
    kb.callback("📊 View Results", "poll_results")
    
    await bot.send_poll(
        chat_id=message.chat_id,
        question="What is your favorite programming language?",
        options=["Python", "JavaScript", "Go", "Rust", "Other"],
        type="Regular",
        allows_multiple_answers=False,
        is_anonymous=True,
        inline_keypad=kb.build(),
        reply_to_message_id=message.message_id
    )

@bot.on_callback("poll_results")
async def poll_results_callback(bot, message):
    await message.answer(
        "📊 Current Results\n\n"
        "Python: 45%\n"
        "JavaScript: 25%\n"
        "Go: 15%\n"
        "Rust: 10%\n"
        "Other: 5%",
        show_alert=True
    )

async def main():
    await bot.run()

if __name__ == "__main__":
    asyncio.run(main())
```

### Media Gallery Bot

```python
import asyncio
import random
from sorosh import Robot
from sorosh.filters import CommandFilter, TextFilter
from sorosh.types import ChatKeypadBuilder, InlineKeyboardBuilder

TOKEN = "YOUR_TOKEN"
bot = Robot(TOKEN, show_progress=True)

# Sample images
IMAGES = [
    "https://cdn.pixabay.com/photo/2024/01/15/10/00/cat-8509285_640.jpg",
    "https://cdn.pixabay.com/photo/2023/12/16/11/23/dog-8452478_640.jpg",
    "https://cdn.pixabay.com/photo/2023/12/11/16/35/bird-8444123_640.jpg",
]

def gallery_keyboard():
    kb = ChatKeypadBuilder(resize_keyboard=True)
    kb.button("🎲 Random Photo", id="random")
    kb.button("📸 Next Photo", id="next")
    return kb.build()

@bot.on_message(CommandFilter(["gallery"]))
async def gallery_handler(bot, message):
    kb = InlineKeyboardBuilder()
    kb.callback("❤️ Like", "like")
    kb.callback("🔄 Next", "next_photo")
    
    await bot.send_photo(
        chat_id=message.chat_id,
        photo=random.choice(IMAGES),
        caption="📸 Random Photo\n\nClick the buttons below!",
        inline_keypad=kb.build(),
        chat_keypad=gallery_keyboard()
    )

@bot.on_message(TextFilter(r"^🎲 Random Photo$"))
async def random_photo_handler(bot, message):
    await gallery_handler(bot, message)

@bot.on_callback("like")
async def like_callback(bot, message):
    await message.answer("❤️ Photo liked!", show_alert=False)

@bot.on_callback("next_photo")
async def next_photo_callback(bot, message):
    await message.answer("🔄 Loading next photo...", show_alert=False)
    await gallery_handler(bot, message)

async def main():
    await bot.run()

if __name__ == "__main__":
    asyncio.run(main())
```

## Contributing

Contributions are welcome! Here's how you can help:

1. Fork the repository
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request

### Development Setup

```bash
# Clone the repository
git clone https://github.com/yourusername/soroush-bot.git
cd soroush-bot

# Create a virtual environment
python -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate

# Install dependencies
pip install -e .
pip install pytest pytest-asyncio

# Run tests
pytest
```

## License

MIT License

Copyright (c) 2024 Soroush Bot Contributors

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

---

## Quick Reference

### Common Methods

| Method | Description |
|--------|-------------|
| `bot.run()` | Start long polling |
| `bot.send_message()` | Send text message |
| `bot.send_photo()` | Send photo |
| `bot.send_audio()` | Send audio file |
| `bot.send_document()` | Send document |
| `bot.send_video()` | Send video |
| `bot.send_animation()` | Send animation (GIF) |
| `bot.send_voice()` | Send voice message |
| `bot.send_sticker()` | Send sticker |
| `bot.send_location()` | Send location |
| `bot.send_contact()` | Send contact |
| `bot.send_dice()` | Send dice |
| `bot.send_poll()` | Send poll |
| `bot.edit_message_text()` | Edit message text |
| `bot.edit_message_caption()` | Edit message caption |
| `bot.delete_message()` | Delete message |

### Message Reply Methods

| Method | Description |
|--------|-------------|
| `message.reply()` | Reply with text |
| `message.reply_image()` | Reply with image |
| `message.reply_photo()` | Reply with photo |
| `message.reply_audio()` | Reply with audio |
| `message.reply_document()` | Reply with document |
| `message.reply_video()` | Reply with video |
| `message.reply_animation()` | Reply with animation |
| `message.reply_voice()` | Reply with voice |
| `message.reply_sticker()` | Reply with sticker |
| `message.reply_location()` | Reply with location |
| `message.reply_contact()` | Reply with contact |
| `message.reply_dice()` | Reply with dice |
| `message.reply_poll()` | Reply with poll |
| `message.edit()` | Edit message |
| `message.delete()` | Delete message |

### Keyboard Builders

| Builder | Description |
|---------|-------------|
| `ChatKeypadBuilder` | Build reply keyboards |
| `InlineKeyboardBuilder` | Build inline keyboards |
| `ReplyKeyboardRemove` | Remove reply keyboard |

### Filters

| Filter | Description |
|--------|-------------|
| `CommandFilter()` | Filter by command |
| `TextFilter()` | Filter by text pattern |
| `PrivateFilter()` | Filter private chats |
| `GroupFilter()` | Filter groups |
| `ChannelFilter()` | Filter channels |
| `HasPhotoFilter()` | Filter messages with photos |
| `HasAudioFilter()` | Filter messages with audio |
| `HasDocumentFilter()` | Filter messages with documents |
| `HasVideoFilter()` | Filter messages with videos |
| `HasAnimationFilter()` | Filter messages with animations |
| `HasVoiceFilter()` | Filter messages with voice |
| `HasStickerFilter()` | Filter messages with stickers |
| `HasLocationFilter()` | Filter messages with locations |
| `HasContactFilter()` | Filter messages with contacts |
| `AndFilter()` | Combine filters with AND |
| `OrFilter()` | Combine filters with OR |
```
