Rubka 7.2.8__py3-none-any.whl
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.
- rubka/__init__.py +79 -0
- rubka/adaptorrubka/__init__.py +4 -0
- rubka/adaptorrubka/client/__init__.py +1 -0
- rubka/adaptorrubka/client/client.py +60 -0
- rubka/adaptorrubka/crypto/__init__.py +1 -0
- rubka/adaptorrubka/crypto/crypto.py +82 -0
- rubka/adaptorrubka/enums.py +36 -0
- rubka/adaptorrubka/exceptions.py +22 -0
- rubka/adaptorrubka/methods/__init__.py +1 -0
- rubka/adaptorrubka/methods/methods.py +90 -0
- rubka/adaptorrubka/network/__init__.py +3 -0
- rubka/adaptorrubka/network/helper.py +22 -0
- rubka/adaptorrubka/network/network.py +221 -0
- rubka/adaptorrubka/network/socket.py +31 -0
- rubka/adaptorrubka/sessions/__init__.py +1 -0
- rubka/adaptorrubka/sessions/sessions.py +72 -0
- rubka/adaptorrubka/types/__init__.py +1 -0
- rubka/adaptorrubka/types/socket/__init__.py +1 -0
- rubka/adaptorrubka/types/socket/message.py +187 -0
- rubka/adaptorrubka/utils/__init__.py +2 -0
- rubka/adaptorrubka/utils/configs.py +18 -0
- rubka/adaptorrubka/utils/utils.py +251 -0
- rubka/api.py +1723 -0
- rubka/asynco.py +2541 -0
- rubka/button.py +404 -0
- rubka/config.py +3 -0
- rubka/context.py +1077 -0
- rubka/decorators.py +30 -0
- rubka/exceptions.py +37 -0
- rubka/filters.py +330 -0
- rubka/helpers.py +1461 -0
- rubka/jobs.py +15 -0
- rubka/keyboards.py +16 -0
- rubka/keypad.py +298 -0
- rubka/logger.py +12 -0
- rubka/metadata.py +114 -0
- rubka/rubino.py +1271 -0
- rubka/tv.py +145 -0
- rubka/update.py +1038 -0
- rubka/utils.py +3 -0
- rubka-7.2.8.dist-info/METADATA +1047 -0
- rubka-7.2.8.dist-info/RECORD +45 -0
- rubka-7.2.8.dist-info/WHEEL +5 -0
- rubka-7.2.8.dist-info/entry_points.txt +2 -0
- rubka-7.2.8.dist-info/top_level.txt +1 -0
rubka/__init__.py
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""
|
|
2
|
+
🔹 Synchronous Example
|
|
3
|
+
```python
|
|
4
|
+
from rubka import Robot, Message
|
|
5
|
+
|
|
6
|
+
bot = Robot(token="YOUR_BOT_TOKEN")
|
|
7
|
+
|
|
8
|
+
@bot.on_message(commands=["start", "help"])
|
|
9
|
+
def handle_start(bot: Robot, message: Message):
|
|
10
|
+
message.reply("👋 Hello! Welcome to the Rubka bot (sync example).")
|
|
11
|
+
|
|
12
|
+
bot.run()
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Explanation
|
|
16
|
+
|
|
17
|
+
Robot is created with your bot token.
|
|
18
|
+
@bot.on_message registers a handler for incoming messages.
|
|
19
|
+
|
|
20
|
+
The function handle_start runs synchronously (step by step, blocking).
|
|
21
|
+
|
|
22
|
+
message.reply sends a message back to the user immediately.
|
|
23
|
+
|
|
24
|
+
Finally, bot.run() starts the event loop and keeps the bot running.
|
|
25
|
+
|
|
26
|
+
This approach is simpler and best for small bots or basic logic where you don’t need concurrency.
|
|
27
|
+
|
|
28
|
+
🔹 Asynchronous Example
|
|
29
|
+
```python
|
|
30
|
+
import asyncio
|
|
31
|
+
from rubka.asynco import Robot, Message
|
|
32
|
+
|
|
33
|
+
bot = Robot(token="YOUR_BOT_TOKEN")
|
|
34
|
+
|
|
35
|
+
@bot.on_message(commands=["start", "help"])
|
|
36
|
+
async def handle_start(bot: Robot, message: Message):
|
|
37
|
+
await message.reply("âš¡ Hello! This is the async version of Rubka.")
|
|
38
|
+
|
|
39
|
+
async def main():
|
|
40
|
+
await bot.run()
|
|
41
|
+
|
|
42
|
+
asyncio.run(main())
|
|
43
|
+
```
|
|
44
|
+
Explanation
|
|
45
|
+
|
|
46
|
+
Uses rubka.asynco.Robot for asynchronous operation.
|
|
47
|
+
|
|
48
|
+
The handler handle_start is defined with async def.
|
|
49
|
+
|
|
50
|
+
await message.reply(...) is non-blocking: the bot can process other tasks while waiting for Rubika’s response.
|
|
51
|
+
|
|
52
|
+
asyncio.run(main()) starts the async event loop.
|
|
53
|
+
|
|
54
|
+
This approach is more powerful and recommended for larger bots or when you:
|
|
55
|
+
|
|
56
|
+
Need to call external APIs.
|
|
57
|
+
|
|
58
|
+
Handle multiple long-running tasks.
|
|
59
|
+
|
|
60
|
+
Want better performance and scalability.
|
|
61
|
+
|
|
62
|
+
👉 In short:
|
|
63
|
+
|
|
64
|
+
Sync = simple, step-by-step, blocking.
|
|
65
|
+
|
|
66
|
+
Async = scalable, concurrent, non-blocking.
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
from .api import Robot
|
|
70
|
+
from .exceptions import APIRequestError
|
|
71
|
+
from .rubino import Bot as rubino
|
|
72
|
+
from .tv import TV as TvRubika
|
|
73
|
+
|
|
74
|
+
__all__ = [
|
|
75
|
+
"Robot",
|
|
76
|
+
"on_message",
|
|
77
|
+
"APIRequestError",
|
|
78
|
+
"create_simple_keyboard",
|
|
79
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .client import Client
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from ..methods import Methods
|
|
2
|
+
|
|
3
|
+
class Client(object):
|
|
4
|
+
|
|
5
|
+
def __init__(
|
|
6
|
+
self,
|
|
7
|
+
session:str=None,
|
|
8
|
+
auth:str=None,
|
|
9
|
+
private:str=None,
|
|
10
|
+
platform:str="web",
|
|
11
|
+
api_version:int=6,
|
|
12
|
+
proxy:str=None,
|
|
13
|
+
time_out:int=10,
|
|
14
|
+
show_progress_bar:bool=True
|
|
15
|
+
) -> None:
|
|
16
|
+
|
|
17
|
+
self.session = session
|
|
18
|
+
self.platform = platform
|
|
19
|
+
self.apiVersion = api_version
|
|
20
|
+
self.proxy = proxy
|
|
21
|
+
self.timeOut = time_out
|
|
22
|
+
|
|
23
|
+
if(session):
|
|
24
|
+
from ..sessions import Sessions
|
|
25
|
+
self.sessions = Sessions(self)
|
|
26
|
+
|
|
27
|
+
if(self.sessions.cheackSessionExists()):
|
|
28
|
+
self.sessionData = self.sessions.loadSessionData()
|
|
29
|
+
else:
|
|
30
|
+
self.sessionData = self.sessions.createSession()
|
|
31
|
+
else:
|
|
32
|
+
from ..utils import Utils
|
|
33
|
+
self.sessionData = {
|
|
34
|
+
"auth": auth,
|
|
35
|
+
"private_key": Utils.privateParse(private=private)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
self.methods = Methods(
|
|
39
|
+
sessionData=self.sessionData,
|
|
40
|
+
platform=platform,
|
|
41
|
+
apiVersion=api_version,
|
|
42
|
+
proxy=proxy,
|
|
43
|
+
timeOut=time_out,
|
|
44
|
+
showProgressBar=show_progress_bar
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
def send_code(self, phone_number:str, pass_key:str=None) -> dict:
|
|
48
|
+
return self.methods.sendCode(phoneNumber=phone_number, passKey=pass_key)
|
|
49
|
+
|
|
50
|
+
def sign_in(self, phone_number:str, phone_code_hash:str, phone_code:str) -> dict:
|
|
51
|
+
return self.methods.signIn(phoneNumber=phone_number, phoneCodeHash=phone_code_hash, phoneCode=phone_code)
|
|
52
|
+
|
|
53
|
+
def register_device(self, device_model:str) -> dict:
|
|
54
|
+
return self.methods.registerDevice(deviceModel=device_model)
|
|
55
|
+
|
|
56
|
+
def logout(self) -> dict:
|
|
57
|
+
return self.methods.logout()
|
|
58
|
+
|
|
59
|
+
def get_all_members(self, object_guid:str, search_text:str=None, start_id:str=None, just_get_guids:bool=False) -> dict:
|
|
60
|
+
return self.methods.getChatAllMembers(objectGuid=object_guid, searchText=search_text, startId=start_id, justGetGuids=just_get_guids)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .crypto import Cryption
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
from Crypto.Util.Padding import pad, unpad
|
|
2
|
+
from base64 import b64encode as b64e, urlsafe_b64decode as b64d, b64decode
|
|
3
|
+
from Crypto.Cipher import AES
|
|
4
|
+
from Crypto.PublicKey import RSA
|
|
5
|
+
from Crypto.Hash import SHA256
|
|
6
|
+
from Crypto.Signature import pkcs1_15
|
|
7
|
+
from Crypto.Cipher import PKCS1_OAEP
|
|
8
|
+
|
|
9
|
+
class Cryption:
|
|
10
|
+
|
|
11
|
+
def __init__(self, auth:str, private_key:str=None):
|
|
12
|
+
self.auth = auth
|
|
13
|
+
|
|
14
|
+
if auth:
|
|
15
|
+
self.key = bytearray(self.secret(auth), 'UTF-8')
|
|
16
|
+
self.iv = bytearray.fromhex('0' * 32)
|
|
17
|
+
|
|
18
|
+
if private_key:
|
|
19
|
+
self.keypair = RSA.import_key(private_key.encode('UTF-8'))
|
|
20
|
+
|
|
21
|
+
def replaceCharAt(self, e, t, i):
|
|
22
|
+
return e[0:t] + i + e[t + len(i):]
|
|
23
|
+
|
|
24
|
+
def secret(self, e):
|
|
25
|
+
t = e[0:8]
|
|
26
|
+
i = e[8:16]
|
|
27
|
+
n = e[16:24] + t + e[24:32] + i
|
|
28
|
+
s = 0
|
|
29
|
+
while s < len(n):
|
|
30
|
+
e = n[s]
|
|
31
|
+
if e >= '0' and e <= '9':
|
|
32
|
+
t = chr((ord(e[0]) - ord('0') + 5) % 10 + ord('0'))
|
|
33
|
+
n = self.replaceCharAt(n, s, t)
|
|
34
|
+
else:
|
|
35
|
+
t = chr((ord(e[0]) - ord('a') + 9) % 26 + ord('a'))
|
|
36
|
+
n = self.replaceCharAt(n, s, t)
|
|
37
|
+
s += 1
|
|
38
|
+
return n
|
|
39
|
+
|
|
40
|
+
def encrypt(self, text):
|
|
41
|
+
raw = pad(text.encode('UTF-8'), AES.block_size)
|
|
42
|
+
aes = AES.new(self.key, AES.MODE_CBC, self.iv)
|
|
43
|
+
enc = aes.encrypt(raw)
|
|
44
|
+
result = b64e(enc).decode('UTF-8')
|
|
45
|
+
return result
|
|
46
|
+
|
|
47
|
+
def decrypt(self, text):
|
|
48
|
+
aes = AES.new(self.key, AES.MODE_CBC, self.iv)
|
|
49
|
+
dec = aes.decrypt(b64d(text.encode('UTF-8')))
|
|
50
|
+
result = unpad(dec, AES.block_size).decode('UTF-8')
|
|
51
|
+
return result
|
|
52
|
+
|
|
53
|
+
def makeSignFromData(self, data_enc:str):
|
|
54
|
+
sha_data = SHA256.new(data_enc.encode('UTF-8'))
|
|
55
|
+
signature = pkcs1_15.new(self.keypair).sign(sha_data)
|
|
56
|
+
return b64e(signature).decode('UTF-8')
|
|
57
|
+
|
|
58
|
+
def decryptRsaOaep(private:str, data_enc:str):
|
|
59
|
+
keyPair = RSA.import_key(private.encode('UTF-8'))
|
|
60
|
+
return PKCS1_OAEP.new(keyPair).decrypt(b64decode(data_enc)).decode('UTF-8')
|
|
61
|
+
|
|
62
|
+
def changeAuthType(self, auth_enc):
|
|
63
|
+
n = ''
|
|
64
|
+
lowercase = 'abcdefghijklmnopqrstuvwxyz'
|
|
65
|
+
uppercase = 'abcdefghijklmnopqrstuvwxyz'.upper()
|
|
66
|
+
digits = '0123456789'
|
|
67
|
+
for s in auth_enc:
|
|
68
|
+
if s in lowercase:
|
|
69
|
+
n += chr(((32 - (ord(s) - 97)) % 26) + 97)
|
|
70
|
+
elif s in uppercase:
|
|
71
|
+
n += chr(((29- (ord(s) - 65)) % 26) + 65)
|
|
72
|
+
elif s in digits:
|
|
73
|
+
n += chr(((13 - (ord(s)- 48)) % 10) + 48)
|
|
74
|
+
else:
|
|
75
|
+
n += s
|
|
76
|
+
return n
|
|
77
|
+
|
|
78
|
+
def rsaKeyGenrate(self):
|
|
79
|
+
keyPair = RSA.generate(1024)
|
|
80
|
+
public = self.changeAuthType(b64e(keyPair.publickey().export_key()).decode('UTF-8'))
|
|
81
|
+
privarte = keyPair.export_key().decode('UTF-8')
|
|
82
|
+
return public, privarte
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
class SetAdminAccessList:
|
|
2
|
+
set_admin:str = "SetAdmin"
|
|
3
|
+
ban_member:str = "BanMember"
|
|
4
|
+
change_info:str = "ChangeInfo"
|
|
5
|
+
pin_messages:str = "PinMessages"
|
|
6
|
+
delete_messages:str = "DeleteGlobalAllMessages"
|
|
7
|
+
edit_messages:str = "EditMessages"
|
|
8
|
+
set_join_link:str = "SetJoinLink"
|
|
9
|
+
set_member_access:str = "SetMemberAccess"
|
|
10
|
+
delete_global_all_messages:str = "DeleteGlobalAllMessages"
|
|
11
|
+
|
|
12
|
+
class SetGroupDefaultAccessList:
|
|
13
|
+
send_messages:str = "SendMessages"
|
|
14
|
+
add_member:str = "AddMember"
|
|
15
|
+
view_admins:str = "ViewAdmins"
|
|
16
|
+
view_members:str = "ViewMembers"
|
|
17
|
+
|
|
18
|
+
class ChatActivities:
|
|
19
|
+
typing:str = "Typing"
|
|
20
|
+
recording:str = "Recording"
|
|
21
|
+
uploading:str = "Uploading"
|
|
22
|
+
|
|
23
|
+
class Filters:
|
|
24
|
+
user:str = "User"
|
|
25
|
+
group:str = "Group"
|
|
26
|
+
channel:str = "Channel"
|
|
27
|
+
bot:str = "Bot"
|
|
28
|
+
service:str = "Service"
|
|
29
|
+
media:str = "Media"
|
|
30
|
+
file:str = "File"
|
|
31
|
+
image:str = "Image"
|
|
32
|
+
video:str = "Video"
|
|
33
|
+
gif:str = "Gif"
|
|
34
|
+
music:str = "Music"
|
|
35
|
+
voice:str = "Voice"
|
|
36
|
+
sticker:str = "Sticker"
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
class ClientException(Exception):
|
|
2
|
+
pass
|
|
3
|
+
|
|
4
|
+
class InvalidAuth(ClientException):
|
|
5
|
+
|
|
6
|
+
def __init__(self, message:str="You don't have access to request this method!"):
|
|
7
|
+
super().__init__(message)
|
|
8
|
+
|
|
9
|
+
class NotRegistered(ClientException):
|
|
10
|
+
|
|
11
|
+
def __init__(self, message:str="The account used is invalid or not registered!"):
|
|
12
|
+
super().__init__(message)
|
|
13
|
+
|
|
14
|
+
class InvalidInput(ClientException):
|
|
15
|
+
|
|
16
|
+
def __init__(self, message:str="Some input given to the method is invalid!"):
|
|
17
|
+
super().__init__(message)
|
|
18
|
+
|
|
19
|
+
class TooRequests(ClientException):
|
|
20
|
+
|
|
21
|
+
def __init__(self, message:str="You won't be able to use this method for a while!"):
|
|
22
|
+
super().__init__(message)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .methods import Methods
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
from random import randint
|
|
2
|
+
from ..network import Network, Socket
|
|
3
|
+
from ..crypto import Cryption
|
|
4
|
+
from ..utils import Utils
|
|
5
|
+
from time import sleep
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Methods:
|
|
9
|
+
|
|
10
|
+
def __init__(self, sessionData:dict, platform:str, apiVersion:int, proxy:str, timeOut:int, showProgressBar:bool) -> None:
|
|
11
|
+
self.platform = platform.lower()
|
|
12
|
+
if not self.platform in ["android", "web", "rubx", "rubikax", "rubino"]:
|
|
13
|
+
print("The \"{}\" is not a valid platform. Choose these one -> (web, android, rubx)".format(platform))
|
|
14
|
+
exit()
|
|
15
|
+
self.apiVersion = apiVersion
|
|
16
|
+
self.proxy = proxy
|
|
17
|
+
self.timeOut = timeOut
|
|
18
|
+
self.showProgressBar = showProgressBar
|
|
19
|
+
self.sessionData = sessionData
|
|
20
|
+
self.crypto = Cryption(
|
|
21
|
+
auth=sessionData["auth"],
|
|
22
|
+
private_key=sessionData["private_key"]
|
|
23
|
+
) if sessionData else Cryption(auth=Utils.randomTmpSession())
|
|
24
|
+
self.network = Network(methods=self)
|
|
25
|
+
self.socket = Socket(methods=self)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def sendCode(self, phoneNumber:str, passKey:str=None, sendInternal:bool=False) -> dict:
|
|
29
|
+
input:dict = {
|
|
30
|
+
"phone_number": f"98{Utils.phoneNumberParse(phoneNumber)}",
|
|
31
|
+
"send_type": "Internal" if sendInternal else "SMS",
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if passKey:
|
|
35
|
+
input["pass_key"] = passKey
|
|
36
|
+
|
|
37
|
+
return self.network.request(
|
|
38
|
+
method="sendCode",
|
|
39
|
+
input=input,
|
|
40
|
+
tmpSession=True
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
def signIn(self, phoneNumber, phoneCodeHash, phoneCode) -> dict:
|
|
44
|
+
publicKey, privateKey = self.crypto.rsaKeyGenrate()
|
|
45
|
+
|
|
46
|
+
data = self.network.request(
|
|
47
|
+
method="signIn",
|
|
48
|
+
input={
|
|
49
|
+
"phone_number": f"98{Utils.phoneNumberParse(phoneNumber)}",
|
|
50
|
+
"phone_code_hash": phoneCodeHash,
|
|
51
|
+
"phone_code": phoneCode,
|
|
52
|
+
"public_key": publicKey
|
|
53
|
+
},
|
|
54
|
+
tmpSession=True
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
data["private_key"] = privateKey
|
|
58
|
+
|
|
59
|
+
return data
|
|
60
|
+
|
|
61
|
+
def registerDevice(self, deviceModel) -> dict:
|
|
62
|
+
return self.network.request(
|
|
63
|
+
method="registerDevice",
|
|
64
|
+
input={
|
|
65
|
+
"app_version": "WB_4.3.3" if self.platform == "web" else "MA_3.4.3",
|
|
66
|
+
"device_hash": Utils.randomDeviceHash(),
|
|
67
|
+
"device_model": deviceModel,
|
|
68
|
+
"is_multi_account": False,
|
|
69
|
+
"lang_code": "fa",
|
|
70
|
+
"system_version": "Windows 11" if self.platform == "web" else "SDK 28",
|
|
71
|
+
"token": "",
|
|
72
|
+
"token_type": "Web" if self.platform == "web" else "Firebase"
|
|
73
|
+
}
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
def getChatAllMembers(self, objectGuid:str, searchText:str, startId:str, justGetGuids:bool=False) -> dict:
|
|
77
|
+
chatType:str = Utils.getChatTypeByGuid(objectGuid=objectGuid)
|
|
78
|
+
|
|
79
|
+
data = self.network.request(
|
|
80
|
+
method=f"get{chatType}AllMembers",
|
|
81
|
+
input={
|
|
82
|
+
f"{chatType.lower()}_guid": objectGuid,
|
|
83
|
+
"search_text": searchText.replace("@", "") if searchText else searchText,
|
|
84
|
+
"start_id": startId
|
|
85
|
+
}
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
if justGetGuids: return [i["member_guid"] for i in data["in_chat_members"]]
|
|
89
|
+
|
|
90
|
+
return data
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from urllib3 import PoolManager
|
|
2
|
+
from json import loads
|
|
3
|
+
from random import randint, choice
|
|
4
|
+
|
|
5
|
+
class Helper:
|
|
6
|
+
|
|
7
|
+
@classmethod
|
|
8
|
+
def getDcmess(self) -> dict:
|
|
9
|
+
return loads(
|
|
10
|
+
PoolManager().request(
|
|
11
|
+
"GET",
|
|
12
|
+
"https://getdcmess.iranlms.ir/"
|
|
13
|
+
).data.decode()
|
|
14
|
+
)["data"]
|
|
15
|
+
|
|
16
|
+
@classmethod
|
|
17
|
+
def getApiServer(self) -> str:
|
|
18
|
+
return f"https://messengerg2c{randint(2, 3)}.iranlms.ir"
|
|
19
|
+
|
|
20
|
+
@classmethod
|
|
21
|
+
def getSocketServer(self) -> str:
|
|
22
|
+
return choice(list(self.getDcmess()["socket"].values()))
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
from json import dumps, loads
|
|
2
|
+
from tqdm import tqdm
|
|
3
|
+
from urllib3 import PoolManager, ProxyManager
|
|
4
|
+
from ..utils import Configs
|
|
5
|
+
from ..exceptions import *
|
|
6
|
+
from .helper import Helper
|
|
7
|
+
|
|
8
|
+
class Network:
|
|
9
|
+
|
|
10
|
+
def __init__(self, methods:object) -> None:
|
|
11
|
+
self.methods = methods
|
|
12
|
+
self.sessionData = methods.sessionData
|
|
13
|
+
self.crypto = methods.crypto
|
|
14
|
+
self.http = ProxyManager(methods.proxy) if methods.proxy else PoolManager()
|
|
15
|
+
|
|
16
|
+
def request(self, method:str, input:dict={}, tmpSession:bool=False, attempt:int = 0, maxAttempt:int=2):
|
|
17
|
+
url:str = Helper.getApiServer()
|
|
18
|
+
platform:str = self.methods.platform.lower()
|
|
19
|
+
apiVersion:int = self.methods.apiVersion
|
|
20
|
+
|
|
21
|
+
if platform in ["rubx", "rubikax"]:
|
|
22
|
+
client:dict = Configs.clients["android"]
|
|
23
|
+
client["package"] = "ir.rubx.bapp"
|
|
24
|
+
|
|
25
|
+
elif platform in ["android"]:
|
|
26
|
+
client:dict = Configs.clients["android"]
|
|
27
|
+
|
|
28
|
+
else:
|
|
29
|
+
client:dict = Configs.clients["web"]
|
|
30
|
+
|
|
31
|
+
data = {
|
|
32
|
+
"api_version": str(apiVersion),
|
|
33
|
+
"tmp_session" if tmpSession else
|
|
34
|
+
"auth": self.crypto.auth if tmpSession else
|
|
35
|
+
self.crypto.changeAuthType(self.sessionData["auth"]) if apiVersion > 5 else
|
|
36
|
+
self.sessionData["auth"],
|
|
37
|
+
"data_enc": self.crypto.encrypt(
|
|
38
|
+
dumps({
|
|
39
|
+
"method": method,
|
|
40
|
+
"input": input,
|
|
41
|
+
"client": client
|
|
42
|
+
})
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
headers:dict = {
|
|
47
|
+
"Referer": "https://web.rubika.ir/",
|
|
48
|
+
"Content-Type": "application/json; charset=utf-8"
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if not tmpSession and apiVersion > 5:
|
|
52
|
+
data["sign"] = self.crypto.makeSignFromData(data["data_enc"])
|
|
53
|
+
|
|
54
|
+
while True:
|
|
55
|
+
result = self.http.request(
|
|
56
|
+
method="POST",
|
|
57
|
+
url=url,
|
|
58
|
+
headers=headers,
|
|
59
|
+
body = dumps(data).encode(),
|
|
60
|
+
timeout=self.methods.timeOut
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
result = loads(self.crypto.decrypt(loads(result.data.decode("UTF-8"))["data_enc"]))
|
|
65
|
+
except:
|
|
66
|
+
attempt += 1
|
|
67
|
+
|
|
68
|
+
if attempt > maxAttempt:
|
|
69
|
+
raise
|
|
70
|
+
|
|
71
|
+
continue
|
|
72
|
+
|
|
73
|
+
if result["status"] == "OK":
|
|
74
|
+
if tmpSession:
|
|
75
|
+
result["data"]["tmp_session"] = self.crypto.auth
|
|
76
|
+
|
|
77
|
+
return result["data"]
|
|
78
|
+
|
|
79
|
+
else:
|
|
80
|
+
raise {
|
|
81
|
+
"INVALID_AUTH": InvalidAuth(),
|
|
82
|
+
"NOT_REGISTERED": NotRegistered(),
|
|
83
|
+
"INVALID_INPUT": InvalidInput(),
|
|
84
|
+
"TOO_REQUESTS": TooRequests()
|
|
85
|
+
}[result["status_det"]]
|
|
86
|
+
|
|
87
|
+
def upload(self, file:str, fileName:str=None, chunkSize:int=131072):
|
|
88
|
+
from ..utils import Utils
|
|
89
|
+
|
|
90
|
+
if isinstance(file, str):
|
|
91
|
+
if Utils.checkLink(url=file):
|
|
92
|
+
file:bytes = self.http.request(method="GET", url=file).data
|
|
93
|
+
mime:str = Utils.getMimeFromByte(bytes=file)
|
|
94
|
+
fileName = fileName or Utils.generateFileName(mime=mime)
|
|
95
|
+
else:
|
|
96
|
+
fileName = fileName or file
|
|
97
|
+
mime = file.split(".")[-1]
|
|
98
|
+
file = open(file, "rb").read()
|
|
99
|
+
|
|
100
|
+
elif not isinstance(file, bytes):
|
|
101
|
+
raise FileNotFoundError("Enter a valid path or url or bytes of file.")
|
|
102
|
+
else:
|
|
103
|
+
mime = Utils.getMimeFromByte(bytes=file)
|
|
104
|
+
fileName = fileName or Utils.generateFileName(mime=mime)
|
|
105
|
+
|
|
106
|
+
def send_chunk(data, maxAttempts=2):
|
|
107
|
+
for attempt in range(maxAttempts):
|
|
108
|
+
try:
|
|
109
|
+
response = self.http.request(
|
|
110
|
+
"POST",
|
|
111
|
+
url=requestSendFileData["upload_url"],
|
|
112
|
+
headers=header,
|
|
113
|
+
body=data
|
|
114
|
+
)
|
|
115
|
+
return loads(response.data.decode("UTF-8"))
|
|
116
|
+
except Exception:
|
|
117
|
+
print(f"\nError uploading file! (Attempt {attempt + 1}/{maxAttempts})")
|
|
118
|
+
|
|
119
|
+
print("\nFailed to upload the file!")
|
|
120
|
+
|
|
121
|
+
requestSendFileData:dict = self.methods.requestSendFile(
|
|
122
|
+
fileName = fileName,
|
|
123
|
+
mime = mime,
|
|
124
|
+
size = len(file)
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
header = {
|
|
128
|
+
"auth": self.sessionData["auth"],
|
|
129
|
+
"access-hash-send": requestSendFileData["access_hash_send"],
|
|
130
|
+
"file-id": requestSendFileData["id"],
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
totalParts = (len(file) + chunkSize - 1) // chunkSize
|
|
134
|
+
|
|
135
|
+
if self.methods.showProgressBar:
|
|
136
|
+
processBar = tqdm(
|
|
137
|
+
desc=f"Uploading {fileName}",
|
|
138
|
+
total=len(file),
|
|
139
|
+
unit="B",
|
|
140
|
+
unit_scale=True,
|
|
141
|
+
unit_divisor=1024,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
for partNumber in range(1, totalParts + 1):
|
|
145
|
+
startIdx = (partNumber - 1) * chunkSize
|
|
146
|
+
endIdx = min(startIdx + chunkSize, len(file))
|
|
147
|
+
header["chunk-size"] = str(endIdx - startIdx)
|
|
148
|
+
header["part-number"] = str(partNumber)
|
|
149
|
+
header["total-part"] = str(totalParts)
|
|
150
|
+
data = file[startIdx:endIdx]
|
|
151
|
+
hashFileReceive = send_chunk(data)
|
|
152
|
+
|
|
153
|
+
if self.methods.showProgressBar:
|
|
154
|
+
processBar.update(len(data))
|
|
155
|
+
|
|
156
|
+
if not hashFileReceive:
|
|
157
|
+
return
|
|
158
|
+
|
|
159
|
+
if partNumber == totalParts:
|
|
160
|
+
|
|
161
|
+
if not hashFileReceive["data"]:
|
|
162
|
+
return
|
|
163
|
+
|
|
164
|
+
requestSendFileData["file"] = file
|
|
165
|
+
requestSendFileData["access_hash_rec"] = hashFileReceive["data"]["access_hash_rec"]
|
|
166
|
+
requestSendFileData["file_name"] = fileName
|
|
167
|
+
requestSendFileData["mime"] = mime
|
|
168
|
+
requestSendFileData["size"] = len(file)
|
|
169
|
+
return requestSendFileData
|
|
170
|
+
|
|
171
|
+
def download(self, accessHashRec:str, fileId:str, dcId:str, size:int, fileName:str, chunkSize:int=262143, attempt:int=0, maxAttempts:int=2):
|
|
172
|
+
headers:dict = {
|
|
173
|
+
"auth": self.sessionData["auth"],
|
|
174
|
+
"access-hash-rec": accessHashRec,
|
|
175
|
+
"dc-id": dcId,
|
|
176
|
+
"file-id": fileId,
|
|
177
|
+
"Host": f"messenger{dcId}.iranlms.ir",
|
|
178
|
+
"client-app-name": "Main",
|
|
179
|
+
"client-app-version": "3.5.7",
|
|
180
|
+
"client-package": "app.rbmain.a",
|
|
181
|
+
"client-platform": "Android",
|
|
182
|
+
"Connection": "Keep-Alive",
|
|
183
|
+
"Content-Type": "application/json",
|
|
184
|
+
"User-Agent": "okhttp/3.12.1"
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
response = self.http.request(
|
|
189
|
+
"POST",
|
|
190
|
+
url=f"https://messenger{dcId}.iranlms.ir/GetFile.ashx",
|
|
191
|
+
headers=headers,
|
|
192
|
+
preload_content=False
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
data:bytes = b""
|
|
196
|
+
|
|
197
|
+
if self.methods.showProgressBar:
|
|
198
|
+
processBar = tqdm(
|
|
199
|
+
desc=f"Downloading {fileName}",
|
|
200
|
+
total=size,
|
|
201
|
+
unit="B",
|
|
202
|
+
unit_scale=True,
|
|
203
|
+
unit_divisor=1024,
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
for downloadedData in response.stream(chunkSize):
|
|
207
|
+
try:
|
|
208
|
+
if downloadedData:
|
|
209
|
+
data += downloadedData
|
|
210
|
+
if self.methods.showProgressBar:
|
|
211
|
+
processBar.update(len(downloadedData))
|
|
212
|
+
|
|
213
|
+
if len(data) >= size:
|
|
214
|
+
return data
|
|
215
|
+
except Exception:
|
|
216
|
+
if attempt <= maxAttempts:
|
|
217
|
+
attempt += 1
|
|
218
|
+
print(f"\nError downloading file! (Attempt {attempt}/{maxAttempts})")
|
|
219
|
+
continue
|
|
220
|
+
|
|
221
|
+
raise TimeoutError("Failed to download the file!")
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import subprocess
|
|
3
|
+
|
|
4
|
+
def install_and_import(package_name):
|
|
5
|
+
try:
|
|
6
|
+
__import__(package_name)
|
|
7
|
+
except ModuleNotFoundError:
|
|
8
|
+
print(f"Module '{package_name}' not found. Installing...")
|
|
9
|
+
subprocess.check_call([sys.executable, "-m", "pip", "install", package_name])
|
|
10
|
+
finally:
|
|
11
|
+
globals()[package_name] = __import__(package_name)
|
|
12
|
+
|
|
13
|
+
install_and_import("websocket")
|
|
14
|
+
|
|
15
|
+
from websocket import WebSocketApp
|
|
16
|
+
|
|
17
|
+
from .helper import Helper
|
|
18
|
+
from json import dumps, loads
|
|
19
|
+
from threading import Thread
|
|
20
|
+
from ..types import Message
|
|
21
|
+
from ..exceptions import NotRegistered, TooRequests
|
|
22
|
+
from ..utils import Utils
|
|
23
|
+
from re import match
|
|
24
|
+
from time import sleep
|
|
25
|
+
|
|
26
|
+
class Socket:
|
|
27
|
+
def __init__(self, methods) -> None:
|
|
28
|
+
self.methods = methods
|
|
29
|
+
self.handlers = {}
|
|
30
|
+
|
|
31
|
+
...
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .sessions import Sessions
|