pyrobale 0.2.8.3__py3-none-any.whl → 0.2.9.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.
- {pyrobale-0.2.8.3.dist-info → pyrobale-0.2.9.2.dist-info}/METADATA +1 -1
- pyrobale-0.2.9.2.dist-info/RECORD +5 -0
- pyrobale.py +2454 -2435
- pyrobale-0.2.8.3.dist-info/RECORD +0 -5
- {pyrobale-0.2.8.3.dist-info → pyrobale-0.2.9.2.dist-info}/WHEEL +0 -0
- {pyrobale-0.2.8.3.dist-info → pyrobale-0.2.9.2.dist-info}/licenses/LICENSE +0 -0
pyrobale.py
CHANGED
@@ -1,2435 +1,2454 @@
|
|
1
|
-
"""
|
2
|
-
PyRobale - A Python library for developing
|
3
|
-
|
4
|
-
Features:
|
5
|
-
- Simple and fast
|
6
|
-
-
|
7
|
-
-
|
8
|
-
-
|
9
|
-
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
import
|
15
|
-
import
|
16
|
-
import
|
17
|
-
import
|
18
|
-
import
|
19
|
-
import
|
20
|
-
import
|
21
|
-
import
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
self.
|
49
|
-
|
50
|
-
|
51
|
-
self.
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
self.
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
self.
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
self.cursor
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
if
|
167
|
-
self.
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
self.
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
self.
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
def
|
204
|
-
if not self.conn:
|
205
|
-
self._initialize_db()
|
206
|
-
self.cursor.execute(""
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
self.
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
def
|
228
|
-
if not self.conn:
|
229
|
-
self._initialize_db()
|
230
|
-
self.cursor.execute(
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
'
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
self.
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
def
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
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
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
self.
|
372
|
-
|
373
|
-
def
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
self.
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
self.
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
return
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
'
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
def
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
return
|
647
|
-
|
648
|
-
def
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
self.
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
697
|
-
|
698
|
-
|
699
|
-
|
700
|
-
self.
|
701
|
-
|
702
|
-
def
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
|
710
|
-
|
711
|
-
|
712
|
-
|
713
|
-
|
714
|
-
|
715
|
-
return self.client.send_message(
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
|
720
|
-
|
721
|
-
|
722
|
-
|
723
|
-
|
724
|
-
|
725
|
-
|
726
|
-
|
727
|
-
|
728
|
-
|
729
|
-
|
730
|
-
|
731
|
-
|
732
|
-
|
733
|
-
self.
|
734
|
-
|
735
|
-
|
736
|
-
|
737
|
-
|
738
|
-
|
739
|
-
|
740
|
-
self.
|
741
|
-
|
742
|
-
|
743
|
-
|
744
|
-
|
745
|
-
|
746
|
-
|
747
|
-
|
748
|
-
|
749
|
-
|
750
|
-
|
751
|
-
|
752
|
-
|
753
|
-
|
754
|
-
|
755
|
-
|
756
|
-
|
757
|
-
|
758
|
-
|
759
|
-
|
760
|
-
|
761
|
-
|
762
|
-
|
763
|
-
|
764
|
-
|
765
|
-
|
766
|
-
|
767
|
-
|
768
|
-
|
769
|
-
|
770
|
-
|
771
|
-
|
772
|
-
|
773
|
-
|
774
|
-
|
775
|
-
|
776
|
-
|
777
|
-
|
778
|
-
|
779
|
-
|
780
|
-
|
781
|
-
|
782
|
-
def
|
783
|
-
|
784
|
-
|
785
|
-
|
786
|
-
|
787
|
-
|
788
|
-
|
789
|
-
|
790
|
-
|
791
|
-
|
792
|
-
|
793
|
-
|
794
|
-
|
795
|
-
|
796
|
-
|
797
|
-
|
798
|
-
|
799
|
-
|
800
|
-
|
801
|
-
|
802
|
-
|
803
|
-
|
804
|
-
|
805
|
-
|
806
|
-
|
807
|
-
|
808
|
-
|
809
|
-
|
810
|
-
|
811
|
-
|
812
|
-
|
813
|
-
|
814
|
-
|
815
|
-
|
816
|
-
|
817
|
-
|
818
|
-
|
819
|
-
|
820
|
-
|
821
|
-
|
822
|
-
|
823
|
-
|
824
|
-
|
825
|
-
|
826
|
-
|
827
|
-
|
828
|
-
|
829
|
-
|
830
|
-
|
831
|
-
|
832
|
-
|
833
|
-
|
834
|
-
|
835
|
-
|
836
|
-
|
837
|
-
|
838
|
-
|
839
|
-
|
840
|
-
|
841
|
-
|
842
|
-
|
843
|
-
|
844
|
-
|
845
|
-
|
846
|
-
|
847
|
-
|
848
|
-
|
849
|
-
"""Send a
|
850
|
-
return self.client.
|
851
|
-
|
852
|
-
|
853
|
-
|
854
|
-
|
855
|
-
|
856
|
-
|
857
|
-
|
858
|
-
|
859
|
-
|
860
|
-
|
861
|
-
|
862
|
-
|
863
|
-
|
864
|
-
|
865
|
-
|
866
|
-
|
867
|
-
|
868
|
-
|
869
|
-
|
870
|
-
|
871
|
-
|
872
|
-
|
873
|
-
|
874
|
-
|
875
|
-
|
876
|
-
|
877
|
-
|
878
|
-
|
879
|
-
|
880
|
-
|
881
|
-
|
882
|
-
|
883
|
-
|
884
|
-
|
885
|
-
|
886
|
-
|
887
|
-
|
888
|
-
|
889
|
-
|
890
|
-
|
891
|
-
|
892
|
-
|
893
|
-
|
894
|
-
|
895
|
-
|
896
|
-
|
897
|
-
|
898
|
-
|
899
|
-
|
900
|
-
|
901
|
-
|
902
|
-
|
903
|
-
|
904
|
-
|
905
|
-
|
906
|
-
|
907
|
-
|
908
|
-
|
909
|
-
|
910
|
-
|
911
|
-
|
912
|
-
|
913
|
-
|
914
|
-
|
915
|
-
|
916
|
-
|
917
|
-
|
918
|
-
|
919
|
-
|
920
|
-
|
921
|
-
|
922
|
-
|
923
|
-
|
924
|
-
|
925
|
-
|
926
|
-
|
927
|
-
|
928
|
-
|
929
|
-
|
930
|
-
|
931
|
-
|
932
|
-
|
933
|
-
|
934
|
-
|
935
|
-
|
936
|
-
|
937
|
-
|
938
|
-
|
939
|
-
|
940
|
-
|
941
|
-
|
942
|
-
|
943
|
-
|
944
|
-
|
945
|
-
|
946
|
-
|
947
|
-
|
948
|
-
|
949
|
-
|
950
|
-
|
951
|
-
|
952
|
-
|
953
|
-
|
954
|
-
|
955
|
-
|
956
|
-
|
957
|
-
|
958
|
-
|
959
|
-
|
960
|
-
|
961
|
-
|
962
|
-
|
963
|
-
|
964
|
-
|
965
|
-
|
966
|
-
|
967
|
-
|
968
|
-
|
969
|
-
|
970
|
-
|
971
|
-
|
972
|
-
|
973
|
-
|
974
|
-
|
975
|
-
'
|
976
|
-
|
977
|
-
|
978
|
-
|
979
|
-
|
980
|
-
|
981
|
-
|
982
|
-
|
983
|
-
|
984
|
-
|
985
|
-
|
986
|
-
|
987
|
-
|
988
|
-
'
|
989
|
-
|
990
|
-
|
991
|
-
|
992
|
-
|
993
|
-
|
994
|
-
|
995
|
-
|
996
|
-
|
997
|
-
|
998
|
-
|
999
|
-
|
1000
|
-
|
1001
|
-
|
1002
|
-
|
1003
|
-
|
1004
|
-
|
1005
|
-
|
1006
|
-
|
1007
|
-
|
1008
|
-
|
1009
|
-
|
1010
|
-
|
1011
|
-
|
1012
|
-
|
1013
|
-
|
1014
|
-
|
1015
|
-
|
1016
|
-
|
1017
|
-
|
1018
|
-
|
1019
|
-
|
1020
|
-
|
1021
|
-
|
1022
|
-
|
1023
|
-
|
1024
|
-
|
1025
|
-
|
1026
|
-
|
1027
|
-
|
1028
|
-
|
1029
|
-
|
1030
|
-
|
1031
|
-
|
1032
|
-
|
1033
|
-
|
1034
|
-
|
1035
|
-
|
1036
|
-
|
1037
|
-
|
1038
|
-
|
1039
|
-
|
1040
|
-
|
1041
|
-
|
1042
|
-
|
1043
|
-
|
1044
|
-
|
1045
|
-
|
1046
|
-
|
1047
|
-
|
1048
|
-
|
1049
|
-
|
1050
|
-
|
1051
|
-
|
1052
|
-
|
1053
|
-
|
1054
|
-
|
1055
|
-
|
1056
|
-
|
1057
|
-
|
1058
|
-
|
1059
|
-
|
1060
|
-
|
1061
|
-
|
1062
|
-
|
1063
|
-
|
1064
|
-
|
1065
|
-
|
1066
|
-
|
1067
|
-
|
1068
|
-
|
1069
|
-
|
1070
|
-
|
1071
|
-
|
1072
|
-
|
1073
|
-
|
1074
|
-
|
1075
|
-
|
1076
|
-
|
1077
|
-
|
1078
|
-
|
1079
|
-
|
1080
|
-
|
1081
|
-
|
1082
|
-
|
1083
|
-
|
1084
|
-
|
1085
|
-
|
1086
|
-
|
1087
|
-
|
1088
|
-
|
1089
|
-
|
1090
|
-
|
1091
|
-
|
1092
|
-
|
1093
|
-
|
1094
|
-
|
1095
|
-
|
1096
|
-
|
1097
|
-
|
1098
|
-
|
1099
|
-
|
1100
|
-
|
1101
|
-
|
1102
|
-
|
1103
|
-
|
1104
|
-
|
1105
|
-
|
1106
|
-
|
1107
|
-
|
1108
|
-
|
1109
|
-
|
1110
|
-
|
1111
|
-
|
1112
|
-
|
1113
|
-
|
1114
|
-
|
1115
|
-
|
1116
|
-
|
1117
|
-
|
1118
|
-
return
|
1119
|
-
|
1120
|
-
|
1121
|
-
|
1122
|
-
|
1123
|
-
|
1124
|
-
|
1125
|
-
|
1126
|
-
|
1127
|
-
|
1128
|
-
|
1129
|
-
|
1130
|
-
|
1131
|
-
"""
|
1132
|
-
|
1133
|
-
|
1134
|
-
|
1135
|
-
|
1136
|
-
|
1137
|
-
|
1138
|
-
|
1139
|
-
|
1140
|
-
|
1141
|
-
|
1142
|
-
|
1143
|
-
|
1144
|
-
|
1145
|
-
|
1146
|
-
|
1147
|
-
|
1148
|
-
|
1149
|
-
|
1150
|
-
|
1151
|
-
|
1152
|
-
|
1153
|
-
|
1154
|
-
|
1155
|
-
|
1156
|
-
|
1157
|
-
|
1158
|
-
|
1159
|
-
|
1160
|
-
|
1161
|
-
|
1162
|
-
|
1163
|
-
|
1164
|
-
|
1165
|
-
|
1166
|
-
|
1167
|
-
|
1168
|
-
|
1169
|
-
|
1170
|
-
|
1171
|
-
|
1172
|
-
|
1173
|
-
|
1174
|
-
|
1175
|
-
|
1176
|
-
|
1177
|
-
|
1178
|
-
|
1179
|
-
|
1180
|
-
|
1181
|
-
|
1182
|
-
|
1183
|
-
|
1184
|
-
|
1185
|
-
|
1186
|
-
|
1187
|
-
|
1188
|
-
def
|
1189
|
-
|
1190
|
-
|
1191
|
-
self.
|
1192
|
-
|
1193
|
-
|
1194
|
-
|
1195
|
-
|
1196
|
-
|
1197
|
-
|
1198
|
-
|
1199
|
-
|
1200
|
-
|
1201
|
-
|
1202
|
-
|
1203
|
-
|
1204
|
-
|
1205
|
-
|
1206
|
-
|
1207
|
-
|
1208
|
-
|
1209
|
-
|
1210
|
-
|
1211
|
-
|
1212
|
-
|
1213
|
-
self.
|
1214
|
-
|
1215
|
-
|
1216
|
-
|
1217
|
-
|
1218
|
-
|
1219
|
-
|
1220
|
-
def
|
1221
|
-
|
1222
|
-
|
1223
|
-
|
1224
|
-
|
1225
|
-
|
1226
|
-
|
1227
|
-
|
1228
|
-
|
1229
|
-
|
1230
|
-
|
1231
|
-
|
1232
|
-
|
1233
|
-
|
1234
|
-
|
1235
|
-
|
1236
|
-
|
1237
|
-
|
1238
|
-
|
1239
|
-
|
1240
|
-
|
1241
|
-
|
1242
|
-
|
1243
|
-
|
1244
|
-
|
1245
|
-
|
1246
|
-
|
1247
|
-
|
1248
|
-
|
1249
|
-
|
1250
|
-
|
1251
|
-
|
1252
|
-
|
1253
|
-
|
1254
|
-
|
1255
|
-
|
1256
|
-
|
1257
|
-
|
1258
|
-
|
1259
|
-
|
1260
|
-
|
1261
|
-
|
1262
|
-
|
1263
|
-
|
1264
|
-
|
1265
|
-
|
1266
|
-
|
1267
|
-
|
1268
|
-
|
1269
|
-
|
1270
|
-
|
1271
|
-
|
1272
|
-
|
1273
|
-
self.
|
1274
|
-
|
1275
|
-
|
1276
|
-
|
1277
|
-
|
1278
|
-
|
1279
|
-
|
1280
|
-
|
1281
|
-
|
1282
|
-
|
1283
|
-
|
1284
|
-
|
1285
|
-
|
1286
|
-
|
1287
|
-
|
1288
|
-
|
1289
|
-
|
1290
|
-
|
1291
|
-
|
1292
|
-
|
1293
|
-
self
|
1294
|
-
|
1295
|
-
|
1296
|
-
|
1297
|
-
|
1298
|
-
|
1299
|
-
|
1300
|
-
|
1301
|
-
|
1302
|
-
|
1303
|
-
|
1304
|
-
|
1305
|
-
|
1306
|
-
|
1307
|
-
|
1308
|
-
|
1309
|
-
|
1310
|
-
def
|
1311
|
-
|
1312
|
-
|
1313
|
-
|
1314
|
-
|
1315
|
-
|
1316
|
-
|
1317
|
-
|
1318
|
-
|
1319
|
-
|
1320
|
-
|
1321
|
-
|
1322
|
-
|
1323
|
-
|
1324
|
-
|
1325
|
-
|
1326
|
-
|
1327
|
-
|
1328
|
-
|
1329
|
-
|
1330
|
-
|
1331
|
-
|
1332
|
-
|
1333
|
-
|
1334
|
-
|
1335
|
-
|
1336
|
-
|
1337
|
-
|
1338
|
-
|
1339
|
-
|
1340
|
-
|
1341
|
-
|
1342
|
-
|
1343
|
-
|
1344
|
-
|
1345
|
-
|
1346
|
-
|
1347
|
-
|
1348
|
-
|
1349
|
-
|
1350
|
-
|
1351
|
-
|
1352
|
-
|
1353
|
-
|
1354
|
-
|
1355
|
-
|
1356
|
-
|
1357
|
-
|
1358
|
-
|
1359
|
-
|
1360
|
-
|
1361
|
-
|
1362
|
-
|
1363
|
-
|
1364
|
-
|
1365
|
-
|
1366
|
-
|
1367
|
-
|
1368
|
-
|
1369
|
-
|
1370
|
-
|
1371
|
-
|
1372
|
-
|
1373
|
-
|
1374
|
-
if
|
1375
|
-
|
1376
|
-
|
1377
|
-
|
1378
|
-
|
1379
|
-
|
1380
|
-
|
1381
|
-
|
1382
|
-
|
1383
|
-
|
1384
|
-
|
1385
|
-
|
1386
|
-
|
1387
|
-
|
1388
|
-
|
1389
|
-
|
1390
|
-
|
1391
|
-
|
1392
|
-
|
1393
|
-
|
1394
|
-
|
1395
|
-
|
1396
|
-
|
1397
|
-
|
1398
|
-
|
1399
|
-
|
1400
|
-
self
|
1401
|
-
|
1402
|
-
|
1403
|
-
self.
|
1404
|
-
|
1405
|
-
|
1406
|
-
|
1407
|
-
|
1408
|
-
|
1409
|
-
|
1410
|
-
|
1411
|
-
|
1412
|
-
|
1413
|
-
|
1414
|
-
|
1415
|
-
|
1416
|
-
|
1417
|
-
|
1418
|
-
|
1419
|
-
|
1420
|
-
|
1421
|
-
|
1422
|
-
|
1423
|
-
|
1424
|
-
|
1425
|
-
|
1426
|
-
|
1427
|
-
|
1428
|
-
self.
|
1429
|
-
|
1430
|
-
|
1431
|
-
|
1432
|
-
|
1433
|
-
|
1434
|
-
|
1435
|
-
|
1436
|
-
|
1437
|
-
|
1438
|
-
|
1439
|
-
|
1440
|
-
|
1441
|
-
|
1442
|
-
|
1443
|
-
|
1444
|
-
|
1445
|
-
|
1446
|
-
|
1447
|
-
|
1448
|
-
|
1449
|
-
|
1450
|
-
|
1451
|
-
|
1452
|
-
|
1453
|
-
|
1454
|
-
|
1455
|
-
|
1456
|
-
|
1457
|
-
|
1458
|
-
|
1459
|
-
|
1460
|
-
|
1461
|
-
|
1462
|
-
|
1463
|
-
|
1464
|
-
|
1465
|
-
|
1466
|
-
|
1467
|
-
|
1468
|
-
|
1469
|
-
|
1470
|
-
|
1471
|
-
|
1472
|
-
|
1473
|
-
|
1474
|
-
|
1475
|
-
|
1476
|
-
|
1477
|
-
|
1478
|
-
|
1479
|
-
|
1480
|
-
|
1481
|
-
|
1482
|
-
|
1483
|
-
|
1484
|
-
|
1485
|
-
|
1486
|
-
|
1487
|
-
|
1488
|
-
|
1489
|
-
|
1490
|
-
|
1491
|
-
|
1492
|
-
|
1493
|
-
|
1494
|
-
|
1495
|
-
|
1496
|
-
|
1497
|
-
|
1498
|
-
|
1499
|
-
|
1500
|
-
|
1501
|
-
|
1502
|
-
|
1503
|
-
|
1504
|
-
|
1505
|
-
|
1506
|
-
|
1507
|
-
|
1508
|
-
|
1509
|
-
|
1510
|
-
|
1511
|
-
|
1512
|
-
|
1513
|
-
|
1514
|
-
|
1515
|
-
|
1516
|
-
|
1517
|
-
|
1518
|
-
|
1519
|
-
|
1520
|
-
|
1521
|
-
|
1522
|
-
|
1523
|
-
|
1524
|
-
|
1525
|
-
|
1526
|
-
|
1527
|
-
|
1528
|
-
|
1529
|
-
|
1530
|
-
|
1531
|
-
|
1532
|
-
|
1533
|
-
|
1534
|
-
|
1535
|
-
|
1536
|
-
|
1537
|
-
|
1538
|
-
|
1539
|
-
|
1540
|
-
|
1541
|
-
|
1542
|
-
|
1543
|
-
|
1544
|
-
|
1545
|
-
|
1546
|
-
|
1547
|
-
|
1548
|
-
|
1549
|
-
|
1550
|
-
|
1551
|
-
|
1552
|
-
|
1553
|
-
|
1554
|
-
|
1555
|
-
|
1556
|
-
|
1557
|
-
|
1558
|
-
|
1559
|
-
|
1560
|
-
|
1561
|
-
|
1562
|
-
|
1563
|
-
|
1564
|
-
|
1565
|
-
|
1566
|
-
|
1567
|
-
|
1568
|
-
|
1569
|
-
|
1570
|
-
|
1571
|
-
|
1572
|
-
|
1573
|
-
|
1574
|
-
|
1575
|
-
|
1576
|
-
|
1577
|
-
|
1578
|
-
|
1579
|
-
|
1580
|
-
|
1581
|
-
|
1582
|
-
|
1583
|
-
|
1584
|
-
|
1585
|
-
|
1586
|
-
|
1587
|
-
|
1588
|
-
|
1589
|
-
|
1590
|
-
|
1591
|
-
|
1592
|
-
|
1593
|
-
|
1594
|
-
|
1595
|
-
|
1596
|
-
|
1597
|
-
|
1598
|
-
|
1599
|
-
|
1600
|
-
|
1601
|
-
|
1602
|
-
|
1603
|
-
|
1604
|
-
|
1605
|
-
|
1606
|
-
|
1607
|
-
|
1608
|
-
|
1609
|
-
|
1610
|
-
|
1611
|
-
|
1612
|
-
|
1613
|
-
|
1614
|
-
|
1615
|
-
|
1616
|
-
|
1617
|
-
|
1618
|
-
|
1619
|
-
|
1620
|
-
|
1621
|
-
|
1622
|
-
|
1623
|
-
|
1624
|
-
|
1625
|
-
|
1626
|
-
|
1627
|
-
|
1628
|
-
|
1629
|
-
|
1630
|
-
|
1631
|
-
|
1632
|
-
|
1633
|
-
|
1634
|
-
|
1635
|
-
|
1636
|
-
|
1637
|
-
|
1638
|
-
|
1639
|
-
|
1640
|
-
|
1641
|
-
|
1642
|
-
|
1643
|
-
|
1644
|
-
|
1645
|
-
|
1646
|
-
|
1647
|
-
|
1648
|
-
|
1649
|
-
|
1650
|
-
|
1651
|
-
|
1652
|
-
|
1653
|
-
|
1654
|
-
|
1655
|
-
|
1656
|
-
|
1657
|
-
|
1658
|
-
|
1659
|
-
|
1660
|
-
|
1661
|
-
|
1662
|
-
|
1663
|
-
|
1664
|
-
|
1665
|
-
|
1666
|
-
|
1667
|
-
|
1668
|
-
|
1669
|
-
|
1670
|
-
|
1671
|
-
|
1672
|
-
|
1673
|
-
|
1674
|
-
|
1675
|
-
|
1676
|
-
|
1677
|
-
|
1678
|
-
|
1679
|
-
if
|
1680
|
-
|
1681
|
-
|
1682
|
-
|
1683
|
-
|
1684
|
-
|
1685
|
-
|
1686
|
-
|
1687
|
-
|
1688
|
-
"""Get
|
1689
|
-
|
1690
|
-
|
1691
|
-
|
1692
|
-
|
1693
|
-
|
1694
|
-
|
1695
|
-
|
1696
|
-
|
1697
|
-
|
1698
|
-
|
1699
|
-
|
1700
|
-
|
1701
|
-
|
1702
|
-
|
1703
|
-
|
1704
|
-
|
1705
|
-
|
1706
|
-
|
1707
|
-
|
1708
|
-
|
1709
|
-
|
1710
|
-
|
1711
|
-
|
1712
|
-
|
1713
|
-
|
1714
|
-
|
1715
|
-
|
1716
|
-
|
1717
|
-
|
1718
|
-
|
1719
|
-
|
1720
|
-
|
1721
|
-
|
1722
|
-
|
1723
|
-
|
1724
|
-
|
1725
|
-
|
1726
|
-
|
1727
|
-
|
1728
|
-
|
1729
|
-
|
1730
|
-
|
1731
|
-
|
1732
|
-
|
1733
|
-
|
1734
|
-
|
1735
|
-
|
1736
|
-
|
1737
|
-
def
|
1738
|
-
|
1739
|
-
|
1740
|
-
|
1741
|
-
|
1742
|
-
|
1743
|
-
|
1744
|
-
|
1745
|
-
|
1746
|
-
|
1747
|
-
|
1748
|
-
|
1749
|
-
|
1750
|
-
|
1751
|
-
|
1752
|
-
|
1753
|
-
|
1754
|
-
|
1755
|
-
|
1756
|
-
|
1757
|
-
|
1758
|
-
|
1759
|
-
|
1760
|
-
|
1761
|
-
|
1762
|
-
|
1763
|
-
|
1764
|
-
|
1765
|
-
|
1766
|
-
|
1767
|
-
|
1768
|
-
|
1769
|
-
|
1770
|
-
|
1771
|
-
|
1772
|
-
|
1773
|
-
|
1774
|
-
|
1775
|
-
|
1776
|
-
|
1777
|
-
|
1778
|
-
|
1779
|
-
|
1780
|
-
|
1781
|
-
|
1782
|
-
|
1783
|
-
|
1784
|
-
|
1785
|
-
|
1786
|
-
|
1787
|
-
|
1788
|
-
|
1789
|
-
|
1790
|
-
|
1791
|
-
|
1792
|
-
|
1793
|
-
|
1794
|
-
|
1795
|
-
|
1796
|
-
|
1797
|
-
|
1798
|
-
|
1799
|
-
|
1800
|
-
|
1801
|
-
|
1802
|
-
|
1803
|
-
|
1804
|
-
|
1805
|
-
|
1806
|
-
|
1807
|
-
|
1808
|
-
|
1809
|
-
|
1810
|
-
|
1811
|
-
|
1812
|
-
|
1813
|
-
|
1814
|
-
|
1815
|
-
|
1816
|
-
|
1817
|
-
|
1818
|
-
|
1819
|
-
data = {
|
1820
|
-
|
1821
|
-
|
1822
|
-
|
1823
|
-
|
1824
|
-
|
1825
|
-
|
1826
|
-
|
1827
|
-
|
1828
|
-
|
1829
|
-
|
1830
|
-
|
1831
|
-
|
1832
|
-
|
1833
|
-
|
1834
|
-
|
1835
|
-
|
1836
|
-
'
|
1837
|
-
|
1838
|
-
|
1839
|
-
|
1840
|
-
|
1841
|
-
|
1842
|
-
|
1843
|
-
|
1844
|
-
|
1845
|
-
|
1846
|
-
|
1847
|
-
|
1848
|
-
|
1849
|
-
|
1850
|
-
|
1851
|
-
|
1852
|
-
|
1853
|
-
|
1854
|
-
|
1855
|
-
|
1856
|
-
|
1857
|
-
|
1858
|
-
|
1859
|
-
|
1860
|
-
|
1861
|
-
|
1862
|
-
|
1863
|
-
|
1864
|
-
|
1865
|
-
|
1866
|
-
|
1867
|
-
|
1868
|
-
|
1869
|
-
|
1870
|
-
|
1871
|
-
|
1872
|
-
|
1873
|
-
|
1874
|
-
|
1875
|
-
|
1876
|
-
|
1877
|
-
|
1878
|
-
|
1879
|
-
|
1880
|
-
|
1881
|
-
|
1882
|
-
|
1883
|
-
|
1884
|
-
|
1885
|
-
|
1886
|
-
|
1887
|
-
|
1888
|
-
|
1889
|
-
|
1890
|
-
|
1891
|
-
|
1892
|
-
|
1893
|
-
|
1894
|
-
|
1895
|
-
|
1896
|
-
|
1897
|
-
|
1898
|
-
|
1899
|
-
|
1900
|
-
|
1901
|
-
|
1902
|
-
|
1903
|
-
|
1904
|
-
|
1905
|
-
|
1906
|
-
'
|
1907
|
-
|
1908
|
-
|
1909
|
-
|
1910
|
-
|
1911
|
-
|
1912
|
-
|
1913
|
-
|
1914
|
-
|
1915
|
-
|
1916
|
-
|
1917
|
-
|
1918
|
-
|
1919
|
-
|
1920
|
-
|
1921
|
-
|
1922
|
-
|
1923
|
-
|
1924
|
-
|
1925
|
-
|
1926
|
-
|
1927
|
-
|
1928
|
-
|
1929
|
-
|
1930
|
-
|
1931
|
-
|
1932
|
-
|
1933
|
-
|
1934
|
-
|
1935
|
-
|
1936
|
-
|
1937
|
-
|
1938
|
-
|
1939
|
-
|
1940
|
-
|
1941
|
-
|
1942
|
-
|
1943
|
-
|
1944
|
-
|
1945
|
-
|
1946
|
-
|
1947
|
-
|
1948
|
-
|
1949
|
-
|
1950
|
-
|
1951
|
-
|
1952
|
-
|
1953
|
-
|
1954
|
-
|
1955
|
-
|
1956
|
-
|
1957
|
-
|
1958
|
-
|
1959
|
-
|
1960
|
-
|
1961
|
-
|
1962
|
-
|
1963
|
-
|
1964
|
-
|
1965
|
-
|
1966
|
-
|
1967
|
-
|
1968
|
-
|
1969
|
-
|
1970
|
-
|
1971
|
-
|
1972
|
-
|
1973
|
-
'
|
1974
|
-
|
1975
|
-
|
1976
|
-
|
1977
|
-
|
1978
|
-
|
1979
|
-
|
1980
|
-
|
1981
|
-
|
1982
|
-
|
1983
|
-
|
1984
|
-
|
1985
|
-
|
1986
|
-
|
1987
|
-
|
1988
|
-
|
1989
|
-
|
1990
|
-
|
1991
|
-
|
1992
|
-
|
1993
|
-
|
1994
|
-
|
1995
|
-
|
1996
|
-
|
1997
|
-
|
1998
|
-
|
1999
|
-
|
2000
|
-
|
2001
|
-
|
2002
|
-
|
2003
|
-
|
2004
|
-
|
2005
|
-
|
2006
|
-
|
2007
|
-
|
2008
|
-
|
2009
|
-
|
2010
|
-
|
2011
|
-
|
2012
|
-
|
2013
|
-
|
2014
|
-
|
2015
|
-
|
2016
|
-
|
2017
|
-
|
2018
|
-
|
2019
|
-
|
2020
|
-
|
2021
|
-
|
2022
|
-
|
2023
|
-
|
2024
|
-
|
2025
|
-
|
2026
|
-
|
2027
|
-
|
2028
|
-
|
2029
|
-
|
2030
|
-
|
2031
|
-
|
2032
|
-
|
2033
|
-
|
2034
|
-
|
2035
|
-
|
2036
|
-
|
2037
|
-
|
2038
|
-
|
2039
|
-
|
2040
|
-
'
|
2041
|
-
|
2042
|
-
|
2043
|
-
|
2044
|
-
|
2045
|
-
|
2046
|
-
|
2047
|
-
|
2048
|
-
|
2049
|
-
|
2050
|
-
|
2051
|
-
|
2052
|
-
|
2053
|
-
|
2054
|
-
|
2055
|
-
|
2056
|
-
|
2057
|
-
|
2058
|
-
|
2059
|
-
|
2060
|
-
|
2061
|
-
|
2062
|
-
|
2063
|
-
|
2064
|
-
|
2065
|
-
|
2066
|
-
|
2067
|
-
|
2068
|
-
|
2069
|
-
|
2070
|
-
|
2071
|
-
|
2072
|
-
'
|
2073
|
-
'
|
2074
|
-
'
|
2075
|
-
'
|
2076
|
-
|
2077
|
-
reply_to_message,
|
2078
|
-
|
2079
|
-
response = self._make_request('POST', '
|
2080
|
-
return Message(self, response)
|
2081
|
-
|
2082
|
-
def
|
2083
|
-
|
2084
|
-
|
2085
|
-
|
2086
|
-
|
2087
|
-
|
2088
|
-
|
2089
|
-
|
2090
|
-
|
2091
|
-
|
2092
|
-
|
2093
|
-
|
2094
|
-
|
2095
|
-
|
2096
|
-
|
2097
|
-
|
2098
|
-
'
|
2099
|
-
'
|
2100
|
-
|
2101
|
-
|
2102
|
-
|
2103
|
-
|
2104
|
-
|
2105
|
-
|
2106
|
-
|
2107
|
-
|
2108
|
-
|
2109
|
-
|
2110
|
-
|
2111
|
-
|
2112
|
-
|
2113
|
-
|
2114
|
-
|
2115
|
-
|
2116
|
-
|
2117
|
-
|
2118
|
-
|
2119
|
-
|
2120
|
-
|
2121
|
-
|
2122
|
-
|
2123
|
-
|
2124
|
-
|
2125
|
-
|
2126
|
-
|
2127
|
-
|
2128
|
-
|
2129
|
-
'
|
2130
|
-
'
|
2131
|
-
|
2132
|
-
|
2133
|
-
|
2134
|
-
|
2135
|
-
|
2136
|
-
|
2137
|
-
|
2138
|
-
|
2139
|
-
|
2140
|
-
|
2141
|
-
|
2142
|
-
|
2143
|
-
|
2144
|
-
|
2145
|
-
|
2146
|
-
|
2147
|
-
|
2148
|
-
|
2149
|
-
|
2150
|
-
|
2151
|
-
|
2152
|
-
|
2153
|
-
|
2154
|
-
|
2155
|
-
|
2156
|
-
|
2157
|
-
|
2158
|
-
|
2159
|
-
|
2160
|
-
|
2161
|
-
|
2162
|
-
|
2163
|
-
|
2164
|
-
|
2165
|
-
|
2166
|
-
|
2167
|
-
|
2168
|
-
|
2169
|
-
|
2170
|
-
|
2171
|
-
|
2172
|
-
|
2173
|
-
|
2174
|
-
|
2175
|
-
|
2176
|
-
|
2177
|
-
|
2178
|
-
|
2179
|
-
|
2180
|
-
|
2181
|
-
|
2182
|
-
|
2183
|
-
|
2184
|
-
|
2185
|
-
|
2186
|
-
|
2187
|
-
|
2188
|
-
|
2189
|
-
|
2190
|
-
|
2191
|
-
|
2192
|
-
|
2193
|
-
|
2194
|
-
|
2195
|
-
|
2196
|
-
|
2197
|
-
|
2198
|
-
|
2199
|
-
|
2200
|
-
|
2201
|
-
|
2202
|
-
|
2203
|
-
|
2204
|
-
|
2205
|
-
return
|
2206
|
-
|
2207
|
-
def
|
2208
|
-
|
2209
|
-
|
2210
|
-
|
2211
|
-
|
2212
|
-
|
2213
|
-
|
2214
|
-
|
2215
|
-
|
2216
|
-
|
2217
|
-
|
2218
|
-
|
2219
|
-
|
2220
|
-
|
2221
|
-
|
2222
|
-
|
2223
|
-
|
2224
|
-
|
2225
|
-
|
2226
|
-
|
2227
|
-
|
2228
|
-
|
2229
|
-
|
2230
|
-
|
2231
|
-
|
2232
|
-
|
2233
|
-
|
2234
|
-
|
2235
|
-
|
2236
|
-
|
2237
|
-
|
2238
|
-
|
2239
|
-
|
2240
|
-
|
2241
|
-
|
2242
|
-
|
2243
|
-
|
2244
|
-
|
2245
|
-
|
2246
|
-
|
2247
|
-
|
2248
|
-
|
2249
|
-
|
2250
|
-
|
2251
|
-
|
2252
|
-
|
2253
|
-
|
2254
|
-
|
2255
|
-
|
2256
|
-
|
2257
|
-
|
2258
|
-
|
2259
|
-
|
2260
|
-
|
2261
|
-
|
2262
|
-
|
2263
|
-
|
2264
|
-
|
2265
|
-
|
2266
|
-
|
2267
|
-
|
2268
|
-
|
2269
|
-
|
2270
|
-
|
2271
|
-
|
2272
|
-
|
2273
|
-
|
2274
|
-
|
2275
|
-
|
2276
|
-
|
2277
|
-
|
2278
|
-
|
2279
|
-
|
2280
|
-
|
2281
|
-
|
2282
|
-
|
2283
|
-
|
2284
|
-
|
2285
|
-
|
2286
|
-
|
2287
|
-
|
2288
|
-
|
2289
|
-
|
2290
|
-
|
2291
|
-
|
2292
|
-
|
2293
|
-
|
2294
|
-
|
2295
|
-
|
2296
|
-
|
2297
|
-
|
2298
|
-
|
2299
|
-
|
2300
|
-
|
2301
|
-
|
2302
|
-
|
2303
|
-
|
2304
|
-
|
2305
|
-
|
2306
|
-
|
2307
|
-
|
2308
|
-
|
2309
|
-
|
2310
|
-
|
2311
|
-
|
2312
|
-
|
2313
|
-
|
2314
|
-
self
|
2315
|
-
|
2316
|
-
|
2317
|
-
|
2318
|
-
|
2319
|
-
|
2320
|
-
|
2321
|
-
|
2322
|
-
|
2323
|
-
|
2324
|
-
|
2325
|
-
|
2326
|
-
|
2327
|
-
|
2328
|
-
|
2329
|
-
|
2330
|
-
|
2331
|
-
|
2332
|
-
|
2333
|
-
|
2334
|
-
|
2335
|
-
|
2336
|
-
|
2337
|
-
|
2338
|
-
|
2339
|
-
|
2340
|
-
|
2341
|
-
|
2342
|
-
|
2343
|
-
|
2344
|
-
|
2345
|
-
|
2346
|
-
|
2347
|
-
|
2348
|
-
|
2349
|
-
|
2350
|
-
|
2351
|
-
|
2352
|
-
|
2353
|
-
|
2354
|
-
|
2355
|
-
|
2356
|
-
|
2357
|
-
|
2358
|
-
|
2359
|
-
|
2360
|
-
|
2361
|
-
|
2362
|
-
|
2363
|
-
|
2364
|
-
|
2365
|
-
|
2366
|
-
|
2367
|
-
|
2368
|
-
|
2369
|
-
|
2370
|
-
|
2371
|
-
|
2372
|
-
|
2373
|
-
|
2374
|
-
|
2375
|
-
|
2376
|
-
|
2377
|
-
|
2378
|
-
|
2379
|
-
|
2380
|
-
|
2381
|
-
|
2382
|
-
|
2383
|
-
|
2384
|
-
|
2385
|
-
|
2386
|
-
|
2387
|
-
|
2388
|
-
|
2389
|
-
|
2390
|
-
|
2391
|
-
|
2392
|
-
|
2393
|
-
|
2394
|
-
|
2395
|
-
|
2396
|
-
|
2397
|
-
|
2398
|
-
|
2399
|
-
|
2400
|
-
|
2401
|
-
|
2402
|
-
|
2403
|
-
|
2404
|
-
|
2405
|
-
|
2406
|
-
|
2407
|
-
|
2408
|
-
|
2409
|
-
|
2410
|
-
|
2411
|
-
|
2412
|
-
|
2413
|
-
|
2414
|
-
|
2415
|
-
|
2416
|
-
|
2417
|
-
|
2418
|
-
|
2419
|
-
|
2420
|
-
|
2421
|
-
|
2422
|
-
|
2423
|
-
|
2424
|
-
|
2425
|
-
|
2426
|
-
|
2427
|
-
|
2428
|
-
|
2429
|
-
|
2430
|
-
|
2431
|
-
|
2432
|
-
|
2433
|
-
|
2434
|
-
|
2435
|
-
|
1
|
+
"""
|
2
|
+
PyRobale - A Python library for developing Bale messenger bots.
|
3
|
+
|
4
|
+
Features:
|
5
|
+
- Simple and fast implementation
|
6
|
+
- Highly customizable and feature-rich
|
7
|
+
- Modern and regularly updated
|
8
|
+
- Built-in database management
|
9
|
+
- User-friendly API
|
10
|
+
- Gentle learning curve
|
11
|
+
- Thread-safe operations
|
12
|
+
- Robust error handling
|
13
|
+
"""
|
14
|
+
from typing import Optional, Dict, Any, List, Union
|
15
|
+
import requests
|
16
|
+
from typing import Union, Optional, Dict, Any, List, Tuple, Callable
|
17
|
+
import threading
|
18
|
+
import traceback
|
19
|
+
import sqlite3
|
20
|
+
import time
|
21
|
+
import sys
|
22
|
+
import collections
|
23
|
+
import inspect
|
24
|
+
import os
|
25
|
+
from typing import Union, Optional, Dict, Any, List, Tuple, Callable
|
26
|
+
import sqlite3
|
27
|
+
import json
|
28
|
+
import traceback
|
29
|
+
|
30
|
+
__version__ = '0.2.9.2'
|
31
|
+
|
32
|
+
class BaleException(Exception):
|
33
|
+
"""Base exception for Bale API errors"""
|
34
|
+
|
35
|
+
def __init__(self, message=None, error_code=None, response=None):
|
36
|
+
self.message = message
|
37
|
+
self.error_code = error_code
|
38
|
+
self.response = response
|
39
|
+
|
40
|
+
error_text = f"Error {error_code}: {message}" if error_code and message else message or str(
|
41
|
+
error_code)
|
42
|
+
super().__init__(error_text)
|
43
|
+
|
44
|
+
def __str__(self):
|
45
|
+
error_details = []
|
46
|
+
if self.error_code:
|
47
|
+
error_details.append(f"code={self.error_code}")
|
48
|
+
if self.message:
|
49
|
+
error_details.append(f"message='{self.message}'")
|
50
|
+
details = ", ".join(error_details)
|
51
|
+
return f"{self.__class__.__name__}({details})"
|
52
|
+
|
53
|
+
|
54
|
+
class BaleAPIError(BaleException):
|
55
|
+
"""Exception raised when Bale API returns an error response"""
|
56
|
+
pass
|
57
|
+
class BaleNetworkError(BaleException):
|
58
|
+
"""Exception raised when network-related issues occur during API calls"""
|
59
|
+
pass
|
60
|
+
|
61
|
+
|
62
|
+
class BaleAuthError(BaleException):
|
63
|
+
"""Exception raised when authentication fails or token is invalid"""
|
64
|
+
pass
|
65
|
+
|
66
|
+
|
67
|
+
class BaleValidationError(BaleException):
|
68
|
+
"""Exception raised when request data fails validation"""
|
69
|
+
pass
|
70
|
+
|
71
|
+
|
72
|
+
class BaleTimeoutError(BaleException):
|
73
|
+
"""Exception raised when API request times out"""
|
74
|
+
pass
|
75
|
+
|
76
|
+
|
77
|
+
class BaleNotFoundError(BaleException):
|
78
|
+
"""Exception raised when requested resource is not found (404)"""
|
79
|
+
pass
|
80
|
+
|
81
|
+
|
82
|
+
class BaleForbiddenError(BaleException):
|
83
|
+
"""Exception raised when access to resource is forbidden (403)"""
|
84
|
+
pass
|
85
|
+
|
86
|
+
|
87
|
+
class BaleServerError(BaleException):
|
88
|
+
"""Exception raised when server encounters an error (5xx)"""
|
89
|
+
pass
|
90
|
+
|
91
|
+
|
92
|
+
class BaleRateLimitError(BaleException):
|
93
|
+
"""Exception raised when API rate limit is exceeded (429)"""
|
94
|
+
pass
|
95
|
+
|
96
|
+
|
97
|
+
class BaleTokenNotFoundError(BaleException):
|
98
|
+
"""Exception raised when required API token is missing"""
|
99
|
+
pass
|
100
|
+
|
101
|
+
|
102
|
+
class BaleUnknownError(BaleException):
|
103
|
+
"""Exception raised for unexpected or unknown errors"""
|
104
|
+
pass
|
105
|
+
|
106
|
+
class ChatActions:
|
107
|
+
"""Represents different chat action states that can be sent to Bale"""
|
108
|
+
TYPING: str = 'typing'
|
109
|
+
PHOTO: str = 'upload_photo'
|
110
|
+
VIDEO: str = 'record_video'
|
111
|
+
CHOOSE_STICKER: str = 'choose_sticker'
|
112
|
+
|
113
|
+
class DataBase:
|
114
|
+
|
115
|
+
"""
|
116
|
+
Database class for managing key-value pairs in a SQLite database.
|
117
|
+
"""
|
118
|
+
|
119
|
+
def __init__(self, name):
|
120
|
+
self.name = name
|
121
|
+
self.conn = None
|
122
|
+
self.cursor = None
|
123
|
+
self._initialize_db()
|
124
|
+
|
125
|
+
def _initialize_db(self):
|
126
|
+
self.conn = sqlite3.connect(self.name)
|
127
|
+
self.cursor = self.conn.cursor()
|
128
|
+
self.cursor.execute('''CREATE TABLE IF NOT EXISTS key_value_store
|
129
|
+
(key TEXT PRIMARY KEY, value TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
130
|
+
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)''')
|
131
|
+
self.conn.commit()
|
132
|
+
|
133
|
+
def __enter__(self):
|
134
|
+
return self
|
135
|
+
|
136
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
137
|
+
self.close()
|
138
|
+
|
139
|
+
def close(self):
|
140
|
+
if self.conn:
|
141
|
+
self.conn.close()
|
142
|
+
self.conn = None
|
143
|
+
self.cursor = None
|
144
|
+
|
145
|
+
def read_database(self, include_timestamps=False):
|
146
|
+
if not self.conn:
|
147
|
+
self._initialize_db()
|
148
|
+
if include_timestamps:
|
149
|
+
self.cursor.execute(
|
150
|
+
"SELECT key, value, created_at, updated_at FROM key_value_store")
|
151
|
+
rows = self.cursor.fetchall()
|
152
|
+
return {
|
153
|
+
key: {
|
154
|
+
'value': json.loads(value),
|
155
|
+
'created_at': created,
|
156
|
+
'updated_at': updated} for key,
|
157
|
+
value,
|
158
|
+
created,
|
159
|
+
updated in rows}
|
160
|
+
else:
|
161
|
+
self.cursor.execute("SELECT key, value FROM key_value_store")
|
162
|
+
rows = self.cursor.fetchall()
|
163
|
+
return {key: json.loads(value) for key, value in rows}
|
164
|
+
|
165
|
+
def write_database(self, data_dict):
|
166
|
+
if not self.conn:
|
167
|
+
self._initialize_db()
|
168
|
+
for key, value in data_dict.items():
|
169
|
+
self.cursor.execute("""
|
170
|
+
INSERT INTO key_value_store (key, value, updated_at)
|
171
|
+
VALUES (?, ?, CURRENT_TIMESTAMP)
|
172
|
+
ON CONFLICT(key) DO UPDATE SET
|
173
|
+
value=excluded.value, updated_at=CURRENT_TIMESTAMP""",
|
174
|
+
(key, json.dumps(value, default=str)))
|
175
|
+
self.conn.commit()
|
176
|
+
|
177
|
+
def read_key(self, key: str, default=None):
|
178
|
+
if not self.conn:
|
179
|
+
self._initialize_db()
|
180
|
+
self.cursor.execute(
|
181
|
+
"SELECT value FROM key_value_store WHERE key = ?", (key,))
|
182
|
+
result = self.cursor.fetchone()
|
183
|
+
return json.loads(result[0]) if result else default
|
184
|
+
|
185
|
+
def write_key(self, key: str, value):
|
186
|
+
if not self.conn:
|
187
|
+
self._initialize_db()
|
188
|
+
self.cursor.execute("""
|
189
|
+
INSERT INTO key_value_store (key, value, updated_at)
|
190
|
+
VALUES (?, ?, CURRENT_TIMESTAMP)
|
191
|
+
ON CONFLICT(key) DO UPDATE SET
|
192
|
+
value=excluded.value, updated_at=CURRENT_TIMESTAMP""",
|
193
|
+
(key, json.dumps(value, default=str)))
|
194
|
+
self.conn.commit()
|
195
|
+
|
196
|
+
def delete_key(self, key: str):
|
197
|
+
if not self.conn:
|
198
|
+
self._initialize_db()
|
199
|
+
self.cursor.execute(
|
200
|
+
"DELETE FROM key_value_store WHERE key = ?", (key,))
|
201
|
+
self.conn.commit()
|
202
|
+
|
203
|
+
def keys(self):
|
204
|
+
if not self.conn:
|
205
|
+
self._initialize_db()
|
206
|
+
self.cursor.execute("SELECT key FROM key_value_store")
|
207
|
+
return [row[0] for row in self.cursor.fetchall()]
|
208
|
+
|
209
|
+
def clear(self):
|
210
|
+
if not self.conn:
|
211
|
+
self._initialize_db()
|
212
|
+
self.cursor.execute("DELETE FROM key_value_store")
|
213
|
+
self.conn.commit()
|
214
|
+
|
215
|
+
def get_metadata(self, key: str):
|
216
|
+
if not self.conn:
|
217
|
+
self._initialize_db()
|
218
|
+
self.cursor.execute("""
|
219
|
+
SELECT created_at, updated_at
|
220
|
+
FROM key_value_store
|
221
|
+
WHERE key = ?""", (key,))
|
222
|
+
result = self.cursor.fetchone()
|
223
|
+
return {
|
224
|
+
'created_at': result[0],
|
225
|
+
'updated_at': result[1]} if result else None
|
226
|
+
|
227
|
+
def exists(self, key: str) -> bool:
|
228
|
+
if not self.conn:
|
229
|
+
self._initialize_db()
|
230
|
+
self.cursor.execute(
|
231
|
+
"SELECT 1 FROM key_value_store WHERE key = ?", (key,))
|
232
|
+
return bool(self.cursor.fetchone())
|
233
|
+
|
234
|
+
|
235
|
+
class ChatMember:
|
236
|
+
def __init__(self, client: 'Client', data: Dict[str, Any]):
|
237
|
+
if data:
|
238
|
+
self.status = data.get('status')
|
239
|
+
self.user = User(
|
240
|
+
client, {
|
241
|
+
'ok': True, 'result': data.get(
|
242
|
+
'user', {})})
|
243
|
+
self.is_anonymous = data.get('is_anonymous')
|
244
|
+
self.can_be_edited = data.get('can_be_edited')
|
245
|
+
self.can_manage_chat = data.get('can_manage_chat')
|
246
|
+
self.can_delete_messages = data.get('can_delete_messages')
|
247
|
+
self.can_manage_video_chats = data.get('can_manage_video_chats')
|
248
|
+
self.can_restrict_members = data.get('can_restrict_members')
|
249
|
+
self.can_promote_members = data.get('can_promote_members')
|
250
|
+
self.can_change_info = data.get('can_change_info')
|
251
|
+
self.can_invite_users = data.get('can_invite_users')
|
252
|
+
self.can_pin_messages = data.get('can_pin_messages')
|
253
|
+
self.can_manage_topics = data.get('can_manage_topics')
|
254
|
+
self.is_creator = data.get('status') == 'creator'
|
255
|
+
|
256
|
+
|
257
|
+
class Chat:
|
258
|
+
"""Represents a chat conversation"""
|
259
|
+
|
260
|
+
def __init__(self, client: 'Client', data: Dict[str, Any]):
|
261
|
+
if not data.get('ok'):
|
262
|
+
raise BaleException(
|
263
|
+
f"API request failed: {traceback.format_exc()}")
|
264
|
+
self.client = client
|
265
|
+
result = data.get('result', {})
|
266
|
+
self.data = data
|
267
|
+
self.id = result.get('id')
|
268
|
+
self.type = result.get('type')
|
269
|
+
self.title = result.get('title')
|
270
|
+
self.username = result.get('username')
|
271
|
+
self.description = result.get('description')
|
272
|
+
self.invite_link = result.get('invite_link')
|
273
|
+
self.photo = result.get('photo')
|
274
|
+
self.is_channel_chat = self.CHANNEL = self.type == "channel"
|
275
|
+
self.is_group_chat = self.GROUP = self.type == "group"
|
276
|
+
self.is_private_chat = self.PRIVATE = self.type == "private"
|
277
|
+
|
278
|
+
def send_photo(self,
|
279
|
+
photo: Union[str,
|
280
|
+
bytes,
|
281
|
+
'InputFile'],
|
282
|
+
caption: Optional[str] = None,
|
283
|
+
parse_mode: Optional[str] = None,
|
284
|
+
reply_markup: Union['MenuKeyboardMarkup',
|
285
|
+
'InlineKeyboardMarkup'] = None) -> 'Message':
|
286
|
+
"""Send a photo to a chat"""
|
287
|
+
files = None
|
288
|
+
data = {
|
289
|
+
'chat_id': self.id,
|
290
|
+
'caption': caption,
|
291
|
+
'parse_mode': parse_mode,
|
292
|
+
'reply_markup': reply_markup.keyboard if isinstance(
|
293
|
+
reply_markup,
|
294
|
+
MenuKeyboardMarkup) else reply_markup.keyboard if isinstance(
|
295
|
+
reply_markup,
|
296
|
+
InlineKeyboardMarkup) else None}
|
297
|
+
|
298
|
+
if isinstance(photo, (bytes, InputFile)) or hasattr(photo, 'read'):
|
299
|
+
files = {
|
300
|
+
'photo': photo if not isinstance(
|
301
|
+
photo, InputFile) else photo.file}
|
302
|
+
else:
|
303
|
+
data['photo'] = photo
|
304
|
+
|
305
|
+
response = self.client._make_request(
|
306
|
+
'POST', 'sendPhoto', data=data, files=files)
|
307
|
+
return Message(self.client, response)
|
308
|
+
|
309
|
+
def send_message(self,
|
310
|
+
text: str,
|
311
|
+
parse_mode: Optional[str] = None,
|
312
|
+
reply_markup: Union['MenuKeyboardMarkup',
|
313
|
+
'InlineKeyboardMarkup'] = None) -> 'Message':
|
314
|
+
return self.client.send_message(
|
315
|
+
self.id, text, parse_mode, reply_markup)
|
316
|
+
|
317
|
+
def forward_message(
|
318
|
+
self, from_chat_id: Union[int, str], message_id: int) -> 'Message':
|
319
|
+
"""Forward a message from another chat"""
|
320
|
+
return self.client.forward_message(self.id, from_chat_id, message_id)
|
321
|
+
|
322
|
+
def copy_message(self,
|
323
|
+
from_chat_id: Union[int,
|
324
|
+
str],
|
325
|
+
message_id: int,
|
326
|
+
caption: Optional[str] = None,
|
327
|
+
parse_mode: Optional[str] = None,
|
328
|
+
reply_markup: Union['MenuKeyboardMarkup',
|
329
|
+
'InlineKeyboardMarkup'] = None) -> 'Message':
|
330
|
+
"""Copy a message from another chat"""
|
331
|
+
return self.client.copy_message(
|
332
|
+
self.id,
|
333
|
+
from_chat_id,
|
334
|
+
message_id,
|
335
|
+
caption,
|
336
|
+
parse_mode,
|
337
|
+
reply_markup)
|
338
|
+
|
339
|
+
def send_audio(self,
|
340
|
+
audio: Union[str,
|
341
|
+
bytes,
|
342
|
+
'InputFile'],
|
343
|
+
caption: Optional[str] = None,
|
344
|
+
parse_mode: Optional[str] = None,
|
345
|
+
duration: Optional[int] = None,
|
346
|
+
performer: Optional[str] = None,
|
347
|
+
title: Optional[str] = None,
|
348
|
+
reply_markup: Union['MenuKeyboardMarkup',
|
349
|
+
'InlineKeyboardMarkup'] = None) -> 'Message':
|
350
|
+
"""Send an audio file"""
|
351
|
+
return self.client.send_audio(
|
352
|
+
self.id,
|
353
|
+
audio,
|
354
|
+
caption,
|
355
|
+
parse_mode,
|
356
|
+
duration,
|
357
|
+
performer,
|
358
|
+
title,
|
359
|
+
reply_markup)
|
360
|
+
|
361
|
+
def send_document(self,
|
362
|
+
document: Union[str,
|
363
|
+
bytes,
|
364
|
+
'InputFile'],
|
365
|
+
caption: Optional[str] = None,
|
366
|
+
parse_mode: Optional[str] = None,
|
367
|
+
reply_markup: Union['MenuKeyboardMarkup',
|
368
|
+
'InlineKeyboardMarkup'] = None) -> 'Message':
|
369
|
+
"""Send a document"""
|
370
|
+
return self.client.send_document(
|
371
|
+
self.id, document, caption, parse_mode, reply_markup)
|
372
|
+
|
373
|
+
def send_video(self,
|
374
|
+
video: Union[str,
|
375
|
+
bytes,
|
376
|
+
'InputFile'],
|
377
|
+
caption: Optional[str] = None,
|
378
|
+
parse_mode: Optional[str] = None,
|
379
|
+
duration: Optional[int] = None,
|
380
|
+
width: Optional[int] = None,
|
381
|
+
height: Optional[int] = None,
|
382
|
+
reply_markup: Union['MenuKeyboardMarkup',
|
383
|
+
'InlineKeyboardMarkup'] = None) -> 'Message':
|
384
|
+
"""Send a video"""
|
385
|
+
return self.client.send_video(
|
386
|
+
self.id,
|
387
|
+
video,
|
388
|
+
caption,
|
389
|
+
parse_mode,
|
390
|
+
duration,
|
391
|
+
width,
|
392
|
+
height,
|
393
|
+
reply_markup)
|
394
|
+
|
395
|
+
def send_animation(self,
|
396
|
+
animation: Union[str,
|
397
|
+
bytes,
|
398
|
+
'InputFile'],
|
399
|
+
caption: Optional[str] = None,
|
400
|
+
parse_mode: Optional[str] = None,
|
401
|
+
duration: Optional[int] = None,
|
402
|
+
width: Optional[int] = None,
|
403
|
+
height: Optional[int] = None,
|
404
|
+
reply_markup: Union['MenuKeyboardMarkup',
|
405
|
+
'InlineKeyboardMarkup'] = None) -> 'Message':
|
406
|
+
"""Send an animation"""
|
407
|
+
return self.client.send_animation(
|
408
|
+
self.id,
|
409
|
+
animation,
|
410
|
+
caption,
|
411
|
+
parse_mode,
|
412
|
+
duration,
|
413
|
+
width,
|
414
|
+
height,
|
415
|
+
reply_markup)
|
416
|
+
|
417
|
+
def send_voice(self,
|
418
|
+
voice: Union[str,
|
419
|
+
bytes,
|
420
|
+
'InputFile'],
|
421
|
+
caption: Optional[str] = None,
|
422
|
+
parse_mode: Optional[str] = None,
|
423
|
+
duration: Optional[int] = None,
|
424
|
+
reply_markup: Union['MenuKeyboardMarkup',
|
425
|
+
'InlineKeyboardMarkup'] = None) -> 'Message':
|
426
|
+
"""Send a voice message"""
|
427
|
+
return self.client.send_voice(
|
428
|
+
self.id,
|
429
|
+
voice,
|
430
|
+
caption,
|
431
|
+
parse_mode,
|
432
|
+
duration,
|
433
|
+
reply_markup)
|
434
|
+
|
435
|
+
def send_media_group(self,
|
436
|
+
chat_id: Union[int,
|
437
|
+
str],
|
438
|
+
media: List[Dict],
|
439
|
+
reply_to_message: Union['Message',
|
440
|
+
int,
|
441
|
+
str] = None) -> List['Message']:
|
442
|
+
"""Send a group of photos, videos, documents or audios as an album"""
|
443
|
+
data = {
|
444
|
+
'chat_id': chat_id,
|
445
|
+
'media': media,
|
446
|
+
'reply_to_message_id': reply_to_message.message_id if isinstance(
|
447
|
+
reply_to_message,
|
448
|
+
Message) else reply_to_message}
|
449
|
+
response = self._make_request('POST', 'sendMediaGroup', json=data)
|
450
|
+
return [Message(self, msg) for msg in response]
|
451
|
+
|
452
|
+
def send_location(self,
|
453
|
+
latitude: float,
|
454
|
+
longitude: float,
|
455
|
+
live_period: Optional[int] = None,
|
456
|
+
reply_markup: Union['MenuKeyboardMarkup',
|
457
|
+
'InlineKeyboardMarkup'] = None) -> 'Message':
|
458
|
+
"""Send a location"""
|
459
|
+
return self.client.send_location(
|
460
|
+
self.id, latitude, longitude, live_period, reply_markup)
|
461
|
+
|
462
|
+
def send_contact(self,
|
463
|
+
phone_number: str,
|
464
|
+
first_name: str,
|
465
|
+
last_name: Optional[str] = None,
|
466
|
+
reply_markup: Union['MenuKeyboardMarkup',
|
467
|
+
'InlineKeyboardMarkup'] = None) -> 'Message':
|
468
|
+
"""Send a contact"""
|
469
|
+
return self.client.send_contact(
|
470
|
+
self.id,
|
471
|
+
phone_number,
|
472
|
+
first_name,
|
473
|
+
last_name,
|
474
|
+
reply_markup)
|
475
|
+
|
476
|
+
def send_invoice(self,
|
477
|
+
title: str,
|
478
|
+
description: str,
|
479
|
+
payload: str,
|
480
|
+
provider_token: str,
|
481
|
+
prices: list,
|
482
|
+
photo_url: Optional[str] = None,
|
483
|
+
reply_to_message: Union[int,
|
484
|
+
str,
|
485
|
+
'Message'] = None,
|
486
|
+
reply_markup: Union['MenuKeyboardMarkup' , 'InlineKeyboardMarkup'] = None):
|
487
|
+
return self.client.send_invoice(
|
488
|
+
self.id,
|
489
|
+
title,
|
490
|
+
description,
|
491
|
+
payload,
|
492
|
+
provider_token,
|
493
|
+
prices,
|
494
|
+
photo_url,
|
495
|
+
reply_to_message,
|
496
|
+
reply_markup)
|
497
|
+
|
498
|
+
def send_action(self, action: str, how_many_times=1) -> bool:
|
499
|
+
"""Send a chat action"""
|
500
|
+
return self.client.send_chat_action(self.id, action, how_many_times)
|
501
|
+
|
502
|
+
def ban_chat_member(
|
503
|
+
self,
|
504
|
+
user_id: int,
|
505
|
+
until_date: Optional[int] = None) -> bool:
|
506
|
+
"""Ban a user from the chat"""
|
507
|
+
data = {
|
508
|
+
'chat_id': self.id,
|
509
|
+
'user_id': user_id,
|
510
|
+
'until_date': until_date
|
511
|
+
}
|
512
|
+
response = self.client._make_request(
|
513
|
+
'POST', 'banChatMember', data=data)
|
514
|
+
return response.get('ok', False)
|
515
|
+
|
516
|
+
def unban_chat_member(
|
517
|
+
self,
|
518
|
+
user_id: int,
|
519
|
+
only_if_banned: bool = False) -> bool:
|
520
|
+
"""Unban a previously banned user from the chat"""
|
521
|
+
data = {
|
522
|
+
'chat_id': self.id,
|
523
|
+
'user_id': user_id,
|
524
|
+
'only_if_banned': only_if_banned
|
525
|
+
}
|
526
|
+
response = self.client._make_request(
|
527
|
+
'POST', 'unbanChatMember', data=data)
|
528
|
+
return response.get('ok', False)
|
529
|
+
|
530
|
+
def promotee_chat_member(
|
531
|
+
self,
|
532
|
+
user_id: int,
|
533
|
+
can_change_info: bool = None,
|
534
|
+
can_post_messages: bool = None,
|
535
|
+
can_edit_messages: bool = None,
|
536
|
+
can_delete_messages: bool = None,
|
537
|
+
can_manage_video_chats: bool = None,
|
538
|
+
can_invite_users: bool = None,
|
539
|
+
can_restrict_members: bool = None) -> bool:
|
540
|
+
"""Promote or demote a chat member"""
|
541
|
+
data = {
|
542
|
+
'chat_id': self.id,
|
543
|
+
'user_id': user_id,
|
544
|
+
'can_change_info': can_change_info,
|
545
|
+
'can_post_messages': can_post_messages,
|
546
|
+
'can_edit_messages': can_edit_messages,
|
547
|
+
'can_delete_messages': can_delete_messages,
|
548
|
+
'can_manage_video_chats': can_manage_video_chats,
|
549
|
+
'can_invite_users': can_invite_users,
|
550
|
+
'can_restrict_members': can_restrict_members
|
551
|
+
}
|
552
|
+
response = self.client._make_request(
|
553
|
+
'POST', 'promoteChatMember', data=data)
|
554
|
+
return response.get('ok', False)
|
555
|
+
|
556
|
+
def set_chat_photo(self, photo: Union[str, bytes, 'InputFile']) -> bool:
|
557
|
+
"""Set a new chat photo"""
|
558
|
+
files = None
|
559
|
+
data = {'chat_id': self.id}
|
560
|
+
|
561
|
+
if isinstance(photo, (bytes, InputFile)) or hasattr(photo, 'read'):
|
562
|
+
files = {
|
563
|
+
'photo': photo if not isinstance(
|
564
|
+
photo, InputFile) else photo.file}
|
565
|
+
else:
|
566
|
+
data['photo'] = photo
|
567
|
+
|
568
|
+
response = self.client._make_request(
|
569
|
+
'POST', 'setChatPhoto', data=data, files=files)
|
570
|
+
return response.get('ok', False)
|
571
|
+
|
572
|
+
def leave_chat(self) -> bool:
|
573
|
+
"""Leave the chat"""
|
574
|
+
data = {'chat_id': self.id}
|
575
|
+
response = self.client._make_request('POST', 'leaveChat', data=data)
|
576
|
+
return response.get('ok', False)
|
577
|
+
|
578
|
+
def get_chat(self) -> 'Chat':
|
579
|
+
"""Get up to date information about the chat"""
|
580
|
+
data = {'chat_id': self.id}
|
581
|
+
response = self.client._make_request('GET', 'getChat', data=data)
|
582
|
+
return Chat(self.client, response)
|
583
|
+
|
584
|
+
def get_members_count(self) -> int:
|
585
|
+
"""Get the number of members in the chat"""
|
586
|
+
data = {'chat_id': self.id}
|
587
|
+
response = self.client._make_request(
|
588
|
+
'POST', 'getChatMembersCount', data=data)
|
589
|
+
return response.get('result', 0)
|
590
|
+
|
591
|
+
def pin_message(
|
592
|
+
self,
|
593
|
+
message_id: int,
|
594
|
+
disable_notification: bool = False) -> bool:
|
595
|
+
"""Pin a message in the chat"""
|
596
|
+
data = {
|
597
|
+
'chat_id': self.id,
|
598
|
+
'message_id': message_id,
|
599
|
+
'disable_notification': disable_notification
|
600
|
+
}
|
601
|
+
response = self.client._make_request(
|
602
|
+
'POST', 'pinChatMessage', data=data)
|
603
|
+
return response.get('ok', False)
|
604
|
+
|
605
|
+
def unpin_message(self, message_id: int) -> bool:
|
606
|
+
"""Unpin a message in the chat"""
|
607
|
+
data = {
|
608
|
+
'chat_id': self.id,
|
609
|
+
'message_id': message_id
|
610
|
+
}
|
611
|
+
response = self.client._make_request(
|
612
|
+
'POST', 'unpinChatMessage', data=data)
|
613
|
+
return response.get('ok', False)
|
614
|
+
|
615
|
+
def unpin_all_messages(self) -> bool:
|
616
|
+
"""Unpin all messages in the chat"""
|
617
|
+
data = {'chat_id': self.id}
|
618
|
+
response = self.client._make_request(
|
619
|
+
'POST', 'unpinAllChatMessages', data=data)
|
620
|
+
return response.get('ok', False)
|
621
|
+
|
622
|
+
def set_chat_title(self, title: str) -> bool:
|
623
|
+
"""Change the title of the chat"""
|
624
|
+
data = {
|
625
|
+
'chat_id': self.id,
|
626
|
+
'title': title
|
627
|
+
}
|
628
|
+
response = self.client._make_request('POST', 'setChatTitle', data=data)
|
629
|
+
return response.get('ok', False)
|
630
|
+
|
631
|
+
def set_chat_description(self, description: str) -> bool:
|
632
|
+
"""Change the description of the chat"""
|
633
|
+
data = {
|
634
|
+
'chat_id': self.id,
|
635
|
+
'description': description
|
636
|
+
}
|
637
|
+
response = self.client._make_request(
|
638
|
+
'POST', 'setChatDescription', data=data)
|
639
|
+
return response.get('ok', False)
|
640
|
+
|
641
|
+
def delete_chat_photo(self) -> bool:
|
642
|
+
"""Delete the chat photo"""
|
643
|
+
data = {'chat_id': self.id}
|
644
|
+
response = self.client._make_request(
|
645
|
+
'POST', 'deleteChatPhoto', data=data)
|
646
|
+
return response.get('ok', False)
|
647
|
+
|
648
|
+
def create_invite_link(self) -> str:
|
649
|
+
"""Create an invite link for the chat"""
|
650
|
+
data = {'chat_id': self.id}
|
651
|
+
response = self.client._make_request(
|
652
|
+
'POST', 'createChatInviteLink', data=data)
|
653
|
+
return response.get('result', {}).get('invite_link')
|
654
|
+
|
655
|
+
def revoke_invite_link(self, invite_link: str) -> bool:
|
656
|
+
"""Revoke an invite link for the chat"""
|
657
|
+
data = {
|
658
|
+
'chat_id': self.id,
|
659
|
+
'invite_link': invite_link
|
660
|
+
}
|
661
|
+
response = self.client._make_request(
|
662
|
+
'POST', 'revokeChatInviteLink', data=data)
|
663
|
+
return response.get('ok', False)
|
664
|
+
|
665
|
+
def export_invite_link(self) -> str:
|
666
|
+
"""Generate a new invite link for the chat"""
|
667
|
+
data = {'chat_id': self.id}
|
668
|
+
response = self.client._make_request(
|
669
|
+
'POST', 'exportChatInviteLink', data=data)
|
670
|
+
return response.get('result')
|
671
|
+
|
672
|
+
|
673
|
+
class User:
|
674
|
+
"""Represents a Bale user"""
|
675
|
+
|
676
|
+
def __init__(self, client: 'Client', data: Dict[str, Any]):
|
677
|
+
if not data.get('ok'):
|
678
|
+
raise BaleException(
|
679
|
+
f"API request failed: {traceback.format_exc()}")
|
680
|
+
self.client = client
|
681
|
+
result = data.get('result', {})
|
682
|
+
self.data = data
|
683
|
+
self.ok = data.get('ok')
|
684
|
+
self.id = result.get('id')
|
685
|
+
self.is_bot = result.get('is_bot')
|
686
|
+
self.first_name = result.get('first_name')
|
687
|
+
self.last_name = result.get('last_name')
|
688
|
+
self.username = result.get('username')
|
689
|
+
|
690
|
+
def set_state(self, state: str) -> None:
|
691
|
+
"""Set the state for a chat or user"""
|
692
|
+
self.client.states[str(self.id)] = state
|
693
|
+
|
694
|
+
def get_state(self) -> str | None:
|
695
|
+
"""Get the state for a chat or user"""
|
696
|
+
return self.client.states.get(str(self.id))
|
697
|
+
|
698
|
+
@property
|
699
|
+
def state(self):
|
700
|
+
return self.get_state()
|
701
|
+
|
702
|
+
def del_state(self) -> None:
|
703
|
+
"""Delete the state for a chat or user"""
|
704
|
+
self.client.states.pop(str(self.id), None)
|
705
|
+
|
706
|
+
def __str__(self):
|
707
|
+
return self.first_name
|
708
|
+
|
709
|
+
def send_message(self,
|
710
|
+
text: str,
|
711
|
+
parse_mode: Optional[str] = None,
|
712
|
+
reply_markup: Union['MenuKeyboardMarkup',
|
713
|
+
'InlineKeyboardMarkup'] = None) -> 'Message':
|
714
|
+
"""Send a message to this user"""
|
715
|
+
return self.client.send_message(
|
716
|
+
self.id, text, parse_mode, reply_markup)
|
717
|
+
|
718
|
+
def send_photo(self,
|
719
|
+
photo: Union[str,
|
720
|
+
bytes,
|
721
|
+
'InputFile'],
|
722
|
+
caption: Optional[str] = None,
|
723
|
+
parse_mode: Optional[str] = None,
|
724
|
+
reply_markup: Union['MenuKeyboardMarkup',
|
725
|
+
'InlineKeyboardMarkup'] = None) -> 'Message':
|
726
|
+
"""Send a photo to a chat"""
|
727
|
+
return self.client.send_photo(
|
728
|
+
self.id, photo, caption, parse_mode, reply_markup)
|
729
|
+
|
730
|
+
def forward_message(
|
731
|
+
self, from_chat_id: Union[int, str], message_id: int) -> 'Message':
|
732
|
+
"""Forward a message to this user"""
|
733
|
+
self.client.forward_message(self.id, from_chat_id, message_id)
|
734
|
+
|
735
|
+
def copy_message(self,
|
736
|
+
from_chat_id: Union[int,
|
737
|
+
str],
|
738
|
+
message_id: int) -> 'Message':
|
739
|
+
"""Copy a message to this user"""
|
740
|
+
self.client.copy_message(self.id, from_chat_id, message_id)
|
741
|
+
|
742
|
+
def send_audio(self,
|
743
|
+
audio: Union[str,
|
744
|
+
bytes,
|
745
|
+
'InputFile'],
|
746
|
+
caption: Optional[str] = None,
|
747
|
+
parse_mode: Optional[str] = None,
|
748
|
+
reply_markup: Union['MenuKeyboardMarkup',
|
749
|
+
'InlineKeyboardMarkup'] = None,
|
750
|
+
reply_to_message: Union[str,
|
751
|
+
int,
|
752
|
+
'Message'] = None) -> 'Message':
|
753
|
+
"""Send an audio file to this user"""
|
754
|
+
self.client.send_audio(
|
755
|
+
self.id,
|
756
|
+
audio,
|
757
|
+
caption,
|
758
|
+
parse_mode,
|
759
|
+
reply_markup,
|
760
|
+
reply_to_message)
|
761
|
+
|
762
|
+
def send_document(self,
|
763
|
+
document: Union[str,
|
764
|
+
bytes,
|
765
|
+
'InputFile'],
|
766
|
+
caption: Optional[str] = None,
|
767
|
+
parse_mode: Optional[str] = None,
|
768
|
+
reply_markup: Union['MenuKeyboardMarkup',
|
769
|
+
'InlineKeyboardMarkup'] = None,
|
770
|
+
reply_to_message: Union[str,
|
771
|
+
int,
|
772
|
+
'Message'] = None) -> 'Message':
|
773
|
+
"""Send a document to this user"""
|
774
|
+
self.client.send_document(
|
775
|
+
self.id,
|
776
|
+
document,
|
777
|
+
caption,
|
778
|
+
parse_mode,
|
779
|
+
reply_markup,
|
780
|
+
reply_markup)
|
781
|
+
|
782
|
+
def send_video(self,
|
783
|
+
video: Union[str,
|
784
|
+
bytes,
|
785
|
+
'InputFile'],
|
786
|
+
caption: Optional[str] = None,
|
787
|
+
parse_mode: Optional[str] = None,
|
788
|
+
reply_markup: Union['MenuKeyboardMarkup',
|
789
|
+
'InlineKeyboardMarkup'] = None,
|
790
|
+
reply_to_message: Union[str,
|
791
|
+
int,
|
792
|
+
'Message'] = None) -> 'Message':
|
793
|
+
"""Send a video to this user"""
|
794
|
+
self.client.send_video(
|
795
|
+
self.id,
|
796
|
+
video,
|
797
|
+
caption,
|
798
|
+
parse_mode,
|
799
|
+
reply_markup,
|
800
|
+
reply_to_message)
|
801
|
+
|
802
|
+
def send_animation(self,
|
803
|
+
animation: Union[str,
|
804
|
+
bytes,
|
805
|
+
'InputFile'],
|
806
|
+
caption: Optional[str] = None,
|
807
|
+
parse_mode: Optional[str] = None,
|
808
|
+
reply_markup: Union['MenuKeyboardMarkup',
|
809
|
+
'InlineKeyboardMarkup'] = None,
|
810
|
+
reply_to_message: Union[int,
|
811
|
+
str,
|
812
|
+
'Message'] = None) -> 'Message':
|
813
|
+
"""Send an animation to this user"""
|
814
|
+
return self.client.send_animation(
|
815
|
+
self.id,
|
816
|
+
animation,
|
817
|
+
caption,
|
818
|
+
parse_mode,
|
819
|
+
reply_markup,
|
820
|
+
reply_to_message)
|
821
|
+
|
822
|
+
def send_voice(self,
|
823
|
+
voice: Union[str,
|
824
|
+
bytes,
|
825
|
+
'InputFile'],
|
826
|
+
caption: Optional[str] = None,
|
827
|
+
parse_mode: Optional[str] = None,
|
828
|
+
reply_markup: Union['MenuKeyboardMarkup',
|
829
|
+
'InlineKeyboardMarkup'] = None,
|
830
|
+
reply_to_message: Union[int,
|
831
|
+
str,
|
832
|
+
'Message'] = None) -> 'Message':
|
833
|
+
"""Send a voice message to this user"""
|
834
|
+
return self.client.send_voice(
|
835
|
+
self.id,
|
836
|
+
voice,
|
837
|
+
caption,
|
838
|
+
parse_mode,
|
839
|
+
reply_markup,
|
840
|
+
reply_to_message)
|
841
|
+
|
842
|
+
def send_media_group(self,
|
843
|
+
chat_id: Union[int,
|
844
|
+
str],
|
845
|
+
media: List[Dict],
|
846
|
+
reply_to_message: Union['Message',
|
847
|
+
int,
|
848
|
+
str] = None) -> List['Message']:
|
849
|
+
"""Send a group of photos, videos, documents or audios as an album"""
|
850
|
+
return self.client.send_media_group(chat_id, media, reply_to_message)
|
851
|
+
|
852
|
+
def send_location(self,
|
853
|
+
latitude: float,
|
854
|
+
longitude: float,
|
855
|
+
reply_markup: Union['MenuKeyboardMarkup',
|
856
|
+
'InlineKeyboardMarkup'] = None,
|
857
|
+
reply_to_message: Union[str,
|
858
|
+
int,
|
859
|
+
'Message'] = None) -> 'Message':
|
860
|
+
"""Send a location to this user"""
|
861
|
+
return self.send_location(
|
862
|
+
latitude,
|
863
|
+
longitude,
|
864
|
+
reply_markup,
|
865
|
+
reply_to_message)
|
866
|
+
|
867
|
+
def send_contact(self,
|
868
|
+
phone_number: str,
|
869
|
+
first_name: str,
|
870
|
+
last_name: Optional[str] = None,
|
871
|
+
reply_markup: Union['MenuKeyboardMarkup',
|
872
|
+
'InlineKeyboardMarkup'] = None,
|
873
|
+
reply_to_message: Union[str,
|
874
|
+
int,
|
875
|
+
'Message'] = None) -> 'Message':
|
876
|
+
"""Send a contact to this user"""
|
877
|
+
return self.client.send_contact(
|
878
|
+
self.id,
|
879
|
+
phone_number,
|
880
|
+
first_name,
|
881
|
+
last_name,
|
882
|
+
reply_markup,
|
883
|
+
reply_to_message)
|
884
|
+
|
885
|
+
def send_invoice(self,
|
886
|
+
title: str,
|
887
|
+
description: str,
|
888
|
+
payload: str,
|
889
|
+
provider_token: str,
|
890
|
+
prices: list,
|
891
|
+
photo_url: Optional[str] = None,
|
892
|
+
reply_to_message: Union[int,
|
893
|
+
str,
|
894
|
+
'Message'] = None,
|
895
|
+
reply_markup: Union['MenuKeyboardMarkup' , 'InlineKeyboardMarkup'] = None):
|
896
|
+
return self.client.send_invoice(
|
897
|
+
self.id,
|
898
|
+
title,
|
899
|
+
description,
|
900
|
+
payload,
|
901
|
+
provider_token,
|
902
|
+
prices,
|
903
|
+
photo_url,
|
904
|
+
reply_to_message,
|
905
|
+
reply_markup)
|
906
|
+
|
907
|
+
def send_action(self, action: str, how_many_times: int = 1):
|
908
|
+
"""Send a chat action to this user"""
|
909
|
+
return self.client.send_chat_action(self.id, action, how_many_times)
|
910
|
+
|
911
|
+
|
912
|
+
class Message:
|
913
|
+
"""Represents a message in Bale"""
|
914
|
+
|
915
|
+
def __init__(self, client: 'Client', data: Dict[str, Any]):
|
916
|
+
if not data.get('ok'):
|
917
|
+
raise BaleException(
|
918
|
+
f"API request failed: {traceback.format_exc()}")
|
919
|
+
self.client = client
|
920
|
+
self.ok = data.get('ok')
|
921
|
+
result = data.get('result', {})
|
922
|
+
self.json_result = result
|
923
|
+
|
924
|
+
self.message_id = self.id = result.get('message_id')
|
925
|
+
self.from_user = self.author = User(
|
926
|
+
client, {'ok': True, 'result': result.get('from', {})})
|
927
|
+
self.date = result.get('date')
|
928
|
+
self.chat = Chat(
|
929
|
+
client, {
|
930
|
+
'ok': True, 'result': result.get(
|
931
|
+
'chat', {})})
|
932
|
+
self.text = result.get('text')
|
933
|
+
self.caption = result.get('caption')
|
934
|
+
|
935
|
+
self.document = Document(
|
936
|
+
result.get(
|
937
|
+
'document',
|
938
|
+
{})) if result.get('document') else None
|
939
|
+
|
940
|
+
photos = result.get('photo', [])
|
941
|
+
self.photo = [Document(photo) for photo in photos] if photos else None
|
942
|
+
self.largest_photo = Document(photos[-1]) if photos else None
|
943
|
+
|
944
|
+
self.video = Document(
|
945
|
+
result.get('video')) if result.get('video') else None
|
946
|
+
self.audio = Document(
|
947
|
+
result.get('audio')) if result.get('audio') else None
|
948
|
+
self.voice = Voice(
|
949
|
+
result.get('voice')) if result.get('voice') else None
|
950
|
+
self.animation = Document(
|
951
|
+
result.get('animation')) if result.get('animation') else None
|
952
|
+
self.sticker = Document(
|
953
|
+
result.get('sticker')) if result.get('sticker') else None
|
954
|
+
self.video_note = Document(
|
955
|
+
result.get('video_note')) if result.get('video_note') else None
|
956
|
+
|
957
|
+
self.media_group_id = result.get('media_group_id')
|
958
|
+
self.has_media = any([self.document,
|
959
|
+
self.photo,
|
960
|
+
self.video,
|
961
|
+
self.audio,
|
962
|
+
self.voice,
|
963
|
+
self.animation,
|
964
|
+
self.sticker,
|
965
|
+
self.video_note])
|
966
|
+
|
967
|
+
self.contact = Contact(
|
968
|
+
result.get('contact')) if result.get('contact') else None
|
969
|
+
self.location = Location(
|
970
|
+
result.get('location')) if result.get('location') else None
|
971
|
+
self.forward_from = User(client, {'ok': True, 'result': result.get(
|
972
|
+
'forward_from', {})}) if result.get('forward_from') else None
|
973
|
+
self.forward_from_message_id = result.get('forward_from_message_id')
|
974
|
+
self.invoice = Invoice(
|
975
|
+
result.get('invoice')) if result.get('invoice') else None
|
976
|
+
self.reply_to_message = Message(client, {'ok': True, 'result': result.get(
|
977
|
+
'reply_to_message', {})}) if result.get('reply_to_message') else None
|
978
|
+
self.reply = self.reply_message
|
979
|
+
self.send = lambda text, parse_mode=None, reply_markup=None: self.client.send_message(
|
980
|
+
self.chat.id, text, parse_mode, reply_markup, reply_to_message=self)
|
981
|
+
|
982
|
+
self.command = None
|
983
|
+
self.args = None
|
984
|
+
txt = self.text.split(' ') if self.text else []
|
985
|
+
|
986
|
+
self.command = txt[0] if txt else None
|
987
|
+
self.has_slash_command = self.command.startswith(
|
988
|
+
'/') if self.text else None
|
989
|
+
self.args = txt[1:] if self.text else None
|
990
|
+
|
991
|
+
self.start = self.command == '/start' if self.text else None
|
992
|
+
|
993
|
+
def edit(self,
|
994
|
+
text: str,
|
995
|
+
parse_mode: Optional[str] = None,
|
996
|
+
reply_markup: Union['MenuKeyboardMarkup',
|
997
|
+
'InlineKeyboardMarkup'] = None) -> 'Message':
|
998
|
+
"""Edit this message"""
|
999
|
+
return self.client.edit_message(
|
1000
|
+
self.chat.id,
|
1001
|
+
self.message_id,
|
1002
|
+
text,
|
1003
|
+
parse_mode,
|
1004
|
+
reply_markup)
|
1005
|
+
|
1006
|
+
def delete(self) -> bool:
|
1007
|
+
"""Delete this message"""
|
1008
|
+
return self.client.delete_message(self.chat.id, self.message_id)
|
1009
|
+
|
1010
|
+
def reply_message(self,
|
1011
|
+
text: str,
|
1012
|
+
parse_mode: Optional[str] = None,
|
1013
|
+
reply_markup: Union['MenuKeyboardMarkup',
|
1014
|
+
'InlineKeyboardMarkup'] = None) -> 'Message':
|
1015
|
+
"""Send a message to this user"""
|
1016
|
+
return self.client.send_message(
|
1017
|
+
self.chat.id,
|
1018
|
+
text,
|
1019
|
+
parse_mode,
|
1020
|
+
reply_markup,
|
1021
|
+
reply_to_message=self)
|
1022
|
+
|
1023
|
+
def reply_photo(self,
|
1024
|
+
photo: Union[str,
|
1025
|
+
bytes,
|
1026
|
+
'InputFile'],
|
1027
|
+
caption: Optional[str] = None,
|
1028
|
+
parse_mode: Optional[str] = None,
|
1029
|
+
reply_markup: Union['MenuKeyboardMarkup',
|
1030
|
+
'InlineKeyboardMarkup'] = None) -> 'Message':
|
1031
|
+
"""Send a photo to a chat"""
|
1032
|
+
return self.client.send_photo(
|
1033
|
+
self.chat.id,
|
1034
|
+
photo,
|
1035
|
+
caption,
|
1036
|
+
parse_mode,
|
1037
|
+
reply_markup,
|
1038
|
+
reply_to_message=self)
|
1039
|
+
|
1040
|
+
def reply_audio(self,
|
1041
|
+
audio: Union[str,
|
1042
|
+
bytes,
|
1043
|
+
'InputFile'],
|
1044
|
+
caption: Optional[str] = None,
|
1045
|
+
parse_mode: Optional[str] = None,
|
1046
|
+
reply_markup: Union['MenuKeyboardMarkup',
|
1047
|
+
'InlineKeyboardMarkup'] = None) -> 'Message':
|
1048
|
+
"""Send an audio file to this user"""
|
1049
|
+
self.client.send_audio(
|
1050
|
+
self.chat.id,
|
1051
|
+
audio,
|
1052
|
+
caption,
|
1053
|
+
parse_mode,
|
1054
|
+
reply_markup,
|
1055
|
+
reply_to_message=self)
|
1056
|
+
|
1057
|
+
def reply_document(self,
|
1058
|
+
document: Union[str,
|
1059
|
+
bytes,
|
1060
|
+
'InputFile'],
|
1061
|
+
caption: Optional[str] = None,
|
1062
|
+
parse_mode: Optional[str] = None,
|
1063
|
+
reply_markup: Union['MenuKeyboardMarkup',
|
1064
|
+
'InlineKeyboardMarkup'] = None) -> 'Message':
|
1065
|
+
"""Send a document to this user"""
|
1066
|
+
self.client.send_document(
|
1067
|
+
self.chat.id,
|
1068
|
+
document,
|
1069
|
+
caption,
|
1070
|
+
parse_mode,
|
1071
|
+
reply_markup,
|
1072
|
+
reply_markup,
|
1073
|
+
reply_to_message=self)
|
1074
|
+
|
1075
|
+
def reply_video(self,
|
1076
|
+
video: Union[str,
|
1077
|
+
bytes,
|
1078
|
+
'InputFile'],
|
1079
|
+
caption: Optional[str] = None,
|
1080
|
+
parse_mode: Optional[str] = None,
|
1081
|
+
reply_markup: Union['MenuKeyboardMarkup',
|
1082
|
+
'InlineKeyboardMarkup'] = None) -> 'Message':
|
1083
|
+
"""Send a video to this user"""
|
1084
|
+
self.client.send_video(
|
1085
|
+
self.chat.id,
|
1086
|
+
video,
|
1087
|
+
caption,
|
1088
|
+
parse_mode,
|
1089
|
+
reply_markup,
|
1090
|
+
reply_to_message=self)
|
1091
|
+
|
1092
|
+
def reply_animation(self,
|
1093
|
+
animation: Union[str,
|
1094
|
+
bytes,
|
1095
|
+
'InputFile'],
|
1096
|
+
caption: Optional[str] = None,
|
1097
|
+
parse_mode: Optional[str] = None,
|
1098
|
+
reply_markup: Union['MenuKeyboardMarkup',
|
1099
|
+
'InlineKeyboardMarkup'] = None) -> 'Message':
|
1100
|
+
"""Send an animation to this user"""
|
1101
|
+
return self.client.send_animation(
|
1102
|
+
self.chat.id,
|
1103
|
+
animation,
|
1104
|
+
caption,
|
1105
|
+
parse_mode,
|
1106
|
+
reply_markup,
|
1107
|
+
reply_to_message=self)
|
1108
|
+
|
1109
|
+
def reply_voice(self,
|
1110
|
+
voice: Union[str,
|
1111
|
+
bytes,
|
1112
|
+
'InputFile'],
|
1113
|
+
caption: Optional[str] = None,
|
1114
|
+
parse_mode: Optional[str] = None,
|
1115
|
+
reply_markup: Union['MenuKeyboardMarkup',
|
1116
|
+
'InlineKeyboardMarkup'] = None) -> 'Message':
|
1117
|
+
"""Send a voice message to this user"""
|
1118
|
+
return self.client.send_voice(
|
1119
|
+
self.chat.id,
|
1120
|
+
voice,
|
1121
|
+
caption,
|
1122
|
+
parse_mode,
|
1123
|
+
reply_markup,
|
1124
|
+
reply_to_message=self)
|
1125
|
+
|
1126
|
+
def reply_media_group(self,
|
1127
|
+
media: List[Dict],
|
1128
|
+
reply_to_message: Union['Message',
|
1129
|
+
int,
|
1130
|
+
str] = None) -> List['Message']:
|
1131
|
+
"""Send a group of photos, videos, documents or audios as an album"""
|
1132
|
+
return self.client.send_media_group(
|
1133
|
+
self.chat.id, media, reply_to_message=self)
|
1134
|
+
|
1135
|
+
def reply_location(self,
|
1136
|
+
latitude: float,
|
1137
|
+
longitude: float,
|
1138
|
+
reply_markup: Union['MenuKeyboardMarkup',
|
1139
|
+
'InlineKeyboardMarkup'] = None) -> 'Message':
|
1140
|
+
"""Send a location to this user"""
|
1141
|
+
return self.client.send_location(
|
1142
|
+
self.chat.id,
|
1143
|
+
latitude,
|
1144
|
+
longitude,
|
1145
|
+
reply_markup,
|
1146
|
+
reply_to_message=self)
|
1147
|
+
|
1148
|
+
def reply_contact(self,
|
1149
|
+
phone_number: str,
|
1150
|
+
first_name: str,
|
1151
|
+
last_name: Optional[str] = None,
|
1152
|
+
reply_markup: Union['MenuKeyboardMarkup',
|
1153
|
+
'InlineKeyboardMarkup'] = None,
|
1154
|
+
reply_to_message: Union[str,
|
1155
|
+
int,
|
1156
|
+
'Message'] = None) -> 'Message':
|
1157
|
+
"""Send a contact to this user"""
|
1158
|
+
return self.client.send_contact(
|
1159
|
+
self.chat.id,
|
1160
|
+
phone_number,
|
1161
|
+
first_name,
|
1162
|
+
last_name,
|
1163
|
+
reply_markup,
|
1164
|
+
reply_to_message)
|
1165
|
+
|
1166
|
+
def reply_invoice(self,
|
1167
|
+
title: str,
|
1168
|
+
description: str,
|
1169
|
+
payload: str,
|
1170
|
+
provider_token: str,
|
1171
|
+
prices: list,
|
1172
|
+
photo_url: Optional[str] = None,
|
1173
|
+
reply_markup: Union['MenuKeyboardMarkup' , 'InlineKeyboardMarkup'] = None):
|
1174
|
+
return self.client.send_invoice(
|
1175
|
+
self.chat.id,
|
1176
|
+
title,
|
1177
|
+
description,
|
1178
|
+
payload,
|
1179
|
+
provider_token,
|
1180
|
+
prices,
|
1181
|
+
photo_url,
|
1182
|
+
self,
|
1183
|
+
reply_markup)
|
1184
|
+
|
1185
|
+
|
1186
|
+
|
1187
|
+
class LabeledPrice:
|
1188
|
+
def __init__(self, label: str, amount: int):
|
1189
|
+
self.label = label
|
1190
|
+
self.amount = amount
|
1191
|
+
self.json = {
|
1192
|
+
"label": self.label,
|
1193
|
+
"amount": self.amount
|
1194
|
+
}
|
1195
|
+
|
1196
|
+
|
1197
|
+
class Document:
|
1198
|
+
def __init__(self, data: dict):
|
1199
|
+
print(data)
|
1200
|
+
if data:
|
1201
|
+
self.file_id = data.get('file_id')
|
1202
|
+
self.file_unique_id = data.get('file_unique_id')
|
1203
|
+
self.file_name = data.get('file_name')
|
1204
|
+
self.mime_type = data.get('mime_type')
|
1205
|
+
self.file_size = data.get('file_size')
|
1206
|
+
self.input_file = InputFile(self.file_id)
|
1207
|
+
else:
|
1208
|
+
self.file_id = None
|
1209
|
+
self.file_unique_id = None
|
1210
|
+
self.file_name = None
|
1211
|
+
self.mime_type = None
|
1212
|
+
self.file_size = None
|
1213
|
+
self.input_file = None
|
1214
|
+
|
1215
|
+
def __bool__(self):
|
1216
|
+
return bool(self.file_id)
|
1217
|
+
|
1218
|
+
|
1219
|
+
class Invoice:
|
1220
|
+
def __init__(self, data: dict):
|
1221
|
+
if data:
|
1222
|
+
self.title = data.get('title')
|
1223
|
+
self.description = data.get('description')
|
1224
|
+
self.start_parameter = data.get('start_parameter')
|
1225
|
+
self.currency = data.get('currency')
|
1226
|
+
self.total_amount = data.get('total_amount')
|
1227
|
+
else:
|
1228
|
+
self.title = None
|
1229
|
+
self.description = None
|
1230
|
+
self.start_parameter = None
|
1231
|
+
self.currency = None
|
1232
|
+
self.total_amount = None
|
1233
|
+
|
1234
|
+
|
1235
|
+
class Photo:
|
1236
|
+
def __init__(self, data):
|
1237
|
+
if data:
|
1238
|
+
self.file_id = data.get('file_id')
|
1239
|
+
self.file_unique_id = data.get('file_unique_id')
|
1240
|
+
self.width = data.get('width')
|
1241
|
+
self.height = data.get('height')
|
1242
|
+
self.file_size = data.get('file_size')
|
1243
|
+
else:
|
1244
|
+
self.file_id = None
|
1245
|
+
self.file_unique_id = None
|
1246
|
+
self.width = None
|
1247
|
+
self.height = None
|
1248
|
+
self.file_size = None
|
1249
|
+
|
1250
|
+
|
1251
|
+
class Voice:
|
1252
|
+
def __init__(self, data: dict):
|
1253
|
+
if data:
|
1254
|
+
self.file_id = data.get('file_id')
|
1255
|
+
self.file_unique_id = data.get('file_unique_id')
|
1256
|
+
self.duration = data.get('duration')
|
1257
|
+
self.mime_type = data.get('mime_type')
|
1258
|
+
self.file_size = data.get('file_size')
|
1259
|
+
else:
|
1260
|
+
self.file_id = None
|
1261
|
+
self.file_unique_id = None
|
1262
|
+
self.duration = None
|
1263
|
+
self.mime_type = None
|
1264
|
+
self.file_size = None
|
1265
|
+
|
1266
|
+
|
1267
|
+
class Location:
|
1268
|
+
def __init__(self, data: dict):
|
1269
|
+
if data:
|
1270
|
+
self.long = self.longitude = data.get('longitude')
|
1271
|
+
self.lat = self.latitude = data.get('latitude')
|
1272
|
+
else:
|
1273
|
+
self.longitude = self.long = None
|
1274
|
+
self.latitude = self.lat = None
|
1275
|
+
|
1276
|
+
|
1277
|
+
class Contact:
|
1278
|
+
def __init__(self, data: dict):
|
1279
|
+
if data:
|
1280
|
+
self.phone_number = data.get('phone_number')
|
1281
|
+
self.first_name = data.get('first_name')
|
1282
|
+
self.last_name = data.get('last_name')
|
1283
|
+
self.user_id = data.get('user_id')
|
1284
|
+
else:
|
1285
|
+
self.phone_number = None
|
1286
|
+
self.first_name = None
|
1287
|
+
self.last_name = None
|
1288
|
+
self.user_id = None
|
1289
|
+
|
1290
|
+
|
1291
|
+
class MenuKeyboardButton:
|
1292
|
+
def __init__(
|
1293
|
+
self,
|
1294
|
+
text: str,
|
1295
|
+
request_contact: bool = False,
|
1296
|
+
request_location: bool = False):
|
1297
|
+
if not text:
|
1298
|
+
raise ValueError("Text cannot be empty")
|
1299
|
+
if request_contact and request_location:
|
1300
|
+
raise ValueError("Cannot request both contact and location")
|
1301
|
+
|
1302
|
+
self.button = {"text": text}
|
1303
|
+
if request_contact:
|
1304
|
+
self.button["request_contact"] = True
|
1305
|
+
if request_location:
|
1306
|
+
self.button["request_location"] = True
|
1307
|
+
|
1308
|
+
|
1309
|
+
class InlineKeyboardButton:
|
1310
|
+
def __init__(
|
1311
|
+
self,
|
1312
|
+
text: str,
|
1313
|
+
callback_data: Optional[str] = None,
|
1314
|
+
url: Optional[str] = None,
|
1315
|
+
web_app: Optional[str] = None):
|
1316
|
+
self.button = {"text": text}
|
1317
|
+
if sum(bool(x) for x in [callback_data, url, web_app]) != 1:
|
1318
|
+
raise ValueError(
|
1319
|
+
"Exactly one of callback_data, url, or web_app must be provided")
|
1320
|
+
if callback_data:
|
1321
|
+
self.button["callback_data"] = callback_data
|
1322
|
+
elif url:
|
1323
|
+
self.button["url"] = url
|
1324
|
+
elif web_app:
|
1325
|
+
self.button["web_app"] = {"url": web_app}
|
1326
|
+
|
1327
|
+
|
1328
|
+
class MenuKeyboardMarkup:
|
1329
|
+
def __init__(self, menu_keyboard: Optional[list] = None):
|
1330
|
+
self.menu_keyboard = []
|
1331
|
+
if menu_keyboard:
|
1332
|
+
for row_idx, row in enumerate(menu_keyboard):
|
1333
|
+
if isinstance(row, tuple) or isinstance(row, str):
|
1334
|
+
buttons = [row] if isinstance(row, str) else row
|
1335
|
+
for button in buttons:
|
1336
|
+
if not isinstance(button, str):
|
1337
|
+
raise ValueError("Button must be string")
|
1338
|
+
self.add(MenuKeyboardButton(button), row_idx)
|
1339
|
+
|
1340
|
+
def add(self, button: MenuKeyboardButton,
|
1341
|
+
row: int = 0) -> 'MenuKeyboardMarkup':
|
1342
|
+
if row < 0:
|
1343
|
+
raise ValueError("Row index cannot be negative")
|
1344
|
+
while len(self.menu_keyboard) <= row:
|
1345
|
+
self.menu_keyboard.append([])
|
1346
|
+
self.menu_keyboard[row].append(button.button)
|
1347
|
+
self.cleanup_empty_rows()
|
1348
|
+
return self
|
1349
|
+
|
1350
|
+
def cleanup_empty_rows(self) -> None:
|
1351
|
+
self.menu_keyboard = [row for row in self.menu_keyboard if row]
|
1352
|
+
|
1353
|
+
def clear(self) -> None:
|
1354
|
+
self.menu_keyboard = []
|
1355
|
+
|
1356
|
+
def remove_button(self, text: str) -> bool:
|
1357
|
+
found = False
|
1358
|
+
for row in self.menu_keyboard:
|
1359
|
+
for button in row[:]:
|
1360
|
+
if button.get('text') == text:
|
1361
|
+
row.remove(button)
|
1362
|
+
found = True
|
1363
|
+
self.cleanup_empty_rows()
|
1364
|
+
return found
|
1365
|
+
|
1366
|
+
@property
|
1367
|
+
def keyboard(self):
|
1368
|
+
return {"keyboard": self.menu_keyboard}
|
1369
|
+
|
1370
|
+
|
1371
|
+
class InlineKeyboardMarkup:
|
1372
|
+
def __init__(self, inline_keyboard: Optional[list] = None):
|
1373
|
+
self.inline_keyboard = []
|
1374
|
+
if inline_keyboard:
|
1375
|
+
for row_idx, row in enumerate(inline_keyboard):
|
1376
|
+
for button in row:
|
1377
|
+
if not isinstance(button, (tuple, list)):
|
1378
|
+
raise ValueError("Button must be a tuple or list")
|
1379
|
+
if len(button) != 2:
|
1380
|
+
raise ValueError(
|
1381
|
+
"Button must contain exactly text and callback_data/url/web_app")
|
1382
|
+
if not isinstance(button[0], str) or not isinstance(
|
1383
|
+
button[1], str):
|
1384
|
+
raise ValueError(
|
1385
|
+
"Button text and data must be strings")
|
1386
|
+
self.add(
|
1387
|
+
InlineKeyboardButton(
|
1388
|
+
button[0],
|
1389
|
+
callback_data=button[1]),
|
1390
|
+
row_idx)
|
1391
|
+
|
1392
|
+
def add(self, button: InlineKeyboardButton,
|
1393
|
+
row: int = 0) -> 'InlineKeyboardMarkup':
|
1394
|
+
if row < 0:
|
1395
|
+
raise ValueError("Row index cannot be negative")
|
1396
|
+
while len(self.inline_keyboard) <= row:
|
1397
|
+
self.inline_keyboard.append([])
|
1398
|
+
self.inline_keyboard[row].append(button.button)
|
1399
|
+
self.cleanup_empty_rows()
|
1400
|
+
return self
|
1401
|
+
|
1402
|
+
def cleanup_empty_rows(self) -> None:
|
1403
|
+
self.inline_keyboard = [row for row in self.inline_keyboard if row]
|
1404
|
+
|
1405
|
+
def clear(self) -> None:
|
1406
|
+
self.inline_keyboard = []
|
1407
|
+
|
1408
|
+
def remove_button(self, text: str) -> bool:
|
1409
|
+
found = False
|
1410
|
+
for row in self.inline_keyboard:
|
1411
|
+
for button in row[:]:
|
1412
|
+
if button.get('text') == text:
|
1413
|
+
row.remove(button)
|
1414
|
+
found = True
|
1415
|
+
self.cleanup_empty_rows()
|
1416
|
+
return found
|
1417
|
+
|
1418
|
+
@property
|
1419
|
+
def keyboard(self) -> dict:
|
1420
|
+
return {"inline_keyboard": self.inline_keyboard}
|
1421
|
+
|
1422
|
+
|
1423
|
+
class InputMedia:
|
1424
|
+
"""Base class for input media types"""
|
1425
|
+
|
1426
|
+
def __init__(self, media: str, caption: str = None):
|
1427
|
+
self.media = media
|
1428
|
+
self.caption = caption
|
1429
|
+
|
1430
|
+
@property
|
1431
|
+
def media_dict(self) -> dict:
|
1432
|
+
media_dict = {
|
1433
|
+
'media': self.media,
|
1434
|
+
'type': self.type
|
1435
|
+
}
|
1436
|
+
if self.caption:
|
1437
|
+
media_dict['caption'] = self.caption
|
1438
|
+
return media_dict
|
1439
|
+
|
1440
|
+
|
1441
|
+
class InputMediaPhoto(InputMedia):
|
1442
|
+
"""Represents a photo to be sent"""
|
1443
|
+
type = 'photo'
|
1444
|
+
|
1445
|
+
|
1446
|
+
class InputMediaVideo(InputMedia):
|
1447
|
+
"""Represents a video to be sent"""
|
1448
|
+
type = 'video'
|
1449
|
+
|
1450
|
+
def __init__(self, media: str, caption: str = None, width: int = None,
|
1451
|
+
height: int = None, duration: int = None):
|
1452
|
+
super().__init__(media, caption)
|
1453
|
+
self.width = width
|
1454
|
+
self.height = height
|
1455
|
+
self.duration = duration
|
1456
|
+
|
1457
|
+
@property
|
1458
|
+
def media_dict(self) -> dict:
|
1459
|
+
media_dict = super().media_dict
|
1460
|
+
if self.width:
|
1461
|
+
media_dict['width'] = self.width
|
1462
|
+
if self.height:
|
1463
|
+
media_dict['height'] = self.height
|
1464
|
+
if self.duration:
|
1465
|
+
media_dict['duration'] = self.duration
|
1466
|
+
return media_dict
|
1467
|
+
|
1468
|
+
|
1469
|
+
class InputMediaAnimation(InputMedia):
|
1470
|
+
"""Represents an animation to be sent"""
|
1471
|
+
type = 'animation'
|
1472
|
+
|
1473
|
+
def __init__(self, media: str, caption: str = None, width: int = None,
|
1474
|
+
height: int = None, duration: int = None):
|
1475
|
+
super().__init__(media, caption)
|
1476
|
+
self.width = width
|
1477
|
+
self.height = height
|
1478
|
+
self.duration = duration
|
1479
|
+
|
1480
|
+
@property
|
1481
|
+
def media_dict(self) -> dict:
|
1482
|
+
media_dict = super().media_dict
|
1483
|
+
if self.width:
|
1484
|
+
media_dict['width'] = self.width
|
1485
|
+
if self.height:
|
1486
|
+
media_dict['height'] = self.height
|
1487
|
+
if self.duration:
|
1488
|
+
media_dict['duration'] = self.duration
|
1489
|
+
return media_dict
|
1490
|
+
|
1491
|
+
|
1492
|
+
class InputFile:
|
1493
|
+
"""Represents a file to be sent"""
|
1494
|
+
|
1495
|
+
def __init__(self, file: Union[str, bytes] = None, file_id: str = None):
|
1496
|
+
if file and file_id:
|
1497
|
+
raise ValueError(
|
1498
|
+
"Either file or file_id should be provided, not both")
|
1499
|
+
elif not file and not file_id:
|
1500
|
+
raise ValueError("Either file or file_id must be provided")
|
1501
|
+
|
1502
|
+
self.file = file
|
1503
|
+
self.file_id = file_id
|
1504
|
+
|
1505
|
+
@property
|
1506
|
+
def file_type(self) -> str:
|
1507
|
+
if self.file_id:
|
1508
|
+
return "id"
|
1509
|
+
if isinstance(self.file, bytes):
|
1510
|
+
return "bytes"
|
1511
|
+
if self.file.startswith(('http://', 'https://')):
|
1512
|
+
return "url"
|
1513
|
+
return "path"
|
1514
|
+
|
1515
|
+
def __str__(self) -> str:
|
1516
|
+
if self.file_id:
|
1517
|
+
return self.file_id
|
1518
|
+
return str(self.file)
|
1519
|
+
|
1520
|
+
|
1521
|
+
class InputMediaAudio(InputMedia):
|
1522
|
+
"""Represents an audio file to be sent"""
|
1523
|
+
type = 'audio'
|
1524
|
+
|
1525
|
+
def __init__(self, media: str, caption: str = None, duration: int = None,
|
1526
|
+
performer: str = None, title: str = None):
|
1527
|
+
super().__init__(media, caption)
|
1528
|
+
self.duration = duration
|
1529
|
+
self.performer = performer
|
1530
|
+
self.title = title
|
1531
|
+
|
1532
|
+
@property
|
1533
|
+
def media_dict(self) -> dict:
|
1534
|
+
media_dict = super().media_dict
|
1535
|
+
if self.duration:
|
1536
|
+
media_dict['duration'] = self.duration
|
1537
|
+
if self.performer:
|
1538
|
+
media_dict['performer'] = self.performer
|
1539
|
+
if self.title:
|
1540
|
+
media_dict['title'] = self.title
|
1541
|
+
return media_dict
|
1542
|
+
|
1543
|
+
|
1544
|
+
class InputMediaDocument(InputMedia):
|
1545
|
+
"""Represents a document to be sent"""
|
1546
|
+
type = 'document'
|
1547
|
+
|
1548
|
+
|
1549
|
+
class CallbackQuery:
|
1550
|
+
|
1551
|
+
"""Represents a callback query from a callback button"""
|
1552
|
+
|
1553
|
+
def __init__(self, client: 'Client', data: Dict[str, Any]):
|
1554
|
+
if not data.get('ok'):
|
1555
|
+
raise BaleException(
|
1556
|
+
f"API request failed: {traceback.format_exc()}")
|
1557
|
+
self.client = client
|
1558
|
+
result = data.get('result', {})
|
1559
|
+
self.id = result.get('id')
|
1560
|
+
self.from_user = self.user = self.author = User(
|
1561
|
+
client, {'ok': True, 'result': result.get('from', {})})
|
1562
|
+
self.message = Message(
|
1563
|
+
client, {
|
1564
|
+
'ok': True, 'result': result.get(
|
1565
|
+
'message', {})})
|
1566
|
+
self.inline_message_id = result.get('inline_message_id')
|
1567
|
+
self.chat_instance = result.get('chat_instance')
|
1568
|
+
self.data = result.get('data')
|
1569
|
+
self.chat = self.message.chat
|
1570
|
+
|
1571
|
+
def answer(self,
|
1572
|
+
text: str,
|
1573
|
+
reply_markup: Optional[Union['MenuKeyboardMarkup',
|
1574
|
+
'InlineKeyboardMarkup']] = None) -> 'Message':
|
1575
|
+
return self.client.send_message(
|
1576
|
+
chat_id=self.message.chat.id,
|
1577
|
+
text=text,
|
1578
|
+
reply_markup=reply_markup)
|
1579
|
+
|
1580
|
+
def reply(self,
|
1581
|
+
text: str,
|
1582
|
+
reply_markup: Optional[Union['MenuKeyboardMarkup',
|
1583
|
+
'InlineKeyboardMarkup']] = None) -> 'Message':
|
1584
|
+
return self.client.send_message(
|
1585
|
+
chat_id=self.message.chat.id,
|
1586
|
+
text=text,
|
1587
|
+
reply_markup=reply_markup,
|
1588
|
+
reply_to_message=self.message.id)
|
1589
|
+
|
1590
|
+
class Client:
|
1591
|
+
"""Main client class for interacting with Bale API"""
|
1592
|
+
|
1593
|
+
def __init__(
|
1594
|
+
self,
|
1595
|
+
token: str,
|
1596
|
+
session: str = 'https://tapi.bale.ai',
|
1597
|
+
database_name='database.db',
|
1598
|
+
auto_log_start_message: bool = True,
|
1599
|
+
):
|
1600
|
+
self.token = token
|
1601
|
+
self.session = session
|
1602
|
+
self.states = {}
|
1603
|
+
self.database_name = database_name
|
1604
|
+
self.auto_log_start_message = auto_log_start_message
|
1605
|
+
self._base_url = f"{session}/bot{token}"
|
1606
|
+
self._file_url = f"{session}/file/bot{token}"
|
1607
|
+
self._session = requests.Session()
|
1608
|
+
self._message_handler = None
|
1609
|
+
self._message_edit_handler = None
|
1610
|
+
self._callback_handler = None
|
1611
|
+
self._member_leave_handler = None
|
1612
|
+
self._member_join_handler = None
|
1613
|
+
self._threads = []
|
1614
|
+
self._polling = False
|
1615
|
+
self.user = None
|
1616
|
+
|
1617
|
+
def set_state(self,
|
1618
|
+
chat_or_user_id: Union[Chat,
|
1619
|
+
User,
|
1620
|
+
int,
|
1621
|
+
str],
|
1622
|
+
state: str) -> None:
|
1623
|
+
"""Set the state for a chat or user"""
|
1624
|
+
if isinstance(chat_or_user_id, (Chat, User)):
|
1625
|
+
chat_or_user_id = chat_or_user_id.id
|
1626
|
+
self.states[str(chat_or_user_id)] = state
|
1627
|
+
|
1628
|
+
def get_state(self,
|
1629
|
+
chat_or_user_id: Union[Chat,
|
1630
|
+
User,
|
1631
|
+
int,
|
1632
|
+
str]) -> str | None:
|
1633
|
+
"""Get the state for a chat or user"""
|
1634
|
+
if isinstance(chat_or_user_id, (Chat, User)):
|
1635
|
+
chat_or_user_id = chat_or_user_id.id
|
1636
|
+
return self.states.get(str(chat_or_user_id))
|
1637
|
+
|
1638
|
+
def del_state(self, chat_or_user_id: Union[Chat, User, int, str]) -> None:
|
1639
|
+
"""Delete the state for a chat or user"""
|
1640
|
+
if isinstance(chat_or_user_id, (Chat, User)):
|
1641
|
+
chat_or_user_id = chat_or_user_id.id
|
1642
|
+
self.states.pop(str(chat_or_user_id), None)
|
1643
|
+
|
1644
|
+
@property
|
1645
|
+
def database(self) -> DataBase:
|
1646
|
+
"""Get the database name"""
|
1647
|
+
db = DataBase(self.database_name)
|
1648
|
+
return db
|
1649
|
+
|
1650
|
+
def get_chat(self, chat_id: int) -> Optional[Dict]:
|
1651
|
+
"""Get chat information from database"""
|
1652
|
+
conn = sqlite3.connect(self.database)
|
1653
|
+
cursor = conn.cursor()
|
1654
|
+
cursor.execute('SELECT * FROM chats WHERE chat_id = ?', (chat_id,))
|
1655
|
+
chat = cursor.fetchone()
|
1656
|
+
conn.close()
|
1657
|
+
if chat:
|
1658
|
+
return {
|
1659
|
+
'chat_id': chat[0],
|
1660
|
+
'type': chat[1],
|
1661
|
+
'title': chat[2],
|
1662
|
+
'created_at': chat[3]
|
1663
|
+
}
|
1664
|
+
return None
|
1665
|
+
|
1666
|
+
def _make_request(self, method: str, endpoint: str,
|
1667
|
+
**kwargs) -> Dict[str, Any]:
|
1668
|
+
"""Make an HTTP request to Bale API"""
|
1669
|
+
url = f"{self._base_url}/{endpoint}"
|
1670
|
+
response = self._session.request(method, url, **kwargs)
|
1671
|
+
response_data = response.json()
|
1672
|
+
if not response_data.get('ok'):
|
1673
|
+
raise BaleException(
|
1674
|
+
response_data['error_code'],
|
1675
|
+
response_data['description'])
|
1676
|
+
return response_data
|
1677
|
+
|
1678
|
+
def __del__(self):
|
1679
|
+
if hasattr(self, '_session'):
|
1680
|
+
self._session.close()
|
1681
|
+
|
1682
|
+
def get_me(self) -> User:
|
1683
|
+
"""Get information about the bot"""
|
1684
|
+
data = self._make_request('GET', 'getMe')
|
1685
|
+
return User(self, data)
|
1686
|
+
|
1687
|
+
def get_file(self, file_id: str) -> bytes:
|
1688
|
+
"""Get file information from Bale API"""
|
1689
|
+
data = {
|
1690
|
+
'file_id': file_id
|
1691
|
+
}
|
1692
|
+
response = self._make_request('POST', 'getFile', json=data)
|
1693
|
+
file_path = response['result']['file_path']
|
1694
|
+
url = f"{self._file_url}/{file_path}"
|
1695
|
+
file_response = self._session.get(url)
|
1696
|
+
return file_response.content
|
1697
|
+
|
1698
|
+
def set_webhook(self, url: str, certificate: Optional[str] = None,
|
1699
|
+
max_connections: Optional[int] = None) -> bool:
|
1700
|
+
"""Set webhook for getting updates"""
|
1701
|
+
data = {
|
1702
|
+
'url': url,
|
1703
|
+
'certificate': certificate,
|
1704
|
+
'max_connections': max_connections
|
1705
|
+
}
|
1706
|
+
return self._make_request('POST', 'setWebhook', json=data)
|
1707
|
+
|
1708
|
+
def get_webhook_info(self) -> Dict[str, Any]:
|
1709
|
+
"""Get current webhook status"""
|
1710
|
+
return self._make_request('GET', 'getWebhookInfo')
|
1711
|
+
|
1712
|
+
def send_message(self,
|
1713
|
+
chat_id: Union[int,
|
1714
|
+
str],
|
1715
|
+
text: str,
|
1716
|
+
parse_mode: Optional[str] = None,
|
1717
|
+
reply_markup: Union[MenuKeyboardMarkup,
|
1718
|
+
InlineKeyboardMarkup] = None,
|
1719
|
+
reply_to_message: Union[Message,
|
1720
|
+
int,
|
1721
|
+
str] = None) -> Message:
|
1722
|
+
"""Send a message to a chat"""
|
1723
|
+
|
1724
|
+
text = str(text)
|
1725
|
+
|
1726
|
+
data = {
|
1727
|
+
'chat_id': chat_id,
|
1728
|
+
'text': text,
|
1729
|
+
'parse_mode': parse_mode,
|
1730
|
+
'reply_markup': reply_markup.keyboard if reply_markup else None,
|
1731
|
+
'reply_to_message_id': reply_to_message.message_id if isinstance(
|
1732
|
+
reply_to_message,
|
1733
|
+
Message) else reply_to_message}
|
1734
|
+
response = self._make_request('POST', 'sendMessage', json=data)
|
1735
|
+
return Message(self, response)
|
1736
|
+
|
1737
|
+
def forward_message(self, chat_id: Union[int, str],
|
1738
|
+
from_chat_id: Union[int, str],
|
1739
|
+
message_id: int) -> Message:
|
1740
|
+
"""Forward a message from one chat to another"""
|
1741
|
+
data = {
|
1742
|
+
'chat_id': chat_id,
|
1743
|
+
'from_chat_id': from_chat_id,
|
1744
|
+
'message_id': message_id
|
1745
|
+
}
|
1746
|
+
response = self._make_request('POST', 'forwardMessage', json=data)
|
1747
|
+
return Message(self, response)
|
1748
|
+
|
1749
|
+
def send_photo(self,
|
1750
|
+
chat_id: Union[int,
|
1751
|
+
str],
|
1752
|
+
photo: Union[str,
|
1753
|
+
bytes,
|
1754
|
+
InputFile],
|
1755
|
+
caption: Optional[str] = None,
|
1756
|
+
parse_mode: Optional[str] = None,
|
1757
|
+
reply_markup: Union[MenuKeyboardMarkup,
|
1758
|
+
InlineKeyboardMarkup] = None,
|
1759
|
+
reply_to_message: Union[Message,
|
1760
|
+
int,
|
1761
|
+
str] = None) -> Message:
|
1762
|
+
"""Send a photo to a chat"""
|
1763
|
+
files = None
|
1764
|
+
data = {
|
1765
|
+
'chat_id': chat_id,
|
1766
|
+
'caption': caption,
|
1767
|
+
'parse_mode': parse_mode,
|
1768
|
+
'reply_markup': reply_markup.keyboard if reply_markup else None,
|
1769
|
+
'reply_to_message_id': reply_to_message.message_id if isinstance(
|
1770
|
+
reply_to_message,
|
1771
|
+
Message) else reply_to_message}
|
1772
|
+
|
1773
|
+
if isinstance(photo, (bytes, InputFile)) or hasattr(photo, 'read'):
|
1774
|
+
files = {
|
1775
|
+
'photo': photo if not isinstance(
|
1776
|
+
photo, InputFile) else photo.file}
|
1777
|
+
else:
|
1778
|
+
data['photo'] = photo
|
1779
|
+
|
1780
|
+
response = self._make_request(
|
1781
|
+
'POST', 'sendPhoto', data=data, files=files)
|
1782
|
+
return Message(self, response)
|
1783
|
+
|
1784
|
+
def delete_message(self, chat_id: Union[int, str],
|
1785
|
+
message_id: int) -> bool:
|
1786
|
+
"""Delete a message from a chat"""
|
1787
|
+
data = {
|
1788
|
+
'chat_id': chat_id,
|
1789
|
+
'message_id': message_id
|
1790
|
+
}
|
1791
|
+
return self._make_request('POST', 'deleteMessage', json=data)
|
1792
|
+
|
1793
|
+
def get_user(self, user_id: Union[int, str]) -> User:
|
1794
|
+
"""Get information about a user"""
|
1795
|
+
data = self._make_request('POST', 'getChat', json={'chat_id': user_id})
|
1796
|
+
return User(self, data)
|
1797
|
+
|
1798
|
+
def edit_message(self,
|
1799
|
+
chat_id: Union[int,
|
1800
|
+
str],
|
1801
|
+
message_id: int,
|
1802
|
+
text: str,
|
1803
|
+
parse_mode: Optional[str] = None,
|
1804
|
+
reply_markup: Union[MenuKeyboardMarkup,
|
1805
|
+
InlineKeyboardMarkup] = None) -> Message:
|
1806
|
+
"""Edit a message in a chat"""
|
1807
|
+
data = {
|
1808
|
+
'chat_id': chat_id,
|
1809
|
+
'message_id': message_id,
|
1810
|
+
'text': text,
|
1811
|
+
'parse_mode': parse_mode,
|
1812
|
+
'reply_markup': reply_markup.keyboard if reply_markup else None
|
1813
|
+
}
|
1814
|
+
response = self._make_request('POST', 'editMessageText', json=data)
|
1815
|
+
return Message(self, response)
|
1816
|
+
|
1817
|
+
def get_chat(self, chat_id: Union[int, str]) -> Chat:
|
1818
|
+
"""Get information about a chat"""
|
1819
|
+
data = self._make_request('POST', 'getChat', json={'chat_id': chat_id})
|
1820
|
+
return Chat(self, data)
|
1821
|
+
|
1822
|
+
def send_audio(self,
|
1823
|
+
chat_id: Union[int,
|
1824
|
+
str],
|
1825
|
+
audio,
|
1826
|
+
caption: Optional[str] = None,
|
1827
|
+
parse_mode: Optional[str] = None,
|
1828
|
+
reply_markup: Union[MenuKeyboardMarkup,
|
1829
|
+
InlineKeyboardMarkup] = None,
|
1830
|
+
reply_to_message: Union[Message,
|
1831
|
+
int,
|
1832
|
+
str] = None) -> Message:
|
1833
|
+
"""Send an audio file"""
|
1834
|
+
files = None
|
1835
|
+
data = {
|
1836
|
+
'chat_id': chat_id,
|
1837
|
+
'caption': caption,
|
1838
|
+
'parse_mode': parse_mode,
|
1839
|
+
'reply_markup': reply_markup.keyboard if reply_markup else None,
|
1840
|
+
'reply_to_message_id': reply_to_message.message_id if isinstance(
|
1841
|
+
reply_to_message,
|
1842
|
+
Message) else reply_to_message}
|
1843
|
+
if isinstance(audio, (bytes, InputFile)) or hasattr(audio, 'read'):
|
1844
|
+
files = {
|
1845
|
+
'audio': audio if not isinstance(
|
1846
|
+
audio, InputFile) else audio.file}
|
1847
|
+
else:
|
1848
|
+
data['audio'] = audio
|
1849
|
+
|
1850
|
+
response = self._make_request(
|
1851
|
+
'POST', 'sendAudio', data=data, files=files)
|
1852
|
+
return Message(self, response)
|
1853
|
+
|
1854
|
+
def send_document(self,
|
1855
|
+
chat_id: Union[int,
|
1856
|
+
str],
|
1857
|
+
document,
|
1858
|
+
caption: Optional[str] = None,
|
1859
|
+
parse_mode: Optional[str] = None,
|
1860
|
+
reply_markup: Union[MenuKeyboardMarkup,
|
1861
|
+
InlineKeyboardMarkup] = None,
|
1862
|
+
reply_to_message: Union[Message,
|
1863
|
+
int,
|
1864
|
+
str] = None) -> Message:
|
1865
|
+
"""Send a document"""
|
1866
|
+
files = None
|
1867
|
+
data = {
|
1868
|
+
'chat_id': chat_id,
|
1869
|
+
'caption': caption,
|
1870
|
+
'parse_mode': parse_mode,
|
1871
|
+
'reply_markup': reply_markup.keyboard if reply_markup else None,
|
1872
|
+
'reply_to_message_id': reply_to_message.message_id if isinstance(
|
1873
|
+
reply_to_message,
|
1874
|
+
Message) else reply_to_message}
|
1875
|
+
|
1876
|
+
if isinstance(
|
1877
|
+
document, (bytes, InputFile)) or hasattr(
|
1878
|
+
document, 'read'):
|
1879
|
+
files = {'document': document if not isinstance(
|
1880
|
+
document, InputFile) else document.file}
|
1881
|
+
else:
|
1882
|
+
data['document'] = document
|
1883
|
+
|
1884
|
+
response = self._make_request(
|
1885
|
+
'POST', 'sendDocument', data=data, files=files)
|
1886
|
+
return Message(self, response)
|
1887
|
+
|
1888
|
+
def send_video(self,
|
1889
|
+
chat_id: Union[int,
|
1890
|
+
str],
|
1891
|
+
video,
|
1892
|
+
caption: Optional[str] = None,
|
1893
|
+
parse_mode: Optional[str] = None,
|
1894
|
+
reply_markup: Union[MenuKeyboardMarkup,
|
1895
|
+
InlineKeyboardMarkup] = None,
|
1896
|
+
reply_to_message: Union[Message,
|
1897
|
+
int,
|
1898
|
+
str] = None) -> Message:
|
1899
|
+
"""Send a video"""
|
1900
|
+
files = None
|
1901
|
+
data = {
|
1902
|
+
'chat_id': chat_id,
|
1903
|
+
'caption': caption,
|
1904
|
+
'parse_mode': parse_mode,
|
1905
|
+
'reply_markup': reply_markup.keyboard if reply_markup else None,
|
1906
|
+
'reply_to_message_id': reply_to_message.message_id if isinstance(
|
1907
|
+
reply_to_message,
|
1908
|
+
Message) else reply_to_message}
|
1909
|
+
|
1910
|
+
if isinstance(video, (bytes, InputFile)) or hasattr(video, 'read'):
|
1911
|
+
files = {
|
1912
|
+
'video': video if not isinstance(
|
1913
|
+
video, InputFile) else video.file}
|
1914
|
+
else:
|
1915
|
+
data['video'] = video
|
1916
|
+
|
1917
|
+
response = self._make_request(
|
1918
|
+
'POST', 'sendVideo', data=data, files=files)
|
1919
|
+
return Message(self, response)
|
1920
|
+
|
1921
|
+
def send_animation(self,
|
1922
|
+
chat_id: Union[int,
|
1923
|
+
str],
|
1924
|
+
animation,
|
1925
|
+
caption: Optional[str] = None,
|
1926
|
+
parse_mode: Optional[str] = None,
|
1927
|
+
reply_markup: Union[MenuKeyboardMarkup,
|
1928
|
+
InlineKeyboardMarkup] = None,
|
1929
|
+
reply_to_message: Union[Message,
|
1930
|
+
int,
|
1931
|
+
str] = None) -> Message:
|
1932
|
+
"""Send an animation (GIF or H.264/MPEG-4 AVC video without sound)"""
|
1933
|
+
files = None
|
1934
|
+
data = {
|
1935
|
+
'chat_id': chat_id,
|
1936
|
+
'caption': caption,
|
1937
|
+
'parse_mode': parse_mode,
|
1938
|
+
'reply_markup': reply_markup.keyboard if reply_markup else None,
|
1939
|
+
'reply_to_message_id': reply_to_message.message_id if isinstance(
|
1940
|
+
reply_to_message,
|
1941
|
+
Message) else reply_to_message}
|
1942
|
+
|
1943
|
+
if isinstance(
|
1944
|
+
animation, (bytes, InputFile)) or hasattr(
|
1945
|
+
animation, 'read'):
|
1946
|
+
files = {'animation': animation if not isinstance(
|
1947
|
+
animation, InputFile) else animation.file}
|
1948
|
+
else:
|
1949
|
+
data['animation'] = animation
|
1950
|
+
|
1951
|
+
response = self._make_request(
|
1952
|
+
'POST', 'sendAnimation', data=data, files=files)
|
1953
|
+
return Message(self, response)
|
1954
|
+
|
1955
|
+
def send_voice(self,
|
1956
|
+
chat_id: Union[int,
|
1957
|
+
str],
|
1958
|
+
voice,
|
1959
|
+
caption: Optional[str] = None,
|
1960
|
+
parse_mode: Optional[str] = None,
|
1961
|
+
reply_markup: Union[MenuKeyboardMarkup,
|
1962
|
+
InlineKeyboardMarkup] = None,
|
1963
|
+
reply_to_message: Union[Message,
|
1964
|
+
int,
|
1965
|
+
str] = None) -> Message:
|
1966
|
+
"""Send a voice message"""
|
1967
|
+
files = None
|
1968
|
+
data = {
|
1969
|
+
'chat_id': chat_id,
|
1970
|
+
'caption': caption,
|
1971
|
+
'parse_mode': parse_mode,
|
1972
|
+
'reply_markup': reply_markup.keyboard if reply_markup else None,
|
1973
|
+
'reply_to_message_id': reply_to_message.message_id if isinstance(
|
1974
|
+
reply_to_message,
|
1975
|
+
Message) else reply_to_message}
|
1976
|
+
|
1977
|
+
if isinstance(voice, (bytes, InputFile)) or hasattr(voice, 'read'):
|
1978
|
+
files = {
|
1979
|
+
'voice': voice if not isinstance(
|
1980
|
+
voice, InputFile) else voice.file}
|
1981
|
+
else:
|
1982
|
+
data['voice'] = voice
|
1983
|
+
|
1984
|
+
response = self._make_request(
|
1985
|
+
'POST', 'sendVoice', data=data, files=files)
|
1986
|
+
return Message(self, response)
|
1987
|
+
|
1988
|
+
def send_media_group(self,
|
1989
|
+
chat_id: Union[int,
|
1990
|
+
str],
|
1991
|
+
media: List[Dict],
|
1992
|
+
reply_to_message: Union[Message,
|
1993
|
+
int,
|
1994
|
+
str] = None) -> List[Message]:
|
1995
|
+
"""Send a group of photos, videos, documents or audios as an album"""
|
1996
|
+
data = {
|
1997
|
+
'chat_id': chat_id,
|
1998
|
+
'media': media,
|
1999
|
+
'reply_to_message_id': reply_to_message.message_id if isinstance(
|
2000
|
+
reply_to_message,
|
2001
|
+
Message) else reply_to_message}
|
2002
|
+
response = self._make_request('POST', 'sendMediaGroup', json=data)
|
2003
|
+
return [Message(self, msg) for msg in response]
|
2004
|
+
|
2005
|
+
def send_location(self,
|
2006
|
+
chat_id: Union[int,
|
2007
|
+
str],
|
2008
|
+
latitude: float,
|
2009
|
+
longitude: float,
|
2010
|
+
reply_markup: Union[MenuKeyboardMarkup,
|
2011
|
+
InlineKeyboardMarkup] = None,
|
2012
|
+
reply_to_message: Union[Message,
|
2013
|
+
int,
|
2014
|
+
str] = None) -> Message:
|
2015
|
+
"""Send a point on the map"""
|
2016
|
+
data = {
|
2017
|
+
'chat_id': chat_id,
|
2018
|
+
'latitude': latitude,
|
2019
|
+
'longitude': longitude,
|
2020
|
+
'reply_markup': reply_markup.keyboard if reply_markup else None,
|
2021
|
+
'reply_to_message_id': reply_to_message.message_id if isinstance(
|
2022
|
+
reply_to_message,
|
2023
|
+
Message) else reply_to_message}
|
2024
|
+
response = self._make_request('POST', 'sendLocation', json=data)
|
2025
|
+
return Message(self, response)
|
2026
|
+
|
2027
|
+
def send_contact(self,
|
2028
|
+
chat_id: Union[int,
|
2029
|
+
str],
|
2030
|
+
phone_number: str,
|
2031
|
+
first_name: str,
|
2032
|
+
last_name: Optional[str] = None,
|
2033
|
+
reply_markup: Union[MenuKeyboardMarkup,
|
2034
|
+
InlineKeyboardMarkup] = None,
|
2035
|
+
reply_to_message: Union[Message,
|
2036
|
+
int,
|
2037
|
+
str] = None) -> Message:
|
2038
|
+
"""Send a phone contact"""
|
2039
|
+
data = {
|
2040
|
+
'chat_id': chat_id,
|
2041
|
+
'phone_number': phone_number,
|
2042
|
+
'first_name': first_name,
|
2043
|
+
'last_name': last_name,
|
2044
|
+
'reply_markup': reply_markup.keyboard if reply_markup else None,
|
2045
|
+
'reply_to_message_id': reply_to_message.message_id if isinstance(
|
2046
|
+
reply_to_message,
|
2047
|
+
Message) else reply_to_message}
|
2048
|
+
response = self._make_request('POST', 'sendContact', json=data)
|
2049
|
+
return Message(self, response)
|
2050
|
+
|
2051
|
+
def send_invoice(self,
|
2052
|
+
chat_id: Union[int,
|
2053
|
+
str],
|
2054
|
+
title: str,
|
2055
|
+
description: str,
|
2056
|
+
payload: str,
|
2057
|
+
provider_token: str,
|
2058
|
+
prices: list,
|
2059
|
+
photo_url: Optional[str] = None,
|
2060
|
+
reply_to_message: Union[int | str | Message] = None,
|
2061
|
+
reply_markup: Union[MenuKeyboardMarkup | InlineKeyboardMarkup] = None) -> Message:
|
2062
|
+
"""Send a invoice"""
|
2063
|
+
r = []
|
2064
|
+
for x in prices:
|
2065
|
+
r.append(x.json)
|
2066
|
+
prices = r
|
2067
|
+
data = {
|
2068
|
+
'chat_id': chat_id,
|
2069
|
+
'title': title,
|
2070
|
+
'description': description,
|
2071
|
+
'payload': payload,
|
2072
|
+
'provider_token': provider_token,
|
2073
|
+
'prices': prices,
|
2074
|
+
'photo_url': photo_url,
|
2075
|
+
'reply_to_message_id': reply_to_message.message_id if isinstance(
|
2076
|
+
reply_to_message,
|
2077
|
+
Message) else reply_to_message,
|
2078
|
+
'reply_markup': reply_markup.keyboard if reply_markup else None}
|
2079
|
+
response = self._make_request('POST', 'sendInvoice', json=data)
|
2080
|
+
return Message(self, response)
|
2081
|
+
|
2082
|
+
def send_chat_action(self,
|
2083
|
+
chat: Union[int,
|
2084
|
+
str,
|
2085
|
+
'Chat'],
|
2086
|
+
action: str,
|
2087
|
+
how_many_times: int = 1) -> bool:
|
2088
|
+
"""Send a chat action"""
|
2089
|
+
if not chat:
|
2090
|
+
raise ValueError("Chat ID cannot be empty")
|
2091
|
+
|
2092
|
+
data = {
|
2093
|
+
'chat_id': str(chat) if isinstance(
|
2094
|
+
chat, (int, str)) else str(
|
2095
|
+
chat.id), 'action': action}
|
2096
|
+
res = []
|
2097
|
+
for _ in range(how_many_times):
|
2098
|
+
response = self._make_request('POST', 'sendChatAction', json=data)
|
2099
|
+
res.append(response.get('ok', False))
|
2100
|
+
return all(res)
|
2101
|
+
|
2102
|
+
def copy_message(self,
|
2103
|
+
chat_id: Union[int,
|
2104
|
+
str,
|
2105
|
+
'Chat'],
|
2106
|
+
from_chat_id: Union[int,
|
2107
|
+
str,
|
2108
|
+
'Chat'],
|
2109
|
+
message_id: Union[int,
|
2110
|
+
str,
|
2111
|
+
'Chat']):
|
2112
|
+
data = {
|
2113
|
+
'chat_id': chat_id if isinstance(
|
2114
|
+
chat_id, (int, str)) else chat_id.id, 'from_chat_id': from_chat_id if isinstance(
|
2115
|
+
from_chat_id, (int, str)) else from_chat_id.id, 'message_id': message_id if isinstance(
|
2116
|
+
message_id, (int, str)) else message_id.id}
|
2117
|
+
response = self._make_request('POST', 'copyMessage', json=data)
|
2118
|
+
return Message(self, response)
|
2119
|
+
|
2120
|
+
def get_chat_member(self,
|
2121
|
+
chat: Union[int,
|
2122
|
+
str,
|
2123
|
+
'Chat'],
|
2124
|
+
user: Union[int,
|
2125
|
+
str,
|
2126
|
+
'User']) -> ChatMember:
|
2127
|
+
"""Get information about a member of a chat including their permissions"""
|
2128
|
+
data = {
|
2129
|
+
'chat_id': chat if isinstance(chat, (int, str)) else chat.id,
|
2130
|
+
'user_id': user if isinstance(user, (int, str)) else user.id
|
2131
|
+
}
|
2132
|
+
response = self._make_request('POST', 'getChatMember', json=data)
|
2133
|
+
return ChatMember(self, response['result'])
|
2134
|
+
|
2135
|
+
def get_chat_administrators(
|
2136
|
+
self, chat: Union[int, str, 'Chat']) -> List[ChatMember]:
|
2137
|
+
"""Get a list of administrators in a chat"""
|
2138
|
+
data = {'chat_id': getattr(chat, 'id', chat)}
|
2139
|
+
response = self._make_request(
|
2140
|
+
'POST', 'getChatAdministrators', json=data)
|
2141
|
+
return [ChatMember(self, member)
|
2142
|
+
for member in response.get('result', [])]
|
2143
|
+
|
2144
|
+
def get_chat_members_count(self, chat: Union[int, str, 'Chat']) -> int:
|
2145
|
+
"""Get the number of members in a chat"""
|
2146
|
+
data = {
|
2147
|
+
'chat_id': chat if isinstance(chat, (int, str)) else chat.id
|
2148
|
+
}
|
2149
|
+
response = self._make_request('GET', 'getChatMembersCount', json=data)
|
2150
|
+
return response['result']
|
2151
|
+
|
2152
|
+
def is_joined(self, user: Union[User, int, str],
|
2153
|
+
chat: Union[Chat, int, str]) -> bool:
|
2154
|
+
"""Check if user is a member of the chat"""
|
2155
|
+
data = {
|
2156
|
+
'chat_id': chat if isinstance(chat, (int, str)) else chat.id,
|
2157
|
+
'user_id': user if isinstance(user, (int, str)) else user.id
|
2158
|
+
}
|
2159
|
+
response = self._make_request('GET', 'getChatMember', json=data)
|
2160
|
+
return response.get('status') not in ['left', 'kicked']
|
2161
|
+
|
2162
|
+
def on_message(self, func):
|
2163
|
+
"""Decorator for handling new messages"""
|
2164
|
+
self._message_handler = func
|
2165
|
+
return func
|
2166
|
+
|
2167
|
+
def on_callback_query(self, func):
|
2168
|
+
"""Decorator for handling callback queries"""
|
2169
|
+
self._callback_handler = func
|
2170
|
+
return func
|
2171
|
+
|
2172
|
+
def on_tick(self, seconds: int):
|
2173
|
+
"""Decorator for handling periodic events"""
|
2174
|
+
def decorator(func):
|
2175
|
+
if not hasattr(self, '_tick_handlers'):
|
2176
|
+
self._tick_handlers = {}
|
2177
|
+
self._tick_handlers[func] = {
|
2178
|
+
'interval': seconds, 'last_run': time.time()}
|
2179
|
+
return func
|
2180
|
+
return decorator
|
2181
|
+
|
2182
|
+
def on_close(self, func):
|
2183
|
+
"""Decorator for handling close event"""
|
2184
|
+
self._close_handler = func
|
2185
|
+
return func
|
2186
|
+
|
2187
|
+
def on_ready(self, func):
|
2188
|
+
"""Decorator for handling ready event"""
|
2189
|
+
self._ready_handler = func
|
2190
|
+
return func
|
2191
|
+
|
2192
|
+
def on_update(self, func):
|
2193
|
+
"""Decorator for handling raw updates"""
|
2194
|
+
self._update_handler = func
|
2195
|
+
return func
|
2196
|
+
|
2197
|
+
def on_member_chat_join(self, func):
|
2198
|
+
"""Decorator for handling new chat members"""
|
2199
|
+
self._member_join_handler = func
|
2200
|
+
return func
|
2201
|
+
|
2202
|
+
def on_member_chat_leave(self, func):
|
2203
|
+
"""Decorator for handling members leaving chat"""
|
2204
|
+
self._member_leave_handler = func
|
2205
|
+
return func
|
2206
|
+
|
2207
|
+
def on_message_edit(self, func):
|
2208
|
+
"""Decorator for handling edited messages"""
|
2209
|
+
self._message_edit_handler = func
|
2210
|
+
return func
|
2211
|
+
|
2212
|
+
def on_command(self, command: str = None, case_sensitive: bool = False):
|
2213
|
+
"""Decorator for handling specific text commands"""
|
2214
|
+
def decorator(func):
|
2215
|
+
if not hasattr(self, '_text_handlers'):
|
2216
|
+
self._text_handlers = {}
|
2217
|
+
cmd = f"/{command.lstrip('/')}" if command else f"/{func.__name__}"
|
2218
|
+
self._text_handlers[cmd] = {
|
2219
|
+
'handler': func, 'case_sensitive': case_sensitive}
|
2220
|
+
return func
|
2221
|
+
return decorator
|
2222
|
+
|
2223
|
+
def _create_thread(self, handler, *args, **kwargs):
|
2224
|
+
"""Helper method to create and start a thread"""
|
2225
|
+
if handler:
|
2226
|
+
thread = threading.Thread(
|
2227
|
+
target=handler, args=args, kwargs=kwargs, daemon=True)
|
2228
|
+
thread.start()
|
2229
|
+
self._threads.append(thread)
|
2230
|
+
return thread
|
2231
|
+
return None
|
2232
|
+
|
2233
|
+
def _handle_message(self, message, update):
|
2234
|
+
"""Handle different types of messages"""
|
2235
|
+
msg_data = update.get('message', {})
|
2236
|
+
|
2237
|
+
if 'new_chat_members' in msg_data and hasattr(
|
2238
|
+
self, '_member_join_handler'):
|
2239
|
+
chat, user = msg_data['chat'], msg_data['new_chat_members'][0]
|
2240
|
+
self._create_thread(
|
2241
|
+
self._member_join_handler,
|
2242
|
+
message,
|
2243
|
+
Chat(self, {"ok": True, "result": chat}),
|
2244
|
+
User(self, {"ok": True, "result": user})
|
2245
|
+
)
|
2246
|
+
return
|
2247
|
+
|
2248
|
+
if 'left_chat_member' in msg_data and hasattr(
|
2249
|
+
self, '_member_leave_handler'):
|
2250
|
+
chat, user = msg_data['chat'], msg_data['left_chat_member']
|
2251
|
+
self._create_thread(
|
2252
|
+
self._member_leave_handler,
|
2253
|
+
message,
|
2254
|
+
Chat(self, {"ok": True, "result": chat}),
|
2255
|
+
User(self, {"ok": True, "result": user})
|
2256
|
+
)
|
2257
|
+
return
|
2258
|
+
|
2259
|
+
if 'text' in msg_data and hasattr(self, '_text_handlers'):
|
2260
|
+
text = msg_data['text']
|
2261
|
+
for command, handler_info in self._text_handlers.items():
|
2262
|
+
handler = handler_info['handler']
|
2263
|
+
case_sensitive = handler_info['case_sensitive']
|
2264
|
+
|
2265
|
+
if case_sensitive:
|
2266
|
+
matches = text.startswith(command)
|
2267
|
+
else:
|
2268
|
+
matches = text.lower().startswith(command.lower())
|
2269
|
+
|
2270
|
+
if matches:
|
2271
|
+
params = inspect.signature(handler).parameters
|
2272
|
+
args = [message]
|
2273
|
+
if len(params) > 1:
|
2274
|
+
command_args = text[len(command):].strip().split()
|
2275
|
+
args.extend(command_args)
|
2276
|
+
self._create_thread(handler, *args)
|
2277
|
+
return
|
2278
|
+
|
2279
|
+
if hasattr(self, '_message_handler'):
|
2280
|
+
params = inspect.signature(self._message_handler).parameters
|
2281
|
+
args = ((message, update) if len(params) > 1 else (message,))
|
2282
|
+
result = self._message_handler(*args)
|
2283
|
+
if isinstance(result, str):
|
2284
|
+
message.chat.send_message(result)
|
2285
|
+
|
2286
|
+
def _handle_update(self, update):
|
2287
|
+
try:
|
2288
|
+
if hasattr(self, '_update_handler'):
|
2289
|
+
self._create_thread(self._update_handler, update)
|
2290
|
+
|
2291
|
+
message_types = {
|
2292
|
+
'message': (Message, self._handle_message),
|
2293
|
+
'edited_message': (Message, lambda m, u: self._create_thread(
|
2294
|
+
self._message_edit_handler, m) if hasattr(
|
2295
|
+
self, '_message_edit_handler') else None)
|
2296
|
+
}
|
2297
|
+
|
2298
|
+
for update_type, (cls, handler) in message_types.items():
|
2299
|
+
if update_type in update:
|
2300
|
+
message = cls(
|
2301
|
+
self, {
|
2302
|
+
'ok': True, 'result': update[update_type]})
|
2303
|
+
handler(message, update)
|
2304
|
+
|
2305
|
+
if 'callback_query' in update and hasattr(
|
2306
|
+
self, '_callback_handler'):
|
2307
|
+
callback_data = update['callback_query']
|
2308
|
+
obj = CallbackQuery(
|
2309
|
+
self, {'ok': True, 'result': callback_data})
|
2310
|
+
message = Message(
|
2311
|
+
self, {
|
2312
|
+
'ok': True, 'result': callback_data['message']}) if 'message' in callback_data else None
|
2313
|
+
chat = Chat(
|
2314
|
+
self, {
|
2315
|
+
'ok': True, 'result': callback_data['message']['chat']}) if message else None
|
2316
|
+
user = User(
|
2317
|
+
self, {
|
2318
|
+
'ok': True, 'result': callback_data['from']})
|
2319
|
+
|
2320
|
+
params = inspect.signature(self._callback_handler).parameters
|
2321
|
+
args = (
|
2322
|
+
obj,
|
2323
|
+
message,
|
2324
|
+
chat,
|
2325
|
+
user) if len(params) > 1 else (
|
2326
|
+
obj,
|
2327
|
+
)
|
2328
|
+
self._create_thread(self._callback_handler, *args)
|
2329
|
+
except Exception as e:
|
2330
|
+
print(f"Error handling update: {e}")
|
2331
|
+
traceback.print_exc()
|
2332
|
+
|
2333
|
+
def _handle_tick_events(self, current_time):
|
2334
|
+
"""Handle periodic tick events"""
|
2335
|
+
if hasattr(self, '_tick_handlers'):
|
2336
|
+
for handler, info in self._tick_handlers.items():
|
2337
|
+
if current_time - info['last_run'] >= info['interval']:
|
2338
|
+
self._create_thread(handler)
|
2339
|
+
info['last_run'] = current_time
|
2340
|
+
|
2341
|
+
def run(self, debug=False):
|
2342
|
+
"""Start polling for new messages"""
|
2343
|
+
try:
|
2344
|
+
self.user = self.get_me()
|
2345
|
+
except Exception as e:
|
2346
|
+
raise BaleTokenNotFoundError(f"Token not found: {str(e)}")
|
2347
|
+
|
2348
|
+
self._polling = True
|
2349
|
+
self._threads = []
|
2350
|
+
offset = 0
|
2351
|
+
past_updates = collections.deque(maxlen=100)
|
2352
|
+
source_file = inspect.getfile(self.__class__)
|
2353
|
+
last_modified = os.path.getmtime(source_file)
|
2354
|
+
|
2355
|
+
if self.auto_log_start_message:
|
2356
|
+
print(f"-+-+-+ [logged in as @{self.get_me().username}] +-+-+-")
|
2357
|
+
if hasattr(self, '_ready_handler'):
|
2358
|
+
self._ready_handler()
|
2359
|
+
|
2360
|
+
while self._polling:
|
2361
|
+
try:
|
2362
|
+
if debug and self._check_source_file_changed(
|
2363
|
+
source_file, last_modified):
|
2364
|
+
last_modified = os.path.getmtime(source_file)
|
2365
|
+
print("Source file changed, restarting...")
|
2366
|
+
python = sys.executable
|
2367
|
+
os.execl(python, python, *sys.argv)
|
2368
|
+
|
2369
|
+
updates = self.get_updates(offset=offset, timeout=30)
|
2370
|
+
for update in updates:
|
2371
|
+
update_id = update['update_id']
|
2372
|
+
if update_id not in past_updates:
|
2373
|
+
past_updates.append(update_id)
|
2374
|
+
self._handle_update(update)
|
2375
|
+
offset = update_id + 1
|
2376
|
+
|
2377
|
+
current_time = time.time()
|
2378
|
+
self._handle_tick_events(current_time)
|
2379
|
+
self._threads = [t for t in self._threads if t.is_alive()]
|
2380
|
+
time.sleep(0.1)
|
2381
|
+
except Exception as e:
|
2382
|
+
print(f"Error in polling: {e}")
|
2383
|
+
traceback.print_exc()
|
2384
|
+
time.sleep(1)
|
2385
|
+
|
2386
|
+
def _check_source_file_changed(self, source_file, last_modified):
|
2387
|
+
"""Check if source file has been modified"""
|
2388
|
+
try:
|
2389
|
+
return os.path.getmtime(source_file) > last_modified
|
2390
|
+
except (FileNotFoundError, OSError) as e:
|
2391
|
+
print(f"Error checking file modification time: {e}")
|
2392
|
+
return False
|
2393
|
+
|
2394
|
+
def get_updates(self, offset=None, timeout=30) -> List[Dict[str, Any]]:
|
2395
|
+
"""Get updates from Bale API"""
|
2396
|
+
params = {'timeout': timeout}
|
2397
|
+
if offset is not None:
|
2398
|
+
params['offset'] = offset
|
2399
|
+
response = self._make_request('GET', 'getUpdates', params=params)
|
2400
|
+
return response.get('result', [])
|
2401
|
+
|
2402
|
+
def safe_close(self):
|
2403
|
+
"""Close the client and stop polling gracefully"""
|
2404
|
+
self._polling = False
|
2405
|
+
for thread in self._threads:
|
2406
|
+
try:
|
2407
|
+
thread.join(timeout=1.0)
|
2408
|
+
except Exception as e:
|
2409
|
+
print(f"Error joining thread: {e}")
|
2410
|
+
self._threads.clear()
|
2411
|
+
if hasattr(self, '_close_handler'):
|
2412
|
+
try:
|
2413
|
+
self._close_handler()
|
2414
|
+
except Exception as e:
|
2415
|
+
print(f"Error in close handler: {e}")
|
2416
|
+
|
2417
|
+
def create_ref_link(self, data: str) -> str:
|
2418
|
+
"""Create a reference link for the bot"""
|
2419
|
+
return f"https://ble.ir/{self.get_me().username}?start={data}"
|
2420
|
+
|
2421
|
+
def run_multiple_bots(bots: List[Client]) -> List[Client]:
|
2422
|
+
"""
|
2423
|
+
Run multiple bots concurrently in separate threads.
|
2424
|
+
|
2425
|
+
Args:
|
2426
|
+
bots: List of Client instances to run
|
2427
|
+
|
2428
|
+
Returns:
|
2429
|
+
List of running bot instances
|
2430
|
+
"""
|
2431
|
+
threads = []
|
2432
|
+
for bot in bots:
|
2433
|
+
thread = threading.Thread(target=bot.run, daemon=True)
|
2434
|
+
thread.start()
|
2435
|
+
threads.append(thread)
|
2436
|
+
|
2437
|
+
for thread in threads:
|
2438
|
+
thread.join()
|
2439
|
+
|
2440
|
+
return bots
|
2441
|
+
|
2442
|
+
|
2443
|
+
def stop_bots(bots: List[Client]) -> None:
|
2444
|
+
"""
|
2445
|
+
Stop multiple bots gracefully.
|
2446
|
+
|
2447
|
+
Args:
|
2448
|
+
bots: List of Client instances to stop
|
2449
|
+
"""
|
2450
|
+
for bot in bots:
|
2451
|
+
try:
|
2452
|
+
bot.safe_close()
|
2453
|
+
except Exception as e:
|
2454
|
+
continue
|