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 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
- def run(self):
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
- received_at_str = item.get("received_at")
316
- if received_at_str:
317
- received_at_ts = datetime.datetime.strptime(received_at_str, "%Y-%m-%d %H:%M:%S").timestamp()
318
- if time.time() - received_at_ts > 20:
319
- continue
320
-
321
- update = None
322
- if "update" in data:
323
- update = data["update"]
324
- elif "inline_message" in data:
325
- update = {
326
- "type": "ReceiveQuery",
327
- "inline_message": data["inline_message"]
328
- }
329
- else:
330
- continue
331
-
332
- message_id = None
333
- if update.get("type") == "NewMessage":
334
- message_id = update.get("new_message", {}).get("message_id")
335
- elif update.get("type") == "ReceiveQuery":
336
- message_id = update.get("inline_message", {}).get("message_id")
337
- elif "message_id" in update:
338
- message_id = update.get("message_id")
339
-
340
- if message_id is not None:
341
- message_id = str(message_id)
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
- if message_id and self._is_duplicate(received_at_str):
344
- continue
702
+ self._processed_message_ids: Dict[str, float] = {}
703
+ error_count = 0
704
+ start_time = time.time()
345
705
 
346
- self._process_update(update)
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
- if message_id:
349
- self._processed_message_ids[message_id] = time.time()
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
- else:
352
- updates = self.get_updates(offset_id=self._offset_id, limit=100)
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
- if updates and updates.get("data"):
355
- for update in updates["data"].get("updates", []):
356
- message_id = None
357
- if update.get("type") == "NewMessage":
358
- message_id = update.get("new_message", {}).get("message_id")
359
- elif update.get("type") == "ReceiveQuery":
360
- message_id = update.get("inline_message", {}).get("message_id")
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
- if message_id is not None:
365
- message_id = str(message_id)
789
+ if message_id is not None:
790
+ message_id = str(message_id)
366
791
 
367
- if message_id and self._is_duplicate(message_id):
368
- continue
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._process_update(update)
802
+ self._offset_id = updates["data"].get("next_offset_id", self._offset_id)
371
803
 
372
- if message_id:
373
- self._processed_message_ids[message_id] = time.time()
804
+ if sleep_time:
805
+ time.sleep(sleep_time)
374
806
 
375
- self._offset_id = updates["data"].get("next_offset_id", self._offset_id)
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']