spluspy 2.0.8__tar.gz → 2.1.0__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.0.8 → spluspy-2.1.0}/CHANGELOG.md +15 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/PKG-INFO +3 -1
- {spluspy-2.0.8 → spluspy-2.1.0}/pyproject.toml +3 -1
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/__version__.py +1 -1
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/client/dialogs.py +97 -0
- spluspy-2.1.0/spluspy/client/chat_mixin.py +507 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/client/client.py +315 -206
- spluspy-2.1.0/spluspy/client/media_mixin.py +531 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/config.py +2 -2
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/utils/version_check.py +2 -1
- {spluspy-2.0.8 → spluspy-2.1.0}/.gitignore +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/CONTRIBUTING.md +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/Dockerfile +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/LICENSE +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/MANIFEST.in +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/README.md +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/README.rst +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/examples/01_hello_bot.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/examples/02_echo_bot.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/examples/03_command_bot.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/examples/04_private_only.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/examples/05_group_only.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/examples/06_regex_bot.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/examples/07_user_filter.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/examples/08_combined_filters.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/examples/09_inline_keyboard.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/examples/10_reply_keyboard.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/examples/11_message_actions.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/examples/12_media_bot.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/examples/13_fsm_bot.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/examples/14_plugin_bot.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/examples/15_middleware_bot.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/examples/16_scheduler_bot.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/examples/17_conversation.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/examples/18_chat_management.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/examples/19_event_types.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/examples/20_sync_bot.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/examples/21_album_handler.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/examples/22_download_bot.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/examples/23_url_button.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/examples/24_string_session.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/examples/25_error_handling.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/examples/25_working_bot.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/examples/26_message_entities.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/examples/27_search_messages.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/examples/28_poll_bot.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/examples/29_dice_bot.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/examples/30_cache_bot.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/examples/31_reaction_bot.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/examples/32_mark_read_bot.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/examples/33_context_manager.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/examples/34_user_info.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/examples/35_chat_info.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/examples/36_forward_copy.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/examples/37_admin_bot.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/examples/38_media_filters.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/examples/39_status_bot.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/examples/40_delete_detection.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/examples/41_read_detection.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/examples/42_raw_events.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/examples/43_poll_handler.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/examples/44_reaction_handler.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/examples/45_typing_handler.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/examples/46_get_messages.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/examples/47_iter_messages.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/examples/48_get_members.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/examples/49_resolve_username.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/examples/50_invoke.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/setup.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/__init__.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/__init__.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/_updates/__init__.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/_updates/entitycache.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/_updates/messagebox.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/_updates/session.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/client/__init__.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/client/account.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/client/auth.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/client/bots.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/client/buttons.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/client/chats.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/client/downloads.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/client/messageparse.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/client/messages.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/client/soroushclient.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/client/telegrambaseclient.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/client/updates.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/client/uploads.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/client/users.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/crypto/__init__.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/crypto/aes.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/crypto/aesctr.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/crypto/authkey.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/crypto/cdndecrypter.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/crypto/factorization.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/crypto/fast_aes.pyx +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/crypto/fast_rsa.pyx +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/crypto/libssl.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/crypto/rsa.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/custom.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/errors/__init__.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/errors/common.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/errors/rpcbaseerrors.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/errors/rpcerrorlist.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/events/__init__.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/events/album.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/events/callbackquery.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/events/chataction.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/events/common.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/events/inlinequery.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/events/messagedeleted.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/events/messageedited.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/events/messageread.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/events/newmessage.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/events/raw.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/events/userupdate.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/extensions/__init__.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/extensions/binaryreader.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/extensions/html.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/extensions/markdown.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/extensions/messagepacker.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/functions.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/helpers.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/hints.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/network/__init__.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/network/authenticator.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/network/connection/__init__.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/network/connection/connection.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/network/connection/http.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/network/connection/tcpabridged.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/network/connection/tcpfull.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/network/connection/tcpintermediate.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/network/connection/tcpmtproxy.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/network/connection/tcpobfuscated.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/network/connection/websocket.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/network/mtprotoplainsender.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/network/mtprotosender.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/network/mtprotostate.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/network/requeststate.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/password.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/requestiter.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/sessions/__init__.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/sessions/abstract.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/sessions/memory.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/sessions/sqlite.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/sessions/string.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/sync.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/__init__.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/alltlobjects.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/core/__init__.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/core/gzippacked.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/core/messagecontainer.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/core/rpcresult.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/core/tlmessage.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/custom/__init__.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/custom/adminlogevent.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/custom/button.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/custom/chatgetter.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/custom/conversation.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/custom/dialog.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/custom/draft.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/custom/file.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/custom/forward.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/custom/inlinebuilder.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/custom/inlineresult.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/custom/inlineresults.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/custom/inputsizedfile.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/custom/message.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/custom/messagebutton.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/custom/participantpermissions.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/custom/qrlogin.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/custom/sendergetter.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/custom/types.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/functions/__init__.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/functions/account.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/functions/auth.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/functions/bots.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/functions/channels.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/functions/chatlists.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/functions/conference.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/functions/contacts.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/functions/folders.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/functions/help.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/functions/langpack.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/functions/messages.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/functions/payments.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/functions/phone.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/functions/photos.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/functions/premium.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/functions/stats.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/functions/stories.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/functions/thirdParty.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/functions/updates.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/functions/upload.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/functions/users.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/patched/__init__.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/tlobject.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/types/__init__.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/types/account.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/types/auth.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/types/bots.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/types/channels.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/types/chatlists.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/types/conference.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/types/contacts.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/types/help.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/types/messages.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/types/payments.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/types/phone.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/types/photos.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/types/premium.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/types/stats.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/types/stickers.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/types/storage.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/types/stories.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/types/thirdParty.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/types/update.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/types/updates.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/types/upload.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/types/users.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/types.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/utils.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/version.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/admin.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/afk.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/cli.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/client/__init__.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/client/conversation.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/compat.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/errors/__init__.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/errors/exceptions.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/events/__init__.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/events/album.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/events/base.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/events/callback.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/events/chat_action.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/events/edited.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/events/inline.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/events/message.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/events/message_deleted.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/events/message_read.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/events/user_update.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/filters/__init__.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/filters/filters.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/fsm/__init__.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/fsm/state.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/middleware/__init__.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/middleware/base.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/middleware/rate_limit.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/mirror.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/models/__init__.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/models/bot.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/models/chat.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/models/enums.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/models/media.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/models/message.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/models/objects.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/models/user.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/network/__init__.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/network/client.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/network/connection.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/network/http_client.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/network/mtproto.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/network/protocols.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/network/spluspy_adapter.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/network/transport.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/plugins/__init__.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/plugins/loader.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/py.typed +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/scheduler/__init__.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/scheduler/scheduler.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/session/__init__.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/session/base.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/session/memory.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/session/sqlite.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/session/string.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/storage/__init__.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/storage/base.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/storage/entity_cache.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/storage/memory.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/storage/postgres.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/storage/redis.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/storage/sqlite.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/sync/__init__.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/sync/client.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/utils/__init__.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/utils/batch.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/utils/cache.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/utils/chat_lock.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/utils/file_transfer.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/utils/helpers.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/utils/logger.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/utils/rate_limiter.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/utils/resolver.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/tests/__init__.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/tests/test_client.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/tests/test_filters.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/tests/test_fsm.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/tests/test_fsm_enhanced.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/tests/test_middleware.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/tests/test_rate_limiter.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/tests/test_resolver.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/tests/test_scheduler.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/tests/test_session.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/tests/test_storage.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/tests/test_types.py +0 -0
- {spluspy-2.0.8 → spluspy-2.1.0}/tests/test_utils.py +0 -0
|
@@ -5,6 +5,21 @@ All notable changes to SplusPy will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
7
7
|
|
|
8
|
+
## [2.1.0] - 2026-07-12
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- Version bump to 2.1.0
|
|
12
|
+
|
|
13
|
+
## [2.0.9] - 2026-07-12
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
- `leave_all()` method to leave all groups and channels at once
|
|
17
|
+
- `delete_all_private()` method to delete all private chat dialogs
|
|
18
|
+
- `/delete_all_private` bot command with confirmation flow
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
- `/leave_all_groups` bot command now uses `leave_all()` to include channels
|
|
22
|
+
|
|
8
23
|
## [2.0.8] - 2026-07-12
|
|
9
24
|
|
|
10
25
|
### Changed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: spluspy
|
|
3
|
-
Version: 2.0
|
|
3
|
+
Version: 2.1.0
|
|
4
4
|
Summary: A modern async Python library for interacting with Soroush Plus
|
|
5
5
|
Project-URL: Homepage, https://github.com/Itskillmaster/spluspy
|
|
6
6
|
Project-URL: Documentation, https://github.com/Itskillmaster/spluspy#readme
|
|
@@ -25,6 +25,8 @@ Classifier: Typing :: Typed
|
|
|
25
25
|
Requires-Python: >=3.10
|
|
26
26
|
Requires-Dist: aiohttp>=3.9.0
|
|
27
27
|
Requires-Dist: aiosqlite>=0.19.0
|
|
28
|
+
Requires-Dist: pyaes>=1.6
|
|
29
|
+
Requires-Dist: rsa>=4.7
|
|
28
30
|
Provides-Extra: all
|
|
29
31
|
Requires-Dist: asyncpg>=0.27.0; extra == 'all'
|
|
30
32
|
Requires-Dist: black>=23.0; extra == 'all'
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "spluspy"
|
|
7
|
-
version = "2.0
|
|
7
|
+
version = "2.1.0"
|
|
8
8
|
description = "A modern async Python library for interacting with Soroush Plus"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -37,6 +37,8 @@ classifiers = [
|
|
|
37
37
|
dependencies = [
|
|
38
38
|
"aiosqlite>=0.19.0",
|
|
39
39
|
"aiohttp>=3.9.0",
|
|
40
|
+
"pyaes>=1.6",
|
|
41
|
+
"rsa>=4.7",
|
|
40
42
|
]
|
|
41
43
|
|
|
42
44
|
[project.optional-dependencies]
|
|
@@ -477,6 +477,103 @@ class DialogMethods:
|
|
|
477
477
|
|
|
478
478
|
return result
|
|
479
479
|
|
|
480
|
+
async def delete_all_private(
|
|
481
|
+
self: 'SoroushClient',
|
|
482
|
+
*,
|
|
483
|
+
revoke: bool = False,
|
|
484
|
+
exclude_ids: typing.Optional[typing.List[int]] = None,
|
|
485
|
+
delay: float = 1.0,
|
|
486
|
+
on_progress: typing.Optional[typing.Callable] = None
|
|
487
|
+
) -> dict:
|
|
488
|
+
"""
|
|
489
|
+
Delete all private chat dialogs (user conversations).
|
|
490
|
+
|
|
491
|
+
Iterates through all dialogs, identifies private chats (users),
|
|
492
|
+
and deletes them. Useful for cleaning up bot spam or old conversations.
|
|
493
|
+
|
|
494
|
+
Arguments
|
|
495
|
+
revoke (`bool`, optional):
|
|
496
|
+
If ``True``, revoke (delete) messages for the other peer too.
|
|
497
|
+
Default is ``False``.
|
|
498
|
+
|
|
499
|
+
exclude_ids (list of `int`, optional):
|
|
500
|
+
List of user IDs to skip (e.g. your own account, important contacts).
|
|
501
|
+
|
|
502
|
+
delay (`float`, optional):
|
|
503
|
+
Delay in seconds between each delete to avoid flood waits.
|
|
504
|
+
Default is ``1.0``.
|
|
505
|
+
|
|
506
|
+
on_progress (callable, optional):
|
|
507
|
+
Callback called with ``(current, total, title)`` for progress updates.
|
|
508
|
+
|
|
509
|
+
Returns
|
|
510
|
+
Dict with 'success', 'failed', 'skipped' counts and details.
|
|
511
|
+
|
|
512
|
+
Example
|
|
513
|
+
.. code-block:: python
|
|
514
|
+
|
|
515
|
+
# Delete all private chats
|
|
516
|
+
result = await client.delete_all_private()
|
|
517
|
+
print(f"Deleted {result['success']} chats")
|
|
518
|
+
|
|
519
|
+
# Delete but keep certain users
|
|
520
|
+
result = await client.delete_all_private(exclude_ids=[12345, 67890])
|
|
521
|
+
"""
|
|
522
|
+
exclude_ids = set(exclude_ids or [])
|
|
523
|
+
result = {"success": 0, "failed": 0, "skipped": 0, "details": []}
|
|
524
|
+
|
|
525
|
+
try:
|
|
526
|
+
dialogs = await self.get_dialogs(limit=None)
|
|
527
|
+
except Exception as exc:
|
|
528
|
+
_log.error("delete_all_private: failed to get dialogs: %s", exc)
|
|
529
|
+
return result
|
|
530
|
+
|
|
531
|
+
private_dialogs = []
|
|
532
|
+
for dialog in dialogs:
|
|
533
|
+
entity = dialog.entity
|
|
534
|
+
if entity is None:
|
|
535
|
+
continue
|
|
536
|
+
# Only user dialogs (private chats)
|
|
537
|
+
if not isinstance(entity, types.User):
|
|
538
|
+
continue
|
|
539
|
+
# Skip self
|
|
540
|
+
if entity.self:
|
|
541
|
+
continue
|
|
542
|
+
user_id = entity.id
|
|
543
|
+
if user_id in exclude_ids:
|
|
544
|
+
result["skipped"] += 1
|
|
545
|
+
result["details"].append({
|
|
546
|
+
"id": user_id,
|
|
547
|
+
"title": getattr(entity, 'first_name', None) or getattr(entity, 'username', str(user_id)),
|
|
548
|
+
"status": "skipped"
|
|
549
|
+
})
|
|
550
|
+
continue
|
|
551
|
+
private_dialogs.append((user_id, entity))
|
|
552
|
+
|
|
553
|
+
total = len(private_dialogs)
|
|
554
|
+
_log.info("delete_all_private: found %d private chats to delete", total)
|
|
555
|
+
|
|
556
|
+
for idx, (user_id, entity) in enumerate(private_dialogs):
|
|
557
|
+
title = getattr(entity, 'first_name', None) or getattr(entity, 'username', str(user_id))
|
|
558
|
+
if on_progress:
|
|
559
|
+
try:
|
|
560
|
+
on_progress(idx + 1, total, title)
|
|
561
|
+
except Exception:
|
|
562
|
+
pass
|
|
563
|
+
try:
|
|
564
|
+
await self.delete_dialog(entity, revoke=revoke)
|
|
565
|
+
result["success"] += 1
|
|
566
|
+
result["details"].append({"id": user_id, "title": title, "status": "deleted"})
|
|
567
|
+
_log.debug("delete_all_private: deleted %s (%d)", title, user_id)
|
|
568
|
+
except Exception as exc:
|
|
569
|
+
result["failed"] += 1
|
|
570
|
+
result["details"].append({"id": user_id, "title": title, "status": "failed"})
|
|
571
|
+
_log.warning("delete_all_private: failed to delete %s (%d): %s", title, user_id, exc)
|
|
572
|
+
if delay > 0 and idx < total - 1:
|
|
573
|
+
await asyncio.sleep(delay)
|
|
574
|
+
|
|
575
|
+
return result
|
|
576
|
+
|
|
480
577
|
def conversation(
|
|
481
578
|
self: 'SoroushClient',
|
|
482
579
|
entity: 'hints.EntityLike',
|
|
@@ -0,0 +1,507 @@
|
|
|
1
|
+
"""Advanced Chat Management Mixin for SplusPy Client.
|
|
2
|
+
|
|
3
|
+
Provides raw MTProto implementations for managing channels/megagroups
|
|
4
|
+
including member retrieval, restriction, and promotion.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
import logging
|
|
11
|
+
from datetime import datetime, timezone
|
|
12
|
+
from typing import Any, Optional, Union
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger("client.chat")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ChatManagementMixin:
|
|
18
|
+
"""Mixin class providing raw MTProto chat management methods.
|
|
19
|
+
|
|
20
|
+
This mixin must be used with a Client class that provides:
|
|
21
|
+
- ``_get_client()`` → returns the underlying adapter/MTProto client
|
|
22
|
+
- ``resolve_peer(peer)`` → resolves a peer to InputPeer
|
|
23
|
+
- ``invoke(request)`` or ``client(request)`` → sends raw TL requests
|
|
24
|
+
- ``_build_user(entity)`` → builds a User model from raw entity
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
async def get_chat_members(
|
|
28
|
+
self,
|
|
29
|
+
chat_id: Union[int, str],
|
|
30
|
+
limit: int = 200,
|
|
31
|
+
) -> list[Any]:
|
|
32
|
+
"""Get a list of members in a channel/megagroup using raw MTProto.
|
|
33
|
+
|
|
34
|
+
Uses channels.GetParticipantsRequest with ChannelParticipantsRecent
|
|
35
|
+
filter to fetch the members list.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
chat_id: Channel/supergroup identifier (int ID, username, etc.).
|
|
39
|
+
limit: Maximum number of members to return (default 200).
|
|
40
|
+
Note: Telegram limits this to 200 per request. Use multiple
|
|
41
|
+
calls with offset for larger lists.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
A list of User objects for each member.
|
|
45
|
+
|
|
46
|
+
Raises:
|
|
47
|
+
RuntimeError: If the client is not connected.
|
|
48
|
+
ValueError: If the peer cannot be resolved to an InputChannel.
|
|
49
|
+
|
|
50
|
+
Example:
|
|
51
|
+
>>> members = await client.get_chat_members(-1001234567890)
|
|
52
|
+
>>> for member in members:
|
|
53
|
+
... print(member.first_name, member.id)
|
|
54
|
+
"""
|
|
55
|
+
client = self._get_client()
|
|
56
|
+
if not client:
|
|
57
|
+
raise RuntimeError("Client not connected")
|
|
58
|
+
|
|
59
|
+
try:
|
|
60
|
+
from spluspy._engine import functions, types
|
|
61
|
+
except ImportError:
|
|
62
|
+
from telethon import functions, types
|
|
63
|
+
|
|
64
|
+
# Resolve to InputChannel
|
|
65
|
+
input_channel = await self._resolve_to_input_channel(client, chat_id)
|
|
66
|
+
if input_channel is None:
|
|
67
|
+
raise ValueError(f"Cannot resolve {chat_id!r} to an InputChannel")
|
|
68
|
+
|
|
69
|
+
all_users: list[Any] = []
|
|
70
|
+
offset = 0
|
|
71
|
+
batch_size = min(limit, 200)
|
|
72
|
+
|
|
73
|
+
while len(all_users) < limit:
|
|
74
|
+
request = functions.channels.GetParticipantsRequest(
|
|
75
|
+
channel=input_channel,
|
|
76
|
+
filter=types.ChannelParticipantsRecent(),
|
|
77
|
+
offset=offset,
|
|
78
|
+
limit=batch_size,
|
|
79
|
+
hash=0,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
result = await client(request)
|
|
83
|
+
|
|
84
|
+
# Extract users from the result
|
|
85
|
+
users = getattr(result, "users", []) or []
|
|
86
|
+
participants = getattr(result, "participants", []) or []
|
|
87
|
+
|
|
88
|
+
if not participants:
|
|
89
|
+
break
|
|
90
|
+
|
|
91
|
+
# Build User objects from the users list
|
|
92
|
+
user_map = {u.id: u for u in users}
|
|
93
|
+
|
|
94
|
+
for participant in participants:
|
|
95
|
+
user_id = getattr(participant, "user_id", None)
|
|
96
|
+
if user_id and user_id in user_map:
|
|
97
|
+
raw_user = user_map[user_id]
|
|
98
|
+
user = self._build_user(raw_user)
|
|
99
|
+
all_users.append(user)
|
|
100
|
+
|
|
101
|
+
offset += len(participants)
|
|
102
|
+
|
|
103
|
+
# If we got fewer than requested, there are no more
|
|
104
|
+
if len(participants) < batch_size:
|
|
105
|
+
break
|
|
106
|
+
|
|
107
|
+
# Yield to event loop
|
|
108
|
+
await asyncio.sleep(0)
|
|
109
|
+
|
|
110
|
+
logger.debug(
|
|
111
|
+
"get_chat_members: fetched %d members from %s",
|
|
112
|
+
len(all_users), chat_id,
|
|
113
|
+
)
|
|
114
|
+
return all_users[:limit]
|
|
115
|
+
|
|
116
|
+
async def restrict_user(
|
|
117
|
+
self,
|
|
118
|
+
chat_id: Union[int, str],
|
|
119
|
+
user_id: Union[int, str],
|
|
120
|
+
send_messages: bool = False,
|
|
121
|
+
until_date: int = 0,
|
|
122
|
+
*,
|
|
123
|
+
send_media: bool = False,
|
|
124
|
+
send_stickers: bool = False,
|
|
125
|
+
send_gifs: bool = False,
|
|
126
|
+
send_inline: bool = False,
|
|
127
|
+
embed_links: bool = False,
|
|
128
|
+
send_polls: bool = False,
|
|
129
|
+
change_info: bool = False,
|
|
130
|
+
invite_users: bool = False,
|
|
131
|
+
pin_messages: bool = False,
|
|
132
|
+
manage_topics: bool = False,
|
|
133
|
+
) -> bool:
|
|
134
|
+
"""Restrict a user in a megagroup/channel using raw MTProto.
|
|
135
|
+
|
|
136
|
+
Uses channels.EditBannedRequest to apply restrictions to a user.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
chat_id: Channel/supergroup identifier.
|
|
140
|
+
user_id: User to restrict (int ID or @username).
|
|
141
|
+
send_messages: If True, user cannot send messages.
|
|
142
|
+
until_date: Unix timestamp when restriction expires.
|
|
143
|
+
0 = permanent restriction.
|
|
144
|
+
send_media: If True, user cannot send media.
|
|
145
|
+
send_stickers: If True, user cannot send stickers.
|
|
146
|
+
send_gifs: If True, user cannot send GIFs.
|
|
147
|
+
send_inline: If True, user cannot use inline bots.
|
|
148
|
+
embed_links: If True, user cannot embed links.
|
|
149
|
+
send_polls: If True, user cannot send polls.
|
|
150
|
+
change_info: If True, user cannot change chat info.
|
|
151
|
+
invite_users: If True, user cannot invite users.
|
|
152
|
+
pin_messages: If True, user cannot pin messages.
|
|
153
|
+
manage_topics: If True, user cannot manage topics.
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
True on success, False on failure.
|
|
157
|
+
|
|
158
|
+
Example:
|
|
159
|
+
>>> # Mute a user (no messages, no media)
|
|
160
|
+
>>> await client.restrict_user(chat_id, user_id,
|
|
161
|
+
... send_messages=True, send_media=True)
|
|
162
|
+
>>>
|
|
163
|
+
>>> # Temporary mute for 1 hour
|
|
164
|
+
>>> import time
|
|
165
|
+
>>> await client.restrict_user(chat_id, user_id,
|
|
166
|
+
... send_messages=True, until_date=int(time.time()) + 3600)
|
|
167
|
+
"""
|
|
168
|
+
client = self._get_client()
|
|
169
|
+
if not client:
|
|
170
|
+
logger.warning("restrict_user skipped: client not connected")
|
|
171
|
+
return False
|
|
172
|
+
|
|
173
|
+
try:
|
|
174
|
+
from spluspy._engine import functions, types
|
|
175
|
+
except ImportError:
|
|
176
|
+
from telethon import functions, types
|
|
177
|
+
|
|
178
|
+
try:
|
|
179
|
+
# Resolve the channel
|
|
180
|
+
input_channel = await self._resolve_to_input_channel(client, chat_id)
|
|
181
|
+
if input_channel is None:
|
|
182
|
+
logger.error("restrict_user: cannot resolve %s to InputChannel", chat_id)
|
|
183
|
+
return False
|
|
184
|
+
|
|
185
|
+
# Resolve the user to InputPeer
|
|
186
|
+
input_peer = await self._resolve_to_input_peer(client, user_id)
|
|
187
|
+
if input_peer is None:
|
|
188
|
+
logger.error("restrict_user: cannot resolve %s to InputPeer", user_id)
|
|
189
|
+
return False
|
|
190
|
+
|
|
191
|
+
# Convert until_date to datetime if needed
|
|
192
|
+
until_datetime = None
|
|
193
|
+
if until_date > 0:
|
|
194
|
+
until_datetime = datetime.fromtimestamp(until_date, tz=timezone.utc)
|
|
195
|
+
|
|
196
|
+
# Construct ChatBannedRights
|
|
197
|
+
# Setting a right to True means the user is BANNED from that action
|
|
198
|
+
banned_rights = types.ChatBannedRights(
|
|
199
|
+
until_date=until_datetime,
|
|
200
|
+
view_messages=False, # Never ban from viewing
|
|
201
|
+
send_messages=send_messages,
|
|
202
|
+
send_media=send_media,
|
|
203
|
+
send_stickers=send_stickers,
|
|
204
|
+
send_gifs=send_gifs,
|
|
205
|
+
send_games=False,
|
|
206
|
+
send_inline=send_inline,
|
|
207
|
+
embed_links=embed_links,
|
|
208
|
+
send_polls=send_polls,
|
|
209
|
+
change_info=change_info,
|
|
210
|
+
invite_users=invite_users,
|
|
211
|
+
pin_messages=pin_messages,
|
|
212
|
+
manage_topics=manage_topics,
|
|
213
|
+
send_photos=send_media,
|
|
214
|
+
send_videos=send_media,
|
|
215
|
+
send_roundvideos=send_media,
|
|
216
|
+
send_audios=send_media,
|
|
217
|
+
send_voices=send_media,
|
|
218
|
+
send_docs=send_media,
|
|
219
|
+
send_plain=send_messages,
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
request = functions.channels.EditBannedRequest(
|
|
223
|
+
channel=input_channel,
|
|
224
|
+
participant=input_peer,
|
|
225
|
+
banned_rights=banned_rights,
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
await client(request)
|
|
229
|
+
logger.debug(
|
|
230
|
+
"restrict_user: restricted user %s in %s",
|
|
231
|
+
user_id, chat_id,
|
|
232
|
+
)
|
|
233
|
+
return True
|
|
234
|
+
|
|
235
|
+
except Exception as exc:
|
|
236
|
+
logger.error(
|
|
237
|
+
"restrict_user(chat=%s, user=%s) failed: %s",
|
|
238
|
+
chat_id, user_id, exc,
|
|
239
|
+
)
|
|
240
|
+
return False
|
|
241
|
+
|
|
242
|
+
async def promote_member(
|
|
243
|
+
self,
|
|
244
|
+
chat_id: Union[int, str],
|
|
245
|
+
user_id: Union[int, str],
|
|
246
|
+
is_admin: bool = True,
|
|
247
|
+
privileges: Optional[dict[str, bool]] = None,
|
|
248
|
+
*,
|
|
249
|
+
rank: str = "",
|
|
250
|
+
) -> bool:
|
|
251
|
+
"""Promote or demote a user in a megagroup/channel using raw MTProto.
|
|
252
|
+
|
|
253
|
+
Uses channels.EditAdminRequest to modify a user's admin status.
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
chat_id: Channel/supergroup identifier.
|
|
257
|
+
user_id: User to promote/demote (int ID or @username).
|
|
258
|
+
is_admin: If True, promote to admin. If False, demote to regular user.
|
|
259
|
+
privileges: Optional dict of admin privileges to grant. Keys are:
|
|
260
|
+
- change_info: Can change channel/group info
|
|
261
|
+
- post_messages: Can post messages in channel
|
|
262
|
+
- edit_messages: Can edit all messages
|
|
263
|
+
- delete_messages: Can delete any messages
|
|
264
|
+
- ban_users: Can ban users
|
|
265
|
+
- invite_users: Can invite users
|
|
266
|
+
- pin_messages: Can pin messages
|
|
267
|
+
- add_admins: Can add other admins
|
|
268
|
+
- anonymous: Can be anonymous admin
|
|
269
|
+
- manage_call: Can manage group calls
|
|
270
|
+
- other: Other admin rights
|
|
271
|
+
- manage_topics: Can manage topics
|
|
272
|
+
- post_stories: Can post stories
|
|
273
|
+
- edit_stories: Can edit stories
|
|
274
|
+
- delete_stories: Can delete stories
|
|
275
|
+
|
|
276
|
+
If None and is_admin=True, applies default admin rights.
|
|
277
|
+
rank: Optional rank/title string for the admin (e.g., "Admin", "Moderator").
|
|
278
|
+
|
|
279
|
+
Returns:
|
|
280
|
+
True on success, False on failure.
|
|
281
|
+
|
|
282
|
+
Example:
|
|
283
|
+
>>> # Promote with default admin rights
|
|
284
|
+
>>> await client.promote_member(chat_id, user_id)
|
|
285
|
+
>>>
|
|
286
|
+
>>> # Promote with specific privileges
|
|
287
|
+
>>> await client.promote_member(chat_id, user_id, privileges={
|
|
288
|
+
... "delete_messages": True,
|
|
289
|
+
... "ban_users": True,
|
|
290
|
+
... "invite_users": True,
|
|
291
|
+
... })
|
|
292
|
+
>>>
|
|
293
|
+
>>> # Demote a user
|
|
294
|
+
>>> await client.promote_member(chat_id, user_id, is_admin=False)
|
|
295
|
+
"""
|
|
296
|
+
client = self._get_client()
|
|
297
|
+
if not client:
|
|
298
|
+
logger.warning("promote_member skipped: client not connected")
|
|
299
|
+
return False
|
|
300
|
+
|
|
301
|
+
try:
|
|
302
|
+
from spluspy._engine import functions, types
|
|
303
|
+
except ImportError:
|
|
304
|
+
from telethon import functions, types
|
|
305
|
+
|
|
306
|
+
try:
|
|
307
|
+
# Resolve the channel
|
|
308
|
+
input_channel = await self._resolve_to_input_channel(client, chat_id)
|
|
309
|
+
if input_channel is None:
|
|
310
|
+
logger.error("promote_member: cannot resolve %s to InputChannel", chat_id)
|
|
311
|
+
return False
|
|
312
|
+
|
|
313
|
+
# Resolve the user to InputUser
|
|
314
|
+
input_user = await self._resolve_to_input_user(client, user_id)
|
|
315
|
+
if input_user is None:
|
|
316
|
+
logger.error("promote_member: cannot resolve %s to InputUser", user_id)
|
|
317
|
+
return False
|
|
318
|
+
|
|
319
|
+
# Construct ChatAdminRights
|
|
320
|
+
if is_admin:
|
|
321
|
+
if privileges:
|
|
322
|
+
# Use provided privileges
|
|
323
|
+
admin_rights = types.ChatAdminRights(
|
|
324
|
+
change_info=privileges.get("change_info", False),
|
|
325
|
+
post_messages=privileges.get("post_messages", False),
|
|
326
|
+
edit_messages=privileges.get("edit_messages", False),
|
|
327
|
+
delete_messages=privileges.get("delete_messages", False),
|
|
328
|
+
ban_users=privileges.get("ban_users", False),
|
|
329
|
+
invite_users=privileges.get("invite_users", False),
|
|
330
|
+
pin_messages=privileges.get("pin_messages", False),
|
|
331
|
+
add_admins=privileges.get("add_admins", False),
|
|
332
|
+
anonymous=privileges.get("anonymous", False),
|
|
333
|
+
manage_call=privileges.get("manage_call", False),
|
|
334
|
+
other=privileges.get("other", False),
|
|
335
|
+
manage_topics=privileges.get("manage_topics", False),
|
|
336
|
+
post_stories=privileges.get("post_stories", False),
|
|
337
|
+
edit_stories=privileges.get("edit_stories", False),
|
|
338
|
+
delete_stories=privileges.get("delete_stories", False),
|
|
339
|
+
)
|
|
340
|
+
else:
|
|
341
|
+
# Default admin rights: delete messages, ban users, invite users
|
|
342
|
+
admin_rights = types.ChatAdminRights(
|
|
343
|
+
change_info=True,
|
|
344
|
+
post_messages=False,
|
|
345
|
+
edit_messages=False,
|
|
346
|
+
delete_messages=True,
|
|
347
|
+
ban_users=True,
|
|
348
|
+
invite_users=True,
|
|
349
|
+
pin_messages=True,
|
|
350
|
+
add_admins=False,
|
|
351
|
+
anonymous=False,
|
|
352
|
+
manage_call=False,
|
|
353
|
+
other=False,
|
|
354
|
+
manage_topics=False,
|
|
355
|
+
post_stories=False,
|
|
356
|
+
edit_stories=False,
|
|
357
|
+
delete_stories=False,
|
|
358
|
+
)
|
|
359
|
+
else:
|
|
360
|
+
# Demote: all rights False
|
|
361
|
+
admin_rights = types.ChatAdminRights(
|
|
362
|
+
change_info=False,
|
|
363
|
+
post_messages=False,
|
|
364
|
+
edit_messages=False,
|
|
365
|
+
delete_messages=False,
|
|
366
|
+
ban_users=False,
|
|
367
|
+
invite_users=False,
|
|
368
|
+
pin_messages=False,
|
|
369
|
+
add_admins=False,
|
|
370
|
+
anonymous=False,
|
|
371
|
+
manage_call=False,
|
|
372
|
+
other=False,
|
|
373
|
+
manage_topics=False,
|
|
374
|
+
post_stories=False,
|
|
375
|
+
edit_stories=False,
|
|
376
|
+
delete_stories=False,
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
request = functions.channels.EditAdminRequest(
|
|
380
|
+
channel=input_channel,
|
|
381
|
+
user_id=input_user,
|
|
382
|
+
admin_rights=admin_rights,
|
|
383
|
+
rank=rank if is_admin else "",
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
await client(request)
|
|
387
|
+
logger.debug(
|
|
388
|
+
"promote_member: %s user %s in %s",
|
|
389
|
+
"promoted" if is_admin else "demoted",
|
|
390
|
+
user_id, chat_id,
|
|
391
|
+
)
|
|
392
|
+
return True
|
|
393
|
+
|
|
394
|
+
except Exception as exc:
|
|
395
|
+
logger.error(
|
|
396
|
+
"promote_member(chat=%s, user=%s) failed: %s",
|
|
397
|
+
chat_id, user_id, exc,
|
|
398
|
+
)
|
|
399
|
+
return False
|
|
400
|
+
|
|
401
|
+
# ── Helper Methods ────────────────────────────────────────────
|
|
402
|
+
|
|
403
|
+
async def _resolve_to_input_channel(
|
|
404
|
+
self, client: Any, chat_id: Union[int, str]
|
|
405
|
+
) -> Optional[Any]:
|
|
406
|
+
"""Resolve a chat_id to an InputChannel object."""
|
|
407
|
+
try:
|
|
408
|
+
from spluspy._engine import types
|
|
409
|
+
except ImportError:
|
|
410
|
+
from telethon import types
|
|
411
|
+
|
|
412
|
+
try:
|
|
413
|
+
# Try get_input_entity first (returns InputPeer*)
|
|
414
|
+
input_peer = await client.get_input_entity(chat_id)
|
|
415
|
+
|
|
416
|
+
# Convert InputPeerChannel to InputChannel
|
|
417
|
+
if isinstance(input_peer, types.InputPeerChannel):
|
|
418
|
+
return types.InputChannel(
|
|
419
|
+
channel_id=input_peer.channel_id,
|
|
420
|
+
access_hash=input_peer.access_hash,
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
# If it's a Chat (basic group), we can't use channels.* methods
|
|
424
|
+
if isinstance(input_peer, types.InputPeerChat):
|
|
425
|
+
logger.warning(
|
|
426
|
+
"_resolve_to_input_channel: %s is a basic group, "
|
|
427
|
+
"not a channel/supergroup. channels.* methods require "
|
|
428
|
+
"a supergroup or channel.",
|
|
429
|
+
chat_id,
|
|
430
|
+
)
|
|
431
|
+
return None
|
|
432
|
+
|
|
433
|
+
except Exception:
|
|
434
|
+
pass
|
|
435
|
+
|
|
436
|
+
# Fallback: try get_entity
|
|
437
|
+
try:
|
|
438
|
+
entity = await client.get_entity(chat_id)
|
|
439
|
+
if isinstance(entity, types.Channel):
|
|
440
|
+
return types.InputChannel(
|
|
441
|
+
channel_id=entity.id,
|
|
442
|
+
access_hash=entity.access_hash,
|
|
443
|
+
)
|
|
444
|
+
except Exception:
|
|
445
|
+
pass
|
|
446
|
+
|
|
447
|
+
return None
|
|
448
|
+
|
|
449
|
+
async def _resolve_to_input_peer(
|
|
450
|
+
self, client: Any, user_id: Union[int, str]
|
|
451
|
+
) -> Optional[Any]:
|
|
452
|
+
"""Resolve a user_id to an InputPeer object."""
|
|
453
|
+
try:
|
|
454
|
+
input_peer = await client.get_input_entity(user_id)
|
|
455
|
+
return input_peer
|
|
456
|
+
except Exception:
|
|
457
|
+
pass
|
|
458
|
+
|
|
459
|
+
# Fallback: try get_entity
|
|
460
|
+
try:
|
|
461
|
+
from spluspy._engine import types
|
|
462
|
+
except ImportError:
|
|
463
|
+
from telethon import types
|
|
464
|
+
|
|
465
|
+
try:
|
|
466
|
+
entity = await client.get_entity(user_id)
|
|
467
|
+
if isinstance(entity, types.User):
|
|
468
|
+
return types.InputPeerUser(
|
|
469
|
+
user_id=entity.id,
|
|
470
|
+
access_hash=entity.access_hash,
|
|
471
|
+
)
|
|
472
|
+
except Exception:
|
|
473
|
+
pass
|
|
474
|
+
|
|
475
|
+
return None
|
|
476
|
+
|
|
477
|
+
async def _resolve_to_input_user(
|
|
478
|
+
self, client: Any, user_id: Union[int, str]
|
|
479
|
+
) -> Optional[Any]:
|
|
480
|
+
"""Resolve a user_id to an InputUser object."""
|
|
481
|
+
try:
|
|
482
|
+
from spluspy._engine import types
|
|
483
|
+
except ImportError:
|
|
484
|
+
from telethon import types
|
|
485
|
+
|
|
486
|
+
try:
|
|
487
|
+
input_peer = await client.get_input_entity(user_id)
|
|
488
|
+
if isinstance(input_peer, types.InputPeerUser):
|
|
489
|
+
return types.InputUser(
|
|
490
|
+
user_id=input_peer.user_id,
|
|
491
|
+
access_hash=input_peer.access_hash,
|
|
492
|
+
)
|
|
493
|
+
except Exception:
|
|
494
|
+
pass
|
|
495
|
+
|
|
496
|
+
# Fallback: try get_entity
|
|
497
|
+
try:
|
|
498
|
+
entity = await client.get_entity(user_id)
|
|
499
|
+
if isinstance(entity, types.User):
|
|
500
|
+
return types.InputUser(
|
|
501
|
+
user_id=entity.id,
|
|
502
|
+
access_hash=entity.access_hash,
|
|
503
|
+
)
|
|
504
|
+
except Exception:
|
|
505
|
+
pass
|
|
506
|
+
|
|
507
|
+
return None
|