spluspy 2.2.7__tar.gz → 2.2.8__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {spluspy-2.2.7/spluspy.egg-info → spluspy-2.2.8}/PKG-INFO +3 -2
- {spluspy-2.2.7 → spluspy-2.2.8}/README.md +2 -1
- {spluspy-2.2.7 → spluspy-2.2.8}/pyproject.toml +1 -1
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/__init__.py +2 -1
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/client/downloads.py +13 -2
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/client/extra.py +16 -3
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/client/telegrambaseclient.py +1 -1
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/client/uploads.py +111 -90
- spluspy-2.2.8/spluspy/enums.py +144 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/network/mtprotosender.py +51 -2
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/custom/message.py +22 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/utils.py +1 -9
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/version.py +1 -1
- {spluspy-2.2.7 → spluspy-2.2.8/spluspy.egg-info}/PKG-INFO +3 -2
- spluspy-2.2.7/spluspy/enums.py +0 -17
- {spluspy-2.2.7 → spluspy-2.2.8}/LICENSE +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/setup.cfg +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/_updates/__init__.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/_updates/entitycache.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/_updates/messagebox.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/_updates/session.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/admin.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/client/__init__.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/client/account.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/client/auth.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/client/buttons.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/client/chats.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/client/dialogs.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/client/messageparse.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/client/messages.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/client/soroushclient.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/client/updates.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/client/users.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/crypto/__init__.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/crypto/aes.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/crypto/aesctr.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/crypto/authkey.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/crypto/cdndecrypter.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/crypto/factorization.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/crypto/libssl.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/crypto/rsa.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/custom.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/errors/__init__.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/errors/common.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/errors/exceptions.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/errors/rpcbaseerrors.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/errors/rpcerrorlist.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/events/__init__.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/events/album.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/events/chataction.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/events/common.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/events/messagedeleted.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/events/messageedited.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/events/messageread.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/events/newmessage.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/events/raw.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/events/userupdate.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/extensions/__init__.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/extensions/binaryreader.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/extensions/html.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/extensions/markdown.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/extensions/messagepacker.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/filters.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/functions.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/helpers.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/hints.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/network/__init__.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/network/authenticator.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/network/connection/__init__.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/network/connection/connection.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/network/connection/http.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/network/connection/tcpabridged.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/network/connection/tcpfull.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/network/connection/tcpintermediate.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/network/connection/tcpmtproxy.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/network/connection/tcpobfuscated.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/network/connection/websocket.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/network/mtprotoplainsender.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/network/mtprotostate.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/network/requeststate.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/password.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/requestiter.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/resolvers.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/sessions/__init__.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/sessions/abstract.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/sessions/memory.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/sessions/sqlite.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/sessions/string.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/sync.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/__init__.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/alltlobjects.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/core/__init__.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/core/gzippacked.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/core/messagecontainer.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/core/rpcresult.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/core/tlmessage.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/custom/__init__.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/custom/adminlogevent.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/custom/button.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/custom/chatgetter.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/custom/conversation.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/custom/dialog.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/custom/draft.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/custom/file.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/custom/forward.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/custom/inputsizedfile.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/custom/messagebutton.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/custom/participantpermissions.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/custom/qrlogin.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/custom/sendergetter.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/custom/types.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/functions/__init__.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/functions/account.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/functions/auth.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/functions/bots.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/functions/channels.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/functions/chatlists.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/functions/conference.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/functions/contacts.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/functions/folders.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/functions/help.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/functions/langpack.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/functions/messages.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/functions/payments.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/functions/phone.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/functions/photos.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/functions/premium.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/functions/stats.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/functions/stories.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/functions/thirdParty.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/functions/updates.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/functions/upload.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/functions/users.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/patched/__init__.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/tlobject.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/types/__init__.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/types/account.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/types/auth.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/types/bots.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/types/channels.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/types/chatlists.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/types/conference.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/types/contacts.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/types/help.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/types/messages.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/types/payments.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/types/phone.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/types/photos.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/types/premium.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/types/stats.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/types/stickers.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/types/storage.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/types/stories.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/types/thirdParty.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/types/update.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/types/updates.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/types/upload.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy/tl/types/users.py +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy.egg-info/SOURCES.txt +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy.egg-info/dependency_links.txt +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy.egg-info/requires.txt +0 -0
- {spluspy-2.2.7 → spluspy-2.2.8}/spluspy.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: spluspy
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.8
|
|
4
4
|
Summary: High-performance Python Self-Bot Framework for Soroush Plus
|
|
5
5
|
Author: Ali Mirshekari
|
|
6
6
|
License: MIT
|
|
@@ -26,7 +26,8 @@ Requires-Dist: rsa
|
|
|
26
26
|
Requires-Dist: pyaes
|
|
27
27
|
Dynamic: license-file
|
|
28
28
|
|
|
29
|
-
# SplusPy v2.2.
|
|
29
|
+
# SplusPy v2.2.8
|
|
30
|
+
|
|
30
31
|
|
|
31
32
|
**A fast, minimal, and fully-featured self-bot framework**
|
|
32
33
|
|
|
@@ -4,11 +4,12 @@ from .tl.custom import Button
|
|
|
4
4
|
from .tl import patched as _ # import for its side-effects
|
|
5
5
|
from . import version, events, utils, errors, functions, custom
|
|
6
6
|
from .filters import filters
|
|
7
|
+
from .enums import MessageMediaType
|
|
7
8
|
|
|
8
9
|
__version__ = version.__version__
|
|
9
10
|
|
|
10
11
|
__all__ = [
|
|
11
|
-
'Client', 'Button', 'filters',
|
|
12
|
+
'Client', 'Button', 'filters', 'MessageMediaType',
|
|
12
13
|
'functions', 'custom', 'errors',
|
|
13
14
|
'events', 'utils', 'connection'
|
|
14
15
|
]
|
|
@@ -118,6 +118,17 @@ class _DirectDownloadIter(RequestIter):
|
|
|
118
118
|
self._exported = True
|
|
119
119
|
return await self._request()
|
|
120
120
|
|
|
121
|
+
except errors.RPCError as e:
|
|
122
|
+
err_msg = str(e).upper()
|
|
123
|
+
if "FILE_REQUEST_RECEIVED_ON_CONNECTION_SERVER" in err_msg or "422" in err_msg:
|
|
124
|
+
self.client._log[__name__].info('File download must be on Media DC; switching to DC 5')
|
|
125
|
+
if self._exported and getattr(self, '_sender', None):
|
|
126
|
+
await self.client._return_exported_sender(self._sender)
|
|
127
|
+
self._sender = await self.client._borrow_exported_sender(5)
|
|
128
|
+
self._exported = True
|
|
129
|
+
return await self._request()
|
|
130
|
+
raise
|
|
131
|
+
|
|
121
132
|
except (errors.FilerefUpgradeNeededError, errors.FileReferenceExpiredError) as e:
|
|
122
133
|
# Only implemented for documents which are the ones that may take that long to download
|
|
123
134
|
if not self._msg_data \
|
|
@@ -142,7 +153,7 @@ class _DirectDownloadIter(RequestIter):
|
|
|
142
153
|
return await self._request()
|
|
143
154
|
|
|
144
155
|
async def close(self):
|
|
145
|
-
if
|
|
156
|
+
if getattr(self, '_sender', None) is None:
|
|
146
157
|
return
|
|
147
158
|
|
|
148
159
|
try:
|
|
@@ -1093,4 +1104,4 @@ class DownloadMethods:
|
|
|
1093
1104
|
return result
|
|
1094
1105
|
i += 1
|
|
1095
1106
|
|
|
1096
|
-
# endregion
|
|
1107
|
+
# endregion
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import typing
|
|
2
2
|
from ..tl import functions, types
|
|
3
|
-
from .. import helpers, utils, hints
|
|
3
|
+
from .. import helpers, utils, hints, errors
|
|
4
4
|
|
|
5
5
|
if typing.TYPE_CHECKING:
|
|
6
6
|
from .soroushclient import SoroushClient
|
|
@@ -224,9 +224,9 @@ class ExtraMethods:
|
|
|
224
224
|
The full chat info (FullChannel or FullChat).
|
|
225
225
|
"""
|
|
226
226
|
entity = await self.get_input_entity(chat_id)
|
|
227
|
-
|
|
227
|
+
try:
|
|
228
228
|
return await self(functions.channels.GetFullChannelRequest(entity))
|
|
229
|
-
|
|
229
|
+
except errors.BadRequestError:
|
|
230
230
|
return await self(functions.messages.GetFullChatRequest(entity.chat_id))
|
|
231
231
|
|
|
232
232
|
async def get_chat_info(self: 'SoroushClient', chat_id):
|
|
@@ -237,10 +237,23 @@ class ExtraMethods:
|
|
|
237
237
|
"""Alias for get_entity. Returns the entity for the given user."""
|
|
238
238
|
return await self.get_entity(user_id)
|
|
239
239
|
|
|
240
|
+
async def get_full_user(self: 'SoroushClient', user_id):
|
|
241
|
+
"""Fetches full user profile information."""
|
|
242
|
+
try:
|
|
243
|
+
entity = await self.get_input_entity(user_id)
|
|
244
|
+
return await self(functions.users.GetFullUserRequest(id=entity))
|
|
245
|
+
except Exception:
|
|
246
|
+
return None
|
|
247
|
+
|
|
240
248
|
async def get_full_chat(self: 'SoroushClient', chat_id):
|
|
241
249
|
"""Alias for get_chat. Returns full chat/channel info."""
|
|
242
250
|
return await self.get_chat(chat_id)
|
|
243
251
|
|
|
252
|
+
async def is_public_chat(self: 'SoroushClient', chat_id: 'hints.EntityLike') -> bool:
|
|
253
|
+
"""Check if a chat is public (has a username) or not."""
|
|
254
|
+
entity = await self.get_entity(chat_id)
|
|
255
|
+
return bool(getattr(entity, 'username', None))
|
|
256
|
+
|
|
244
257
|
async def get_dialogs(
|
|
245
258
|
self: 'SoroushClient',
|
|
246
259
|
limit: int = 0,
|
|
@@ -855,7 +855,7 @@ class SoroushPlusBaseClient(abc.ABC):
|
|
|
855
855
|
# Soroush: map all DCs to splus.ir servers
|
|
856
856
|
if not cdn:
|
|
857
857
|
domain = '.splus.ir'
|
|
858
|
-
if dc_id in (1, 2, 3, 4
|
|
858
|
+
if dc_id in (1, 2, 3, 4):
|
|
859
859
|
ip = f'im-server{domain}'
|
|
860
860
|
elif dc_id == 6:
|
|
861
861
|
ip = f'im-server{domain}'
|
|
@@ -9,7 +9,7 @@ from io import BytesIO
|
|
|
9
9
|
|
|
10
10
|
from ..crypto import AES
|
|
11
11
|
|
|
12
|
-
from .. import utils, helpers, hints
|
|
12
|
+
from .. import utils, helpers, hints, errors
|
|
13
13
|
from ..tl import types, functions, custom
|
|
14
14
|
|
|
15
15
|
try:
|
|
@@ -666,96 +666,117 @@ class UploadMethods:
|
|
|
666
666
|
if isinstance(file, (types.InputFile, types.InputFileBig)):
|
|
667
667
|
return file # Already uploaded
|
|
668
668
|
|
|
669
|
+
media_sender = None
|
|
670
|
+
media_dc = 5 # دیتاسنتر اختصاصی فایلهای سروش پلاس
|
|
671
|
+
|
|
669
672
|
pos = 0
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
if not part_size_kb:
|
|
675
|
-
part_size_kb = utils.get_appropriated_part_size(file_size)
|
|
676
|
-
|
|
677
|
-
if part_size_kb > 512:
|
|
678
|
-
raise ValueError('The part size must be less or equal to 512KB')
|
|
679
|
-
|
|
680
|
-
part_size = int(part_size_kb * 1024)
|
|
681
|
-
if part_size % 1024 != 0:
|
|
682
|
-
raise ValueError(
|
|
683
|
-
'The part size must be evenly divisible by 1024')
|
|
684
|
-
|
|
685
|
-
# Set a default file name if None was specified
|
|
686
|
-
file_id = helpers.generate_random_long()
|
|
687
|
-
if not file_name:
|
|
688
|
-
file_name = stream.name or str(file_id)
|
|
689
|
-
|
|
690
|
-
# If the file name lacks extension, add it if possible.
|
|
691
|
-
# Else SoroushPlus complains with `PHOTO_EXT_INVALID_ERROR`
|
|
692
|
-
# even if the uploaded image is indeed a photo.
|
|
693
|
-
if not os.path.splitext(file_name)[-1]:
|
|
694
|
-
file_name += utils._get_extension(stream)
|
|
695
|
-
|
|
696
|
-
# Determine whether the file is too big (over 10MB) or not
|
|
697
|
-
# SoroushPlus does make a distinction between smaller or larger files
|
|
698
|
-
is_big = file_size > 10 * 1024 * 1024
|
|
699
|
-
hash_md5 = hashlib.md5()
|
|
700
|
-
|
|
701
|
-
part_count = (file_size + part_size - 1) // part_size
|
|
702
|
-
self._log[__name__].info('Uploading file of %d bytes in %d chunks of %d',
|
|
703
|
-
file_size, part_count, part_size)
|
|
704
|
-
|
|
705
|
-
pos = 0
|
|
706
|
-
for part_index in range(part_count):
|
|
707
|
-
# Read the file by in chunks of size part_size
|
|
708
|
-
part = await helpers._maybe_await(stream.read(part_size))
|
|
709
|
-
|
|
710
|
-
if not isinstance(part, bytes):
|
|
711
|
-
raise TypeError(
|
|
712
|
-
'file descriptor returned {}, not bytes (you must '
|
|
713
|
-
'open the file in bytes mode)'.format(type(part)))
|
|
714
|
-
|
|
715
|
-
# `file_size` could be wrong in which case `part` may not be
|
|
716
|
-
# `part_size` before reaching the end.
|
|
717
|
-
if len(part) != part_size and part_index < part_count - 1:
|
|
718
|
-
raise ValueError(
|
|
719
|
-
'read less than {} before reaching the end; either '
|
|
720
|
-
'`file_size` or `read` are wrong'.format(part_size))
|
|
721
|
-
|
|
722
|
-
pos += len(part)
|
|
723
|
-
|
|
724
|
-
# Encryption part if needed
|
|
725
|
-
if key and iv:
|
|
726
|
-
part = AES.encrypt_ige(part, key, iv)
|
|
727
|
-
|
|
728
|
-
if not is_big:
|
|
729
|
-
# Bit odd that MD5 is only needed for small files and not
|
|
730
|
-
# big ones with more chance for corruption, but that's
|
|
731
|
-
# what SoroushPlus wants.
|
|
732
|
-
hash_md5.update(part)
|
|
733
|
-
|
|
734
|
-
# The SavePartRequest is different depending on whether
|
|
735
|
-
# the file is too large or not (over or less than 10MB)
|
|
736
|
-
if is_big:
|
|
737
|
-
request = functions.upload.SaveBigFilePartRequest(
|
|
738
|
-
file_id, part_index, part_count, part)
|
|
739
|
-
else:
|
|
740
|
-
request = functions.upload.SaveFilePartRequest(
|
|
741
|
-
file_id, part_index, part)
|
|
742
|
-
|
|
743
|
-
result = await self(request)
|
|
744
|
-
if result:
|
|
745
|
-
self._log[__name__].debug('Uploaded %d/%d',
|
|
746
|
-
part_index + 1, part_count)
|
|
747
|
-
if progress_callback:
|
|
748
|
-
await helpers._maybe_await(progress_callback(pos, file_size))
|
|
749
|
-
else:
|
|
750
|
-
raise RuntimeError(
|
|
751
|
-
'Failed to upload file part {}.'.format(part_index))
|
|
673
|
+
try:
|
|
674
|
+
async with helpers._FileStream(file, file_size=file_size) as stream:
|
|
675
|
+
# Opening the stream will determine the correct file size
|
|
676
|
+
file_size = stream.file_size
|
|
752
677
|
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
678
|
+
if not part_size_kb:
|
|
679
|
+
part_size_kb = utils.get_appropriated_part_size(file_size)
|
|
680
|
+
|
|
681
|
+
if part_size_kb > 512:
|
|
682
|
+
raise ValueError('The part size must be less or equal to 512KB')
|
|
683
|
+
|
|
684
|
+
part_size = int(part_size_kb * 1024)
|
|
685
|
+
if part_size % 1024 != 0:
|
|
686
|
+
raise ValueError(
|
|
687
|
+
'The part size must be evenly divisible by 1024')
|
|
688
|
+
|
|
689
|
+
# Set a default file name if None was specified
|
|
690
|
+
file_id = helpers.generate_random_long()
|
|
691
|
+
if not file_name:
|
|
692
|
+
file_name = stream.name or str(file_id)
|
|
693
|
+
|
|
694
|
+
# If the file name lacks extension, add it if possible.
|
|
695
|
+
# Else SoroushPlus complains with `PHOTO_EXT_INVALID_ERROR`
|
|
696
|
+
# even if the uploaded image is indeed a photo.
|
|
697
|
+
if not os.path.splitext(file_name)[-1]:
|
|
698
|
+
file_name += utils._get_extension(stream)
|
|
699
|
+
|
|
700
|
+
# Determine whether the file is too big (over 10MB) or not
|
|
701
|
+
# SoroushPlus does make a distinction between smaller or larger files
|
|
702
|
+
is_big = file_size > 10 * 1024 * 1024
|
|
703
|
+
hash_md5 = hashlib.md5()
|
|
704
|
+
|
|
705
|
+
part_count = (file_size + part_size - 1) // part_size
|
|
706
|
+
self._log[__name__].info('Uploading file of %d bytes in %d chunks of %d',
|
|
707
|
+
file_size, part_count, part_size)
|
|
708
|
+
|
|
709
|
+
pos = 0
|
|
710
|
+
for part_index in range(part_count):
|
|
711
|
+
# Read the file by in chunks of size part_size
|
|
712
|
+
part = await helpers._maybe_await(stream.read(part_size))
|
|
713
|
+
|
|
714
|
+
if not isinstance(part, bytes):
|
|
715
|
+
raise TypeError(
|
|
716
|
+
'file descriptor returned {}, not bytes (you must '
|
|
717
|
+
'open the file in bytes mode)'.format(type(part)))
|
|
718
|
+
|
|
719
|
+
# `file_size` could be wrong in which case `part` may not be
|
|
720
|
+
# `part_size` before reaching the end.
|
|
721
|
+
if len(part) != part_size and part_index < part_count - 1:
|
|
722
|
+
raise ValueError(
|
|
723
|
+
'read less than {} before reaching the end; either '
|
|
724
|
+
'`file_size` or `read` are wrong'.format(part_size))
|
|
725
|
+
|
|
726
|
+
pos += len(part)
|
|
727
|
+
|
|
728
|
+
# Encryption part if needed
|
|
729
|
+
if key and iv:
|
|
730
|
+
part = AES.encrypt_ige(part, key, iv)
|
|
731
|
+
|
|
732
|
+
if not is_big:
|
|
733
|
+
# Bit odd that MD5 is only needed for small files and not
|
|
734
|
+
# big ones with more chance for corruption, but that's
|
|
735
|
+
# what SoroushPlus wants.
|
|
736
|
+
hash_md5.update(part)
|
|
737
|
+
|
|
738
|
+
# The SavePartRequest is different depending on whether
|
|
739
|
+
# the file is too large or not (over or less than 10MB)
|
|
740
|
+
if is_big:
|
|
741
|
+
request = functions.upload.SaveBigFilePartRequest(
|
|
742
|
+
file_id, part_index, part_count, part)
|
|
743
|
+
else:
|
|
744
|
+
request = functions.upload.SaveFilePartRequest(
|
|
745
|
+
file_id, part_index, part)
|
|
746
|
+
|
|
747
|
+
# ارسال ریکوئست آپلود با هندل کردن اتصال سرور مدیا
|
|
748
|
+
if media_sender:
|
|
749
|
+
result = await self._call(media_sender, request)
|
|
750
|
+
else:
|
|
751
|
+
try:
|
|
752
|
+
result = await self(request)
|
|
753
|
+
except errors.RPCError as e:
|
|
754
|
+
err_msg = str(e).upper()
|
|
755
|
+
if "FILE_REQUEST_RECEIVED_ON_CONNECTION_SERVER" in err_msg or "422" in err_msg:
|
|
756
|
+
self._log[__name__].info('Switching to Media DC (DC 5) for upload...')
|
|
757
|
+
media_sender = await self._borrow_exported_sender(media_dc)
|
|
758
|
+
result = await self._call(media_sender, request)
|
|
759
|
+
else:
|
|
760
|
+
raise
|
|
761
|
+
|
|
762
|
+
if result:
|
|
763
|
+
self._log[__name__].debug('Uploaded %d/%d',
|
|
764
|
+
part_index + 1, part_count)
|
|
765
|
+
if progress_callback:
|
|
766
|
+
await helpers._maybe_await(progress_callback(pos, file_size))
|
|
767
|
+
else:
|
|
768
|
+
raise RuntimeError(
|
|
769
|
+
'Failed to upload file part {}.'.format(part_index))
|
|
770
|
+
|
|
771
|
+
if is_big:
|
|
772
|
+
return types.InputFileBig(file_id, part_count, file_name)
|
|
773
|
+
else:
|
|
774
|
+
return custom.InputSizedFile(
|
|
775
|
+
file_id, part_count, file_name, md5=hash_md5, size=file_size
|
|
776
|
+
)
|
|
777
|
+
finally:
|
|
778
|
+
if media_sender:
|
|
779
|
+
await self._return_exported_sender(media_sender)
|
|
759
780
|
|
|
760
781
|
# endregion
|
|
761
782
|
|
|
@@ -864,4 +885,4 @@ class UploadMethods:
|
|
|
864
885
|
)
|
|
865
886
|
return file_handle, media, as_image
|
|
866
887
|
|
|
867
|
-
# endregion
|
|
888
|
+
# endregion
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class MessageMediaType(Enum):
|
|
5
|
+
PHOTO = "photo"
|
|
6
|
+
VIDEO = "video"
|
|
7
|
+
VIDEO_NOTE = "video_note"
|
|
8
|
+
DOCUMENT = "document"
|
|
9
|
+
AUDIO = "audio"
|
|
10
|
+
VOICE = "voice"
|
|
11
|
+
STICKER = "sticker"
|
|
12
|
+
ANIMATION = "animation"
|
|
13
|
+
GIF = "gif"
|
|
14
|
+
WEB_PAGE = "web_page"
|
|
15
|
+
GEO = "geo"
|
|
16
|
+
GEO_LIVE = "geo_live"
|
|
17
|
+
CONTACT = "contact"
|
|
18
|
+
GAME = "game"
|
|
19
|
+
POLL = "poll"
|
|
20
|
+
DICE = "dice"
|
|
21
|
+
VENUE = "venue"
|
|
22
|
+
STORY = "story"
|
|
23
|
+
GIVEAWAY = "giveaway"
|
|
24
|
+
INVOICE = "invoice"
|
|
25
|
+
UNSUPPORTED = "unsupported"
|
|
26
|
+
EMPTY = "empty"
|
|
27
|
+
|
|
28
|
+
@classmethod
|
|
29
|
+
def from_media(cls, media):
|
|
30
|
+
"""
|
|
31
|
+
Detect MessageMediaType from a MessageMedia object.
|
|
32
|
+
|
|
33
|
+
Usage:
|
|
34
|
+
from spluspy.enums import MessageMediaType
|
|
35
|
+
media_type = MessageMediaType.from_media(message.media)
|
|
36
|
+
"""
|
|
37
|
+
from .tl import types
|
|
38
|
+
|
|
39
|
+
if media is None or isinstance(media, types.MessageMediaEmpty):
|
|
40
|
+
return cls.EMPTY
|
|
41
|
+
|
|
42
|
+
if isinstance(media, types.MessageMediaPhoto):
|
|
43
|
+
return cls.PHOTO
|
|
44
|
+
|
|
45
|
+
if isinstance(media, types.MessageMediaDocument):
|
|
46
|
+
doc = media.document
|
|
47
|
+
if doc is None or isinstance(doc, types.DocumentEmpty):
|
|
48
|
+
return cls.DOCUMENT
|
|
49
|
+
|
|
50
|
+
attrs = {type(a): a for a in doc.attributes}
|
|
51
|
+
|
|
52
|
+
if types.DocumentAttributeSticker in attrs:
|
|
53
|
+
return cls.STICKER
|
|
54
|
+
|
|
55
|
+
if types.DocumentAttributeAnimated in attrs:
|
|
56
|
+
if types.DocumentAttributeVideo in attrs:
|
|
57
|
+
return cls.ANIMATION
|
|
58
|
+
return cls.GIF
|
|
59
|
+
|
|
60
|
+
if types.DocumentAttributeVideo in attrs:
|
|
61
|
+
vid = attrs[types.DocumentAttributeVideo]
|
|
62
|
+
if vid.round_message:
|
|
63
|
+
return cls.VIDEO_NOTE
|
|
64
|
+
return cls.VIDEO
|
|
65
|
+
|
|
66
|
+
if types.DocumentAttributeAudio in attrs:
|
|
67
|
+
aud = attrs[types.DocumentAttributeAudio]
|
|
68
|
+
if aud.voice:
|
|
69
|
+
return cls.VOICE
|
|
70
|
+
return cls.AUDIO
|
|
71
|
+
|
|
72
|
+
return cls.DOCUMENT
|
|
73
|
+
|
|
74
|
+
if isinstance(media, types.MessageMediaWebPage):
|
|
75
|
+
return cls.WEB_PAGE
|
|
76
|
+
|
|
77
|
+
if isinstance(media, types.MessageMediaGeo):
|
|
78
|
+
return cls.GEO
|
|
79
|
+
|
|
80
|
+
if isinstance(media, types.MessageMediaGeoLive):
|
|
81
|
+
return cls.GEO_LIVE
|
|
82
|
+
|
|
83
|
+
if isinstance(media, types.MessageMediaContact):
|
|
84
|
+
return cls.CONTACT
|
|
85
|
+
|
|
86
|
+
if isinstance(media, types.MessageMediaGame):
|
|
87
|
+
return cls.GAME
|
|
88
|
+
|
|
89
|
+
if isinstance(media, types.MessageMediaPoll):
|
|
90
|
+
return cls.POLL
|
|
91
|
+
|
|
92
|
+
if isinstance(media, types.MessageMediaDice):
|
|
93
|
+
return cls.DICE
|
|
94
|
+
|
|
95
|
+
if isinstance(media, types.MessageMediaVenue):
|
|
96
|
+
return cls.VENUE
|
|
97
|
+
|
|
98
|
+
if isinstance(media, types.MessageMediaStory):
|
|
99
|
+
return cls.STORY
|
|
100
|
+
|
|
101
|
+
if isinstance(media, types.MessageMediaGiveaway):
|
|
102
|
+
return cls.GIVEAWAY
|
|
103
|
+
|
|
104
|
+
if isinstance(media, types.MessageMediaInvoice):
|
|
105
|
+
return cls.INVOICE
|
|
106
|
+
|
|
107
|
+
if isinstance(media, types.MessageMediaUnsupported):
|
|
108
|
+
return cls.UNSUPPORTED
|
|
109
|
+
|
|
110
|
+
return cls.EMPTY
|
|
111
|
+
|
|
112
|
+
@classmethod
|
|
113
|
+
def from_message(cls, message):
|
|
114
|
+
"""
|
|
115
|
+
Detect MessageMediaType from a Message object.
|
|
116
|
+
|
|
117
|
+
Usage:
|
|
118
|
+
from spluspy.enums import MessageMediaType
|
|
119
|
+
media_type = MessageMediaType.from_message(event.message)
|
|
120
|
+
"""
|
|
121
|
+
if hasattr(message, 'media'):
|
|
122
|
+
return cls.from_media(message.media)
|
|
123
|
+
return cls.EMPTY
|
|
124
|
+
|
|
125
|
+
def is_image(self):
|
|
126
|
+
return self == self.PHOTO
|
|
127
|
+
|
|
128
|
+
def is_video(self):
|
|
129
|
+
return self in (self.VIDEO, self.VIDEO_NOTE, self.ANIMATION)
|
|
130
|
+
|
|
131
|
+
def is_audio(self):
|
|
132
|
+
return self in (self.AUDIO, self.VOICE)
|
|
133
|
+
|
|
134
|
+
def is_document(self):
|
|
135
|
+
return self == self.DOCUMENT
|
|
136
|
+
|
|
137
|
+
def is_sticker(self):
|
|
138
|
+
return self == self.STICKER
|
|
139
|
+
|
|
140
|
+
def is_animation(self):
|
|
141
|
+
return self in (self.ANIMATION, self.GIF)
|
|
142
|
+
|
|
143
|
+
def is_media(self):
|
|
144
|
+
return self not in (self.EMPTY, self.UNSUPPORTED)
|
|
@@ -30,7 +30,7 @@ from ..crypto import AuthKey
|
|
|
30
30
|
from ..helpers import retry_range
|
|
31
31
|
|
|
32
32
|
|
|
33
|
-
_RECV_TIMEOUT =
|
|
33
|
+
_RECV_TIMEOUT = 60 # seconds; triggers reconnection if no data received
|
|
34
34
|
|
|
35
35
|
|
|
36
36
|
class MTProtoSender:
|
|
@@ -154,6 +154,49 @@ class MTProtoSender:
|
|
|
154
154
|
"""
|
|
155
155
|
await self._disconnect()
|
|
156
156
|
|
|
157
|
+
def _intercept_request(self, request):
|
|
158
|
+
"""
|
|
159
|
+
Soroush+ Interceptor Middleware:
|
|
160
|
+
Intercepts and rewrites outgoing requests to bypass Soroush+ server restrictions
|
|
161
|
+
and prevent RPCError 422 (NOT_SUPPORTED).
|
|
162
|
+
"""
|
|
163
|
+
req_type = type(request).__name__
|
|
164
|
+
|
|
165
|
+
try:
|
|
166
|
+
# 1. تبدیل متد خروج از گروه (جلوگیری از ارور 422)
|
|
167
|
+
if req_type == 'DeleteChatUserRequest':
|
|
168
|
+
from ..tl.functions.channels import LeaveChannelRequest
|
|
169
|
+
from ..tl.types import InputChannel
|
|
170
|
+
self._log.debug("Soroush+ Interceptor: Converting DeleteChatUserRequest -> LeaveChannelRequest")
|
|
171
|
+
# سروش برای خروج از همه گروهها فقط این متد را میپذیرد
|
|
172
|
+
return LeaveChannelRequest(channel=InputChannel(request.chat_id, 0))
|
|
173
|
+
|
|
174
|
+
# 2. تبدیل متد دریافت لیست گروهها (جلوگیری از ارور 422)
|
|
175
|
+
elif req_type == 'GetChatsRequest':
|
|
176
|
+
from ..tl.functions.channels import GetChannelsRequest
|
|
177
|
+
from ..tl.types import InputChannel
|
|
178
|
+
self._log.debug("Soroush+ Interceptor: Converting GetChatsRequest -> GetChannelsRequest")
|
|
179
|
+
channels = []
|
|
180
|
+
for chat_id in request.id:
|
|
181
|
+
if isinstance(chat_id, int):
|
|
182
|
+
channels.append(InputChannel(chat_id, 0))
|
|
183
|
+
else:
|
|
184
|
+
channels.append(chat_id)
|
|
185
|
+
return GetChannelsRequest(id=channels)
|
|
186
|
+
|
|
187
|
+
# 3. تبدیل متد دریافت اطلاعات کامل گروه (جلوگیری از ارور 422)
|
|
188
|
+
elif req_type == 'GetFullChatRequest':
|
|
189
|
+
from ..tl.functions.channels import GetFullChannelRequest
|
|
190
|
+
from ..tl.types import InputChannel
|
|
191
|
+
self._log.debug("Soroush+ Interceptor: Converting GetFullChatRequest -> GetFullChannelRequest")
|
|
192
|
+
return GetFullChannelRequest(channel=InputChannel(request.chat_id, 0))
|
|
193
|
+
|
|
194
|
+
except Exception as e:
|
|
195
|
+
self._log.error('Interceptor failed to convert request %s: %s', req_type, e)
|
|
196
|
+
|
|
197
|
+
# اگر درخواست نیازی به تغییر نداشت، خودش رو برمیگردونیم
|
|
198
|
+
return request
|
|
199
|
+
|
|
157
200
|
def send(self, request, ordered=False):
|
|
158
201
|
"""
|
|
159
202
|
This method enqueues the given request to be sent. Its send
|
|
@@ -181,6 +224,9 @@ class MTProtoSender:
|
|
|
181
224
|
raise ConnectionError('Cannot send requests while disconnected')
|
|
182
225
|
|
|
183
226
|
if not utils.is_list_like(request):
|
|
227
|
+
# --- SOROUSH+ INTERCEPTOR HOOK ---
|
|
228
|
+
request = self._intercept_request(request)
|
|
229
|
+
|
|
184
230
|
try:
|
|
185
231
|
state = RequestState(request)
|
|
186
232
|
except struct.error as e:
|
|
@@ -196,6 +242,9 @@ class MTProtoSender:
|
|
|
196
242
|
futures = []
|
|
197
243
|
state = None
|
|
198
244
|
for req in request:
|
|
245
|
+
# --- SOROUSH+ INTERCEPTOR HOOK ---
|
|
246
|
+
req = self._intercept_request(req)
|
|
247
|
+
|
|
199
248
|
try:
|
|
200
249
|
state = RequestState(req, after=ordered and state)
|
|
201
250
|
except struct.error as e:
|
|
@@ -976,4 +1025,4 @@ class MTProtoSender:
|
|
|
976
1025
|
# Even if the library didn't disconnect, the server would (and then
|
|
977
1026
|
# the library would reconnect and learn about auth key being invalid).
|
|
978
1027
|
if isinstance(message.obj, DestroyAuthKeyOk):
|
|
979
|
-
await self._disconnect(error=AuthKeyNotFound())
|
|
1028
|
+
await self._disconnect(error=AuthKeyNotFound())
|
|
@@ -7,6 +7,7 @@ from .forward import Forward
|
|
|
7
7
|
from .file import File
|
|
8
8
|
from .. import TLObject, types, functions
|
|
9
9
|
from ... import utils, errors
|
|
10
|
+
from ...enums import MessageMediaType
|
|
10
11
|
|
|
11
12
|
_log = logging.getLogger(__name__)
|
|
12
13
|
|
|
@@ -150,6 +151,27 @@ class Message(ChatGetter, SenderGetter, TLObject):
|
|
|
150
151
|
self._file = File(media)
|
|
151
152
|
return self._file
|
|
152
153
|
|
|
154
|
+
@property
|
|
155
|
+
def media_type(self):
|
|
156
|
+
"""
|
|
157
|
+
Returns the MessageMediaType of this message.
|
|
158
|
+
|
|
159
|
+
Usage:
|
|
160
|
+
from spluspy.enums import MessageMediaType
|
|
161
|
+
|
|
162
|
+
@client.on_message()
|
|
163
|
+
async def handler(client, event):
|
|
164
|
+
media_type = event.message.media_type
|
|
165
|
+
|
|
166
|
+
if media_type == MessageMediaType.PHOTO:
|
|
167
|
+
await event.reply("عکس دریافت شد!")
|
|
168
|
+
elif media_type == MessageMediaType.VIDEO:
|
|
169
|
+
await event.reply("ویدیو دریافت شد!")
|
|
170
|
+
elif media_type == MessageMediaType.STICKER:
|
|
171
|
+
await event.reply("استیکر دریافت شد!")
|
|
172
|
+
"""
|
|
173
|
+
return MessageMediaType.from_media(self.media)
|
|
174
|
+
|
|
153
175
|
@property
|
|
154
176
|
def photo(self):
|
|
155
177
|
if isinstance(self.media, types.MessageMediaPhoto):
|
|
@@ -58,9 +58,6 @@ USERNAME_RE = re.compile(
|
|
|
58
58
|
TG_JOIN_RE = re.compile(
|
|
59
59
|
r'tg://(join)\?invite='
|
|
60
60
|
)
|
|
61
|
-
SOROUSH_JOIN_RE = re.compile(
|
|
62
|
-
r'https?://splus\.ir/joingroup/([a-zA-Z0-9_-]+)'
|
|
63
|
-
)
|
|
64
61
|
|
|
65
62
|
VALID_USERNAME_RE = re.compile(
|
|
66
63
|
r'^[a-z](?:(?!__)\w){1,30}[a-z\d]$',
|
|
@@ -936,11 +933,6 @@ def parse_username(username):
|
|
|
936
933
|
Returns ``(None, False)`` if the ``username`` or link is not valid.
|
|
937
934
|
"""
|
|
938
935
|
username = username.strip()
|
|
939
|
-
|
|
940
|
-
splus_match = SOROUSH_JOIN_RE.match(username)
|
|
941
|
-
if splus_match:
|
|
942
|
-
return splus_match.group(1), True
|
|
943
|
-
|
|
944
936
|
m = USERNAME_RE.match(username) or TG_JOIN_RE.match(username)
|
|
945
937
|
if m:
|
|
946
938
|
username = username[m.end():]
|
|
@@ -1575,4 +1567,4 @@ async def maybe_async(coro):
|
|
|
1575
1567
|
if inspect.isawaitable(result):
|
|
1576
1568
|
warnings.warn('Using async sessions support is an experimental feature')
|
|
1577
1569
|
result = await result
|
|
1578
|
-
return result
|
|
1570
|
+
return result
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: spluspy
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.8
|
|
4
4
|
Summary: High-performance Python Self-Bot Framework for Soroush Plus
|
|
5
5
|
Author: Ali Mirshekari
|
|
6
6
|
License: MIT
|
|
@@ -26,7 +26,8 @@ Requires-Dist: rsa
|
|
|
26
26
|
Requires-Dist: pyaes
|
|
27
27
|
Dynamic: license-file
|
|
28
28
|
|
|
29
|
-
# SplusPy v2.2.
|
|
29
|
+
# SplusPy v2.2.8
|
|
30
|
+
|
|
30
31
|
|
|
31
32
|
**A fast, minimal, and fully-featured self-bot framework**
|
|
32
33
|
|