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.
Files changed (307) hide show
  1. {spluspy-2.0.8 → spluspy-2.1.0}/CHANGELOG.md +15 -0
  2. {spluspy-2.0.8 → spluspy-2.1.0}/PKG-INFO +3 -1
  3. {spluspy-2.0.8 → spluspy-2.1.0}/pyproject.toml +3 -1
  4. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/__version__.py +1 -1
  5. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/client/dialogs.py +97 -0
  6. spluspy-2.1.0/spluspy/client/chat_mixin.py +507 -0
  7. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/client/client.py +315 -206
  8. spluspy-2.1.0/spluspy/client/media_mixin.py +531 -0
  9. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/config.py +2 -2
  10. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/utils/version_check.py +2 -1
  11. {spluspy-2.0.8 → spluspy-2.1.0}/.gitignore +0 -0
  12. {spluspy-2.0.8 → spluspy-2.1.0}/CONTRIBUTING.md +0 -0
  13. {spluspy-2.0.8 → spluspy-2.1.0}/Dockerfile +0 -0
  14. {spluspy-2.0.8 → spluspy-2.1.0}/LICENSE +0 -0
  15. {spluspy-2.0.8 → spluspy-2.1.0}/MANIFEST.in +0 -0
  16. {spluspy-2.0.8 → spluspy-2.1.0}/README.md +0 -0
  17. {spluspy-2.0.8 → spluspy-2.1.0}/README.rst +0 -0
  18. {spluspy-2.0.8 → spluspy-2.1.0}/examples/01_hello_bot.py +0 -0
  19. {spluspy-2.0.8 → spluspy-2.1.0}/examples/02_echo_bot.py +0 -0
  20. {spluspy-2.0.8 → spluspy-2.1.0}/examples/03_command_bot.py +0 -0
  21. {spluspy-2.0.8 → spluspy-2.1.0}/examples/04_private_only.py +0 -0
  22. {spluspy-2.0.8 → spluspy-2.1.0}/examples/05_group_only.py +0 -0
  23. {spluspy-2.0.8 → spluspy-2.1.0}/examples/06_regex_bot.py +0 -0
  24. {spluspy-2.0.8 → spluspy-2.1.0}/examples/07_user_filter.py +0 -0
  25. {spluspy-2.0.8 → spluspy-2.1.0}/examples/08_combined_filters.py +0 -0
  26. {spluspy-2.0.8 → spluspy-2.1.0}/examples/09_inline_keyboard.py +0 -0
  27. {spluspy-2.0.8 → spluspy-2.1.0}/examples/10_reply_keyboard.py +0 -0
  28. {spluspy-2.0.8 → spluspy-2.1.0}/examples/11_message_actions.py +0 -0
  29. {spluspy-2.0.8 → spluspy-2.1.0}/examples/12_media_bot.py +0 -0
  30. {spluspy-2.0.8 → spluspy-2.1.0}/examples/13_fsm_bot.py +0 -0
  31. {spluspy-2.0.8 → spluspy-2.1.0}/examples/14_plugin_bot.py +0 -0
  32. {spluspy-2.0.8 → spluspy-2.1.0}/examples/15_middleware_bot.py +0 -0
  33. {spluspy-2.0.8 → spluspy-2.1.0}/examples/16_scheduler_bot.py +0 -0
  34. {spluspy-2.0.8 → spluspy-2.1.0}/examples/17_conversation.py +0 -0
  35. {spluspy-2.0.8 → spluspy-2.1.0}/examples/18_chat_management.py +0 -0
  36. {spluspy-2.0.8 → spluspy-2.1.0}/examples/19_event_types.py +0 -0
  37. {spluspy-2.0.8 → spluspy-2.1.0}/examples/20_sync_bot.py +0 -0
  38. {spluspy-2.0.8 → spluspy-2.1.0}/examples/21_album_handler.py +0 -0
  39. {spluspy-2.0.8 → spluspy-2.1.0}/examples/22_download_bot.py +0 -0
  40. {spluspy-2.0.8 → spluspy-2.1.0}/examples/23_url_button.py +0 -0
  41. {spluspy-2.0.8 → spluspy-2.1.0}/examples/24_string_session.py +0 -0
  42. {spluspy-2.0.8 → spluspy-2.1.0}/examples/25_error_handling.py +0 -0
  43. {spluspy-2.0.8 → spluspy-2.1.0}/examples/25_working_bot.py +0 -0
  44. {spluspy-2.0.8 → spluspy-2.1.0}/examples/26_message_entities.py +0 -0
  45. {spluspy-2.0.8 → spluspy-2.1.0}/examples/27_search_messages.py +0 -0
  46. {spluspy-2.0.8 → spluspy-2.1.0}/examples/28_poll_bot.py +0 -0
  47. {spluspy-2.0.8 → spluspy-2.1.0}/examples/29_dice_bot.py +0 -0
  48. {spluspy-2.0.8 → spluspy-2.1.0}/examples/30_cache_bot.py +0 -0
  49. {spluspy-2.0.8 → spluspy-2.1.0}/examples/31_reaction_bot.py +0 -0
  50. {spluspy-2.0.8 → spluspy-2.1.0}/examples/32_mark_read_bot.py +0 -0
  51. {spluspy-2.0.8 → spluspy-2.1.0}/examples/33_context_manager.py +0 -0
  52. {spluspy-2.0.8 → spluspy-2.1.0}/examples/34_user_info.py +0 -0
  53. {spluspy-2.0.8 → spluspy-2.1.0}/examples/35_chat_info.py +0 -0
  54. {spluspy-2.0.8 → spluspy-2.1.0}/examples/36_forward_copy.py +0 -0
  55. {spluspy-2.0.8 → spluspy-2.1.0}/examples/37_admin_bot.py +0 -0
  56. {spluspy-2.0.8 → spluspy-2.1.0}/examples/38_media_filters.py +0 -0
  57. {spluspy-2.0.8 → spluspy-2.1.0}/examples/39_status_bot.py +0 -0
  58. {spluspy-2.0.8 → spluspy-2.1.0}/examples/40_delete_detection.py +0 -0
  59. {spluspy-2.0.8 → spluspy-2.1.0}/examples/41_read_detection.py +0 -0
  60. {spluspy-2.0.8 → spluspy-2.1.0}/examples/42_raw_events.py +0 -0
  61. {spluspy-2.0.8 → spluspy-2.1.0}/examples/43_poll_handler.py +0 -0
  62. {spluspy-2.0.8 → spluspy-2.1.0}/examples/44_reaction_handler.py +0 -0
  63. {spluspy-2.0.8 → spluspy-2.1.0}/examples/45_typing_handler.py +0 -0
  64. {spluspy-2.0.8 → spluspy-2.1.0}/examples/46_get_messages.py +0 -0
  65. {spluspy-2.0.8 → spluspy-2.1.0}/examples/47_iter_messages.py +0 -0
  66. {spluspy-2.0.8 → spluspy-2.1.0}/examples/48_get_members.py +0 -0
  67. {spluspy-2.0.8 → spluspy-2.1.0}/examples/49_resolve_username.py +0 -0
  68. {spluspy-2.0.8 → spluspy-2.1.0}/examples/50_invoke.py +0 -0
  69. {spluspy-2.0.8 → spluspy-2.1.0}/setup.py +0 -0
  70. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/__init__.py +0 -0
  71. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/__init__.py +0 -0
  72. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/_updates/__init__.py +0 -0
  73. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/_updates/entitycache.py +0 -0
  74. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/_updates/messagebox.py +0 -0
  75. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/_updates/session.py +0 -0
  76. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/client/__init__.py +0 -0
  77. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/client/account.py +0 -0
  78. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/client/auth.py +0 -0
  79. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/client/bots.py +0 -0
  80. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/client/buttons.py +0 -0
  81. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/client/chats.py +0 -0
  82. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/client/downloads.py +0 -0
  83. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/client/messageparse.py +0 -0
  84. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/client/messages.py +0 -0
  85. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/client/soroushclient.py +0 -0
  86. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/client/telegrambaseclient.py +0 -0
  87. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/client/updates.py +0 -0
  88. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/client/uploads.py +0 -0
  89. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/client/users.py +0 -0
  90. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/crypto/__init__.py +0 -0
  91. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/crypto/aes.py +0 -0
  92. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/crypto/aesctr.py +0 -0
  93. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/crypto/authkey.py +0 -0
  94. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/crypto/cdndecrypter.py +0 -0
  95. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/crypto/factorization.py +0 -0
  96. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/crypto/fast_aes.pyx +0 -0
  97. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/crypto/fast_rsa.pyx +0 -0
  98. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/crypto/libssl.py +0 -0
  99. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/crypto/rsa.py +0 -0
  100. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/custom.py +0 -0
  101. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/errors/__init__.py +0 -0
  102. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/errors/common.py +0 -0
  103. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/errors/rpcbaseerrors.py +0 -0
  104. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/errors/rpcerrorlist.py +0 -0
  105. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/events/__init__.py +0 -0
  106. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/events/album.py +0 -0
  107. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/events/callbackquery.py +0 -0
  108. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/events/chataction.py +0 -0
  109. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/events/common.py +0 -0
  110. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/events/inlinequery.py +0 -0
  111. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/events/messagedeleted.py +0 -0
  112. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/events/messageedited.py +0 -0
  113. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/events/messageread.py +0 -0
  114. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/events/newmessage.py +0 -0
  115. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/events/raw.py +0 -0
  116. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/events/userupdate.py +0 -0
  117. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/extensions/__init__.py +0 -0
  118. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/extensions/binaryreader.py +0 -0
  119. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/extensions/html.py +0 -0
  120. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/extensions/markdown.py +0 -0
  121. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/extensions/messagepacker.py +0 -0
  122. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/functions.py +0 -0
  123. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/helpers.py +0 -0
  124. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/hints.py +0 -0
  125. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/network/__init__.py +0 -0
  126. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/network/authenticator.py +0 -0
  127. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/network/connection/__init__.py +0 -0
  128. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/network/connection/connection.py +0 -0
  129. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/network/connection/http.py +0 -0
  130. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/network/connection/tcpabridged.py +0 -0
  131. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/network/connection/tcpfull.py +0 -0
  132. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/network/connection/tcpintermediate.py +0 -0
  133. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/network/connection/tcpmtproxy.py +0 -0
  134. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/network/connection/tcpobfuscated.py +0 -0
  135. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/network/connection/websocket.py +0 -0
  136. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/network/mtprotoplainsender.py +0 -0
  137. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/network/mtprotosender.py +0 -0
  138. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/network/mtprotostate.py +0 -0
  139. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/network/requeststate.py +0 -0
  140. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/password.py +0 -0
  141. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/requestiter.py +0 -0
  142. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/sessions/__init__.py +0 -0
  143. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/sessions/abstract.py +0 -0
  144. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/sessions/memory.py +0 -0
  145. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/sessions/sqlite.py +0 -0
  146. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/sessions/string.py +0 -0
  147. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/sync.py +0 -0
  148. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/__init__.py +0 -0
  149. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/alltlobjects.py +0 -0
  150. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/core/__init__.py +0 -0
  151. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/core/gzippacked.py +0 -0
  152. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/core/messagecontainer.py +0 -0
  153. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/core/rpcresult.py +0 -0
  154. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/core/tlmessage.py +0 -0
  155. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/custom/__init__.py +0 -0
  156. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/custom/adminlogevent.py +0 -0
  157. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/custom/button.py +0 -0
  158. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/custom/chatgetter.py +0 -0
  159. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/custom/conversation.py +0 -0
  160. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/custom/dialog.py +0 -0
  161. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/custom/draft.py +0 -0
  162. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/custom/file.py +0 -0
  163. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/custom/forward.py +0 -0
  164. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/custom/inlinebuilder.py +0 -0
  165. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/custom/inlineresult.py +0 -0
  166. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/custom/inlineresults.py +0 -0
  167. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/custom/inputsizedfile.py +0 -0
  168. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/custom/message.py +0 -0
  169. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/custom/messagebutton.py +0 -0
  170. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/custom/participantpermissions.py +0 -0
  171. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/custom/qrlogin.py +0 -0
  172. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/custom/sendergetter.py +0 -0
  173. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/custom/types.py +0 -0
  174. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/functions/__init__.py +0 -0
  175. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/functions/account.py +0 -0
  176. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/functions/auth.py +0 -0
  177. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/functions/bots.py +0 -0
  178. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/functions/channels.py +0 -0
  179. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/functions/chatlists.py +0 -0
  180. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/functions/conference.py +0 -0
  181. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/functions/contacts.py +0 -0
  182. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/functions/folders.py +0 -0
  183. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/functions/help.py +0 -0
  184. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/functions/langpack.py +0 -0
  185. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/functions/messages.py +0 -0
  186. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/functions/payments.py +0 -0
  187. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/functions/phone.py +0 -0
  188. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/functions/photos.py +0 -0
  189. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/functions/premium.py +0 -0
  190. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/functions/stats.py +0 -0
  191. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/functions/stories.py +0 -0
  192. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/functions/thirdParty.py +0 -0
  193. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/functions/updates.py +0 -0
  194. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/functions/upload.py +0 -0
  195. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/functions/users.py +0 -0
  196. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/patched/__init__.py +0 -0
  197. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/tlobject.py +0 -0
  198. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/types/__init__.py +0 -0
  199. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/types/account.py +0 -0
  200. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/types/auth.py +0 -0
  201. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/types/bots.py +0 -0
  202. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/types/channels.py +0 -0
  203. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/types/chatlists.py +0 -0
  204. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/types/conference.py +0 -0
  205. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/types/contacts.py +0 -0
  206. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/types/help.py +0 -0
  207. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/types/messages.py +0 -0
  208. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/types/payments.py +0 -0
  209. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/types/phone.py +0 -0
  210. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/types/photos.py +0 -0
  211. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/types/premium.py +0 -0
  212. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/types/stats.py +0 -0
  213. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/types/stickers.py +0 -0
  214. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/types/storage.py +0 -0
  215. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/types/stories.py +0 -0
  216. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/types/thirdParty.py +0 -0
  217. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/types/update.py +0 -0
  218. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/types/updates.py +0 -0
  219. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/types/upload.py +0 -0
  220. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/tl/types/users.py +0 -0
  221. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/types.py +0 -0
  222. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/utils.py +0 -0
  223. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/_engine/version.py +0 -0
  224. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/admin.py +0 -0
  225. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/afk.py +0 -0
  226. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/cli.py +0 -0
  227. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/client/__init__.py +0 -0
  228. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/client/conversation.py +0 -0
  229. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/compat.py +0 -0
  230. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/errors/__init__.py +0 -0
  231. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/errors/exceptions.py +0 -0
  232. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/events/__init__.py +0 -0
  233. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/events/album.py +0 -0
  234. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/events/base.py +0 -0
  235. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/events/callback.py +0 -0
  236. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/events/chat_action.py +0 -0
  237. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/events/edited.py +0 -0
  238. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/events/inline.py +0 -0
  239. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/events/message.py +0 -0
  240. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/events/message_deleted.py +0 -0
  241. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/events/message_read.py +0 -0
  242. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/events/user_update.py +0 -0
  243. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/filters/__init__.py +0 -0
  244. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/filters/filters.py +0 -0
  245. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/fsm/__init__.py +0 -0
  246. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/fsm/state.py +0 -0
  247. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/middleware/__init__.py +0 -0
  248. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/middleware/base.py +0 -0
  249. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/middleware/rate_limit.py +0 -0
  250. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/mirror.py +0 -0
  251. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/models/__init__.py +0 -0
  252. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/models/bot.py +0 -0
  253. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/models/chat.py +0 -0
  254. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/models/enums.py +0 -0
  255. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/models/media.py +0 -0
  256. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/models/message.py +0 -0
  257. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/models/objects.py +0 -0
  258. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/models/user.py +0 -0
  259. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/network/__init__.py +0 -0
  260. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/network/client.py +0 -0
  261. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/network/connection.py +0 -0
  262. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/network/http_client.py +0 -0
  263. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/network/mtproto.py +0 -0
  264. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/network/protocols.py +0 -0
  265. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/network/spluspy_adapter.py +0 -0
  266. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/network/transport.py +0 -0
  267. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/plugins/__init__.py +0 -0
  268. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/plugins/loader.py +0 -0
  269. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/py.typed +0 -0
  270. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/scheduler/__init__.py +0 -0
  271. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/scheduler/scheduler.py +0 -0
  272. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/session/__init__.py +0 -0
  273. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/session/base.py +0 -0
  274. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/session/memory.py +0 -0
  275. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/session/sqlite.py +0 -0
  276. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/session/string.py +0 -0
  277. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/storage/__init__.py +0 -0
  278. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/storage/base.py +0 -0
  279. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/storage/entity_cache.py +0 -0
  280. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/storage/memory.py +0 -0
  281. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/storage/postgres.py +0 -0
  282. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/storage/redis.py +0 -0
  283. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/storage/sqlite.py +0 -0
  284. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/sync/__init__.py +0 -0
  285. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/sync/client.py +0 -0
  286. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/utils/__init__.py +0 -0
  287. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/utils/batch.py +0 -0
  288. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/utils/cache.py +0 -0
  289. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/utils/chat_lock.py +0 -0
  290. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/utils/file_transfer.py +0 -0
  291. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/utils/helpers.py +0 -0
  292. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/utils/logger.py +0 -0
  293. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/utils/rate_limiter.py +0 -0
  294. {spluspy-2.0.8 → spluspy-2.1.0}/spluspy/utils/resolver.py +0 -0
  295. {spluspy-2.0.8 → spluspy-2.1.0}/tests/__init__.py +0 -0
  296. {spluspy-2.0.8 → spluspy-2.1.0}/tests/test_client.py +0 -0
  297. {spluspy-2.0.8 → spluspy-2.1.0}/tests/test_filters.py +0 -0
  298. {spluspy-2.0.8 → spluspy-2.1.0}/tests/test_fsm.py +0 -0
  299. {spluspy-2.0.8 → spluspy-2.1.0}/tests/test_fsm_enhanced.py +0 -0
  300. {spluspy-2.0.8 → spluspy-2.1.0}/tests/test_middleware.py +0 -0
  301. {spluspy-2.0.8 → spluspy-2.1.0}/tests/test_rate_limiter.py +0 -0
  302. {spluspy-2.0.8 → spluspy-2.1.0}/tests/test_resolver.py +0 -0
  303. {spluspy-2.0.8 → spluspy-2.1.0}/tests/test_scheduler.py +0 -0
  304. {spluspy-2.0.8 → spluspy-2.1.0}/tests/test_session.py +0 -0
  305. {spluspy-2.0.8 → spluspy-2.1.0}/tests/test_storage.py +0 -0
  306. {spluspy-2.0.8 → spluspy-2.1.0}/tests/test_types.py +0 -0
  307. {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.8
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.8"
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]
@@ -1,6 +1,6 @@
1
1
  """SplusPy version information."""
2
2
 
3
- __version__ = "2.0.8"
3
+ __version__ = "2.1.0"
4
4
  __author__ = "Ali Mirshekari"
5
5
  __license__ = "MIT"
6
6
  __url__ = "https://github.com/Itskillmaster/spluspy"
@@ -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