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