Skip to main content

SplusPy - Soroush & Soroush Plus Python Library

The Ultimate Async Python Library for Building Bots and Userbots on Soroush Plus

License: MIT PyPI version Python versions Downloads Code style: black Type checked: mypy

English | فارسی


SplusPy is the most comprehensive async Python library for building bots, userbots, and automations on Soroush Plus. Whether you're creating a ربات سروش پلاس (Soroush Plus bot) or a یوزربات سروش (Soroush userbot), SplusPy provides everything you need with a clean, modern API inspired by Telethon and Pyrogram. This کتابخانه پایتون سروش supports both bot and user accounts out of the box with built-in MTProto credentials.

SplusPy جامع‌ترین کتابخانه پایتون ناهمگام برای ساخت ربات سروش پلاس، یوزربات سروش و اتوماسیون روی پلتفرم سروش پلاس است. این کتابخانه پایتون سروش هم برای حساب کاربری و هم برای حساب ربات پشتیبانی کامل دارد و با API مدرن و تمیز خود توسعه ربات‌ها و یوزربات‌های سروش پلاس را بسیار آسان می‌کند.


English

Features

Feature Description
No API Key Required Built-in Soroush Plus credentials
Fully Asynchronous Built with Python's asyncio
Sync Support Use without async/await via spluspy.sync
Bot & User Support Both account types
Event-Driven Handlers Powerful event system with decorators
Filter System Composable filters (&, |, ~)
Inline & Reply Buttons Interactive keyboards
Conversation API For interactive bot flows
FSM (Finite State Machine) Built-in state management for bots
Plugin System Dynamic plugin loading
Middleware Pre/post processing of updates
Scheduler Built-in task scheduler
Multiple Storage Backends Memory, SQLite, Redis, PostgreSQL
Rate Limiting Token bucket algorithm with flood wait handling
AFK Auto-Reply Smart auto-responder with per-chat rate limiting
Chat Administration Ban, mute, pin, purge — high-level admin API
Message Mirroring Real-time message cloning between chats
Batch Operations Send, delete, forward multiple messages at once
File Transfer with Progress Upload/download with progress tracking
Professional Logging Structured, namespaced loggers
Type Hints Everywhere Full type safety
Clean Architecture SOLID principles, modular design

Requirements

  • Python 3.10+
  • No external API keys needed (built-in Soroush Plus credentials)

Installation

pip install spluspy

For faster encryption:

pip install spluspy[speed]

With Redis backend:

pip install spluspy[redis]

With PostgreSQL backend:

pip install spluspy[postgres]

All optional dependencies:

pip install spluspy[all]

Quick Start

Simplest Bot

from spluspy import Client

bot = Client("my_session")

@bot.on_message()
async def handler(client, message):
    await message.reply("Hello!")

bot.run()

User Account

from spluspy import Client

client = Client("session_name")

@client.on_message()
async def handler(client, message):
    await message.reply("Hey there!")

async def main():
    await client.start(phone="+98XXXXXXXXXX")
    await client.run_until_disconnected()

import asyncio
asyncio.run(main())

Sync Usage (No Async/Await)

from spluspy.sync import Client

bot = Client("session")

@bot.on_message()
def handler(client, message):
    message.reply("Hello!")

bot.run()

Complete API Reference


Client

The main entry point for all SplusPy operations. Manages authentication, event dispatching, middleware, plugins, and API interactions. Supports both bot mode (bot_token) and user mode (phone).

Constructor

Client(
    session: Union[str, Session] = "spluspy",
    session_name: Optional[str] = None,
    *,
    session_string: Optional[str] = None,
    api_id: Optional[int] = None,
    api_hash: Optional[str] = None,
    bot_token: Optional[str] = None,
    phone: Optional[str] = None,
    proxy: Optional[dict[str, Any]] = None,
    flood_sleep_threshold: int = 60,
    max_retries: Optional[int] = None,
    request_timeout: float = 30.0,
    log_level: int = logging.CRITICAL,
    log_file: Optional[str] = None,
    log_dir: str = "logs",
    errors_only: bool = False,
) -> None
Parameter Type Default Description
session str | Session "spluspy" Session name or a Session object. Used to persist authentication data.
session_name str | None None Alternative name for the session file. Overrides session if provided.
session_string str | None None A portable base64 session string for deploying without files.
api_id int | None None Custom API ID. If None, uses built-in Soroush Plus credentials.
api_hash str | None None Custom API hash. If None, uses built-in Soroush Plus credentials.
bot_token str | None None Bot token for bot mode. If None, runs in user mode.
phone str | None None Phone number for user mode authentication.
proxy dict | None None Proxy configuration (e.g. {"proxy_type": "socks5", "addr": "127.0.0.1", "port": 1080}).
flood_sleep_threshold int 60 Seconds to auto-sleep on FloodWait errors before raising.
max_retries int | None None Maximum retry attempts for failed requests. None = infinite.
request_timeout float 30.0 Timeout in seconds for API requests.
log_level int CRITICAL Logging level (e.g. logging.INFO, logging.DEBUG).
log_file str | None None Path to log file. None = console only.
log_dir str "logs" Directory for log files.
errors_only bool False If True, only logs errors (suppresses info/debug).

Properties

Property Type Description
session_name str Returns the session name.
is_connected bool Whether the client is currently connected to the server.
middleware MiddlewareManager Access the middleware manager to add/remove middleware.
plugins PluginManager Access the plugin manager to load/unload plugins.
scheduler Scheduler Access the built-in task scheduler.

Event Registration Methods

on(event, **kwargs) -> Callable

Register a handler for any event type. The event parameter accepts an EventBuilder or an Event subclass. Optional priority kwarg controls execution order (lower = first).

@bot.on(NewMessage(), priority=HandlerPriority.HIGH)
async def handler(client, event):
    pass
on_message(*message_filters, **kwargs) -> Callable

Register a handler that fires on new messages. Multiple filter arguments are ANDed together. The handler receives (client, message).

Parameter Type Description
*message_filters Filter One or more filters. All must pass for the handler to fire.
priority int Execution priority. Default: HandlerPriority.NORMAL (50).
@bot.on_message(filters.text & filters.private)
async def handler(client, message):
    await message.reply("Hello!")
on_edited_message(*message_filters, **kwargs) -> Callable

Register a handler for edited messages. Same filter/priority API as on_message.

on_callback_query(**kwargs) -> Callable

Register a handler for inline button callback queries. Handler receives (client, callback_query).

Note: Not supported on Soroush+ user sessions. Exists for API compatibility.

on_inline_query(**kwargs) -> Callable

Register a handler for inline queries. Handler receives (client, inline_query).

Note: Not supported on Soroush+ user sessions. Exists for API compatibility.

on_chat_action(**kwargs) -> Callable

Register a handler for chat actions (joins, leaves, pins, etc.). Handler receives (client, action_event).

on_user_update(**kwargs) -> Callable

Register a handler for user status changes (online/offline/typing). Handler receives (client, user_update).

on_message_deleted(**kwargs) -> Callable

Register a handler for deleted messages. Handler receives (client, deleted_event).

on_message_read(**kwargs) -> Callable

Register a handler for read receipts. Handler receives (client, read_event).

on_error(*exception_types, priority=HandlerPriority.LOW) -> Callable

Register a global error handler. If no exception types are specified, catches all errors. The handler receives an ErrorEvent object.

@bot.on_error(FloodWait, Unauthorized)
async def error_handler(client, event):
    print(f"Error: {event.exception}")

Connection Lifecycle

async connect() -> None

Connect to the Soroush Plus server. Attempts adapter-based connection first, falls back to built-in MTProto. Must be called before sending/receiving messages in user mode.

async disconnect() -> None

Disconnect from the server. Stops the scheduler, closes middleware and sessions.

async start() -> None

Start the client with interactive prompts for phone/password/code authentication. Automatically connects and handles the full auth flow.

async stop() -> None

Alias for disconnect(). Stops the client.

async run_until_disconnected() -> None

Run the event loop until the client is disconnected. Handles transient disconnections gracefully with automatic reconnection.

run() -> None

Blocking entry point. Connects, runs until disconnected, then cleans up. Tries uvloop for better performance. This is the simplest way to run a bot:

bot = Client("session")
bot.run()  # Blocks forever

Message Sending

async send_message(chat_id, text=None, *, reply_to=None, parse_mode=None, link_preview=True, **kwargs) -> Message

Send a text message to a chat.

Parameter Type Default Description
chat_id int (required) Target chat/user ID.
text str | None None Message text.
reply_to int | None None Message ID to reply to.
parse_mode str | None None "html", "markdown", or None for raw text.
link_preview bool True Whether to show link previews.

Returns: Message — the sent message object.

async edit_message(message, *, text=None, parse_mode=None, delay=0, **kwargs) -> Message

Edit a previously sent message's text. If delay is set, waits that many seconds before editing (non-blocking).

Parameter Type Default Description
message Message | int (required) Message object or message ID.
text str | None None New text content.
delay int 0 Seconds to wait before editing.
async delete_messages(*messages, **kwargs) -> bool

Delete one or more messages. Accepts Message objects or integer IDs.

Returns: True on success.

async delete_message(chat_id, message_id, *, delay=0) -> bool

Delete a single message with an optional delay before deletion.

async forward_messages(chat_id, *messages) -> Message

Forward one or more messages to another chat. Returns a placeholder Message.

async send_photo(chat_id, photo, *, caption=None, **kwargs) -> Message

Send a photo. photo can be a file path (str), bytes, or a file-like object.

Parameter Type Default Description
chat_id int (required) Target chat/user ID.
photo str | bytes | IO (required) Photo file path, bytes, or file-like object.
caption str | None None Caption text for the photo.
async send_video(chat_id, video, *, caption=None, duration=0, width=0, height=0, supports_streaming=True, thumb=None, force_document=False, progress_callback=None, reply_to=None, **kwargs) -> Message

Send a video. Duration, width, and height are auto-detected if left at 0.

Parameter Type Default Description
video str | bytes | IO (required) Video file.
caption str | None None Video caption.
duration int 0 Duration in seconds (auto-detected if 0).
width int 0 Width in pixels (auto-detected if 0).
height int 0 Height in pixels (auto-detected if 0).
supports_streaming bool True Whether the video supports streaming.
progress_callback Callable | None None Callback for upload progress: fn(current, total).
async send_voice(chat_id, voice, *, caption=None, duration=0, progress_callback=None, reply_to=None, **kwargs) -> Message

Send a voice note (audio message).

async send_document(chat_id, document, *, caption=None, file_name=None, force_document=True, progress_callback=None, reply_to=None, **kwargs) -> Message

Send a document/file. force_document=True ensures the file is sent as a document, not auto-detected.

async send_audio(chat_id, audio, *, caption=None, duration=0, performer=None, title=None, progress_callback=None, reply_to=None, **kwargs) -> Message

Send an audio file with optional metadata (performer, title).

async send_animation(chat_id, animation, *, caption=None, duration=0, width=0, height=0, thumb=None, progress_callback=None, reply_to=None, **kwargs) -> Message

Send an animation (GIF).

async send_location(chat_id, lat, lon, *, reply_to=None) -> Message

Send a geographic location.

Parameter Type Description
lat float Latitude (-90 to 90).
lon float Longitude (-180 to 180).
async send_contact(chat_id, phone, first_name, last_name="", *, vcard="", reply_to=None) -> Message

Send a contact card.

async send_poll(chat_id, question, options, *, correct_option=None, explanation=None, is_anonymous=True, allows_multiple_answers=False) -> Message

Send a poll to a chat.

Parameter Type Default Description
question str (required) Poll question.
options list[str] (required) List of answer options (2-10).
correct_option int | None None Index of the correct answer (for quizzes).
explanation str | None None Explanation shown after voting.
is_anonymous bool True Whether the poll is anonymous.
allows_multiple_answers bool False Whether users can select multiple options.
async send_dice(chat_id, emoji="🎲", *, reply_to=None) -> Message

Send a dice animation. Supported emojis: 🎲 (dice), 🎯 (darts), 🏀 (basketball), (football), 🎳 (bowling), 🎰 (slot machine).

async send_reaction(chat_id, message, emoji) -> bool

React to a message with an emoji.

Returns: True on success.

async send_sticker(chat_id, sticker, *, emoji=None, sticker_set=None, progress_callback=None, reply_to=None, **kwargs) -> Message

Send a sticker.

async send_video_note(chat_id, video, *, caption=None, duration=0, width=0, height=0, progress_callback=None, reply_to=None, **kwargs) -> Message

Send a round video note.

async send_chat_action(chat_id, action="typing") -> bool

Send a typing indicator or upload progress status.

Supported actions: "typing", "upload_photo", "record_video", "upload_video", "record_audio", "upload_audio", "upload_document", "find_location", "record_voice", "upload_voice", "choose_sticker".

Chat Management

async ban_user(chat_id, user_id) -> bool

Ban a user from a chat. The user cannot rejoin unless unbanned.

async unban_user(chat_id, user_id) -> bool

Unban a previously banned user, allowing them to rejoin the chat.

async mute_user(chat_id, user_id) -> bool

Mute a user (restrict them from sending any messages in the chat).

async unmute_user(chat_id, user_id) -> bool

Unmute a user, restoring their ability to send messages.

async safe_ban_user(chat_id, user_id) -> bool

Ban a user, catching permission errors gracefully. Never raises — returns False on failure.

async safe_send_message(chat_id, text=None, *, reply_to=None, parse_mode=None, link_preview=True, **kwargs) -> Message

Send a message, catching permission errors gracefully. Never raises — returns a fallback Message on failure.

async unblock_chat_member(chat_id, user_id) -> bool

Remove a user from the account's block list.

async get_chat_blocked_users(chat_id) -> list[dict]

Fetch all blocked users in a chat. Returns a list of dicts with user_id and date keys.

async join_chat(chat_id) -> Any

Join a chat by ID, username, or invite link. Returns a Chat object.

Raises typed exceptions: InvalidInviteLinkError, InviteLinkExpiredError, ChatFullError, ChatDeactivatedError, MembershipRequiredError, FloodWait, ChatNotFound, JoinChatError.

async leave_chat(chat_id) -> bool

Leave a chat. Auto-detects whether it's a channel or basic group.

async leave_group(chat_id) -> bool

Leave a basic group specifically.

async leave_channel(chat_id) -> bool

Leave a channel or supergroup specifically.

async get_all_groups() -> list[dict]

Get all groups and channels the user is a member of. Returns list of dicts with id, title, type keys.

async leave_all_groups(exclude_ids=None) -> dict

Leave all basic groups. Returns a dict with success, failed, skipped counts and details.

async leave_all_channels(exclude_ids=None) -> dict

Leave all channels. Returns a dict with success, failed, skipped counts and details.

async leave_all(exclude_ids=None) -> dict

Leave all groups and channels at once. Returns summary dict.

async delete_all_private(*, revoke=False, exclude_ids=None, delay=1.0, on_progress=None) -> dict

Delete all private chat dialogs. Returns summary dict with counts and details.

async pin_chat_message(chat_id, message_id, notify=True) -> bool

Pin a message by ID. If notify=True, all members are notified.

async unpin_chat_message(chat_id, message_id) -> bool

Unpin a specific message by ID.

async pin_message(chat_id, message, *, notify=False) -> bool

Pin a message. Accepts either a Message object or an integer message ID.

async unpin_message(chat_id, message) -> bool

Unpin a message. Accepts either a Message object or an integer message ID.

async unpin_all_messages(chat_id) -> bool

Unpin all pinned messages in a chat at once.

async mark_read(chat_id, message=None) -> bool

Mark a specific message (or all messages in a chat) as read.

Info Retrieval

async get_me() -> Any

Get the currently authenticated user. Returns a User object or None.

async get_chat(chat_id) -> Any

Get a chat entity by ID or username.

async get_full_chat(chat_id) -> Optional[Chat]

Get full chat info including title and member_count. Supports numeric IDs, usernames, and invite link hashes.

async is_chat_member(chat_id) -> bool

Check if the current user is a member of the specified chat.

async get_user(user_id) -> Optional[User]

Get full user info by ID or @username. Returns a User with bio, status, and all other fields.

async get_bio(user_id) -> Optional[str]

Get a user's biography/about text.

async get_first_name(user_id) -> Optional[str]

Get a user's first name.

async get_last_name(user_id) -> Optional[str]

Get a user's last name.

async get_full_name(user_id) -> Optional[str]

Get a user's full name (first + last name combined).

async get_username(user_id) -> Optional[str]

Get a user's username without the @ prefix.

async get_user_link(user_id) -> Optional[str]

Get a t.me/ profile link for the user.

async get_description(chat_id) -> Optional[str]

Get a group/channel description (about text).

async get_group_link(chat_id) -> Optional[str]

Get the t.me/ invite link for a group or channel.

async get_group_id(username) -> Optional[int]

Resolve a @username to a chat/channel integer ID.

async get_chat_info(chat_id) -> Optional[dict]

Get comprehensive chat info as a dictionary with keys: id, title, username, link, description, member_count, type.

async get_user_info(user_id) -> Optional[dict]

Get comprehensive user info as a dictionary with keys: id, first_name, last_name, full_name, username, link, bio, phone, is_bot, is_premium, status.

async get_messages(chat_id, *, limit=100, **kwargs) -> list[Message]

Get messages from a chat. Returns list of Message objects, newest first.

async iter_messages(chat_id, *, limit=1000, offset_id=0, search=None, batch_size=100) -> AsyncGenerator[Message, None]

Async generator yielding messages in memory-efficient batches. Ideal for processing large histories without loading everything into memory.

Parameter Type Default Description
chat_id int | str (required) Chat to iterate messages from.
limit int 1000 Maximum number of messages to yield.
offset_id int 0 Start from this message ID (newer messages).
search str | None None Search query to filter messages.
batch_size int 100 Number of messages to fetch per API call.
async search_messages(chat_id, query, *, limit=100) -> list[Message]

Search for messages in a chat by text query.

async get_history(chat_id, *, limit=100) -> list[Message]

Alias for get_messages().

async get_members(chat_id, *, limit=100) -> list[ChatMember]

Alias for get_chat_members().

async iter_chat_members(chat_id, *, limit=200, batch_size=100) -> AsyncGenerator[ChatMember, None]

Async generator yielding chat members in batches.

async iter_members(chat_id, *, limit=200, batch_size=100) -> AsyncGenerator[ChatMember, None]

Alias for iter_chat_members().

async iter_dialogs(*, limit=500, batch_size=100) -> AsyncGenerator[Chat, None]

Async generator yielding dialogs (chats) in batches.

async get_contacts() -> list[User]

Get the account's contact list as User objects.

async resolve_username(username) -> int

Resolve a username to a numeric ID. Returns 0 if not found.

async resolve_peer(peer) -> Any

Resolve a peer identifier to an MTProto InputPeer object. Supports integer IDs, @username, and phone numbers.

Profile Management

async update_profile(first_name=None, last_name=None, about=None, bio=None) -> bool

Update the current user's profile fields. Returns True on success.

async set_profile_photo(photo) -> bool

Upload and set a new profile photo. photo can be a file path or bytes.

async delete_profile_photo(photo_id=None) -> bool

Delete a profile photo. Pass None to delete the most recent one.

async set_username(username) -> bool

Change the current user's username. Pass an empty string "" to remove it.

async set_offline(offline=True) -> bool

Set the user's online/offline appearance. True = appear offline.

Low-level

async invoke(*args, **kwargs) -> Any

Invoke a raw MTProto API method. This is a low-level escape hatch for API methods not covered by the high-level interface.

async export_session_string() -> str

Export the current session as a portable base64 string. Useful for deploying without session files.

Conversation

conversation(chat_id, *, timeout=30.0, exclusive=True) -> Conversation

Create an interactive conversation context manager for request/response style messaging. See Conversation API below.


Conversation API

A context-managed conversational flow for interactive bot dialogs. Used with async with client.conversation(peer) as conv:.

Constructor

Conversation(
    client: Client,
    peer: Union[int, str],
    timeout: Optional[float] = 30.0,
    exclusive: bool = True,
)
Parameter Type Default Description
client Client (required) The SplusPy client instance.
peer int | str (required) Chat ID or username to converse with.
timeout float 30.0 Default timeout in seconds for waiting responses.
exclusive bool True If True, only captures messages from the target peer.

Methods

async send_message(text, **kwargs) -> Message

Send a message in this conversation.

async get_response(timeout=None) -> Message

Wait for the next incoming message from the conversation peer. Raises ConversationTimeoutError if no message arrives within the timeout.

async wait_response(timeout=None) -> Message

Alias for get_response().

async send_and_wait(text, *, timeout=None, **kwargs) -> Message

Send a message and wait for a reply in a single call. Convenience method combining send_message + get_response.

empty() -> bool

Returns True if the internal message queue is empty.

clear() -> None

Discard any unread messages in the internal queue.

Usage Example

async with client.conversation(chat_id, timeout=30) as conv:
    await conv.send_message("What is your name?")
    response = await conv.get_response()
    await conv.send_and_wait(f"Nice to meet you, {response.text}!")

Client Chat Management Mixin Methods

Additional chat management methods available on the Client instance.

async get_chat_members(chat_id, limit=200) -> list[Any]

Get a list of members in a channel/megagroup using raw MTProto. Returns User objects.

async restrict_user(chat_id, user_id, send_messages=False, until_date=0, *, send_media=False, send_stickers=False, send_gifs=False, send_inline=False, embed_links=False, send_polls=False, change_info=False, invite_users=False, pin_messages=False, manage_topics=False) -> bool

Restrict a user in a megagroup/channel with fine-grained permission control.

Parameter Type Default Description
send_messages bool False Allow sending messages.
send_media bool False Allow sending media.
send_stickers bool False Allow sending stickers.
send_gifs bool False Allow sending GIFs.
send_inline bool False Allow using inline bots.
embed_links bool False Allow embedding links.
send_polls bool False Allow sending polls.
change_info bool False Allow changing chat info.
invite_users bool False Allow inviting users.
pin_messages bool False Allow pinning messages.
manage_topics bool False Allow managing topics.
until_date int 0 Restriction expiry timestamp. 0 = permanent.
async promote_member(chat_id, user_id, is_admin=True, privileges=None, *, rank="") -> bool

Promote or demote a user in a megagroup/channel. privileges is a dict of permission booleans. rank sets the admin rank title.

async set_chat_permissions(chat_id, send_messages=None, send_media=None, send_stickers=None, send_gifs=None, send_polls=None, embed_links=None, invite_users=None, change_info=None, pin_messages=None) -> bool

Set the default chat permissions for all non-admin members. True = allowed, False = restricted, None = no change.

async set_chat_title(chat_id, title) -> bool

Change the title of a channel or supergroup.

async set_chat_about(chat_id, about) -> bool

Change the about/description text of a chat (max 70 characters).

async set_chat_photo(chat_id, file_path) -> bool

Upload and set a new photo for a channel or supergroup.

async start_group_call(chat_id, title=None) -> bool

Start a group call (voice chat) in a supergroup. Requires admin rights with manage_call permission.

async stop_group_call(chat_id) -> bool

Stop (discard) an active group call in a supergroup.


Client Media Mixin Methods

Low-level media operations using raw MTProto.

Constant Value Description
UPLOAD_CHUNK_SIZE 524288 (512 KB) Default upload chunk size.
DOWNLOAD_CHUNK_SIZE 1048576 (1 MB) Default download chunk size.
MAX_SMALL_FILE_SIZE 10485760 (10 MB) Max size for small file uploads.
async send_file(chat_id, file_path, caption="", progress_callback=None, *, force_document=False, force_photo=False, thumb=None, attributes=None, voice_note=False, video_note=False, background=False, clear_draft=False, noforwards=False, schedule_date=None, reply_to=None, duration=0, width=0, height=0, performer=None, title=None, supports_streaming=True, sticker_emoji=None, sticker_set=None) -> Any

Send a file using raw MTProto upload with chunked transfer. Auto-detects photo vs document based on file type.

async send_location_media(chat_id, lat, lon, *, reply_to=None) -> Any

Send a geographic location using raw MTProto.

async send_contact_media(chat_id, phone, first_name, last_name="", vcard="", *, reply_to=None) -> Any

Send a contact card using raw MTProto.

async send_dice_media(chat_id, emoticon="🎲", *, reply_to=None) -> Any

Send a dice animation using raw MTProto.

async download_media(message, file_name=None, progress_callback=None, *, dc_id=None) -> Optional[str]

Download a file from a message. Returns the path to the downloaded file, or None on failure.

Parameter Type Default Description
message Message | Any (required) Message containing the media.
file_name str | None None Custom file name for the download.
progress_callback Callable | None None Progress callback: fn(current, total).
dc_id int | None None Data center ID override.

Message Model

The Message dataclass represents a received or sent message.

Attributes

Field Type Default Description
id int 0 Unique message identifier.
text str "" Message text or media caption.
date datetime | None None Message timestamp.
chat_id int 0 Chat this message belongs to.
chat Chat | None None Resolved Chat object.
sender_id int | None None Sender user ID.
sender User | None None Resolved User object.
media Media | None None Attached media object.
media_type MessageMediaType NONE Kind of media (PHOTO, VIDEO, etc.).
reply_to int | None None ID of the message being replied to.
reply_to_sender_id int | None None Sender ID from the reply header.
reply_to_message Message | None None Full replied-to message object.
entities list[MessageEntity] [] Formatting entities (bold, links, etc.).
forward_origin dict | None None Forward origin information.
views int 0 View count for channel messages.
edit_date datetime | None None Last edit timestamp.
is_group bool False Whether from a group chat.
is_channel bool False Whether from a channel.
is_private bool False Whether from a private chat.
new_chat_members list [] Users who joined (service messages).
left_chat_member User | None None User who left (service messages).
service_type str | None None Type of service event.

Properties

Property Return Type Description
message_id int Alias for id.
is_forwarded bool Whether message was forwarded.
is_reply bool Whether message is a reply to another message.
is_media bool Whether message contains any media attachment.

Methods

async reply(text=None, *, parse_mode=None, link_preview=True, buttons=None, file=None, **kwargs) -> Message

Reply to this message (sends with reply header).

async edit(text=None, *, parse_mode=None, link_preview=True, buttons=None, delay=0) -> Message

Edit this message's text. If delay > 0, waits before editing.

async delete(*, delay=0) -> bool

Delete this message. If delay > 0, waits before deleting.

async forward(chat_id) -> Message

Forward this message to another chat (keeps the forward header).

async copy(chat_id) -> Message

Copy this message to another chat (no forward header).

async get_reply_message() -> Optional[Message]

Fetch the full message that this message replies to.

async pin(notify=True) -> bool

Pin this message in its chat.

async unpin() -> bool

Unpin this message from its chat.

async react(emoji) -> bool

React to this message with an emoji.

async mark_read() -> bool

Mark this message as read.

async unblock_sender() -> bool

Unblock the sender of this message.

async download(file_path=None, *, progress=None) -> Optional[str]

Download the media attached to this message. Returns the local file path.

Aliases: download_media(), download_file(), save().

async reply_photo(photo, caption=None, **kwargs) -> Message

Reply with a photo.

async reply_video(video, caption=None, **kwargs) -> Message

Reply with a video.

async reply_voice(voice, caption=None, **kwargs) -> Message

Reply with a voice note.

async reply_document(document, caption=None, **kwargs) -> Message

Reply with a document.

async reply_audio(audio, caption=None, *, duration=0, performer=None, title=None, **kwargs) -> Message

Reply with an audio file.

async reply_animation(animation, caption=None, **kwargs) -> Message

Reply with a GIF/animation.

async reply_sticker(sticker, **kwargs) -> Message

Reply with a sticker.

async reply_location(lat, lon) -> Message

Reply with a geographic location.

async reply_contact(phone, first_name, last_name="", *, vcard="") -> Message

Reply with a contact card.

async reply_poll(question, options, *, is_anonymous=True, allows_multiple_answers=False) -> Message

Reply with a poll.

async reply_dice(emoji="🎲") -> Message

Reply with a dice animation.

async reply_video_note(video, *, caption=None, duration=0, **kwargs) -> Message

Reply with a round video note.


User Model

Represents a Soroush Plus user.

Attributes

Field Type Default Description
id int (required) Unique user identifier.
first_name str "" First name.
last_name str "" Last name.
username str | None None Username without @.
phone str | None None Phone number.
bio str | None None Biography/about text.
is_bot bool False Whether this is a bot account.
is_self bool False Whether this is the current authenticated user.
is_premium bool False Whether user has premium subscription.
status UserStatus EMPTY Online status.

Properties

Property Return Type Description
full_name str Combined first + last name.
mention str @username if available, otherwise full name.
link str | None https://t.me/username profile link. None if no username.

Chat Model

Represents a Soroush Plus chat (private, group, supergroup, or channel).

Attributes

Field Type Default Description
id int (required) Unique chat identifier.
title str "" Chat title.
type ChatType PRIVATE Type of chat.
username str | None None Public username.
description str | None None Chat description.
member_count int 0 Number of members.

Properties

Property Return Type Description
is_private bool Whether it's a private (1-to-1) chat.
is_group bool Whether it's a group or supergroup.
is_supergroup bool Whether it's a supergroup.
is_channel bool Whether it's a channel.
link str | None Deep link to the chat.
display_name str Human-readable name (title or username).

Subclasses

  • Channel(Chat) — Pre-set type to CHANNEL.
  • Group(Chat) — Pre-set type to GROUP or SUPERGROUP.

ChatMember Model

Represents a member of a chat.

Attributes

Field Type Description
user User The user object.
status ChatMemberStatus Membership status.
joined_date datetime | None When they joined.
invited_by int | None Who invited them.
restricted_until int | None Restriction expiry timestamp.

Properties

Property Return Type Description
user_id int Shortcut to user.id.
is_creator bool Whether this is the chat creator/owner.
is_admin bool Whether they have admin rights (includes creator).
is_banned bool Whether they are banned.
is_left bool Whether they have left the chat.

Button Factory

Static factory methods for creating keyboard buttons. All methods are @staticmethod.

Inline Buttons

Button.inline(text, callback_data) -> InlineKeyboardButton

Create an inline keyboard button with callback data.

Parameter Type Description
text str Button display text.
callback_data bytes Data sent back when button is pressed.
Button.url(text, url) -> InlineKeyboardButton

Create an inline button that opens a URL.

Button.switch_inline(text, query="") -> InlineKeyboardButton

Create an inline button that switches to inline mode with a pre-filled query.

Button.switch_inline_current(text, query="") -> InlineKeyboardButton

Same as switch_inline but keeps the user in the current chat.

Reply Buttons

Button.text(text, resize=True, one_time=False) -> ReplyMarkup

Create a single-button reply keyboard.

Special Buttons

Button.request_location(text="Share Location") -> KeyboardButton

Create a button that requests the user's location.

Button.request_phone(text="Share Phone") -> KeyboardButton

Create a button that requests the user's phone number.

Button.clear() -> ReplyMarkup

Create a special reply markup that removes/hides the current keyboard.

Keyboard Builders

Button.build_inline(*rows) -> ReplyMarkup

Build an inline keyboard from rows of buttons.

keyboard = Button.build_inline(
    [Button.inline("Option 1", b"opt1"), Button.inline("Option 2", b"opt2")],
    [Button.url("Visit", "https://example.com")]
)
Button.build_reply(*rows, resize=True, one_time=False) -> ReplyMarkup

Build a reply keyboard from rows of buttons.

kb = Button.build_reply(
    [Button.text("Menu"), Button.text("Settings")],
    [Button.text("Help")]
)

CallbackQuery Model

Represents a callback query from an inline button press.

Attributes

Field Type Description
id str Unique callback query identifier.
data bytes | None Raw callback data bytes.
chat_instance str Chat instance identifier.
from_user User The user who pressed the button.
message Message | None The message containing the button.

Properties

Property Return Type Description
text str | None Decoded callback data as UTF-8 string.

Methods

async answer(text=None, show_alert=False, cache_time=0) -> bool

Answer the callback query. Shows a popup notification if show_alert=True.

async edit_message(text=None, reply_markup=None) -> bool

Edit the message that contains the inline keyboard.


InlineQuery Model

Represents an inline query from a user.

Attributes

Field Type Description
id str Unique inline query identifier.
query str The search query text.
offset str Pagination offset.
chat_type str | None Chat type context.
from_user User The user who sent the query.

Methods

async answer(results, cache_time=300, is_personal=True, next_offset=None) -> bool

Answer the inline query with a list of result dictionaries.


Events

Event (Base Class)

Abstract base for all events. All events support propagation control.

stop_propagation() -> None

Stop the event from being passed to subsequent handlers with lower priority.

is_propagation_stopped (property) -> bool

Returns True if propagation was stopped by a handler.

HandlerPriority

Controls the order in which handlers are executed.

Value Int Description
FIRST 0 Executes first, before all others.
HIGH 10 Executes early.
NORMAL 50 Default priority.
LOW 100 Executes late.
LAST 200 Executes last, after all others.

NewMessage Event

Fired when a new message is received.

Fields
Field Type Description
message Message The incoming message.
pattern_match Pattern.Match | None Regex match result (if filters.regex was used).
Properties

text, chat_id, sender_id, is_private, is_group, is_channel, raw_text, sender, chat, reply_to, reply_to_sender_id, reply_to_message, media, photo, video, voice, document, sticker, forward_origin, new_chat_members, left_chat_member, service_type.

Methods
async def reply(self, text: str, **kwargs) -> Message
async def respond(self, text: str, **kwargs) -> Message  # Send without reply header
async def delete(self, **kwargs) -> bool
async def edit(self, text: str, **kwargs) -> Message
async def forward(self, chat_id: int) -> Message
async def pin(self, notify=False) -> bool
async def react(self, emoji: str) -> bool
async def mark_read(self) -> bool
async def download(self, file_path=None) -> Optional[str]

ErrorEvent

Fired when a handler raises an exception.

Field Type Description
exception Exception The raised exception.
handler Callable | None The handler that failed.
update Any The original update.
Property Type Description
message str Error message string.
type str Exception class name.

ChatAction Event

Fired on chat actions (joins, leaves, pins).

Property Type Description
is_join bool Whether a user joined.
is_leave bool Whether a user left.
is_pin bool Whether a message was pinned.
user_name str Display name of the user.

UserUpdate Event

Fired when a user's online status changes.

Property Type Description
is_online bool Whether the user came online.
is_offline bool Whether the user went offline.

MessageDeleted Event

Fired when messages are deleted. Fields: deleted_ids (list of ints), chat_id.

MessageRead Event

Fired when messages are read. Fields: read_ids (list of ints), chat_id, max_id.

Album Event

Fired when a group of media messages is received (e.g., multiple photos sent at once).

Field Type Description
messages list[Message] All messages in the album.
chat_id int The chat where the album was received.
group_id str Album group identifier.

| Property | Type | Description | |----------||-------------| | total | int | Number of messages in the album. |


Filters

Filters are composable predicates that determine which messages trigger a handler. Use & (AND), | (OR), and ~ (NOT) to combine them.

Chat-Type Filters

Filter Matches
filters.private Messages from private (1-to-1) chats.
filters.group Messages from group or supergroup chats.
filters.channel Messages from channels.

Content-Type Filters

Filter Matches
filters.text Messages with non-empty text.
filters.photo Messages containing a photo.
filters.video Messages containing a video.
filters.audio Messages containing audio.
filters.voice Messages containing a voice note.
filters.document Messages containing a document.
filters.sticker Messages containing a sticker.
filters.animation Messages containing a GIF.
filters.contact Messages containing a contact card.
filters.location Messages containing a location.
filters.poll Messages containing a poll.
filters.video_note Messages containing a video note.
filters.dice Messages containing a dice animation.
filters.media Messages with any media attachment.

State Filters

Filter Matches
filters.reply Messages that are replies to other messages.
filters.forwarded Forwarded messages.
filters.me Messages sent by the current user.
filters.bot Messages sent by bots.
filters.outgoing Outgoing (outbound) messages.
filters.incoming Incoming (inbound) messages.
filters.mentioned Messages where the current user is mentioned.
filters.new_chat_members Service messages: users joined.
filters.left_chat_member Service messages: user left/was removed.
filters.service Any system/service message.

Factory Functions

filters.command(*commands, prefixes=None) -> Command

Create a command filter. Matches messages starting with / or ! followed by the given command names.

@bot.on_message(filters.command("start", "help"))
async def handler(client, message):
    # Matches /start, /help, !start, !help
    pass
Parameter Type Default Description
*commands str (required) Command names to match.
prefixes list[str] | None None Custom prefixes. Default: ["/", "!"].
filters.regex(pattern, flags=0) -> Regex

Create a regex filter. The match object is stored on message.pattern_match.

@bot.on_message(filters.regex(r"(\d+)"))
async def handler(client, message):
    number = message.pattern_match.group(1)
filters.user(*user_ids) -> UserFilter

Create a user ID filter. Matches messages from specific users.

filters.chat(*chat_ids) -> ChatFilter

Create a chat ID filter. Matches messages from specific chats.

filters.text_contains(substring, case_sensitive=True) -> TextContains

Filter messages that contain a specific substring.

filters.text_startswith(prefix) -> TextStartsWith

Filter messages that start with a specific prefix.

filters.text_endswith(suffix) -> TextEndsWith

Filter messages that end with a specific suffix.

filters.length(min=0, max=999999) -> Length

Filter messages by text length.

filters.from_callable(func) -> Filter

Wrap any callable as a filter. If the argument is already a Filter, returns it as-is.

Composition Examples

# AND: private AND text
@bot.on_message(filters.private & filters.text)

# OR: photo OR video
@bot.on_message(filters.photo | filters.video)

# NOT: NOT outgoing
@bot.on_message(~filters.outgoing)

# Complex: (private OR group) AND text AND NOT bot
@bot.on_message((filters.private | filters.group) & filters.text & ~filters.bot)

FSM (Finite State Machine)

State

Represents a single FSM state. Auto-named via the __set_name__ descriptor protocol when assigned as a class attribute.

State(name: Optional[str] = None)
Property Type Description
name str The state name (auto-derived from class attribute name).

StateTransition

Represents a state transition with an optional async guard function.

StateTransition(target: State, guard: Optional[Callable] = None)
async check(update) -> bool

Check whether this transition is allowed. If no guard is set, always returns True.

FSMContext

Per-user context for reading/writing FSM state and data.

FSMContext(storage: Storage, user_id: int, prefix: str = "fsm")
Property Type Description
key str Full storage key including prefix and user ID.
state_key str Key for storing the current state.
data_key str Key for storing user data.
user_id int The user this context belongs to.
async get_state() -> Optional[State]

Get the current state for this user. Returns None if no state is set.

async set_state(state) -> None

Set the current state for this user. Pass None to clear the state.

async get_data() -> dict[str, Any]

Get all stored data for this user in the current state.

async set_data(**kwargs) -> None

Update the stored data with the provided key-value pairs.

async get(key, default=None) -> Any

Get a single data value by key.

async set(key, value) -> None

Set a single data value.

async reset() -> None

Clear both the state and all data for this user.

async finish() -> None

Alias for reset(). Clears state and data.

StateMachine

Factory for FSMContext with decorator-based state routing.

StateMachine(storage: Storage)
def state(state, *, on_enter=None, on_leave=None) -> Callable

Decorator that registers a handler for a specific state.

@sm.state(Form.name)
async def handle_name(ctx, message):
    await ctx.set(name=message.text)
    return StateTransition(to=Form.age)
def on_enter(state) -> Callable

Decorator that registers a callback when entering a state.

def on_leave(state) -> Callable

Decorator that registers a callback when leaving a state.

def fallback() -> Callable

Decorator that registers a handler for unmatched states (no registered handler for the current state).

def context(user_id, prefix="fsm") -> FSMContext

Create an FSMContext for a specific user.

async handle(update, user_id) -> bool

Route an update to the appropriate state handler. Returns True if a handler was found and executed.

Usage Example

from spluspy.fsm import State, StateMachine, StateTransition
from spluspy.storage import SQLiteStorage

storage = SQLiteStorage("fsm.db")
sm = StateMachine(storage)

class Registration:
    name = State()
    age = State()
    complete = State()

@sm.state(Registration.name)
async def handle_name(ctx, message):
    await ctx.set(name=message.text)
    return StateTransition(to=Registration.age)

@sm.state(Registration.age)
async def handle_age(ctx, message):
    await ctx.set(age=message.text)
    return StateTransition(to=Registration.complete)

@sm.state(Registration.complete)
async def handle_complete(ctx, message):
    data = await ctx.get()
    await message.reply(f"Done: {data}")
    await ctx.finish()

Storage Backends

Storage (Abstract Base)

All storage backends implement this interface.

async def get(key: str) -> Optional[Any]
async def set(key: str, value: Any, ttl: Optional[int] = None) -> None
async def delete(key: str) -> bool
async def exists(key: str) -> bool
async def clear() -> None

MemoryStorage

In-memory non-persistent storage with optional TTL support. Data is lost when the process exits.

MemoryStorage()

Use case: Development, testing, short-lived sessions.

SQLiteStorage

Persistent SQLite-backed storage. Automatically creates the database and table on start().

SQLiteStorage(db_path: str = "spluspy_storage.db")
Method Description
async start() Open the database and create the table if needed.
async close() Close the database connection.

Use case: Single-user bots, local persistence.

RedisStorage

Persistent Redis-backed storage. Requires pip install spluspy[redis].

RedisStorage(
    url: str = "redis://localhost:6379/0",
    prefix: str = "spluspy:",
    default_ttl: Optional[int] = None,
)
Extra Method Description
async increment(key, amount=1) -> int Atomically increment a counter.
async set_hash(key, mapping, ttl=None) Store a dict as a Redis hash.
async get_hash(key) -> dict Retrieve a Redis hash as a dict.

Use case: Multi-process deployments, shared state.

PostgresStorage

Persistent PostgreSQL-backed storage. Requires pip install spluspy[postgres].

PostgresStorage(
    dsn: str = "postgresql://localhost/spluspy",
    table: str = "spluspy_kv",
    prefix: str = "",
)
Extra Method Description
async cleanup_expired() -> int Remove expired entries. Returns count removed.
async keys(pattern="*") -> list[str] List all keys matching a pattern.

Use case: Enterprise deployments, existing PostgreSQL infrastructure.

Factory Function

def get_storage(backend: str = "memory", **kwargs) -> Storage

Create a storage by name: "memory", "sqlite", "redis", "postgres".

EntityCache (Storage)

SQLite-backed peer resolution cache for caching user/chat entities.

EntityCache(storage: Optional[Storage] = None)
Method Description
async start() Initialize the cache.
async get(entity_id) -> Optional[dict] Get entity by ID.
async get_by_username(username) -> Optional[dict] Get entity by username.
async get_by_phone(phone) -> Optional[dict] Get entity by phone.
async put(entity_id, access_hash, entity_type="user", *, username=None, phone=None) Store an entity.
async put_entity(entity) Store an entity from an object.
async put_many(entities) -> int Store multiple entities. Returns count stored.
async remove(entity_id) -> bool Remove an entity by ID.
async clear() Remove all cached entities.
async count() -> int Get the number of cached entities.

Middleware

Middleware (Abstract Base)

All middleware must implement on_update. The handler chain is executed in FIFO order.

class Middleware(ABC):
    @abstractmethod
    async def on_update(self, update: Any, handler: Handler) -> Any

    async def on_startup(self) -> None   # Called when client starts
    async def on_shutdown(self) -> None  # Called when client stops

MiddlewareManager

Manages an ordered chain of middleware. Errors in one middleware are isolated and don't break the chain.

MiddlewareManager()
Method Description
add(mw: Middleware) Add middleware to the end of the chain.
remove(mw: Middleware) Remove middleware from the chain.
async execute(update, final_handler) -> Any Execute the middleware chain.
async startup() Notify all middleware of client startup.
async shutdown() Notify all middleware of client shutdown.

RateLimitMiddleware

Rate limiting middleware for automatic flood wait handling.

RateLimitMiddleware(
    rate: float = 1.0,
    capacity: float = 1.0,
    flood_sleep_threshold: int = 60,
)
Parameter Type Default Description
rate float 1.0 Token refill rate (tokens per second).
capacity float 1.0 Maximum burst capacity.
flood_sleep_threshold int 60 Max seconds to sleep on flood wait.
Method Description
async on_update(update, handler) -> Any Process update through rate limiter.
handle_flood_wait(method, seconds) Register a flood wait event.
get_stats() -> dict Get rate limiter statistics.

Usage Example

from spluspy.middleware import Middleware

class LoggingMiddleware(Middleware):
    async def on_update(self, update, handler):
        print(f"Update: {update}")
        result = await handler(update)
        print("Handler completed")
        return result

bot.middleware.add(LoggingMiddleware())

Utils

LRUCache

Thread-safe LRU cache with optional TTL (time-to-live) for automatic expiration.

LRUCache(max_size: int = 1024, default_ttl: Optional[float] = None)
Method Description
async get(key) -> Optional[V] Get a value by key. Returns None if not found or expired.
async set(key, value, ttl=None) Set a key-value pair. Optional per-entry TTL override.
async delete(key) -> bool Delete a key. Returns True if the key existed.
async exists(key) -> bool Check if a key exists and is not expired.
async clear() Remove all entries.
async size() -> int Get the current number of entries.
get_stats() -> dict Returns size, max_size, hits, misses, hit_rate.
cache = LRUCache(max_size=1000, ttl=300)  # 5-minute TTL
await cache.set("key", "value")
value = await cache.get("key")
stats = cache.get_stats()  # {'hits': 42, 'misses': 3, 'hit_rate': 0.933, ...}

RateLimiter

Multi-endpoint rate limiter using the token bucket algorithm with flood wait handling.

RateLimiter(default_rate: float = 1.0, default_capacity: float = 1.0, flood_sleep_threshold: int = 60)
Method Description
async acquire(method="default") Wait until a token is available for the given method.
register_flood_wait(method, seconds) Register a flood wait for the method (prevents requests until the wait expires).
limit(method="default", rate=None, capacity=None) -> Callable Decorator that rate-limits a function.
get_wait_time(method="default") -> float Get remaining wait time for the method (0 = ready).
clear_flood_wait(method) Clear the flood wait state for a method.

TokenBucket

Low-level token bucket implementation.

TokenBucket(rate: float = 1.0, capacity: float = 1.0)
Method Description
async acquire(tokens=1.0, blocking=True) -> bool Acquire tokens. If blocking=True, waits until available. Returns True on success.

ChatLockManager

Per-chat granular locking to eliminate global lock contention in concurrent operations.

ChatLockManager()
Method Description
acquire(key: int) Acquire a lock for a specific chat (async context manager).
release(key: int) Release the lock for a chat.
is_locked(key: int) -> bool Check if a chat is currently locked.
locked_count() -> int Number of currently locked chats.
pending_count() -> int Number of pending lock requests.

Helper Functions

def generate_random_id() -> int
# Generate a random 64-bit integer ID.

def timestamp_to_datetime(ts: Optional[int]) -> Optional[datetime]
# Convert a Unix timestamp to a datetime object.

def datetime_to_timestamp(dt: Optional[Union[datetime, date]]) -> Optional[int]
# Convert a datetime/date to a Unix timestamp.

def sanitize_filename(name: str) -> str
# Remove or replace unsafe characters from a filename.

def parse_mode(mode: Optional[str]) -> Optional[str]
# Normalize a parse mode string.

def chunk_list(items: list, size: int) -> list[list]
# Split a list into chunks of the given size.

def truncate(text: str, max_length: int = 4096) -> str
# Truncate text to a maximum length, adding "..." if truncated.

Batch Operations

async def batch_send(client, chat_id, messages, *, delay=0.1, parse_mode=None) -> list[Message]
# Send multiple text messages sequentially with a delay between each.

async def batch_delete(client, chat_id, messages, *, batch_size=100) -> bool
# Delete multiple messages in batches.

async def batch_forward(client, target_chat, source_chat, messages, *, batch_size=100, delay=0.1) -> list[Message]
# Forward multiple messages in batches.

async def batch_get_messages(client, chat_id, message_ids, *, batch_size=100) -> list[Message]
# Fetch multiple messages by ID in batches.

File Transfer

class TransferProgress:
    total: int          # Total file size in bytes
    transferred: int    # Bytes transferred so far
    speed: float        # Transfer speed in bytes/sec
    elapsed: float      # Elapsed time in seconds
    eta: float          # Estimated time remaining in seconds
    percent -> float    # Completion percentage (0-100)

class ProgressTracker:
    ProgressTracker(callback=None, update_interval=0.1)
    def update(self, chunk_size: int) -> None  # Called after each chunk
    def finish(self) -> None                    # Called when transfer completes
# Upload with progress
tracker = ProgressTracker(on_progress=lambda p: print(f"{p.percent}%"))
await bot.send_document(chat_id, "large_file.zip", progress=tracker)

# Download with progress
await message.download(progress=tracker)

TargetResolver

Resolves message targets from various sources (reply, username, ID, etc.).

TargetResolver(client: Client)
Method Description
async resolve(message, *, args=None) -> TargetResolution Resolve a target from a message context.
@dataclass
class TargetResolution:
    user_id: Optional[int]      # Resolved user ID
    message_id: Optional[int]   # Resolved message ID
    source: str                 # Resolution source: "reply", "username", "id", etc.
    display_name: Optional[str] # Human-readable name
def resolve_reply_sender(message: Message) -> Optional[int]
# Synchronously extract the sender ID from a reply header.

Logger Utilities

def setup_logging(level="INFO", log_file=None, log_dir="logs", max_bytes=10*1024*1024,
                  backup_count=5, use_color=True, console_output=True,
                  reconnect_only=False, errors_only=False) -> None
# Configure the logging system.

def get_logger(name=None, level=None) -> logging.Logger
# Get a named logger instance.

def set_level(level: Union[str, int]) -> None
# Change the global log level.

def log() -> logging.Logger
# Get the default root logger.

def log_event(logger, event: str, **data) -> None
# Log a structured event.

def log_request(logger, method: str, **params) -> None
# Log an API request.

def log_response(logger, method: str, status="OK", **data) -> None
# Log an API response.

def log_error(logger, error: Exception, context="") -> None
# Log an error with context.

def log_user_action(logger, action: str, user_id: int, chat_id=0, target_id=0, detail="") -> None
# Log a user action.

def log_security(logger, event: str, user_id: int, chat_id=0, reason="") -> None
# Log a security-related event.

def log_performance(logger, operation: str, duration_ms: float, **extra) -> None
# Log a performance measurement.

Version Check

async def check_for_update() -> Optional[str]
# Check PyPI for a newer version. Returns version string or None.

def print_update_notice(new_version: str) -> None
# Print a formatted update notice to the console.

async def check_and_notify() -> None
# Check and print update notice if available. Call at startup.

AfkManager

Smart auto-responder for AFK (Away From Keyboard) mode with per-chat rate limiting.

Constructor

AfkManager(
    client: Client,
    message: str = "I'm currently AFK. I'll reply when I'm back.",
    *,
    cooldown: float = 60.0,
    max_replies: Optional[int] = 10,
    only_private: bool = False,
    only_mentions: bool = False,
)
Parameter Type Default Description
client Client (required) The SplusPy client instance.
message str auto-reply text Default AFK response message.
cooldown float 60.0 Minimum seconds between replies to the same user.
max_replies int | None 10 Maximum total auto-replies per AFK session. None = unlimited.
only_private bool False If True, only auto-reply in private chats.
only_mentions bool False If True, only auto-reply when mentioned.

Properties

Property Type Description
is_afk bool Whether AFK mode is currently active.
afk_reason str The reason for being AFK.
afk_since float | None Unix timestamp when AFK mode started.
total_replies int Total number of auto-replies sent during this AFK session.

Methods

set_afk(enabled, *, reason="", message=None) -> None

Enable or disable AFK mode.

Parameter Type Default Description
enabled bool (required) True to activate AFK, False to deactivate.
reason str "" Reason for being AFK (shown in the auto-reply).
message str | None None Override the default AFK message for this session.
async handle(message) -> bool

Process an incoming message while AFK. Handles rate limiting and auto-reply. Returns True if a reply was sent.

get_stats() -> dict

Returns a dictionary with keys: is_afk, afk_reason, afk_since, total_replies, active_chats, cooldown, max_replies.

Usage Example

from spluspy import Client, filters
from spluspy.afk import AfkManager

bot = Client("my_account")
afk = AfkManager(bot, message="I'm currently AFK. Back soon!")

@bot.on_message(filters.command("afk"))
async def set_afk(client, message):
    afk.set_afk(True, reason="Lunch break")
    await message.reply("I'm now AFK!")

@bot.on_message(filters.command("back"))
async def unset_afk(client, message):
    afk.set_afk(False)
    await message.reply(f"Back! Sent {afk.total_replies} auto-replies.")

@bot.on_message(filters.private & filters.incoming)
async def auto_reply(client, message):
    if afk.is_afk:
        await afk.handle(message)

ChatAdmin

High-level chat administration API with batch operations and admin logging.

Constructor

ChatAdmin(client: Client)

Methods

async ban_user(chat_id, user_id, *, delete_messages=False) -> bool

Ban a user from a chat. Optionally delete their recent messages.

async unban_user(chat_id, user_id) -> bool

Unban a user, allowing them to rejoin.

async mute_user(chat_id, user_id, *, duration=None) -> bool

Mute a user. duration in seconds; None = permanent.

async unmute_user(chat_id, user_id) -> bool

Unmute a user.

async pin_chat_message(chat_id, message_id, notify=True) -> bool

Pin a message by ID.

async unpin_chat_message(chat_id, message_id) -> bool

Unpin a message by ID.

async pin_message(chat_id, message, *, notify=False) -> bool

Pin a message (accepts Message object or int).

async unpin_message(chat_id, message) -> bool

Unpin a message (accepts Message object or int).

async unpin_all(chat_id) -> bool

Unpin all pinned messages in a chat.

async purge_messages(chat_id, *, limit=100, before=None, after=None) -> int

Delete a batch of messages. Returns the count of deleted messages.

Parameter Type Default Description
chat_id int (required) Target chat.
limit int 100 Maximum messages to delete.
before int | None None Only delete messages with ID less than this.
after int | None None Only delete messages with ID greater than this.
async get_admin_log(chat_id, *, limit=100) -> list[dict]

Get admin event log. Returns list of dicts with id, date, user_id, action keys.

async bulk_action(chat_id, action, user_ids, *, delay=0.5, **kwargs) -> dict[int, bool]

Perform bulk ban/unban/mute/unmute. Returns a dict mapping user_id -> success.

Parameter Type Description
action str One of: "ban", "unban", "mute", "unmute".
user_ids list[int] List of user IDs to action.
delay float Delay between each action (to avoid flood).

MessageMirror

Real-time message cloning between chats.

Constructor

MessageMirror(client: Client)

Methods

add_route(source, targets, *, filter_func=None, strip_forward=True, strip_sender=False, add_prefix=None) -> None

Add a complete mirroring route from a source chat to one or more target chats.

Parameter Type Default Description
source int (required) Source chat ID to mirror from.
targets list[int] (required) Target chat IDs to mirror to.
filter_func Callable | None None Optional filter function: fn(message) -> bool.
strip_forward bool True Remove forward headers from mirrored messages.
strip_sender bool False Remove sender info from mirrored messages.
add_prefix str | None None Add a text prefix to mirrored messages.
add_source(source) -> None

Add a source chat for mirroring.

add_target(target, *, source=None) -> None

Add a target chat. If source is None, targets the most recently added source.

remove_route(source) -> None

Remove a mirroring route by source chat ID.

async start() -> None

Start the mirroring engine. Begins listening for new messages.

async stop() -> None

Stop the mirroring engine.

get_stats() -> dict

Returns: active (bool), routes (int), total_mirrored (int), sources (list of ints).

Usage Example

from spluspy.mirror import MessageMirror

mirror = MessageMirror(bot)

mirror.add_route(
    source=-1001234567890,
    targets=[-1009876543210, -1001112223334],
    strip_forward=True,
    strip_sender=False,
    add_prefix="[Mirror]"
)

await mirror.start()

Scheduler

Scheduler (Low-level)

Manages scheduled and recurring tasks.

Scheduler()
Property Type Description
running bool Whether the scheduler is running.
Method Description
add(name, callback, *, interval=None, delay=0.0, max_runs=None) -> ScheduledTask Schedule a task.
remove(name) -> bool Remove a task by name.
get(name) -> Optional[ScheduledTask] Get a task by name.
async start() Start the scheduler loop.
async stop() Stop the scheduler loop.

MessageScheduler

High-level message scheduling for bots.

MessageScheduler(client: Client)
Property Type Description
pending_count int Number of pending scheduled tasks.
Method Description
async start() Start the scheduler.
async stop() Stop the scheduler.
schedule_message(chat_id, text, *, delay=None, when=None, name=None) -> ScheduledTask Schedule a single message.
schedule_interval(chat_id, text, *, interval, name=None, max_runs=None) -> ScheduledTask Schedule a recurring message.
schedule_callback(name, callback, *, delay=None, interval=None, max_runs=None) -> ScheduledTask Schedule a custom callback.
cancel(name) -> bool Cancel a scheduled task by name.

Usage Example

from spluspy.scheduler.scheduler import MessageScheduler

scheduler = MessageScheduler(bot)

# Send a message every hour
scheduler.schedule_interval("hourly_greeting", chat_id, "Hello!", interval=3600)

# Send a message after a delay (5 minutes)
scheduler.schedule_once("reminder", chat_id, "Don't forget!", delay=300)

# Cancel a scheduled task
scheduler.cancel("hourly_greeting")

Error Hierarchy

All errors inherit from SplusPyError(Exception).

SplusPyError
├── SoroushPlusAPIError(message, code=0)
│   ├── RPCError(message, code=0)
│   │   ├── FloodWait(seconds, message="")      # Has .seconds attribute
│   │   ├── Unauthorized(message, code=0)
│   │   ├── Forbidden(message, code=0)
│   │   ├── BadRequest(message, code=0)
│   │   │   ├── ChatNotFound(message, code=0)
│   │   │   ├── UserNotFound(message, code=0)
│   │   │   └── MessageNotFound(message, code=0)
│   │   └── ...
│   ├── SessionExpiredError(message)
│   └── SessionError(message)
├── AuthError(message)
├── ValidationError(message)
├── TimeoutError(message)
├── ConnectionError(message)
├── PluginError(message)
├── FSMError(message)
├── StorageError(message)
├── JoinChatError(message, link="")
│   ├── InvalidInviteLinkError(link, reason="...")
│   ├── InviteLinkExpiredError(link="", reason="...")
│   ├── ChatFullError(chat_id="", limit=0)
│   ├── ChatDeactivatedError(chat_id="")
│   └── MembershipRequiredError(chat_id="", reason="...")
└── FloodWaitError(message)   # From rate limiter

Error Handling Example

from spluspy.errors import FloodWait, Unauthorized, BadRequest

@bot.on_message()
async def safe_handler(client, message):
    try:
        await message.reply("Hello!")
    except FloodWait as e:
        await asyncio.sleep(e.seconds)  # Wait the required time
    except Unauthorized:
        await message.reply("Not authorized!")
    except BadRequest as e:
        print(f"Bad request: {e}")

Global Error Handler

from spluspy.events import ErrorHandlerBuilder

error_handler = ErrorHandlerBuilder()
error_handler.on(FloodWait)(lambda e: print(f"Flood wait: {e.seconds}s"))
error_handler.on(Unauthorized)(lambda e: print("Unauthorized"))

bot.on_error(error_handler.build())

Plugin System

Plugin

Represents a loaded plugin.

Plugin(name: str, module: Any, enabled: bool = True)
Attribute Type Description
name str Plugin name (derived from module filename).
module Any The loaded Python module.
enabled bool Whether the plugin is enabled.

PluginManager

Discovers, loads, and manages plugins.

PluginManager(client: Client)
Property Type Description
plugins dict[str, Plugin] Mapping of loaded plugins by name.
Method Description
load(path) -> list[Plugin] Load all plugins from a directory or single module file.
unload(name) -> bool Unload a plugin by name. Returns True if unloaded.
reload(name) -> Optional[Plugin] Reload a plugin. Returns the reloaded Plugin or None.

Plugin Structure

Plugins are Python modules that define a register(client) function:

# plugins/hello.py
def register(client):
    @client.on_message(filters.command("hello"))
    async def hello_handler(client, message):
        await message.reply("Hello from plugin!")
# main.py
from spluspy import Client

bot = Client("session")
bot.plugins.load("plugins")  # Load all plugins from the "plugins" directory
bot.run()

Sync Wrapper

The spluspy.sync module provides a synchronous wrapper around the async Client. Every async method is wrapped so it can be called without await.

from spluspy.sync import Client

bot = Client("session")

@bot.on_message()
def handler(client, message):
    # No async/await needed!
    message.reply("Hello!")

bot.run()

The sync Client has the same constructor and all the same methods as the async Client, but without async/await:

# Lifecycle
bot.start()
bot.stop()
bot.connect()
bot.disconnect()
bot.run()

# Messages (no await!)
bot.send_message(chat_id, "Hello!")
bot.edit_message(message, text="New text")
bot.delete_messages(msg1, msg2)

# All other methods work the same way
bot.get_me()
bot.get_messages(chat_id)
bot.ban_user(chat_id, user_id)

Enums

class ChatType(str, Enum):
    PRIVATE = "private"
    GROUP = "group"
    SUPERGROUP = "supergroup"
    CHANNEL = "channel"

class MessageMediaType(str, Enum):
    NONE = "none"
    PHOTO = "photo"
    VIDEO = "video"
    AUDIO = "audio"
    DOCUMENT = "document"
    VOICE = "voice"
    VIDEO_NOTE = "video_note"
    STICKER = "sticker"
    ANIMATION = "animation"
    CONTACT = "contact"
    LOCATION = "location"
    POLL = "poll"
    DICE = "dice"

class ParseMode(str, Enum):
    NONE = "none"
    MARKDOWN = "markdown"
    MARKDOWN_V2 = "markdown_v2"
    HTML = "html"

class UserStatus(str, Enum):
    ONLINE = "online"
    OFFLINE = "offline"
    LAST_SEEN = "last_seen"
    RECENTLY = "recently"
    LAST_WEEK = "last_week"
    LAST_MONTH = "last_month"
    LONG_AGO = "long_ago"
    EMPTY = "empty"

class ChatMemberStatus(IntEnum):
    CREATOR = 4
    ADMIN = 3
    MEMBER = 2
    RESTRICTED = 1
    LEFT = 0
    BANNED = -1

Media Types

All media types are dataclasses with slots=True, inheriting from Media.

Class Key Attributes
Media media_type, raw
Photo width, height, file_id, file_size, file_name
Video width, height, duration, file_id, file_size, file_name, thumb
Audio duration, performer, title, file_id, file_size, file_name
Document file_id, file_size, file_name, mime_type
Voice duration, file_id, file_size, mime_type
VideoNote duration, file_id, file_size, mime_type
Sticker sticker_id, emoji, set_name, width, height, is_animated
Animation file_name, width, height, duration, file_id, file_size
Contact phone_number, first_name, last_name, user_id
Location latitude, longitude
PollMedia / Poll question, options, is_anonymous, total_voter_count
Dice emoji, value

Object Types

Class Attributes
MessageEntity type, offset, length, url, user_id, language, custom_emoji_id
InlineKeyboardButton text, callback_data, url, switch_inline_query, switch_inline_query_current_chat
KeyboardButton text, request_contact, request_location
ReplyMarkup inline_keyboard, keyboard, is_one_time, is_resize, selective, placeholder
ForceReply selective, placeholder
MessageReplyHeader reply_to_msg_id, reply_to_peer_id, quote

Project Structure

spluspy/
├── __init__.py          # Public API
├── __version__.py       # Version info
├── cli.py               # CLI entry point
├── config.py            # Configuration
├── compat.py            # Compatibility layer
├── afk.py               # AFK auto-responder
├── admin.py             # Chat administration
├── mirror.py            # Message mirroring engine
├── client/              # Client and conversation API
│   ├── client.py        # Main Client class
│   ├── conversation.py  # Conversation API
│   ├── chat_mixin.py    # Chat management mixin
│   └── media_mixin.py   # Media operations mixin
├── models/              # Domain models (Message, User, Chat, Media, etc.)
│   ├── message.py       # Message dataclass
│   ├── user.py          # User dataclass
│   ├── chat.py          # Chat, Channel, Group dataclasses
│   ├── bot.py           # Button factory
│   ├── media.py         # Media type dataclasses
│   ├── objects.py       # MessageEntity, ReplyMarkup, etc.
│   └── enums.py         # ChatType, UserStatus, etc.
├── events/              # Event types and builders
├── filters/             # Composable message filters
├── errors/              # Custom exception hierarchy
├── session/             # Session backends (SQLite, Memory, String)
├── network/             # TCP connections and connection pool
├── storage/             # Key-value storage backends
├── plugins/             # Plugin loader
├── middleware/          # Middleware system
├── fsm/                 # Finite state machine
├── scheduler/           # Task scheduler
├── utils/               # Logger, cache, helpers
├── sync/                # Synchronous client wrapper
└── _engine/             # Low-level MTProto engine

CLI

# Run a bot
spluspy run bot.py

# Run with custom session name
spluspy run bot.py --session my_bot

# Get session info
spluspy session-info my_session.session

# Show version
spluspy version

# Validate a bot script for syntax errors
spluspy validate bot.py

Docker

FROM python:3.12-slim

WORKDIR /app

# Install system dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
    gcc \
    && rm -rf /var/lib/apt/lists/*

# Install spluspy
RUN pip install --no-cache-dir spluspy[all]

# Copy your bot script
COPY bot.py .

# Run the bot
CMD ["python", "bot.py"]

Build and run:

docker build -t my-spluspy-bot .
docker run -v ./sessions:/app/sessions my-spluspy-bot

فارسی

ویژگی‌ها

ویژگی توضیحات
بدون نیاز به API Key کلیدهای سروش پلاس به صورت داخلی
ناهمگام کامل ساخته شده با asyncio پایتون
پشتیبانی از حالت همگام استفاده بدون async/await از طریق spluspy.sync
پشتیبانی ربات و کاربر هر دو نوع حساب
هندلرهای رویدادمحور سیستم رویداد قدرتمند با دکوراتورها
سیستم فیلتر فیلترهای قابل ترکیب (&, |, ~)
دکمه‌های اینلاین و ریپلای کیبوردهای تعاملی
API مکالمه برای جریان‌های تعاملی ربات
FSM (ماشین حالت محدود) مدیریت وضعیت داخلی برای ربات‌ها
سیستم پلاگین بارگذاری پویای پلاگین‌ها
میان‌افزار (Middleware) پردازش قبل/بعد از به‌روزرسانی‌ها
زمان‌بند (Scheduler) زمان‌بندی داخلی وظایف
بک‌اندهای ذخیره‌سازی متعدد حافظه، SQLite، Redis، PostgreSQL
محدودیت نرخ (Rate Limiting) الگوریتم سطل توکن با مدیریت انتظار سیلاب
پاسخ خودکار AFK پاسخگوی هوشمند با محدودیت نرخ به ازای هر چت
مدیریت چت مسدود کردن، بی‌صدا کردن، سنجاق، پاکسازی — API مدیریتی
آینه‌سازی پیام کلون کردن پیام و رسانه به صورت بلادرنگ بین چت‌ها
عملیات دسته‌ای ارسال، حذف، فوروارد چندین پیام به صورت همزمان
انتقال فایل با پیشرفت آپلود/دانلود با ردیابی پیشرفت
لاگ حرفه‌ای لاگرهای ساختاریافته و فضای نام‌دار
نوع‌نویسی در همه جا ایمنی کامل نوع
معماری تمیز اصول SOLID، طراحی ماژولار

پیش‌نیازها

  • پایتون 3.10 به بالا
  • نیازی به کلید API خارجی نیست (کلیدهای سروش پلاس به صورت داخلی)

نصب

pip install spluspy

برای رمزگذاری سریع‌تر:

pip install spluspy[speed]

با بک‌اند Redis:

pip install spluspy[redis]

با بک‌اند PostgreSQL:

pip install spluspy[postgres]

همه وابستگی‌های اختیاری:

pip install spluspy[all]

شروع سریع

ساده‌ترین ربات

from spluspy import Client

bot = Client("my_session")

@bot.on_message()
async def handler(client, message):
    await message.reply("سلام!")

bot.run()

حساب کاربری

from spluspy import Client

client = Client("session_name")

@client.on_message()
async def handler(client, message):
    await message.reply("سلام دنیا!")

async def main():
    await client.start(phone="+98XXXXXXXXXX")
    await client.run_until_disconnected()

import asyncio
asyncio.run(main())

استفاده همگام (بدون Async/Await)

from spluspy.sync import Client

bot = Client("session")

@bot.on_message()
def handler(client, message):
    message.reply("سلام!")

bot.run()

رویدادها

دکوراتور رویداد
@bot.on_message() پیام جدید
@bot.on_edited_message() ویرایش پیام
@bot.on_callback_query() کلیک دکمه اینلاین
@bot.on_inline_query() کوئری اینلاین
@bot.on_chat_action() پیوستن/خروج/سنجاق
@bot.on_user_update() تغییر وضعیت
@bot.on_message_deleted() حذف پیام
@bot.on_message_read() رسید خواندن
@bot.on_error() مدیریت خطای سراسری

اولویت رویداد

from spluspy.events import HandlerPriority

@bot.on_message(priority=HandlerPriority.FIRST)
async def high_priority_handler(client, message):
    pass

@bot.on_message(priority=HandlerPriority.LOW)
async def low_priority_handler(client, message):
    pass

توقف انتشار

@bot.on_message()
async def stopper(client, message):
    if message.text == "/stop":
        message.stop_propagation()
        await message.reply("توقف!")

فیلترها

from spluspy import filters

@bot.on_message(filters.text)                    # فقط متن
@bot.on_message(filters.private)                 # چت‌های خصوصی
@bot.on_message(filters.group)                   # گروه‌ها
@bot.on_message(filters.command("start"))        # دستور /start
@bot.on_message(filters.regex(r"\d+"))           # تطابق با عبارت باقاعده
@bot.on_message(filters.user(123))               # کاربر خاص
@bot.on_message(filters.text & filters.private)  # ترکیبی
@bot.on_message(filters.photo | filters.video)   # عکس یا ویدیو

متدهای پیام

await message.reply("سلام")              # پاسخ
await message.edit("متن جدید")           # ویرایش
await message.delete()                    # حذف
await message.forward(chat_id)            # فوروارد
await message.copy(chat_id)               # کپی (بدون هدر فوروارد)
await message.pin()                       # سنجاق
await message.react("❤️")                 # واکنش
await message.mark_read()                 # علامت خواندن
await message.download()                  # دانلود رسانه
await message.reply_photo("photo.jpg")    # پاسخ با عکس
await message.reply_video("video.mp4")    # پاسخ با ویدیو
await message.reply_document("file.pdf")  # پاسخ با سند

دکمه‌ها

from spluspy import Button

# کیبورد اینلاین
keyboard = Button.build_inline([
    Button.inline("گزینه ۱", b"opt1"),
    Button.inline("گزینه ۲", b"opt2")
])
await bot.send_message(chat_id, "انتخاب کنید:", buttons=keyboard)

# کیبورد ریپلای
kb = Button.build_reply([
    Button.text("منو"),
    Button.text("تنظیمات")
])
await bot.send_message(chat_id, "انتخاب کنید:", buttons=kb)

# حذف کیبورد
await bot.send_message(chat_id, "تمام", buttons=Button.clear())

FSM (ماشین حالت محدود)

from spluspy.fsm import State, StateMachine
from spluspy.storage import MemoryStorage

storage = MemoryStorage()
fsm = StateMachine(storage)

class Form:
    name = State()
    age = State()

@bot.on_message(filters.command("register"))
async def start_register(client, message):
    ctx = fsm.context(message.sender_id)
    await ctx.set_state(Form.name)
    await message.reply("نام شما چیست؟")

@bot.on_message(filters.private)
async def process_form(client, message):
    ctx = fsm.context(message.sender_id)
    state = await ctx.get_state()

    if state == Form.name:
        await ctx.set_data(name=message.text)
        await ctx.set_state(Form.age)
        await message.reply("سن شما چقدر است؟")
    elif state == Form.age:
        data = await ctx.get_data()
        await ctx.reset()
        await message.reply(f"ثبت شد! نام: {data.get('name')}, سن: {message.text}")

بک‌اندهای ذخیره‌سازی

from spluspy.storage import MemoryStorage, SQLiteStorage, RedisStorage, PostgresStorage, get_storage

# حافظه (پیش‌فرض)
storage = MemoryStorage()

# SQLite
storage = SQLiteStorage("data.db")

# Redis
storage = RedisStorage(host="localhost", port=6379, db=0)

# PostgreSQL
storage = PostgresStorage(dsn="postgresql://user:pass@localhost/db")

# تابع کارخانه
storage = get_storage("redis", host="localhost")

سیستم پلاگین

# plugins/hello.py
def register(client):
    @client.on_message(filters.command("hello"))
    async def hello_handler(client, message):
        await message.reply("سلام از پلاگین!")
# main.py
from spluspy import Client

bot = Client("session")
bot.plugins.load("plugins")
bot.run()

میان‌افزار (Middleware)

from spluspy.middleware import Middleware

class LoggingMiddleware(Middleware):
    async def on_update(self, update, handler):
        print(f"به‌روزرسانی دریافت شد: {update}")
        result = await handler(update)
        print(f"هندلر تکمیل شد")
        return result

bot.middleware.add(LoggingMiddleware())

محدودیت نرخ

from spluspy.utils import RateLimiter

limiter = RateLimiter(max_calls=10, period=60)

@bot.on_message()
async def limited_handler(client, message):
    if not limiter.allow():
        await message.reply("محدودیت نرخ! دوباره تلاش کنید.")
        return
    await message.reply("باشه")

پاسخ خودکار AFK

from spluspy import Client, filters
from spluspy.afk import AfkManager

bot = Client("my_account")
afk = AfkManager(bot, message="الان AFK هستم. زود برمی‌گردم!")

@bot.on_message(filters.command("afk"))
async def set_afk(client, message):
    afk.set_afk(True, reason="ناهار")
    await message.reply("الان AFK هستم!")

@bot.on_message(filters.command("back"))
async def unset_afk(client, message):
    afk.set_afk(False)
    await message.reply(f"برگشتم! {afk.total_replies} پاسخ خودکار ارسال شد.")

مدیریت چت

from spluspy.admin import ChatAdmin

admin = ChatAdmin(bot)

await admin.ban_user(chat_id, user_id)
await admin.unban_user(chat_id, user_id)
await admin.mute_user(chat_id, user_id)
await admin.unmute_user(chat_id, user_id)
await admin.pin_message(chat_id, message)
await admin.unpin_message(chat_id, message)
await admin.unpin_all(chat_id)
await admin.purge_messages(chat_id, limit=100)

# عملیات دسته‌ای
await admin.bulk_action(chat_id, "ban", [user_id1, user_id2])

# لاگ مدیریتی
events = await admin.get_admin_log(chat_id, limit=50)

آینه‌سازی پیام

from spluspy.mirror import MessageMirror

mirror = MessageMirror(bot)

mirror.add_route(
    source=-1001234567890,
    targets=[-1009876543210, -1001112223334],
    strip_forward=True,
    strip_sender=False,
    add_prefix="[Mirror]"
)

await mirror.start()

زمان‌بند

from spluspy.scheduler.scheduler import MessageScheduler

scheduler = MessageScheduler(bot)

# ارسال پیام هر ساعت
scheduler.schedule_interval("hourly_greeting", chat_id, "سلام!", interval=3600)

# ارسال پیام با تاخیر
scheduler.schedule_once("reminder", chat_id, "فراموش نکن!", delay=300)

# لغو وظیفه زمان‌بندی شده
scheduler.cancel("hourly_greeting")

مدیریت خطا

from spluspy import filters
from spluspy.errors import FloodWait, Unauthorized, BadRequest

@bot.on_message()
async def safe_handler(client, message):
    try:
        await message.reply("سلام!")
    except FloodWait as e:
        await asyncio.sleep(e.seconds)
    except Unauthorized:
        await message.reply("غیرمجاز!")
    except BadRequest as e:
        print(f"درخواست نادرست: {e}")

عملیات دسته‌ای

# ارسال چندین پیام
messages = ["سلام ۱", "سلام ۲", "سلام ۳"]
results = await bot.batch_send(chat_id, messages)

# حذف چندین پیام
await bot.batch_delete(chat_id, [msg1, msg2, msg3])

# فوروارد چندین پیام
await bot.batch_forward(chat_id, [msg1, msg2])

انتقال فایل با پیشرفت

from spluspy.utils import ProgressTracker

# آپلود با پیشرفت
tracker = ProgressTracker(on_progress=lambda p: print(f"{p.percent}%"))
await bot.send_document(chat_id, "فایل_بزرگ.zip", progress=tracker)

# دانلود با پیشرفت
await message.download(progress=tracker)

رابط خط فرمان (CLI)

# اجرای ربات
spluspy run bot.py

# اجرا با نشست سفارشی
spluspy run bot.py --session my_bot

# اطلاعات نشست
spluspy session-info my_session.session

# نمایش نسخه
spluspy version

# اعتبارسنجی اسکریپت ربات
spluspy validate bot.py

Docker

FROM python:3.12-slim

WORKDIR /app

RUN apt-get update && apt-get install -y --no-install-recommends \
    gcc \
    && rm -rf /var/lib/apt/lists/*

RUN pip install --no-cache-dir spluspy[all]

COPY bot.py .

CMD ["python", "bot.py"]

ساخت و اجرا:

docker build -t my-spluspy-bot .
docker run -v ./sessions:/app/sessions my-spluspy-bot

ساختار پروژه

spluspy/
├── __init__.py          # API عمومی
├── __version__.py       # اطلاعات نسخه
├── cli.py               # نقطه ورود CLI
├── config.py            # پیکربندی
├── compat.py            # لایه سازگاری
├── afk.py               # پاسخگوی خودکار AFK
├── admin.py             # مدیریت چت
├── mirror.py            # موتور آینه‌سازی پیام
├── client/              # کلاینت و API مکالمه
│   ├── client.py        # کلاس اصلی Client
│   ├── conversation.py  # API مکالمه
│   ├── chat_mixin.py    # میکسین مدیریت چت
│   └── media_mixin.py   # میکسین عملیات رسانه
├── models/              # مدل‌های دامنه
│   ├── message.py       # داده‌کلاس Message
│   ├── user.py          # داده‌کلاس User
│   ├── chat.py          # داده‌کلاس Chat, Channel, Group
│   ├── bot.py           # کارخانه Button
│   ├── media.py         # انواع رسانه
│   ├── objects.py       # MessageEntity, ReplyMarkup, و غیره
│   └── enums.py         # ChatType, UserStatus, و غیره
├── events/              # انواع رویداد و بیلدرها
├── filters/             # فیلترهای قابل ترکیب پیام
├── errors/              # سلسله مراتب استثنای سفارشی
├── session/             # بک‌اندهای نشست
├── network/             # اتصالات TCP و استخر اتصال
├── storage/             # بک‌اندهای ذخیره‌سازی کلید-مقدار
├── plugins/             # بارگذار پلاگین
├── middleware/          # سیستم میان‌افزار
├── fsm/                 # ماشین حالت محدود
├── scheduler/           # زمان‌بند وظایف
├── utils/               # لاگر، کش، کمک‌کننده‌ها
├── sync/                # کلاینت همگام‌سازی شده
└── _engine/             # موتور MTProto سطح پایین

سلسله مراتب خطاها

SplusPyError
├── SoroushPlusAPIError
│   ├── RPCError
│   │   ├── FloodWait
│   │   ├── Unauthorized
│   │   ├── Forbidden
│   │   ├── BadRequest
│   │   │   ├── ChatNotFound
│   │   │   ├── UserNotFound
│   │   │   └── MessageNotFound
│   │   └── ...
│   ├── SessionExpiredError
│   └── SessionError
├── AuthError
├── ValidationError
├── TimeoutError
├── ConnectionError
├── PluginError
├── FSMError
├── StorageError
├── JoinChatError
│   ├── InvalidInviteLinkError
│   ├── InviteLinkExpiredError
│   ├── ChatFullError
│   ├── ChatDeactivatedError
│   └── MembershipRequiredError
└── FloodWaitError (محدودیت نرخ)

Contributing / مشارکت

English

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/your-feature)
  3. Set up the development environment:
    python -m venv venv
    source venv/bin/activate
    pip install -e ".[dev]"
    
  4. Make your changes
  5. Run linting and formatting:
    ruff check spluspy/
    black spluspy/
    
  6. Run type checking:
    mypy spluspy/
    
  7. Run tests: pytest
  8. Submit a pull request

فارسی

  1. مخزن را Fork کنید
  2. شاخه ویژگی بسازید (git checkout -b feature/your-feature)
  3. محیط توسعه را راه‌اندازی کنید:
    python -m venv venv
    source venv/bin/activate
    pip install -e ".[dev]"
    
  4. تغییرات خود را اعمال کنید
  5. لینتر و فرمت‌کننده را اجرا کنید:
    ruff check spluspy/
    black spluspy/
    
  6. بررسی نوع را اجرا کنید:
    mypy spluspy/
    
  7. تست‌ها را اجرا کنید: pytest
  8. درخواست Pull ارسال کنید

License / مجوز

MIT License — see LICENSE for details.

مجوز MIT — جزئیات را در LICENSE مشاهده کنید.


Disclaimer / سلب مسئولیت

English: SPlusPy is an unofficial third-party library. Use it responsibly and ensure your applications comply with Soroush Plus's Terms of Service.

فارسی: SPlusPy یک کتابخانه غیررسمی و شخص ثالث است. مسئولانه از آن استفاده کنید و مطمئن شوید برنامه‌های شما با شرایط استفاده سروش پلاس مطابقت دارند.


Made with ❤️ for the Soroush Plus community

ساخته شده با ❤️ برای جامعه سروش پلاس

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

spluspy-2.2.3-py3-none-any.whl (1.7 MB view details)

Uploaded Python 3

File details

Details for the file spluspy-2.2.3-py3-none-any.whl.

File metadata

  • Download URL: spluspy-2.2.3-py3-none-any.whl
  • Upload date:
  • Size: 1.7 MB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.4

File hashes

Hashes for spluspy-2.2.3-py3-none-any.whl
Algorithm Hash digest
SHA256 0492bf155e8213bdeeedbe8f07714b5bb7aa33f82184d4213da3d272a7bd7945
MD5 61dffa7de679d6e7e06f0360540a6614
BLAKE2b-256 2ab391932e604d0b1305b858f2c87d538594fbc0197c6069b4aaf953f27f0cfe

See more details on using hashes here.

Release history Release notifications | RSS feed

2.2.10

2 files

2.2.9

2 files

2.2.8

2 files

2.2.7

2 files

2.2.6

2 files

2.2.5

2 files

2.2.4

2 files

This release

2.2.3 This release

1 file

2.2.2

2 files

2.2.1

2 files

2.2.0

2 files

2.1.13

2 files

2.1.12

2 files

2.1.11

2 files

2.1.10

2 files

2.1.9

2 files

2.1.8

2 files

2.1.7

2 files

2.1.6

2 files

2.1.5

2 files

2.1.4

2 files

2.1.3

2 files

2.1.2

2 files

2.1.1

2 files

2.1.0

2 files

2.0.9

2 files

2.0.8

2 files

2.0.7

2 files

2.0.6

2 files

2.0.5

2 files

2.0.4

2 files

2.0.3

2 files

2.0.2

2 files

2.0.1

2 files

2.0.0

2 files

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