Rubka 4.4.8__tar.gz → 4.4.20__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. {rubka-4.4.8 → rubka-4.4.20}/PKG-INFO +7 -1
  2. {rubka-4.4.8 → rubka-4.4.20}/README.md +5 -0
  3. {rubka-4.4.8 → rubka-4.4.20}/Rubka.egg-info/PKG-INFO +7 -1
  4. {rubka-4.4.8 → rubka-4.4.20}/Rubka.egg-info/requires.txt +1 -0
  5. {rubka-4.4.8 → rubka-4.4.20}/rubka/api.py +177 -52
  6. {rubka-4.4.8 → rubka-4.4.20}/rubka/context.py +7 -1
  7. {rubka-4.4.8 → rubka-4.4.20}/setup.py +3 -2
  8. {rubka-4.4.8 → rubka-4.4.20}/Rubka.egg-info/SOURCES.txt +0 -0
  9. {rubka-4.4.8 → rubka-4.4.20}/Rubka.egg-info/dependency_links.txt +0 -0
  10. {rubka-4.4.8 → rubka-4.4.20}/Rubka.egg-info/top_level.txt +0 -0
  11. {rubka-4.4.8 → rubka-4.4.20}/rubka/__init__.py +0 -0
  12. {rubka-4.4.8 → rubka-4.4.20}/rubka/adaptorrubka/__init__.py +0 -0
  13. {rubka-4.4.8 → rubka-4.4.20}/rubka/adaptorrubka/client/__init__.py +0 -0
  14. {rubka-4.4.8 → rubka-4.4.20}/rubka/adaptorrubka/client/client.py +0 -0
  15. {rubka-4.4.8 → rubka-4.4.20}/rubka/adaptorrubka/crypto/__init__.py +0 -0
  16. {rubka-4.4.8 → rubka-4.4.20}/rubka/adaptorrubka/crypto/crypto.py +0 -0
  17. {rubka-4.4.8 → rubka-4.4.20}/rubka/adaptorrubka/enums.py +0 -0
  18. {rubka-4.4.8 → rubka-4.4.20}/rubka/adaptorrubka/exceptions.py +0 -0
  19. {rubka-4.4.8 → rubka-4.4.20}/rubka/adaptorrubka/methods/__init__.py +0 -0
  20. {rubka-4.4.8 → rubka-4.4.20}/rubka/adaptorrubka/methods/methods.py +0 -0
  21. {rubka-4.4.8 → rubka-4.4.20}/rubka/adaptorrubka/network/__init__.py +0 -0
  22. {rubka-4.4.8 → rubka-4.4.20}/rubka/adaptorrubka/network/helper.py +0 -0
  23. {rubka-4.4.8 → rubka-4.4.20}/rubka/adaptorrubka/network/network.py +0 -0
  24. {rubka-4.4.8 → rubka-4.4.20}/rubka/adaptorrubka/network/socket.py +0 -0
  25. {rubka-4.4.8 → rubka-4.4.20}/rubka/adaptorrubka/sessions/__init__.py +0 -0
  26. {rubka-4.4.8 → rubka-4.4.20}/rubka/adaptorrubka/sessions/sessions.py +0 -0
  27. {rubka-4.4.8 → rubka-4.4.20}/rubka/adaptorrubka/types/__init__.py +0 -0
  28. {rubka-4.4.8 → rubka-4.4.20}/rubka/adaptorrubka/types/socket/__init__.py +0 -0
  29. {rubka-4.4.8 → rubka-4.4.20}/rubka/adaptorrubka/types/socket/message.py +0 -0
  30. {rubka-4.4.8 → rubka-4.4.20}/rubka/adaptorrubka/utils/__init__.py +0 -0
  31. {rubka-4.4.8 → rubka-4.4.20}/rubka/adaptorrubka/utils/configs.py +0 -0
  32. {rubka-4.4.8 → rubka-4.4.20}/rubka/adaptorrubka/utils/utils.py +0 -0
  33. {rubka-4.4.8 → rubka-4.4.20}/rubka/button.py +0 -0
  34. {rubka-4.4.8 → rubka-4.4.20}/rubka/config.py +0 -0
  35. {rubka-4.4.8 → rubka-4.4.20}/rubka/decorators.py +0 -0
  36. {rubka-4.4.8 → rubka-4.4.20}/rubka/exceptions.py +0 -0
  37. {rubka-4.4.8 → rubka-4.4.20}/rubka/jobs.py +0 -0
  38. {rubka-4.4.8 → rubka-4.4.20}/rubka/keyboards.py +0 -0
  39. {rubka-4.4.8 → rubka-4.4.20}/rubka/keypad.py +0 -0
  40. {rubka-4.4.8 → rubka-4.4.20}/rubka/logger.py +0 -0
  41. {rubka-4.4.8 → rubka-4.4.20}/rubka/rubino.py +0 -0
  42. {rubka-4.4.8 → rubka-4.4.20}/rubka/utils.py +0 -0
  43. {rubka-4.4.8 → rubka-4.4.20}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Rubka
3
- Version: 4.4.8
3
+ Version: 4.4.20
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/blob/main/project_library.zip
@@ -18,6 +18,7 @@ Description-Content-Type: text/markdown
18
18
  Requires-Dist: requests
19
19
  Requires-Dist: Pillow
20
20
  Requires-Dist: websocket-client
21
+ Requires-Dist: pycryptodome
21
22
  Dynamic: author
22
23
  Dynamic: author-email
23
24
  Dynamic: classifier
@@ -32,6 +33,11 @@ Dynamic: requires-python
32
33
  Dynamic: summary
33
34
 
34
35
  # 📚 Rubka Bot Python Library Documentation
36
+ # نمونه تنظیم وب‌هوک (Webhook) در کتابخونه rubla
37
+
38
+ برای مشاهده مستندات کامل و آخرین نسخه راهنما، لطفاً به آدرس زیر مراجعه کنید:
39
+ [github.com/Mahdy-Ahmadi](https://github.com/Mahdy-Ahmadi/rubka/blob/main/webhook.md))
40
+
35
41
 
36
42
  ## 🧠 Introduction
37
43
  `rubka` is a Python library to interact with the [Rubika Bot API](https://rubika.ir/). This library helps you create Telegram-like bots with support for messages, inline buttons, chat keypads, and callback handling.
@@ -1,4 +1,9 @@
1
1
  # 📚 Rubka Bot Python Library Documentation
2
+ # نمونه تنظیم وب‌هوک (Webhook) در کتابخونه rubla
3
+
4
+ برای مشاهده مستندات کامل و آخرین نسخه راهنما، لطفاً به آدرس زیر مراجعه کنید:
5
+ [github.com/Mahdy-Ahmadi](https://github.com/Mahdy-Ahmadi/rubka/blob/main/webhook.md))
6
+
2
7
 
3
8
  ## 🧠 Introduction
4
9
  `rubka` is a Python library to interact with the [Rubika Bot API](https://rubika.ir/). This library helps you create Telegram-like bots with support for messages, inline buttons, chat keypads, and callback handling.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Rubka
3
- Version: 4.4.8
3
+ Version: 4.4.20
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/blob/main/project_library.zip
@@ -18,6 +18,7 @@ Description-Content-Type: text/markdown
18
18
  Requires-Dist: requests
19
19
  Requires-Dist: Pillow
20
20
  Requires-Dist: websocket-client
21
+ Requires-Dist: pycryptodome
21
22
  Dynamic: author
22
23
  Dynamic: author-email
23
24
  Dynamic: classifier
@@ -32,6 +33,11 @@ Dynamic: requires-python
32
33
  Dynamic: summary
33
34
 
34
35
  # 📚 Rubka Bot Python Library Documentation
36
+ # نمونه تنظیم وب‌هوک (Webhook) در کتابخونه rubla
37
+
38
+ برای مشاهده مستندات کامل و آخرین نسخه راهنما، لطفاً به آدرس زیر مراجعه کنید:
39
+ [github.com/Mahdy-Ahmadi](https://github.com/Mahdy-Ahmadi/rubka/blob/main/webhook.md))
40
+
35
41
 
36
42
  ## 🧠 Introduction
37
43
  `rubka` is a Python library to interact with the [Rubika Bot API](https://rubika.ir/). This library helps you create Telegram-like bots with support for messages, inline buttons, chat keypads, and callback handling.
@@ -1,3 +1,4 @@
1
1
  requests
2
2
  Pillow
3
3
  websocket-client
4
+ pycryptodome
@@ -8,7 +8,8 @@ from .context import Message,InlineMessage
8
8
  from typing import Optional, Union, Literal, Dict, Any
9
9
  from pathlib import Path
10
10
  import requests
11
-
11
+ import time
12
+ import datetime
12
13
  API_URL = "https://botapi.rubika.ir/v3"
13
14
  import sys
14
15
  import subprocess
@@ -77,7 +78,7 @@ class Robot:
77
78
  Initialized with bot token.
78
79
  """
79
80
 
80
- def __init__(self, token: str,session_name : str = None,auth : str = None , Key : str = None,platform : str ="web",timeout : int =10):
81
+ def __init__(self, token: str,session_name : str = None,auth : str = None , Key : str = None,platform : str ="web",web_hook:str=None,timeout : int =10):
81
82
  """
82
83
  راه‌اندازی اولیه ربات روبیکا و پیکربندی پارامترهای پایه.
83
84
 
@@ -98,11 +99,14 @@ class Robot:
98
99
  >>> bot = Robot(token="BOT_TOKEN", platform="android", timeout=15)
99
100
  """
100
101
  self.token = token
102
+ self._inline_query_handlers = []
103
+ self._inline_query_handlers: List[dict] = []
101
104
  self.timeout = timeout
102
105
  self.auth = auth
103
106
  self.session_name = session_name
104
107
  self.Key = Key
105
108
  self.platform = platform
109
+ self.web_hook = web_hook
106
110
  self._offset_id = None
107
111
  self.session = requests.Session()
108
112
  self.sessions: Dict[str, Dict[str, Any]] = {}
@@ -111,6 +115,23 @@ class Robot:
111
115
  self._inline_query_handler = None
112
116
  self._callback_handlers = None
113
117
  self._callback_handlers = [] # ✅ این خط مهمه
118
+ if web_hook:
119
+ try:
120
+ json_url = requests.get(web_hook, timeout=self.timeout).json().get('url', web_hook)
121
+ for endpoint_type in [
122
+ "ReceiveUpdate",
123
+ "ReceiveInlineMessage",
124
+ "ReceiveQuery",
125
+ "GetSelectionItem",
126
+ "SearchSelectionItems"
127
+ ]:
128
+ print(self.update_bot_endpoint(self.web_hook, endpoint_type))
129
+ self.web_hook = json_url
130
+ except Exception as e:
131
+ logger.error(f"Failed to set webhook from {web_hook}: {e}")
132
+ else:
133
+ self.web_hook = None
134
+
114
135
 
115
136
 
116
137
  logger.info(f"Initialized RubikaBot with token: {token[:8]}***")
@@ -156,48 +177,65 @@ class Robot:
156
177
  })
157
178
  return func
158
179
  return decorator
159
- def on_inline_query(self):
180
+ def _handle_inline_query(self, inline_message: InlineMessage):
181
+ aux_button_id = inline_message.aux_data.button_id if inline_message.aux_data else None
182
+
183
+ for handler in self._inline_query_handlers:
184
+ if handler["button_id"] is None or handler["button_id"] == aux_button_id:
185
+ try:
186
+ handler["func"](self, inline_message)
187
+ except Exception as e:
188
+ print(f"Error in inline query handler: {e}")
189
+
190
+ def on_inline_query(self, button_id: Optional[str] = None):
160
191
  def decorator(func: Callable[[Any, InlineMessage], None]):
161
- self._inline_query_handler = func
192
+ self._inline_query_handlers.append({
193
+ "func": func,
194
+ "button_id": button_id
195
+ })
162
196
  return func
163
197
  return decorator
198
+
164
199
 
165
- def _process_update(self, update: Dict[str, Any]):
200
+ def _process_update(self, update: dict):
166
201
  import threading, time
167
202
 
168
- if update.get('type') == 'ReceiveQuery':
203
+ # هندل پیام inline (معمولاً type = ReceiveQuery)
204
+ if update.get("type") == "ReceiveQuery":
169
205
  msg = update.get("inline_message", {})
170
- if self._inline_query_handler:
206
+ if update.get("type") == "ReceiveQuery":
207
+ msg = update.get("inline_message", {})
171
208
  context = InlineMessage(bot=self, raw_data=msg)
172
- threading.Thread(target=self._inline_query_handler, args=(self, context), daemon=True).start()
173
- return # اگر inline بود ادامه نده
209
+ threading.Thread(target=self._handle_inline_query, args=(context,), daemon=True).start()
210
+ return
174
211
 
175
- if update.get('type') == 'NewMessage':
176
- msg = update.get('new_message', {})
177
- chat_id = update.get('chat_id')
178
- message_id = msg.get('message_id')
179
- sender_id = msg.get('sender_id')
180
- text = msg.get('text')
212
+ # هندل پیام جدید متنی
213
+ if update.get("type") == "NewMessage":
214
+ msg = update.get("new_message", {})
215
+ chat_id = update.get("chat_id")
216
+ message_id = msg.get("message_id")
217
+ sender_id = msg.get("sender_id")
218
+ text = msg.get("text")
181
219
 
182
- # فیلتر زمان قدیمی
220
+ # فیلتر زمان پیام (مثلاً پیام‌های خیلی قدیمی)
183
221
  try:
184
222
  if msg.get("time") and (time.time() - float(msg["time"])) > 20:
185
223
  return
186
224
  except Exception:
187
225
  return
188
226
 
189
- # ساخت context یکجا
227
+ # ساخت context پیام
190
228
  context = Message(bot=self, chat_id=chat_id, message_id=message_id, sender_id=sender_id, text=text, raw_data=msg)
191
229
 
192
- # 💠 اول بررسی دکمه‌ها (callback)
230
+ # هندل callback ها (دکمه‌ها)
193
231
  if context.aux_data and hasattr(self, "_callback_handlers"):
194
232
  for handler in self._callback_handlers:
195
233
  if not handler["button_id"] or context.aux_data.button_id == handler["button_id"]:
196
234
  threading.Thread(target=handler["func"], args=(self, context), daemon=True).start()
197
- return # فقط اولین callback اجرا شود
235
+ return # فقط یک callback اجرا شود
198
236
 
199
- # 💠 بعد بررسی پیام‌های متنی معمولی
200
- if self._message_handler:
237
+ # هندل پیام‌های متنی معمولی
238
+ if hasattr(self, "_message_handler") and self._message_handler:
201
239
  handler = self._message_handler
202
240
 
203
241
  if handler["commands"]:
@@ -217,31 +255,129 @@ class Robot:
217
255
 
218
256
 
219
257
 
258
+
259
+ def get_updates(
260
+ self,
261
+ offset_id: Optional[str] = None,
262
+ limit: Optional[int] = None
263
+ ) -> Dict[str, Any]:
264
+ """Get updates."""
265
+ data = {}
266
+ if offset_id:
267
+ data["offset_id"] = offset_id
268
+ if limit:
269
+ data["limit"] = limit
270
+ return self._post("getUpdates", data)
271
+ def update_webhook(
272
+ self,
273
+ offset_id: Optional[str] = None,
274
+ limit: Optional[int] = None
275
+ ) -> Dict[str, Any]:
276
+ data = {}
277
+ if offset_id:
278
+ data["offset_id"] = offset_id
279
+ if limit:
280
+ data["limit"] = limit
281
+ return list(requests.get(self.web_hook).json())
282
+ def _is_duplicate(self, message_id: str, max_age_sec: int = 300) -> bool:
283
+ now = time.time()
284
+
285
+ # حذف پیام‌های قدیمی‌تر از max_age_sec
286
+ expired = [mid for mid, ts in self._processed_message_ids.items() if now - ts > max_age_sec]
287
+ for mid in expired:
288
+ del self._processed_message_ids[mid]
289
+
290
+ # بررسی تکراری بودن پیام
291
+ if message_id in self._processed_message_ids:
292
+ return True
293
+
294
+ self._processed_message_ids[message_id] = now
295
+ return False
296
+
297
+
298
+ import time
299
+
300
+
220
301
  def run(self):
221
302
  print("Bot started running...")
222
- if self._offset_id is None:
223
- try:
224
- latest = self.get_updates(limit=100)
225
- if latest and latest.get("data") and latest["data"].get("updates"):
226
- updates = latest["data"]["updates"]
227
- last_update = updates[-1]
228
- self._offset_id = latest["data"].get("next_offset_id")
229
- print(f"Offset initialized to: {self._offset_id}")
230
- else:
231
- print("No updates found.")
232
- except Exception as e:
233
- print(f"Failed to fetch latest message: {e}")
303
+ self._processed_message_ids: Dict[str, float] = {}
304
+ # self._offset_id می‌تواند قبلاً مقداردهی شده باشد
234
305
 
235
306
  while True:
236
307
  try:
237
- updates = self.get_updates(offset_id=self._offset_id, limit=100)
238
- if updates and updates.get("data"):
239
- for update in updates["data"].get("updates", []):
240
- self._process_update(update)
241
- self._offset_id = updates["data"].get("next_offset_id", self._offset_id)
242
- except Exception as e:
243
- print(f"Error in run loop: {e}")
308
+ if self.web_hook:
309
+ updates = self.update_webhook()
310
+
311
+ if isinstance(updates, list):
312
+ for item in updates:
313
+ data = item.get("data", {})
314
+
315
+ # بررسی زمان دریافت پیام و رد کردن پیام‌های قدیمی (بیش از 20 ثانیه)
316
+ received_at_str = item.get("received_at")
317
+ if received_at_str:
318
+ received_at_ts = datetime.datetime.strptime(received_at_str, "%Y-%m-%d %H:%M:%S").timestamp()
319
+ if time.time() - received_at_ts > 20:
320
+ continue # رد کردن پیام قدیمی
321
+
322
+ update = None
323
+ if "update" in data:
324
+ update = data["update"]
325
+ elif "inline_message" in data:
326
+ update = {
327
+ "type": "ReceiveQuery",
328
+ "inline_message": data["inline_message"]
329
+ }
330
+ else:
331
+ continue
332
+
333
+ message_id = None
334
+ if update.get("type") == "NewMessage":
335
+ message_id = update.get("new_message", {}).get("message_id")
336
+ elif update.get("type") == "ReceiveQuery":
337
+ message_id = update.get("inline_message", {}).get("message_id")
338
+ elif "message_id" in update:
339
+ message_id = update.get("message_id")
340
+
341
+ if message_id is not None:
342
+ message_id = str(message_id)
343
+
344
+ if message_id and self._is_duplicate(message_id):
345
+ continue
346
+
347
+ self._process_update(update)
348
+
349
+ # ثبت پیام به عنوان پردازش شده
350
+ if message_id:
351
+ self._processed_message_ids[message_id] = time.time()
352
+
353
+ else:
354
+ updates = self.get_updates(offset_id=self._offset_id, limit=100)
355
+
356
+ if updates and updates.get("data"):
357
+ for update in updates["data"].get("updates", []):
358
+ message_id = None
359
+ if update.get("type") == "NewMessage":
360
+ message_id = update.get("new_message", {}).get("message_id")
361
+ elif update.get("type") == "ReceiveQuery":
362
+ message_id = update.get("inline_message", {}).get("message_id")
363
+ elif "message_id" in update:
364
+ message_id = update.get("message_id")
365
+
366
+ if message_id is not None:
367
+ message_id = str(message_id)
368
+
369
+ if message_id and self._is_duplicate(message_id):
370
+ continue
371
+
372
+ self._process_update(update)
244
373
 
374
+ if message_id:
375
+ self._processed_message_ids[message_id] = time.time()
376
+
377
+ self._offset_id = updates["data"].get("next_offset_id", self._offset_id)
378
+
379
+ except Exception as e:
380
+ print(f"❌ Error in run loop: {e}")
245
381
  def send_message(
246
382
  self,
247
383
  chat_id: str,
@@ -576,18 +712,7 @@ class Robot:
576
712
  chat_keypad_type=chat_keypad_type
577
713
  )
578
714
 
579
- def get_updates(
580
- self,
581
- offset_id: Optional[str] = None,
582
- limit: Optional[int] = None
583
- ) -> Dict[str, Any]:
584
- """Get updates."""
585
- data = {}
586
- if offset_id:
587
- data["offset_id"] = offset_id
588
- if limit:
589
- data["limit"] = limit
590
- return self._post("getUpdates", data)
715
+
591
716
 
592
717
  def forward_message(
593
718
  self,
@@ -408,6 +408,12 @@ class Message:
408
408
  chat_id=self.chat_id,
409
409
  message_id=self.message_id
410
410
  )
411
+ class AuxData:
412
+ def __init__(self, data: dict):
413
+ self.start_id = data.get("start_id")
414
+ self.button_id = data.get("button_id")
415
+
416
+ # نمونه کلاس InlineMessage با ویژگی aux_data که شامل دکمه است
411
417
  class InlineMessage:
412
418
  def __init__(self, bot, raw_data: dict):
413
419
  self.bot = bot
@@ -438,4 +444,4 @@ class InlineMessage:
438
444
  return self.bot.delete_message(
439
445
  chat_id=self.chat_id,
440
446
  message_id=self.message_id
441
- )
447
+ )
@@ -8,7 +8,7 @@ except FileNotFoundError:
8
8
 
9
9
  setup(
10
10
  name='Rubka',
11
- version='4.4.8',
11
+ version='4.4.20',
12
12
  description='A Python library for interacting with Rubika Bot API.',
13
13
  long_description=long_description,
14
14
  long_description_content_type='text/markdown',
@@ -31,7 +31,8 @@ setup(
31
31
  install_requires=[
32
32
  "requests",
33
33
  "Pillow",
34
- "websocket-client"
34
+ "websocket-client",
35
+ 'pycryptodome'
35
36
  ]
36
37
 
37
38
  )
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