Rubka 2.11.13__tar.gz → 3.2.5__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.
- {rubka-2.11.13 → rubka-3.2.5}/PKG-INFO +1 -1
- {rubka-2.11.13 → rubka-3.2.5}/Rubka.egg-info/PKG-INFO +1 -1
- rubka-3.2.5/Rubka.egg-info/SOURCES.txt +39 -0
- rubka-3.2.5/rubka/adaptorrubka/__init__.py +4 -0
- rubka-3.2.5/rubka/adaptorrubka/client/__init__.py +1 -0
- rubka-3.2.5/rubka/adaptorrubka/client/client.py +60 -0
- rubka-3.2.5/rubka/adaptorrubka/crypto/__init__.py +1 -0
- rubka-3.2.5/rubka/adaptorrubka/crypto/crypto.py +82 -0
- rubka-3.2.5/rubka/adaptorrubka/enums.py +36 -0
- rubka-3.2.5/rubka/adaptorrubka/exceptions.py +22 -0
- rubka-3.2.5/rubka/adaptorrubka/methods/__init__.py +1 -0
- rubka-3.2.5/rubka/adaptorrubka/methods/methods.py +90 -0
- rubka-3.2.5/rubka/adaptorrubka/network/__init__.py +3 -0
- rubka-3.2.5/rubka/adaptorrubka/network/helper.py +22 -0
- rubka-3.2.5/rubka/adaptorrubka/network/network.py +221 -0
- rubka-3.2.5/rubka/adaptorrubka/network/socket.py +16 -0
- rubka-3.2.5/rubka/adaptorrubka/sessions/__init__.py +1 -0
- rubka-3.2.5/rubka/adaptorrubka/sessions/sessions.py +72 -0
- rubka-3.2.5/rubka/adaptorrubka/types/__init__.py +1 -0
- rubka-3.2.5/rubka/adaptorrubka/types/socket/__init__.py +1 -0
- rubka-3.2.5/rubka/adaptorrubka/types/socket/message.py +187 -0
- rubka-3.2.5/rubka/adaptorrubka/utils/__init__.py +2 -0
- rubka-3.2.5/rubka/adaptorrubka/utils/configs.py +18 -0
- rubka-3.2.5/rubka/adaptorrubka/utils/utils.py +251 -0
- {rubka-2.11.13 → rubka-3.2.5}/rubka/api.py +7 -0
- {rubka-2.11.13 → rubka-3.2.5}/setup.py +1 -1
- rubka-2.11.13/Rubka.egg-info/SOURCES.txt +0 -18
- {rubka-2.11.13 → rubka-3.2.5}/README.md +0 -0
- {rubka-2.11.13 → rubka-3.2.5}/Rubka.egg-info/dependency_links.txt +0 -0
- {rubka-2.11.13 → rubka-3.2.5}/Rubka.egg-info/requires.txt +0 -0
- {rubka-2.11.13 → rubka-3.2.5}/Rubka.egg-info/top_level.txt +0 -0
- {rubka-2.11.13 → rubka-3.2.5}/rubka/__init__.py +0 -0
- {rubka-2.11.13 → rubka-3.2.5}/rubka/config.py +0 -0
- {rubka-2.11.13 → rubka-3.2.5}/rubka/context.py +0 -0
- {rubka-2.11.13 → rubka-3.2.5}/rubka/decorators.py +0 -0
- {rubka-2.11.13 → rubka-3.2.5}/rubka/exceptions.py +0 -0
- {rubka-2.11.13 → rubka-3.2.5}/rubka/jobs.py +0 -0
- {rubka-2.11.13 → rubka-3.2.5}/rubka/keyboards.py +0 -0
- {rubka-2.11.13 → rubka-3.2.5}/rubka/keypad.py +0 -0
- {rubka-2.11.13 → rubka-3.2.5}/rubka/logger.py +0 -0
- {rubka-2.11.13 → rubka-3.2.5}/rubka/utils.py +0 -0
- {rubka-2.11.13 → rubka-3.2.5}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: Rubka
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 3.2.5
|
|
4
4
|
Summary: A Python library for interacting with Rubika Bot API.
|
|
5
5
|
Home-page: https://github.com/Mahdy-Ahmadi/Rubka
|
|
6
6
|
Download-URL: https://github.com/Mahdy-Ahmadi/Rubka/archive/refs/tags/v0.1.0.tar.gz
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: Rubka
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 3.2.5
|
|
4
4
|
Summary: A Python library for interacting with Rubika Bot API.
|
|
5
5
|
Home-page: https://github.com/Mahdy-Ahmadi/Rubka
|
|
6
6
|
Download-URL: https://github.com/Mahdy-Ahmadi/Rubka/archive/refs/tags/v0.1.0.tar.gz
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
setup.py
|
|
3
|
+
Rubka.egg-info/PKG-INFO
|
|
4
|
+
Rubka.egg-info/SOURCES.txt
|
|
5
|
+
Rubka.egg-info/dependency_links.txt
|
|
6
|
+
Rubka.egg-info/requires.txt
|
|
7
|
+
Rubka.egg-info/top_level.txt
|
|
8
|
+
rubka/__init__.py
|
|
9
|
+
rubka/api.py
|
|
10
|
+
rubka/config.py
|
|
11
|
+
rubka/context.py
|
|
12
|
+
rubka/decorators.py
|
|
13
|
+
rubka/exceptions.py
|
|
14
|
+
rubka/jobs.py
|
|
15
|
+
rubka/keyboards.py
|
|
16
|
+
rubka/keypad.py
|
|
17
|
+
rubka/logger.py
|
|
18
|
+
rubka/utils.py
|
|
19
|
+
rubka/adaptorrubka/__init__.py
|
|
20
|
+
rubka/adaptorrubka/enums.py
|
|
21
|
+
rubka/adaptorrubka/exceptions.py
|
|
22
|
+
rubka/adaptorrubka/client/__init__.py
|
|
23
|
+
rubka/adaptorrubka/client/client.py
|
|
24
|
+
rubka/adaptorrubka/crypto/__init__.py
|
|
25
|
+
rubka/adaptorrubka/crypto/crypto.py
|
|
26
|
+
rubka/adaptorrubka/methods/__init__.py
|
|
27
|
+
rubka/adaptorrubka/methods/methods.py
|
|
28
|
+
rubka/adaptorrubka/network/__init__.py
|
|
29
|
+
rubka/adaptorrubka/network/helper.py
|
|
30
|
+
rubka/adaptorrubka/network/network.py
|
|
31
|
+
rubka/adaptorrubka/network/socket.py
|
|
32
|
+
rubka/adaptorrubka/sessions/__init__.py
|
|
33
|
+
rubka/adaptorrubka/sessions/sessions.py
|
|
34
|
+
rubka/adaptorrubka/types/__init__.py
|
|
35
|
+
rubka/adaptorrubka/types/socket/__init__.py
|
|
36
|
+
rubka/adaptorrubka/types/socket/message.py
|
|
37
|
+
rubka/adaptorrubka/utils/__init__.py
|
|
38
|
+
rubka/adaptorrubka/utils/configs.py
|
|
39
|
+
rubka/adaptorrubka/utils/utils.py
|
|
@@ -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,16 @@
|
|
|
1
|
+
from websocket import WebSocketApp
|
|
2
|
+
from .helper import Helper
|
|
3
|
+
from json import dumps, loads
|
|
4
|
+
from threading import Thread
|
|
5
|
+
from ..types import Message
|
|
6
|
+
from ..exceptions import NotRegistered, TooRequests
|
|
7
|
+
from ..utils import Utils
|
|
8
|
+
from re import match
|
|
9
|
+
from time import sleep
|
|
10
|
+
|
|
11
|
+
class Socket:
|
|
12
|
+
def __init__(self, methods) -> None:
|
|
13
|
+
self.methods = methods
|
|
14
|
+
self.handlers = {}
|
|
15
|
+
|
|
16
|
+
...
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .sessions import Sessions
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from os.path import exists
|
|
2
|
+
from json import loads, dumps
|
|
3
|
+
|
|
4
|
+
class Sessions:
|
|
5
|
+
|
|
6
|
+
def __init__(self, client:object) -> None:
|
|
7
|
+
self.client = client
|
|
8
|
+
|
|
9
|
+
def cheackSessionExists(self):
|
|
10
|
+
return exists(f"{self.client.session}.rubka")
|
|
11
|
+
|
|
12
|
+
def loadSessionData(self):
|
|
13
|
+
return loads(open(f"{self.client.session}.rubka", encoding="UTF-8").read())
|
|
14
|
+
|
|
15
|
+
def createSession(self):
|
|
16
|
+
from ..methods import Methods
|
|
17
|
+
methods:object = Methods(
|
|
18
|
+
sessionData={},
|
|
19
|
+
platform=self.client.platform,
|
|
20
|
+
apiVersion=6,
|
|
21
|
+
proxy=self.client.proxy,
|
|
22
|
+
timeOut=self.client.timeOut,
|
|
23
|
+
showProgressBar=True
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
while True:
|
|
27
|
+
phoneNumber:str = input("\nphone number :\t")
|
|
28
|
+
try:
|
|
29
|
+
sendCodeData:dict = methods.sendCode(phoneNumber=phoneNumber)
|
|
30
|
+
except:
|
|
31
|
+
print("The phone number is invalid! Please try again.")
|
|
32
|
+
continue
|
|
33
|
+
|
|
34
|
+
if sendCodeData['status'] == 'SendPassKey':
|
|
35
|
+
while True:
|
|
36
|
+
passKey:str = input(f'\npass key [{sendCodeData["hint_pass_key"]}] : ')
|
|
37
|
+
sendCodeData:dict = methods.sendCode(phoneNumber=phoneNumber, passKey=passKey)
|
|
38
|
+
|
|
39
|
+
if sendCodeData['status'] == 'InvalidPassKey':
|
|
40
|
+
print(f'\nThe pass key({sendCodeData["hint_pass_key"]})try again.')
|
|
41
|
+
continue
|
|
42
|
+
break
|
|
43
|
+
|
|
44
|
+
while True:
|
|
45
|
+
phoneCode:str = input("\ncode : ").strip()
|
|
46
|
+
signInData:dict = methods.signIn(phoneNumber=phoneNumber, phoneCodeHash=sendCodeData['phone_code_hash'], phoneCode=phoneCode)
|
|
47
|
+
if signInData['status'] != 'OK':
|
|
48
|
+
print("The code is invalid! Please try again.")
|
|
49
|
+
continue
|
|
50
|
+
break
|
|
51
|
+
|
|
52
|
+
from ..crypto import Cryption
|
|
53
|
+
|
|
54
|
+
sessionData = {
|
|
55
|
+
'auth': Cryption.decryptRsaOaep(signInData["private_key"], signInData['auth']),
|
|
56
|
+
'private_key': signInData["private_key"],
|
|
57
|
+
'user': signInData['user'],
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
open(f"{self.client.session}.rubka", "w", encoding="UTF-8").write(dumps(sessionData, indent=4))
|
|
61
|
+
|
|
62
|
+
Methods(
|
|
63
|
+
sessionData=sessionData,
|
|
64
|
+
platform=self.client.platform,
|
|
65
|
+
apiVersion=6,
|
|
66
|
+
proxy=self.client.proxy,
|
|
67
|
+
timeOut=self.client.timeOut,
|
|
68
|
+
showProgressBar=True
|
|
69
|
+
).registerDevice(deviceModel=f"rubka-Api-{self.client.session}")
|
|
70
|
+
print(f"\nSign successful")
|
|
71
|
+
|
|
72
|
+
return sessionData
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .socket import Message
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .message import Message
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
class ReplyInfo:
|
|
2
|
+
def __init__(self, text, author_guid) -> None:
|
|
3
|
+
self.text = text
|
|
4
|
+
self.author_guid = author_guid
|
|
5
|
+
pass
|
|
6
|
+
|
|
7
|
+
@classmethod
|
|
8
|
+
def from_json(cls, json: dict):
|
|
9
|
+
return cls(json["text"], json["author_object_guid"])
|
|
10
|
+
|
|
11
|
+
class Message:
|
|
12
|
+
def __init__(self, data:dict, methods:object) -> None:
|
|
13
|
+
self.data = data
|
|
14
|
+
self.methods = methods
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
def object_guid(self) -> str:
|
|
18
|
+
return self.data["chat_updates"][0].get("object_guid")
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
def chat_type(self) -> str:
|
|
22
|
+
return self.data["chat_updates"][0].get("type")
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def count_unseen(self) -> int:
|
|
26
|
+
return int(self.data["chat_updates"][0]["chat"].get("count_unseen", 0))
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def last_seen_peer_mid(self) -> str:
|
|
30
|
+
return self.data["chat_updates"][0]["chat"].get("last_seen_peer_mid")
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def time_string(self) -> str:
|
|
34
|
+
return self.data["chat_updates"][0]["chat"].get("time_string")
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def is_mine(self) -> bool:
|
|
38
|
+
return self.data["chat_updates"][0]["chat"]["last_message"].get("is_mine")
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def time(self) -> str:
|
|
42
|
+
return self.data["chat_updates"][0]["chat"]["last_message"].get("time")
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def status(self) -> str:
|
|
46
|
+
return self.data["chat_updates"][0]["chat"].get("status")
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def last_message_id(self) -> str:
|
|
50
|
+
return self.data["chat_updates"][0]["chat"].get("last_message_id")
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def action(self) -> str:
|
|
54
|
+
return self.data["message_updates"][0].get("action")
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def message_id(self) -> str:
|
|
58
|
+
return self.data["message_updates"][0].get("message_id")
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def reply_message_id(self) -> str:
|
|
62
|
+
return self.data["message_updates"][0]["message"].get("reply_to_message_id")
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def text(self) -> str:
|
|
66
|
+
return str(self.data["message_updates"][0]["message"].get("text"))
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def is_edited(self) -> bool:
|
|
70
|
+
return self.data["message_updates"][0]["message"].get("is_edited")
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
def message_type(self) -> str:
|
|
74
|
+
if self.file_inline:
|
|
75
|
+
return self.file_inline["type"]
|
|
76
|
+
|
|
77
|
+
return self.data["message_updates"][0]["message"].get("type")
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def author_type(self) -> str:
|
|
81
|
+
return self.data["message_updates"][0]["message"].get("author_type")
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def author_guid(self) -> str:
|
|
85
|
+
return self.data["message_updates"][0]["message"].get("author_object_guid")
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def prev_message_id(self) -> str:
|
|
89
|
+
return self.data["message_updates"][0].get("prev_message_id")
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def state(self) -> str:
|
|
93
|
+
return self.data["message_updates"][0].get("state")
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def title(self) -> str:
|
|
97
|
+
if self.data['show_notifications']:
|
|
98
|
+
return self.data['show_notifications'][0].get('title')
|
|
99
|
+
|
|
100
|
+
@property
|
|
101
|
+
def author_title(self) -> str:
|
|
102
|
+
return self.data['chat_updates'][0]['chat']['last_message'].get('author_title', self.title)
|
|
103
|
+
|
|
104
|
+
@property
|
|
105
|
+
def is_user(self) -> bool:
|
|
106
|
+
return self.chat_type == "User"
|
|
107
|
+
|
|
108
|
+
@property
|
|
109
|
+
def is_group(self) -> bool:
|
|
110
|
+
return self.chat_type == "Group"
|
|
111
|
+
|
|
112
|
+
@property
|
|
113
|
+
def is_forward(self) -> bool:
|
|
114
|
+
return "forwarded_from" in self.data["message_updates"][0]["message"].keys()
|
|
115
|
+
|
|
116
|
+
@property
|
|
117
|
+
def forward_from(self) -> str:
|
|
118
|
+
return self.data["message_updates"][0]["message"]["forwarded_from"].get("type_from") if self.is_forward else None
|
|
119
|
+
|
|
120
|
+
@property
|
|
121
|
+
def forward_object_guid(self) -> str:
|
|
122
|
+
return self.data["message_updates"][0]["message"]["forwarded_from"].get("object_guid") if self.is_forward else None
|
|
123
|
+
|
|
124
|
+
@property
|
|
125
|
+
def forward_message_id(self) -> str:
|
|
126
|
+
return self.data["message_updates"][0]["message"]["forwarded_from"].get("message_id") if self.is_forward else None
|
|
127
|
+
|
|
128
|
+
@property
|
|
129
|
+
def is_event(self) -> bool:
|
|
130
|
+
return 'event_data' in self.data['message_updates'][0]['message'].keys()
|
|
131
|
+
|
|
132
|
+
@property
|
|
133
|
+
def event_type(self) -> str:
|
|
134
|
+
return self.data['message_updates'][0]['message']['event_data'].get('type') if self.is_event else None
|
|
135
|
+
|
|
136
|
+
@property
|
|
137
|
+
def event_object_guid(self) -> str:
|
|
138
|
+
return self.data['message_updates'][0]['message']['event_data']['performer_object'].get('object_guid') if self.is_event else None
|
|
139
|
+
|
|
140
|
+
@property
|
|
141
|
+
def pinned_message_id(self) -> str:
|
|
142
|
+
return self.data['message_updates'][0]['message']['event_data'].get('pinned_message_id') if self.is_event else None
|
|
143
|
+
|
|
144
|
+
@property
|
|
145
|
+
def file_inline(self) -> dict:
|
|
146
|
+
return self.data["message_updates"][0]["message"].get("file_inline")
|
|
147
|
+
|
|
148
|
+
@property
|
|
149
|
+
def has_link(self) -> bool:
|
|
150
|
+
for link in ["http:/", "https:/", "www.", ".ir", ".com", ".net" "@"]:
|
|
151
|
+
if link in self.text.lower():
|
|
152
|
+
return True
|
|
153
|
+
return False
|
|
154
|
+
|
|
155
|
+
@property
|
|
156
|
+
def reply_info(self) -> ReplyInfo:
|
|
157
|
+
if not self.reply_message_id:
|
|
158
|
+
return
|
|
159
|
+
|
|
160
|
+
return ReplyInfo.from_json(self.methods.getMessagesById(self.object_guid, [self.reply_message_id])["messages"][0])
|
|
161
|
+
|
|
162
|
+
def reply(self, text:str) -> dict:
|
|
163
|
+
return self.methods.sendText(objectGuid=self.object_guid, text=text, messageId=self.message_id)
|
|
164
|
+
|
|
165
|
+
def seen(self) -> dict:
|
|
166
|
+
return self.methods.seenChats(seenList={self.object_guid: self.message_id})
|
|
167
|
+
|
|
168
|
+
def reaction(self, reaction:int) -> dict:
|
|
169
|
+
return self.methods.actionOnMessageReaction(objectGuid=self.object_guid, messageId=self.message_id, reactionId=reaction, action="Add")
|
|
170
|
+
|
|
171
|
+
def delete(self, delete_for_all:bool=True) -> dict:
|
|
172
|
+
return self.methods.deleteMessages(objectGuid=self.object_guid, messageIds=[self.message_id], deleteForAll=delete_for_all)
|
|
173
|
+
|
|
174
|
+
def pin(self) -> dict:
|
|
175
|
+
return self.methods.pinMessage(objectGuid=self.object_guid, messageId=self.message_id)
|
|
176
|
+
|
|
177
|
+
def forward(self, to_object_guid:str) -> dict:
|
|
178
|
+
return self.methods.forwardMessages(objectGuid=self.object_guid, message_ids=[self.message_id], toObjectGuid=to_object_guid)
|
|
179
|
+
|
|
180
|
+
def ban(self) -> dict:
|
|
181
|
+
return self.methods.banMember(objectGuid=self.object_guid, memberGuid=self.author_guid)
|
|
182
|
+
|
|
183
|
+
def check_join(self, object_guid:str) -> bool:
|
|
184
|
+
return self.methods.checkJoin(objectGuid=object_guid, userGuid=self.author_guid)
|
|
185
|
+
|
|
186
|
+
def download(self, save:bool=False, save_as:str=None) -> dict:
|
|
187
|
+
return self.methods.download(save=save, saveAs=save_as, fileInline=self.file_inline)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
class Configs:
|
|
2
|
+
clients:dict = {
|
|
3
|
+
"web": {
|
|
4
|
+
"app_name": "Main",
|
|
5
|
+
"app_version": "4.4.6",
|
|
6
|
+
"lang_code": "fa",
|
|
7
|
+
"package": "web.rubika.ir",
|
|
8
|
+
"platform": "Web",
|
|
9
|
+
},
|
|
10
|
+
"android": {
|
|
11
|
+
"app_name": "Main",
|
|
12
|
+
"app_version": "3.5.7",
|
|
13
|
+
"lang_code": "fa",
|
|
14
|
+
"package": "app.rbmain.a",
|
|
15
|
+
"temp_code": "27",
|
|
16
|
+
"platform": "Android"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
|
|
2
|
+
from random import choices, randint
|
|
3
|
+
from time import time
|
|
4
|
+
from re import finditer, sub
|
|
5
|
+
from base64 import b64encode
|
|
6
|
+
from io import BytesIO
|
|
7
|
+
from tempfile import NamedTemporaryFile
|
|
8
|
+
from mutagen import mp3, File
|
|
9
|
+
from filetype import guess
|
|
10
|
+
from os import system, chmod, remove
|
|
11
|
+
from .configs import Configs
|
|
12
|
+
|
|
13
|
+
class Utils:
|
|
14
|
+
|
|
15
|
+
def randomTmpSession() -> str:
|
|
16
|
+
return "".join(choices("abcdefghijklmnopqrstuvwxyz", k=32))
|
|
17
|
+
|
|
18
|
+
def randomDeviceHash() -> str:
|
|
19
|
+
return "".join(choices("0123456789", k=26))
|
|
20
|
+
|
|
21
|
+
def randomRnd() -> str:
|
|
22
|
+
return str(randint(-99999999, 99999999))
|
|
23
|
+
|
|
24
|
+
def privateParse(private:str) -> str:
|
|
25
|
+
if private:
|
|
26
|
+
if not private.startswith("-----BEGIN RSA PRIVATE KEY-----"):
|
|
27
|
+
private = "-----BEGIN RSA PRIVATE KEY-----\\n" + private
|
|
28
|
+
|
|
29
|
+
if not private.endswith("-----END RSA PRIVATE KEY-----"):
|
|
30
|
+
private += "\\n-----END RSA PRIVATE KEY-----"
|
|
31
|
+
|
|
32
|
+
return private.replace("\\n", "\n").strip()
|
|
33
|
+
|
|
34
|
+
def getState() -> int:
|
|
35
|
+
return int(time()) - 150
|
|
36
|
+
|
|
37
|
+
def phoneNumberParse(phoneNumber:str) -> str:
|
|
38
|
+
if str(phoneNumber).startswith("0"):
|
|
39
|
+
phoneNumber = phoneNumber[1:]
|
|
40
|
+
|
|
41
|
+
elif str(phoneNumber).startswith("98"):
|
|
42
|
+
phoneNumber = phoneNumber[2:]
|
|
43
|
+
|
|
44
|
+
elif str(phoneNumber).startswith("+98"):
|
|
45
|
+
phoneNumber = phoneNumber[3:]
|
|
46
|
+
|
|
47
|
+
return phoneNumber
|
|
48
|
+
|
|
49
|
+
def getChatTypeByGuid(objectGuid:str) -> str:
|
|
50
|
+
for chatType in [("u0", "User"), ("g0", "Group"), ("c0", "Channel"), ("s0", "Service"), ("b0", "Bot")]:
|
|
51
|
+
if objectGuid.startswith(chatType[0]):
|
|
52
|
+
return chatType[1]
|
|
53
|
+
|
|
54
|
+
def isChatType(chatType:str) -> bool:
|
|
55
|
+
chatType = chatType.lower()
|
|
56
|
+
for type in ["user", "group", "channel", "service", "bot"]:
|
|
57
|
+
if type == chatType:
|
|
58
|
+
return True
|
|
59
|
+
|
|
60
|
+
def isMessageType(messageType:str) -> bool:
|
|
61
|
+
messageType = messageType.lower()
|
|
62
|
+
for type in ["text", "image", "video", "gif", "video message", "voice", "music", "file"]:
|
|
63
|
+
if type == messageType:
|
|
64
|
+
return True
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def getChatTypeByLink(link:str) -> str:
|
|
68
|
+
if "rubika.ir/joing" in link: return "Group"
|
|
69
|
+
elif "rubika.ir/joinc" in link: return "Channel"
|
|
70
|
+
|
|
71
|
+
def checkMetadata(text):
|
|
72
|
+
if text is None:
|
|
73
|
+
return [], ""
|
|
74
|
+
|
|
75
|
+
real_text = sub(r"``|\*\*|__|~~|--|@@|##|", "", text)
|
|
76
|
+
metadata = []
|
|
77
|
+
conflict = 0
|
|
78
|
+
mentionObjectIndex = 0
|
|
79
|
+
result = []
|
|
80
|
+
|
|
81
|
+
patterns = {
|
|
82
|
+
"Mono": r"\`\`([^``]*)\`\`",
|
|
83
|
+
"Bold": r"\*\*([^**]*)\*\*",
|
|
84
|
+
"Italic": r"\_\_([^__]*)\_\_",
|
|
85
|
+
"Strike": r"\~\~([^~~]*)\~\~",
|
|
86
|
+
"Underline": r"\-\-([^__]*)\-\-",
|
|
87
|
+
"Mention": r"\@\@([^@@]*)\@\@",
|
|
88
|
+
"Spoiler": r"\#\#([^##]*)\#\#",
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
for style, pattern in patterns.items():
|
|
92
|
+
for match in finditer(pattern, text):
|
|
93
|
+
metadata.append((match.start(), len(match.group(1)), style))
|
|
94
|
+
|
|
95
|
+
metadata.sort()
|
|
96
|
+
|
|
97
|
+
for start, length, style in metadata:
|
|
98
|
+
if not style == "Mention":
|
|
99
|
+
result.append({
|
|
100
|
+
"type": style,
|
|
101
|
+
"from_index": start - conflict,
|
|
102
|
+
"length": length,
|
|
103
|
+
})
|
|
104
|
+
conflict += 4
|
|
105
|
+
else:
|
|
106
|
+
mentionObjects = [i.group(1) for i in finditer(r"\@\(([^(]*)\)", text)]
|
|
107
|
+
mentionType = Utils.getChatTypeByGuid(objectGuid=mentionObjects[mentionObjectIndex]) or "Link"
|
|
108
|
+
|
|
109
|
+
if mentionType == "Link":
|
|
110
|
+
result.append(
|
|
111
|
+
{
|
|
112
|
+
"from_index": start - conflict,
|
|
113
|
+
"length": length,
|
|
114
|
+
"link": {
|
|
115
|
+
"hyperlink_data": {
|
|
116
|
+
"url": mentionObjects[mentionObjectIndex]
|
|
117
|
+
},
|
|
118
|
+
"type": "hyperlink",
|
|
119
|
+
},
|
|
120
|
+
"type": mentionType,
|
|
121
|
+
}
|
|
122
|
+
)
|
|
123
|
+
else:
|
|
124
|
+
result.append(
|
|
125
|
+
{
|
|
126
|
+
"type": "MentionText",
|
|
127
|
+
"from_index": start - conflict,
|
|
128
|
+
"length": length,
|
|
129
|
+
"mention_text_object_guid": mentionObjects[mentionObjectIndex],
|
|
130
|
+
"mention_text_object_type": mentionType
|
|
131
|
+
}
|
|
132
|
+
)
|
|
133
|
+
real_text = real_text.replace(f"({mentionObjects[mentionObjectIndex]})", "")
|
|
134
|
+
conflict += 6 + len(mentionObjects[mentionObjectIndex])
|
|
135
|
+
mentionObjectIndex += 1
|
|
136
|
+
|
|
137
|
+
return result, real_text
|
|
138
|
+
|
|
139
|
+
def checkLink(url:str) -> dict:
|
|
140
|
+
for i in ["http:/", "https://"]:
|
|
141
|
+
if url.startswith(i): return True
|
|
142
|
+
|
|
143
|
+
def getMimeFromByte(bytes:bytes) -> str:
|
|
144
|
+
mime = guess(bytes)
|
|
145
|
+
return "pyrubi" if mime is None else mime.extension
|
|
146
|
+
|
|
147
|
+
def generateFileName(mime:str) -> str:
|
|
148
|
+
return "Pyrubi Library {}.{}".format(randint(1, 1000), mime)
|
|
149
|
+
|
|
150
|
+
def getImageSize(bytes:bytes) -> str:
|
|
151
|
+
try:
|
|
152
|
+
from PIL import Image
|
|
153
|
+
except ImportError:
|
|
154
|
+
system("pip install pillow")
|
|
155
|
+
from PIL import Image
|
|
156
|
+
|
|
157
|
+
width, height = Image.open(BytesIO(bytes)).size
|
|
158
|
+
return width , height
|
|
159
|
+
|
|
160
|
+
def getImageThumbnail(bytes:bytes) -> str:
|
|
161
|
+
try:
|
|
162
|
+
from PIL import Image
|
|
163
|
+
except ImportError:
|
|
164
|
+
system("pip install pillow")
|
|
165
|
+
from PIL import Image
|
|
166
|
+
|
|
167
|
+
image = Image.open(BytesIO(bytes))
|
|
168
|
+
width, height = image.size
|
|
169
|
+
if height > width:
|
|
170
|
+
new_height = 40
|
|
171
|
+
new_width = round(new_height * width / height)
|
|
172
|
+
else:
|
|
173
|
+
new_width = 40
|
|
174
|
+
new_height = round(new_width * height / width)
|
|
175
|
+
image = image.resize((new_width, new_height), Image.LANCZOS)
|
|
176
|
+
changed_image = BytesIO()
|
|
177
|
+
image.save(changed_image, format="PNG")
|
|
178
|
+
return b64encode(changed_image.getvalue()).decode("UTF-8")
|
|
179
|
+
|
|
180
|
+
def getVideoData(bytes:bytes) -> list:
|
|
181
|
+
try:
|
|
182
|
+
from moviepy.editor import VideoFileClip
|
|
183
|
+
|
|
184
|
+
with NamedTemporaryFile(delete=False, dir=".") as temp_video:
|
|
185
|
+
temp_video.write(bytes)
|
|
186
|
+
temp_path = temp_video.name
|
|
187
|
+
|
|
188
|
+
chmod(temp_path, 0o777)
|
|
189
|
+
|
|
190
|
+
try:
|
|
191
|
+
from PIL import Image
|
|
192
|
+
except ImportError:
|
|
193
|
+
system("pip install pillow")
|
|
194
|
+
from PIL import Image
|
|
195
|
+
|
|
196
|
+
with VideoFileClip(temp_path) as clip:
|
|
197
|
+
duration = clip.duration
|
|
198
|
+
resolution = clip.size
|
|
199
|
+
thumbnail = clip.get_frame(0)
|
|
200
|
+
thumbnail_image = Image.fromarray(thumbnail)
|
|
201
|
+
thumbnail_buffer = BytesIO()
|
|
202
|
+
thumbnail_image.save(thumbnail_buffer, format="JPEG")
|
|
203
|
+
thumbnail_b64 = b64encode(thumbnail_buffer.getvalue()).decode("UTF-8")
|
|
204
|
+
clip.close()
|
|
205
|
+
|
|
206
|
+
remove(temp_path)
|
|
207
|
+
|
|
208
|
+
return thumbnail_b64, resolution, duration
|
|
209
|
+
except ImportError:
|
|
210
|
+
print(Colors.YELLOW + "Can't get video data! Please install the moviepy library by following command:\npip install moviepy" + Colors.RESET)
|
|
211
|
+
return Configs.defaultTumbInline, [900, 720], 1
|
|
212
|
+
except:
|
|
213
|
+
return Configs.defaultTumbInline, [900, 720], 1
|
|
214
|
+
|
|
215
|
+
def getVoiceDuration(bytes:bytes) -> int:
|
|
216
|
+
file = BytesIO()
|
|
217
|
+
file.write(bytes)
|
|
218
|
+
file.seek(0)
|
|
219
|
+
return mp3.MP3(file).info.length
|
|
220
|
+
|
|
221
|
+
def getMusicArtist(bytes:bytes) -> str:
|
|
222
|
+
try:
|
|
223
|
+
audio = File(BytesIO(bytes), easy=True)
|
|
224
|
+
|
|
225
|
+
if audio and "artist" in audio:
|
|
226
|
+
return audio["artist"][0]
|
|
227
|
+
|
|
228
|
+
return "pyrubi"
|
|
229
|
+
except Exception:
|
|
230
|
+
return "pyrubi"
|
|
231
|
+
|
|
232
|
+
class Colors:
|
|
233
|
+
RESET = "\033[0m"
|
|
234
|
+
BLACK = "\033[30m"
|
|
235
|
+
RED = "\033[31m"
|
|
236
|
+
GREEN = "\033[32m"
|
|
237
|
+
YELLOW = "\033[33m"
|
|
238
|
+
BLUE = "\033[34m"
|
|
239
|
+
MAGENTA = "\033[35m"
|
|
240
|
+
CYAN = "\033[36m"
|
|
241
|
+
WHITE = "\033[37m"
|
|
242
|
+
|
|
243
|
+
BG_RESET = "\033[49m"
|
|
244
|
+
BG_BLACK = "\033[40m"
|
|
245
|
+
BG_RED = "\033[41m"
|
|
246
|
+
BG_GREEN = "\033[42m"
|
|
247
|
+
BG_YELLOW = "\033[43m"
|
|
248
|
+
BG_BLUE = "\033[44m"
|
|
249
|
+
BG_MAGENTA = "\033[45m"
|
|
250
|
+
BG_CYAN = "\033[46m"
|
|
251
|
+
BG_WHITE = "\033[47m"
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import requests
|
|
2
2
|
from typing import List, Optional, Dict, Any, Literal
|
|
3
3
|
from .exceptions import APIRequestError
|
|
4
|
+
from .adaptorrubka import Client as Client_get
|
|
4
5
|
from .logger import logger
|
|
5
6
|
from typing import Callable
|
|
6
7
|
from .context import Message,InlineMessage
|
|
@@ -55,6 +56,9 @@ def check_rubka_version():
|
|
|
55
56
|
print(f"Please update it using:\n\npip install --upgrade {package_name}\n")
|
|
56
57
|
|
|
57
58
|
check_rubka_version()
|
|
59
|
+
def show_last_six_words(text):
|
|
60
|
+
text = text.strip()
|
|
61
|
+
return text[-6:]
|
|
58
62
|
class Robot:
|
|
59
63
|
"""
|
|
60
64
|
Main class to interact with Rubika Bot API.
|
|
@@ -218,6 +222,9 @@ class Robot:
|
|
|
218
222
|
|
|
219
223
|
return self._post("sendMessage", payload)
|
|
220
224
|
|
|
225
|
+
def Get_Member_channel(self,object_guid: str, search_text: str = None, start_id: str = None, just_get_guids: bool = False):
|
|
226
|
+
getter = Client_get(show_last_six_words(self.token))
|
|
227
|
+
return getter.get_all_members(object_guid,search_text,start_id,just_get_guids)
|
|
221
228
|
def send_poll(
|
|
222
229
|
self,
|
|
223
230
|
chat_id: str,
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
README.md
|
|
2
|
-
setup.py
|
|
3
|
-
Rubka.egg-info/PKG-INFO
|
|
4
|
-
Rubka.egg-info/SOURCES.txt
|
|
5
|
-
Rubka.egg-info/dependency_links.txt
|
|
6
|
-
Rubka.egg-info/requires.txt
|
|
7
|
-
Rubka.egg-info/top_level.txt
|
|
8
|
-
rubka/__init__.py
|
|
9
|
-
rubka/api.py
|
|
10
|
-
rubka/config.py
|
|
11
|
-
rubka/context.py
|
|
12
|
-
rubka/decorators.py
|
|
13
|
-
rubka/exceptions.py
|
|
14
|
-
rubka/jobs.py
|
|
15
|
-
rubka/keyboards.py
|
|
16
|
-
rubka/keypad.py
|
|
17
|
-
rubka/logger.py
|
|
18
|
-
rubka/utils.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|