reykit 1.0.1__py3-none-any.whl → 1.1.0__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.
reywechat/rreceive.py DELETED
@@ -1,1232 +0,0 @@
1
- # !/usr/bin/env python
2
- # -*- coding: utf-8 -*-
3
-
4
- """
5
- @Time : 2023-10-26 11:18:58
6
- @Author : Rey
7
- @Contact : reyxbo@163.com
8
- @Explain : Receive methods.
9
- """
10
-
11
-
12
- from __future__ import annotations
13
- from typing import Any, TypedDict, Literal, Optional, NoReturn, overload
14
- from collections.abc import Callable
15
- from queue import Queue
16
- from json import loads as json_loads
17
- from bs4 import BeautifulSoup as BSBeautifulSoup
18
- from bs4.element import Tag as BSTag
19
- from reykit.rcomm import get_file_stream_time, listen_socket
20
- from reykit.rexception import throw, catch_exc
21
- from reykit.rimage import decode_qrcode
22
- from reykit.ros import RFile, RFolder, os_exists
23
- from reykit.rregex import search
24
- from reykit.rtime import sleep, wait
25
- from reykit.rwrap import wrap_thread, wrap_exc
26
- from reykit.rmultitask import RThreadPool
27
-
28
- from .rexception import RWeChatExecuteNoRuleReplyError, RWeChatExecuteTriggerReplyError
29
- from .rwechat import RWeChat
30
-
31
-
32
- __all__ = (
33
- 'RMessage',
34
- 'RReceive'
35
- )
36
-
37
-
38
- MessageParameters = TypedDict(
39
- 'MessageParameters',
40
- {
41
- 'time': int,
42
- 'id': int,
43
- 'number': int,
44
- 'room': Optional[str],
45
- 'user': Optional[str],
46
- 'type': int,
47
- 'display': str,
48
- 'data': str,
49
- 'file': dict[Literal['path', 'name', 'md5', 'size'], str]
50
- }
51
- )
52
-
53
-
54
- class RMessage(object):
55
- """
56
- Rey's `message` type.
57
- """
58
-
59
-
60
- def __init__(
61
- self,
62
- rreceive: RReceive,
63
- time: int,
64
- id_: int,
65
- number: int,
66
- type_: int,
67
- display: str,
68
- data: str,
69
- user: Optional[str] = None,
70
- room: Optional[str] = None,
71
- file: Optional[dict[Literal['path', 'name', 'md5', 'size'], str]] = None
72
- ) -> None:
73
- """
74
- Build `message` attributes.
75
-
76
- Parameters
77
- ----------
78
- rreceive : `RReceive` instance.
79
- time : Message timestamp.
80
- id : Message ID.
81
- number : Message local number.
82
- type : Message type.
83
- display : Message description text.
84
- data : Message source data.
85
- user : Message sender user ID.
86
- - `None`: System message.
87
- - `str`: User messages.
88
- room : Message chat room ID.
89
- - `None`: Private chat.
90
- - `str`: Chat room chat.
91
- file : Message file information.
92
- - `None`: Non file message.
93
- - `dict`: File message.
94
- `Key 'path'`: File path.
95
- `Key 'name'`: File name.
96
- `Key 'md5'`: File MD5.
97
- `Key 'size'`: File byte size.
98
- """
99
-
100
- # Import.
101
- from .rexecute import Rule
102
-
103
- # Set attribute.
104
- self.rreceive = rreceive
105
- self.time = time
106
- self.id = id_
107
- self.number = number
108
- self.type = type_
109
- self.display = display
110
- self.data = data
111
- self.user = user
112
- self.room = room
113
- self.file = file
114
- self._user_name: Optional[str] = None
115
- self._room_name: Optional[str] = None
116
- self._is_quote: Optional[bool] = None
117
- self._is_quote_self: Optional[bool] = None
118
- self._quote_params: Optional[dict[Literal['text', 'quote_id', 'quote_type', 'quote_user', 'quote_user_name', 'quote_data'], Optional[str]]] = None
119
- self._is_at: Optional[bool] = None
120
- self._is_at_self: Optional[bool] = None
121
- self._is_new_user: Optional[bool] = None
122
- self._is_new_room: Optional[bool] = None
123
- self._is_new_room_user: Optional[bool] = None
124
- self._new_room_user_name: Optional[str] = None
125
- self._is_change_room_name: Optional[bool] = None
126
- self._change_room_name: Optional[str] = None
127
- self._is_kick_out_room: Optional[bool] = None
128
- self._is_dissolve_room: Optional[bool] = None
129
- self._is_image: Optional[bool] = None
130
- self._image_qrcodes: Optional[list[str]] = None
131
- self._is_xml: Optional[bool] = None
132
- self._is_app: Optional[bool] = None
133
- self._app_params: Optional[dict] = None
134
- self._valid: Optional[bool] = None
135
- self.ruling: Optional[Rule] = None
136
- self.replied: bool = False
137
- self.execute_continue = self.rreceive.rexecute.continue_
138
- self.execute_break = self.rreceive.rexecute.break_
139
- self.exc_reports: list[str] = []
140
-
141
-
142
- @property
143
- def params(self) -> MessageParameters:
144
- """
145
- Return parameters dictionary.
146
-
147
- Returns
148
- -------
149
- Parameters dictionary.
150
- """
151
-
152
- # Get parameter.
153
- params = {
154
- 'time': self.time,
155
- 'id': self.id,
156
- 'number': self.number,
157
- 'room': self.room,
158
- 'user': self.user,
159
- 'type': self.type,
160
- 'display': self.display,
161
- 'data': self.data,
162
- 'file': self.file
163
- }
164
-
165
- return params
166
-
167
-
168
- def __str__(self) -> str:
169
- """
170
- Return parameters dictionary in string format.
171
-
172
- Returns
173
- -------
174
- Parameters dictionary in string format.
175
- """
176
-
177
- # Convert.
178
- params_str = str(self.params)
179
-
180
- return params_str
181
-
182
-
183
- @property
184
- def user_name(self) -> str:
185
- """
186
- Message sender user name.
187
-
188
- Returns
189
- -------
190
- User name.
191
- """
192
-
193
- # Judged.
194
- if self._user_name is not None:
195
- return self._user_name
196
-
197
- # Set.
198
- self._user_name = self.rreceive.rwechat.rclient.get_contact_name(
199
- self.user
200
- )
201
-
202
- return self._user_name
203
-
204
-
205
- @property
206
- def room_name(self) -> str:
207
- """
208
- Message sender chat room name.
209
-
210
- Returns
211
- -------
212
- Chat room name.
213
- """
214
-
215
- # Break.
216
- if self.room is None:
217
- return
218
-
219
- # Judged.
220
- if self._room_name is not None:
221
- return self._room_name
222
-
223
- # Set.
224
- self._room_name = self.rreceive.rwechat.rclient.get_contact_name(
225
- self.room
226
- )
227
-
228
- return self._room_name
229
-
230
-
231
- @property
232
- def is_quote(self) -> bool:
233
- """
234
- Whether if is message of quote message.
235
-
236
- Returns
237
- -------
238
- Judge result.
239
- """
240
-
241
- # Judged.
242
- if self._is_quote is not None:
243
- return self._is_quote
244
-
245
- # Judge.
246
- self._is_quote = (
247
- self.type == 49
248
- and '<type>57</type>' in self.data
249
- )
250
-
251
- return self._is_quote
252
-
253
-
254
- @property
255
- def is_quote_self(self) -> bool:
256
- """
257
- Whether if is message of quote self.
258
-
259
- Returns
260
- -------
261
- Judge result.
262
- """
263
-
264
- # Judged.
265
- if self._is_quote_self is not None:
266
- return self._is_quote_self
267
-
268
- # Judge.
269
- self._is_quote_self = (
270
- self.is_quote
271
- and '<chatusr>%s</chatusr>' % self.rreceive.rwechat.rclient.login_info['id'] in self.data
272
- )
273
-
274
- return self._is_quote_self
275
-
276
-
277
- @property
278
- def quote_params(self) -> dict[
279
- Literal['text', 'quote_id', 'quote_type', 'quote_user', 'quote_user_name', 'quote_data'],
280
- Optional[str]
281
- ]:
282
- """
283
- Return quote parameters of message.
284
-
285
- Returns
286
- -------
287
- Quote parameters of message.
288
- - `Key 'text'`: Message text.
289
- - `Key 'quote_id'`: Quote message ID.
290
- - `Key 'quote_type'`: Quote message type.
291
- - `Key 'quote_user'`: Quote message user ID.
292
- - `Key 'quote_user_name'`: Quote message user name.
293
- - `Key 'quote_data'`: Quote message data.
294
- """
295
-
296
- # Extracted.
297
- if self._quote_params is not None:
298
- return self._quote_params
299
-
300
- # Check.
301
- if not self.is_quote:
302
- throw(value=self.is_quote)
303
-
304
- # Extract.
305
- pattern = '<title>(.+?)</title>'
306
- text = search(pattern, self.data)
307
- pattern = r'<svrid>(\w+?)</svrid>'
308
- quote_id = search(pattern, self.data)
309
- pattern = r'<refermsg>\s*<type>(\d+?)</type>'
310
- quote_type = int(search(pattern, self.data))
311
- pattern = r'<chatusr>(\w+?)</chatusr>'
312
- quote_user = search(pattern, self.data)
313
- pattern = '<displayname>(.+?)</displayname>'
314
- quote_user_name = search(pattern, self.data)
315
- pattern = '<content>(.+?)</content>'
316
- quote_data = search(pattern, self.data)
317
- self._quote_params = {
318
- 'text': text,
319
- 'quote_id': quote_id,
320
- 'quote_type': quote_type,
321
- 'quote_user': quote_user,
322
- 'quote_user_name': quote_user_name,
323
- 'quote_data': quote_data
324
- }
325
-
326
- return self._quote_params
327
-
328
-
329
- @property
330
- def is_at(self) -> bool:
331
- """
332
- Whether if is `@` message.
333
-
334
- Returns
335
- -------
336
- Judge result.
337
- """
338
-
339
- # Judged.
340
- if self._is_at is not None:
341
- return self._is_at
342
-
343
- # Judge.
344
- if self.type == 1:
345
- text = self.data
346
- elif self.is_quote:
347
- text = self.quote_params['text']
348
- pattern = r'@\w+ '
349
- result = search(pattern, text)
350
- self._is_at = result is not None
351
-
352
- return self._is_at
353
-
354
-
355
- @property
356
- def is_at_self(self) -> bool:
357
- """
358
- Whether if is message of `@` self.
359
-
360
- Returns
361
- -------
362
- Judge result.
363
- """
364
-
365
- # Judged.
366
- if self._is_at_self is not None:
367
- return self._is_at_self
368
-
369
- # Judge.
370
- if self.type == 1:
371
- text = self.data
372
- elif self.is_quote:
373
- text = self.quote_params['text']
374
- pattern = '@%s ' % self.rreceive.rwechat.rclient.login_info['name']
375
- result = search(pattern, text)
376
- self._is_at_self = result is not None
377
-
378
- return self._is_at_self
379
-
380
-
381
- @property
382
- def is_new_user(self) -> bool:
383
- """
384
- Whether if is new user.
385
-
386
- Returns
387
- -------
388
- Judge result.
389
- """
390
-
391
- # Judged.
392
- if self._is_new_user is not None:
393
- return self._is_new_user
394
-
395
- # Judge.
396
- self._is_new_user = (
397
- self.type == 10000
398
- and (
399
- self.data == '以上是打招呼的内容'
400
- or self.data.startswith('你已添加了')
401
- )
402
- )
403
-
404
- return self._is_new_user
405
-
406
-
407
- @property
408
- def is_new_room(self) -> bool:
409
- """
410
- Whether if is new chat room.
411
-
412
- Returns
413
- -------
414
- Judge result.
415
- """
416
-
417
- # Judged.
418
- if self._is_new_room is not None:
419
- return self._is_new_room
420
-
421
- # Judge.
422
- self._is_new_room = (
423
- self.type == 10000
424
- and (
425
- '邀请你和' in self.data[:38]
426
- or '邀请你加入了群聊' in self.data[:42]
427
- )
428
- )
429
-
430
- return self._is_new_room
431
-
432
-
433
- @property
434
- def is_new_room_user(self) -> bool:
435
- """
436
- Whether if is new chat room user.
437
-
438
- Returns
439
- -------
440
- Judge result.
441
- """
442
-
443
- # Judged.
444
- if self._is_new_room_user is not None:
445
- return self._is_new_room_user
446
-
447
- # Judge.
448
- self._is_new_room_user = (
449
- self.type == 10000
450
- and '邀请"' in self.data[:37]
451
- and self.data.endswith('"加入了群聊')
452
- )
453
-
454
- return self._is_new_room_user
455
-
456
-
457
- @property
458
- def new_room_user_name(self) -> Optional[str]:
459
- """
460
- Return new chat room user name.
461
-
462
- Returns
463
- -------
464
- New chat room user name.
465
- """
466
-
467
- # Extracted.
468
- if self._new_room_user_name is not None:
469
- return self._new_room_user_name
470
-
471
- # Extract.
472
- pattern = '邀请"(.+?)"加入了群聊'
473
- result = search(pattern, self.data)
474
- self._new_room_user_name = result
475
-
476
- return result
477
-
478
-
479
- @property
480
- def is_change_room_name(self) -> bool:
481
- """
482
- Whether if is change chat room name.
483
-
484
- Returns
485
- -------
486
- Judge result.
487
- """
488
-
489
- # Judged.
490
- if self._is_change_room_name is not None:
491
- return self._is_change_room_name
492
-
493
- # Judge.
494
- self._is_change_room_name = (
495
- self.type == 10000
496
- and '修改群名为“' in self.data[:40]
497
- )
498
-
499
- return self._is_change_room_name
500
-
501
-
502
- @property
503
- def change_room_name(self) -> Optional[str]:
504
- """
505
- Return change chat room name.
506
-
507
- Returns
508
- -------
509
- Change chat room name.
510
- """
511
-
512
- # Extracted.
513
- if self._change_room_name is not None:
514
- return self._change_room_name
515
-
516
- # Extract.
517
- pattern = '修改群名为“(.+?)”'
518
- result = search(pattern, self.data)
519
- self._change_room_name = result
520
-
521
- return self._change_room_name
522
-
523
-
524
- @property
525
- def is_kick_out_room(self) -> bool:
526
- """
527
- Whether if is kick out chat room.
528
-
529
- Returns
530
- -------
531
- Judge result.
532
- """
533
-
534
- # Judged.
535
- if self._is_kick_out_room is not None:
536
- return self._is_kick_out_room
537
-
538
- # Judge.
539
- self._is_kick_out_room = (
540
- self.type == 10000
541
- and self.data.startswith('你被')
542
- and self.data.endswith('移出群聊')
543
- )
544
-
545
- return self._is_kick_out_room
546
-
547
-
548
- @property
549
- def is_dissolve_room(self) -> bool:
550
- """
551
- Whether if is dissolve chat room.
552
-
553
- Returns
554
- -------
555
- Judge result.
556
- """
557
-
558
- # Judged.
559
- if self._is_dissolve_room is not None:
560
- return self._is_dissolve_room
561
-
562
- # Judge.
563
- self._is_dissolve_room = (
564
- self.type == 10000
565
- and self.data.startswith('群主')
566
- and self.data.endswith('已解散该群聊')
567
- )
568
-
569
- return self._is_dissolve_room
570
-
571
-
572
- @property
573
- def is_image(self) -> bool:
574
- """
575
- Whether if is image.
576
-
577
- Returns
578
- -------
579
- Judge result.
580
- """
581
-
582
- # Judged.
583
- if self._is_image is not None:
584
- return self._is_image
585
-
586
- # Judge.
587
- self._is_image = self.type == 3
588
-
589
- return self._is_image
590
-
591
-
592
- @property
593
- def image_qrcodes(self) -> list[str]:
594
- """
595
- Return image QR code texts.
596
-
597
- Returns
598
- -------
599
- Image QR code texts.
600
- """
601
-
602
- # Extracted.
603
- if self._image_qrcodes is not None:
604
- return self._image_qrcodes
605
-
606
- # Check.
607
- if not self.is_image:
608
- throw(value=self.is_image)
609
-
610
- # Extract.
611
- self._image_qrcodes = decode_qrcode(self.file['path'])
612
-
613
- return self._image_qrcodes
614
-
615
-
616
- @property
617
- def is_xml(self) -> bool:
618
- """
619
- Whether if is XML format.
620
-
621
- Returns
622
- -------
623
- Judge result.
624
- """
625
-
626
- # Judged.
627
- if self._is_xml is not None:
628
- return self._is_xml
629
-
630
- # Judge.
631
- self._is_xml = (
632
- self.type != 1
633
- and self.data.startswith('<?xml ')
634
- )
635
-
636
- return self._is_xml
637
-
638
-
639
- @property
640
- def is_app(self) -> bool:
641
- """
642
- Whether if is application share.
643
-
644
- Returns
645
- -------
646
- Judge result.
647
- """
648
-
649
- # Judged.
650
- if self._is_app is not None:
651
- return self._is_app
652
-
653
- # Judge.
654
- self.is_app = (
655
- self.type == 49
656
- and self.is_xml
657
- and '<appmsg ' in self.data[:50]
658
- )
659
-
660
- return self.is_app
661
-
662
-
663
- @property
664
- def app_params(self) -> dict:
665
- """
666
- Return application share parameters.
667
-
668
- Returns
669
- -------
670
- Application share parameters.
671
- """
672
-
673
- # Extracted.
674
- if self._app_params is not None:
675
- return self._app_params
676
-
677
- # Check.
678
- if not self.is_app:
679
- throw(value=self.is_app)
680
-
681
- # Extract.
682
- bs_document = BSBeautifulSoup(
683
- self.data,
684
- 'xml'
685
- )
686
- bs_appmsg = bs_document.find('appmsg')
687
- self._app_params = {
688
- bs_element.name: bs_element.text
689
- for bs_element in bs_appmsg.contents
690
- if bs_element.__class__ == BSTag
691
- }
692
-
693
- return self._app_params
694
-
695
-
696
- @property
697
- def valid(self) -> bool:
698
- """
699
- Judge if is valid user or chat room or chat room user from database.
700
-
701
- Returns
702
- -------
703
- Judgment result.
704
- - `True`: Valid.
705
- - `False`: Invalid or no record.
706
- """
707
-
708
- # Extracted.
709
- if self._valid is not None:
710
- return self._valid
711
-
712
- # Judge.
713
- self._valid = self.rreceive.rwechat.rdatabase.is_valid(self)
714
-
715
- return self._valid
716
-
717
-
718
- @overload
719
- def reply(
720
- self,
721
- send_type: Literal[0],
722
- *,
723
- text: str
724
- ) -> None: ...
725
-
726
- @overload
727
- def reply(
728
- self,
729
- send_type: Literal[1],
730
- *,
731
- user_id: str | list[str],
732
- text: str
733
- ) -> None: ...
734
-
735
- @overload
736
- def reply(
737
- self,
738
- send_type: Literal[2, 3, 4],
739
- *,
740
- path: str,
741
- file_name: Optional[str] = None
742
- ) -> None: ...
743
-
744
- @overload
745
- def reply(
746
- self,
747
- send_type: Literal[5],
748
- *,
749
- user_id: str
750
- ) -> None: ...
751
-
752
- @overload
753
- def reply(
754
- self,
755
- send_type: Literal[6],
756
- *,
757
- page_url: str,
758
- title: str,
759
- text: Optional[str] = None,
760
- image_url: Optional[str] = None,
761
- public_name: Optional[str] = None,
762
- public_id: Optional[str] = None
763
- ) -> None: ...
764
-
765
- @overload
766
- def reply(
767
- self,
768
- send_type: Literal[7],
769
- *,
770
- message_id: str
771
- ) -> None: ...
772
-
773
- @overload
774
- def reply(
775
- self,
776
- send_type: Any,
777
- **params: Any
778
- ) -> NoReturn: ...
779
-
780
- def reply(
781
- self,
782
- send_type: Optional[Literal[0, 1, 2, 3, 4, 5, 6, 7]] = None,
783
- **params: Any
784
- ) -> None:
785
- """
786
- Send reply message.
787
-
788
- Parameters
789
- ----------
790
- send_type : Send type.
791
- - `Literal[0]` Send text message, use `RClient.send_text`: method.
792
- - `Literal[1]` Send text message with `@`, use `RClient.send_text_at`: method.
793
- - `Literal[2]` Send file message, use `RClient.send_file`: method.
794
- - `Literal[3]` Send image message, use `RClient.send_image`: method.
795
- - `Literal[4]` Send emotion message, use `RClient.send_emotion`: method.
796
- - `Literal[5]` Send pat message, use `RClient.send_pat`: method.
797
- - `Literal[6]` Send public account message, use `RClient.send_public`: method.
798
- - `Literal[7]` Forward message, use `RClient.send_forward`: method.
799
- params : Send parameters.
800
- - `Callable`: Use execute return value.
801
- - `Any`: Use this value.
802
- `Key 'file_name'`: Given file name.
803
- """
804
-
805
- # Check.
806
- if self.ruling is None:
807
- throw(RWeChatExecuteNoRuleReplyError)
808
- if self.ruling['mode'] != 'reply':
809
- throw(RWeChatExecuteTriggerReplyError)
810
-
811
- # Get parameter.
812
- if self.room is None:
813
- receive_id = self.user
814
- else:
815
- receive_id = self.room
816
-
817
- # Status.
818
- self.replied = True
819
-
820
- # Send.
821
- self.rreceive.rwechat.rsend.send(
822
- send_type,
823
- receive_id=receive_id,
824
- **params
825
- )
826
-
827
-
828
- class RReceive(object):
829
- """
830
- Rey's `receive` type.
831
- """
832
-
833
-
834
- def __init__(
835
- self,
836
- rwechat: RWeChat,
837
- max_receiver: int,
838
- bandwidth_downstream: float
839
- ) -> None:
840
- """
841
- Build `receive` attributes.
842
-
843
- Parameters
844
- ----------
845
- rwechat : `RClient` instance.
846
- max_receiver : Maximum number of receivers.
847
- bandwidth_downstream : Download bandwidth, impact receive timeout, unit Mpbs.
848
- """
849
-
850
- # Import.
851
- from .rexecute import RExecute
852
-
853
- # Set attribute.
854
- self.rwechat = rwechat
855
- self.max_receiver = max_receiver
856
- self.bandwidth_downstream = bandwidth_downstream
857
- self.queue: Queue[RMessage] = Queue()
858
- self.handlers: list[Callable[[RMessage], Any]] = []
859
- self.started: Optional[bool] = False
860
- self.rexecute = RExecute(self)
861
-
862
- # Start.
863
- self._start_callback()
864
- self._start_receiver(self.max_receiver)
865
- self.rwechat.rclient.hook_message(
866
- '127.0.0.1',
867
- self.rwechat.rclient.message_callback_port,
868
- 60
869
- )
870
-
871
-
872
- @wrap_thread
873
- def _start_callback(self) -> None:
874
- """
875
- Start callback socket.
876
- """
877
-
878
-
879
- # Define.
880
- def put_queue(data: bytes) -> None:
881
- """
882
- Put message data into receive queue.
883
-
884
- Parameters
885
- ----------
886
- data : Socket receive data.
887
- """
888
-
889
- # Decode.
890
- data: dict = json_loads(data)
891
-
892
- # Break.
893
- if 'msgId' not in data: return
894
-
895
- # Extract.
896
- rmessage = RMessage(
897
- self,
898
- data['createTime'],
899
- data['msgId'],
900
- data['msgSequence'],
901
- data['type'],
902
- data['displayFullContent'],
903
- data['content'],
904
- data['fromUser']
905
- )
906
-
907
- # Put.
908
- self.queue.put(rmessage)
909
-
910
-
911
- # Listen socket.
912
- listen_socket(
913
- '127.0.0.1',
914
- self.rwechat.rclient.message_callback_port,
915
- put_queue
916
- )
917
-
918
-
919
- @wrap_thread
920
- def _start_receiver(
921
- self,
922
- max_receiver: int
923
- ) -> None:
924
- """
925
- Start receiver, that will sequentially handle message in the receive queue.
926
-
927
- Parameters
928
- ----------
929
- max_receiver : Maximum number of receivers.
930
- """
931
-
932
-
933
- # Define.
934
- def handles(rmessage: RMessage) -> None:
935
- """
936
- Use handlers to handle message.
937
-
938
- Parameters
939
- ----------
940
- rmessage : `RMessage` instance.
941
- """
942
-
943
- # Set parameter.
944
- handlers = [
945
- self._handler_room,
946
- self._handler_file,
947
- *self.handlers
948
- ]
949
-
950
- # Handle.
951
-
952
- ## Define.
953
- def handle_handler_exception() -> None:
954
- """
955
- Handle Handler exception.
956
- """
957
-
958
- # Catch exception.
959
- exc_report, *_ = catch_exc()
960
-
961
- # Save.
962
- rmessage.exc_reports.append(exc_report)
963
-
964
-
965
- ## Loop.
966
- for handler in handlers:
967
- wrap_exc(
968
- handler,
969
- rmessage,
970
- _handler=handle_handler_exception
971
- )
972
-
973
- # Log.
974
- self.rwechat.rlog.log_receive(rmessage)
975
-
976
-
977
- # Thread pool.
978
- thread_pool = RThreadPool(
979
- handles,
980
- _max_workers=max_receiver
981
- )
982
-
983
- # Loop.
984
- while True:
985
- match self.started:
986
-
987
- ## Stop.
988
- case False:
989
- sleep(0.1)
990
- continue
991
-
992
- ## End.
993
- case None:
994
- break
995
-
996
- ## Submit.
997
- rmessage = self.queue.get()
998
- thread_pool.one(rmessage)
999
-
1000
-
1001
- def add_handler(
1002
- self,
1003
- handler: Callable[[RMessage], Any]
1004
- ) -> None:
1005
- """
1006
- Add message handler function.
1007
-
1008
- Parameters
1009
- ----------
1010
- handler : Handler method, input parameter is `RMessage` instance.
1011
- """
1012
-
1013
- # Add.
1014
- self.handlers.append(handler)
1015
-
1016
-
1017
- def _handler_room(
1018
- self,
1019
- rmessage: RMessage
1020
- ) -> None:
1021
- """
1022
- Handle room message.
1023
- """
1024
-
1025
- # Break.
1026
- if (
1027
- rmessage.user.__class__ != str
1028
- or not rmessage.user.endswith('chatroom')
1029
- ):
1030
- return
1031
-
1032
- # Set attribute.
1033
- rmessage.room = rmessage.user
1034
- if ':\n' in rmessage.data:
1035
- user, data = rmessage.data.split(':\n', 1)
1036
- rmessage.user = user
1037
- rmessage.data = data
1038
- else:
1039
- rmessage.user = None
1040
-
1041
-
1042
- def _handler_file(
1043
- self,
1044
- rmessage: RMessage
1045
- ) -> None:
1046
- """
1047
- Handle file message.
1048
- """
1049
-
1050
- # Save.
1051
- rfolder = RFolder(self.rwechat.dir_file)
1052
- generate_path = None
1053
- match rmessage.type:
1054
-
1055
- ## Image.
1056
- case 3:
1057
-
1058
- ### Get attribute.
1059
- file_name = f'{rmessage.id}.jpg'
1060
- pattern = r'length="(\d+)".*?md5="([\da-f]{32})"'
1061
- file_size, file_md5 = search(pattern, rmessage.data)
1062
- file_size = int(file_size)
1063
-
1064
- ### Exist.
1065
- pattern = f'^{file_md5}$'
1066
- search_path = rfolder.search(pattern)
1067
-
1068
- ### Generate.
1069
- if search_path is None:
1070
- self.rwechat.rclient.download_file(rmessage.id)
1071
- generate_path = '%swxhelper/image/%s.dat' % (
1072
- self.rwechat.rclient.login_info['account_data_path'],
1073
- rmessage.id
1074
- )
1075
-
1076
- ## Voice.
1077
- case 34:
1078
-
1079
- ### Get attribute.
1080
- file_name = f'{rmessage.id}.amr'
1081
- pattern = r'length="(\d+)"'
1082
- file_size = int(search(pattern, rmessage.data))
1083
- file_md5 = None
1084
-
1085
- ### Generate.
1086
- self.rwechat.rclient.download_voice(
1087
- rmessage.id,
1088
- self.rwechat.dir_file
1089
- )
1090
- generate_path = '%s/%s.amr' % (
1091
- self.rwechat.dir_file,
1092
- rmessage.id
1093
- )
1094
-
1095
- ## Video.
1096
- case 43:
1097
-
1098
- ### Get attribute.
1099
- file_name = f'{rmessage.id}.mp4'
1100
- pattern = r'length="(\d+)"'
1101
- file_size = int(search(pattern, rmessage.data))
1102
- pattern = r'md5="([\da-f]{32})"'
1103
- file_md5 = search(pattern, rmessage.data)
1104
-
1105
- ### Exist.
1106
- pattern = f'^{file_md5}$'
1107
- search_path = rfolder.search(pattern)
1108
-
1109
- ### Generate.
1110
- if search_path is None:
1111
- self.rwechat.rclient.download_file(rmessage.id)
1112
- generate_path = '%swxhelper/video/%s.mp4' % (
1113
- self.rwechat.rclient.login_info['account_data_path'],
1114
- rmessage.id
1115
- )
1116
-
1117
- ## Other.
1118
- case 49:
1119
-
1120
- ### Check.
1121
- pattern = r'^.+? : \[文件\](.+)$'
1122
- file_name = search(pattern, rmessage.display)
1123
- if file_name is None:
1124
- return
1125
- if '<type>6</type>' not in rmessage.data:
1126
- return
1127
-
1128
- ### Get attribute.
1129
- pattern = r'<totallen>(\d+)</totallen>'
1130
- file_size = int(search(pattern, rmessage.data))
1131
- pattern = r'<md5>([\da-f]{32})</md5>'
1132
- file_md5 = search(pattern, rmessage.data)
1133
-
1134
- ### Exist.
1135
- pattern = f'^{file_md5}$'
1136
- search_path = rfolder.search(pattern)
1137
-
1138
- ### Generate.
1139
- if search_path is None:
1140
- self.rwechat.rclient.download_file(rmessage.id)
1141
- generate_path = '%swxhelper/file/%s_%s' % (
1142
- self.rwechat.rclient.login_info['account_data_path'],
1143
- rmessage.id,
1144
- file_name
1145
- )
1146
-
1147
- ## Break.
1148
- case _:
1149
- return
1150
-
1151
- # Wait.
1152
- if generate_path is not None:
1153
- stream_time = get_file_stream_time(file_size, self.bandwidth_downstream)
1154
- timeout = 10 + stream_time * (self.max_receiver + 1)
1155
- wait(
1156
- os_exists,
1157
- generate_path,
1158
- _interval = 0.05,
1159
- _timeout=timeout
1160
- )
1161
- sleep(0.2)
1162
-
1163
- # Move.
1164
- if generate_path is None:
1165
- save_path = '%s/%s' % (
1166
- self.rwechat.dir_file,
1167
- file_md5
1168
- )
1169
- else:
1170
- rfile = RFile(generate_path)
1171
- search_path = None
1172
- if file_md5 is None:
1173
- file_md5 = rfile.md5
1174
-
1175
- ### Exist.
1176
- pattern = f'^{file_md5}$'
1177
- search_path = rfolder.search(pattern)
1178
-
1179
- if search_path is None:
1180
- save_path = '%s/%s' % (
1181
- self.rwechat.dir_file,
1182
- file_md5
1183
- )
1184
- rfile.move(save_path)
1185
-
1186
- # Set parameter.
1187
- file = {
1188
- 'path': save_path,
1189
- 'name': file_name,
1190
- 'md5': file_md5,
1191
- 'size': file_size
1192
- }
1193
- rmessage.file = file
1194
-
1195
-
1196
- def start(self) -> None:
1197
- """
1198
- Start receiver.
1199
- """
1200
-
1201
- # Start.
1202
- self.started = True
1203
-
1204
- # Report.
1205
- print('Start receiver.')
1206
-
1207
-
1208
- def stop(self) -> None:
1209
- """
1210
- Stop receiver.
1211
- """
1212
-
1213
- # Stop.
1214
- self.started = False
1215
-
1216
- # Report.
1217
- print('Stop receiver.')
1218
-
1219
-
1220
- def end(self) -> None:
1221
- """
1222
- End receiver.
1223
- """
1224
-
1225
- # End.
1226
- self.started = None
1227
-
1228
- # Report.
1229
- print('End receiver.')
1230
-
1231
-
1232
- __del__ = end