reykit 1.0.0__py3-none-any.whl → 1.0.1__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.
- reydb/__init__.py +25 -0
- reydb/rall.py +17 -0
- reydb/rbuild.py +1235 -0
- reydb/rconnection.py +2276 -0
- reydb/rexecute.py +354 -0
- reydb/rfile.py +416 -0
- reydb/rinformation.py +515 -0
- reydb/rparameter.py +243 -0
- reykit/__init__.py +1 -1
- reykit/rrandom.py +40 -9
- {reykit-1.0.0.dist-info → reykit-1.0.1.dist-info}/METADATA +1 -1
- reykit-1.0.1.dist-info/RECORD +64 -0
- reyweb/__init__.py +19 -0
- reyweb/rall.py +12 -0
- reyweb/rbaidu/__init__.py +21 -0
- reyweb/rbaidu/rbaidu_base.py +186 -0
- reyweb/rbaidu/rbaidu_chat.py +299 -0
- reyweb/rbaidu/rbaidu_image.py +183 -0
- reyweb/rbaidu/rbaidu_voice.py +256 -0
- reywechat/__init__.py +32 -0
- reywechat/data/client_api.dll +0 -0
- reywechat/rall.py +20 -0
- reywechat/rclient.py +912 -0
- reywechat/rdatabase.py +1189 -0
- reywechat/rexception.py +65 -0
- reywechat/rexecute.py +201 -0
- reywechat/rlog.py +198 -0
- reywechat/rreceive.py +1232 -0
- reywechat/rschedule.py +136 -0
- reywechat/rsend.py +630 -0
- reywechat/rwechat.py +201 -0
- reyworm/__init__.py +24 -0
- reyworm/rall.py +16 -0
- reyworm/rbrowser.py +134 -0
- reyworm/rcalendar.py +159 -0
- reyworm/rnews.py +126 -0
- reyworm/rsecurity.py +239 -0
- reyworm/rtranslate.py +75 -0
- reykit-1.0.0.dist-info/RECORD +0 -30
- {reykit-1.0.0.dist-info → reykit-1.0.1.dist-info}/WHEEL +0 -0
- {reykit-1.0.0.dist-info → reykit-1.0.1.dist-info}/top_level.txt +0 -0
reywechat/rreceive.py
ADDED
@@ -0,0 +1,1232 @@
|
|
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
|