spluspy 2.1.6__tar.gz → 2.1.7__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 (308) hide show
  1. {spluspy-2.1.6 → spluspy-2.1.7}/CHANGELOG.md +9 -0
  2. {spluspy-2.1.6 → spluspy-2.1.7}/PKG-INFO +1 -1
  3. {spluspy-2.1.6 → spluspy-2.1.7}/pyproject.toml +1 -1
  4. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/__version__.py +1 -1
  5. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/client/account.py +1 -0
  6. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/client/auth.py +8 -2
  7. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/client/chats.py +1 -1
  8. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/client/dialogs.py +2 -0
  9. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/client/downloads.py +9 -0
  10. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/client/messages.py +1 -0
  11. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/client/telegrambaseclient.py +10 -4
  12. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/client/updates.py +71 -37
  13. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/client/uploads.py +4 -0
  14. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/client/users.py +15 -9
  15. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/crypto/__init__.py +86 -84
  16. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/crypto/aes.py +4 -2
  17. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/crypto/libssl.py +138 -137
  18. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/extensions/binaryreader.py +236 -231
  19. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/extensions/html.py +208 -206
  20. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/extensions/messagepacker.py +122 -119
  21. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/network/connection/connection.py +2 -2
  22. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/network/connection/websocket.py +12 -8
  23. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/network/mtprotosender.py +54 -2
  24. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/network/mtprotostate.py +4 -0
  25. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/sessions/sqlite.py +394 -393
  26. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/custom/chatgetter.py +3 -2
  27. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/custom/draft.py +194 -194
  28. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/custom/message.py +4 -0
  29. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/custom/messagebutton.py +2 -0
  30. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/custom/sendergetter.py +2 -1
  31. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/version.py +1 -1
  32. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/afk.py +202 -201
  33. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/client/chat_mixin.py +973 -954
  34. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/client/client.py +26 -31
  35. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/client/conversation.py +294 -295
  36. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/client/media_mixin.py +531 -522
  37. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/config.py +2 -2
  38. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/middleware/base.py +2 -2
  39. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/mirror.py +207 -204
  40. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/network/client.py +12 -2
  41. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/network/connection.py +6 -0
  42. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/network/http_client.py +355 -347
  43. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/network/spluspy_adapter.py +495 -450
  44. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/network/transport.py +252 -249
  45. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/plugins/loader.py +3 -0
  46. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/storage/postgres.py +183 -183
  47. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/storage/redis.py +140 -140
  48. spluspy-2.1.7/spluspy/utils/logger.py +282 -0
  49. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/utils/rate_limiter.py +1 -2
  50. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/utils/resolver.py +372 -366
  51. spluspy-2.1.6/spluspy/utils/logger.py +0 -119
  52. {spluspy-2.1.6 → spluspy-2.1.7}/.gitignore +0 -0
  53. {spluspy-2.1.6 → spluspy-2.1.7}/CONTRIBUTING.md +0 -0
  54. {spluspy-2.1.6 → spluspy-2.1.7}/Dockerfile +0 -0
  55. {spluspy-2.1.6 → spluspy-2.1.7}/LICENSE +0 -0
  56. {spluspy-2.1.6 → spluspy-2.1.7}/MANIFEST.in +0 -0
  57. {spluspy-2.1.6 → spluspy-2.1.7}/README.md +0 -0
  58. {spluspy-2.1.6 → spluspy-2.1.7}/README.rst +0 -0
  59. {spluspy-2.1.6 → spluspy-2.1.7}/examples/01_hello_bot.py +0 -0
  60. {spluspy-2.1.6 → spluspy-2.1.7}/examples/02_echo_bot.py +0 -0
  61. {spluspy-2.1.6 → spluspy-2.1.7}/examples/03_command_bot.py +0 -0
  62. {spluspy-2.1.6 → spluspy-2.1.7}/examples/04_private_only.py +0 -0
  63. {spluspy-2.1.6 → spluspy-2.1.7}/examples/05_group_only.py +0 -0
  64. {spluspy-2.1.6 → spluspy-2.1.7}/examples/06_regex_bot.py +0 -0
  65. {spluspy-2.1.6 → spluspy-2.1.7}/examples/07_user_filter.py +0 -0
  66. {spluspy-2.1.6 → spluspy-2.1.7}/examples/08_combined_filters.py +0 -0
  67. {spluspy-2.1.6 → spluspy-2.1.7}/examples/09_inline_keyboard.py +0 -0
  68. {spluspy-2.1.6 → spluspy-2.1.7}/examples/10_reply_keyboard.py +0 -0
  69. {spluspy-2.1.6 → spluspy-2.1.7}/examples/11_message_actions.py +0 -0
  70. {spluspy-2.1.6 → spluspy-2.1.7}/examples/12_media_bot.py +0 -0
  71. {spluspy-2.1.6 → spluspy-2.1.7}/examples/13_fsm_bot.py +0 -0
  72. {spluspy-2.1.6 → spluspy-2.1.7}/examples/14_plugin_bot.py +0 -0
  73. {spluspy-2.1.6 → spluspy-2.1.7}/examples/15_middleware_bot.py +0 -0
  74. {spluspy-2.1.6 → spluspy-2.1.7}/examples/16_scheduler_bot.py +0 -0
  75. {spluspy-2.1.6 → spluspy-2.1.7}/examples/17_conversation.py +0 -0
  76. {spluspy-2.1.6 → spluspy-2.1.7}/examples/18_chat_management.py +0 -0
  77. {spluspy-2.1.6 → spluspy-2.1.7}/examples/19_event_types.py +0 -0
  78. {spluspy-2.1.6 → spluspy-2.1.7}/examples/20_sync_bot.py +0 -0
  79. {spluspy-2.1.6 → spluspy-2.1.7}/examples/21_album_handler.py +0 -0
  80. {spluspy-2.1.6 → spluspy-2.1.7}/examples/22_download_bot.py +0 -0
  81. {spluspy-2.1.6 → spluspy-2.1.7}/examples/23_url_button.py +0 -0
  82. {spluspy-2.1.6 → spluspy-2.1.7}/examples/24_string_session.py +0 -0
  83. {spluspy-2.1.6 → spluspy-2.1.7}/examples/25_error_handling.py +0 -0
  84. {spluspy-2.1.6 → spluspy-2.1.7}/examples/25_working_bot.py +0 -0
  85. {spluspy-2.1.6 → spluspy-2.1.7}/examples/26_message_entities.py +0 -0
  86. {spluspy-2.1.6 → spluspy-2.1.7}/examples/27_search_messages.py +0 -0
  87. {spluspy-2.1.6 → spluspy-2.1.7}/examples/28_poll_bot.py +0 -0
  88. {spluspy-2.1.6 → spluspy-2.1.7}/examples/29_dice_bot.py +0 -0
  89. {spluspy-2.1.6 → spluspy-2.1.7}/examples/30_cache_bot.py +0 -0
  90. {spluspy-2.1.6 → spluspy-2.1.7}/examples/31_reaction_bot.py +0 -0
  91. {spluspy-2.1.6 → spluspy-2.1.7}/examples/32_mark_read_bot.py +0 -0
  92. {spluspy-2.1.6 → spluspy-2.1.7}/examples/33_context_manager.py +0 -0
  93. {spluspy-2.1.6 → spluspy-2.1.7}/examples/34_user_info.py +0 -0
  94. {spluspy-2.1.6 → spluspy-2.1.7}/examples/35_chat_info.py +0 -0
  95. {spluspy-2.1.6 → spluspy-2.1.7}/examples/36_forward_copy.py +0 -0
  96. {spluspy-2.1.6 → spluspy-2.1.7}/examples/37_admin_bot.py +0 -0
  97. {spluspy-2.1.6 → spluspy-2.1.7}/examples/38_media_filters.py +0 -0
  98. {spluspy-2.1.6 → spluspy-2.1.7}/examples/39_status_bot.py +0 -0
  99. {spluspy-2.1.6 → spluspy-2.1.7}/examples/40_delete_detection.py +0 -0
  100. {spluspy-2.1.6 → spluspy-2.1.7}/examples/41_read_detection.py +0 -0
  101. {spluspy-2.1.6 → spluspy-2.1.7}/examples/42_raw_events.py +0 -0
  102. {spluspy-2.1.6 → spluspy-2.1.7}/examples/43_poll_handler.py +0 -0
  103. {spluspy-2.1.6 → spluspy-2.1.7}/examples/44_reaction_handler.py +0 -0
  104. {spluspy-2.1.6 → spluspy-2.1.7}/examples/45_typing_handler.py +0 -0
  105. {spluspy-2.1.6 → spluspy-2.1.7}/examples/46_get_messages.py +0 -0
  106. {spluspy-2.1.6 → spluspy-2.1.7}/examples/47_iter_messages.py +0 -0
  107. {spluspy-2.1.6 → spluspy-2.1.7}/examples/48_get_members.py +0 -0
  108. {spluspy-2.1.6 → spluspy-2.1.7}/examples/49_resolve_username.py +0 -0
  109. {spluspy-2.1.6 → spluspy-2.1.7}/examples/50_invoke.py +0 -0
  110. {spluspy-2.1.6 → spluspy-2.1.7}/setup.py +0 -0
  111. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/__init__.py +0 -0
  112. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/__init__.py +0 -0
  113. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/_updates/__init__.py +0 -0
  114. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/_updates/entitycache.py +0 -0
  115. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/_updates/messagebox.py +0 -0
  116. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/_updates/session.py +0 -0
  117. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/client/__init__.py +0 -0
  118. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/client/bots.py +0 -0
  119. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/client/buttons.py +0 -0
  120. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/client/messageparse.py +0 -0
  121. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/client/soroushclient.py +0 -0
  122. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/crypto/aesctr.py +0 -0
  123. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/crypto/authkey.py +0 -0
  124. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/crypto/cdndecrypter.py +0 -0
  125. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/crypto/factorization.py +0 -0
  126. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/crypto/fast_aes.pyx +0 -0
  127. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/crypto/fast_rsa.pyx +0 -0
  128. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/crypto/rsa.py +0 -0
  129. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/custom.py +0 -0
  130. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/errors/__init__.py +0 -0
  131. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/errors/common.py +0 -0
  132. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/errors/rpcbaseerrors.py +0 -0
  133. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/errors/rpcerrorlist.py +0 -0
  134. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/events/__init__.py +0 -0
  135. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/events/album.py +0 -0
  136. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/events/callbackquery.py +0 -0
  137. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/events/chataction.py +0 -0
  138. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/events/common.py +0 -0
  139. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/events/inlinequery.py +0 -0
  140. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/events/messagedeleted.py +0 -0
  141. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/events/messageedited.py +0 -0
  142. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/events/messageread.py +0 -0
  143. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/events/newmessage.py +0 -0
  144. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/events/raw.py +0 -0
  145. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/events/userupdate.py +0 -0
  146. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/extensions/__init__.py +0 -0
  147. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/extensions/markdown.py +0 -0
  148. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/functions.py +0 -0
  149. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/helpers.py +0 -0
  150. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/hints.py +0 -0
  151. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/network/__init__.py +0 -0
  152. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/network/authenticator.py +0 -0
  153. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/network/connection/__init__.py +0 -0
  154. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/network/connection/http.py +0 -0
  155. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/network/connection/tcpabridged.py +0 -0
  156. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/network/connection/tcpfull.py +0 -0
  157. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/network/connection/tcpintermediate.py +0 -0
  158. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/network/connection/tcpmtproxy.py +0 -0
  159. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/network/connection/tcpobfuscated.py +0 -0
  160. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/network/mtprotoplainsender.py +0 -0
  161. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/network/requeststate.py +0 -0
  162. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/password.py +0 -0
  163. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/requestiter.py +0 -0
  164. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/sessions/__init__.py +0 -0
  165. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/sessions/abstract.py +0 -0
  166. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/sessions/memory.py +0 -0
  167. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/sessions/string.py +0 -0
  168. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/sync.py +0 -0
  169. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/__init__.py +0 -0
  170. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/alltlobjects.py +0 -0
  171. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/core/__init__.py +0 -0
  172. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/core/gzippacked.py +0 -0
  173. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/core/messagecontainer.py +0 -0
  174. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/core/rpcresult.py +0 -0
  175. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/core/tlmessage.py +0 -0
  176. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/custom/__init__.py +0 -0
  177. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/custom/adminlogevent.py +0 -0
  178. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/custom/button.py +0 -0
  179. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/custom/conversation.py +0 -0
  180. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/custom/dialog.py +0 -0
  181. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/custom/file.py +0 -0
  182. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/custom/forward.py +0 -0
  183. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/custom/inlinebuilder.py +0 -0
  184. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/custom/inlineresult.py +0 -0
  185. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/custom/inlineresults.py +0 -0
  186. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/custom/inputsizedfile.py +0 -0
  187. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/custom/participantpermissions.py +0 -0
  188. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/custom/qrlogin.py +0 -0
  189. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/custom/types.py +0 -0
  190. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/functions/__init__.py +0 -0
  191. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/functions/account.py +0 -0
  192. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/functions/auth.py +0 -0
  193. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/functions/bots.py +0 -0
  194. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/functions/channels.py +0 -0
  195. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/functions/chatlists.py +0 -0
  196. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/functions/conference.py +0 -0
  197. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/functions/contacts.py +0 -0
  198. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/functions/folders.py +0 -0
  199. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/functions/help.py +0 -0
  200. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/functions/langpack.py +0 -0
  201. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/functions/messages.py +0 -0
  202. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/functions/payments.py +0 -0
  203. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/functions/phone.py +0 -0
  204. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/functions/photos.py +0 -0
  205. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/functions/premium.py +0 -0
  206. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/functions/stats.py +0 -0
  207. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/functions/stories.py +0 -0
  208. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/functions/thirdParty.py +0 -0
  209. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/functions/updates.py +0 -0
  210. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/functions/upload.py +0 -0
  211. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/functions/users.py +0 -0
  212. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/patched/__init__.py +0 -0
  213. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/tlobject.py +0 -0
  214. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/types/__init__.py +0 -0
  215. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/types/account.py +0 -0
  216. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/types/auth.py +0 -0
  217. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/types/bots.py +0 -0
  218. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/types/channels.py +0 -0
  219. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/types/chatlists.py +0 -0
  220. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/types/conference.py +0 -0
  221. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/types/contacts.py +0 -0
  222. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/types/help.py +0 -0
  223. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/types/messages.py +0 -0
  224. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/types/payments.py +0 -0
  225. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/types/phone.py +0 -0
  226. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/types/photos.py +0 -0
  227. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/types/premium.py +0 -0
  228. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/types/stats.py +0 -0
  229. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/types/stickers.py +0 -0
  230. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/types/storage.py +0 -0
  231. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/types/stories.py +0 -0
  232. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/types/thirdParty.py +0 -0
  233. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/types/update.py +0 -0
  234. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/types/updates.py +0 -0
  235. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/types/upload.py +0 -0
  236. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/tl/types/users.py +0 -0
  237. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/types.py +0 -0
  238. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/_engine/utils.py +0 -0
  239. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/admin.py +0 -0
  240. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/cli.py +0 -0
  241. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/client/__init__.py +0 -0
  242. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/compat.py +0 -0
  243. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/errors/__init__.py +0 -0
  244. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/errors/exceptions.py +0 -0
  245. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/events/__init__.py +0 -0
  246. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/events/album.py +0 -0
  247. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/events/base.py +0 -0
  248. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/events/callback.py +0 -0
  249. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/events/chat_action.py +0 -0
  250. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/events/edited.py +0 -0
  251. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/events/inline.py +0 -0
  252. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/events/message.py +0 -0
  253. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/events/message_deleted.py +0 -0
  254. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/events/message_read.py +0 -0
  255. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/events/user_update.py +0 -0
  256. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/filters/__init__.py +0 -0
  257. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/filters/filters.py +0 -0
  258. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/fsm/__init__.py +0 -0
  259. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/fsm/state.py +0 -0
  260. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/middleware/__init__.py +0 -0
  261. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/middleware/rate_limit.py +0 -0
  262. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/models/__init__.py +0 -0
  263. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/models/bot.py +0 -0
  264. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/models/chat.py +0 -0
  265. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/models/enums.py +0 -0
  266. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/models/media.py +0 -0
  267. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/models/message.py +0 -0
  268. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/models/objects.py +0 -0
  269. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/models/user.py +0 -0
  270. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/network/__init__.py +0 -0
  271. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/network/mtproto.py +0 -0
  272. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/network/protocols.py +0 -0
  273. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/plugins/__init__.py +0 -0
  274. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/py.typed +0 -0
  275. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/scheduler/__init__.py +0 -0
  276. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/scheduler/scheduler.py +0 -0
  277. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/session/__init__.py +0 -0
  278. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/session/base.py +0 -0
  279. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/session/memory.py +0 -0
  280. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/session/sqlite.py +0 -0
  281. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/session/string.py +0 -0
  282. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/storage/__init__.py +0 -0
  283. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/storage/base.py +0 -0
  284. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/storage/entity_cache.py +0 -0
  285. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/storage/memory.py +0 -0
  286. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/storage/sqlite.py +0 -0
  287. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/sync/__init__.py +0 -0
  288. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/sync/client.py +0 -0
  289. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/utils/__init__.py +0 -0
  290. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/utils/batch.py +0 -0
  291. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/utils/cache.py +0 -0
  292. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/utils/chat_lock.py +0 -0
  293. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/utils/file_transfer.py +0 -0
  294. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/utils/helpers.py +0 -0
  295. {spluspy-2.1.6 → spluspy-2.1.7}/spluspy/utils/version_check.py +0 -0
  296. {spluspy-2.1.6 → spluspy-2.1.7}/tests/__init__.py +0 -0
  297. {spluspy-2.1.6 → spluspy-2.1.7}/tests/test_client.py +0 -0
  298. {spluspy-2.1.6 → spluspy-2.1.7}/tests/test_filters.py +0 -0
  299. {spluspy-2.1.6 → spluspy-2.1.7}/tests/test_fsm.py +0 -0
  300. {spluspy-2.1.6 → spluspy-2.1.7}/tests/test_fsm_enhanced.py +0 -0
  301. {spluspy-2.1.6 → spluspy-2.1.7}/tests/test_middleware.py +0 -0
  302. {spluspy-2.1.6 → spluspy-2.1.7}/tests/test_rate_limiter.py +0 -0
  303. {spluspy-2.1.6 → spluspy-2.1.7}/tests/test_resolver.py +0 -0
  304. {spluspy-2.1.6 → spluspy-2.1.7}/tests/test_scheduler.py +0 -0
  305. {spluspy-2.1.6 → spluspy-2.1.7}/tests/test_session.py +0 -0
  306. {spluspy-2.1.6 → spluspy-2.1.7}/tests/test_storage.py +0 -0
  307. {spluspy-2.1.6 → spluspy-2.1.7}/tests/test_types.py +0 -0
  308. {spluspy-2.1.6 → spluspy-2.1.7}/tests/test_utils.py +0 -0
@@ -5,6 +5,15 @@ 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.7] - 2026-07-15
9
+
10
+ ### Fixed
11
+ - Fixed critical reconnection bug: `_reconnect()` now resets `_user_connected` flag so send/recv loops restart after connection drop
12
+ - Fixed `_connection_monitor` checking wrong attribute name (`_update_task` → `_updates_handle`)
13
+ - Added keepalive loop monitoring to `_connection_monitor`
14
+ - Added clean-exit detection for update loop in connection monitor
15
+ - Wrapped connection monitor body in try/except to prevent silent monitor death
16
+
8
17
  ## [2.1.3] - 2026-07-13
9
18
 
10
19
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: spluspy
3
- Version: 2.1.6
3
+ Version: 2.1.7
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
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "spluspy"
7
- version = "2.1.6"
7
+ version = "2.1.7"
8
8
  description = "A modern async Python library for interacting with Soroush Plus"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -1,6 +1,6 @@
1
1
  """SplusPy version information."""
2
2
 
3
- __version__ = "2.1.6"
3
+ __version__ = "2.1.7"
4
4
  __author__ = "Ali Mirshekari"
5
5
  __license__ = "MIT"
6
6
  __url__ = "https://github.com/Itskillmaster/spluspy"
@@ -242,5 +242,6 @@ class AccountMethods:
242
242
  async with _TakeoutClient(True, self, None) as takeout:
243
243
  takeout.success = success
244
244
  except ValueError as e:
245
+ _log.debug('Failed to end takeout: %s', e)
245
246
  return False
246
247
  return True
@@ -160,7 +160,7 @@ class AuthMethods:
160
160
  'you are accidentally reusing an existing session'
161
161
  )
162
162
  except Exception as e:
163
- pass
163
+ _log.debug('Failed to verify existing session user: %s', e)
164
164
 
165
165
  return self
166
166
 
@@ -352,6 +352,7 @@ class AuthMethods:
352
352
  except Exception as e:
353
353
  # get_me may fail on Soroush (GetUsersRequest returns 500).
354
354
  # Check authorization via GetState instead.
355
+ _log.debug('get_me failed, checking auth state: %s', e)
355
356
  if await self.is_user_authorized():
356
357
  return None
357
358
  if me:
@@ -399,6 +400,7 @@ class AuthMethods:
399
400
  try:
400
401
  me = await self.get_me()
401
402
  except Exception as e:
403
+ _log.debug('get_me failed after auth check: %s', e)
402
404
  me = None
403
405
  if me:
404
406
  return await self._on_login(me)
@@ -419,7 +421,7 @@ class AuthMethods:
419
421
  int(state.date.timestamp()), state.seq, 0),
420
422
  [])
421
423
  except Exception as e:
422
- pass
424
+ _log.debug('Failed to initialize message box during login: %s', e)
423
425
  return None
424
426
  raise
425
427
 
@@ -535,6 +537,9 @@ class AuthMethods:
535
537
  if _retry_count > 2:
536
538
  raise
537
539
  self._phone_code_hash.pop(phone, None)
540
+ self._log[__name__].debug(
541
+ "Phone code expired in ResendCodeRequest, requesting a new code"
542
+ )
538
543
  return await self.send_code_request(
539
544
  phone, force_sms=False, _retry_count=_retry_count+1)
540
545
 
@@ -603,6 +608,7 @@ class AuthMethods:
603
608
  try:
604
609
  await self(functions.auth.LogOutRequest())
605
610
  except errors.RPCError as e:
611
+ _log.debug('Logout failed: %s', e)
606
612
  return False
607
613
 
608
614
  self._mb_entity_cache.set_self_user(None, None, None)
@@ -88,7 +88,7 @@ class _ChatAction:
88
88
  await self._client(self._request)
89
89
  await asyncio.sleep(self._delay)
90
90
  except ConnectionError as e:
91
- pass
91
+ _log.debug('Connection error during typing action: %s', e)
92
92
  except asyncio.CancelledError:
93
93
  if self._auto_cancel:
94
94
  await self._client(functions.messages.SetTypingRequest(
@@ -467,6 +467,7 @@ class DialogMethods:
467
467
  ))
468
468
  except errors.PeerIdInvalidError as e:
469
469
  # Happens if we didn't have the deactivated information
470
+ _log.debug('PeerIdInvalidError when deleting chat: %s', e)
470
471
  result = None
471
472
  else:
472
473
  result = None
@@ -563,6 +564,7 @@ class DialogMethods:
563
564
  await self.delete_dialog(entity, revoke=revoke)
564
565
  result["success"] += 1
565
566
  result["details"].append({"id": user_id, "title": title, "status": "deleted"})
567
+ _log.debug("delete_all_private: deleted %s (%d)", title, user_id)
566
568
  except Exception as exc:
567
569
  result["failed"] += 1
568
570
  result["details"].append({"id": user_id, "title": title, "status": "failed"})
@@ -108,10 +108,12 @@ class _DirectDownloadIter(RequestIter):
108
108
  raise
109
109
 
110
110
  self._timed_out = True
111
+ self.client._log[__name__].debug('Got timeout while downloading file, retrying once')
111
112
  await asyncio.sleep(TIMED_OUT_SLEEP)
112
113
  return await self._request()
113
114
 
114
115
  except errors.FileMigrateError as e:
116
+ self.client._log[__name__].debug('File lives in another DC')
115
117
  self._sender = await self.client._borrow_exported_sender(e.new_dc)
116
118
  self._exported = True
117
119
  return await self._request()
@@ -123,6 +125,7 @@ class _DirectDownloadIter(RequestIter):
123
125
  or self.request.location.thumb_size != '':
124
126
  raise
125
127
 
128
+ self.client._log[__name__].debug('File ref expired during download; refetching message')
126
129
  chat, msg_id = self._msg_data
127
130
  msg = await self.client.get_messages(chat, ids=msg_id)
128
131
 
@@ -583,6 +586,7 @@ class DownloadMethods:
583
586
  if in_memory:
584
587
  return f.getvalue()
585
588
  except _CdnRedirect as e:
589
+ self._log[__name__].debug('FileCdnRedirect to CDN data center %s', e.cdn_redirect.dc_id)
586
590
  return await self._download_file(
587
591
  input_location=input_location,
588
592
  file=file,
@@ -750,8 +754,12 @@ class DownloadMethods:
750
754
  and stride % MIN_CHUNK_SIZE == 0 \
751
755
  and (limit is None or offset % limit == 0):
752
756
  cls = _DirectDownloadIter
757
+ self._log[__name__].debug('Starting direct file download in chunks of '
758
+ '%d at %d, stride %d', request_size, offset, stride)
753
759
  else:
754
760
  cls = _GenericDownloadIter
761
+ self._log[__name__].debug('Starting indirect file download in chunks of '
762
+ '%d at %d, stride %d', request_size, offset, stride)
755
763
 
756
764
  return cls(
757
765
  self,
@@ -1056,6 +1064,7 @@ class DownloadMethods:
1056
1064
  if x and not isreserved(x)
1057
1065
  )
1058
1066
  except StopIteration:
1067
+ _log.debug('No valid filename found in possible_names')
1059
1068
  name = None
1060
1069
 
1061
1070
  if not name:
@@ -311,6 +311,7 @@ class _IDsIter(RequestIter):
311
311
  functions.channels.GetMessagesRequest(self._entity, ids))
312
312
  except errors.MessageIdsEmptyError as e:
313
313
  # All IDs were invalid, use a dummy result
314
+ _log.debug('MessageIdsEmptyError for channel messages: %s', e)
314
315
  r = types.messages.MessagesNotModified(len(ids))
315
316
  else:
316
317
  r = await self.client(functions.messages.GetMessagesRequest(ids))
@@ -394,7 +394,6 @@ class SoroushPlusBaseClient(abc.ABC):
394
394
  # Used for non-sequential updates, in order to terminate all pending tasks on disconnect.
395
395
  self._sequential_updates = sequential_updates
396
396
  self._event_handler_tasks = set()
397
- self._max_handler_tasks = 50 # Limit concurrent handler tasks
398
397
 
399
398
  self._authorized = None # None = unknown, False = no, True = yes
400
399
 
@@ -575,7 +574,7 @@ class SoroushPlusBaseClient(abc.ABC):
575
574
  self_user = await utils.maybe_async(self.session.get_input_entity(self_id))
576
575
  self._mb_entity_cache.set_self_user(self_id, None, self_user.access_hash)
577
576
  except ValueError as e:
578
- pass
577
+ _base_log.debug('Could not load self user from session: %s', e)
579
578
 
580
579
  if self._catch_up:
581
580
  ss = SessionState(0, 0, False, 0, 0, 0, 0, None)
@@ -611,7 +610,7 @@ class SoroushPlusBaseClient(abc.ABC):
611
610
  # Soroush server sends config types with custom constructor IDs
612
611
  # and different field layouts than standard SoroushPlus. The response
613
612
  # is not used anyway, so we can safely ignore parse errors.
614
- pass
613
+ _base_log.debug('Ignored config parse error during init: %s', e)
615
614
 
616
615
  # Don't call get_me() here — on Soroush the auth key is not
617
616
  # registered until after login, and GetUsersRequest (used by
@@ -672,7 +671,7 @@ class SoroushPlusBaseClient(abc.ABC):
672
671
  # "Event loop stopped before Future completed."
673
672
  #
674
673
  # However, it doesn't really make a lot of sense.
675
- pass
674
+ _base_log.debug('RuntimeError during disconnect: %s', e)
676
675
 
677
676
  def set_proxy(self: 'SoroushClient', proxy: typing.Union[tuple, dict]):
678
677
  """
@@ -775,6 +774,7 @@ class SoroushPlusBaseClient(abc.ABC):
775
774
  """
776
775
  Permanently switches the current connection to the new data center.
777
776
  """
777
+ self._log[__name__].debug('Reconnecting to new data center %s', new_dc)
778
778
  dc = await self._get_dc(new_dc)
779
779
 
780
780
  await utils.maybe_async(self.session.set_dc(dc.id, dc.ip_address, dc.port))
@@ -884,6 +884,7 @@ class SoroushPlusBaseClient(abc.ABC):
884
884
  proxy=self._proxy,
885
885
  local_addr=self._local_addr
886
886
  ))
887
+ self._log[__name__].debug('Exporting auth for new borrowed sender in %s', dc)
887
888
  auth = await self(functions.auth.ExportAuthorizationRequest(dc_id))
888
889
  self._init_request.query = functions.auth.ImportAuthorizationRequest(id=auth.id, bytes=auth.bytes)
889
890
  req = functions.InvokeWithLayerRequest(LAYER, self._init_request)
@@ -899,6 +900,7 @@ class SoroushPlusBaseClient(abc.ABC):
899
900
  Once its job is over it should be `_return_exported_sender`.
900
901
  """
901
902
  async with self._borrow_sender_lock:
903
+ self._log[__name__].debug('Borrowing sender for dc_id %d', dc_id)
902
904
  state, sender = self._borrowed_senders.get(dc_id, (None, None))
903
905
 
904
906
  if state is None:
@@ -927,6 +929,7 @@ class SoroushPlusBaseClient(abc.ABC):
927
929
  been returned, the sender is cleanly disconnected.
928
930
  """
929
931
  async with self._borrow_sender_lock:
932
+ self._log[__name__].debug('Returning borrowed sender for dc_id %d', sender.dc_id)
930
933
  state, _ = self._borrowed_senders[sender.dc_id]
931
934
  state.add_return()
932
935
 
@@ -937,6 +940,8 @@ class SoroushPlusBaseClient(abc.ABC):
937
940
  async with self._borrow_sender_lock:
938
941
  for dc_id, (state, sender) in self._borrowed_senders.items():
939
942
  if state.should_disconnect():
943
+ self._log[__name__].debug(
944
+ 'Disconnecting borrowed sender for DC %d', dc_id)
940
945
 
941
946
  # Disconnect should never raise
942
947
  await sender.disconnect()
@@ -951,6 +956,7 @@ class SoroushPlusBaseClient(abc.ABC):
951
956
  await utils.maybe_async(session.set_dc(dc.id, dc.ip_address, dc.port))
952
957
  self._exported_sessions[cdn_redirect.dc_id] = session
953
958
 
959
+ self._log[__name__].debug('Creating new CDN client')
954
960
  client = self.__class__(
955
961
  session, self.api_id, self.api_hash,
956
962
  proxy=self._proxy,
@@ -4,6 +4,7 @@ import itertools
4
4
  import random
5
5
  import sys
6
6
  import time
7
+ import traceback
7
8
  import typing
8
9
  import logging
9
10
  import warnings
@@ -283,11 +284,14 @@ class UpdateMethods:
283
284
  if self._sequential_updates:
284
285
  await self._dispatch_update(updates_to_dispatch.popleft())
285
286
  else:
286
- # Respect concurrency limit to prevent memory issues with many groups
287
- if len(self._event_handler_tasks) < self._max_handler_tasks:
288
- task = self.loop.create_task(self._dispatch_update(updates_to_dispatch.popleft()))
289
- self._event_handler_tasks.add(task)
290
- task.add_done_callback(self._event_handler_tasks.discard)
287
+ # Spawn one task per loop iteration instead of draining the entire
288
+ # deque in a tight while-loop. Draining all at once creates O(n) tasks
289
+ # before the event loop gets a chance to run any of them, causing task
290
+ # count (and scheduling overhead) to grow unboundedly under high load.
291
+ # TODO if _dispatch_update fails for whatever reason, it's not logged! this should be fixed
292
+ task = self.loop.create_task(self._dispatch_update(updates_to_dispatch.popleft()))
293
+ self._event_handler_tasks.add(task)
294
+ task.add_done_callback(self._event_handler_tasks.discard)
291
295
 
292
296
  continue
293
297
 
@@ -295,6 +299,7 @@ class UpdateMethods:
295
299
 
296
300
  get_diff = self._message_box.get_difference()
297
301
  if get_diff:
302
+ self._log[__name__].debug('Getting difference for account updates')
298
303
  try:
299
304
  diff = await self(get_diff)
300
305
  except (
@@ -304,10 +309,12 @@ class UpdateMethods:
304
309
  ValueError
305
310
  ) as e:
306
311
  # SoroushPlus is having issues
312
+ self._log[__name__].debug('Cannot get difference since SoroushPlus is having issues: %s', type(e).__name__)
307
313
  self._message_box.end_difference()
308
314
  continue
309
315
  except (errors.UnauthorizedError, errors.AuthKeyError) as e:
310
316
  # Not logged in or broken authorization key, can't get difference
317
+ self._log[__name__].debug('Cannot get difference since the account is not logged in: %s', type(e).__name__)
311
318
  self._message_box.end_difference()
312
319
  if was_once_logged_in:
313
320
  self._updates_error = e
@@ -334,16 +341,21 @@ class UpdateMethods:
334
341
  # Network is likely down, but it's unclear for how long.
335
342
  # If disconnect is called this task will be cancelled along with the sleep.
336
343
  # If disconnect is not called, getting difference should be retried after a few seconds.
344
+ self._log[__name__].debug('Cannot get difference since the network is down: %s: %s', type(e).__name__, e)
337
345
  self._message_box.end_difference()
338
346
  await asyncio.sleep(5)
339
347
  continue
340
348
  updates, users, chats = self._message_box.apply_difference(diff, self._mb_entity_cache)
349
+ if updates:
350
+ self._log[__name__].debug('Got difference for account updates')
351
+
341
352
  _preprocess_updates = await self._preprocess_updates(updates, users, chats)
342
353
  updates_to_dispatch.extend(_preprocess_updates)
343
354
  continue
344
355
 
345
356
  get_diff = self._message_box.get_channel_difference(self._mb_entity_cache)
346
357
  if get_diff:
358
+ self._log[__name__].debug('Getting difference for channel %s updates', get_diff.channel.channel_id)
347
359
  try:
348
360
  diff = await self(get_diff)
349
361
  except (errors.UnauthorizedError, errors.AuthKeyError) as e:
@@ -414,6 +426,10 @@ class UpdateMethods:
414
426
  # Timeout triggered a get difference, but we have been banned in the channel since then.
415
427
  # Because we can no longer fetch updates from this channel, we should stop keeping track
416
428
  # of it entirely.
429
+ self._log[__name__].debug(
430
+ 'Account is now banned in %d so we can no longer fetch updates from it',
431
+ get_diff.channel.channel_id
432
+ )
417
433
  self._message_box.end_channel_difference(
418
434
  get_diff,
419
435
  PrematureEndReason.BANNED,
@@ -434,6 +450,10 @@ class UpdateMethods:
434
450
  )
435
451
  continue
436
452
  except OSError as e:
453
+ self._log[__name__].debug(
454
+ 'Cannot get difference for channel %d since the network is down: %s: %s',
455
+ get_diff.channel.channel_id, type(e).__name__, e
456
+ )
437
457
  self._message_box.end_channel_difference(
438
458
  get_diff,
439
459
  PrematureEndReason.TEMPORARY_SERVER_ISSUES,
@@ -443,6 +463,9 @@ class UpdateMethods:
443
463
  continue
444
464
 
445
465
  updates, users, chats = self._message_box.apply_channel_difference(get_diff, diff, self._mb_entity_cache)
466
+ if updates:
467
+ self._log[__name__].debug('Got difference for channel %d updates', get_diff.channel.channel_id)
468
+
446
469
  _preprocess_updates = await self._preprocess_updates(updates, users, chats)
447
470
  updates_to_dispatch.extend(_preprocess_updates)
448
471
  continue
@@ -475,6 +498,7 @@ class UpdateMethods:
475
498
  try:
476
499
  updates = await asyncio.wait_for(self._updates_queue.get(), deadline_delay)
477
500
  except asyncio.TimeoutError:
501
+ self._log[__name__].debug('Timeout waiting for updates expired')
478
502
  continue
479
503
  else:
480
504
  continue
@@ -625,7 +649,6 @@ class UpdateMethods:
625
649
  if conv._custom:
626
650
  await conv._check_custom(built)
627
651
 
628
- tasks = []
629
652
  for builder, callback in self._event_builders:
630
653
  event = built[type(builder)]
631
654
  if not event:
@@ -640,27 +663,32 @@ class UpdateMethods:
640
663
  if not filter:
641
664
  continue
642
665
 
643
- async def _run_cb(cb=callback, ev=event):
644
- try:
645
- await cb(ev)
646
- except errors.AlreadyInConversationError:
647
- pass
648
- except events.StopPropagation:
649
- pass
650
- except Exception as e:
651
- if not isinstance(e, asyncio.CancelledError) or self.is_connected():
652
- self._log[__name__].exception('Unhandled exception on handler')
653
-
654
- tasks.append(_run_cb())
655
-
656
- if tasks:
657
- await asyncio.gather(*tasks, return_exceptions=True)
666
+ try:
667
+ await callback(event)
668
+ except errors.AlreadyInConversationError:
669
+ name = getattr(callback, '__name__', repr(callback))
670
+ self._log[__name__].debug(
671
+ 'Event handler "%s" already has an open conversation, '
672
+ 'ignoring new one', name)
673
+ except events.StopPropagation:
674
+ name = getattr(callback, '__name__', repr(callback))
675
+ self._log[__name__].debug(
676
+ 'Event handler "%s" stopped chain of propagation '
677
+ 'for event %s.', name, type(event).__name__
678
+ )
679
+ break
680
+ except Exception as e:
681
+ if not isinstance(e, asyncio.CancelledError) or self.is_connected():
682
+ name = getattr(callback, '__name__', repr(callback))
683
+ self._log[__name__].exception('Unhandled exception on %s', name)
658
684
 
659
685
  async def _dispatch_event(self: 'SoroushClient', event):
660
686
  """
661
687
  Dispatches a single, out-of-order event. Used by `AlbumHack`.
662
688
  """
663
- tasks = []
689
+ # We're duplicating a most logic from `_dispatch_update`, but all in
690
+ # the name of speed; we don't want to make it worse for all updates
691
+ # just because albums may need it.
664
692
  for builder, callback in self._event_builders:
665
693
  if isinstance(builder, events.Raw):
666
694
  continue
@@ -676,21 +704,24 @@ class UpdateMethods:
676
704
  if not filter:
677
705
  continue
678
706
 
679
- async def _run_cb(cb=callback, ev=event):
680
- try:
681
- await cb(ev)
682
- except errors.AlreadyInConversationError:
683
- pass
684
- except events.StopPropagation:
685
- pass
686
- except Exception as e:
687
- if not isinstance(e, asyncio.CancelledError) or self.is_connected():
688
- self._log[__name__].exception('Unhandled exception on handler')
689
-
690
- tasks.append(_run_cb())
691
-
692
- if tasks:
693
- await asyncio.gather(*tasks, return_exceptions=True)
707
+ try:
708
+ await callback(event)
709
+ except errors.AlreadyInConversationError:
710
+ name = getattr(callback, '__name__', repr(callback))
711
+ self._log[__name__].debug(
712
+ 'Event handler "%s" already has an open conversation, '
713
+ 'ignoring new one', name)
714
+ except events.StopPropagation:
715
+ name = getattr(callback, '__name__', repr(callback))
716
+ self._log[__name__].debug(
717
+ 'Event handler "%s" stopped chain of propagation '
718
+ 'for event %s.', name, type(event).__name__
719
+ )
720
+ break
721
+ except Exception as e:
722
+ if not isinstance(e, asyncio.CancelledError) or self.is_connected():
723
+ name = getattr(callback, '__name__', repr(callback))
724
+ self._log[__name__].exception('Unhandled exception on %s', name)
694
725
 
695
726
  async def _handle_auto_reconnect(self: 'SoroushClient'):
696
727
  # Use lightweight get_me to let SoroushPlus know we want updates
@@ -701,12 +732,15 @@ class UpdateMethods:
701
732
  'after reconnect: %s: %s', type(e), e)
702
733
 
703
734
  try:
735
+ self._log[__name__].debug(
736
+ 'Asking for the current state after reconnect...')
704
737
 
705
738
  # If a disconnection occurs, the old known state will be
706
739
  # the latest one we were aware of, so we can catch up since
707
740
  # the most recent state we were aware of.
708
741
  await self.catch_up()
709
742
 
743
+ self._log[__name__].debug('Successfully fetched missed updates')
710
744
  except errors.RPCError as e:
711
745
  self._log[__name__].warning('Failed to get missed updates after '
712
746
  'reconnect: %r', e)
@@ -699,6 +699,8 @@ class UploadMethods:
699
699
  hash_md5 = hashlib.md5()
700
700
 
701
701
  part_count = (file_size + part_size - 1) // part_size
702
+ self._log[__name__].debug('Uploading file of %d bytes in %d chunks of %d',
703
+ file_size, part_count, part_size)
702
704
 
703
705
  pos = 0
704
706
  for part_index in range(part_count):
@@ -740,6 +742,8 @@ class UploadMethods:
740
742
 
741
743
  result = await self(request)
742
744
  if result:
745
+ self._log[__name__].debug('Uploaded %d/%d',
746
+ part_index + 1, part_count)
743
747
  if progress_callback:
744
748
  await helpers._maybe_await(progress_callback(pos, file_size))
745
749
  else:
@@ -54,6 +54,7 @@ class UserMethods:
54
54
  if diff <= 3: # Flood waits below 3 seconds are "ignored"
55
55
  self._flood_waited_requests.pop(r.CONSTRUCTOR_ID, None)
56
56
  elif diff <= flood_sleep_threshold:
57
+ self._log[__name__].debug(*_fmt_flood(diff, r, early=True))
57
58
  await asyncio.sleep(diff)
58
59
  self._flood_waited_requests.pop(r.CONSTRUCTOR_ID, None)
59
60
  else:
@@ -124,12 +125,14 @@ class UserMethods:
124
125
  e.seconds = 1
125
126
 
126
127
  if e.seconds <= self.flood_sleep_threshold:
128
+ self._log[__name__].debug(*_fmt_flood(e.seconds, request))
127
129
  await asyncio.sleep(e.seconds)
128
130
  else:
129
131
  raise
130
132
  except (errors.PhoneMigrateError, errors.NetworkMigrateError,
131
133
  errors.UserMigrateError) as e:
132
134
  last_error = e
135
+ self._log[__name__].debug('Phone migrated to %d', e.new_dc)
133
136
  should_raise = isinstance(e, (
134
137
  errors.PhoneMigrateError, errors.NetworkMigrateError
135
138
  ))
@@ -178,6 +181,7 @@ class UserMethods:
178
181
 
179
182
  return utils.get_input_peer(me, allow_self=False) if input_peer else me
180
183
  except errors.UnauthorizedError as e:
184
+ _log.debug('Not authorized to get self user: %s', e)
181
185
  return None
182
186
 
183
187
  @property
@@ -225,6 +229,7 @@ class UserMethods:
225
229
  await self(functions.updates.GetStateRequest())
226
230
  self._authorized = True
227
231
  except errors.RPCError as e:
232
+ _log.debug('Authorization check failed: %s', e)
228
233
  self._authorized = False
229
234
  except Exception:
230
235
  raise
@@ -311,7 +316,7 @@ class UserMethods:
311
316
  try:
312
317
  lists[helpers._entity_type(x)].append(x)
313
318
  except TypeError as e:
314
- pass
319
+ _log.debug('Could not determine entity type for %s: %s', x, e)
315
320
 
316
321
  users = lists[helpers._EntityType.USER]
317
322
  chats = lists[helpers._EntityType.CHAT]
@@ -424,7 +429,7 @@ class UserMethods:
424
429
  try:
425
430
  return utils.get_input_peer(peer)
426
431
  except TypeError as e:
427
- pass
432
+ _log.debug('Could not convert peer to input_peer: %s', e)
428
433
 
429
434
  # Next in priority is having a peer (or its ID) cached in-memory
430
435
  try:
@@ -432,7 +437,7 @@ class UserMethods:
432
437
  if isinstance(peer, int) or peer.SUBCLASS_OF_ID == 0x2d45687:
433
438
  return self._mb_entity_cache.get(utils.get_peer_id(peer, add_mark=False))._as_input_peer()
434
439
  except AttributeError as e:
435
- pass
440
+ _log.debug('Could not get input entity from cache: %s', e)
436
441
 
437
442
  # Then come known strings that take precedence
438
443
  if peer in ('me', 'self'):
@@ -443,7 +448,7 @@ class UserMethods:
443
448
  input_entity = await utils.maybe_async(self.session.get_input_entity(peer))
444
449
  return input_entity
445
450
  except ValueError as e:
446
- pass
451
+ _log.debug('Entity not found in session: %s', e)
447
452
 
448
453
  # Only network left to try
449
454
  if isinstance(peer, str):
@@ -475,7 +480,7 @@ class UserMethods:
475
480
  types.InputChannel(peer.channel_id, access_hash=0)]))
476
481
  return utils.get_input_peer(channels.chats[0])
477
482
  except errors.ChannelInvalidError as e:
478
- pass
483
+ _log.debug('Channel invalid: %s', e)
479
484
 
480
485
  raise ValueError(
481
486
  'Could not find the input entity for {} ({}). Please read https://'
@@ -514,6 +519,7 @@ class UserMethods:
514
519
  # 0x2d45687, 0xc91c90b6 == crc32(b'Peer') and b'InputPeer'
515
520
  peer = await self.get_input_entity(peer)
516
521
  except AttributeError as e:
522
+ _log.debug('Peer has no SUBCLASS_OF_ID: %s', e)
517
523
  peer = await self.get_input_entity(peer)
518
524
 
519
525
  if isinstance(peer, types.InputPeerSelf):
@@ -577,13 +583,13 @@ class UserMethods:
577
583
  else:
578
584
  return next(x for x in result.chats if x.id == pid)
579
585
  except StopIteration as e:
580
- pass
586
+ _log.debug('Entity not found in resolve result: %s', e)
581
587
  try:
582
588
  # Nobody with this username, maybe it's an exact name/title
583
589
  input_entity = await utils.maybe_async(self.session.get_input_entity(string))
584
590
  return await self.get_entity(input_entity)
585
591
  except ValueError as e:
586
- pass
592
+ _log.debug('Entity not found by name: %s', e)
587
593
 
588
594
  raise ValueError(
589
595
  'Cannot find any entity corresponding to "{}"'.format(string)
@@ -602,7 +608,7 @@ class UserMethods:
602
608
  elif dialog.SUBCLASS_OF_ID == 0xc91c90b6: # crc32(b'InputPeer')
603
609
  return types.InputDialogPeer(dialog)
604
610
  except AttributeError as e:
605
- pass
611
+ _log.debug('Dialog has no SUBCLASS_OF_ID: %s', e)
606
612
 
607
613
  return types.InputDialogPeer(await self.get_input_entity(dialog))
608
614
 
@@ -618,7 +624,7 @@ class UserMethods:
618
624
  notify.peer = await self.get_input_entity(notify.peer)
619
625
  return notify
620
626
  except AttributeError as e:
621
- pass
627
+ _log.debug('Notify has no SUBCLASS_OF_ID: %s', e)
622
628
 
623
629
  return types.InputNotifyPeer(await self.get_input_entity(notify))
624
630