reywechat 1.0.52__py3-none-any.whl → 1.0.54__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/rdb.py +21 -4
- reywechat/rreceive.py +574 -144
- {reywechat-1.0.52.dist-info → reywechat-1.0.54.dist-info}/METADATA +1 -1
- {reywechat-1.0.52.dist-info → reywechat-1.0.54.dist-info}/RECORD +6 -6
- {reywechat-1.0.52.dist-info → reywechat-1.0.54.dist-info}/WHEEL +0 -0
- {reywechat-1.0.52.dist-info → reywechat-1.0.54.dist-info}/licenses/LICENSE +0 -0
reywechat/rdb.py
CHANGED
@@ -296,14 +296,31 @@ class WeChatDatabase(WeChatBase):
|
|
296
296
|
'1 is text message, '
|
297
297
|
'3 is image message, '
|
298
298
|
'34 is voice message, '
|
299
|
-
'37 is new friend, '
|
300
|
-
'42 is business card, '
|
299
|
+
'37 is new friend invitation message, '
|
300
|
+
'42 is business card message, '
|
301
301
|
'43 is video message, '
|
302
302
|
'47 is emoticon message, '
|
303
303
|
'48 is position message, '
|
304
|
-
'49 is
|
304
|
+
'49 is share message ('
|
305
|
+
'data type, '
|
306
|
+
'1 is pure link text, '
|
307
|
+
'6 is other side upload file completed, '
|
308
|
+
'17 is initiate real time location, '
|
309
|
+
'19 or 40 is forward, '
|
310
|
+
'33 is mini program, '
|
311
|
+
'51 is video channel, '
|
312
|
+
'57 is quote, '
|
313
|
+
'74 is other side start uploading file, '
|
314
|
+
'2000 is transfer money, '
|
315
|
+
'include "<appname>[^<>]+</appname>" is app, '
|
316
|
+
'other omit'
|
317
|
+
'), '
|
318
|
+
'50 is voice call or video call invitation message, '
|
319
|
+
'51 is system synchronize data message, '
|
320
|
+
'56 is real time position data message, '
|
305
321
|
'1000 is system message, '
|
306
|
-
'1002 is recall message
|
322
|
+
'1002 is recall message, '
|
323
|
+
'other omit.'
|
307
324
|
)
|
308
325
|
},
|
309
326
|
{
|
reywechat/rreceive.py
CHANGED
@@ -14,8 +14,6 @@ from typing import Any, TypedDict, Literal, overload
|
|
14
14
|
from collections.abc import Callable
|
15
15
|
from queue import Queue
|
16
16
|
from json import loads as json_loads
|
17
|
-
from bs4 import BeautifulSoup as BSBeautifulSoup
|
18
|
-
from bs4.element import Tag as BSTag
|
19
17
|
from reykit.rbase import throw
|
20
18
|
from reykit.rimage import decode_qrcode
|
21
19
|
from reykit.rlog import Mark
|
@@ -47,19 +45,40 @@ MessageParameterFile = TypedDict(
|
|
47
45
|
}
|
48
46
|
)
|
49
47
|
MessageParameter = TypedDict(
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
48
|
+
'MessageParameter',
|
49
|
+
{
|
50
|
+
'time': int,
|
51
|
+
'id': int,
|
52
|
+
'number': int,
|
53
|
+
'room': str | None,
|
54
|
+
'user': str | None,
|
55
|
+
'type': int,
|
56
|
+
'display': str,
|
57
|
+
'data': str,
|
58
|
+
'file': MessageParameterFile
|
59
|
+
}
|
60
|
+
)
|
61
|
+
MessageShareParameter = TypedDict(
|
62
|
+
'MessageShareParameter',
|
63
|
+
{
|
64
|
+
'name': str | None,
|
65
|
+
'title': str | None,
|
66
|
+
'des': str | None,
|
67
|
+
'url': str | None
|
68
|
+
}
|
69
|
+
)
|
70
|
+
MessageQuoteParameter = TypedDict(
|
71
|
+
'MessageQuoteParameter',
|
72
|
+
{
|
73
|
+
'text': str,
|
74
|
+
'quote_id': int,
|
75
|
+
'quote_time': int,
|
76
|
+
'quote_type': int,
|
77
|
+
'quote_user': str,
|
78
|
+
'quote_user_name': str,
|
79
|
+
'quote_data': str
|
80
|
+
}
|
81
|
+
)
|
63
82
|
|
64
83
|
|
65
84
|
class WeChatMessage(WeChatBase):
|
@@ -136,12 +155,28 @@ class WeChatMessage(WeChatBase):
|
|
136
155
|
self._user_name: str | None = None
|
137
156
|
self._room_name: str | None = None
|
138
157
|
self._window_name: str | None = None
|
158
|
+
self._text: str | None = None
|
159
|
+
self._voice_len: float | None = None
|
160
|
+
self._video_len: int | None = None
|
161
|
+
self._business_card_name: str | None = None
|
162
|
+
|
163
|
+
### Share.
|
164
|
+
self._share_type: int | None = None
|
165
|
+
self._share_params: MessageShareParameter | None = None
|
166
|
+
self._is_file_uploading: bool | None = None
|
167
|
+
self._is_file_uploaded: bool | None = None
|
168
|
+
self._is_forward: bool | None = None
|
169
|
+
self._is_mini_program: bool | None = None
|
139
170
|
self._is_quote: bool | None = None
|
140
|
-
self.
|
171
|
+
self._is_quote_me: bool | None = None
|
141
172
|
self._quote_params: dict[Literal['text', 'quote_id', 'quote_type', 'quote_user', 'quote_user_name', 'quote_data'], str] | None = None
|
173
|
+
self._is_money: bool | None = None
|
174
|
+
self._money_amount: float | None = None
|
175
|
+
self._is_app: bool | None = None
|
176
|
+
|
142
177
|
self._at_names: list[str] = None
|
143
178
|
self._is_at: bool | None = None
|
144
|
-
self.
|
179
|
+
self._is_at_me: bool | None = None
|
145
180
|
self._is_call: bool | None = None
|
146
181
|
self._call_text: str | None = None
|
147
182
|
self._is_call_next: bool | None = None
|
@@ -154,11 +189,9 @@ class WeChatMessage(WeChatBase):
|
|
154
189
|
self._change_room_name: str | None = None
|
155
190
|
self._is_kick_out_room: bool | None = None
|
156
191
|
self._is_dissolve_room: bool | None = None
|
157
|
-
self._is_image: bool | None = None
|
158
192
|
self._image_qrcodes: list[str] | None = None
|
193
|
+
self._is_html: bool | None = None
|
159
194
|
self._is_xml: bool | None = None
|
160
|
-
self._is_app: bool | None = None
|
161
|
-
self._app_params: dict | None = None
|
162
195
|
self._valid: bool | None = None
|
163
196
|
|
164
197
|
## Update call.
|
@@ -277,10 +310,403 @@ class WeChatMessage(WeChatBase):
|
|
277
310
|
return self._window_name
|
278
311
|
|
279
312
|
|
313
|
+
@property
|
314
|
+
def text(self) -> str:
|
315
|
+
"""
|
316
|
+
Text description of message content.
|
317
|
+
|
318
|
+
Returns
|
319
|
+
-------
|
320
|
+
Text.
|
321
|
+
"""
|
322
|
+
|
323
|
+
# Cache.
|
324
|
+
if self._text is not None:
|
325
|
+
return self._text
|
326
|
+
|
327
|
+
# Get.
|
328
|
+
match self.type:
|
329
|
+
|
330
|
+
## Text.
|
331
|
+
case 1:
|
332
|
+
self._text = self.data
|
333
|
+
|
334
|
+
## Image.
|
335
|
+
case 3:
|
336
|
+
self._text = '[图片]'
|
337
|
+
|
338
|
+
## Voice.
|
339
|
+
case 34:
|
340
|
+
voice_len = round(self.voice_len, 1)
|
341
|
+
self._text = f'[{voice_len}秒的语音]'
|
342
|
+
|
343
|
+
## New firend invitation.
|
344
|
+
case 37:
|
345
|
+
self._text = '[新好友邀请]'
|
346
|
+
|
347
|
+
## Business card.
|
348
|
+
case 42:
|
349
|
+
self._text = f'[分享名片"{self.business_card_name}"]'
|
350
|
+
|
351
|
+
## Video.
|
352
|
+
case 43:
|
353
|
+
self._text = f'[{self.video_len}秒的视频]'
|
354
|
+
|
355
|
+
## Emoticon.
|
356
|
+
case 47:
|
357
|
+
self._text = f'[动画表情]'
|
358
|
+
|
359
|
+
## Position.
|
360
|
+
case 48:
|
361
|
+
self._text = '[地图位置分享]'
|
362
|
+
|
363
|
+
## Share.
|
364
|
+
case 49:
|
365
|
+
|
366
|
+
### Pure URL text.
|
367
|
+
if self.share_type == 1:
|
368
|
+
self._text = '[网址]'
|
369
|
+
if self.share_params['title'] is not None:
|
370
|
+
self._text += f' {self.share_params['title']}'
|
371
|
+
|
372
|
+
### File uploaded.
|
373
|
+
elif self.is_file_uploaded:
|
374
|
+
self._text = f'[文件"{self.file['name']}"完成上传]'
|
375
|
+
|
376
|
+
### Initiate real time location.
|
377
|
+
elif self.share_type == 17:
|
378
|
+
self._text = '[开始实时地图位置分享]'
|
379
|
+
|
380
|
+
### Forword messages.
|
381
|
+
elif self.is_forword:
|
382
|
+
if self.share_params['title'] is None:
|
383
|
+
self._text = '[转发聊天记录]'
|
384
|
+
else:
|
385
|
+
self._text = f'[转发"{self.share_params['title']}"]'
|
386
|
+
if self.share_params['des'] is not None:
|
387
|
+
self._text += f' {self.share_params['des']}'
|
388
|
+
|
389
|
+
### Mini program.
|
390
|
+
elif self.is_mini_program:
|
391
|
+
if self.share_params['name'] is None:
|
392
|
+
self._text = '[小程序分享]'
|
393
|
+
else:
|
394
|
+
self._text = f'[小程序"{self.share_params['name']}"分享]'
|
395
|
+
if self.share_params['title'] is not None:
|
396
|
+
self._text += f' {self.share_params['title']}'
|
397
|
+
|
398
|
+
### Video channel.
|
399
|
+
elif self.share_type == 51:
|
400
|
+
if self.share_params['name'] is None:
|
401
|
+
self._text = '[视频号分享]'
|
402
|
+
else:
|
403
|
+
self._text = f'[视频号"{self.share_params['name']}"分享]'
|
404
|
+
if self.share_params['title'] is not None:
|
405
|
+
self._text += f' {self.share_params['title']}'
|
406
|
+
|
407
|
+
### Quote.
|
408
|
+
elif self.is_quote:
|
409
|
+
self._text = f'[引用了"{self.quote_params['quote_user_name']}"的消息并发言] {self.quote_params['text']}'
|
410
|
+
|
411
|
+
### Quote me.
|
412
|
+
elif self.is_quote_me:
|
413
|
+
self._text = f'[引用了你的消息并发言] {self.quote_params['text']}'
|
414
|
+
|
415
|
+
### File uploading.
|
416
|
+
elif self.is_file_uploading:
|
417
|
+
file_name: str = search('<title><![CDATA[([^<>]+)]]></title>', self.data)
|
418
|
+
self._text = f'[文件"{file_name}"开始上传]'
|
419
|
+
|
420
|
+
### Transfer money.
|
421
|
+
elif self.is_money:
|
422
|
+
self._text = f'[转账{self.money_amount}¥]'
|
423
|
+
|
424
|
+
### App.
|
425
|
+
elif self.is_app:
|
426
|
+
if self.share_params['name'] is None:
|
427
|
+
self._text = '[APP分享]'
|
428
|
+
else:
|
429
|
+
self._text = f'[APP"{self.share_params['name']}"分享]'
|
430
|
+
if self.share_params["title"] is not None:
|
431
|
+
self._text += f' {self.share_params["title"]}'
|
432
|
+
if self.share_params["des"] is not None:
|
433
|
+
self._text += f' {self.share_params["des"]}'
|
434
|
+
|
435
|
+
### Other.
|
436
|
+
else:
|
437
|
+
if self.share_params['name'] is None:
|
438
|
+
self._text = '[分享]'
|
439
|
+
else:
|
440
|
+
self._text = f'["{self.share_params['name']}"分享]'
|
441
|
+
if self.share_params["title"] is not None:
|
442
|
+
self._text += f' {self.share_params["title"]}'
|
443
|
+
if self.share_params["des"] is not None:
|
444
|
+
self._text += f' {self.share_params["des"]}'
|
445
|
+
|
446
|
+
## Voice call or video call.
|
447
|
+
case 50:
|
448
|
+
self._text = '[视频或语音通话]'
|
449
|
+
|
450
|
+
## System sync.
|
451
|
+
case 51:
|
452
|
+
self._text = '[系统同步]'
|
453
|
+
|
454
|
+
## Real time position.
|
455
|
+
case 56:
|
456
|
+
self._text = '[实时地图位置分享中]'
|
457
|
+
|
458
|
+
## System.
|
459
|
+
case 1000:
|
460
|
+
self._text = '[系统信息]'
|
461
|
+
|
462
|
+
## Recall.
|
463
|
+
case 1002:
|
464
|
+
self._text = '[撤回了一条消息]'
|
465
|
+
|
466
|
+
case _:
|
467
|
+
self._text = '[消息]'
|
468
|
+
|
469
|
+
return self._text
|
470
|
+
|
471
|
+
|
472
|
+
@property
|
473
|
+
def voice_len(self) -> float:
|
474
|
+
"""
|
475
|
+
Voice message length, unit is seconds.
|
476
|
+
|
477
|
+
Returns
|
478
|
+
-------
|
479
|
+
Voice message length.
|
480
|
+
"""
|
481
|
+
|
482
|
+
# Cache.
|
483
|
+
if self._voice_len is not None:
|
484
|
+
return self._voice_len
|
485
|
+
|
486
|
+
# Check.
|
487
|
+
if self.type != 34:
|
488
|
+
throw(AssertionError, self.type)
|
489
|
+
|
490
|
+
# Get.
|
491
|
+
pattern = r'voicelength="(\d+)"'
|
492
|
+
voice_len_us_str = search(pattern, self.data)
|
493
|
+
self._voice_len = int(voice_len_us_str) / 1000
|
494
|
+
|
495
|
+
return self._voice_len
|
496
|
+
|
497
|
+
|
498
|
+
@property
|
499
|
+
def video_len(self) -> int:
|
500
|
+
"""
|
501
|
+
Video message length, unit is seconds.
|
502
|
+
|
503
|
+
Returns
|
504
|
+
-------
|
505
|
+
Video message length.
|
506
|
+
"""
|
507
|
+
|
508
|
+
# Cache.
|
509
|
+
if self._video_len is not None:
|
510
|
+
return self._video_len
|
511
|
+
|
512
|
+
# Check.
|
513
|
+
if self.type != 43:
|
514
|
+
throw(AssertionError, self.type)
|
515
|
+
|
516
|
+
# Get.
|
517
|
+
pattern = r'playlength="(\d+)"'
|
518
|
+
video_len_s_str = search(pattern, self.data)
|
519
|
+
self._video_len = int(video_len_s_str)
|
520
|
+
|
521
|
+
return self._video_len
|
522
|
+
|
523
|
+
|
524
|
+
@property
|
525
|
+
def business_card_name(self) -> str:
|
526
|
+
"""
|
527
|
+
Nickname of business card message.
|
528
|
+
|
529
|
+
Returns
|
530
|
+
-------
|
531
|
+
Voice message length.
|
532
|
+
"""
|
533
|
+
|
534
|
+
# Cache.
|
535
|
+
if self._business_card_name is not None:
|
536
|
+
return self._business_card_name
|
537
|
+
|
538
|
+
# Check.
|
539
|
+
if self.type != 42:
|
540
|
+
throw(AssertionError, self.type)
|
541
|
+
|
542
|
+
# Get.
|
543
|
+
pattern = r'nickname="([^"]+)"'
|
544
|
+
self._business_card_name = search(pattern, self.data)
|
545
|
+
|
546
|
+
return self._business_card_name
|
547
|
+
|
548
|
+
|
549
|
+
@property
|
550
|
+
def share_type(self) -> int:
|
551
|
+
"""
|
552
|
+
Type number of share message.
|
553
|
+
|
554
|
+
Returns
|
555
|
+
-------
|
556
|
+
Type number.
|
557
|
+
"""
|
558
|
+
|
559
|
+
# Cache.
|
560
|
+
if self._share_type is not None:
|
561
|
+
return self._share_type
|
562
|
+
|
563
|
+
# Check.
|
564
|
+
if self.type != 49:
|
565
|
+
throw(AssertionError, self.type)
|
566
|
+
|
567
|
+
# Get.
|
568
|
+
pattern = r'<type>(\d+)</type>'
|
569
|
+
share_type_str: str = search(pattern, self.data)
|
570
|
+
self._share_type = int(share_type_str)
|
571
|
+
|
572
|
+
return self._share_type
|
573
|
+
|
574
|
+
|
575
|
+
@property
|
576
|
+
def share_params(self) -> MessageShareParameter:
|
577
|
+
"""
|
578
|
+
Share message parameters.
|
579
|
+
|
580
|
+
Returns
|
581
|
+
-------
|
582
|
+
Parameters.
|
583
|
+
"""
|
584
|
+
|
585
|
+
# Cache.
|
586
|
+
if self._share_params is not None:
|
587
|
+
return self._share_params
|
588
|
+
|
589
|
+
# Check.
|
590
|
+
if self.type != 49:
|
591
|
+
throw(AssertionError, self.type)
|
592
|
+
|
593
|
+
# Extract.
|
594
|
+
name: str | None = search('.*<appname>([^<>]+)</appname>', self.data)
|
595
|
+
if name is None:
|
596
|
+
name: str | None = search('.*<sourcedisplayname>([^<>]+)</sourcedisplayname>', self.data)
|
597
|
+
if name is None:
|
598
|
+
name: str | None = search('.*<nickname>([^<>]+)</nickname>', self.data)
|
599
|
+
title: str | None = search('<title>([^<>]+)</title>', self.data)
|
600
|
+
des: str | None = search('.*<des>([^<>]+)</des>', self.data)
|
601
|
+
if des is None:
|
602
|
+
des: str | None = search('.*<desc>([^<>]+)</desc>', self.data)
|
603
|
+
url: str | None = search('.*<url>([^<>]+)</url>', self.data)
|
604
|
+
self._share_params: MessageShareParameter = {
|
605
|
+
'name': name,
|
606
|
+
'title': title,
|
607
|
+
'des': des,
|
608
|
+
'url': url
|
609
|
+
}
|
610
|
+
|
611
|
+
return self._share_params
|
612
|
+
|
613
|
+
|
614
|
+
@property
|
615
|
+
def is_file_uploading(self) -> bool:
|
616
|
+
"""
|
617
|
+
Whether if is share message of other side file uploading.
|
618
|
+
|
619
|
+
Returns
|
620
|
+
-------
|
621
|
+
Judge result.
|
622
|
+
"""
|
623
|
+
|
624
|
+
# Cache.
|
625
|
+
if self._is_file_uploading is not None:
|
626
|
+
return self._is_file_uploading
|
627
|
+
|
628
|
+
# Judge.
|
629
|
+
self._is_file_uploading = (
|
630
|
+
self.type == 49
|
631
|
+
and self.share_type == 74
|
632
|
+
)
|
633
|
+
|
634
|
+
return self._is_file_uploading
|
635
|
+
|
636
|
+
|
637
|
+
@property
|
638
|
+
def is_file_uploaded(self) -> bool:
|
639
|
+
"""
|
640
|
+
Whether if is share message of other side file uploaded.
|
641
|
+
|
642
|
+
Returns
|
643
|
+
-------
|
644
|
+
Judge result.
|
645
|
+
"""
|
646
|
+
|
647
|
+
# Cache.
|
648
|
+
if self._is_file_uploaded is not None:
|
649
|
+
return self._is_file_uploaded
|
650
|
+
|
651
|
+
# Judge.
|
652
|
+
self._is_file_uploading = (
|
653
|
+
self.type == 49
|
654
|
+
and self.share_type == 6
|
655
|
+
)
|
656
|
+
|
657
|
+
return self._is_file_uploaded
|
658
|
+
|
659
|
+
|
660
|
+
@property
|
661
|
+
def is_forword(self) -> bool:
|
662
|
+
"""
|
663
|
+
Whether if is share message of forward messages.
|
664
|
+
|
665
|
+
Returns
|
666
|
+
-------
|
667
|
+
Judge result.
|
668
|
+
"""
|
669
|
+
|
670
|
+
# Cache.
|
671
|
+
if self._is_forward is not None:
|
672
|
+
return self._is_forward
|
673
|
+
|
674
|
+
# Judge.
|
675
|
+
self._is_forward = (
|
676
|
+
self.type == 49
|
677
|
+
and self.share_type in (19, 40)
|
678
|
+
)
|
679
|
+
|
680
|
+
return self._is_forward
|
681
|
+
|
682
|
+
|
683
|
+
@property
|
684
|
+
def is_mini_program(self) -> bool:
|
685
|
+
"""
|
686
|
+
Whether if is share message of mini program.
|
687
|
+
|
688
|
+
Returns
|
689
|
+
-------
|
690
|
+
Judge result.
|
691
|
+
"""
|
692
|
+
|
693
|
+
# Cache.
|
694
|
+
if self._is_mini_program is not None:
|
695
|
+
return self._is_mini_program
|
696
|
+
|
697
|
+
# Judge.
|
698
|
+
self._is_mini_program = (
|
699
|
+
self.type == 49
|
700
|
+
and self.type == 33
|
701
|
+
)
|
702
|
+
|
703
|
+
return self._is_mini_program
|
704
|
+
|
705
|
+
|
280
706
|
@property
|
281
707
|
def is_quote(self) -> bool:
|
282
708
|
"""
|
283
|
-
Whether if is message of quote
|
709
|
+
Whether if is share message of quote.
|
284
710
|
|
285
711
|
Returns
|
286
712
|
-------
|
@@ -294,16 +720,16 @@ class WeChatMessage(WeChatBase):
|
|
294
720
|
# Judge.
|
295
721
|
self._is_quote = (
|
296
722
|
self.type == 49
|
297
|
-
and
|
723
|
+
and self.share_type == 57
|
298
724
|
)
|
299
725
|
|
300
726
|
return self._is_quote
|
301
727
|
|
302
728
|
|
303
729
|
@property
|
304
|
-
def
|
730
|
+
def is_quote_me(self) -> bool:
|
305
731
|
"""
|
306
|
-
Whether if is message of quote
|
732
|
+
Whether if is share message of quote me.
|
307
733
|
|
308
734
|
Returns
|
309
735
|
-------
|
@@ -311,25 +737,22 @@ class WeChatMessage(WeChatBase):
|
|
311
737
|
"""
|
312
738
|
|
313
739
|
# Cache.
|
314
|
-
if self.
|
315
|
-
return self.
|
740
|
+
if self._is_quote_me is not None:
|
741
|
+
return self._is_quote_me
|
316
742
|
|
317
743
|
# Judge.
|
318
|
-
self.
|
744
|
+
self._is_quote_me = (
|
319
745
|
self.is_quote
|
320
746
|
and '<chatusr>%s</chatusr>' % self.receiver.wechat.client.login_info['id'] in self.data
|
321
747
|
)
|
322
748
|
|
323
|
-
return self.
|
749
|
+
return self._is_quote_me
|
324
750
|
|
325
751
|
|
326
752
|
@property
|
327
|
-
def quote_params(self) ->
|
328
|
-
Literal['text', 'quote_id', 'quote_time', 'quote_type', 'quote_user', 'quote_user_name', 'quote_data'],
|
329
|
-
str | None
|
330
|
-
]:
|
753
|
+
def quote_params(self) -> MessageQuoteParameter:
|
331
754
|
"""
|
332
|
-
|
755
|
+
Quote message parameters.
|
333
756
|
|
334
757
|
Returns
|
335
758
|
-------
|
@@ -343,13 +766,13 @@ class WeChatMessage(WeChatBase):
|
|
343
766
|
- `Key 'quote_data'`: Quote message data.
|
344
767
|
"""
|
345
768
|
|
346
|
-
#
|
769
|
+
# Cache.
|
347
770
|
if self._quote_params is not None:
|
348
771
|
return self._quote_params
|
349
772
|
|
350
773
|
# Check.
|
351
774
|
if not self.is_quote:
|
352
|
-
throw(AssertionError, self.
|
775
|
+
throw(AssertionError, self._is_quote)
|
353
776
|
|
354
777
|
# Extract.
|
355
778
|
pattern = '<title>(.+?)</title>'
|
@@ -369,7 +792,7 @@ class WeChatMessage(WeChatBase):
|
|
369
792
|
quote_user_name: str = search(pattern, self.data)
|
370
793
|
pattern = '<content>(.+?)</content>'
|
371
794
|
quote_data: str = search(pattern, self.data)
|
372
|
-
self._quote_params = {
|
795
|
+
self._quote_params: MessageQuoteParameter = {
|
373
796
|
'text': text,
|
374
797
|
'quote_id': quote_id,
|
375
798
|
'quote_time': quote_time,
|
@@ -382,6 +805,77 @@ class WeChatMessage(WeChatBase):
|
|
382
805
|
return self._quote_params
|
383
806
|
|
384
807
|
|
808
|
+
@property
|
809
|
+
def is_money(self) -> bool:
|
810
|
+
"""
|
811
|
+
Whether if is message of transfer money.
|
812
|
+
|
813
|
+
Returns
|
814
|
+
-------
|
815
|
+
Judge result.
|
816
|
+
"""
|
817
|
+
|
818
|
+
# Cache.
|
819
|
+
if self._is_money is not None:
|
820
|
+
return self._is_money
|
821
|
+
|
822
|
+
# Judge.
|
823
|
+
self._is_money = (
|
824
|
+
self.type == 49
|
825
|
+
and self.share_type == 2000
|
826
|
+
)
|
827
|
+
|
828
|
+
return self._is_money
|
829
|
+
|
830
|
+
|
831
|
+
@property
|
832
|
+
def money_amount(self) -> float:
|
833
|
+
"""
|
834
|
+
Transfer money amount.
|
835
|
+
|
836
|
+
Returns
|
837
|
+
-------
|
838
|
+
Amount.
|
839
|
+
"""
|
840
|
+
|
841
|
+
# Cache.
|
842
|
+
if self._money_amount is not None:
|
843
|
+
return self._money_amount
|
844
|
+
|
845
|
+
# Check.
|
846
|
+
if not self.is_money:
|
847
|
+
throw(AssertionError, self._is_money)
|
848
|
+
|
849
|
+
# Judge.
|
850
|
+
amount_str: str = search(r'<feedesc><![CDATA[¥([\d.,]+)]]></feedesc>', self.data)
|
851
|
+
self._money_amount = float(amount_str)
|
852
|
+
|
853
|
+
return self._money_amount
|
854
|
+
|
855
|
+
|
856
|
+
@property
|
857
|
+
def is_app(self) -> bool:
|
858
|
+
"""
|
859
|
+
Whether if is application share.
|
860
|
+
|
861
|
+
Returns
|
862
|
+
-------
|
863
|
+
Judge result.
|
864
|
+
"""
|
865
|
+
|
866
|
+
# Cache.
|
867
|
+
if self._is_app is not None:
|
868
|
+
return self._is_app
|
869
|
+
|
870
|
+
# Judge.
|
871
|
+
self._is_app = (
|
872
|
+
self.type == 49
|
873
|
+
and search('<appname>[^<>]+</appname>', self.data) is not None
|
874
|
+
)
|
875
|
+
|
876
|
+
return self._is_app
|
877
|
+
|
878
|
+
|
385
879
|
@property
|
386
880
|
def at_names(self) -> list[str]:
|
387
881
|
"""
|
@@ -428,9 +922,9 @@ class WeChatMessage(WeChatBase):
|
|
428
922
|
|
429
923
|
|
430
924
|
@property
|
431
|
-
def
|
925
|
+
def is_at_me(self) -> bool:
|
432
926
|
"""
|
433
|
-
Whether if is message of `@`
|
927
|
+
Whether if is message of `@` me.
|
434
928
|
|
435
929
|
Returns
|
436
930
|
-------
|
@@ -438,13 +932,13 @@ class WeChatMessage(WeChatBase):
|
|
438
932
|
"""
|
439
933
|
|
440
934
|
# Cache.
|
441
|
-
if self.
|
442
|
-
return self.
|
935
|
+
if self._is_at_me is not None:
|
936
|
+
return self._is_at_me
|
443
937
|
|
444
938
|
# Judge.
|
445
|
-
self.
|
939
|
+
self._is_at_me = self.receiver.wechat.client.login_info['name'] in self.at_names
|
446
940
|
|
447
|
-
return self.
|
941
|
+
return self._is_at_me
|
448
942
|
|
449
943
|
|
450
944
|
@property
|
@@ -462,25 +956,24 @@ class WeChatMessage(WeChatBase):
|
|
462
956
|
return self._is_call
|
463
957
|
|
464
958
|
# Text.
|
465
|
-
if self.type
|
466
|
-
text = self.
|
467
|
-
|
468
|
-
text = self.quote_params['text']
|
959
|
+
if self.type in (1, 3, 34, 42, 43, 47, 48, 49, 50, 56, 1002):
|
960
|
+
text = self.text
|
961
|
+
text = text.strip()
|
469
962
|
else:
|
470
963
|
self._is_call = False
|
471
964
|
self._call_text = None
|
472
965
|
return self._is_call
|
473
966
|
text = text.strip()
|
474
967
|
|
475
|
-
## At
|
476
|
-
|
477
|
-
if
|
478
|
-
|
479
|
-
text = text.replace(
|
968
|
+
## At me.
|
969
|
+
at_me_keyword = '@%s\u2005' % self.receiver.wechat.client.login_info['name']
|
970
|
+
if at_me_keyword in text:
|
971
|
+
is_at_me = True
|
972
|
+
text = text.replace(at_me_keyword, '')
|
480
973
|
else:
|
481
|
-
|
974
|
+
is_at_me = False
|
482
975
|
|
483
|
-
## Call
|
976
|
+
## Call me.
|
484
977
|
pattern = fr'^{self.receiver.call_name}[\s,,]*(.*)$'
|
485
978
|
result: str | None = search(pattern, text)
|
486
979
|
if result is not None:
|
@@ -499,13 +992,13 @@ class WeChatMessage(WeChatBase):
|
|
499
992
|
or self.room is None
|
500
993
|
|
501
994
|
## At self.
|
502
|
-
or
|
995
|
+
or is_at_me
|
503
996
|
|
504
997
|
## Call self.
|
505
998
|
or is_call_name
|
506
999
|
|
507
|
-
## Quote
|
508
|
-
or self.
|
1000
|
+
## Quote me.
|
1001
|
+
or self.is_quote_me
|
509
1002
|
|
510
1003
|
):
|
511
1004
|
is_call = True
|
@@ -549,13 +1042,13 @@ class WeChatMessage(WeChatBase):
|
|
549
1042
|
|
550
1043
|
# Check.
|
551
1044
|
if not self.is_call:
|
552
|
-
throw(AssertionError, self.
|
1045
|
+
throw(AssertionError, self._is_call)
|
553
1046
|
|
554
1047
|
return self._call_text
|
555
1048
|
|
556
1049
|
|
557
1050
|
@property
|
558
|
-
def is_call_next(self) ->
|
1051
|
+
def is_call_next(self) -> bool:
|
559
1052
|
"""
|
560
1053
|
Whether if is message of call next message.
|
561
1054
|
|
@@ -575,7 +1068,7 @@ class WeChatMessage(WeChatBase):
|
|
575
1068
|
|
576
1069
|
|
577
1070
|
@property
|
578
|
-
def is_last_call(self) ->
|
1071
|
+
def is_last_call(self) -> bool:
|
579
1072
|
"""
|
580
1073
|
Whether if is message of last message call this time.
|
581
1074
|
|
@@ -692,7 +1185,7 @@ class WeChatMessage(WeChatBase):
|
|
692
1185
|
|
693
1186
|
# Extract.
|
694
1187
|
pattern = '邀请"(.+?)"加入了群聊'
|
695
|
-
result = search(pattern, self.data)
|
1188
|
+
result: str = search(pattern, self.data)
|
696
1189
|
self._new_room_user_name = result
|
697
1190
|
|
698
1191
|
return result
|
@@ -737,7 +1230,7 @@ class WeChatMessage(WeChatBase):
|
|
737
1230
|
|
738
1231
|
# Extract.
|
739
1232
|
pattern = '修改群名为“(.+?)”'
|
740
|
-
result = search(pattern, self.data)
|
1233
|
+
result: str = search(pattern, self.data)
|
741
1234
|
self._change_room_name = result
|
742
1235
|
|
743
1236
|
return self._change_room_name
|
@@ -791,26 +1284,6 @@ class WeChatMessage(WeChatBase):
|
|
791
1284
|
return self._is_dissolve_room
|
792
1285
|
|
793
1286
|
|
794
|
-
@property
|
795
|
-
def is_image(self) -> bool:
|
796
|
-
"""
|
797
|
-
Whether if is image.
|
798
|
-
|
799
|
-
Returns
|
800
|
-
-------
|
801
|
-
Judge result.
|
802
|
-
"""
|
803
|
-
|
804
|
-
# Cache.
|
805
|
-
if self._is_image is not None:
|
806
|
-
return self._is_image
|
807
|
-
|
808
|
-
# Judge.
|
809
|
-
self._is_image = self.type == 3
|
810
|
-
|
811
|
-
return self._is_image
|
812
|
-
|
813
|
-
|
814
1287
|
@property
|
815
1288
|
def image_qrcodes(self) -> list[str]:
|
816
1289
|
"""
|
@@ -821,13 +1294,13 @@ class WeChatMessage(WeChatBase):
|
|
821
1294
|
Image QR code texts.
|
822
1295
|
"""
|
823
1296
|
|
824
|
-
#
|
1297
|
+
# Cache.
|
825
1298
|
if self._image_qrcodes is not None:
|
826
1299
|
return self._image_qrcodes
|
827
1300
|
|
828
1301
|
# Check.
|
829
|
-
if
|
830
|
-
throw(AssertionError, self.
|
1302
|
+
if self.type != 3:
|
1303
|
+
throw(AssertionError, self.type)
|
831
1304
|
|
832
1305
|
# Extract.
|
833
1306
|
self._image_qrcodes = decode_qrcode(self.file['path'])
|
@@ -836,9 +1309,9 @@ class WeChatMessage(WeChatBase):
|
|
836
1309
|
|
837
1310
|
|
838
1311
|
@property
|
839
|
-
def
|
1312
|
+
def is_html(self) -> bool:
|
840
1313
|
"""
|
841
|
-
Whether if is
|
1314
|
+
Whether if is HTML format.
|
842
1315
|
|
843
1316
|
Returns
|
844
1317
|
-------
|
@@ -846,22 +1319,22 @@ class WeChatMessage(WeChatBase):
|
|
846
1319
|
"""
|
847
1320
|
|
848
1321
|
# Cache.
|
849
|
-
if self.
|
850
|
-
return self.
|
1322
|
+
if self._is_html is not None:
|
1323
|
+
return self._is_html
|
851
1324
|
|
852
1325
|
# Judge.
|
853
|
-
self.
|
1326
|
+
self._is_html = (
|
854
1327
|
self.type != 1
|
855
|
-
and
|
1328
|
+
and search(r'^<(\S+)[ >].*</\1>\s*', self.data) is not None
|
856
1329
|
)
|
857
1330
|
|
858
|
-
return self.
|
1331
|
+
return self._is_html
|
859
1332
|
|
860
1333
|
|
861
1334
|
@property
|
862
|
-
def
|
1335
|
+
def is_xml(self) -> bool:
|
863
1336
|
"""
|
864
|
-
Whether if is
|
1337
|
+
Whether if is XML format.
|
865
1338
|
|
866
1339
|
Returns
|
867
1340
|
-------
|
@@ -869,50 +1342,17 @@ class WeChatMessage(WeChatBase):
|
|
869
1342
|
"""
|
870
1343
|
|
871
1344
|
# Cache.
|
872
|
-
if self.
|
873
|
-
return self.
|
1345
|
+
if self._is_xml is not None:
|
1346
|
+
return self._is_xml
|
874
1347
|
|
875
1348
|
# Judge.
|
876
|
-
self.
|
877
|
-
self.type
|
878
|
-
and self.
|
879
|
-
and
|
880
|
-
)
|
881
|
-
|
882
|
-
return self.is_app
|
883
|
-
|
884
|
-
|
885
|
-
@property
|
886
|
-
def app_params(self) -> dict:
|
887
|
-
"""
|
888
|
-
Return application share parameters.
|
889
|
-
|
890
|
-
Returns
|
891
|
-
-------
|
892
|
-
Application share parameters.
|
893
|
-
"""
|
894
|
-
|
895
|
-
# Extracted.
|
896
|
-
if self._app_params is not None:
|
897
|
-
return self._app_params
|
898
|
-
|
899
|
-
# Check.
|
900
|
-
if not self.is_app:
|
901
|
-
throw(AssertionError, self.is_app)
|
902
|
-
|
903
|
-
# Extract.
|
904
|
-
bs_document = BSBeautifulSoup(
|
905
|
-
self.data,
|
906
|
-
'xml'
|
1349
|
+
self._is_xml = (
|
1350
|
+
self.type != 1
|
1351
|
+
and self.data.startswith('<?xml ')
|
1352
|
+
and self.data.rstrip().endswith('</msg>')
|
907
1353
|
)
|
908
|
-
bs_appmsg = bs_document.find('appmsg')
|
909
|
-
self._app_params = {
|
910
|
-
bs_element.name: bs_element.text
|
911
|
-
for bs_element in bs_appmsg.contents
|
912
|
-
if type(bs_element) == BSTag
|
913
|
-
}
|
914
1354
|
|
915
|
-
return self.
|
1355
|
+
return self._is_xml
|
916
1356
|
|
917
1357
|
|
918
1358
|
@property
|
@@ -1336,17 +1776,7 @@ class WechatReceiver(WeChatBase):
|
|
1336
1776
|
)
|
1337
1777
|
|
1338
1778
|
## Other.
|
1339
|
-
case 49:
|
1340
|
-
|
1341
|
-
### Check.
|
1342
|
-
pattern = r'^.+? : \[文件\](.+)$'
|
1343
|
-
file_name: str | None = search(pattern, message.display)
|
1344
|
-
if (
|
1345
|
-
file_name is None
|
1346
|
-
or '<type>6</type>' not in message.data
|
1347
|
-
):
|
1348
|
-
return
|
1349
|
-
|
1779
|
+
case 49 if message.is_file_uploaded:
|
1350
1780
|
pattern = r'<md5>([\da-f]{32})</md5>'
|
1351
1781
|
file_md5: str = search(pattern, message.data)
|
1352
1782
|
cache_path = self.wechat.cache.index(file_md5, file_name, copy=True)
|
@@ -3,14 +3,14 @@ reywechat/rall.py,sha256=5J_X-XMOyb1Vp1jyS9-oRFXGOtp2vRPX1g3tJot_Eck,371
|
|
3
3
|
reywechat/rbase.py,sha256=hbxn5spvcl_C_Bw8A9teulOXT9GMlxUw145_YbXIOzc,1124
|
4
4
|
reywechat/rcache.py,sha256=7UsHHfgFOgxuSqlTSAO6CprgUUOeBCXYus0kxmRBQxk,908
|
5
5
|
reywechat/rclient.py,sha256=lc1CPle9h08mwP8NlJN0ybzcNJxtpV0ma6q6cz1RIxk,22518
|
6
|
-
reywechat/rdb.py,sha256=
|
6
|
+
reywechat/rdb.py,sha256=KfGo6OlTwh4p78VCGqw8lI6cNFGM2CSlpp1OpF7sqSI,48342
|
7
7
|
reywechat/rlog.py,sha256=4EsTgrgC05JvWeKAucUaWGga638CRRJISJN6qncBCAw,5249
|
8
|
-
reywechat/rreceive.py,sha256=
|
8
|
+
reywechat/rreceive.py,sha256=AubilVG1e95gukkhlFL9V7WjhRTbkGaqPxSPehTbf4Q,48225
|
9
9
|
reywechat/rsend.py,sha256=aTmc1ycL8bon4KXoX7VpRWQKTsVER6f6bRLKWWvPspY,18049
|
10
10
|
reywechat/rtrigger.py,sha256=WdOQwobPdGPyyE9J-qtQFPd60713T0aWqKk02PLdCRE,4966
|
11
11
|
reywechat/rwechat.py,sha256=g0pbprMPv_qWb_lGFrPDAWsJO4vPSIgFLkw0Y28CZUo,4751
|
12
12
|
reywechat/data/client_api.dll,sha256=H9uj-x9Ztg0jFZK0yY6NsnyH5_119dQRFfoVVMidxRs,592384
|
13
|
-
reywechat-1.0.
|
14
|
-
reywechat-1.0.
|
15
|
-
reywechat-1.0.
|
16
|
-
reywechat-1.0.
|
13
|
+
reywechat-1.0.54.dist-info/METADATA,sha256=qDFNVoRlHH9WQ1ej-wQR_eTifKOeGmn8eNo6aTjlWmA,1551
|
14
|
+
reywechat-1.0.54.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
15
|
+
reywechat-1.0.54.dist-info/licenses/LICENSE,sha256=UYLPqp7BvPiH8yEZduJqmmyEl6hlM3lKrFIefiD4rvk,1059
|
16
|
+
reywechat-1.0.54.dist-info/RECORD,,
|
File without changes
|
File without changes
|