Rubka 5.0.0__py3-none-any.whl → 6.4.2__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 +622 -65
- rubka/asynco.py +964 -24
- rubka/button.py +32 -1
- rubka/context.py +4 -0
- rubka/update.py +489 -0
- {rubka-5.0.0.dist-info → rubka-6.4.2.dist-info}/METADATA +1 -1
- {rubka-5.0.0.dist-info → rubka-6.4.2.dist-info}/RECORD +9 -8
- {rubka-5.0.0.dist-info → rubka-6.4.2.dist-info}/WHEEL +0 -0
- {rubka-5.0.0.dist-info → rubka-6.4.2.dist-info}/top_level.txt +0 -0
rubka/api.py
CHANGED
|
@@ -17,6 +17,7 @@ import mimetypes
|
|
|
17
17
|
import re
|
|
18
18
|
import sys
|
|
19
19
|
import subprocess
|
|
20
|
+
class InvalidTokenError(Exception):pass
|
|
20
21
|
def install_package(package_name):
|
|
21
22
|
try:
|
|
22
23
|
subprocess.check_call([sys.executable, "-m", "pip", "install", package_name], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
@@ -109,6 +110,7 @@ class Robot:
|
|
|
109
110
|
self.show_progress = show_progress
|
|
110
111
|
self.session_name = session_name
|
|
111
112
|
self.Key = Key
|
|
113
|
+
|
|
112
114
|
self.platform = platform
|
|
113
115
|
self.web_hook = web_hook
|
|
114
116
|
self.hook = web_hook
|
|
@@ -121,6 +123,7 @@ class Robot:
|
|
|
121
123
|
self._message_handlers: List[dict] = []
|
|
122
124
|
self._callback_handlers = None
|
|
123
125
|
self._callback_handlers = []
|
|
126
|
+
self.geteToken()
|
|
124
127
|
if web_hook:
|
|
125
128
|
try:
|
|
126
129
|
json_url = requests.get(web_hook, timeout=self.timeout).json().get('url', web_hook)
|
|
@@ -141,7 +144,6 @@ class Robot:
|
|
|
141
144
|
|
|
142
145
|
|
|
143
146
|
logger.info(f"Initialized RubikaBot with token: {token[:8]}***")
|
|
144
|
-
|
|
145
147
|
def _post(self, method: str, data: Dict[str, Any]) -> Dict[str, Any]:
|
|
146
148
|
url = f"{API_URL}/{self.token}/{method}"
|
|
147
149
|
try:
|
|
@@ -163,6 +165,330 @@ class Robot:
|
|
|
163
165
|
def get_me(self) -> Dict[str, Any]:
|
|
164
166
|
"""Get info about the bot itself."""
|
|
165
167
|
return self._post("getMe", {})
|
|
168
|
+
def geteToken(self):
|
|
169
|
+
"""b"""
|
|
170
|
+
if self.get_me()['status'] != "OK":
|
|
171
|
+
raise InvalidTokenError("The provided bot token is invalid or expired.")
|
|
172
|
+
def on_message_private(
|
|
173
|
+
self,
|
|
174
|
+
chat_id: Optional[Union[str, List[str]]] = None,
|
|
175
|
+
commands: Optional[List[str]] = None,
|
|
176
|
+
filters: Optional[Callable[[Message], bool]] = None,
|
|
177
|
+
sender_id: Optional[Union[str, List[str]]] = None,
|
|
178
|
+
sender_type: Optional[str] = None,
|
|
179
|
+
allow_forwarded: bool = True,
|
|
180
|
+
allow_files: bool = True,
|
|
181
|
+
allow_stickers: bool = True,
|
|
182
|
+
allow_polls: bool = True,
|
|
183
|
+
allow_contacts: bool = True,
|
|
184
|
+
allow_locations: bool = True,
|
|
185
|
+
min_text_length: Optional[int] = None,
|
|
186
|
+
max_text_length: Optional[int] = None,
|
|
187
|
+
contains: Optional[str] = None,
|
|
188
|
+
startswith: Optional[str] = None,
|
|
189
|
+
endswith: Optional[str] = None,
|
|
190
|
+
case_sensitive: bool = False
|
|
191
|
+
):
|
|
192
|
+
"""
|
|
193
|
+
Sync decorator for handling only private messages with extended filters.
|
|
194
|
+
"""
|
|
195
|
+
|
|
196
|
+
def decorator(func: Callable[[Any, Message], None]):
|
|
197
|
+
def wrapper(bot, message: Message):
|
|
198
|
+
|
|
199
|
+
if not message.is_private:
|
|
200
|
+
return
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
if chat_id:
|
|
204
|
+
if isinstance(chat_id, str) and message.chat_id != chat_id:
|
|
205
|
+
return
|
|
206
|
+
if isinstance(chat_id, list) and message.chat_id not in chat_id:
|
|
207
|
+
return
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
if sender_id:
|
|
211
|
+
if isinstance(sender_id, str) and message.sender_id != sender_id:
|
|
212
|
+
return
|
|
213
|
+
if isinstance(sender_id, list) and message.sender_id not in sender_id:
|
|
214
|
+
return
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
if sender_type and message.sender_type != sender_type:
|
|
218
|
+
return
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
if not allow_forwarded and message.forwarded_from:
|
|
222
|
+
return
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
if not allow_files and message.file:
|
|
226
|
+
return
|
|
227
|
+
if not allow_stickers and message.sticker:
|
|
228
|
+
return
|
|
229
|
+
if not allow_polls and message.poll:
|
|
230
|
+
return
|
|
231
|
+
if not allow_contacts and message.contact_message:
|
|
232
|
+
return
|
|
233
|
+
if not allow_locations and (message.location or message.live_location):
|
|
234
|
+
return
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
if message.text:
|
|
238
|
+
text = message.text if case_sensitive else message.text.lower()
|
|
239
|
+
if min_text_length and len(message.text) < min_text_length:
|
|
240
|
+
return
|
|
241
|
+
if max_text_length and len(message.text) > max_text_length:
|
|
242
|
+
return
|
|
243
|
+
if contains and (contains if case_sensitive else contains.lower()) not in text:
|
|
244
|
+
return
|
|
245
|
+
if startswith and not text.startswith(startswith if case_sensitive else startswith.lower()):
|
|
246
|
+
return
|
|
247
|
+
if endswith and not text.endswith(endswith if case_sensitive else endswith.lower()):
|
|
248
|
+
return
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
if commands:
|
|
252
|
+
if not message.text:
|
|
253
|
+
return
|
|
254
|
+
parts = message.text.strip().split()
|
|
255
|
+
cmd = parts[0].lstrip("/")
|
|
256
|
+
if cmd not in commands:
|
|
257
|
+
return
|
|
258
|
+
message.args = parts[1:]
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
if filters and not filters(message):
|
|
262
|
+
return
|
|
263
|
+
|
|
264
|
+
return func(bot, message)
|
|
265
|
+
|
|
266
|
+
self._message_handlers.append({
|
|
267
|
+
"func": wrapper,
|
|
268
|
+
"filters": filters,
|
|
269
|
+
"commands": commands,
|
|
270
|
+
"chat_id": chat_id,
|
|
271
|
+
"private_only": True,
|
|
272
|
+
"sender_id": sender_id,
|
|
273
|
+
"sender_type": sender_type
|
|
274
|
+
})
|
|
275
|
+
return wrapper
|
|
276
|
+
|
|
277
|
+
return decorator
|
|
278
|
+
def on_message_channel(
|
|
279
|
+
self,
|
|
280
|
+
chat_id: Optional[Union[str, List[str]]] = None,
|
|
281
|
+
commands: Optional[List[str]] = None,
|
|
282
|
+
filters: Optional[Callable[[Message], bool]] = None,
|
|
283
|
+
sender_id: Optional[Union[str, List[str]]] = None,
|
|
284
|
+
sender_type: Optional[str] = None,
|
|
285
|
+
allow_forwarded: bool = True,
|
|
286
|
+
allow_files: bool = True,
|
|
287
|
+
allow_stickers: bool = True,
|
|
288
|
+
allow_polls: bool = True,
|
|
289
|
+
allow_contacts: bool = True,
|
|
290
|
+
allow_locations: bool = True,
|
|
291
|
+
min_text_length: Optional[int] = None,
|
|
292
|
+
max_text_length: Optional[int] = None,
|
|
293
|
+
contains: Optional[str] = None,
|
|
294
|
+
startswith: Optional[str] = None,
|
|
295
|
+
endswith: Optional[str] = None,
|
|
296
|
+
case_sensitive: bool = False
|
|
297
|
+
):
|
|
298
|
+
"""
|
|
299
|
+
Sync decorator for handling only private messages with extended filters.
|
|
300
|
+
"""
|
|
301
|
+
|
|
302
|
+
def decorator(func: Callable[[Any, Message], None]):
|
|
303
|
+
def wrapper(bot, message: Message):
|
|
304
|
+
|
|
305
|
+
if not message.is_channel:
|
|
306
|
+
return
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
if chat_id:
|
|
310
|
+
if isinstance(chat_id, str) and message.chat_id != chat_id:
|
|
311
|
+
return
|
|
312
|
+
if isinstance(chat_id, list) and message.chat_id not in chat_id:
|
|
313
|
+
return
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
if sender_id:
|
|
317
|
+
if isinstance(sender_id, str) and message.sender_id != sender_id:
|
|
318
|
+
return
|
|
319
|
+
if isinstance(sender_id, list) and message.sender_id not in sender_id:
|
|
320
|
+
return
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
if sender_type and message.sender_type != sender_type:
|
|
324
|
+
return
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
if not allow_forwarded and message.forwarded_from:
|
|
328
|
+
return
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
if not allow_files and message.file:
|
|
332
|
+
return
|
|
333
|
+
if not allow_stickers and message.sticker:
|
|
334
|
+
return
|
|
335
|
+
if not allow_polls and message.poll:
|
|
336
|
+
return
|
|
337
|
+
if not allow_contacts and message.contact_message:
|
|
338
|
+
return
|
|
339
|
+
if not allow_locations and (message.location or message.live_location):
|
|
340
|
+
return
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
if message.text:
|
|
344
|
+
text = message.text if case_sensitive else message.text.lower()
|
|
345
|
+
if min_text_length and len(message.text) < min_text_length:
|
|
346
|
+
return
|
|
347
|
+
if max_text_length and len(message.text) > max_text_length:
|
|
348
|
+
return
|
|
349
|
+
if contains and (contains if case_sensitive else contains.lower()) not in text:
|
|
350
|
+
return
|
|
351
|
+
if startswith and not text.startswith(startswith if case_sensitive else startswith.lower()):
|
|
352
|
+
return
|
|
353
|
+
if endswith and not text.endswith(endswith if case_sensitive else endswith.lower()):
|
|
354
|
+
return
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
if commands:
|
|
358
|
+
if not message.text:
|
|
359
|
+
return
|
|
360
|
+
parts = message.text.strip().split()
|
|
361
|
+
cmd = parts[0].lstrip("/")
|
|
362
|
+
if cmd not in commands:
|
|
363
|
+
return
|
|
364
|
+
message.args = parts[1:]
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
if filters and not filters(message):
|
|
368
|
+
return
|
|
369
|
+
|
|
370
|
+
return func(bot, message)
|
|
371
|
+
|
|
372
|
+
self._message_handlers.append({
|
|
373
|
+
"func": wrapper,
|
|
374
|
+
"filters": filters,
|
|
375
|
+
"commands": commands,
|
|
376
|
+
"chat_id": chat_id,
|
|
377
|
+
"private_only": True,
|
|
378
|
+
"sender_id": sender_id,
|
|
379
|
+
"sender_type": sender_type
|
|
380
|
+
})
|
|
381
|
+
return wrapper
|
|
382
|
+
|
|
383
|
+
return decorator
|
|
384
|
+
|
|
385
|
+
def on_message_group(
|
|
386
|
+
self,
|
|
387
|
+
chat_id: Optional[Union[str, List[str]]] = None,
|
|
388
|
+
commands: Optional[List[str]] = None,
|
|
389
|
+
filters: Optional[Callable[[Message], bool]] = None,
|
|
390
|
+
sender_id: Optional[Union[str, List[str]]] = None,
|
|
391
|
+
sender_type: Optional[str] = None,
|
|
392
|
+
allow_forwarded: bool = True,
|
|
393
|
+
allow_files: bool = True,
|
|
394
|
+
allow_stickers: bool = True,
|
|
395
|
+
allow_polls: bool = True,
|
|
396
|
+
allow_contacts: bool = True,
|
|
397
|
+
allow_locations: bool = True,
|
|
398
|
+
min_text_length: Optional[int] = None,
|
|
399
|
+
max_text_length: Optional[int] = None,
|
|
400
|
+
contains: Optional[str] = None,
|
|
401
|
+
startswith: Optional[str] = None,
|
|
402
|
+
endswith: Optional[str] = None,
|
|
403
|
+
case_sensitive: bool = False
|
|
404
|
+
):
|
|
405
|
+
"""
|
|
406
|
+
Sync decorator for handling only group messages with extended filters.
|
|
407
|
+
"""
|
|
408
|
+
|
|
409
|
+
def decorator(func: Callable[[Any, Message], None]):
|
|
410
|
+
def wrapper(bot, message: Message):
|
|
411
|
+
|
|
412
|
+
if not message.is_group:
|
|
413
|
+
return
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
if chat_id:
|
|
417
|
+
if isinstance(chat_id, str) and message.chat_id != chat_id:
|
|
418
|
+
return
|
|
419
|
+
if isinstance(chat_id, list) and message.chat_id not in chat_id:
|
|
420
|
+
return
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
if sender_id:
|
|
424
|
+
if isinstance(sender_id, str) and message.sender_id != sender_id:
|
|
425
|
+
return
|
|
426
|
+
if isinstance(sender_id, list) and message.sender_id not in sender_id:
|
|
427
|
+
return
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
if sender_type and message.sender_type != sender_type:
|
|
431
|
+
return
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
if not allow_forwarded and message.forwarded_from:
|
|
435
|
+
return
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
if not allow_files and message.file:
|
|
439
|
+
return
|
|
440
|
+
if not allow_stickers and message.sticker:
|
|
441
|
+
return
|
|
442
|
+
if not allow_polls and message.poll:
|
|
443
|
+
return
|
|
444
|
+
if not allow_contacts and message.contact_message:
|
|
445
|
+
return
|
|
446
|
+
if not allow_locations and (message.location or message.live_location):
|
|
447
|
+
return
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
if message.text:
|
|
451
|
+
text = message.text if case_sensitive else message.text.lower()
|
|
452
|
+
if min_text_length and len(message.text) < min_text_length:
|
|
453
|
+
return
|
|
454
|
+
if max_text_length and len(message.text) > max_text_length:
|
|
455
|
+
return
|
|
456
|
+
if contains and (contains if case_sensitive else contains.lower()) not in text:
|
|
457
|
+
return
|
|
458
|
+
if startswith and not text.startswith(startswith if case_sensitive else startswith.lower()):
|
|
459
|
+
return
|
|
460
|
+
if endswith and not text.endswith(endswith if case_sensitive else endswith.lower()):
|
|
461
|
+
return
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
if commands:
|
|
465
|
+
if not message.text:
|
|
466
|
+
return
|
|
467
|
+
parts = message.text.strip().split()
|
|
468
|
+
cmd = parts[0].lstrip("/")
|
|
469
|
+
if cmd not in commands:
|
|
470
|
+
return
|
|
471
|
+
message.args = parts[1:]
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
if filters and not filters(message):
|
|
475
|
+
return
|
|
476
|
+
|
|
477
|
+
return func(bot, message)
|
|
478
|
+
|
|
479
|
+
self._message_handlers.append({
|
|
480
|
+
"func": wrapper,
|
|
481
|
+
"filters": filters,
|
|
482
|
+
"commands": commands,
|
|
483
|
+
"chat_id": chat_id,
|
|
484
|
+
"group_only": True,
|
|
485
|
+
"sender_id": sender_id,
|
|
486
|
+
"sender_type": sender_type
|
|
487
|
+
})
|
|
488
|
+
return wrapper
|
|
489
|
+
|
|
490
|
+
return decorator
|
|
491
|
+
|
|
166
492
|
def on_message(self, filters: Optional[Callable[[Message], bool]] = None, commands: Optional[List[str]] = None):
|
|
167
493
|
def decorator(func: Callable[[Any, Message], None]):
|
|
168
494
|
self._message_handlers.append({
|
|
@@ -172,6 +498,15 @@ class Robot:
|
|
|
172
498
|
})
|
|
173
499
|
return func
|
|
174
500
|
return decorator
|
|
501
|
+
def message_handler(self, filters: Optional[Callable[[Message], bool]] = None, commands: Optional[List[str]] = None):
|
|
502
|
+
def decorator(func: Callable[[Any, Message], None]):
|
|
503
|
+
self._message_handlers.append({
|
|
504
|
+
"func": func,
|
|
505
|
+
"filters": filters,
|
|
506
|
+
"commands": commands
|
|
507
|
+
})
|
|
508
|
+
return func
|
|
509
|
+
return decorator
|
|
175
510
|
def on_update(self, filters: Optional[Callable[[Message], bool]] = None, commands: Optional[List[str]] = None):
|
|
176
511
|
def decorator(func: Callable[[Any, Message], None]):
|
|
177
512
|
self._message_handlers.append({
|
|
@@ -183,6 +518,26 @@ class Robot:
|
|
|
183
518
|
return decorator
|
|
184
519
|
|
|
185
520
|
def on_callback(self, button_id: Optional[str] = None):
|
|
521
|
+
def decorator(func: Callable[[Any, Message], None]):
|
|
522
|
+
if not hasattr(self, "_callback_handlers"):
|
|
523
|
+
self._callback_handlers = []
|
|
524
|
+
self._callback_handlers.append({
|
|
525
|
+
"func": func,
|
|
526
|
+
"button_id": button_id
|
|
527
|
+
})
|
|
528
|
+
return func
|
|
529
|
+
return decorator
|
|
530
|
+
def callback_query(self, button_id: Optional[str] = None):
|
|
531
|
+
def decorator(func: Callable[[Any, Message], None]):
|
|
532
|
+
if not hasattr(self, "_callback_handlers"):
|
|
533
|
+
self._callback_handlers = []
|
|
534
|
+
self._callback_handlers.append({
|
|
535
|
+
"func": func,
|
|
536
|
+
"button_id": button_id
|
|
537
|
+
})
|
|
538
|
+
return func
|
|
539
|
+
return decorator
|
|
540
|
+
def callback_query_handler(self, button_id: Optional[str] = None):
|
|
186
541
|
def decorator(func: Callable[[Any, Message], None]):
|
|
187
542
|
if not hasattr(self, "_callback_handlers"):
|
|
188
543
|
self._callback_handlers = []
|
|
@@ -218,6 +573,15 @@ class Robot:
|
|
|
218
573
|
if update.get("type") == "ReceiveQuery":
|
|
219
574
|
msg = update.get("inline_message", {})
|
|
220
575
|
context = InlineMessage(bot=self, raw_data=msg)
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
if hasattr(self, "_callback_handlers"):
|
|
579
|
+
for handler in self._callback_handlers:
|
|
580
|
+
cb_id = getattr(context.aux_data, "button_id", None)
|
|
581
|
+
if not handler["button_id"] or handler["button_id"] == cb_id:
|
|
582
|
+
threading.Thread(target=handler["func"], args=(self, context), daemon=True).start()
|
|
583
|
+
|
|
584
|
+
|
|
221
585
|
threading.Thread(target=self._handle_inline_query, args=(context,), daemon=True).start()
|
|
222
586
|
return
|
|
223
587
|
|
|
@@ -299,83 +663,173 @@ class Robot:
|
|
|
299
663
|
import time
|
|
300
664
|
|
|
301
665
|
|
|
302
|
-
|
|
303
|
-
print("Bot started running...")
|
|
304
|
-
self._processed_message_ids: Dict[str, float] = {}
|
|
305
|
-
|
|
306
|
-
while True:
|
|
307
|
-
try:
|
|
308
|
-
if self.web_hook:
|
|
309
|
-
updates = self.update_webhook()
|
|
666
|
+
import datetime
|
|
310
667
|
|
|
311
|
-
if isinstance(updates, list):
|
|
312
|
-
for item in updates:
|
|
313
|
-
data = item.get("data", {})
|
|
314
668
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
669
|
+
def run(
|
|
670
|
+
self,
|
|
671
|
+
debug=False,
|
|
672
|
+
sleep_time=0.1,
|
|
673
|
+
webhook_timeout=20,
|
|
674
|
+
update_limit=100,
|
|
675
|
+
retry_delay=5,
|
|
676
|
+
stop_on_error=False,
|
|
677
|
+
max_errors=None,
|
|
678
|
+
max_runtime=None,
|
|
679
|
+
allowed_update_types=None,
|
|
680
|
+
ignore_duplicate_messages=True,
|
|
681
|
+
skip_inline_queries=False,
|
|
682
|
+
skip_channel_posts=False,
|
|
683
|
+
skip_service_messages=False,
|
|
684
|
+
skip_edited_messages=False,
|
|
685
|
+
skip_bot_messages=False,
|
|
686
|
+
log_file=None,
|
|
687
|
+
print_exceptions=True,
|
|
688
|
+
error_handler=None,
|
|
689
|
+
shutdown_hook=None,
|
|
690
|
+
log_to_console=True,
|
|
691
|
+
custom_update_fetcher=None,
|
|
692
|
+
custom_update_processor=None,
|
|
693
|
+
message_filter=None,
|
|
694
|
+
notify_on_error=False,
|
|
695
|
+
notification_handler=None,
|
|
696
|
+
):
|
|
697
|
+
import time
|
|
698
|
+
from typing import Dict
|
|
699
|
+
if debug:
|
|
700
|
+
print("[DEBUG] Bot started running server...")
|
|
342
701
|
|
|
343
|
-
|
|
344
|
-
|
|
702
|
+
self._processed_message_ids: Dict[str, float] = {}
|
|
703
|
+
error_count = 0
|
|
704
|
+
start_time = time.time()
|
|
345
705
|
|
|
346
|
-
|
|
706
|
+
try:
|
|
707
|
+
while True:
|
|
708
|
+
try:
|
|
709
|
+
|
|
710
|
+
if max_runtime and (time.time() - start_time > max_runtime):
|
|
711
|
+
if debug:
|
|
712
|
+
print("[DEBUG] Max runtime reached, stopping...")
|
|
713
|
+
break
|
|
714
|
+
|
|
715
|
+
|
|
716
|
+
if self.web_hook:
|
|
717
|
+
updates = custom_update_fetcher() if custom_update_fetcher else self.update_webhook()
|
|
718
|
+
if isinstance(updates, list):
|
|
719
|
+
for item in updates:
|
|
720
|
+
data = item.get("data", {})
|
|
721
|
+
received_at_str = item.get("received_at")
|
|
722
|
+
|
|
723
|
+
if received_at_str:
|
|
724
|
+
try:
|
|
725
|
+
received_at_ts = datetime.datetime.strptime(received_at_str, "%Y-%m-%d %H:%M:%S").timestamp()
|
|
726
|
+
if time.time() - received_at_ts > webhook_timeout:
|
|
727
|
+
continue
|
|
728
|
+
except (ValueError, TypeError):
|
|
729
|
+
pass
|
|
730
|
+
|
|
731
|
+
update = data.get("update") or (
|
|
732
|
+
{"type": "ReceiveQuery", "inline_message": data.get("inline_message")}
|
|
733
|
+
if "inline_message" in data else None
|
|
734
|
+
)
|
|
735
|
+
if not update:
|
|
736
|
+
continue
|
|
347
737
|
|
|
348
|
-
|
|
349
|
-
|
|
738
|
+
|
|
739
|
+
if skip_inline_queries and update.get("type") == "ReceiveQuery":
|
|
740
|
+
continue
|
|
741
|
+
if skip_channel_posts and update.get("type") == "ChannelPost":
|
|
742
|
+
continue
|
|
743
|
+
if skip_service_messages and update.get("type") == "ServiceMessage":
|
|
744
|
+
continue
|
|
745
|
+
if skip_edited_messages and update.get("type") == "EditedMessage":
|
|
746
|
+
continue
|
|
747
|
+
if skip_bot_messages and update.get("from", {}).get("is_bot"):
|
|
748
|
+
continue
|
|
749
|
+
if allowed_update_types and update.get("type") not in allowed_update_types:
|
|
750
|
+
continue
|
|
350
751
|
|
|
351
|
-
|
|
352
|
-
|
|
752
|
+
message_id = (
|
|
753
|
+
update.get("new_message", {}).get("message_id")
|
|
754
|
+
if update.get("type") == "NewMessage"
|
|
755
|
+
else update.get("inline_message", {}).get("message_id")
|
|
756
|
+
if update.get("type") == "ReceiveQuery"
|
|
757
|
+
else update.get("message_id")
|
|
758
|
+
)
|
|
759
|
+
|
|
760
|
+
if message_id is not None:
|
|
761
|
+
message_id = str(message_id)
|
|
762
|
+
|
|
763
|
+
if message_id and (not ignore_duplicate_messages or not self._is_duplicate(received_at_str)):
|
|
764
|
+
if message_filter and not message_filter(update):
|
|
765
|
+
continue
|
|
766
|
+
if custom_update_processor:
|
|
767
|
+
custom_update_processor(update)
|
|
768
|
+
else:
|
|
769
|
+
self._process_update(update)
|
|
770
|
+
if message_id:
|
|
771
|
+
self._processed_message_ids[message_id] = time.time()
|
|
772
|
+
|
|
773
|
+
|
|
774
|
+
else:
|
|
775
|
+
updates = custom_update_fetcher() if custom_update_fetcher else self.get_updates(offset_id=self._offset_id, limit=update_limit)
|
|
776
|
+
if updates and updates.get("data"):
|
|
777
|
+
for update in updates["data"].get("updates", []):
|
|
778
|
+
if allowed_update_types and update.get("type") not in allowed_update_types:
|
|
779
|
+
continue
|
|
353
780
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
elif "message_id" in update:
|
|
362
|
-
message_id = update.get("message_id")
|
|
781
|
+
message_id = (
|
|
782
|
+
update.get("new_message", {}).get("message_id")
|
|
783
|
+
if update.get("type") == "NewMessage"
|
|
784
|
+
else update.get("inline_message", {}).get("message_id")
|
|
785
|
+
if update.get("type") == "ReceiveQuery"
|
|
786
|
+
else update.get("message_id")
|
|
787
|
+
)
|
|
363
788
|
|
|
364
|
-
|
|
365
|
-
|
|
789
|
+
if message_id is not None:
|
|
790
|
+
message_id = str(message_id)
|
|
366
791
|
|
|
367
|
-
|
|
368
|
-
|
|
792
|
+
if message_id and (not ignore_duplicate_messages or not self._is_duplicate(message_id)):
|
|
793
|
+
if message_filter and not message_filter(update):
|
|
794
|
+
continue
|
|
795
|
+
if custom_update_processor:
|
|
796
|
+
custom_update_processor(update)
|
|
797
|
+
else:
|
|
798
|
+
self._process_update(update)
|
|
799
|
+
if message_id:
|
|
800
|
+
self._processed_message_ids[message_id] = time.time()
|
|
369
801
|
|
|
370
|
-
self.
|
|
802
|
+
self._offset_id = updates["data"].get("next_offset_id", self._offset_id)
|
|
371
803
|
|
|
372
|
-
|
|
373
|
-
|
|
804
|
+
if sleep_time:
|
|
805
|
+
time.sleep(sleep_time)
|
|
374
806
|
|
|
375
|
-
|
|
807
|
+
except Exception as e:
|
|
808
|
+
error_count += 1
|
|
809
|
+
if log_to_console:
|
|
810
|
+
print(f"Error in run loop: {e}")
|
|
811
|
+
if log_file:
|
|
812
|
+
with open(log_file, "a", encoding="utf-8") as f:
|
|
813
|
+
f.write(f"{datetime.datetime.now()} - ERROR: {e}\n")
|
|
814
|
+
if print_exceptions:
|
|
815
|
+
import traceback
|
|
816
|
+
traceback.print_exc()
|
|
817
|
+
if error_handler:
|
|
818
|
+
error_handler(e)
|
|
819
|
+
if notify_on_error and notification_handler:
|
|
820
|
+
notification_handler(e)
|
|
821
|
+
|
|
822
|
+
if max_errors and error_count >= max_errors and stop_on_error:
|
|
823
|
+
break
|
|
824
|
+
|
|
825
|
+
time.sleep(retry_delay)
|
|
826
|
+
|
|
827
|
+
finally:
|
|
828
|
+
if shutdown_hook:
|
|
829
|
+
shutdown_hook()
|
|
830
|
+
if debug:
|
|
831
|
+
print("Bot stopped and session closed.")
|
|
376
832
|
|
|
377
|
-
except Exception as e:
|
|
378
|
-
print(f"❌ Error in run loop: {e}")
|
|
379
833
|
def send_message(
|
|
380
834
|
self,
|
|
381
835
|
chat_id: str,
|
|
@@ -627,6 +1081,109 @@ class Robot:
|
|
|
627
1081
|
data = response.json()
|
|
628
1082
|
return data.get('data', {}).get('file_id')
|
|
629
1083
|
|
|
1084
|
+
def send_button_join(
|
|
1085
|
+
self,
|
|
1086
|
+
chat_id,
|
|
1087
|
+
title_button : Union[str, list],
|
|
1088
|
+
username : Union[str, list],
|
|
1089
|
+
text,
|
|
1090
|
+
reply_to_message_id=None,
|
|
1091
|
+
id="None"
|
|
1092
|
+
):
|
|
1093
|
+
from .button import InlineBuilder
|
|
1094
|
+
builder = InlineBuilder()
|
|
1095
|
+
|
|
1096
|
+
|
|
1097
|
+
if isinstance(username, (list, tuple)) and isinstance(title_button, (list, tuple)):
|
|
1098
|
+
for t, u in zip(title_button, username):
|
|
1099
|
+
builder = builder.row(
|
|
1100
|
+
InlineBuilder().button_join_channel(
|
|
1101
|
+
text=t,
|
|
1102
|
+
id=id,
|
|
1103
|
+
username=u
|
|
1104
|
+
)
|
|
1105
|
+
)
|
|
1106
|
+
|
|
1107
|
+
|
|
1108
|
+
elif isinstance(username, (list, tuple)) and isinstance(title_button, str):
|
|
1109
|
+
for u in username:
|
|
1110
|
+
builder = builder.row(
|
|
1111
|
+
InlineBuilder().button_join_channel(
|
|
1112
|
+
text=title_button,
|
|
1113
|
+
id=id,
|
|
1114
|
+
username=u
|
|
1115
|
+
)
|
|
1116
|
+
)
|
|
1117
|
+
|
|
1118
|
+
|
|
1119
|
+
else:
|
|
1120
|
+
builder = builder.row(
|
|
1121
|
+
InlineBuilder().button_join_channel(
|
|
1122
|
+
text=title_button,
|
|
1123
|
+
id=id,
|
|
1124
|
+
username=username
|
|
1125
|
+
)
|
|
1126
|
+
)
|
|
1127
|
+
|
|
1128
|
+
return self.send_message(
|
|
1129
|
+
chat_id=chat_id,
|
|
1130
|
+
text=text,
|
|
1131
|
+
inline_keypad=builder.build(),
|
|
1132
|
+
reply_to_message_id=reply_to_message_id
|
|
1133
|
+
)
|
|
1134
|
+
|
|
1135
|
+
|
|
1136
|
+
def send_button_url(
|
|
1137
|
+
self,
|
|
1138
|
+
chat_id,
|
|
1139
|
+
title_button : Union[str, list],
|
|
1140
|
+
url : Union[str, list],
|
|
1141
|
+
text,
|
|
1142
|
+
reply_to_message_id=None,
|
|
1143
|
+
id="None"
|
|
1144
|
+
):
|
|
1145
|
+
from .button import InlineBuilder
|
|
1146
|
+
builder = InlineBuilder()
|
|
1147
|
+
|
|
1148
|
+
|
|
1149
|
+
if isinstance(url, (list, tuple)) and isinstance(title_button, (list, tuple)):
|
|
1150
|
+
for t, u in zip(title_button, url):
|
|
1151
|
+
builder = builder.row(
|
|
1152
|
+
InlineBuilder().button_url_link(
|
|
1153
|
+
text=t,
|
|
1154
|
+
id=id,
|
|
1155
|
+
url=u
|
|
1156
|
+
)
|
|
1157
|
+
)
|
|
1158
|
+
|
|
1159
|
+
|
|
1160
|
+
elif isinstance(url, (list, tuple)) and isinstance(title_button, str):
|
|
1161
|
+
for u in url:
|
|
1162
|
+
builder = builder.row(
|
|
1163
|
+
InlineBuilder().button_url_link(
|
|
1164
|
+
text=title_button,
|
|
1165
|
+
id=id,
|
|
1166
|
+
url=u
|
|
1167
|
+
)
|
|
1168
|
+
)
|
|
1169
|
+
|
|
1170
|
+
|
|
1171
|
+
else:
|
|
1172
|
+
builder = builder.row(
|
|
1173
|
+
InlineBuilder().button_url_link(
|
|
1174
|
+
text=title_button,
|
|
1175
|
+
id=id,
|
|
1176
|
+
url=url
|
|
1177
|
+
)
|
|
1178
|
+
)
|
|
1179
|
+
|
|
1180
|
+
return self.send_message(
|
|
1181
|
+
chat_id=chat_id,
|
|
1182
|
+
text=text,
|
|
1183
|
+
inline_keypad=builder.build(),
|
|
1184
|
+
reply_to_message_id=reply_to_message_id
|
|
1185
|
+
)
|
|
1186
|
+
|
|
630
1187
|
|
|
631
1188
|
def get_upload_url(self, media_type: Literal['File', 'Image', 'Voice', 'Music', 'Gif','Video']) -> str:
|
|
632
1189
|
allowed = ['File', 'Image', 'Voice', 'Music', 'Gif','Video']
|