spluspy 2.2.0__tar.gz → 2.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- spluspy-2.2.2/PKG-INFO +3290 -0
- spluspy-2.2.2/README.md +3232 -0
- spluspy-2.2.2/docs/index.html +1392 -0
- spluspy-2.2.2/docs/logo.svg +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/pyproject.toml +2 -2
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/__init__.py +2 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/__version__.py +1 -1
- spluspy-2.2.2/spluspy/_engine/crypto/aesctr.py +70 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/crypto/factorization.py +4 -4
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/crypto/libssl.py +11 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/crypto/rsa.py +8 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/extensions/binaryreader.py +15 -3
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/network/connection/connection.py +24 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/network/connection/tcpfull.py +2 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/network/connection/tcpmtproxy.py +2 -1
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/network/connection/tcpobfuscated.py +2 -1
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/tlobject.py +3 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/version.py +1 -1
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/admin.py +3 -2
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/client/chat_mixin.py +290 -1
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/client/client.py +548 -30
- spluspy-2.2.2/spluspy/client/media_mixin.py +1021 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/config.py +2 -2
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/filters/filters.py +20 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/models/__init__.py +4 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/models/enums.py +2 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/models/media.py +32 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/models/message.py +188 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/network/transport.py +12 -0
- spluspy-2.2.2/spluspy/py.typed +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/session/sqlite.py +6 -7
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/storage/entity_cache.py +55 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/storage/sqlite.py +5 -4
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/utils/file_transfer.py +92 -2
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/utils/logger.py +117 -1
- spluspy-2.2.0/PKG-INFO +0 -1406
- spluspy-2.2.0/README.md +0 -1349
- spluspy-2.2.0/spluspy/_engine/crypto/aesctr.py +0 -42
- spluspy-2.2.0/spluspy/client/media_mixin.py +0 -531
- {spluspy-2.2.0 → spluspy-2.2.2}/.gitignore +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/CHANGELOG.md +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/CONTRIBUTING.md +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/Dockerfile +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/LICENSE +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/MANIFEST.in +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/README.rst +0 -0
- /spluspy-2.2.0/spluspy/py.typed → /spluspy-2.2.2/docs/.nojekyll +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/examples/01_hello_bot.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/examples/02_echo_bot.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/examples/03_command_bot.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/examples/04_private_only.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/examples/05_group_only.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/examples/06_regex_bot.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/examples/07_user_filter.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/examples/08_combined_filters.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/examples/09_inline_keyboard.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/examples/10_reply_keyboard.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/examples/11_message_actions.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/examples/12_media_bot.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/examples/13_fsm_bot.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/examples/14_plugin_bot.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/examples/15_middleware_bot.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/examples/16_scheduler_bot.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/examples/17_conversation.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/examples/18_chat_management.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/examples/19_event_types.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/examples/20_sync_bot.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/examples/21_album_handler.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/examples/22_download_bot.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/examples/23_url_button.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/examples/24_string_session.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/examples/25_error_handling.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/examples/25_working_bot.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/examples/26_message_entities.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/examples/27_search_messages.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/examples/28_poll_bot.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/examples/29_dice_bot.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/examples/30_cache_bot.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/examples/31_reaction_bot.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/examples/32_mark_read_bot.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/examples/33_context_manager.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/examples/34_user_info.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/examples/35_chat_info.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/examples/36_forward_copy.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/examples/37_admin_bot.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/examples/38_media_filters.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/examples/39_status_bot.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/examples/40_delete_detection.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/examples/41_read_detection.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/examples/42_raw_events.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/examples/43_poll_handler.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/examples/44_reaction_handler.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/examples/45_typing_handler.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/examples/46_get_messages.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/examples/47_iter_messages.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/examples/48_get_members.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/examples/49_resolve_username.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/examples/50_invoke.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/setup.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/__init__.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/_updates/__init__.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/_updates/entitycache.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/_updates/messagebox.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/_updates/session.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/client/__init__.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/client/account.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/client/auth.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/client/bots.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/client/buttons.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/client/chats.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/client/dialogs.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/client/downloads.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/client/messageparse.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/client/messages.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/client/soroushclient.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/client/telegrambaseclient.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/client/updates.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/client/uploads.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/client/users.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/crypto/__init__.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/crypto/aes.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/crypto/authkey.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/crypto/cdndecrypter.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/custom.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/errors/__init__.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/errors/common.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/errors/rpcbaseerrors.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/errors/rpcerrorlist.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/events/__init__.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/events/album.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/events/callbackquery.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/events/chataction.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/events/common.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/events/inlinequery.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/events/messagedeleted.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/events/messageedited.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/events/messageread.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/events/newmessage.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/events/raw.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/events/userupdate.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/extensions/__init__.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/extensions/html.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/extensions/markdown.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/extensions/messagepacker.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/functions.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/helpers.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/hints.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/network/__init__.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/network/authenticator.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/network/connection/__init__.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/network/connection/http.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/network/connection/tcpabridged.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/network/connection/tcpintermediate.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/network/connection/websocket.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/network/mtprotoplainsender.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/network/mtprotosender.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/network/mtprotostate.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/network/requeststate.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/password.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/requestiter.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/sessions/__init__.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/sessions/abstract.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/sessions/memory.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/sessions/sqlite.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/sessions/string.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/sync.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/__init__.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/alltlobjects.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/core/__init__.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/core/gzippacked.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/core/messagecontainer.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/core/rpcresult.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/core/tlmessage.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/custom/__init__.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/custom/adminlogevent.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/custom/button.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/custom/chatgetter.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/custom/conversation.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/custom/dialog.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/custom/draft.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/custom/file.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/custom/forward.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/custom/inlinebuilder.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/custom/inlineresult.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/custom/inlineresults.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/custom/inputsizedfile.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/custom/message.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/custom/messagebutton.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/custom/participantpermissions.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/custom/qrlogin.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/custom/sendergetter.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/custom/types.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/functions/__init__.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/functions/account.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/functions/auth.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/functions/bots.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/functions/channels.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/functions/chatlists.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/functions/conference.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/functions/contacts.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/functions/folders.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/functions/help.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/functions/langpack.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/functions/messages.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/functions/payments.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/functions/phone.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/functions/photos.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/functions/premium.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/functions/stats.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/functions/stories.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/functions/thirdParty.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/functions/updates.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/functions/upload.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/functions/users.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/patched/__init__.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/types/__init__.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/types/account.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/types/auth.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/types/bots.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/types/channels.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/types/chatlists.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/types/conference.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/types/contacts.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/types/help.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/types/messages.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/types/payments.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/types/phone.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/types/photos.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/types/premium.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/types/stats.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/types/stickers.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/types/storage.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/types/stories.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/types/thirdParty.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/types/update.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/types/updates.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/types/upload.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/tl/types/users.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/types.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/_engine/utils.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/afk.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/cli.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/client/__init__.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/client/conversation.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/compat.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/errors/__init__.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/errors/exceptions.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/events/__init__.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/events/album.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/events/base.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/events/callback.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/events/chat_action.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/events/edited.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/events/inline.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/events/message.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/events/message_deleted.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/events/message_read.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/events/user_update.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/filters/__init__.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/fsm/__init__.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/fsm/state.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/middleware/__init__.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/middleware/base.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/middleware/rate_limit.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/mirror.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/models/bot.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/models/chat.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/models/objects.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/models/user.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/network/__init__.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/network/client.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/network/connection.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/network/http_client.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/network/mtproto.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/network/protocols.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/network/spluspy_adapter.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/plugins/__init__.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/plugins/loader.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/scheduler/__init__.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/scheduler/scheduler.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/session/__init__.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/session/base.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/session/memory.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/session/string.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/storage/__init__.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/storage/base.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/storage/memory.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/storage/postgres.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/storage/redis.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/sync/__init__.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/sync/client.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/utils/__init__.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/utils/batch.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/utils/cache.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/utils/chat_lock.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/utils/helpers.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/utils/rate_limiter.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/utils/resolver.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/spluspy/utils/version_check.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/tests/__init__.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/tests/test_client.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/tests/test_filters.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/tests/test_fsm.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/tests/test_fsm_enhanced.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/tests/test_middleware.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/tests/test_rate_limiter.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/tests/test_resolver.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/tests/test_scheduler.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/tests/test_session.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/tests/test_storage.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/tests/test_types.py +0 -0
- {spluspy-2.2.0 → spluspy-2.2.2}/tests/test_utils.py +0 -0
spluspy-2.2.2/PKG-INFO
ADDED
|
@@ -0,0 +1,3290 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: spluspy
|
|
3
|
+
Version: 2.2.2
|
|
4
|
+
Summary: A modern async Python library for interacting with Soroush Plus
|
|
5
|
+
Project-URL: Homepage, https://github.com/Itskillmaster/spluspy
|
|
6
|
+
Project-URL: Documentation, https://github.com/Itskillmaster/spluspy#readme
|
|
7
|
+
Project-URL: Repository, https://github.com/Itskillmaster/spluspy
|
|
8
|
+
Project-URL: Issues, https://github.com/Itskillmaster/spluspy/issues
|
|
9
|
+
Author: Ali Mirshekari
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: async,bot,mtproto,soroush,splus,userbot
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Communications :: Chat
|
|
23
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
24
|
+
Classifier: Typing :: Typed
|
|
25
|
+
Requires-Python: >=3.10
|
|
26
|
+
Requires-Dist: aiohttp>=3.9.0
|
|
27
|
+
Requires-Dist: aiosqlite>=0.19.0
|
|
28
|
+
Requires-Dist: pyaes>=1.6
|
|
29
|
+
Requires-Dist: rsa>=4.7
|
|
30
|
+
Provides-Extra: all
|
|
31
|
+
Requires-Dist: asyncpg>=0.27.0; extra == 'all'
|
|
32
|
+
Requires-Dist: black>=23.0; extra == 'all'
|
|
33
|
+
Requires-Dist: cryptg; extra == 'all'
|
|
34
|
+
Requires-Dist: mypy>=1.0; extra == 'all'
|
|
35
|
+
Requires-Dist: orjson>=3.9.0; extra == 'all'
|
|
36
|
+
Requires-Dist: pytest-asyncio>=0.21; extra == 'all'
|
|
37
|
+
Requires-Dist: pytest-cov>=4.0; extra == 'all'
|
|
38
|
+
Requires-Dist: pytest>=7.0; extra == 'all'
|
|
39
|
+
Requires-Dist: redis>=4.0; extra == 'all'
|
|
40
|
+
Requires-Dist: ruff>=0.1.0; extra == 'all'
|
|
41
|
+
Requires-Dist: uvloop>=0.19.0; (sys_platform != 'win32') and extra == 'all'
|
|
42
|
+
Provides-Extra: dev
|
|
43
|
+
Requires-Dist: black>=23.0; extra == 'dev'
|
|
44
|
+
Requires-Dist: mypy>=1.0; extra == 'dev'
|
|
45
|
+
Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
|
|
46
|
+
Requires-Dist: pytest-cov>=4.0; extra == 'dev'
|
|
47
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
48
|
+
Requires-Dist: ruff>=0.1.0; extra == 'dev'
|
|
49
|
+
Provides-Extra: postgres
|
|
50
|
+
Requires-Dist: asyncpg>=0.27.0; extra == 'postgres'
|
|
51
|
+
Provides-Extra: redis
|
|
52
|
+
Requires-Dist: redis>=4.0; extra == 'redis'
|
|
53
|
+
Provides-Extra: speed
|
|
54
|
+
Requires-Dist: cryptg; extra == 'speed'
|
|
55
|
+
Requires-Dist: orjson>=3.9.0; extra == 'speed'
|
|
56
|
+
Requires-Dist: uvloop>=0.19.0; (sys_platform != 'win32') and extra == 'speed'
|
|
57
|
+
Description-Content-Type: text/markdown
|
|
58
|
+
|
|
59
|
+
<div align="center">
|
|
60
|
+
|
|
61
|
+
# SplusPy
|
|
62
|
+
|
|
63
|
+
**A modern async Python library for Soroush Plus**
|
|
64
|
+
|
|
65
|
+
[](https://github.com/Itskillmaster/spluspy/blob/main/LICENSE)
|
|
66
|
+
[](https://pypi.org/project/spluspy/)
|
|
67
|
+
[](https://pypi.org/project/spluspy/)
|
|
68
|
+
[](https://pypi.org/project/spluspy/)
|
|
69
|
+
[](https://github.com/psf/black)
|
|
70
|
+
[](https://mypy-lang.org/)
|
|
71
|
+
|
|
72
|
+
[English](#-features) | [فارسی](#-ویژگیها)
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
**SplusPy** is a modern, asynchronous Python library for interacting with [Soroush Plus](https://web.splus.ir) — both as a user account and a bot account.
|
|
77
|
+
|
|
78
|
+
Built from the ground up with clean architecture, it's designed to feel like [Telethon](https://github.com/LonamiWebs/Telethon) or [Pyrogram](https://github.com/pyrogram/pyrogram), but specifically for the Soroush Plus ecosystem.
|
|
79
|
+
|
|
80
|
+
**SplusPy** یک کتابخانه پایتون مدرن و ناهمگام (Asynchronous) برای تعامل با [سروش پلاس](https://web.splus.ir) است — هم به عنوان حساب کاربری و هم به عنوان حساب ربات.
|
|
81
|
+
|
|
82
|
+
این کتابخانه از صفر با معماری تمیز طراحی شده و الهام گرفته از [Telethon](https://github.com/LonamiWebs/Telethon) و [Pyrogram](https://github.com/pyrogram/pyrogram) است، اما مخصوص اکوسیستم سروش پلاس میباشد.
|
|
83
|
+
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## English
|
|
89
|
+
|
|
90
|
+
### Features
|
|
91
|
+
|
|
92
|
+
| Feature | Description |
|
|
93
|
+
|---------|-------------|
|
|
94
|
+
| **No API Key Required** | Built-in Soroush Plus credentials |
|
|
95
|
+
| **Fully Asynchronous** | Built with Python's `asyncio` |
|
|
96
|
+
| **Sync Support** | Use without `async/await` via `spluspy.sync` |
|
|
97
|
+
| **Bot & User Support** | Both account types |
|
|
98
|
+
| **Event-Driven Handlers** | Powerful event system with decorators |
|
|
99
|
+
| **Filter System** | Composable filters (`&`, `\|`, `~`) |
|
|
100
|
+
| **Inline & Reply Buttons** | Interactive keyboards |
|
|
101
|
+
| **Conversation API** | For interactive bot flows |
|
|
102
|
+
| **FSM (Finite State Machine)** | Built-in state management for bots |
|
|
103
|
+
| **Plugin System** | Dynamic plugin loading |
|
|
104
|
+
| **Middleware** | Pre/post processing of updates |
|
|
105
|
+
| **Scheduler** | Built-in task scheduler |
|
|
106
|
+
| **Multiple Storage Backends** | Memory, SQLite, Redis, PostgreSQL |
|
|
107
|
+
| **Rate Limiting** | Token bucket algorithm with flood wait handling |
|
|
108
|
+
| **AFK Auto-Reply** | Smart auto-responder with per-chat rate limiting |
|
|
109
|
+
| **Chat Administration** | Ban, mute, pin, purge — high-level admin API |
|
|
110
|
+
| **Message Mirroring** | Real-time message cloning between chats |
|
|
111
|
+
| **Batch Operations** | Send, delete, forward multiple messages at once |
|
|
112
|
+
| **File Transfer with Progress** | Upload/download with progress tracking |
|
|
113
|
+
| **Professional Logging** | Structured, namespaced loggers |
|
|
114
|
+
| **Type Hints Everywhere** | Full type safety |
|
|
115
|
+
| **Clean Architecture** | SOLID principles, modular design |
|
|
116
|
+
|
|
117
|
+
### Requirements
|
|
118
|
+
|
|
119
|
+
- Python 3.10+
|
|
120
|
+
- No external API keys needed (built-in Soroush Plus credentials)
|
|
121
|
+
|
|
122
|
+
### Installation
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
pip install spluspy
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
For faster encryption:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
pip install spluspy[speed]
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
With Redis backend:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
pip install spluspy[redis]
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
With PostgreSQL backend:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
pip install spluspy[postgres]
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
All optional dependencies:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
pip install spluspy[all]
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Quick Start
|
|
153
|
+
|
|
154
|
+
#### Simplest Bot
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
from spluspy import Client
|
|
158
|
+
|
|
159
|
+
bot = Client("my_session")
|
|
160
|
+
|
|
161
|
+
@bot.on_message()
|
|
162
|
+
async def handler(client, message):
|
|
163
|
+
await message.reply("Hello!")
|
|
164
|
+
|
|
165
|
+
bot.run()
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
#### User Account
|
|
169
|
+
|
|
170
|
+
```python
|
|
171
|
+
from spluspy import Client
|
|
172
|
+
|
|
173
|
+
client = Client("session_name")
|
|
174
|
+
|
|
175
|
+
@client.on_message()
|
|
176
|
+
async def handler(client, message):
|
|
177
|
+
await message.reply("Hey there!")
|
|
178
|
+
|
|
179
|
+
async def main():
|
|
180
|
+
await client.start(phone="+98XXXXXXXXXX")
|
|
181
|
+
await client.run_until_disconnected()
|
|
182
|
+
|
|
183
|
+
import asyncio
|
|
184
|
+
asyncio.run(main())
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
#### Sync Usage (No Async/Await)
|
|
188
|
+
|
|
189
|
+
```python
|
|
190
|
+
from spluspy.sync import Client
|
|
191
|
+
|
|
192
|
+
bot = Client("session")
|
|
193
|
+
|
|
194
|
+
@bot.on_message()
|
|
195
|
+
def handler(client, message):
|
|
196
|
+
message.reply("Hello!")
|
|
197
|
+
|
|
198
|
+
bot.run()
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## Complete API Reference
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
### Client
|
|
208
|
+
|
|
209
|
+
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`).
|
|
210
|
+
|
|
211
|
+
#### Constructor
|
|
212
|
+
|
|
213
|
+
```python
|
|
214
|
+
Client(
|
|
215
|
+
session: Union[str, Session] = "spluspy",
|
|
216
|
+
session_name: Optional[str] = None,
|
|
217
|
+
*,
|
|
218
|
+
session_string: Optional[str] = None,
|
|
219
|
+
api_id: Optional[int] = None,
|
|
220
|
+
api_hash: Optional[str] = None,
|
|
221
|
+
bot_token: Optional[str] = None,
|
|
222
|
+
phone: Optional[str] = None,
|
|
223
|
+
proxy: Optional[dict[str, Any]] = None,
|
|
224
|
+
flood_sleep_threshold: int = 60,
|
|
225
|
+
max_retries: Optional[int] = None,
|
|
226
|
+
request_timeout: float = 30.0,
|
|
227
|
+
log_level: int = logging.CRITICAL,
|
|
228
|
+
log_file: Optional[str] = None,
|
|
229
|
+
log_dir: str = "logs",
|
|
230
|
+
errors_only: bool = False,
|
|
231
|
+
) -> None
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
| Parameter | Type | Default | Description |
|
|
235
|
+
|-----------|------|---------|-------------|
|
|
236
|
+
| `session` | `str \| Session` | `"spluspy"` | Session name or a `Session` object. Used to persist authentication data. |
|
|
237
|
+
| `session_name` | `str \| None` | `None` | Alternative name for the session file. Overrides `session` if provided. |
|
|
238
|
+
| `session_string` | `str \| None` | `None` | A portable base64 session string for deploying without files. |
|
|
239
|
+
| `api_id` | `int \| None` | `None` | Custom API ID. If `None`, uses built-in Soroush Plus credentials. |
|
|
240
|
+
| `api_hash` | `str \| None` | `None` | Custom API hash. If `None`, uses built-in Soroush Plus credentials. |
|
|
241
|
+
| `bot_token` | `str \| None` | `None` | Bot token for bot mode. If `None`, runs in user mode. |
|
|
242
|
+
| `phone` | `str \| None` | `None` | Phone number for user mode authentication. |
|
|
243
|
+
| `proxy` | `dict \| None` | `None` | Proxy configuration (e.g. `{"proxy_type": "socks5", "addr": "127.0.0.1", "port": 1080}`). |
|
|
244
|
+
| `flood_sleep_threshold` | `int` | `60` | Seconds to auto-sleep on `FloodWait` errors before raising. |
|
|
245
|
+
| `max_retries` | `int \| None` | `None` | Maximum retry attempts for failed requests. `None` = infinite. |
|
|
246
|
+
| `request_timeout` | `float` | `30.0` | Timeout in seconds for API requests. |
|
|
247
|
+
| `log_level` | `int` | `CRITICAL` | Logging level (e.g. `logging.INFO`, `logging.DEBUG`). |
|
|
248
|
+
| `log_file` | `str \| None` | `None` | Path to log file. `None` = console only. |
|
|
249
|
+
| `log_dir` | `str` | `"logs"` | Directory for log files. |
|
|
250
|
+
| `errors_only` | `bool` | `False` | If `True`, only logs errors (suppresses info/debug). |
|
|
251
|
+
|
|
252
|
+
#### Properties
|
|
253
|
+
|
|
254
|
+
| Property | Type | Description |
|
|
255
|
+
|----------|------|-------------|
|
|
256
|
+
| `session_name` | `str` | Returns the session name. |
|
|
257
|
+
| `is_connected` | `bool` | Whether the client is currently connected to the server. |
|
|
258
|
+
| `middleware` | `MiddlewareManager` | Access the middleware manager to add/remove middleware. |
|
|
259
|
+
| `plugins` | `PluginManager` | Access the plugin manager to load/unload plugins. |
|
|
260
|
+
| `scheduler` | `Scheduler` | Access the built-in task scheduler. |
|
|
261
|
+
|
|
262
|
+
#### Event Registration Methods
|
|
263
|
+
|
|
264
|
+
##### `on(event, **kwargs) -> Callable`
|
|
265
|
+
|
|
266
|
+
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).
|
|
267
|
+
|
|
268
|
+
```python
|
|
269
|
+
@bot.on(NewMessage(), priority=HandlerPriority.HIGH)
|
|
270
|
+
async def handler(client, event):
|
|
271
|
+
pass
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
##### `on_message(*message_filters, **kwargs) -> Callable`
|
|
275
|
+
|
|
276
|
+
Register a handler that fires on new messages. Multiple filter arguments are ANDed together. The handler receives `(client, message)`.
|
|
277
|
+
|
|
278
|
+
| Parameter | Type | Description |
|
|
279
|
+
|-----------|------|-------------|
|
|
280
|
+
| `*message_filters` | `Filter` | One or more filters. All must pass for the handler to fire. |
|
|
281
|
+
| `priority` | `int` | Execution priority. Default: `HandlerPriority.NORMAL` (50). |
|
|
282
|
+
|
|
283
|
+
```python
|
|
284
|
+
@bot.on_message(filters.text & filters.private)
|
|
285
|
+
async def handler(client, message):
|
|
286
|
+
await message.reply("Hello!")
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
##### `on_edited_message(*message_filters, **kwargs) -> Callable`
|
|
290
|
+
|
|
291
|
+
Register a handler for edited messages. Same filter/priority API as `on_message`.
|
|
292
|
+
|
|
293
|
+
##### `on_callback_query(**kwargs) -> Callable`
|
|
294
|
+
|
|
295
|
+
Register a handler for inline button callback queries. Handler receives `(client, callback_query)`.
|
|
296
|
+
|
|
297
|
+
> **Note:** Not supported on Soroush+ user sessions. Exists for API compatibility.
|
|
298
|
+
|
|
299
|
+
##### `on_inline_query(**kwargs) -> Callable`
|
|
300
|
+
|
|
301
|
+
Register a handler for inline queries. Handler receives `(client, inline_query)`.
|
|
302
|
+
|
|
303
|
+
> **Note:** Not supported on Soroush+ user sessions. Exists for API compatibility.
|
|
304
|
+
|
|
305
|
+
##### `on_chat_action(**kwargs) -> Callable`
|
|
306
|
+
|
|
307
|
+
Register a handler for chat actions (joins, leaves, pins, etc.). Handler receives `(client, action_event)`.
|
|
308
|
+
|
|
309
|
+
##### `on_user_update(**kwargs) -> Callable`
|
|
310
|
+
|
|
311
|
+
Register a handler for user status changes (online/offline/typing). Handler receives `(client, user_update)`.
|
|
312
|
+
|
|
313
|
+
##### `on_message_deleted(**kwargs) -> Callable`
|
|
314
|
+
|
|
315
|
+
Register a handler for deleted messages. Handler receives `(client, deleted_event)`.
|
|
316
|
+
|
|
317
|
+
##### `on_message_read(**kwargs) -> Callable`
|
|
318
|
+
|
|
319
|
+
Register a handler for read receipts. Handler receives `(client, read_event)`.
|
|
320
|
+
|
|
321
|
+
##### `on_error(*exception_types, priority=HandlerPriority.LOW) -> Callable`
|
|
322
|
+
|
|
323
|
+
Register a global error handler. If no exception types are specified, catches all errors. The handler receives an `ErrorEvent` object.
|
|
324
|
+
|
|
325
|
+
```python
|
|
326
|
+
@bot.on_error(FloodWait, Unauthorized)
|
|
327
|
+
async def error_handler(client, event):
|
|
328
|
+
print(f"Error: {event.exception}")
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
#### Connection Lifecycle
|
|
332
|
+
|
|
333
|
+
##### `async connect() -> None`
|
|
334
|
+
|
|
335
|
+
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.
|
|
336
|
+
|
|
337
|
+
##### `async disconnect() -> None`
|
|
338
|
+
|
|
339
|
+
Disconnect from the server. Stops the scheduler, closes middleware and sessions.
|
|
340
|
+
|
|
341
|
+
##### `async start() -> None`
|
|
342
|
+
|
|
343
|
+
Start the client with interactive prompts for phone/password/code authentication. Automatically connects and handles the full auth flow.
|
|
344
|
+
|
|
345
|
+
##### `async stop() -> None`
|
|
346
|
+
|
|
347
|
+
Alias for `disconnect()`. Stops the client.
|
|
348
|
+
|
|
349
|
+
##### `async run_until_disconnected() -> None`
|
|
350
|
+
|
|
351
|
+
Run the event loop until the client is disconnected. Handles transient disconnections gracefully with automatic reconnection.
|
|
352
|
+
|
|
353
|
+
##### `run() -> None`
|
|
354
|
+
|
|
355
|
+
**Blocking** entry point. Connects, runs until disconnected, then cleans up. Tries `uvloop` for better performance. This is the simplest way to run a bot:
|
|
356
|
+
|
|
357
|
+
```python
|
|
358
|
+
bot = Client("session")
|
|
359
|
+
bot.run() # Blocks forever
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
#### Message Sending
|
|
363
|
+
|
|
364
|
+
##### `async send_message(chat_id, text=None, *, reply_to=None, parse_mode=None, link_preview=True, **kwargs) -> Message`
|
|
365
|
+
|
|
366
|
+
Send a text message to a chat.
|
|
367
|
+
|
|
368
|
+
| Parameter | Type | Default | Description |
|
|
369
|
+
|-----------|------|---------|-------------|
|
|
370
|
+
| `chat_id` | `int` | (required) | Target chat/user ID. |
|
|
371
|
+
| `text` | `str \| None` | `None` | Message text. |
|
|
372
|
+
| `reply_to` | `int \| None` | `None` | Message ID to reply to. |
|
|
373
|
+
| `parse_mode` | `str \| None` | `None` | `"html"`, `"markdown"`, or `None` for raw text. |
|
|
374
|
+
| `link_preview` | `bool` | `True` | Whether to show link previews. |
|
|
375
|
+
|
|
376
|
+
**Returns:** `Message` — the sent message object.
|
|
377
|
+
|
|
378
|
+
##### `async edit_message(message, *, text=None, parse_mode=None, delay=0, **kwargs) -> Message`
|
|
379
|
+
|
|
380
|
+
Edit a previously sent message's text. If `delay` is set, waits that many seconds before editing (non-blocking).
|
|
381
|
+
|
|
382
|
+
| Parameter | Type | Default | Description |
|
|
383
|
+
|-----------|------|---------|-------------|
|
|
384
|
+
| `message` | `Message \| int` | (required) | Message object or message ID. |
|
|
385
|
+
| `text` | `str \| None` | `None` | New text content. |
|
|
386
|
+
| `delay` | `int` | `0` | Seconds to wait before editing. |
|
|
387
|
+
|
|
388
|
+
##### `async delete_messages(*messages, **kwargs) -> bool`
|
|
389
|
+
|
|
390
|
+
Delete one or more messages. Accepts `Message` objects or integer IDs.
|
|
391
|
+
|
|
392
|
+
**Returns:** `True` on success.
|
|
393
|
+
|
|
394
|
+
##### `async delete_message(chat_id, message_id, *, delay=0) -> bool`
|
|
395
|
+
|
|
396
|
+
Delete a single message with an optional delay before deletion.
|
|
397
|
+
|
|
398
|
+
##### `async forward_messages(chat_id, *messages) -> Message`
|
|
399
|
+
|
|
400
|
+
Forward one or more messages to another chat. Returns a placeholder `Message`.
|
|
401
|
+
|
|
402
|
+
##### `async send_photo(chat_id, photo, *, caption=None, **kwargs) -> Message`
|
|
403
|
+
|
|
404
|
+
Send a photo. `photo` can be a file path (`str`), `bytes`, or a file-like object.
|
|
405
|
+
|
|
406
|
+
| Parameter | Type | Default | Description |
|
|
407
|
+
|-----------|------|---------|-------------|
|
|
408
|
+
| `chat_id` | `int` | (required) | Target chat/user ID. |
|
|
409
|
+
| `photo` | `str \| bytes \| IO` | (required) | Photo file path, bytes, or file-like object. |
|
|
410
|
+
| `caption` | `str \| None` | `None` | Caption text for the photo. |
|
|
411
|
+
|
|
412
|
+
##### `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`
|
|
413
|
+
|
|
414
|
+
Send a video. Duration, width, and height are auto-detected if left at `0`.
|
|
415
|
+
|
|
416
|
+
| Parameter | Type | Default | Description |
|
|
417
|
+
|-----------|------|---------|-------------|
|
|
418
|
+
| `video` | `str \| bytes \| IO` | (required) | Video file. |
|
|
419
|
+
| `caption` | `str \| None` | `None` | Video caption. |
|
|
420
|
+
| `duration` | `int` | `0` | Duration in seconds (auto-detected if 0). |
|
|
421
|
+
| `width` | `int` | `0` | Width in pixels (auto-detected if 0). |
|
|
422
|
+
| `height` | `int` | `0` | Height in pixels (auto-detected if 0). |
|
|
423
|
+
| `supports_streaming` | `bool` | `True` | Whether the video supports streaming. |
|
|
424
|
+
| `progress_callback` | `Callable \| None` | `None` | Callback for upload progress: `fn(current, total)`. |
|
|
425
|
+
|
|
426
|
+
##### `async send_voice(chat_id, voice, *, caption=None, duration=0, progress_callback=None, reply_to=None, **kwargs) -> Message`
|
|
427
|
+
|
|
428
|
+
Send a voice note (audio message).
|
|
429
|
+
|
|
430
|
+
##### `async send_document(chat_id, document, *, caption=None, file_name=None, force_document=True, progress_callback=None, reply_to=None, **kwargs) -> Message`
|
|
431
|
+
|
|
432
|
+
Send a document/file. `force_document=True` ensures the file is sent as a document, not auto-detected.
|
|
433
|
+
|
|
434
|
+
##### `async send_audio(chat_id, audio, *, caption=None, duration=0, performer=None, title=None, progress_callback=None, reply_to=None, **kwargs) -> Message`
|
|
435
|
+
|
|
436
|
+
Send an audio file with optional metadata (performer, title).
|
|
437
|
+
|
|
438
|
+
##### `async send_animation(chat_id, animation, *, caption=None, duration=0, width=0, height=0, thumb=None, progress_callback=None, reply_to=None, **kwargs) -> Message`
|
|
439
|
+
|
|
440
|
+
Send an animation (GIF).
|
|
441
|
+
|
|
442
|
+
##### `async send_location(chat_id, lat, lon, *, reply_to=None) -> Message`
|
|
443
|
+
|
|
444
|
+
Send a geographic location.
|
|
445
|
+
|
|
446
|
+
| Parameter | Type | Description |
|
|
447
|
+
|-----------|------|-------------|
|
|
448
|
+
| `lat` | `float` | Latitude (-90 to 90). |
|
|
449
|
+
| `lon` | `float` | Longitude (-180 to 180). |
|
|
450
|
+
|
|
451
|
+
##### `async send_contact(chat_id, phone, first_name, last_name="", *, vcard="", reply_to=None) -> Message`
|
|
452
|
+
|
|
453
|
+
Send a contact card.
|
|
454
|
+
|
|
455
|
+
##### `async send_poll(chat_id, question, options, *, correct_option=None, explanation=None, is_anonymous=True, allows_multiple_answers=False) -> Message`
|
|
456
|
+
|
|
457
|
+
Send a poll to a chat.
|
|
458
|
+
|
|
459
|
+
| Parameter | Type | Default | Description |
|
|
460
|
+
|-----------|------|---------|-------------|
|
|
461
|
+
| `question` | `str` | (required) | Poll question. |
|
|
462
|
+
| `options` | `list[str]` | (required) | List of answer options (2-10). |
|
|
463
|
+
| `correct_option` | `int \| None` | `None` | Index of the correct answer (for quizzes). |
|
|
464
|
+
| `explanation` | `str \| None` | `None` | Explanation shown after voting. |
|
|
465
|
+
| `is_anonymous` | `bool` | `True` | Whether the poll is anonymous. |
|
|
466
|
+
| `allows_multiple_answers` | `bool` | `False` | Whether users can select multiple options. |
|
|
467
|
+
|
|
468
|
+
##### `async send_dice(chat_id, emoji="🎲", *, reply_to=None) -> Message`
|
|
469
|
+
|
|
470
|
+
Send a dice animation. Supported emojis: `🎲` (dice), `🎯` (darts), `🏀` (basketball), `⚽` (football), `🎳` (bowling), `🎰` (slot machine).
|
|
471
|
+
|
|
472
|
+
##### `async send_reaction(chat_id, message, emoji) -> bool`
|
|
473
|
+
|
|
474
|
+
React to a message with an emoji.
|
|
475
|
+
|
|
476
|
+
**Returns:** `True` on success.
|
|
477
|
+
|
|
478
|
+
##### `async send_sticker(chat_id, sticker, *, emoji=None, sticker_set=None, progress_callback=None, reply_to=None, **kwargs) -> Message`
|
|
479
|
+
|
|
480
|
+
Send a sticker.
|
|
481
|
+
|
|
482
|
+
##### `async send_video_note(chat_id, video, *, caption=None, duration=0, width=0, height=0, progress_callback=None, reply_to=None, **kwargs) -> Message`
|
|
483
|
+
|
|
484
|
+
Send a round video note.
|
|
485
|
+
|
|
486
|
+
##### `async send_chat_action(chat_id, action="typing") -> bool`
|
|
487
|
+
|
|
488
|
+
Send a typing indicator or upload progress status.
|
|
489
|
+
|
|
490
|
+
Supported actions: `"typing"`, `"upload_photo"`, `"record_video"`, `"upload_video"`, `"record_audio"`, `"upload_audio"`, `"upload_document"`, `"find_location"`, `"record_voice"`, `"upload_voice"`, `"choose_sticker"`.
|
|
491
|
+
|
|
492
|
+
#### Chat Management
|
|
493
|
+
|
|
494
|
+
##### `async ban_user(chat_id, user_id) -> bool`
|
|
495
|
+
|
|
496
|
+
Ban a user from a chat. The user cannot rejoin unless unbanned.
|
|
497
|
+
|
|
498
|
+
##### `async unban_user(chat_id, user_id) -> bool`
|
|
499
|
+
|
|
500
|
+
Unban a previously banned user, allowing them to rejoin the chat.
|
|
501
|
+
|
|
502
|
+
##### `async mute_user(chat_id, user_id) -> bool`
|
|
503
|
+
|
|
504
|
+
Mute a user (restrict them from sending any messages in the chat).
|
|
505
|
+
|
|
506
|
+
##### `async unmute_user(chat_id, user_id) -> bool`
|
|
507
|
+
|
|
508
|
+
Unmute a user, restoring their ability to send messages.
|
|
509
|
+
|
|
510
|
+
##### `async safe_ban_user(chat_id, user_id) -> bool`
|
|
511
|
+
|
|
512
|
+
Ban a user, catching permission errors gracefully. Never raises — returns `False` on failure.
|
|
513
|
+
|
|
514
|
+
##### `async safe_send_message(chat_id, text=None, *, reply_to=None, parse_mode=None, link_preview=True, **kwargs) -> Message`
|
|
515
|
+
|
|
516
|
+
Send a message, catching permission errors gracefully. Never raises — returns a fallback `Message` on failure.
|
|
517
|
+
|
|
518
|
+
##### `async unblock_chat_member(chat_id, user_id) -> bool`
|
|
519
|
+
|
|
520
|
+
Remove a user from the account's block list.
|
|
521
|
+
|
|
522
|
+
##### `async get_chat_blocked_users(chat_id) -> list[dict]`
|
|
523
|
+
|
|
524
|
+
Fetch all blocked users in a chat. Returns a list of dicts with `user_id` and `date` keys.
|
|
525
|
+
|
|
526
|
+
##### `async join_chat(chat_id) -> Any`
|
|
527
|
+
|
|
528
|
+
Join a chat by ID, username, or invite link. Returns a `Chat` object.
|
|
529
|
+
|
|
530
|
+
**Raises typed exceptions:** `InvalidInviteLinkError`, `InviteLinkExpiredError`, `ChatFullError`, `ChatDeactivatedError`, `MembershipRequiredError`, `FloodWait`, `ChatNotFound`, `JoinChatError`.
|
|
531
|
+
|
|
532
|
+
##### `async leave_chat(chat_id) -> bool`
|
|
533
|
+
|
|
534
|
+
Leave a chat. Auto-detects whether it's a channel or basic group.
|
|
535
|
+
|
|
536
|
+
##### `async leave_group(chat_id) -> bool`
|
|
537
|
+
|
|
538
|
+
Leave a basic group specifically.
|
|
539
|
+
|
|
540
|
+
##### `async leave_channel(chat_id) -> bool`
|
|
541
|
+
|
|
542
|
+
Leave a channel or supergroup specifically.
|
|
543
|
+
|
|
544
|
+
##### `async get_all_groups() -> list[dict]`
|
|
545
|
+
|
|
546
|
+
Get all groups and channels the user is a member of. Returns list of dicts with `id`, `title`, `type` keys.
|
|
547
|
+
|
|
548
|
+
##### `async leave_all_groups(exclude_ids=None) -> dict`
|
|
549
|
+
|
|
550
|
+
Leave all basic groups. Returns a dict with `success`, `failed`, `skipped` counts and `details`.
|
|
551
|
+
|
|
552
|
+
##### `async leave_all_channels(exclude_ids=None) -> dict`
|
|
553
|
+
|
|
554
|
+
Leave all channels. Returns a dict with `success`, `failed`, `skipped` counts and `details`.
|
|
555
|
+
|
|
556
|
+
##### `async leave_all(exclude_ids=None) -> dict`
|
|
557
|
+
|
|
558
|
+
Leave all groups and channels at once. Returns summary dict.
|
|
559
|
+
|
|
560
|
+
##### `async delete_all_private(*, revoke=False, exclude_ids=None, delay=1.0, on_progress=None) -> dict`
|
|
561
|
+
|
|
562
|
+
Delete all private chat dialogs. Returns summary dict with counts and details.
|
|
563
|
+
|
|
564
|
+
##### `async pin_chat_message(chat_id, message_id, notify=True) -> bool`
|
|
565
|
+
|
|
566
|
+
Pin a message by ID. If `notify=True`, all members are notified.
|
|
567
|
+
|
|
568
|
+
##### `async unpin_chat_message(chat_id, message_id) -> bool`
|
|
569
|
+
|
|
570
|
+
Unpin a specific message by ID.
|
|
571
|
+
|
|
572
|
+
##### `async pin_message(chat_id, message, *, notify=False) -> bool`
|
|
573
|
+
|
|
574
|
+
Pin a message. Accepts either a `Message` object or an integer message ID.
|
|
575
|
+
|
|
576
|
+
##### `async unpin_message(chat_id, message) -> bool`
|
|
577
|
+
|
|
578
|
+
Unpin a message. Accepts either a `Message` object or an integer message ID.
|
|
579
|
+
|
|
580
|
+
##### `async unpin_all_messages(chat_id) -> bool`
|
|
581
|
+
|
|
582
|
+
Unpin all pinned messages in a chat at once.
|
|
583
|
+
|
|
584
|
+
##### `async mark_read(chat_id, message=None) -> bool`
|
|
585
|
+
|
|
586
|
+
Mark a specific message (or all messages in a chat) as read.
|
|
587
|
+
|
|
588
|
+
#### Info Retrieval
|
|
589
|
+
|
|
590
|
+
##### `async get_me() -> Any`
|
|
591
|
+
|
|
592
|
+
Get the currently authenticated user. Returns a `User` object or `None`.
|
|
593
|
+
|
|
594
|
+
##### `async get_chat(chat_id) -> Any`
|
|
595
|
+
|
|
596
|
+
Get a chat entity by ID or username.
|
|
597
|
+
|
|
598
|
+
##### `async get_full_chat(chat_id) -> Optional[Chat]`
|
|
599
|
+
|
|
600
|
+
Get full chat info including title and `member_count`. Supports numeric IDs, usernames, and invite link hashes.
|
|
601
|
+
|
|
602
|
+
##### `async is_chat_member(chat_id) -> bool`
|
|
603
|
+
|
|
604
|
+
Check if the current user is a member of the specified chat.
|
|
605
|
+
|
|
606
|
+
##### `async get_user(user_id) -> Optional[User]`
|
|
607
|
+
|
|
608
|
+
Get full user info by ID or `@username`. Returns a `User` with bio, status, and all other fields.
|
|
609
|
+
|
|
610
|
+
##### `async get_bio(user_id) -> Optional[str]`
|
|
611
|
+
|
|
612
|
+
Get a user's biography/about text.
|
|
613
|
+
|
|
614
|
+
##### `async get_first_name(user_id) -> Optional[str]`
|
|
615
|
+
|
|
616
|
+
Get a user's first name.
|
|
617
|
+
|
|
618
|
+
##### `async get_last_name(user_id) -> Optional[str]`
|
|
619
|
+
|
|
620
|
+
Get a user's last name.
|
|
621
|
+
|
|
622
|
+
##### `async get_full_name(user_id) -> Optional[str]`
|
|
623
|
+
|
|
624
|
+
Get a user's full name (first + last name combined).
|
|
625
|
+
|
|
626
|
+
##### `async get_username(user_id) -> Optional[str]`
|
|
627
|
+
|
|
628
|
+
Get a user's username without the `@` prefix.
|
|
629
|
+
|
|
630
|
+
##### `async get_user_link(user_id) -> Optional[str]`
|
|
631
|
+
|
|
632
|
+
Get a `t.me/` profile link for the user.
|
|
633
|
+
|
|
634
|
+
##### `async get_description(chat_id) -> Optional[str]`
|
|
635
|
+
|
|
636
|
+
Get a group/channel description (about text).
|
|
637
|
+
|
|
638
|
+
##### `async get_group_link(chat_id) -> Optional[str]`
|
|
639
|
+
|
|
640
|
+
Get the `t.me/` invite link for a group or channel.
|
|
641
|
+
|
|
642
|
+
##### `async get_group_id(username) -> Optional[int]`
|
|
643
|
+
|
|
644
|
+
Resolve a `@username` to a chat/channel integer ID.
|
|
645
|
+
|
|
646
|
+
##### `async get_chat_info(chat_id) -> Optional[dict]`
|
|
647
|
+
|
|
648
|
+
Get comprehensive chat info as a dictionary with keys: `id`, `title`, `username`, `link`, `description`, `member_count`, `type`.
|
|
649
|
+
|
|
650
|
+
##### `async get_user_info(user_id) -> Optional[dict]`
|
|
651
|
+
|
|
652
|
+
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`.
|
|
653
|
+
|
|
654
|
+
##### `async get_messages(chat_id, *, limit=100, **kwargs) -> list[Message]`
|
|
655
|
+
|
|
656
|
+
Get messages from a chat. Returns list of `Message` objects, newest first.
|
|
657
|
+
|
|
658
|
+
##### `async iter_messages(chat_id, *, limit=1000, offset_id=0, search=None, batch_size=100) -> AsyncGenerator[Message, None]`
|
|
659
|
+
|
|
660
|
+
Async generator yielding messages in memory-efficient batches. Ideal for processing large histories without loading everything into memory.
|
|
661
|
+
|
|
662
|
+
| Parameter | Type | Default | Description |
|
|
663
|
+
|-----------|------|---------|-------------|
|
|
664
|
+
| `chat_id` | `int \| str` | (required) | Chat to iterate messages from. |
|
|
665
|
+
| `limit` | `int` | `1000` | Maximum number of messages to yield. |
|
|
666
|
+
| `offset_id` | `int` | `0` | Start from this message ID (newer messages). |
|
|
667
|
+
| `search` | `str \| None` | `None` | Search query to filter messages. |
|
|
668
|
+
| `batch_size` | `int` | `100` | Number of messages to fetch per API call. |
|
|
669
|
+
|
|
670
|
+
##### `async search_messages(chat_id, query, *, limit=100) -> list[Message]`
|
|
671
|
+
|
|
672
|
+
Search for messages in a chat by text query.
|
|
673
|
+
|
|
674
|
+
##### `async get_history(chat_id, *, limit=100) -> list[Message]`
|
|
675
|
+
|
|
676
|
+
Alias for `get_messages()`.
|
|
677
|
+
|
|
678
|
+
##### `async get_members(chat_id, *, limit=100) -> list[ChatMember]`
|
|
679
|
+
|
|
680
|
+
Alias for `get_chat_members()`.
|
|
681
|
+
|
|
682
|
+
##### `async iter_chat_members(chat_id, *, limit=200, batch_size=100) -> AsyncGenerator[ChatMember, None]`
|
|
683
|
+
|
|
684
|
+
Async generator yielding chat members in batches.
|
|
685
|
+
|
|
686
|
+
##### `async iter_members(chat_id, *, limit=200, batch_size=100) -> AsyncGenerator[ChatMember, None]`
|
|
687
|
+
|
|
688
|
+
Alias for `iter_chat_members()`.
|
|
689
|
+
|
|
690
|
+
##### `async iter_dialogs(*, limit=500, batch_size=100) -> AsyncGenerator[Chat, None]`
|
|
691
|
+
|
|
692
|
+
Async generator yielding dialogs (chats) in batches.
|
|
693
|
+
|
|
694
|
+
##### `async get_contacts() -> list[User]`
|
|
695
|
+
|
|
696
|
+
Get the account's contact list as `User` objects.
|
|
697
|
+
|
|
698
|
+
##### `async resolve_username(username) -> int`
|
|
699
|
+
|
|
700
|
+
Resolve a username to a numeric ID. Returns `0` if not found.
|
|
701
|
+
|
|
702
|
+
##### `async resolve_peer(peer) -> Any`
|
|
703
|
+
|
|
704
|
+
Resolve a peer identifier to an MTProto `InputPeer` object. Supports integer IDs, `@username`, and phone numbers.
|
|
705
|
+
|
|
706
|
+
#### Profile Management
|
|
707
|
+
|
|
708
|
+
##### `async update_profile(first_name=None, last_name=None, about=None, bio=None) -> bool`
|
|
709
|
+
|
|
710
|
+
Update the current user's profile fields. Returns `True` on success.
|
|
711
|
+
|
|
712
|
+
##### `async set_profile_photo(photo) -> bool`
|
|
713
|
+
|
|
714
|
+
Upload and set a new profile photo. `photo` can be a file path or `bytes`.
|
|
715
|
+
|
|
716
|
+
##### `async delete_profile_photo(photo_id=None) -> bool`
|
|
717
|
+
|
|
718
|
+
Delete a profile photo. Pass `None` to delete the most recent one.
|
|
719
|
+
|
|
720
|
+
##### `async set_username(username) -> bool`
|
|
721
|
+
|
|
722
|
+
Change the current user's username. Pass an empty string `""` to remove it.
|
|
723
|
+
|
|
724
|
+
##### `async set_offline(offline=True) -> bool`
|
|
725
|
+
|
|
726
|
+
Set the user's online/offline appearance. `True` = appear offline.
|
|
727
|
+
|
|
728
|
+
#### Low-level
|
|
729
|
+
|
|
730
|
+
##### `async invoke(*args, **kwargs) -> Any`
|
|
731
|
+
|
|
732
|
+
Invoke a raw MTProto API method. This is a low-level escape hatch for API methods not covered by the high-level interface.
|
|
733
|
+
|
|
734
|
+
##### `async export_session_string() -> str`
|
|
735
|
+
|
|
736
|
+
Export the current session as a portable base64 string. Useful for deploying without session files.
|
|
737
|
+
|
|
738
|
+
#### Conversation
|
|
739
|
+
|
|
740
|
+
##### `conversation(chat_id, *, timeout=30.0, exclusive=True) -> Conversation`
|
|
741
|
+
|
|
742
|
+
Create an interactive conversation context manager for request/response style messaging. See [Conversation API](#conversation-api) below.
|
|
743
|
+
|
|
744
|
+
---
|
|
745
|
+
|
|
746
|
+
### Conversation API
|
|
747
|
+
|
|
748
|
+
A context-managed conversational flow for interactive bot dialogs. Used with `async with client.conversation(peer) as conv:`.
|
|
749
|
+
|
|
750
|
+
#### Constructor
|
|
751
|
+
|
|
752
|
+
```python
|
|
753
|
+
Conversation(
|
|
754
|
+
client: Client,
|
|
755
|
+
peer: Union[int, str],
|
|
756
|
+
timeout: Optional[float] = 30.0,
|
|
757
|
+
exclusive: bool = True,
|
|
758
|
+
)
|
|
759
|
+
```
|
|
760
|
+
|
|
761
|
+
| Parameter | Type | Default | Description |
|
|
762
|
+
|-----------|------|---------|-------------|
|
|
763
|
+
| `client` | `Client` | (required) | The SplusPy client instance. |
|
|
764
|
+
| `peer` | `int \| str` | (required) | Chat ID or username to converse with. |
|
|
765
|
+
| `timeout` | `float` | `30.0` | Default timeout in seconds for waiting responses. |
|
|
766
|
+
| `exclusive` | `bool` | `True` | If `True`, only captures messages from the target peer. |
|
|
767
|
+
|
|
768
|
+
#### Methods
|
|
769
|
+
|
|
770
|
+
##### `async send_message(text, **kwargs) -> Message`
|
|
771
|
+
|
|
772
|
+
Send a message in this conversation.
|
|
773
|
+
|
|
774
|
+
##### `async get_response(timeout=None) -> Message`
|
|
775
|
+
|
|
776
|
+
Wait for the next incoming message from the conversation peer. Raises `ConversationTimeoutError` if no message arrives within the timeout.
|
|
777
|
+
|
|
778
|
+
##### `async wait_response(timeout=None) -> Message`
|
|
779
|
+
|
|
780
|
+
Alias for `get_response()`.
|
|
781
|
+
|
|
782
|
+
##### `async send_and_wait(text, *, timeout=None, **kwargs) -> Message`
|
|
783
|
+
|
|
784
|
+
Send a message and wait for a reply in a single call. Convenience method combining `send_message` + `get_response`.
|
|
785
|
+
|
|
786
|
+
##### `empty() -> bool`
|
|
787
|
+
|
|
788
|
+
Returns `True` if the internal message queue is empty.
|
|
789
|
+
|
|
790
|
+
##### `clear() -> None`
|
|
791
|
+
|
|
792
|
+
Discard any unread messages in the internal queue.
|
|
793
|
+
|
|
794
|
+
#### Usage Example
|
|
795
|
+
|
|
796
|
+
```python
|
|
797
|
+
async with client.conversation(chat_id, timeout=30) as conv:
|
|
798
|
+
await conv.send_message("What is your name?")
|
|
799
|
+
response = await conv.get_response()
|
|
800
|
+
await conv.send_and_wait(f"Nice to meet you, {response.text}!")
|
|
801
|
+
```
|
|
802
|
+
|
|
803
|
+
---
|
|
804
|
+
|
|
805
|
+
### Client Chat Management Mixin Methods
|
|
806
|
+
|
|
807
|
+
Additional chat management methods available on the `Client` instance.
|
|
808
|
+
|
|
809
|
+
##### `async get_chat_members(chat_id, limit=200) -> list[Any]`
|
|
810
|
+
|
|
811
|
+
Get a list of members in a channel/megagroup using raw MTProto. Returns `User` objects.
|
|
812
|
+
|
|
813
|
+
##### `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`
|
|
814
|
+
|
|
815
|
+
Restrict a user in a megagroup/channel with fine-grained permission control.
|
|
816
|
+
|
|
817
|
+
| Parameter | Type | Default | Description |
|
|
818
|
+
|-----------|------|---------|-------------|
|
|
819
|
+
| `send_messages` | `bool` | `False` | Allow sending messages. |
|
|
820
|
+
| `send_media` | `bool` | `False` | Allow sending media. |
|
|
821
|
+
| `send_stickers` | `bool` | `False` | Allow sending stickers. |
|
|
822
|
+
| `send_gifs` | `bool` | `False` | Allow sending GIFs. |
|
|
823
|
+
| `send_inline` | `bool` | `False` | Allow using inline bots. |
|
|
824
|
+
| `embed_links` | `bool` | `False` | Allow embedding links. |
|
|
825
|
+
| `send_polls` | `bool` | `False` | Allow sending polls. |
|
|
826
|
+
| `change_info` | `bool` | `False` | Allow changing chat info. |
|
|
827
|
+
| `invite_users` | `bool` | `False` | Allow inviting users. |
|
|
828
|
+
| `pin_messages` | `bool` | `False` | Allow pinning messages. |
|
|
829
|
+
| `manage_topics` | `bool` | `False` | Allow managing topics. |
|
|
830
|
+
| `until_date` | `int` | `0` | Restriction expiry timestamp. `0` = permanent. |
|
|
831
|
+
|
|
832
|
+
##### `async promote_member(chat_id, user_id, is_admin=True, privileges=None, *, rank="") -> bool`
|
|
833
|
+
|
|
834
|
+
Promote or demote a user in a megagroup/channel. `privileges` is a dict of permission booleans. `rank` sets the admin rank title.
|
|
835
|
+
|
|
836
|
+
##### `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`
|
|
837
|
+
|
|
838
|
+
Set the default chat permissions for all non-admin members. `True` = allowed, `False` = restricted, `None` = no change.
|
|
839
|
+
|
|
840
|
+
##### `async set_chat_title(chat_id, title) -> bool`
|
|
841
|
+
|
|
842
|
+
Change the title of a channel or supergroup.
|
|
843
|
+
|
|
844
|
+
##### `async set_chat_about(chat_id, about) -> bool`
|
|
845
|
+
|
|
846
|
+
Change the about/description text of a chat (max 70 characters).
|
|
847
|
+
|
|
848
|
+
##### `async set_chat_photo(chat_id, file_path) -> bool`
|
|
849
|
+
|
|
850
|
+
Upload and set a new photo for a channel or supergroup.
|
|
851
|
+
|
|
852
|
+
##### `async start_group_call(chat_id, title=None) -> bool`
|
|
853
|
+
|
|
854
|
+
Start a group call (voice chat) in a supergroup. Requires admin rights with `manage_call` permission.
|
|
855
|
+
|
|
856
|
+
##### `async stop_group_call(chat_id) -> bool`
|
|
857
|
+
|
|
858
|
+
Stop (discard) an active group call in a supergroup.
|
|
859
|
+
|
|
860
|
+
---
|
|
861
|
+
|
|
862
|
+
### Client Media Mixin Methods
|
|
863
|
+
|
|
864
|
+
Low-level media operations using raw MTProto.
|
|
865
|
+
|
|
866
|
+
| Constant | Value | Description |
|
|
867
|
+
|----------|-------|-------------|
|
|
868
|
+
| `UPLOAD_CHUNK_SIZE` | `524288` (512 KB) | Default upload chunk size. |
|
|
869
|
+
| `DOWNLOAD_CHUNK_SIZE` | `1048576` (1 MB) | Default download chunk size. |
|
|
870
|
+
| `MAX_SMALL_FILE_SIZE` | `10485760` (10 MB) | Max size for small file uploads. |
|
|
871
|
+
|
|
872
|
+
##### `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`
|
|
873
|
+
|
|
874
|
+
Send a file using raw MTProto upload with chunked transfer. Auto-detects photo vs document based on file type.
|
|
875
|
+
|
|
876
|
+
##### `async send_location_media(chat_id, lat, lon, *, reply_to=None) -> Any`
|
|
877
|
+
|
|
878
|
+
Send a geographic location using raw MTProto.
|
|
879
|
+
|
|
880
|
+
##### `async send_contact_media(chat_id, phone, first_name, last_name="", vcard="", *, reply_to=None) -> Any`
|
|
881
|
+
|
|
882
|
+
Send a contact card using raw MTProto.
|
|
883
|
+
|
|
884
|
+
##### `async send_dice_media(chat_id, emoticon="🎲", *, reply_to=None) -> Any`
|
|
885
|
+
|
|
886
|
+
Send a dice animation using raw MTProto.
|
|
887
|
+
|
|
888
|
+
##### `async download_media(message, file_name=None, progress_callback=None, *, dc_id=None) -> Optional[str]`
|
|
889
|
+
|
|
890
|
+
Download a file from a message. Returns the path to the downloaded file, or `None` on failure.
|
|
891
|
+
|
|
892
|
+
| Parameter | Type | Default | Description |
|
|
893
|
+
|-----------|------|---------|-------------|
|
|
894
|
+
| `message` | `Message \| Any` | (required) | Message containing the media. |
|
|
895
|
+
| `file_name` | `str \| None` | `None` | Custom file name for the download. |
|
|
896
|
+
| `progress_callback` | `Callable \| None` | `None` | Progress callback: `fn(current, total)`. |
|
|
897
|
+
| `dc_id` | `int \| None` | `None` | Data center ID override. |
|
|
898
|
+
|
|
899
|
+
---
|
|
900
|
+
|
|
901
|
+
### Message Model
|
|
902
|
+
|
|
903
|
+
The `Message` dataclass represents a received or sent message.
|
|
904
|
+
|
|
905
|
+
#### Attributes
|
|
906
|
+
|
|
907
|
+
| Field | Type | Default | Description |
|
|
908
|
+
|-------|------|---------|-------------|
|
|
909
|
+
| `id` | `int` | `0` | Unique message identifier. |
|
|
910
|
+
| `text` | `str` | `""` | Message text or media caption. |
|
|
911
|
+
| `date` | `datetime \| None` | `None` | Message timestamp. |
|
|
912
|
+
| `chat_id` | `int` | `0` | Chat this message belongs to. |
|
|
913
|
+
| `chat` | `Chat \| None` | `None` | Resolved `Chat` object. |
|
|
914
|
+
| `sender_id` | `int \| None` | `None` | Sender user ID. |
|
|
915
|
+
| `sender` | `User \| None` | `None` | Resolved `User` object. |
|
|
916
|
+
| `media` | `Media \| None` | `None` | Attached media object. |
|
|
917
|
+
| `media_type` | `MessageMediaType` | `NONE` | Kind of media (`PHOTO`, `VIDEO`, etc.). |
|
|
918
|
+
| `reply_to` | `int \| None` | `None` | ID of the message being replied to. |
|
|
919
|
+
| `reply_to_sender_id` | `int \| None` | `None` | Sender ID from the reply header. |
|
|
920
|
+
| `reply_to_message` | `Message \| None` | `None` | Full replied-to message object. |
|
|
921
|
+
| `entities` | `list[MessageEntity]` | `[]` | Formatting entities (bold, links, etc.). |
|
|
922
|
+
| `forward_origin` | `dict \| None` | `None` | Forward origin information. |
|
|
923
|
+
| `views` | `int` | `0` | View count for channel messages. |
|
|
924
|
+
| `edit_date` | `datetime \| None` | `None` | Last edit timestamp. |
|
|
925
|
+
| `is_group` | `bool` | `False` | Whether from a group chat. |
|
|
926
|
+
| `is_channel` | `bool` | `False` | Whether from a channel. |
|
|
927
|
+
| `is_private` | `bool` | `False` | Whether from a private chat. |
|
|
928
|
+
| `new_chat_members` | `list` | `[]` | Users who joined (service messages). |
|
|
929
|
+
| `left_chat_member` | `User \| None` | `None` | User who left (service messages). |
|
|
930
|
+
| `service_type` | `str \| None` | `None` | Type of service event. |
|
|
931
|
+
|
|
932
|
+
#### Properties
|
|
933
|
+
|
|
934
|
+
| Property | Return Type | Description |
|
|
935
|
+
|----------|-------------|-------------|
|
|
936
|
+
| `message_id` | `int` | Alias for `id`. |
|
|
937
|
+
| `is_forwarded` | `bool` | Whether message was forwarded. |
|
|
938
|
+
| `is_reply` | `bool` | Whether message is a reply to another message. |
|
|
939
|
+
| `is_media` | `bool` | Whether message contains any media attachment. |
|
|
940
|
+
|
|
941
|
+
#### Methods
|
|
942
|
+
|
|
943
|
+
##### `async reply(text=None, *, parse_mode=None, link_preview=True, buttons=None, file=None, **kwargs) -> Message`
|
|
944
|
+
|
|
945
|
+
Reply to this message (sends with reply header).
|
|
946
|
+
|
|
947
|
+
##### `async edit(text=None, *, parse_mode=None, link_preview=True, buttons=None, delay=0) -> Message`
|
|
948
|
+
|
|
949
|
+
Edit this message's text. If `delay > 0`, waits before editing.
|
|
950
|
+
|
|
951
|
+
##### `async delete(*, delay=0) -> bool`
|
|
952
|
+
|
|
953
|
+
Delete this message. If `delay > 0`, waits before deleting.
|
|
954
|
+
|
|
955
|
+
##### `async forward(chat_id) -> Message`
|
|
956
|
+
|
|
957
|
+
Forward this message to another chat (keeps the forward header).
|
|
958
|
+
|
|
959
|
+
##### `async copy(chat_id) -> Message`
|
|
960
|
+
|
|
961
|
+
Copy this message to another chat (no forward header).
|
|
962
|
+
|
|
963
|
+
##### `async get_reply_message() -> Optional[Message]`
|
|
964
|
+
|
|
965
|
+
Fetch the full message that this message replies to.
|
|
966
|
+
|
|
967
|
+
##### `async pin(notify=True) -> bool`
|
|
968
|
+
|
|
969
|
+
Pin this message in its chat.
|
|
970
|
+
|
|
971
|
+
##### `async unpin() -> bool`
|
|
972
|
+
|
|
973
|
+
Unpin this message from its chat.
|
|
974
|
+
|
|
975
|
+
##### `async react(emoji) -> bool`
|
|
976
|
+
|
|
977
|
+
React to this message with an emoji.
|
|
978
|
+
|
|
979
|
+
##### `async mark_read() -> bool`
|
|
980
|
+
|
|
981
|
+
Mark this message as read.
|
|
982
|
+
|
|
983
|
+
##### `async unblock_sender() -> bool`
|
|
984
|
+
|
|
985
|
+
Unblock the sender of this message.
|
|
986
|
+
|
|
987
|
+
##### `async download(file_path=None, *, progress=None) -> Optional[str]`
|
|
988
|
+
|
|
989
|
+
Download the media attached to this message. Returns the local file path.
|
|
990
|
+
|
|
991
|
+
Aliases: `download_media()`, `download_file()`, `save()`.
|
|
992
|
+
|
|
993
|
+
##### `async reply_photo(photo, caption=None, **kwargs) -> Message`
|
|
994
|
+
|
|
995
|
+
Reply with a photo.
|
|
996
|
+
|
|
997
|
+
##### `async reply_video(video, caption=None, **kwargs) -> Message`
|
|
998
|
+
|
|
999
|
+
Reply with a video.
|
|
1000
|
+
|
|
1001
|
+
##### `async reply_voice(voice, caption=None, **kwargs) -> Message`
|
|
1002
|
+
|
|
1003
|
+
Reply with a voice note.
|
|
1004
|
+
|
|
1005
|
+
##### `async reply_document(document, caption=None, **kwargs) -> Message`
|
|
1006
|
+
|
|
1007
|
+
Reply with a document.
|
|
1008
|
+
|
|
1009
|
+
##### `async reply_audio(audio, caption=None, *, duration=0, performer=None, title=None, **kwargs) -> Message`
|
|
1010
|
+
|
|
1011
|
+
Reply with an audio file.
|
|
1012
|
+
|
|
1013
|
+
##### `async reply_animation(animation, caption=None, **kwargs) -> Message`
|
|
1014
|
+
|
|
1015
|
+
Reply with a GIF/animation.
|
|
1016
|
+
|
|
1017
|
+
##### `async reply_sticker(sticker, **kwargs) -> Message`
|
|
1018
|
+
|
|
1019
|
+
Reply with a sticker.
|
|
1020
|
+
|
|
1021
|
+
##### `async reply_location(lat, lon) -> Message`
|
|
1022
|
+
|
|
1023
|
+
Reply with a geographic location.
|
|
1024
|
+
|
|
1025
|
+
##### `async reply_contact(phone, first_name, last_name="", *, vcard="") -> Message`
|
|
1026
|
+
|
|
1027
|
+
Reply with a contact card.
|
|
1028
|
+
|
|
1029
|
+
##### `async reply_poll(question, options, *, is_anonymous=True, allows_multiple_answers=False) -> Message`
|
|
1030
|
+
|
|
1031
|
+
Reply with a poll.
|
|
1032
|
+
|
|
1033
|
+
##### `async reply_dice(emoji="🎲") -> Message`
|
|
1034
|
+
|
|
1035
|
+
Reply with a dice animation.
|
|
1036
|
+
|
|
1037
|
+
##### `async reply_video_note(video, *, caption=None, duration=0, **kwargs) -> Message`
|
|
1038
|
+
|
|
1039
|
+
Reply with a round video note.
|
|
1040
|
+
|
|
1041
|
+
---
|
|
1042
|
+
|
|
1043
|
+
### User Model
|
|
1044
|
+
|
|
1045
|
+
Represents a Soroush Plus user.
|
|
1046
|
+
|
|
1047
|
+
#### Attributes
|
|
1048
|
+
|
|
1049
|
+
| Field | Type | Default | Description |
|
|
1050
|
+
|-------|------|---------|-------------|
|
|
1051
|
+
| `id` | `int` | (required) | Unique user identifier. |
|
|
1052
|
+
| `first_name` | `str` | `""` | First name. |
|
|
1053
|
+
| `last_name` | `str` | `""` | Last name. |
|
|
1054
|
+
| `username` | `str \| None` | `None` | Username without `@`. |
|
|
1055
|
+
| `phone` | `str \| None` | `None` | Phone number. |
|
|
1056
|
+
| `bio` | `str \| None` | `None` | Biography/about text. |
|
|
1057
|
+
| `is_bot` | `bool` | `False` | Whether this is a bot account. |
|
|
1058
|
+
| `is_self` | `bool` | `False` | Whether this is the current authenticated user. |
|
|
1059
|
+
| `is_premium` | `bool` | `False` | Whether user has premium subscription. |
|
|
1060
|
+
| `status` | `UserStatus` | `EMPTY` | Online status. |
|
|
1061
|
+
|
|
1062
|
+
#### Properties
|
|
1063
|
+
|
|
1064
|
+
| Property | Return Type | Description |
|
|
1065
|
+
|----------|-------------|-------------|
|
|
1066
|
+
| `full_name` | `str` | Combined first + last name. |
|
|
1067
|
+
| `mention` | `str` | `@username` if available, otherwise full name. |
|
|
1068
|
+
| `link` | `str \| None` | `https://t.me/username` profile link. `None` if no username. |
|
|
1069
|
+
|
|
1070
|
+
---
|
|
1071
|
+
|
|
1072
|
+
### Chat Model
|
|
1073
|
+
|
|
1074
|
+
Represents a Soroush Plus chat (private, group, supergroup, or channel).
|
|
1075
|
+
|
|
1076
|
+
#### Attributes
|
|
1077
|
+
|
|
1078
|
+
| Field | Type | Default | Description |
|
|
1079
|
+
|-------|------|---------|-------------|
|
|
1080
|
+
| `id` | `int` | (required) | Unique chat identifier. |
|
|
1081
|
+
| `title` | `str` | `""` | Chat title. |
|
|
1082
|
+
| `type` | `ChatType` | `PRIVATE` | Type of chat. |
|
|
1083
|
+
| `username` | `str \| None` | `None` | Public username. |
|
|
1084
|
+
| `description` | `str \| None` | `None` | Chat description. |
|
|
1085
|
+
| `member_count` | `int` | `0` | Number of members. |
|
|
1086
|
+
|
|
1087
|
+
#### Properties
|
|
1088
|
+
|
|
1089
|
+
| Property | Return Type | Description |
|
|
1090
|
+
|----------|-------------|-------------|
|
|
1091
|
+
| `is_private` | `bool` | Whether it's a private (1-to-1) chat. |
|
|
1092
|
+
| `is_group` | `bool` | Whether it's a group or supergroup. |
|
|
1093
|
+
| `is_supergroup` | `bool` | Whether it's a supergroup. |
|
|
1094
|
+
| `is_channel` | `bool` | Whether it's a channel. |
|
|
1095
|
+
| `link` | `str \| None` | Deep link to the chat. |
|
|
1096
|
+
| `display_name` | `str` | Human-readable name (title or username). |
|
|
1097
|
+
|
|
1098
|
+
#### Subclasses
|
|
1099
|
+
|
|
1100
|
+
- `Channel(Chat)` — Pre-set type to `CHANNEL`.
|
|
1101
|
+
- `Group(Chat)` — Pre-set type to `GROUP` or `SUPERGROUP`.
|
|
1102
|
+
|
|
1103
|
+
---
|
|
1104
|
+
|
|
1105
|
+
### ChatMember Model
|
|
1106
|
+
|
|
1107
|
+
Represents a member of a chat.
|
|
1108
|
+
|
|
1109
|
+
#### Attributes
|
|
1110
|
+
|
|
1111
|
+
| Field | Type | Description |
|
|
1112
|
+
|-------|------|-------------|
|
|
1113
|
+
| `user` | `User` | The user object. |
|
|
1114
|
+
| `status` | `ChatMemberStatus` | Membership status. |
|
|
1115
|
+
| `joined_date` | `datetime \| None` | When they joined. |
|
|
1116
|
+
| `invited_by` | `int \| None` | Who invited them. |
|
|
1117
|
+
| `restricted_until` | `int \| None` | Restriction expiry timestamp. |
|
|
1118
|
+
|
|
1119
|
+
#### Properties
|
|
1120
|
+
|
|
1121
|
+
| Property | Return Type | Description |
|
|
1122
|
+
|----------|-------------|-------------|
|
|
1123
|
+
| `user_id` | `int` | Shortcut to `user.id`. |
|
|
1124
|
+
| `is_creator` | `bool` | Whether this is the chat creator/owner. |
|
|
1125
|
+
| `is_admin` | `bool` | Whether they have admin rights (includes creator). |
|
|
1126
|
+
| `is_banned` | `bool` | Whether they are banned. |
|
|
1127
|
+
| `is_left` | `bool` | Whether they have left the chat. |
|
|
1128
|
+
|
|
1129
|
+
---
|
|
1130
|
+
|
|
1131
|
+
### Button Factory
|
|
1132
|
+
|
|
1133
|
+
Static factory methods for creating keyboard buttons. All methods are `@staticmethod`.
|
|
1134
|
+
|
|
1135
|
+
#### Inline Buttons
|
|
1136
|
+
|
|
1137
|
+
##### `Button.inline(text, callback_data) -> InlineKeyboardButton`
|
|
1138
|
+
|
|
1139
|
+
Create an inline keyboard button with callback data.
|
|
1140
|
+
|
|
1141
|
+
| Parameter | Type | Description |
|
|
1142
|
+
|-----------|------|-------------|
|
|
1143
|
+
| `text` | `str` | Button display text. |
|
|
1144
|
+
| `callback_data` | `bytes` | Data sent back when button is pressed. |
|
|
1145
|
+
|
|
1146
|
+
##### `Button.url(text, url) -> InlineKeyboardButton`
|
|
1147
|
+
|
|
1148
|
+
Create an inline button that opens a URL.
|
|
1149
|
+
|
|
1150
|
+
##### `Button.switch_inline(text, query="") -> InlineKeyboardButton`
|
|
1151
|
+
|
|
1152
|
+
Create an inline button that switches to inline mode with a pre-filled query.
|
|
1153
|
+
|
|
1154
|
+
##### `Button.switch_inline_current(text, query="") -> InlineKeyboardButton`
|
|
1155
|
+
|
|
1156
|
+
Same as `switch_inline` but keeps the user in the current chat.
|
|
1157
|
+
|
|
1158
|
+
#### Reply Buttons
|
|
1159
|
+
|
|
1160
|
+
##### `Button.text(text, resize=True, one_time=False) -> ReplyMarkup`
|
|
1161
|
+
|
|
1162
|
+
Create a single-button reply keyboard.
|
|
1163
|
+
|
|
1164
|
+
#### Special Buttons
|
|
1165
|
+
|
|
1166
|
+
##### `Button.request_location(text="Share Location") -> KeyboardButton`
|
|
1167
|
+
|
|
1168
|
+
Create a button that requests the user's location.
|
|
1169
|
+
|
|
1170
|
+
##### `Button.request_phone(text="Share Phone") -> KeyboardButton`
|
|
1171
|
+
|
|
1172
|
+
Create a button that requests the user's phone number.
|
|
1173
|
+
|
|
1174
|
+
##### `Button.clear() -> ReplyMarkup`
|
|
1175
|
+
|
|
1176
|
+
Create a special reply markup that removes/hides the current keyboard.
|
|
1177
|
+
|
|
1178
|
+
#### Keyboard Builders
|
|
1179
|
+
|
|
1180
|
+
##### `Button.build_inline(*rows) -> ReplyMarkup`
|
|
1181
|
+
|
|
1182
|
+
Build an inline keyboard from rows of buttons.
|
|
1183
|
+
|
|
1184
|
+
```python
|
|
1185
|
+
keyboard = Button.build_inline(
|
|
1186
|
+
[Button.inline("Option 1", b"opt1"), Button.inline("Option 2", b"opt2")],
|
|
1187
|
+
[Button.url("Visit", "https://example.com")]
|
|
1188
|
+
)
|
|
1189
|
+
```
|
|
1190
|
+
|
|
1191
|
+
##### `Button.build_reply(*rows, resize=True, one_time=False) -> ReplyMarkup`
|
|
1192
|
+
|
|
1193
|
+
Build a reply keyboard from rows of buttons.
|
|
1194
|
+
|
|
1195
|
+
```python
|
|
1196
|
+
kb = Button.build_reply(
|
|
1197
|
+
[Button.text("Menu"), Button.text("Settings")],
|
|
1198
|
+
[Button.text("Help")]
|
|
1199
|
+
)
|
|
1200
|
+
```
|
|
1201
|
+
|
|
1202
|
+
---
|
|
1203
|
+
|
|
1204
|
+
### CallbackQuery Model
|
|
1205
|
+
|
|
1206
|
+
Represents a callback query from an inline button press.
|
|
1207
|
+
|
|
1208
|
+
#### Attributes
|
|
1209
|
+
|
|
1210
|
+
| Field | Type | Description |
|
|
1211
|
+
|-------|------|-------------|
|
|
1212
|
+
| `id` | `str` | Unique callback query identifier. |
|
|
1213
|
+
| `data` | `bytes \| None` | Raw callback data bytes. |
|
|
1214
|
+
| `chat_instance` | `str` | Chat instance identifier. |
|
|
1215
|
+
| `from_user` | `User` | The user who pressed the button. |
|
|
1216
|
+
| `message` | `Message \| None` | The message containing the button. |
|
|
1217
|
+
|
|
1218
|
+
#### Properties
|
|
1219
|
+
|
|
1220
|
+
| Property | Return Type | Description |
|
|
1221
|
+
|----------|-------------|-------------|
|
|
1222
|
+
| `text` | `str \| None` | Decoded callback data as UTF-8 string. |
|
|
1223
|
+
|
|
1224
|
+
#### Methods
|
|
1225
|
+
|
|
1226
|
+
##### `async answer(text=None, show_alert=False, cache_time=0) -> bool`
|
|
1227
|
+
|
|
1228
|
+
Answer the callback query. Shows a popup notification if `show_alert=True`.
|
|
1229
|
+
|
|
1230
|
+
##### `async edit_message(text=None, reply_markup=None) -> bool`
|
|
1231
|
+
|
|
1232
|
+
Edit the message that contains the inline keyboard.
|
|
1233
|
+
|
|
1234
|
+
---
|
|
1235
|
+
|
|
1236
|
+
### InlineQuery Model
|
|
1237
|
+
|
|
1238
|
+
Represents an inline query from a user.
|
|
1239
|
+
|
|
1240
|
+
#### Attributes
|
|
1241
|
+
|
|
1242
|
+
| Field | Type | Description |
|
|
1243
|
+
|-------|------|-------------|
|
|
1244
|
+
| `id` | `str` | Unique inline query identifier. |
|
|
1245
|
+
| `query` | `str` | The search query text. |
|
|
1246
|
+
| `offset` | `str` | Pagination offset. |
|
|
1247
|
+
| `chat_type` | `str \| None` | Chat type context. |
|
|
1248
|
+
| `from_user` | `User` | The user who sent the query. |
|
|
1249
|
+
|
|
1250
|
+
#### Methods
|
|
1251
|
+
|
|
1252
|
+
##### `async answer(results, cache_time=300, is_personal=True, next_offset=None) -> bool`
|
|
1253
|
+
|
|
1254
|
+
Answer the inline query with a list of result dictionaries.
|
|
1255
|
+
|
|
1256
|
+
---
|
|
1257
|
+
|
|
1258
|
+
### Events
|
|
1259
|
+
|
|
1260
|
+
#### Event (Base Class)
|
|
1261
|
+
|
|
1262
|
+
Abstract base for all events. All events support propagation control.
|
|
1263
|
+
|
|
1264
|
+
##### `stop_propagation() -> None`
|
|
1265
|
+
|
|
1266
|
+
Stop the event from being passed to subsequent handlers with lower priority.
|
|
1267
|
+
|
|
1268
|
+
##### `is_propagation_stopped` (property) -> `bool`
|
|
1269
|
+
|
|
1270
|
+
Returns `True` if propagation was stopped by a handler.
|
|
1271
|
+
|
|
1272
|
+
#### HandlerPriority
|
|
1273
|
+
|
|
1274
|
+
Controls the order in which handlers are executed.
|
|
1275
|
+
|
|
1276
|
+
| Value | Int | Description |
|
|
1277
|
+
|-------|-----|-------------|
|
|
1278
|
+
| `FIRST` | `0` | Executes first, before all others. |
|
|
1279
|
+
| `HIGH` | `10` | Executes early. |
|
|
1280
|
+
| `NORMAL` | `50` | Default priority. |
|
|
1281
|
+
| `LOW` | `100` | Executes late. |
|
|
1282
|
+
| `LAST` | `200` | Executes last, after all others. |
|
|
1283
|
+
|
|
1284
|
+
#### NewMessage Event
|
|
1285
|
+
|
|
1286
|
+
Fired when a new message is received.
|
|
1287
|
+
|
|
1288
|
+
##### Fields
|
|
1289
|
+
|
|
1290
|
+
| Field | Type | Description |
|
|
1291
|
+
|-------|------|-------------|
|
|
1292
|
+
| `message` | `Message` | The incoming message. |
|
|
1293
|
+
| `pattern_match` | `Pattern.Match \| None` | Regex match result (if `filters.regex` was used). |
|
|
1294
|
+
|
|
1295
|
+
##### Properties
|
|
1296
|
+
|
|
1297
|
+
`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`.
|
|
1298
|
+
|
|
1299
|
+
##### Methods
|
|
1300
|
+
|
|
1301
|
+
```python
|
|
1302
|
+
async def reply(self, text: str, **kwargs) -> Message
|
|
1303
|
+
async def respond(self, text: str, **kwargs) -> Message # Send without reply header
|
|
1304
|
+
async def delete(self, **kwargs) -> bool
|
|
1305
|
+
async def edit(self, text: str, **kwargs) -> Message
|
|
1306
|
+
async def forward(self, chat_id: int) -> Message
|
|
1307
|
+
async def pin(self, notify=False) -> bool
|
|
1308
|
+
async def react(self, emoji: str) -> bool
|
|
1309
|
+
async def mark_read(self) -> bool
|
|
1310
|
+
async def download(self, file_path=None) -> Optional[str]
|
|
1311
|
+
```
|
|
1312
|
+
|
|
1313
|
+
#### ErrorEvent
|
|
1314
|
+
|
|
1315
|
+
Fired when a handler raises an exception.
|
|
1316
|
+
|
|
1317
|
+
| Field | Type | Description |
|
|
1318
|
+
|-------|------|-------------|
|
|
1319
|
+
| `exception` | `Exception` | The raised exception. |
|
|
1320
|
+
| `handler` | `Callable \| None` | The handler that failed. |
|
|
1321
|
+
| `update` | `Any` | The original update. |
|
|
1322
|
+
|
|
1323
|
+
| Property | Type | Description |
|
|
1324
|
+
|----------|------|-------------|
|
|
1325
|
+
| `message` | `str` | Error message string. |
|
|
1326
|
+
| `type` | `str` | Exception class name. |
|
|
1327
|
+
|
|
1328
|
+
#### ChatAction Event
|
|
1329
|
+
|
|
1330
|
+
Fired on chat actions (joins, leaves, pins).
|
|
1331
|
+
|
|
1332
|
+
| Property | Type | Description |
|
|
1333
|
+
|----------|------|-------------|
|
|
1334
|
+
| `is_join` | `bool` | Whether a user joined. |
|
|
1335
|
+
| `is_leave` | `bool` | Whether a user left. |
|
|
1336
|
+
| `is_pin` | `bool` | Whether a message was pinned. |
|
|
1337
|
+
| `user_name` | `str` | Display name of the user. |
|
|
1338
|
+
|
|
1339
|
+
#### UserUpdate Event
|
|
1340
|
+
|
|
1341
|
+
Fired when a user's online status changes.
|
|
1342
|
+
|
|
1343
|
+
| Property | Type | Description |
|
|
1344
|
+
|----------|------|-------------|
|
|
1345
|
+
| `is_online` | `bool` | Whether the user came online. |
|
|
1346
|
+
| `is_offline` | `bool` | Whether the user went offline. |
|
|
1347
|
+
|
|
1348
|
+
#### MessageDeleted Event
|
|
1349
|
+
|
|
1350
|
+
Fired when messages are deleted. Fields: `deleted_ids` (list of ints), `chat_id`.
|
|
1351
|
+
|
|
1352
|
+
#### MessageRead Event
|
|
1353
|
+
|
|
1354
|
+
Fired when messages are read. Fields: `read_ids` (list of ints), `chat_id`, `max_id`.
|
|
1355
|
+
|
|
1356
|
+
#### Album Event
|
|
1357
|
+
|
|
1358
|
+
Fired when a group of media messages is received (e.g., multiple photos sent at once).
|
|
1359
|
+
|
|
1360
|
+
| Field | Type | Description |
|
|
1361
|
+
|-------|------|-------------|
|
|
1362
|
+
| `messages` | `list[Message]` | All messages in the album. |
|
|
1363
|
+
| `chat_id` | `int` | The chat where the album was received. |
|
|
1364
|
+
| `group_id` | `str` | Album group identifier. |
|
|
1365
|
+
|
|
1366
|
+
| Property | Type | Description |
|
|
1367
|
+
|----------||-------------|
|
|
1368
|
+
| `total` | `int` | Number of messages in the album. |
|
|
1369
|
+
|
|
1370
|
+
---
|
|
1371
|
+
|
|
1372
|
+
### Filters
|
|
1373
|
+
|
|
1374
|
+
Filters are composable predicates that determine which messages trigger a handler. Use `&` (AND), `|` (OR), and `~` (NOT) to combine them.
|
|
1375
|
+
|
|
1376
|
+
#### Chat-Type Filters
|
|
1377
|
+
|
|
1378
|
+
| Filter | Matches |
|
|
1379
|
+
|--------|---------|
|
|
1380
|
+
| `filters.private` | Messages from private (1-to-1) chats. |
|
|
1381
|
+
| `filters.group` | Messages from group or supergroup chats. |
|
|
1382
|
+
| `filters.channel` | Messages from channels. |
|
|
1383
|
+
|
|
1384
|
+
#### Content-Type Filters
|
|
1385
|
+
|
|
1386
|
+
| Filter | Matches |
|
|
1387
|
+
|--------|---------|
|
|
1388
|
+
| `filters.text` | Messages with non-empty text. |
|
|
1389
|
+
| `filters.photo` | Messages containing a photo. |
|
|
1390
|
+
| `filters.video` | Messages containing a video. |
|
|
1391
|
+
| `filters.audio` | Messages containing audio. |
|
|
1392
|
+
| `filters.voice` | Messages containing a voice note. |
|
|
1393
|
+
| `filters.document` | Messages containing a document. |
|
|
1394
|
+
| `filters.sticker` | Messages containing a sticker. |
|
|
1395
|
+
| `filters.animation` | Messages containing a GIF. |
|
|
1396
|
+
| `filters.contact` | Messages containing a contact card. |
|
|
1397
|
+
| `filters.location` | Messages containing a location. |
|
|
1398
|
+
| `filters.poll` | Messages containing a poll. |
|
|
1399
|
+
| `filters.video_note` | Messages containing a video note. |
|
|
1400
|
+
| `filters.dice` | Messages containing a dice animation. |
|
|
1401
|
+
| `filters.media` | Messages with any media attachment. |
|
|
1402
|
+
|
|
1403
|
+
#### State Filters
|
|
1404
|
+
|
|
1405
|
+
| Filter | Matches |
|
|
1406
|
+
|--------|---------|
|
|
1407
|
+
| `filters.reply` | Messages that are replies to other messages. |
|
|
1408
|
+
| `filters.forwarded` | Forwarded messages. |
|
|
1409
|
+
| `filters.me` | Messages sent by the current user. |
|
|
1410
|
+
| `filters.bot` | Messages sent by bots. |
|
|
1411
|
+
| `filters.outgoing` | Outgoing (outbound) messages. |
|
|
1412
|
+
| `filters.incoming` | Incoming (inbound) messages. |
|
|
1413
|
+
| `filters.mentioned` | Messages where the current user is mentioned. |
|
|
1414
|
+
| `filters.new_chat_members` | Service messages: users joined. |
|
|
1415
|
+
| `filters.left_chat_member` | Service messages: user left/was removed. |
|
|
1416
|
+
| `filters.service` | Any system/service message. |
|
|
1417
|
+
|
|
1418
|
+
#### Factory Functions
|
|
1419
|
+
|
|
1420
|
+
##### `filters.command(*commands, prefixes=None) -> Command`
|
|
1421
|
+
|
|
1422
|
+
Create a command filter. Matches messages starting with `/` or `!` followed by the given command names.
|
|
1423
|
+
|
|
1424
|
+
```python
|
|
1425
|
+
@bot.on_message(filters.command("start", "help"))
|
|
1426
|
+
async def handler(client, message):
|
|
1427
|
+
# Matches /start, /help, !start, !help
|
|
1428
|
+
pass
|
|
1429
|
+
```
|
|
1430
|
+
|
|
1431
|
+
| Parameter | Type | Default | Description |
|
|
1432
|
+
|-----------|------|---------|-------------|
|
|
1433
|
+
| `*commands` | `str` | (required) | Command names to match. |
|
|
1434
|
+
| `prefixes` | `list[str] \| None` | `None` | Custom prefixes. Default: `["/", "!"]`. |
|
|
1435
|
+
|
|
1436
|
+
##### `filters.regex(pattern, flags=0) -> Regex`
|
|
1437
|
+
|
|
1438
|
+
Create a regex filter. The match object is stored on `message.pattern_match`.
|
|
1439
|
+
|
|
1440
|
+
```python
|
|
1441
|
+
@bot.on_message(filters.regex(r"(\d+)"))
|
|
1442
|
+
async def handler(client, message):
|
|
1443
|
+
number = message.pattern_match.group(1)
|
|
1444
|
+
```
|
|
1445
|
+
|
|
1446
|
+
##### `filters.user(*user_ids) -> UserFilter`
|
|
1447
|
+
|
|
1448
|
+
Create a user ID filter. Matches messages from specific users.
|
|
1449
|
+
|
|
1450
|
+
##### `filters.chat(*chat_ids) -> ChatFilter`
|
|
1451
|
+
|
|
1452
|
+
Create a chat ID filter. Matches messages from specific chats.
|
|
1453
|
+
|
|
1454
|
+
##### `filters.text_contains(substring, case_sensitive=True) -> TextContains`
|
|
1455
|
+
|
|
1456
|
+
Filter messages that contain a specific substring.
|
|
1457
|
+
|
|
1458
|
+
##### `filters.text_startswith(prefix) -> TextStartsWith`
|
|
1459
|
+
|
|
1460
|
+
Filter messages that start with a specific prefix.
|
|
1461
|
+
|
|
1462
|
+
##### `filters.text_endswith(suffix) -> TextEndsWith`
|
|
1463
|
+
|
|
1464
|
+
Filter messages that end with a specific suffix.
|
|
1465
|
+
|
|
1466
|
+
##### `filters.length(min=0, max=999999) -> Length`
|
|
1467
|
+
|
|
1468
|
+
Filter messages by text length.
|
|
1469
|
+
|
|
1470
|
+
##### `filters.from_callable(func) -> Filter`
|
|
1471
|
+
|
|
1472
|
+
Wrap any callable as a filter. If the argument is already a `Filter`, returns it as-is.
|
|
1473
|
+
|
|
1474
|
+
#### Composition Examples
|
|
1475
|
+
|
|
1476
|
+
```python
|
|
1477
|
+
# AND: private AND text
|
|
1478
|
+
@bot.on_message(filters.private & filters.text)
|
|
1479
|
+
|
|
1480
|
+
# OR: photo OR video
|
|
1481
|
+
@bot.on_message(filters.photo | filters.video)
|
|
1482
|
+
|
|
1483
|
+
# NOT: NOT outgoing
|
|
1484
|
+
@bot.on_message(~filters.outgoing)
|
|
1485
|
+
|
|
1486
|
+
# Complex: (private OR group) AND text AND NOT bot
|
|
1487
|
+
@bot.on_message((filters.private | filters.group) & filters.text & ~filters.bot)
|
|
1488
|
+
```
|
|
1489
|
+
|
|
1490
|
+
---
|
|
1491
|
+
|
|
1492
|
+
### FSM (Finite State Machine)
|
|
1493
|
+
|
|
1494
|
+
#### State
|
|
1495
|
+
|
|
1496
|
+
Represents a single FSM state. Auto-named via the `__set_name__` descriptor protocol when assigned as a class attribute.
|
|
1497
|
+
|
|
1498
|
+
```python
|
|
1499
|
+
State(name: Optional[str] = None)
|
|
1500
|
+
```
|
|
1501
|
+
|
|
1502
|
+
| Property | Type | Description |
|
|
1503
|
+
|----------|------|-------------|
|
|
1504
|
+
| `name` | `str` | The state name (auto-derived from class attribute name). |
|
|
1505
|
+
|
|
1506
|
+
#### StateTransition
|
|
1507
|
+
|
|
1508
|
+
Represents a state transition with an optional async guard function.
|
|
1509
|
+
|
|
1510
|
+
```python
|
|
1511
|
+
StateTransition(target: State, guard: Optional[Callable] = None)
|
|
1512
|
+
```
|
|
1513
|
+
|
|
1514
|
+
##### `async check(update) -> bool`
|
|
1515
|
+
|
|
1516
|
+
Check whether this transition is allowed. If no guard is set, always returns `True`.
|
|
1517
|
+
|
|
1518
|
+
#### FSMContext
|
|
1519
|
+
|
|
1520
|
+
Per-user context for reading/writing FSM state and data.
|
|
1521
|
+
|
|
1522
|
+
```python
|
|
1523
|
+
FSMContext(storage: Storage, user_id: int, prefix: str = "fsm")
|
|
1524
|
+
```
|
|
1525
|
+
|
|
1526
|
+
| Property | Type | Description |
|
|
1527
|
+
|----------|------|-------------|
|
|
1528
|
+
| `key` | `str` | Full storage key including prefix and user ID. |
|
|
1529
|
+
| `state_key` | `str` | Key for storing the current state. |
|
|
1530
|
+
| `data_key` | `str` | Key for storing user data. |
|
|
1531
|
+
| `user_id` | `int` | The user this context belongs to. |
|
|
1532
|
+
|
|
1533
|
+
##### `async get_state() -> Optional[State]`
|
|
1534
|
+
|
|
1535
|
+
Get the current state for this user. Returns `None` if no state is set.
|
|
1536
|
+
|
|
1537
|
+
##### `async set_state(state) -> None`
|
|
1538
|
+
|
|
1539
|
+
Set the current state for this user. Pass `None` to clear the state.
|
|
1540
|
+
|
|
1541
|
+
##### `async get_data() -> dict[str, Any]`
|
|
1542
|
+
|
|
1543
|
+
Get all stored data for this user in the current state.
|
|
1544
|
+
|
|
1545
|
+
##### `async set_data(**kwargs) -> None`
|
|
1546
|
+
|
|
1547
|
+
Update the stored data with the provided key-value pairs.
|
|
1548
|
+
|
|
1549
|
+
##### `async get(key, default=None) -> Any`
|
|
1550
|
+
|
|
1551
|
+
Get a single data value by key.
|
|
1552
|
+
|
|
1553
|
+
##### `async set(key, value) -> None`
|
|
1554
|
+
|
|
1555
|
+
Set a single data value.
|
|
1556
|
+
|
|
1557
|
+
##### `async reset() -> None`
|
|
1558
|
+
|
|
1559
|
+
Clear both the state and all data for this user.
|
|
1560
|
+
|
|
1561
|
+
##### `async finish() -> None`
|
|
1562
|
+
|
|
1563
|
+
Alias for `reset()`. Clears state and data.
|
|
1564
|
+
|
|
1565
|
+
#### StateMachine
|
|
1566
|
+
|
|
1567
|
+
Factory for `FSMContext` with decorator-based state routing.
|
|
1568
|
+
|
|
1569
|
+
```python
|
|
1570
|
+
StateMachine(storage: Storage)
|
|
1571
|
+
```
|
|
1572
|
+
|
|
1573
|
+
##### `def state(state, *, on_enter=None, on_leave=None) -> Callable`
|
|
1574
|
+
|
|
1575
|
+
Decorator that registers a handler for a specific state.
|
|
1576
|
+
|
|
1577
|
+
```python
|
|
1578
|
+
@sm.state(Form.name)
|
|
1579
|
+
async def handle_name(ctx, message):
|
|
1580
|
+
await ctx.set(name=message.text)
|
|
1581
|
+
return StateTransition(to=Form.age)
|
|
1582
|
+
```
|
|
1583
|
+
|
|
1584
|
+
##### `def on_enter(state) -> Callable`
|
|
1585
|
+
|
|
1586
|
+
Decorator that registers a callback when entering a state.
|
|
1587
|
+
|
|
1588
|
+
##### `def on_leave(state) -> Callable`
|
|
1589
|
+
|
|
1590
|
+
Decorator that registers a callback when leaving a state.
|
|
1591
|
+
|
|
1592
|
+
##### `def fallback() -> Callable`
|
|
1593
|
+
|
|
1594
|
+
Decorator that registers a handler for unmatched states (no registered handler for the current state).
|
|
1595
|
+
|
|
1596
|
+
##### `def context(user_id, prefix="fsm") -> FSMContext`
|
|
1597
|
+
|
|
1598
|
+
Create an `FSMContext` for a specific user.
|
|
1599
|
+
|
|
1600
|
+
##### `async handle(update, user_id) -> bool`
|
|
1601
|
+
|
|
1602
|
+
Route an update to the appropriate state handler. Returns `True` if a handler was found and executed.
|
|
1603
|
+
|
|
1604
|
+
#### Usage Example
|
|
1605
|
+
|
|
1606
|
+
```python
|
|
1607
|
+
from spluspy.fsm import State, StateMachine, StateTransition
|
|
1608
|
+
from spluspy.storage import SQLiteStorage
|
|
1609
|
+
|
|
1610
|
+
storage = SQLiteStorage("fsm.db")
|
|
1611
|
+
sm = StateMachine(storage)
|
|
1612
|
+
|
|
1613
|
+
class Registration:
|
|
1614
|
+
name = State()
|
|
1615
|
+
age = State()
|
|
1616
|
+
complete = State()
|
|
1617
|
+
|
|
1618
|
+
@sm.state(Registration.name)
|
|
1619
|
+
async def handle_name(ctx, message):
|
|
1620
|
+
await ctx.set(name=message.text)
|
|
1621
|
+
return StateTransition(to=Registration.age)
|
|
1622
|
+
|
|
1623
|
+
@sm.state(Registration.age)
|
|
1624
|
+
async def handle_age(ctx, message):
|
|
1625
|
+
await ctx.set(age=message.text)
|
|
1626
|
+
return StateTransition(to=Registration.complete)
|
|
1627
|
+
|
|
1628
|
+
@sm.state(Registration.complete)
|
|
1629
|
+
async def handle_complete(ctx, message):
|
|
1630
|
+
data = await ctx.get()
|
|
1631
|
+
await message.reply(f"Done: {data}")
|
|
1632
|
+
await ctx.finish()
|
|
1633
|
+
```
|
|
1634
|
+
|
|
1635
|
+
---
|
|
1636
|
+
|
|
1637
|
+
### Storage Backends
|
|
1638
|
+
|
|
1639
|
+
#### Storage (Abstract Base)
|
|
1640
|
+
|
|
1641
|
+
All storage backends implement this interface.
|
|
1642
|
+
|
|
1643
|
+
```python
|
|
1644
|
+
async def get(key: str) -> Optional[Any]
|
|
1645
|
+
async def set(key: str, value: Any, ttl: Optional[int] = None) -> None
|
|
1646
|
+
async def delete(key: str) -> bool
|
|
1647
|
+
async def exists(key: str) -> bool
|
|
1648
|
+
async def clear() -> None
|
|
1649
|
+
```
|
|
1650
|
+
|
|
1651
|
+
#### MemoryStorage
|
|
1652
|
+
|
|
1653
|
+
In-memory non-persistent storage with optional TTL support. Data is lost when the process exits.
|
|
1654
|
+
|
|
1655
|
+
```python
|
|
1656
|
+
MemoryStorage()
|
|
1657
|
+
```
|
|
1658
|
+
|
|
1659
|
+
**Use case:** Development, testing, short-lived sessions.
|
|
1660
|
+
|
|
1661
|
+
#### SQLiteStorage
|
|
1662
|
+
|
|
1663
|
+
Persistent SQLite-backed storage. Automatically creates the database and table on `start()`.
|
|
1664
|
+
|
|
1665
|
+
```python
|
|
1666
|
+
SQLiteStorage(db_path: str = "spluspy_storage.db")
|
|
1667
|
+
```
|
|
1668
|
+
|
|
1669
|
+
| Method | Description |
|
|
1670
|
+
|--------|-------------|
|
|
1671
|
+
| `async start()` | Open the database and create the table if needed. |
|
|
1672
|
+
| `async close()` | Close the database connection. |
|
|
1673
|
+
|
|
1674
|
+
**Use case:** Single-user bots, local persistence.
|
|
1675
|
+
|
|
1676
|
+
#### RedisStorage
|
|
1677
|
+
|
|
1678
|
+
Persistent Redis-backed storage. Requires `pip install spluspy[redis]`.
|
|
1679
|
+
|
|
1680
|
+
```python
|
|
1681
|
+
RedisStorage(
|
|
1682
|
+
url: str = "redis://localhost:6379/0",
|
|
1683
|
+
prefix: str = "spluspy:",
|
|
1684
|
+
default_ttl: Optional[int] = None,
|
|
1685
|
+
)
|
|
1686
|
+
```
|
|
1687
|
+
|
|
1688
|
+
| Extra Method | Description |
|
|
1689
|
+
|--------------|-------------|
|
|
1690
|
+
| `async increment(key, amount=1) -> int` | Atomically increment a counter. |
|
|
1691
|
+
| `async set_hash(key, mapping, ttl=None)` | Store a dict as a Redis hash. |
|
|
1692
|
+
| `async get_hash(key) -> dict` | Retrieve a Redis hash as a dict. |
|
|
1693
|
+
|
|
1694
|
+
**Use case:** Multi-process deployments, shared state.
|
|
1695
|
+
|
|
1696
|
+
#### PostgresStorage
|
|
1697
|
+
|
|
1698
|
+
Persistent PostgreSQL-backed storage. Requires `pip install spluspy[postgres]`.
|
|
1699
|
+
|
|
1700
|
+
```python
|
|
1701
|
+
PostgresStorage(
|
|
1702
|
+
dsn: str = "postgresql://localhost/spluspy",
|
|
1703
|
+
table: str = "spluspy_kv",
|
|
1704
|
+
prefix: str = "",
|
|
1705
|
+
)
|
|
1706
|
+
```
|
|
1707
|
+
|
|
1708
|
+
| Extra Method | Description |
|
|
1709
|
+
|--------------|-------------|
|
|
1710
|
+
| `async cleanup_expired() -> int` | Remove expired entries. Returns count removed. |
|
|
1711
|
+
| `async keys(pattern="*") -> list[str]` | List all keys matching a pattern. |
|
|
1712
|
+
|
|
1713
|
+
**Use case:** Enterprise deployments, existing PostgreSQL infrastructure.
|
|
1714
|
+
|
|
1715
|
+
#### Factory Function
|
|
1716
|
+
|
|
1717
|
+
```python
|
|
1718
|
+
def get_storage(backend: str = "memory", **kwargs) -> Storage
|
|
1719
|
+
```
|
|
1720
|
+
|
|
1721
|
+
Create a storage by name: `"memory"`, `"sqlite"`, `"redis"`, `"postgres"`.
|
|
1722
|
+
|
|
1723
|
+
#### EntityCache (Storage)
|
|
1724
|
+
|
|
1725
|
+
SQLite-backed peer resolution cache for caching user/chat entities.
|
|
1726
|
+
|
|
1727
|
+
```python
|
|
1728
|
+
EntityCache(storage: Optional[Storage] = None)
|
|
1729
|
+
```
|
|
1730
|
+
|
|
1731
|
+
| Method | Description |
|
|
1732
|
+
|--------|-------------|
|
|
1733
|
+
| `async start()` | Initialize the cache. |
|
|
1734
|
+
| `async get(entity_id) -> Optional[dict]` | Get entity by ID. |
|
|
1735
|
+
| `async get_by_username(username) -> Optional[dict]` | Get entity by username. |
|
|
1736
|
+
| `async get_by_phone(phone) -> Optional[dict]` | Get entity by phone. |
|
|
1737
|
+
| `async put(entity_id, access_hash, entity_type="user", *, username=None, phone=None)` | Store an entity. |
|
|
1738
|
+
| `async put_entity(entity)` | Store an entity from an object. |
|
|
1739
|
+
| `async put_many(entities) -> int` | Store multiple entities. Returns count stored. |
|
|
1740
|
+
| `async remove(entity_id) -> bool` | Remove an entity by ID. |
|
|
1741
|
+
| `async clear()` | Remove all cached entities. |
|
|
1742
|
+
| `async count() -> int` | Get the number of cached entities. |
|
|
1743
|
+
|
|
1744
|
+
---
|
|
1745
|
+
|
|
1746
|
+
### Middleware
|
|
1747
|
+
|
|
1748
|
+
#### Middleware (Abstract Base)
|
|
1749
|
+
|
|
1750
|
+
All middleware must implement `on_update`. The handler chain is executed in FIFO order.
|
|
1751
|
+
|
|
1752
|
+
```python
|
|
1753
|
+
class Middleware(ABC):
|
|
1754
|
+
@abstractmethod
|
|
1755
|
+
async def on_update(self, update: Any, handler: Handler) -> Any
|
|
1756
|
+
|
|
1757
|
+
async def on_startup(self) -> None # Called when client starts
|
|
1758
|
+
async def on_shutdown(self) -> None # Called when client stops
|
|
1759
|
+
```
|
|
1760
|
+
|
|
1761
|
+
#### MiddlewareManager
|
|
1762
|
+
|
|
1763
|
+
Manages an ordered chain of middleware. Errors in one middleware are isolated and don't break the chain.
|
|
1764
|
+
|
|
1765
|
+
```python
|
|
1766
|
+
MiddlewareManager()
|
|
1767
|
+
```
|
|
1768
|
+
|
|
1769
|
+
| Method | Description |
|
|
1770
|
+
|--------|-------------|
|
|
1771
|
+
| `add(mw: Middleware)` | Add middleware to the end of the chain. |
|
|
1772
|
+
| `remove(mw: Middleware)` | Remove middleware from the chain. |
|
|
1773
|
+
| `async execute(update, final_handler) -> Any` | Execute the middleware chain. |
|
|
1774
|
+
| `async startup()` | Notify all middleware of client startup. |
|
|
1775
|
+
| `async shutdown()` | Notify all middleware of client shutdown. |
|
|
1776
|
+
|
|
1777
|
+
#### RateLimitMiddleware
|
|
1778
|
+
|
|
1779
|
+
Rate limiting middleware for automatic flood wait handling.
|
|
1780
|
+
|
|
1781
|
+
```python
|
|
1782
|
+
RateLimitMiddleware(
|
|
1783
|
+
rate: float = 1.0,
|
|
1784
|
+
capacity: float = 1.0,
|
|
1785
|
+
flood_sleep_threshold: int = 60,
|
|
1786
|
+
)
|
|
1787
|
+
```
|
|
1788
|
+
|
|
1789
|
+
| Parameter | Type | Default | Description |
|
|
1790
|
+
|-----------|------|---------|-------------|
|
|
1791
|
+
| `rate` | `float` | `1.0` | Token refill rate (tokens per second). |
|
|
1792
|
+
| `capacity` | `float` | `1.0` | Maximum burst capacity. |
|
|
1793
|
+
| `flood_sleep_threshold` | `int` | `60` | Max seconds to sleep on flood wait. |
|
|
1794
|
+
|
|
1795
|
+
| Method | Description |
|
|
1796
|
+
|--------|-------------|
|
|
1797
|
+
| `async on_update(update, handler) -> Any` | Process update through rate limiter. |
|
|
1798
|
+
| `handle_flood_wait(method, seconds)` | Register a flood wait event. |
|
|
1799
|
+
| `get_stats() -> dict` | Get rate limiter statistics. |
|
|
1800
|
+
|
|
1801
|
+
#### Usage Example
|
|
1802
|
+
|
|
1803
|
+
```python
|
|
1804
|
+
from spluspy.middleware import Middleware
|
|
1805
|
+
|
|
1806
|
+
class LoggingMiddleware(Middleware):
|
|
1807
|
+
async def on_update(self, update, handler):
|
|
1808
|
+
print(f"Update: {update}")
|
|
1809
|
+
result = await handler(update)
|
|
1810
|
+
print("Handler completed")
|
|
1811
|
+
return result
|
|
1812
|
+
|
|
1813
|
+
bot.middleware.add(LoggingMiddleware())
|
|
1814
|
+
```
|
|
1815
|
+
|
|
1816
|
+
---
|
|
1817
|
+
|
|
1818
|
+
### Utils
|
|
1819
|
+
|
|
1820
|
+
#### LRUCache
|
|
1821
|
+
|
|
1822
|
+
Thread-safe LRU cache with optional TTL (time-to-live) for automatic expiration.
|
|
1823
|
+
|
|
1824
|
+
```python
|
|
1825
|
+
LRUCache(max_size: int = 1024, default_ttl: Optional[float] = None)
|
|
1826
|
+
```
|
|
1827
|
+
|
|
1828
|
+
| Method | Description |
|
|
1829
|
+
|--------|-------------|
|
|
1830
|
+
| `async get(key) -> Optional[V]` | Get a value by key. Returns `None` if not found or expired. |
|
|
1831
|
+
| `async set(key, value, ttl=None)` | Set a key-value pair. Optional per-entry TTL override. |
|
|
1832
|
+
| `async delete(key) -> bool` | Delete a key. Returns `True` if the key existed. |
|
|
1833
|
+
| `async exists(key) -> bool` | Check if a key exists and is not expired. |
|
|
1834
|
+
| `async clear()` | Remove all entries. |
|
|
1835
|
+
| `async size() -> int` | Get the current number of entries. |
|
|
1836
|
+
| `get_stats() -> dict` | Returns `size`, `max_size`, `hits`, `misses`, `hit_rate`. |
|
|
1837
|
+
|
|
1838
|
+
```python
|
|
1839
|
+
cache = LRUCache(max_size=1000, ttl=300) # 5-minute TTL
|
|
1840
|
+
await cache.set("key", "value")
|
|
1841
|
+
value = await cache.get("key")
|
|
1842
|
+
stats = cache.get_stats() # {'hits': 42, 'misses': 3, 'hit_rate': 0.933, ...}
|
|
1843
|
+
```
|
|
1844
|
+
|
|
1845
|
+
#### RateLimiter
|
|
1846
|
+
|
|
1847
|
+
Multi-endpoint rate limiter using the token bucket algorithm with flood wait handling.
|
|
1848
|
+
|
|
1849
|
+
```python
|
|
1850
|
+
RateLimiter(default_rate: float = 1.0, default_capacity: float = 1.0, flood_sleep_threshold: int = 60)
|
|
1851
|
+
```
|
|
1852
|
+
|
|
1853
|
+
| Method | Description |
|
|
1854
|
+
|--------|-------------|
|
|
1855
|
+
| `async acquire(method="default")` | Wait until a token is available for the given method. |
|
|
1856
|
+
| `register_flood_wait(method, seconds)` | Register a flood wait for the method (prevents requests until the wait expires). |
|
|
1857
|
+
| `limit(method="default", rate=None, capacity=None) -> Callable` | Decorator that rate-limits a function. |
|
|
1858
|
+
| `get_wait_time(method="default") -> float` | Get remaining wait time for the method (0 = ready). |
|
|
1859
|
+
| `clear_flood_wait(method)` | Clear the flood wait state for a method. |
|
|
1860
|
+
|
|
1861
|
+
#### TokenBucket
|
|
1862
|
+
|
|
1863
|
+
Low-level token bucket implementation.
|
|
1864
|
+
|
|
1865
|
+
```python
|
|
1866
|
+
TokenBucket(rate: float = 1.0, capacity: float = 1.0)
|
|
1867
|
+
```
|
|
1868
|
+
|
|
1869
|
+
| Method | Description |
|
|
1870
|
+
|--------|-------------|
|
|
1871
|
+
| `async acquire(tokens=1.0, blocking=True) -> bool` | Acquire tokens. If `blocking=True`, waits until available. Returns `True` on success. |
|
|
1872
|
+
|
|
1873
|
+
#### ChatLockManager
|
|
1874
|
+
|
|
1875
|
+
Per-chat granular locking to eliminate global lock contention in concurrent operations.
|
|
1876
|
+
|
|
1877
|
+
```python
|
|
1878
|
+
ChatLockManager()
|
|
1879
|
+
```
|
|
1880
|
+
|
|
1881
|
+
| Method | Description |
|
|
1882
|
+
|--------|-------------|
|
|
1883
|
+
| `acquire(key: int)` | Acquire a lock for a specific chat (async context manager). |
|
|
1884
|
+
| `release(key: int)` | Release the lock for a chat. |
|
|
1885
|
+
| `is_locked(key: int) -> bool` | Check if a chat is currently locked. |
|
|
1886
|
+
| `locked_count() -> int` | Number of currently locked chats. |
|
|
1887
|
+
| `pending_count() -> int` | Number of pending lock requests. |
|
|
1888
|
+
|
|
1889
|
+
#### Helper Functions
|
|
1890
|
+
|
|
1891
|
+
```python
|
|
1892
|
+
def generate_random_id() -> int
|
|
1893
|
+
# Generate a random 64-bit integer ID.
|
|
1894
|
+
|
|
1895
|
+
def timestamp_to_datetime(ts: Optional[int]) -> Optional[datetime]
|
|
1896
|
+
# Convert a Unix timestamp to a datetime object.
|
|
1897
|
+
|
|
1898
|
+
def datetime_to_timestamp(dt: Optional[Union[datetime, date]]) -> Optional[int]
|
|
1899
|
+
# Convert a datetime/date to a Unix timestamp.
|
|
1900
|
+
|
|
1901
|
+
def sanitize_filename(name: str) -> str
|
|
1902
|
+
# Remove or replace unsafe characters from a filename.
|
|
1903
|
+
|
|
1904
|
+
def parse_mode(mode: Optional[str]) -> Optional[str]
|
|
1905
|
+
# Normalize a parse mode string.
|
|
1906
|
+
|
|
1907
|
+
def chunk_list(items: list, size: int) -> list[list]
|
|
1908
|
+
# Split a list into chunks of the given size.
|
|
1909
|
+
|
|
1910
|
+
def truncate(text: str, max_length: int = 4096) -> str
|
|
1911
|
+
# Truncate text to a maximum length, adding "..." if truncated.
|
|
1912
|
+
```
|
|
1913
|
+
|
|
1914
|
+
#### Batch Operations
|
|
1915
|
+
|
|
1916
|
+
```python
|
|
1917
|
+
async def batch_send(client, chat_id, messages, *, delay=0.1, parse_mode=None) -> list[Message]
|
|
1918
|
+
# Send multiple text messages sequentially with a delay between each.
|
|
1919
|
+
|
|
1920
|
+
async def batch_delete(client, chat_id, messages, *, batch_size=100) -> bool
|
|
1921
|
+
# Delete multiple messages in batches.
|
|
1922
|
+
|
|
1923
|
+
async def batch_forward(client, target_chat, source_chat, messages, *, batch_size=100, delay=0.1) -> list[Message]
|
|
1924
|
+
# Forward multiple messages in batches.
|
|
1925
|
+
|
|
1926
|
+
async def batch_get_messages(client, chat_id, message_ids, *, batch_size=100) -> list[Message]
|
|
1927
|
+
# Fetch multiple messages by ID in batches.
|
|
1928
|
+
```
|
|
1929
|
+
|
|
1930
|
+
#### File Transfer
|
|
1931
|
+
|
|
1932
|
+
```python
|
|
1933
|
+
class TransferProgress:
|
|
1934
|
+
total: int # Total file size in bytes
|
|
1935
|
+
transferred: int # Bytes transferred so far
|
|
1936
|
+
speed: float # Transfer speed in bytes/sec
|
|
1937
|
+
elapsed: float # Elapsed time in seconds
|
|
1938
|
+
eta: float # Estimated time remaining in seconds
|
|
1939
|
+
percent -> float # Completion percentage (0-100)
|
|
1940
|
+
|
|
1941
|
+
class ProgressTracker:
|
|
1942
|
+
ProgressTracker(callback=None, update_interval=0.1)
|
|
1943
|
+
def update(self, chunk_size: int) -> None # Called after each chunk
|
|
1944
|
+
def finish(self) -> None # Called when transfer completes
|
|
1945
|
+
```
|
|
1946
|
+
|
|
1947
|
+
```python
|
|
1948
|
+
# Upload with progress
|
|
1949
|
+
tracker = ProgressTracker(on_progress=lambda p: print(f"{p.percent}%"))
|
|
1950
|
+
await bot.send_document(chat_id, "large_file.zip", progress=tracker)
|
|
1951
|
+
|
|
1952
|
+
# Download with progress
|
|
1953
|
+
await message.download(progress=tracker)
|
|
1954
|
+
```
|
|
1955
|
+
|
|
1956
|
+
#### TargetResolver
|
|
1957
|
+
|
|
1958
|
+
Resolves message targets from various sources (reply, username, ID, etc.).
|
|
1959
|
+
|
|
1960
|
+
```python
|
|
1961
|
+
TargetResolver(client: Client)
|
|
1962
|
+
```
|
|
1963
|
+
|
|
1964
|
+
| Method | Description |
|
|
1965
|
+
|--------|-------------|
|
|
1966
|
+
| `async resolve(message, *, args=None) -> TargetResolution` | Resolve a target from a message context. |
|
|
1967
|
+
|
|
1968
|
+
```python
|
|
1969
|
+
@dataclass
|
|
1970
|
+
class TargetResolution:
|
|
1971
|
+
user_id: Optional[int] # Resolved user ID
|
|
1972
|
+
message_id: Optional[int] # Resolved message ID
|
|
1973
|
+
source: str # Resolution source: "reply", "username", "id", etc.
|
|
1974
|
+
display_name: Optional[str] # Human-readable name
|
|
1975
|
+
```
|
|
1976
|
+
|
|
1977
|
+
```python
|
|
1978
|
+
def resolve_reply_sender(message: Message) -> Optional[int]
|
|
1979
|
+
# Synchronously extract the sender ID from a reply header.
|
|
1980
|
+
```
|
|
1981
|
+
|
|
1982
|
+
#### Logger Utilities
|
|
1983
|
+
|
|
1984
|
+
```python
|
|
1985
|
+
def setup_logging(level="INFO", log_file=None, log_dir="logs", max_bytes=10*1024*1024,
|
|
1986
|
+
backup_count=5, use_color=True, console_output=True,
|
|
1987
|
+
reconnect_only=False, errors_only=False) -> None
|
|
1988
|
+
# Configure the logging system.
|
|
1989
|
+
|
|
1990
|
+
def get_logger(name=None, level=None) -> logging.Logger
|
|
1991
|
+
# Get a named logger instance.
|
|
1992
|
+
|
|
1993
|
+
def set_level(level: Union[str, int]) -> None
|
|
1994
|
+
# Change the global log level.
|
|
1995
|
+
|
|
1996
|
+
def log() -> logging.Logger
|
|
1997
|
+
# Get the default root logger.
|
|
1998
|
+
|
|
1999
|
+
def log_event(logger, event: str, **data) -> None
|
|
2000
|
+
# Log a structured event.
|
|
2001
|
+
|
|
2002
|
+
def log_request(logger, method: str, **params) -> None
|
|
2003
|
+
# Log an API request.
|
|
2004
|
+
|
|
2005
|
+
def log_response(logger, method: str, status="OK", **data) -> None
|
|
2006
|
+
# Log an API response.
|
|
2007
|
+
|
|
2008
|
+
def log_error(logger, error: Exception, context="") -> None
|
|
2009
|
+
# Log an error with context.
|
|
2010
|
+
|
|
2011
|
+
def log_user_action(logger, action: str, user_id: int, chat_id=0, target_id=0, detail="") -> None
|
|
2012
|
+
# Log a user action.
|
|
2013
|
+
|
|
2014
|
+
def log_security(logger, event: str, user_id: int, chat_id=0, reason="") -> None
|
|
2015
|
+
# Log a security-related event.
|
|
2016
|
+
|
|
2017
|
+
def log_performance(logger, operation: str, duration_ms: float, **extra) -> None
|
|
2018
|
+
# Log a performance measurement.
|
|
2019
|
+
```
|
|
2020
|
+
|
|
2021
|
+
#### Version Check
|
|
2022
|
+
|
|
2023
|
+
```python
|
|
2024
|
+
async def check_for_update() -> Optional[str]
|
|
2025
|
+
# Check PyPI for a newer version. Returns version string or None.
|
|
2026
|
+
|
|
2027
|
+
def print_update_notice(new_version: str) -> None
|
|
2028
|
+
# Print a formatted update notice to the console.
|
|
2029
|
+
|
|
2030
|
+
async def check_and_notify() -> None
|
|
2031
|
+
# Check and print update notice if available. Call at startup.
|
|
2032
|
+
```
|
|
2033
|
+
|
|
2034
|
+
---
|
|
2035
|
+
|
|
2036
|
+
### AfkManager
|
|
2037
|
+
|
|
2038
|
+
Smart auto-responder for AFK (Away From Keyboard) mode with per-chat rate limiting.
|
|
2039
|
+
|
|
2040
|
+
#### Constructor
|
|
2041
|
+
|
|
2042
|
+
```python
|
|
2043
|
+
AfkManager(
|
|
2044
|
+
client: Client,
|
|
2045
|
+
message: str = "I'm currently AFK. I'll reply when I'm back.",
|
|
2046
|
+
*,
|
|
2047
|
+
cooldown: float = 60.0,
|
|
2048
|
+
max_replies: Optional[int] = 10,
|
|
2049
|
+
only_private: bool = False,
|
|
2050
|
+
only_mentions: bool = False,
|
|
2051
|
+
)
|
|
2052
|
+
```
|
|
2053
|
+
|
|
2054
|
+
| Parameter | Type | Default | Description |
|
|
2055
|
+
|-----------|------|---------|-------------|
|
|
2056
|
+
| `client` | `Client` | (required) | The SplusPy client instance. |
|
|
2057
|
+
| `message` | `str` | auto-reply text | Default AFK response message. |
|
|
2058
|
+
| `cooldown` | `float` | `60.0` | Minimum seconds between replies to the same user. |
|
|
2059
|
+
| `max_replies` | `int \| None` | `10` | Maximum total auto-replies per AFK session. `None` = unlimited. |
|
|
2060
|
+
| `only_private` | `bool` | `False` | If `True`, only auto-reply in private chats. |
|
|
2061
|
+
| `only_mentions` | `bool` | `False` | If `True`, only auto-reply when mentioned. |
|
|
2062
|
+
|
|
2063
|
+
#### Properties
|
|
2064
|
+
|
|
2065
|
+
| Property | Type | Description |
|
|
2066
|
+
|----------|------|-------------|
|
|
2067
|
+
| `is_afk` | `bool` | Whether AFK mode is currently active. |
|
|
2068
|
+
| `afk_reason` | `str` | The reason for being AFK. |
|
|
2069
|
+
| `afk_since` | `float \| None` | Unix timestamp when AFK mode started. |
|
|
2070
|
+
| `total_replies` | `int` | Total number of auto-replies sent during this AFK session. |
|
|
2071
|
+
|
|
2072
|
+
#### Methods
|
|
2073
|
+
|
|
2074
|
+
##### `set_afk(enabled, *, reason="", message=None) -> None`
|
|
2075
|
+
|
|
2076
|
+
Enable or disable AFK mode.
|
|
2077
|
+
|
|
2078
|
+
| Parameter | Type | Default | Description |
|
|
2079
|
+
|-----------|------|---------|-------------|
|
|
2080
|
+
| `enabled` | `bool` | (required) | `True` to activate AFK, `False` to deactivate. |
|
|
2081
|
+
| `reason` | `str` | `""` | Reason for being AFK (shown in the auto-reply). |
|
|
2082
|
+
| `message` | `str \| None` | `None` | Override the default AFK message for this session. |
|
|
2083
|
+
|
|
2084
|
+
##### `async handle(message) -> bool`
|
|
2085
|
+
|
|
2086
|
+
Process an incoming message while AFK. Handles rate limiting and auto-reply. Returns `True` if a reply was sent.
|
|
2087
|
+
|
|
2088
|
+
##### `get_stats() -> dict`
|
|
2089
|
+
|
|
2090
|
+
Returns a dictionary with keys: `is_afk`, `afk_reason`, `afk_since`, `total_replies`, `active_chats`, `cooldown`, `max_replies`.
|
|
2091
|
+
|
|
2092
|
+
#### Usage Example
|
|
2093
|
+
|
|
2094
|
+
```python
|
|
2095
|
+
from spluspy import Client, filters
|
|
2096
|
+
from spluspy.afk import AfkManager
|
|
2097
|
+
|
|
2098
|
+
bot = Client("my_account")
|
|
2099
|
+
afk = AfkManager(bot, message="I'm currently AFK. Back soon!")
|
|
2100
|
+
|
|
2101
|
+
@bot.on_message(filters.command("afk"))
|
|
2102
|
+
async def set_afk(client, message):
|
|
2103
|
+
afk.set_afk(True, reason="Lunch break")
|
|
2104
|
+
await message.reply("I'm now AFK!")
|
|
2105
|
+
|
|
2106
|
+
@bot.on_message(filters.command("back"))
|
|
2107
|
+
async def unset_afk(client, message):
|
|
2108
|
+
afk.set_afk(False)
|
|
2109
|
+
await message.reply(f"Back! Sent {afk.total_replies} auto-replies.")
|
|
2110
|
+
|
|
2111
|
+
@bot.on_message(filters.private & filters.incoming)
|
|
2112
|
+
async def auto_reply(client, message):
|
|
2113
|
+
if afk.is_afk:
|
|
2114
|
+
await afk.handle(message)
|
|
2115
|
+
```
|
|
2116
|
+
|
|
2117
|
+
---
|
|
2118
|
+
|
|
2119
|
+
### ChatAdmin
|
|
2120
|
+
|
|
2121
|
+
High-level chat administration API with batch operations and admin logging.
|
|
2122
|
+
|
|
2123
|
+
#### Constructor
|
|
2124
|
+
|
|
2125
|
+
```python
|
|
2126
|
+
ChatAdmin(client: Client)
|
|
2127
|
+
```
|
|
2128
|
+
|
|
2129
|
+
#### Methods
|
|
2130
|
+
|
|
2131
|
+
##### `async ban_user(chat_id, user_id, *, delete_messages=False) -> bool`
|
|
2132
|
+
|
|
2133
|
+
Ban a user from a chat. Optionally delete their recent messages.
|
|
2134
|
+
|
|
2135
|
+
##### `async unban_user(chat_id, user_id) -> bool`
|
|
2136
|
+
|
|
2137
|
+
Unban a user, allowing them to rejoin.
|
|
2138
|
+
|
|
2139
|
+
##### `async mute_user(chat_id, user_id, *, duration=None) -> bool`
|
|
2140
|
+
|
|
2141
|
+
Mute a user. `duration` in seconds; `None` = permanent.
|
|
2142
|
+
|
|
2143
|
+
##### `async unmute_user(chat_id, user_id) -> bool`
|
|
2144
|
+
|
|
2145
|
+
Unmute a user.
|
|
2146
|
+
|
|
2147
|
+
##### `async pin_chat_message(chat_id, message_id, notify=True) -> bool`
|
|
2148
|
+
|
|
2149
|
+
Pin a message by ID.
|
|
2150
|
+
|
|
2151
|
+
##### `async unpin_chat_message(chat_id, message_id) -> bool`
|
|
2152
|
+
|
|
2153
|
+
Unpin a message by ID.
|
|
2154
|
+
|
|
2155
|
+
##### `async pin_message(chat_id, message, *, notify=False) -> bool`
|
|
2156
|
+
|
|
2157
|
+
Pin a message (accepts `Message` object or int).
|
|
2158
|
+
|
|
2159
|
+
##### `async unpin_message(chat_id, message) -> bool`
|
|
2160
|
+
|
|
2161
|
+
Unpin a message (accepts `Message` object or int).
|
|
2162
|
+
|
|
2163
|
+
##### `async unpin_all(chat_id) -> bool`
|
|
2164
|
+
|
|
2165
|
+
Unpin all pinned messages in a chat.
|
|
2166
|
+
|
|
2167
|
+
##### `async purge_messages(chat_id, *, limit=100, before=None, after=None) -> int`
|
|
2168
|
+
|
|
2169
|
+
Delete a batch of messages. Returns the count of deleted messages.
|
|
2170
|
+
|
|
2171
|
+
| Parameter | Type | Default | Description |
|
|
2172
|
+
|-----------|------|---------|-------------|
|
|
2173
|
+
| `chat_id` | `int` | (required) | Target chat. |
|
|
2174
|
+
| `limit` | `int` | `100` | Maximum messages to delete. |
|
|
2175
|
+
| `before` | `int \| None` | `None` | Only delete messages with ID less than this. |
|
|
2176
|
+
| `after` | `int \| None` | `None` | Only delete messages with ID greater than this. |
|
|
2177
|
+
|
|
2178
|
+
##### `async get_admin_log(chat_id, *, limit=100) -> list[dict]`
|
|
2179
|
+
|
|
2180
|
+
Get admin event log. Returns list of dicts with `id`, `date`, `user_id`, `action` keys.
|
|
2181
|
+
|
|
2182
|
+
##### `async bulk_action(chat_id, action, user_ids, *, delay=0.5, **kwargs) -> dict[int, bool]`
|
|
2183
|
+
|
|
2184
|
+
Perform bulk ban/unban/mute/unmute. Returns a dict mapping `user_id -> success`.
|
|
2185
|
+
|
|
2186
|
+
| Parameter | Type | Description |
|
|
2187
|
+
|-----------|------|-------------|
|
|
2188
|
+
| `action` | `str` | One of: `"ban"`, `"unban"`, `"mute"`, `"unmute"`. |
|
|
2189
|
+
| `user_ids` | `list[int]` | List of user IDs to action. |
|
|
2190
|
+
| `delay` | `float` | Delay between each action (to avoid flood). |
|
|
2191
|
+
|
|
2192
|
+
---
|
|
2193
|
+
|
|
2194
|
+
### MessageMirror
|
|
2195
|
+
|
|
2196
|
+
Real-time message cloning between chats.
|
|
2197
|
+
|
|
2198
|
+
#### Constructor
|
|
2199
|
+
|
|
2200
|
+
```python
|
|
2201
|
+
MessageMirror(client: Client)
|
|
2202
|
+
```
|
|
2203
|
+
|
|
2204
|
+
#### Methods
|
|
2205
|
+
|
|
2206
|
+
##### `add_route(source, targets, *, filter_func=None, strip_forward=True, strip_sender=False, add_prefix=None) -> None`
|
|
2207
|
+
|
|
2208
|
+
Add a complete mirroring route from a source chat to one or more target chats.
|
|
2209
|
+
|
|
2210
|
+
| Parameter | Type | Default | Description |
|
|
2211
|
+
|-----------|------|---------|-------------|
|
|
2212
|
+
| `source` | `int` | (required) | Source chat ID to mirror from. |
|
|
2213
|
+
| `targets` | `list[int]` | (required) | Target chat IDs to mirror to. |
|
|
2214
|
+
| `filter_func` | `Callable \| None` | `None` | Optional filter function: `fn(message) -> bool`. |
|
|
2215
|
+
| `strip_forward` | `bool` | `True` | Remove forward headers from mirrored messages. |
|
|
2216
|
+
| `strip_sender` | `bool` | `False` | Remove sender info from mirrored messages. |
|
|
2217
|
+
| `add_prefix` | `str \| None` | `None` | Add a text prefix to mirrored messages. |
|
|
2218
|
+
|
|
2219
|
+
##### `add_source(source) -> None`
|
|
2220
|
+
|
|
2221
|
+
Add a source chat for mirroring.
|
|
2222
|
+
|
|
2223
|
+
##### `add_target(target, *, source=None) -> None`
|
|
2224
|
+
|
|
2225
|
+
Add a target chat. If `source` is `None`, targets the most recently added source.
|
|
2226
|
+
|
|
2227
|
+
##### `remove_route(source) -> None`
|
|
2228
|
+
|
|
2229
|
+
Remove a mirroring route by source chat ID.
|
|
2230
|
+
|
|
2231
|
+
##### `async start() -> None`
|
|
2232
|
+
|
|
2233
|
+
Start the mirroring engine. Begins listening for new messages.
|
|
2234
|
+
|
|
2235
|
+
##### `async stop() -> None`
|
|
2236
|
+
|
|
2237
|
+
Stop the mirroring engine.
|
|
2238
|
+
|
|
2239
|
+
##### `get_stats() -> dict`
|
|
2240
|
+
|
|
2241
|
+
Returns: `active` (bool), `routes` (int), `total_mirrored` (int), `sources` (list of ints).
|
|
2242
|
+
|
|
2243
|
+
#### Usage Example
|
|
2244
|
+
|
|
2245
|
+
```python
|
|
2246
|
+
from spluspy.mirror import MessageMirror
|
|
2247
|
+
|
|
2248
|
+
mirror = MessageMirror(bot)
|
|
2249
|
+
|
|
2250
|
+
mirror.add_route(
|
|
2251
|
+
source=-1001234567890,
|
|
2252
|
+
targets=[-1009876543210, -1001112223334],
|
|
2253
|
+
strip_forward=True,
|
|
2254
|
+
strip_sender=False,
|
|
2255
|
+
add_prefix="[Mirror]"
|
|
2256
|
+
)
|
|
2257
|
+
|
|
2258
|
+
await mirror.start()
|
|
2259
|
+
```
|
|
2260
|
+
|
|
2261
|
+
---
|
|
2262
|
+
|
|
2263
|
+
### Scheduler
|
|
2264
|
+
|
|
2265
|
+
#### Scheduler (Low-level)
|
|
2266
|
+
|
|
2267
|
+
Manages scheduled and recurring tasks.
|
|
2268
|
+
|
|
2269
|
+
```python
|
|
2270
|
+
Scheduler()
|
|
2271
|
+
```
|
|
2272
|
+
|
|
2273
|
+
| Property | Type | Description |
|
|
2274
|
+
|----------|------|-------------|
|
|
2275
|
+
| `running` | `bool` | Whether the scheduler is running. |
|
|
2276
|
+
|
|
2277
|
+
| Method | Description |
|
|
2278
|
+
|--------|-------------|
|
|
2279
|
+
| `add(name, callback, *, interval=None, delay=0.0, max_runs=None) -> ScheduledTask` | Schedule a task. |
|
|
2280
|
+
| `remove(name) -> bool` | Remove a task by name. |
|
|
2281
|
+
| `get(name) -> Optional[ScheduledTask]` | Get a task by name. |
|
|
2282
|
+
| `async start()` | Start the scheduler loop. |
|
|
2283
|
+
| `async stop()` | Stop the scheduler loop. |
|
|
2284
|
+
|
|
2285
|
+
#### MessageScheduler
|
|
2286
|
+
|
|
2287
|
+
High-level message scheduling for bots.
|
|
2288
|
+
|
|
2289
|
+
```python
|
|
2290
|
+
MessageScheduler(client: Client)
|
|
2291
|
+
```
|
|
2292
|
+
|
|
2293
|
+
| Property | Type | Description |
|
|
2294
|
+
|----------|------|-------------|
|
|
2295
|
+
| `pending_count` | `int` | Number of pending scheduled tasks. |
|
|
2296
|
+
|
|
2297
|
+
| Method | Description |
|
|
2298
|
+
|--------|-------------|
|
|
2299
|
+
| `async start()` | Start the scheduler. |
|
|
2300
|
+
| `async stop()` | Stop the scheduler. |
|
|
2301
|
+
| `schedule_message(chat_id, text, *, delay=None, when=None, name=None) -> ScheduledTask` | Schedule a single message. |
|
|
2302
|
+
| `schedule_interval(chat_id, text, *, interval, name=None, max_runs=None) -> ScheduledTask` | Schedule a recurring message. |
|
|
2303
|
+
| `schedule_callback(name, callback, *, delay=None, interval=None, max_runs=None) -> ScheduledTask` | Schedule a custom callback. |
|
|
2304
|
+
| `cancel(name) -> bool` | Cancel a scheduled task by name. |
|
|
2305
|
+
|
|
2306
|
+
#### Usage Example
|
|
2307
|
+
|
|
2308
|
+
```python
|
|
2309
|
+
from spluspy.scheduler.scheduler import MessageScheduler
|
|
2310
|
+
|
|
2311
|
+
scheduler = MessageScheduler(bot)
|
|
2312
|
+
|
|
2313
|
+
# Send a message every hour
|
|
2314
|
+
scheduler.schedule_interval("hourly_greeting", chat_id, "Hello!", interval=3600)
|
|
2315
|
+
|
|
2316
|
+
# Send a message after a delay (5 minutes)
|
|
2317
|
+
scheduler.schedule_once("reminder", chat_id, "Don't forget!", delay=300)
|
|
2318
|
+
|
|
2319
|
+
# Cancel a scheduled task
|
|
2320
|
+
scheduler.cancel("hourly_greeting")
|
|
2321
|
+
```
|
|
2322
|
+
|
|
2323
|
+
---
|
|
2324
|
+
|
|
2325
|
+
### Error Hierarchy
|
|
2326
|
+
|
|
2327
|
+
All errors inherit from `SplusPyError(Exception)`.
|
|
2328
|
+
|
|
2329
|
+
```
|
|
2330
|
+
SplusPyError
|
|
2331
|
+
├── SoroushPlusAPIError(message, code=0)
|
|
2332
|
+
│ ├── RPCError(message, code=0)
|
|
2333
|
+
│ │ ├── FloodWait(seconds, message="") # Has .seconds attribute
|
|
2334
|
+
│ │ ├── Unauthorized(message, code=0)
|
|
2335
|
+
│ │ ├── Forbidden(message, code=0)
|
|
2336
|
+
│ │ ├── BadRequest(message, code=0)
|
|
2337
|
+
│ │ │ ├── ChatNotFound(message, code=0)
|
|
2338
|
+
│ │ │ ├── UserNotFound(message, code=0)
|
|
2339
|
+
│ │ │ └── MessageNotFound(message, code=0)
|
|
2340
|
+
│ │ └── ...
|
|
2341
|
+
│ ├── SessionExpiredError(message)
|
|
2342
|
+
│ └── SessionError(message)
|
|
2343
|
+
├── AuthError(message)
|
|
2344
|
+
├── ValidationError(message)
|
|
2345
|
+
├── TimeoutError(message)
|
|
2346
|
+
├── ConnectionError(message)
|
|
2347
|
+
├── PluginError(message)
|
|
2348
|
+
├── FSMError(message)
|
|
2349
|
+
├── StorageError(message)
|
|
2350
|
+
├── JoinChatError(message, link="")
|
|
2351
|
+
│ ├── InvalidInviteLinkError(link, reason="...")
|
|
2352
|
+
│ ├── InviteLinkExpiredError(link="", reason="...")
|
|
2353
|
+
│ ├── ChatFullError(chat_id="", limit=0)
|
|
2354
|
+
│ ├── ChatDeactivatedError(chat_id="")
|
|
2355
|
+
│ └── MembershipRequiredError(chat_id="", reason="...")
|
|
2356
|
+
└── FloodWaitError(message) # From rate limiter
|
|
2357
|
+
```
|
|
2358
|
+
|
|
2359
|
+
#### Error Handling Example
|
|
2360
|
+
|
|
2361
|
+
```python
|
|
2362
|
+
from spluspy.errors import FloodWait, Unauthorized, BadRequest
|
|
2363
|
+
|
|
2364
|
+
@bot.on_message()
|
|
2365
|
+
async def safe_handler(client, message):
|
|
2366
|
+
try:
|
|
2367
|
+
await message.reply("Hello!")
|
|
2368
|
+
except FloodWait as e:
|
|
2369
|
+
await asyncio.sleep(e.seconds) # Wait the required time
|
|
2370
|
+
except Unauthorized:
|
|
2371
|
+
await message.reply("Not authorized!")
|
|
2372
|
+
except BadRequest as e:
|
|
2373
|
+
print(f"Bad request: {e}")
|
|
2374
|
+
```
|
|
2375
|
+
|
|
2376
|
+
#### Global Error Handler
|
|
2377
|
+
|
|
2378
|
+
```python
|
|
2379
|
+
from spluspy.events import ErrorHandlerBuilder
|
|
2380
|
+
|
|
2381
|
+
error_handler = ErrorHandlerBuilder()
|
|
2382
|
+
error_handler.on(FloodWait)(lambda e: print(f"Flood wait: {e.seconds}s"))
|
|
2383
|
+
error_handler.on(Unauthorized)(lambda e: print("Unauthorized"))
|
|
2384
|
+
|
|
2385
|
+
bot.on_error(error_handler.build())
|
|
2386
|
+
```
|
|
2387
|
+
|
|
2388
|
+
---
|
|
2389
|
+
|
|
2390
|
+
### Plugin System
|
|
2391
|
+
|
|
2392
|
+
#### Plugin
|
|
2393
|
+
|
|
2394
|
+
Represents a loaded plugin.
|
|
2395
|
+
|
|
2396
|
+
```python
|
|
2397
|
+
Plugin(name: str, module: Any, enabled: bool = True)
|
|
2398
|
+
```
|
|
2399
|
+
|
|
2400
|
+
| Attribute | Type | Description |
|
|
2401
|
+
|-----------|------|-------------|
|
|
2402
|
+
| `name` | `str` | Plugin name (derived from module filename). |
|
|
2403
|
+
| `module` | `Any` | The loaded Python module. |
|
|
2404
|
+
| `enabled` | `bool` | Whether the plugin is enabled. |
|
|
2405
|
+
|
|
2406
|
+
#### PluginManager
|
|
2407
|
+
|
|
2408
|
+
Discovers, loads, and manages plugins.
|
|
2409
|
+
|
|
2410
|
+
```python
|
|
2411
|
+
PluginManager(client: Client)
|
|
2412
|
+
```
|
|
2413
|
+
|
|
2414
|
+
| Property | Type | Description |
|
|
2415
|
+
|----------|------|-------------|
|
|
2416
|
+
| `plugins` | `dict[str, Plugin]` | Mapping of loaded plugins by name. |
|
|
2417
|
+
|
|
2418
|
+
| Method | Description |
|
|
2419
|
+
|--------|-------------|
|
|
2420
|
+
| `load(path) -> list[Plugin]` | Load all plugins from a directory or single module file. |
|
|
2421
|
+
| `unload(name) -> bool` | Unload a plugin by name. Returns `True` if unloaded. |
|
|
2422
|
+
| `reload(name) -> Optional[Plugin]` | Reload a plugin. Returns the reloaded `Plugin` or `None`. |
|
|
2423
|
+
|
|
2424
|
+
#### Plugin Structure
|
|
2425
|
+
|
|
2426
|
+
Plugins are Python modules that define a `register(client)` function:
|
|
2427
|
+
|
|
2428
|
+
```python
|
|
2429
|
+
# plugins/hello.py
|
|
2430
|
+
def register(client):
|
|
2431
|
+
@client.on_message(filters.command("hello"))
|
|
2432
|
+
async def hello_handler(client, message):
|
|
2433
|
+
await message.reply("Hello from plugin!")
|
|
2434
|
+
```
|
|
2435
|
+
|
|
2436
|
+
```python
|
|
2437
|
+
# main.py
|
|
2438
|
+
from spluspy import Client
|
|
2439
|
+
|
|
2440
|
+
bot = Client("session")
|
|
2441
|
+
bot.plugins.load("plugins") # Load all plugins from the "plugins" directory
|
|
2442
|
+
bot.run()
|
|
2443
|
+
```
|
|
2444
|
+
|
|
2445
|
+
---
|
|
2446
|
+
|
|
2447
|
+
### Sync Wrapper
|
|
2448
|
+
|
|
2449
|
+
The `spluspy.sync` module provides a synchronous wrapper around the async `Client`. Every async method is wrapped so it can be called without `await`.
|
|
2450
|
+
|
|
2451
|
+
```python
|
|
2452
|
+
from spluspy.sync import Client
|
|
2453
|
+
|
|
2454
|
+
bot = Client("session")
|
|
2455
|
+
|
|
2456
|
+
@bot.on_message()
|
|
2457
|
+
def handler(client, message):
|
|
2458
|
+
# No async/await needed!
|
|
2459
|
+
message.reply("Hello!")
|
|
2460
|
+
|
|
2461
|
+
bot.run()
|
|
2462
|
+
```
|
|
2463
|
+
|
|
2464
|
+
The sync `Client` has the same constructor and all the same methods as the async `Client`, but without `async/await`:
|
|
2465
|
+
|
|
2466
|
+
```python
|
|
2467
|
+
# Lifecycle
|
|
2468
|
+
bot.start()
|
|
2469
|
+
bot.stop()
|
|
2470
|
+
bot.connect()
|
|
2471
|
+
bot.disconnect()
|
|
2472
|
+
bot.run()
|
|
2473
|
+
|
|
2474
|
+
# Messages (no await!)
|
|
2475
|
+
bot.send_message(chat_id, "Hello!")
|
|
2476
|
+
bot.edit_message(message, text="New text")
|
|
2477
|
+
bot.delete_messages(msg1, msg2)
|
|
2478
|
+
|
|
2479
|
+
# All other methods work the same way
|
|
2480
|
+
bot.get_me()
|
|
2481
|
+
bot.get_messages(chat_id)
|
|
2482
|
+
bot.ban_user(chat_id, user_id)
|
|
2483
|
+
```
|
|
2484
|
+
|
|
2485
|
+
---
|
|
2486
|
+
|
|
2487
|
+
### Enums
|
|
2488
|
+
|
|
2489
|
+
```python
|
|
2490
|
+
class ChatType(str, Enum):
|
|
2491
|
+
PRIVATE = "private"
|
|
2492
|
+
GROUP = "group"
|
|
2493
|
+
SUPERGROUP = "supergroup"
|
|
2494
|
+
CHANNEL = "channel"
|
|
2495
|
+
|
|
2496
|
+
class MessageMediaType(str, Enum):
|
|
2497
|
+
NONE = "none"
|
|
2498
|
+
PHOTO = "photo"
|
|
2499
|
+
VIDEO = "video"
|
|
2500
|
+
AUDIO = "audio"
|
|
2501
|
+
DOCUMENT = "document"
|
|
2502
|
+
VOICE = "voice"
|
|
2503
|
+
VIDEO_NOTE = "video_note"
|
|
2504
|
+
STICKER = "sticker"
|
|
2505
|
+
ANIMATION = "animation"
|
|
2506
|
+
CONTACT = "contact"
|
|
2507
|
+
LOCATION = "location"
|
|
2508
|
+
POLL = "poll"
|
|
2509
|
+
DICE = "dice"
|
|
2510
|
+
|
|
2511
|
+
class ParseMode(str, Enum):
|
|
2512
|
+
NONE = "none"
|
|
2513
|
+
MARKDOWN = "markdown"
|
|
2514
|
+
MARKDOWN_V2 = "markdown_v2"
|
|
2515
|
+
HTML = "html"
|
|
2516
|
+
|
|
2517
|
+
class UserStatus(str, Enum):
|
|
2518
|
+
ONLINE = "online"
|
|
2519
|
+
OFFLINE = "offline"
|
|
2520
|
+
LAST_SEEN = "last_seen"
|
|
2521
|
+
RECENTLY = "recently"
|
|
2522
|
+
LAST_WEEK = "last_week"
|
|
2523
|
+
LAST_MONTH = "last_month"
|
|
2524
|
+
LONG_AGO = "long_ago"
|
|
2525
|
+
EMPTY = "empty"
|
|
2526
|
+
|
|
2527
|
+
class ChatMemberStatus(IntEnum):
|
|
2528
|
+
CREATOR = 4
|
|
2529
|
+
ADMIN = 3
|
|
2530
|
+
MEMBER = 2
|
|
2531
|
+
RESTRICTED = 1
|
|
2532
|
+
LEFT = 0
|
|
2533
|
+
BANNED = -1
|
|
2534
|
+
```
|
|
2535
|
+
|
|
2536
|
+
---
|
|
2537
|
+
|
|
2538
|
+
### Media Types
|
|
2539
|
+
|
|
2540
|
+
All media types are dataclasses with `slots=True`, inheriting from `Media`.
|
|
2541
|
+
|
|
2542
|
+
| Class | Key Attributes |
|
|
2543
|
+
|-------|----------------|
|
|
2544
|
+
| `Media` | `media_type`, `raw` |
|
|
2545
|
+
| `Photo` | `width`, `height`, `file_id`, `file_size`, `file_name` |
|
|
2546
|
+
| `Video` | `width`, `height`, `duration`, `file_id`, `file_size`, `file_name`, `thumb` |
|
|
2547
|
+
| `Audio` | `duration`, `performer`, `title`, `file_id`, `file_size`, `file_name` |
|
|
2548
|
+
| `Document` | `file_id`, `file_size`, `file_name`, `mime_type` |
|
|
2549
|
+
| `Voice` | `duration`, `file_id`, `file_size`, `mime_type` |
|
|
2550
|
+
| `VideoNote` | `duration`, `file_id`, `file_size`, `mime_type` |
|
|
2551
|
+
| `Sticker` | `sticker_id`, `emoji`, `set_name`, `width`, `height`, `is_animated` |
|
|
2552
|
+
| `Animation` | `file_name`, `width`, `height`, `duration`, `file_id`, `file_size` |
|
|
2553
|
+
| `Contact` | `phone_number`, `first_name`, `last_name`, `user_id` |
|
|
2554
|
+
| `Location` | `latitude`, `longitude` |
|
|
2555
|
+
| `PollMedia` / `Poll` | `question`, `options`, `is_anonymous`, `total_voter_count` |
|
|
2556
|
+
| `Dice` | `emoji`, `value` |
|
|
2557
|
+
|
|
2558
|
+
---
|
|
2559
|
+
|
|
2560
|
+
### Object Types
|
|
2561
|
+
|
|
2562
|
+
| Class | Attributes |
|
|
2563
|
+
|-------|------------|
|
|
2564
|
+
| `MessageEntity` | `type`, `offset`, `length`, `url`, `user_id`, `language`, `custom_emoji_id` |
|
|
2565
|
+
| `InlineKeyboardButton` | `text`, `callback_data`, `url`, `switch_inline_query`, `switch_inline_query_current_chat` |
|
|
2566
|
+
| `KeyboardButton` | `text`, `request_contact`, `request_location` |
|
|
2567
|
+
| `ReplyMarkup` | `inline_keyboard`, `keyboard`, `is_one_time`, `is_resize`, `selective`, `placeholder` |
|
|
2568
|
+
| `ForceReply` | `selective`, `placeholder` |
|
|
2569
|
+
| `MessageReplyHeader` | `reply_to_msg_id`, `reply_to_peer_id`, `quote` |
|
|
2570
|
+
|
|
2571
|
+
---
|
|
2572
|
+
|
|
2573
|
+
### Project Structure
|
|
2574
|
+
|
|
2575
|
+
```
|
|
2576
|
+
spluspy/
|
|
2577
|
+
├── __init__.py # Public API
|
|
2578
|
+
├── __version__.py # Version info
|
|
2579
|
+
├── cli.py # CLI entry point
|
|
2580
|
+
├── config.py # Configuration
|
|
2581
|
+
├── compat.py # Compatibility layer
|
|
2582
|
+
├── afk.py # AFK auto-responder
|
|
2583
|
+
├── admin.py # Chat administration
|
|
2584
|
+
├── mirror.py # Message mirroring engine
|
|
2585
|
+
├── client/ # Client and conversation API
|
|
2586
|
+
│ ├── client.py # Main Client class
|
|
2587
|
+
│ ├── conversation.py # Conversation API
|
|
2588
|
+
│ ├── chat_mixin.py # Chat management mixin
|
|
2589
|
+
│ └── media_mixin.py # Media operations mixin
|
|
2590
|
+
├── models/ # Domain models (Message, User, Chat, Media, etc.)
|
|
2591
|
+
│ ├── message.py # Message dataclass
|
|
2592
|
+
│ ├── user.py # User dataclass
|
|
2593
|
+
│ ├── chat.py # Chat, Channel, Group dataclasses
|
|
2594
|
+
│ ├── bot.py # Button factory
|
|
2595
|
+
│ ├── media.py # Media type dataclasses
|
|
2596
|
+
│ ├── objects.py # MessageEntity, ReplyMarkup, etc.
|
|
2597
|
+
│ └── enums.py # ChatType, UserStatus, etc.
|
|
2598
|
+
├── events/ # Event types and builders
|
|
2599
|
+
├── filters/ # Composable message filters
|
|
2600
|
+
├── errors/ # Custom exception hierarchy
|
|
2601
|
+
├── session/ # Session backends (SQLite, Memory, String)
|
|
2602
|
+
├── network/ # TCP connections and connection pool
|
|
2603
|
+
├── storage/ # Key-value storage backends
|
|
2604
|
+
├── plugins/ # Plugin loader
|
|
2605
|
+
├── middleware/ # Middleware system
|
|
2606
|
+
├── fsm/ # Finite state machine
|
|
2607
|
+
├── scheduler/ # Task scheduler
|
|
2608
|
+
├── utils/ # Logger, cache, helpers
|
|
2609
|
+
├── sync/ # Synchronous client wrapper
|
|
2610
|
+
└── _engine/ # Low-level MTProto engine
|
|
2611
|
+
```
|
|
2612
|
+
|
|
2613
|
+
---
|
|
2614
|
+
|
|
2615
|
+
### CLI
|
|
2616
|
+
|
|
2617
|
+
```bash
|
|
2618
|
+
# Run a bot
|
|
2619
|
+
spluspy run bot.py
|
|
2620
|
+
|
|
2621
|
+
# Run with custom session name
|
|
2622
|
+
spluspy run bot.py --session my_bot
|
|
2623
|
+
|
|
2624
|
+
# Get session info
|
|
2625
|
+
spluspy session-info my_session.session
|
|
2626
|
+
|
|
2627
|
+
# Show version
|
|
2628
|
+
spluspy version
|
|
2629
|
+
|
|
2630
|
+
# Validate a bot script for syntax errors
|
|
2631
|
+
spluspy validate bot.py
|
|
2632
|
+
```
|
|
2633
|
+
|
|
2634
|
+
### Docker
|
|
2635
|
+
|
|
2636
|
+
```dockerfile
|
|
2637
|
+
FROM python:3.12-slim
|
|
2638
|
+
|
|
2639
|
+
WORKDIR /app
|
|
2640
|
+
|
|
2641
|
+
# Install system dependencies
|
|
2642
|
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
2643
|
+
gcc \
|
|
2644
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
2645
|
+
|
|
2646
|
+
# Install spluspy
|
|
2647
|
+
RUN pip install --no-cache-dir spluspy[all]
|
|
2648
|
+
|
|
2649
|
+
# Copy your bot script
|
|
2650
|
+
COPY bot.py .
|
|
2651
|
+
|
|
2652
|
+
# Run the bot
|
|
2653
|
+
CMD ["python", "bot.py"]
|
|
2654
|
+
```
|
|
2655
|
+
|
|
2656
|
+
Build and run:
|
|
2657
|
+
|
|
2658
|
+
```bash
|
|
2659
|
+
docker build -t my-spluspy-bot .
|
|
2660
|
+
docker run -v ./sessions:/app/sessions my-spluspy-bot
|
|
2661
|
+
```
|
|
2662
|
+
|
|
2663
|
+
---
|
|
2664
|
+
|
|
2665
|
+
## فارسی
|
|
2666
|
+
|
|
2667
|
+
### ویژگیها
|
|
2668
|
+
|
|
2669
|
+
| ویژگی | توضیحات |
|
|
2670
|
+
|-------|---------|
|
|
2671
|
+
| **بدون نیاز به API Key** | کلیدهای سروش پلاس به صورت داخلی |
|
|
2672
|
+
| **ناهمگام کامل** | ساخته شده با `asyncio` پایتون |
|
|
2673
|
+
| **پشتیبانی از حالت همگام** | استفاده بدون `async/await` از طریق `spluspy.sync` |
|
|
2674
|
+
| **پشتیبانی ربات و کاربر** | هر دو نوع حساب |
|
|
2675
|
+
| **هندلرهای رویدادمحور** | سیستم رویداد قدرتمند با دکوراتورها |
|
|
2676
|
+
| **سیستم فیلتر** | فیلترهای قابل ترکیب (`&`, `\|`, `~`) |
|
|
2677
|
+
| **دکمههای اینلاین و ریپلای** | کیبوردهای تعاملی |
|
|
2678
|
+
| **API مکالمه** | برای جریانهای تعاملی ربات |
|
|
2679
|
+
| **FSM (ماشین حالت محدود)** | مدیریت وضعیت داخلی برای رباتها |
|
|
2680
|
+
| **سیستم پلاگین** | بارگذاری پویای پلاگینها |
|
|
2681
|
+
| **میانافزار (Middleware)** | پردازش قبل/بعد از بهروزرسانیها |
|
|
2682
|
+
| **زمانبند (Scheduler)** | زمانبندی داخلی وظایف |
|
|
2683
|
+
| **بکاندهای ذخیرهسازی متعدد** | حافظه، SQLite، Redis، PostgreSQL |
|
|
2684
|
+
| **محدودیت نرخ (Rate Limiting)** | الگوریتم سطل توکن با مدیریت انتظار سیلاب |
|
|
2685
|
+
| **پاسخ خودکار AFK** | پاسخگوی هوشمند با محدودیت نرخ به ازای هر چت |
|
|
2686
|
+
| **مدیریت چت** | مسدود کردن، بیصدا کردن، سنجاق، پاکسازی — API مدیریتی |
|
|
2687
|
+
| **آینهسازی پیام** | کلون کردن پیام و رسانه به صورت بلادرنگ بین چتها |
|
|
2688
|
+
| **عملیات دستهای** | ارسال، حذف، فوروارد چندین پیام به صورت همزمان |
|
|
2689
|
+
| **انتقال فایل با پیشرفت** | آپلود/دانلود با ردیابی پیشرفت |
|
|
2690
|
+
| **لاگ حرفهای** | لاگرهای ساختاریافته و فضای نامدار |
|
|
2691
|
+
| **نوعنویسی در همه جا** | ایمنی کامل نوع |
|
|
2692
|
+
| **معماری تمیز** | اصول SOLID، طراحی ماژولار |
|
|
2693
|
+
|
|
2694
|
+
### پیشنیازها
|
|
2695
|
+
|
|
2696
|
+
- پایتون 3.10 به بالا
|
|
2697
|
+
- نیازی به کلید API خارجی نیست (کلیدهای سروش پلاس به صورت داخلی)
|
|
2698
|
+
|
|
2699
|
+
### نصب
|
|
2700
|
+
|
|
2701
|
+
```bash
|
|
2702
|
+
pip install spluspy
|
|
2703
|
+
```
|
|
2704
|
+
|
|
2705
|
+
برای رمزگذاری سریعتر:
|
|
2706
|
+
|
|
2707
|
+
```bash
|
|
2708
|
+
pip install spluspy[speed]
|
|
2709
|
+
```
|
|
2710
|
+
|
|
2711
|
+
با بکاند Redis:
|
|
2712
|
+
|
|
2713
|
+
```bash
|
|
2714
|
+
pip install spluspy[redis]
|
|
2715
|
+
```
|
|
2716
|
+
|
|
2717
|
+
با بکاند PostgreSQL:
|
|
2718
|
+
|
|
2719
|
+
```bash
|
|
2720
|
+
pip install spluspy[postgres]
|
|
2721
|
+
```
|
|
2722
|
+
|
|
2723
|
+
همه وابستگیهای اختیاری:
|
|
2724
|
+
|
|
2725
|
+
```bash
|
|
2726
|
+
pip install spluspy[all]
|
|
2727
|
+
```
|
|
2728
|
+
|
|
2729
|
+
### شروع سریع
|
|
2730
|
+
|
|
2731
|
+
#### سادهترین ربات
|
|
2732
|
+
|
|
2733
|
+
```python
|
|
2734
|
+
from spluspy import Client
|
|
2735
|
+
|
|
2736
|
+
bot = Client("my_session")
|
|
2737
|
+
|
|
2738
|
+
@bot.on_message()
|
|
2739
|
+
async def handler(client, message):
|
|
2740
|
+
await message.reply("سلام!")
|
|
2741
|
+
|
|
2742
|
+
bot.run()
|
|
2743
|
+
```
|
|
2744
|
+
|
|
2745
|
+
#### حساب کاربری
|
|
2746
|
+
|
|
2747
|
+
```python
|
|
2748
|
+
from spluspy import Client
|
|
2749
|
+
|
|
2750
|
+
client = Client("session_name")
|
|
2751
|
+
|
|
2752
|
+
@client.on_message()
|
|
2753
|
+
async def handler(client, message):
|
|
2754
|
+
await message.reply("سلام دنیا!")
|
|
2755
|
+
|
|
2756
|
+
async def main():
|
|
2757
|
+
await client.start(phone="+98XXXXXXXXXX")
|
|
2758
|
+
await client.run_until_disconnected()
|
|
2759
|
+
|
|
2760
|
+
import asyncio
|
|
2761
|
+
asyncio.run(main())
|
|
2762
|
+
```
|
|
2763
|
+
|
|
2764
|
+
#### استفاده همگام (بدون Async/Await)
|
|
2765
|
+
|
|
2766
|
+
```python
|
|
2767
|
+
from spluspy.sync import Client
|
|
2768
|
+
|
|
2769
|
+
bot = Client("session")
|
|
2770
|
+
|
|
2771
|
+
@bot.on_message()
|
|
2772
|
+
def handler(client, message):
|
|
2773
|
+
message.reply("سلام!")
|
|
2774
|
+
|
|
2775
|
+
bot.run()
|
|
2776
|
+
```
|
|
2777
|
+
|
|
2778
|
+
### رویدادها
|
|
2779
|
+
|
|
2780
|
+
| دکوراتور | رویداد |
|
|
2781
|
+
|----------|--------|
|
|
2782
|
+
| `@bot.on_message()` | پیام جدید |
|
|
2783
|
+
| `@bot.on_edited_message()` | ویرایش پیام |
|
|
2784
|
+
| `@bot.on_callback_query()` | کلیک دکمه اینلاین |
|
|
2785
|
+
| `@bot.on_inline_query()` | کوئری اینلاین |
|
|
2786
|
+
| `@bot.on_chat_action()` | پیوستن/خروج/سنجاق |
|
|
2787
|
+
| `@bot.on_user_update()` | تغییر وضعیت |
|
|
2788
|
+
| `@bot.on_message_deleted()` | حذف پیام |
|
|
2789
|
+
| `@bot.on_message_read()` | رسید خواندن |
|
|
2790
|
+
| `@bot.on_error()` | مدیریت خطای سراسری |
|
|
2791
|
+
|
|
2792
|
+
#### اولویت رویداد
|
|
2793
|
+
|
|
2794
|
+
```python
|
|
2795
|
+
from spluspy.events import HandlerPriority
|
|
2796
|
+
|
|
2797
|
+
@bot.on_message(priority=HandlerPriority.FIRST)
|
|
2798
|
+
async def high_priority_handler(client, message):
|
|
2799
|
+
pass
|
|
2800
|
+
|
|
2801
|
+
@bot.on_message(priority=HandlerPriority.LOW)
|
|
2802
|
+
async def low_priority_handler(client, message):
|
|
2803
|
+
pass
|
|
2804
|
+
```
|
|
2805
|
+
|
|
2806
|
+
#### توقف انتشار
|
|
2807
|
+
|
|
2808
|
+
```python
|
|
2809
|
+
@bot.on_message()
|
|
2810
|
+
async def stopper(client, message):
|
|
2811
|
+
if message.text == "/stop":
|
|
2812
|
+
message.stop_propagation()
|
|
2813
|
+
await message.reply("توقف!")
|
|
2814
|
+
```
|
|
2815
|
+
|
|
2816
|
+
### فیلترها
|
|
2817
|
+
|
|
2818
|
+
```python
|
|
2819
|
+
from spluspy import filters
|
|
2820
|
+
|
|
2821
|
+
@bot.on_message(filters.text) # فقط متن
|
|
2822
|
+
@bot.on_message(filters.private) # چتهای خصوصی
|
|
2823
|
+
@bot.on_message(filters.group) # گروهها
|
|
2824
|
+
@bot.on_message(filters.command("start")) # دستور /start
|
|
2825
|
+
@bot.on_message(filters.regex(r"\d+")) # تطابق با عبارت باقاعده
|
|
2826
|
+
@bot.on_message(filters.user(123)) # کاربر خاص
|
|
2827
|
+
@bot.on_message(filters.text & filters.private) # ترکیبی
|
|
2828
|
+
@bot.on_message(filters.photo | filters.video) # عکس یا ویدیو
|
|
2829
|
+
```
|
|
2830
|
+
|
|
2831
|
+
### متدهای پیام
|
|
2832
|
+
|
|
2833
|
+
```python
|
|
2834
|
+
await message.reply("سلام") # پاسخ
|
|
2835
|
+
await message.edit("متن جدید") # ویرایش
|
|
2836
|
+
await message.delete() # حذف
|
|
2837
|
+
await message.forward(chat_id) # فوروارد
|
|
2838
|
+
await message.copy(chat_id) # کپی (بدون هدر فوروارد)
|
|
2839
|
+
await message.pin() # سنجاق
|
|
2840
|
+
await message.react("❤️") # واکنش
|
|
2841
|
+
await message.mark_read() # علامت خواندن
|
|
2842
|
+
await message.download() # دانلود رسانه
|
|
2843
|
+
await message.reply_photo("photo.jpg") # پاسخ با عکس
|
|
2844
|
+
await message.reply_video("video.mp4") # پاسخ با ویدیو
|
|
2845
|
+
await message.reply_document("file.pdf") # پاسخ با سند
|
|
2846
|
+
```
|
|
2847
|
+
|
|
2848
|
+
### دکمهها
|
|
2849
|
+
|
|
2850
|
+
```python
|
|
2851
|
+
from spluspy import Button
|
|
2852
|
+
|
|
2853
|
+
# کیبورد اینلاین
|
|
2854
|
+
keyboard = Button.build_inline([
|
|
2855
|
+
Button.inline("گزینه ۱", b"opt1"),
|
|
2856
|
+
Button.inline("گزینه ۲", b"opt2")
|
|
2857
|
+
])
|
|
2858
|
+
await bot.send_message(chat_id, "انتخاب کنید:", buttons=keyboard)
|
|
2859
|
+
|
|
2860
|
+
# کیبورد ریپلای
|
|
2861
|
+
kb = Button.build_reply([
|
|
2862
|
+
Button.text("منو"),
|
|
2863
|
+
Button.text("تنظیمات")
|
|
2864
|
+
])
|
|
2865
|
+
await bot.send_message(chat_id, "انتخاب کنید:", buttons=kb)
|
|
2866
|
+
|
|
2867
|
+
# حذف کیبورد
|
|
2868
|
+
await bot.send_message(chat_id, "تمام", buttons=Button.clear())
|
|
2869
|
+
```
|
|
2870
|
+
|
|
2871
|
+
### FSM (ماشین حالت محدود)
|
|
2872
|
+
|
|
2873
|
+
```python
|
|
2874
|
+
from spluspy.fsm import State, StateMachine
|
|
2875
|
+
from spluspy.storage import MemoryStorage
|
|
2876
|
+
|
|
2877
|
+
storage = MemoryStorage()
|
|
2878
|
+
fsm = StateMachine(storage)
|
|
2879
|
+
|
|
2880
|
+
class Form:
|
|
2881
|
+
name = State()
|
|
2882
|
+
age = State()
|
|
2883
|
+
|
|
2884
|
+
@bot.on_message(filters.command("register"))
|
|
2885
|
+
async def start_register(client, message):
|
|
2886
|
+
ctx = fsm.context(message.sender_id)
|
|
2887
|
+
await ctx.set_state(Form.name)
|
|
2888
|
+
await message.reply("نام شما چیست؟")
|
|
2889
|
+
|
|
2890
|
+
@bot.on_message(filters.private)
|
|
2891
|
+
async def process_form(client, message):
|
|
2892
|
+
ctx = fsm.context(message.sender_id)
|
|
2893
|
+
state = await ctx.get_state()
|
|
2894
|
+
|
|
2895
|
+
if state == Form.name:
|
|
2896
|
+
await ctx.set_data(name=message.text)
|
|
2897
|
+
await ctx.set_state(Form.age)
|
|
2898
|
+
await message.reply("سن شما چقدر است؟")
|
|
2899
|
+
elif state == Form.age:
|
|
2900
|
+
data = await ctx.get_data()
|
|
2901
|
+
await ctx.reset()
|
|
2902
|
+
await message.reply(f"ثبت شد! نام: {data.get('name')}, سن: {message.text}")
|
|
2903
|
+
```
|
|
2904
|
+
|
|
2905
|
+
### بکاندهای ذخیرهسازی
|
|
2906
|
+
|
|
2907
|
+
```python
|
|
2908
|
+
from spluspy.storage import MemoryStorage, SQLiteStorage, RedisStorage, PostgresStorage, get_storage
|
|
2909
|
+
|
|
2910
|
+
# حافظه (پیشفرض)
|
|
2911
|
+
storage = MemoryStorage()
|
|
2912
|
+
|
|
2913
|
+
# SQLite
|
|
2914
|
+
storage = SQLiteStorage("data.db")
|
|
2915
|
+
|
|
2916
|
+
# Redis
|
|
2917
|
+
storage = RedisStorage(host="localhost", port=6379, db=0)
|
|
2918
|
+
|
|
2919
|
+
# PostgreSQL
|
|
2920
|
+
storage = PostgresStorage(dsn="postgresql://user:pass@localhost/db")
|
|
2921
|
+
|
|
2922
|
+
# تابع کارخانه
|
|
2923
|
+
storage = get_storage("redis", host="localhost")
|
|
2924
|
+
```
|
|
2925
|
+
|
|
2926
|
+
### سیستم پلاگین
|
|
2927
|
+
|
|
2928
|
+
```python
|
|
2929
|
+
# plugins/hello.py
|
|
2930
|
+
def register(client):
|
|
2931
|
+
@client.on_message(filters.command("hello"))
|
|
2932
|
+
async def hello_handler(client, message):
|
|
2933
|
+
await message.reply("سلام از پلاگین!")
|
|
2934
|
+
```
|
|
2935
|
+
|
|
2936
|
+
```python
|
|
2937
|
+
# main.py
|
|
2938
|
+
from spluspy import Client
|
|
2939
|
+
|
|
2940
|
+
bot = Client("session")
|
|
2941
|
+
bot.plugins.load("plugins")
|
|
2942
|
+
bot.run()
|
|
2943
|
+
```
|
|
2944
|
+
|
|
2945
|
+
### میانافزار (Middleware)
|
|
2946
|
+
|
|
2947
|
+
```python
|
|
2948
|
+
from spluspy.middleware import Middleware
|
|
2949
|
+
|
|
2950
|
+
class LoggingMiddleware(Middleware):
|
|
2951
|
+
async def on_update(self, update, handler):
|
|
2952
|
+
print(f"بهروزرسانی دریافت شد: {update}")
|
|
2953
|
+
result = await handler(update)
|
|
2954
|
+
print(f"هندلر تکمیل شد")
|
|
2955
|
+
return result
|
|
2956
|
+
|
|
2957
|
+
bot.middleware.add(LoggingMiddleware())
|
|
2958
|
+
```
|
|
2959
|
+
|
|
2960
|
+
### محدودیت نرخ
|
|
2961
|
+
|
|
2962
|
+
```python
|
|
2963
|
+
from spluspy.utils import RateLimiter
|
|
2964
|
+
|
|
2965
|
+
limiter = RateLimiter(max_calls=10, period=60)
|
|
2966
|
+
|
|
2967
|
+
@bot.on_message()
|
|
2968
|
+
async def limited_handler(client, message):
|
|
2969
|
+
if not limiter.allow():
|
|
2970
|
+
await message.reply("محدودیت نرخ! دوباره تلاش کنید.")
|
|
2971
|
+
return
|
|
2972
|
+
await message.reply("باشه")
|
|
2973
|
+
```
|
|
2974
|
+
|
|
2975
|
+
### پاسخ خودکار AFK
|
|
2976
|
+
|
|
2977
|
+
```python
|
|
2978
|
+
from spluspy import Client, filters
|
|
2979
|
+
from spluspy.afk import AfkManager
|
|
2980
|
+
|
|
2981
|
+
bot = Client("my_account")
|
|
2982
|
+
afk = AfkManager(bot, message="الان AFK هستم. زود برمیگردم!")
|
|
2983
|
+
|
|
2984
|
+
@bot.on_message(filters.command("afk"))
|
|
2985
|
+
async def set_afk(client, message):
|
|
2986
|
+
afk.set_afk(True, reason="ناهار")
|
|
2987
|
+
await message.reply("الان AFK هستم!")
|
|
2988
|
+
|
|
2989
|
+
@bot.on_message(filters.command("back"))
|
|
2990
|
+
async def unset_afk(client, message):
|
|
2991
|
+
afk.set_afk(False)
|
|
2992
|
+
await message.reply(f"برگشتم! {afk.total_replies} پاسخ خودکار ارسال شد.")
|
|
2993
|
+
```
|
|
2994
|
+
|
|
2995
|
+
### مدیریت چت
|
|
2996
|
+
|
|
2997
|
+
```python
|
|
2998
|
+
from spluspy.admin import ChatAdmin
|
|
2999
|
+
|
|
3000
|
+
admin = ChatAdmin(bot)
|
|
3001
|
+
|
|
3002
|
+
await admin.ban_user(chat_id, user_id)
|
|
3003
|
+
await admin.unban_user(chat_id, user_id)
|
|
3004
|
+
await admin.mute_user(chat_id, user_id)
|
|
3005
|
+
await admin.unmute_user(chat_id, user_id)
|
|
3006
|
+
await admin.pin_message(chat_id, message)
|
|
3007
|
+
await admin.unpin_message(chat_id, message)
|
|
3008
|
+
await admin.unpin_all(chat_id)
|
|
3009
|
+
await admin.purge_messages(chat_id, limit=100)
|
|
3010
|
+
|
|
3011
|
+
# عملیات دستهای
|
|
3012
|
+
await admin.bulk_action(chat_id, "ban", [user_id1, user_id2])
|
|
3013
|
+
|
|
3014
|
+
# لاگ مدیریتی
|
|
3015
|
+
events = await admin.get_admin_log(chat_id, limit=50)
|
|
3016
|
+
```
|
|
3017
|
+
|
|
3018
|
+
### آینهسازی پیام
|
|
3019
|
+
|
|
3020
|
+
```python
|
|
3021
|
+
from spluspy.mirror import MessageMirror
|
|
3022
|
+
|
|
3023
|
+
mirror = MessageMirror(bot)
|
|
3024
|
+
|
|
3025
|
+
mirror.add_route(
|
|
3026
|
+
source=-1001234567890,
|
|
3027
|
+
targets=[-1009876543210, -1001112223334],
|
|
3028
|
+
strip_forward=True,
|
|
3029
|
+
strip_sender=False,
|
|
3030
|
+
add_prefix="[Mirror]"
|
|
3031
|
+
)
|
|
3032
|
+
|
|
3033
|
+
await mirror.start()
|
|
3034
|
+
```
|
|
3035
|
+
|
|
3036
|
+
### زمانبند
|
|
3037
|
+
|
|
3038
|
+
```python
|
|
3039
|
+
from spluspy.scheduler.scheduler import MessageScheduler
|
|
3040
|
+
|
|
3041
|
+
scheduler = MessageScheduler(bot)
|
|
3042
|
+
|
|
3043
|
+
# ارسال پیام هر ساعت
|
|
3044
|
+
scheduler.schedule_interval("hourly_greeting", chat_id, "سلام!", interval=3600)
|
|
3045
|
+
|
|
3046
|
+
# ارسال پیام با تاخیر
|
|
3047
|
+
scheduler.schedule_once("reminder", chat_id, "فراموش نکن!", delay=300)
|
|
3048
|
+
|
|
3049
|
+
# لغو وظیفه زمانبندی شده
|
|
3050
|
+
scheduler.cancel("hourly_greeting")
|
|
3051
|
+
```
|
|
3052
|
+
|
|
3053
|
+
### مدیریت خطا
|
|
3054
|
+
|
|
3055
|
+
```python
|
|
3056
|
+
from spluspy import filters
|
|
3057
|
+
from spluspy.errors import FloodWait, Unauthorized, BadRequest
|
|
3058
|
+
|
|
3059
|
+
@bot.on_message()
|
|
3060
|
+
async def safe_handler(client, message):
|
|
3061
|
+
try:
|
|
3062
|
+
await message.reply("سلام!")
|
|
3063
|
+
except FloodWait as e:
|
|
3064
|
+
await asyncio.sleep(e.seconds)
|
|
3065
|
+
except Unauthorized:
|
|
3066
|
+
await message.reply("غیرمجاز!")
|
|
3067
|
+
except BadRequest as e:
|
|
3068
|
+
print(f"درخواست نادرست: {e}")
|
|
3069
|
+
```
|
|
3070
|
+
|
|
3071
|
+
### عملیات دستهای
|
|
3072
|
+
|
|
3073
|
+
```python
|
|
3074
|
+
# ارسال چندین پیام
|
|
3075
|
+
messages = ["سلام ۱", "سلام ۲", "سلام ۳"]
|
|
3076
|
+
results = await bot.batch_send(chat_id, messages)
|
|
3077
|
+
|
|
3078
|
+
# حذف چندین پیام
|
|
3079
|
+
await bot.batch_delete(chat_id, [msg1, msg2, msg3])
|
|
3080
|
+
|
|
3081
|
+
# فوروارد چندین پیام
|
|
3082
|
+
await bot.batch_forward(chat_id, [msg1, msg2])
|
|
3083
|
+
```
|
|
3084
|
+
|
|
3085
|
+
### انتقال فایل با پیشرفت
|
|
3086
|
+
|
|
3087
|
+
```python
|
|
3088
|
+
from spluspy.utils import ProgressTracker
|
|
3089
|
+
|
|
3090
|
+
# آپلود با پیشرفت
|
|
3091
|
+
tracker = ProgressTracker(on_progress=lambda p: print(f"{p.percent}%"))
|
|
3092
|
+
await bot.send_document(chat_id, "فایل_بزرگ.zip", progress=tracker)
|
|
3093
|
+
|
|
3094
|
+
# دانلود با پیشرفت
|
|
3095
|
+
await message.download(progress=tracker)
|
|
3096
|
+
```
|
|
3097
|
+
|
|
3098
|
+
### رابط خط فرمان (CLI)
|
|
3099
|
+
|
|
3100
|
+
```bash
|
|
3101
|
+
# اجرای ربات
|
|
3102
|
+
spluspy run bot.py
|
|
3103
|
+
|
|
3104
|
+
# اجرا با نشست سفارشی
|
|
3105
|
+
spluspy run bot.py --session my_bot
|
|
3106
|
+
|
|
3107
|
+
# اطلاعات نشست
|
|
3108
|
+
spluspy session-info my_session.session
|
|
3109
|
+
|
|
3110
|
+
# نمایش نسخه
|
|
3111
|
+
spluspy version
|
|
3112
|
+
|
|
3113
|
+
# اعتبارسنجی اسکریپت ربات
|
|
3114
|
+
spluspy validate bot.py
|
|
3115
|
+
```
|
|
3116
|
+
|
|
3117
|
+
### Docker
|
|
3118
|
+
|
|
3119
|
+
```dockerfile
|
|
3120
|
+
FROM python:3.12-slim
|
|
3121
|
+
|
|
3122
|
+
WORKDIR /app
|
|
3123
|
+
|
|
3124
|
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
3125
|
+
gcc \
|
|
3126
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
3127
|
+
|
|
3128
|
+
RUN pip install --no-cache-dir spluspy[all]
|
|
3129
|
+
|
|
3130
|
+
COPY bot.py .
|
|
3131
|
+
|
|
3132
|
+
CMD ["python", "bot.py"]
|
|
3133
|
+
```
|
|
3134
|
+
|
|
3135
|
+
ساخت و اجرا:
|
|
3136
|
+
|
|
3137
|
+
```bash
|
|
3138
|
+
docker build -t my-spluspy-bot .
|
|
3139
|
+
docker run -v ./sessions:/app/sessions my-spluspy-bot
|
|
3140
|
+
```
|
|
3141
|
+
|
|
3142
|
+
### ساختار پروژه
|
|
3143
|
+
|
|
3144
|
+
```
|
|
3145
|
+
spluspy/
|
|
3146
|
+
├── __init__.py # API عمومی
|
|
3147
|
+
├── __version__.py # اطلاعات نسخه
|
|
3148
|
+
├── cli.py # نقطه ورود CLI
|
|
3149
|
+
├── config.py # پیکربندی
|
|
3150
|
+
├── compat.py # لایه سازگاری
|
|
3151
|
+
├── afk.py # پاسخگوی خودکار AFK
|
|
3152
|
+
├── admin.py # مدیریت چت
|
|
3153
|
+
├── mirror.py # موتور آینهسازی پیام
|
|
3154
|
+
├── client/ # کلاینت و API مکالمه
|
|
3155
|
+
│ ├── client.py # کلاس اصلی Client
|
|
3156
|
+
│ ├── conversation.py # API مکالمه
|
|
3157
|
+
│ ├── chat_mixin.py # میکسین مدیریت چت
|
|
3158
|
+
│ └── media_mixin.py # میکسین عملیات رسانه
|
|
3159
|
+
├── models/ # مدلهای دامنه
|
|
3160
|
+
│ ├── message.py # دادهکلاس Message
|
|
3161
|
+
│ ├── user.py # دادهکلاس User
|
|
3162
|
+
│ ├── chat.py # دادهکلاس Chat, Channel, Group
|
|
3163
|
+
│ ├── bot.py # کارخانه Button
|
|
3164
|
+
│ ├── media.py # انواع رسانه
|
|
3165
|
+
│ ├── objects.py # MessageEntity, ReplyMarkup, و غیره
|
|
3166
|
+
│ └── enums.py # ChatType, UserStatus, و غیره
|
|
3167
|
+
├── events/ # انواع رویداد و بیلدرها
|
|
3168
|
+
├── filters/ # فیلترهای قابل ترکیب پیام
|
|
3169
|
+
├── errors/ # سلسله مراتب استثنای سفارشی
|
|
3170
|
+
├── session/ # بکاندهای نشست
|
|
3171
|
+
├── network/ # اتصالات TCP و استخر اتصال
|
|
3172
|
+
├── storage/ # بکاندهای ذخیرهسازی کلید-مقدار
|
|
3173
|
+
├── plugins/ # بارگذار پلاگین
|
|
3174
|
+
├── middleware/ # سیستم میانافزار
|
|
3175
|
+
├── fsm/ # ماشین حالت محدود
|
|
3176
|
+
├── scheduler/ # زمانبند وظایف
|
|
3177
|
+
├── utils/ # لاگر، کش، کمککنندهها
|
|
3178
|
+
├── sync/ # کلاینت همگامسازی شده
|
|
3179
|
+
└── _engine/ # موتور MTProto سطح پایین
|
|
3180
|
+
```
|
|
3181
|
+
|
|
3182
|
+
### سلسله مراتب خطاها
|
|
3183
|
+
|
|
3184
|
+
```
|
|
3185
|
+
SplusPyError
|
|
3186
|
+
├── SoroushPlusAPIError
|
|
3187
|
+
│ ├── RPCError
|
|
3188
|
+
│ │ ├── FloodWait
|
|
3189
|
+
│ │ ├── Unauthorized
|
|
3190
|
+
│ │ ├── Forbidden
|
|
3191
|
+
│ │ ├── BadRequest
|
|
3192
|
+
│ │ │ ├── ChatNotFound
|
|
3193
|
+
│ │ │ ├── UserNotFound
|
|
3194
|
+
│ │ │ └── MessageNotFound
|
|
3195
|
+
│ │ └── ...
|
|
3196
|
+
│ ├── SessionExpiredError
|
|
3197
|
+
│ └── SessionError
|
|
3198
|
+
├── AuthError
|
|
3199
|
+
├── ValidationError
|
|
3200
|
+
├── TimeoutError
|
|
3201
|
+
├── ConnectionError
|
|
3202
|
+
├── PluginError
|
|
3203
|
+
├── FSMError
|
|
3204
|
+
├── StorageError
|
|
3205
|
+
├── JoinChatError
|
|
3206
|
+
│ ├── InvalidInviteLinkError
|
|
3207
|
+
│ ├── InviteLinkExpiredError
|
|
3208
|
+
│ ├── ChatFullError
|
|
3209
|
+
│ ├── ChatDeactivatedError
|
|
3210
|
+
│ └── MembershipRequiredError
|
|
3211
|
+
└── FloodWaitError (محدودیت نرخ)
|
|
3212
|
+
```
|
|
3213
|
+
|
|
3214
|
+
---
|
|
3215
|
+
|
|
3216
|
+
## Contributing / مشارکت
|
|
3217
|
+
|
|
3218
|
+
### English
|
|
3219
|
+
|
|
3220
|
+
1. Fork the repository
|
|
3221
|
+
2. Create a feature branch (`git checkout -b feature/your-feature`)
|
|
3222
|
+
3. Set up the development environment:
|
|
3223
|
+
```bash
|
|
3224
|
+
python -m venv venv
|
|
3225
|
+
source venv/bin/activate
|
|
3226
|
+
pip install -e ".[dev]"
|
|
3227
|
+
```
|
|
3228
|
+
4. Make your changes
|
|
3229
|
+
5. Run linting and formatting:
|
|
3230
|
+
```bash
|
|
3231
|
+
ruff check spluspy/
|
|
3232
|
+
black spluspy/
|
|
3233
|
+
```
|
|
3234
|
+
6. Run type checking:
|
|
3235
|
+
```bash
|
|
3236
|
+
mypy spluspy/
|
|
3237
|
+
```
|
|
3238
|
+
7. Run tests: `pytest`
|
|
3239
|
+
8. Submit a pull request
|
|
3240
|
+
|
|
3241
|
+
### فارسی
|
|
3242
|
+
|
|
3243
|
+
1. مخزن را Fork کنید
|
|
3244
|
+
2. شاخه ویژگی بسازید (`git checkout -b feature/your-feature`)
|
|
3245
|
+
3. محیط توسعه را راهاندازی کنید:
|
|
3246
|
+
```bash
|
|
3247
|
+
python -m venv venv
|
|
3248
|
+
source venv/bin/activate
|
|
3249
|
+
pip install -e ".[dev]"
|
|
3250
|
+
```
|
|
3251
|
+
4. تغییرات خود را اعمال کنید
|
|
3252
|
+
5. لینتر و فرمتکننده را اجرا کنید:
|
|
3253
|
+
```bash
|
|
3254
|
+
ruff check spluspy/
|
|
3255
|
+
black spluspy/
|
|
3256
|
+
```
|
|
3257
|
+
6. بررسی نوع را اجرا کنید:
|
|
3258
|
+
```bash
|
|
3259
|
+
mypy spluspy/
|
|
3260
|
+
```
|
|
3261
|
+
7. تستها را اجرا کنید: `pytest`
|
|
3262
|
+
8. درخواست Pull ارسال کنید
|
|
3263
|
+
|
|
3264
|
+
---
|
|
3265
|
+
|
|
3266
|
+
## License / مجوز
|
|
3267
|
+
|
|
3268
|
+
MIT License — see [LICENSE](LICENSE) for details.
|
|
3269
|
+
|
|
3270
|
+
مجوز MIT — جزئیات را در [LICENSE](LICENSE) مشاهده کنید.
|
|
3271
|
+
|
|
3272
|
+
---
|
|
3273
|
+
|
|
3274
|
+
## Disclaimer / سلب مسئولیت
|
|
3275
|
+
|
|
3276
|
+
**English:**
|
|
3277
|
+
SPlusPy is an unofficial third-party library. Use it responsibly and ensure your applications comply with Soroush Plus's Terms of Service.
|
|
3278
|
+
|
|
3279
|
+
**فارسی:**
|
|
3280
|
+
SPlusPy یک کتابخانه غیررسمی و شخص ثالث است. مسئولانه از آن استفاده کنید و مطمئن شوید برنامههای شما با شرایط استفاده سروش پلاس مطابقت دارند.
|
|
3281
|
+
|
|
3282
|
+
---
|
|
3283
|
+
|
|
3284
|
+
<div align="center">
|
|
3285
|
+
|
|
3286
|
+
**Made with ❤️ for the Soroush Plus community**
|
|
3287
|
+
|
|
3288
|
+
**ساخته شده با ❤️ برای جامعه سروش پلاس**
|
|
3289
|
+
|
|
3290
|
+
</div>
|