Rubka 2.11.13__py3-none-any.whl → 3.3.4__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.
@@ -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,2 @@
1
+ from .configs import Configs
2
+ from .utils import Utils
@@ -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"
rubka/api.py CHANGED
@@ -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,40 @@ class Robot:
218
222
 
219
223
  return self._post("sendMessage", payload)
220
224
 
225
+ def _get_client(self):
226
+ return Client_get(show_last_six_words(self.token))
227
+
228
+ def check_join(self, channel_guid: str, chat_id: str = None) -> bool | list[str]:
229
+ client = self._get_client()
230
+
231
+ if chat_id:
232
+ chat_info = self.get_chat(chat_id).get('data', {}).get('chat', {})
233
+ username = chat_info.get('username')
234
+ user_id = chat_info.get('user_id')
235
+
236
+ if username:
237
+ members = self.get_all_member(channel_guid, search_text=username).get('in_chat_members', [])
238
+ return any(m.get('username') == username for m in members)
239
+
240
+ elif user_id:
241
+ member_guids = client.get_all_members(channel_guid, just_get_guids=True)
242
+ return user_id in member_guids
243
+
244
+ return False
245
+
246
+ return False
247
+
248
+
249
+ def get_all_member(
250
+ self,
251
+ channel_guid: str,
252
+ search_text: str = None,
253
+ start_id: str = None,
254
+ just_get_guids: bool = False
255
+ ):
256
+ client = self._get_client()
257
+ return client.get_all_members(channel_guid, search_text, start_id, just_get_guids)
258
+
221
259
  def send_poll(
222
260
  self,
223
261
  chat_id: str,
@@ -366,3 +404,23 @@ class Robot:
366
404
  "chat_keypad_type": "New",
367
405
  "chat_keypad": chat_keypad
368
406
  })
407
+ def get_name(self, chat_id: str) -> str:
408
+ try:
409
+ chat = self.get_chat(chat_id)
410
+ chat_info = chat.get("data", {}).get("chat", {})
411
+ first_name = chat_info.get("first_name", "")
412
+ last_name = chat_info.get("last_name", "")
413
+
414
+ if first_name and last_name:
415
+ return f"{first_name} {last_name}"
416
+ elif first_name:
417
+ return first_name
418
+ elif last_name:
419
+ return last_name
420
+ else:
421
+ return "Unknown"
422
+ except Exception:
423
+ return "Unknown"
424
+ def get_username(self, chat_id: str) -> str:
425
+ chat_info = self.get_chat(chat_id).get("data", {}).get("chat", {})
426
+ return chat_info.get("username", "None")