Rubka 4.4.8__py3-none-any.whl → 4.4.20__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/api.py +177 -52
- rubka/context.py +7 -1
- {rubka-4.4.8.dist-info → rubka-4.4.20.dist-info}/METADATA +7 -1
- {rubka-4.4.8.dist-info → rubka-4.4.20.dist-info}/RECORD +6 -6
- {rubka-4.4.8.dist-info → rubka-4.4.20.dist-info}/WHEEL +0 -0
- {rubka-4.4.8.dist-info → rubka-4.4.20.dist-info}/top_level.txt +0 -0
rubka/api.py
CHANGED
|
@@ -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
|
|
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.
|
|
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:
|
|
200
|
+
def _process_update(self, update: dict):
|
|
166
201
|
import threading, time
|
|
167
202
|
|
|
168
|
-
|
|
203
|
+
# هندل پیام inline (معمولاً type = ReceiveQuery)
|
|
204
|
+
if update.get("type") == "ReceiveQuery":
|
|
169
205
|
msg = update.get("inline_message", {})
|
|
170
|
-
if
|
|
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.
|
|
173
|
-
|
|
209
|
+
threading.Thread(target=self._handle_inline_query, args=(context,), daemon=True).start()
|
|
210
|
+
return
|
|
174
211
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
-
#
|
|
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 # فقط
|
|
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
|
-
|
|
223
|
-
|
|
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
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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
|
-
|
|
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,
|
rubka/context.py
CHANGED
|
@@ -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
|
+
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: Rubka
|
|
3
|
-
Version: 4.4.
|
|
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,8 +1,8 @@
|
|
|
1
1
|
rubka/__init__.py,sha256=TR1DABU5Maz2eO62ZEFiwOqNU0dH6l6HZfqRUxeo4eY,194
|
|
2
|
-
rubka/api.py,sha256=
|
|
2
|
+
rubka/api.py,sha256=NICbYs-4hpnsypGaJeDZ0xoBiuvLV0whSkak9sxM_Bw,32719
|
|
3
3
|
rubka/button.py,sha256=4fMSZR7vUADxSmw1R3_pZ4dw5uMLZX5sOkwPPyNTBDE,8437
|
|
4
4
|
rubka/config.py,sha256=Bck59xkOiqioLv0GkQ1qPGnBXVctz1hKk6LT4h2EPx0,78
|
|
5
|
-
rubka/context.py,sha256=
|
|
5
|
+
rubka/context.py,sha256=j1scXTy_wBY52MmMixfZyIQnB0sdYjRwx17-8ZZmyB4,17017
|
|
6
6
|
rubka/decorators.py,sha256=hGwUoE4q2ImrunJIGJ_kzGYYxQf1ueE0isadqraKEts,1157
|
|
7
7
|
rubka/exceptions.py,sha256=tujZt1XrhWaw-lmdeVadVceUptpw4XzNgE44sAAY0gs,90
|
|
8
8
|
rubka/jobs.py,sha256=GvLMLsVhcSEzRTgkvnPISPEBN71suW2xXI0hUaUZPTo,378
|
|
@@ -32,7 +32,7 @@ rubka/adaptorrubka/types/socket/message.py,sha256=0WgLMZh4eow8Zn7AiSX4C3GZjQTkIg
|
|
|
32
32
|
rubka/adaptorrubka/utils/__init__.py,sha256=OgCFkXdNFh379quNwIVOAWY2NP5cIOxU5gDRRALTk4o,54
|
|
33
33
|
rubka/adaptorrubka/utils/configs.py,sha256=nMUEOJh1NqDJsf9W9PurkN_DLYjO6kKPMm923i4Jj_A,492
|
|
34
34
|
rubka/adaptorrubka/utils/utils.py,sha256=5-LioLNYX_TIbQGDeT50j7Sg9nAWH2LJUUs-iEXpsUY,8816
|
|
35
|
-
rubka-4.4.
|
|
36
|
-
rubka-4.4.
|
|
37
|
-
rubka-4.4.
|
|
38
|
-
rubka-4.4.
|
|
35
|
+
rubka-4.4.20.dist-info/METADATA,sha256=zVVTaP_jxIAlAD1TyHLWyJjv6y138FScv3g104EBPHo,33217
|
|
36
|
+
rubka-4.4.20.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
37
|
+
rubka-4.4.20.dist-info/top_level.txt,sha256=vy2A4lot11cRMdQS-F4HDCIXL3JK8RKfu7HMDkezJW4,6
|
|
38
|
+
rubka-4.4.20.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|