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.
reywechat/rsend.py ADDED
@@ -0,0 +1,630 @@
1
+ # !/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """
5
+ @Time : 2024-01-03 22:53:18
6
+ @Author : Rey
7
+ @Contact : reyxbo@163.com
8
+ @Explain : Send methods.
9
+ """
10
+
11
+
12
+ from __future__ import annotations
13
+ from typing import Any, Literal, Optional, Union, NoReturn, overload
14
+ from collections.abc import Callable
15
+ from functools import wraps as functools_wraps
16
+ from os.path import join as os_join
17
+ from queue import Queue
18
+ from re import escape as re_escape
19
+ from reykit.rcomm import get_file_stream_time
20
+ from reykit.rexception import throw, catch_exc
21
+ from reykit.rrandom import randn
22
+ from reykit.rregex import sub
23
+ from reykit.ros import RFile
24
+ from reykit.rtime import sleep
25
+ from reykit.rwrap import wrap_thread, wrap_exc
26
+
27
+ from .rexception import RWeChatExecuteContinueError, RWeChatExecuteBreakError
28
+ from .rwechat import RWeChat
29
+
30
+
31
+ __all__ = (
32
+ 'RSendParam',
33
+ 'RSend'
34
+ )
35
+
36
+
37
+ class RSendParam(object):
38
+ """
39
+ Rey's `send parameters` type.
40
+ """
41
+
42
+
43
+ def __init__(
44
+ self,
45
+ rsend: RSend,
46
+ send_type: Literal[0, 1, 2, 3, 4, 5, 6, 7],
47
+ receive_id: str,
48
+ params: dict,
49
+ send_id: Optional[int]
50
+ ) -> None:
51
+ """
52
+ Build `send parameters` attributes.
53
+
54
+ Parameters
55
+ ----------
56
+ rsend : `RSend` instance.
57
+ send_type : Send type.
58
+ - `Literal[0]` Send text message, use `RClient.send_text`: method.
59
+ - `Literal[1]` Send text message with `@`, use `RClient.send_text_at`: method.
60
+ - `Literal[2]` Send file message, use `RClient.send_file`: method.
61
+ - `Literal[3]` Send image message, use `RClient.send_image`: method.
62
+ - `Literal[4]` Send emotion message, use `RClient.send_emotion`: method.
63
+ - `Literal[5]` Send pat message, use `RClient.send_pat`: method.
64
+ - `Literal[6]` Send public account message, use `RClient.send_public`: method.
65
+ - `Literal[7]` Forward message, use `RClient.send_forward`: method.
66
+ receive_id : User ID or chat room ID of receive message.
67
+ params : Send parameters.
68
+ send_id : Send ID of database.
69
+ """
70
+
71
+ # Set attribute.
72
+ self.rsend = rsend
73
+ self.send_type = send_type
74
+ self.receive_id = receive_id
75
+ self.params = params
76
+ self.send_id = send_id
77
+ self.cache_path: Optional[str] = None
78
+ self.exc_reports: list[str] = []
79
+
80
+
81
+ class RSend(object):
82
+ """
83
+ Rey's `send` type.
84
+ """
85
+
86
+
87
+ def __init__(
88
+ self,
89
+ rwechat: RWeChat,
90
+ bandwidth_upstream: float
91
+ ) -> None:
92
+ """
93
+ Build `send` attributes.
94
+
95
+ Parameters
96
+ ----------
97
+ rwechat : `RClient` instance.
98
+ bandwidth_upstream : Upload bandwidth, impact send interval, unit Mpbs.
99
+ """
100
+
101
+ # Set attribute.
102
+ self.rwechat = rwechat
103
+ self.bandwidth_upstream = bandwidth_upstream
104
+ self.queue: Queue[RSendParam] = Queue()
105
+ self.handlers: list[Callable[[RSendParam], Any]] = []
106
+ self.started: Optional[bool] = False
107
+
108
+ # Start.
109
+ self._delete_cache_file()
110
+ self._start_sender()
111
+
112
+
113
+ @wrap_thread
114
+ def _start_sender(self) -> None:
115
+ """
116
+ Start sender, that will sequentially send message in the send queue.
117
+ """
118
+
119
+ # Loop.
120
+ while True:
121
+ match self.started:
122
+
123
+ ## Stop.
124
+ case False:
125
+ sleep(0.1)
126
+ continue
127
+
128
+ ## End.
129
+ case None:
130
+ break
131
+
132
+ ## Send.
133
+ rsparam = self.queue.get()
134
+ try:
135
+ self._send(rsparam)
136
+
137
+ ## Exception.
138
+ except:
139
+
140
+ # Catch exception.
141
+ exc_report, *_ = catch_exc()
142
+
143
+ # Save.
144
+ rsparam.exc_reports.append(exc_report)
145
+
146
+
147
+ ## Handle.
148
+
149
+ ### Define.
150
+ def handle_handler_exception() -> None:
151
+ """
152
+ Handle Handler exception.
153
+ """
154
+
155
+ # Catch exception.
156
+ exc_report, *_ = catch_exc()
157
+
158
+ # Save.
159
+ rsparam.exc_reports.append(exc_report)
160
+
161
+
162
+ ### Loop.
163
+ for handler in self.handlers:
164
+ wrap_exc(
165
+ handler,
166
+ rsparam,
167
+ _handler=handle_handler_exception
168
+ )
169
+
170
+ ## Log.
171
+ self.rwechat.rlog.log_send(rsparam)
172
+
173
+
174
+ def _delete_cache_file(self) -> None:
175
+ """
176
+ Add handler, Delete cache file.
177
+ """
178
+
179
+
180
+ # Define.
181
+ def handler_delete_cache_file(rsparam: RSendParam) -> None:
182
+ """
183
+ Delete cache file.
184
+
185
+ Parameters
186
+ ----------
187
+ rsparam : `RSendParams` instance.
188
+ """
189
+
190
+ # Break.
191
+ if rsparam.cache_path is None: return
192
+
193
+ # Delete.
194
+ rfile = RFile(rsparam.cache_path)
195
+ rfile.remove()
196
+
197
+
198
+ # Add handler.
199
+ self.add_handler(handler_delete_cache_file)
200
+
201
+
202
+ def _send(
203
+ self,
204
+ rsparam: RSendParam
205
+ ) -> None:
206
+ """
207
+ Send message.
208
+
209
+ Parameters
210
+ ----------
211
+ rsparam : `RSendParams` instance.
212
+ """
213
+
214
+ # Handle parameter.
215
+ for key, value in rsparam.params.items():
216
+
217
+ ## Callable.
218
+ if callable(value):
219
+ rsparam.params[key] = value()
220
+
221
+ # File.
222
+
223
+ ## From file ID.
224
+ if (file_id := rsparam.params.get('file_id')) is not None:
225
+ rsparam.params['path'], rsparam.params['file_name'] = self.rwechat.rdatabase._download_file(file_id)
226
+
227
+ ## Set file name.
228
+ if (
229
+ (path := rsparam.params.get('path')) is not None
230
+ and (file_name := rsparam.params.get('file_name')) is not None
231
+ ):
232
+ rfile = RFile(path)
233
+ copy_path = os_join(self.rwechat.dir_file, file_name)
234
+ rfile.copy(copy_path)
235
+ rsparam.cache_path = copy_path
236
+ path = copy_path
237
+
238
+ # Send.
239
+ match rsparam.send_type:
240
+
241
+ ## Text.
242
+ case 0:
243
+ self.rwechat.rclient.send_text(
244
+ rsparam.receive_id,
245
+ rsparam.params['text']
246
+ )
247
+
248
+ ## Text with '@'.
249
+ case 1:
250
+ self.rwechat.rclient.send_text_at(
251
+ rsparam.receive_id,
252
+ rsparam.params['user_id'],
253
+ rsparam.params['text']
254
+ )
255
+
256
+ ## File.
257
+ case 2:
258
+ self.rwechat.rclient.send_file(
259
+ rsparam.receive_id,
260
+ path
261
+ )
262
+
263
+ ## Image.
264
+ case 3:
265
+ self.rwechat.rclient.send_image(
266
+ rsparam.receive_id,
267
+ path
268
+ )
269
+
270
+ ## Emotion.
271
+ case 4:
272
+ self.rwechat.rclient.send_emotion(
273
+ rsparam.receive_id,
274
+ path
275
+ )
276
+
277
+ ## Pat.
278
+ case 5:
279
+ self.rwechat.rclient.send_pat(
280
+ rsparam.receive_id,
281
+ rsparam.params['user_id']
282
+ )
283
+
284
+ ## Public account.
285
+ case 6:
286
+ self.rwechat.rclient.send_public(
287
+ rsparam.receive_id,
288
+ rsparam.params['page_url'],
289
+ rsparam.params['title'],
290
+ rsparam.params['text'],
291
+ rsparam.params['image_url'],
292
+ rsparam.params['public_name'],
293
+ rsparam.params['public_id']
294
+ )
295
+
296
+ ## Forward.
297
+ case 7:
298
+ self.rwechat.rclient.send_forward(
299
+ rsparam.receive_id,
300
+ rsparam.params['message_id']
301
+ )
302
+
303
+ ## Throw exception.
304
+ case _:
305
+ throw(ValueError, rsparam.send_type)
306
+
307
+ # Wait.
308
+ self._wait(rsparam)
309
+
310
+
311
+ def _wait(
312
+ self,
313
+ rsparam: RSendParam
314
+ ) -> None:
315
+ """
316
+ Waiting after sending.
317
+
318
+ Parameters
319
+ ----------
320
+ rsparam : `RSendParams` instance.
321
+ """
322
+
323
+ # Get parameter.
324
+ seconds = randn(0.8, 1.2, precision=2)
325
+
326
+ ## File.
327
+ if rsparam.send_type in (2, 3, 4):
328
+ stream_time = get_file_stream_time(rsparam.params['path'], self.bandwidth_upstream)
329
+ if stream_time > seconds:
330
+ seconds = stream_time
331
+
332
+ # Wait.
333
+ sleep(seconds)
334
+
335
+
336
+ @overload
337
+ def send(
338
+ self,
339
+ send_type: Literal[0],
340
+ receive_id: str,
341
+ send_id: Optional[int] = None,
342
+ *,
343
+ text: str
344
+ ) -> None: ...
345
+
346
+ @overload
347
+ def send(
348
+ self,
349
+ send_type: Literal[1],
350
+ receive_id: str,
351
+ send_id: Optional[int] = None,
352
+ *,
353
+ user_id: Union[str, list[str], Literal['notify@all']],
354
+ text: str
355
+ ) -> None: ...
356
+
357
+ @overload
358
+ def send(
359
+ self,
360
+ send_type: Literal[2, 3, 4],
361
+ receive_id: str,
362
+ send_id: Optional[int] = None,
363
+ *,
364
+ path: str,
365
+ file_name: Optional[str] = None
366
+ ) -> None: ...
367
+
368
+ @overload
369
+ def send(
370
+ self,
371
+ send_type: Literal[5],
372
+ receive_id: str,
373
+ send_id: Optional[int] = None,
374
+ *,
375
+ user_id: str
376
+ ) -> None: ...
377
+
378
+ @overload
379
+ def send(
380
+ self,
381
+ send_type: Literal[6],
382
+ receive_id: str,
383
+ send_id: Optional[int] = None,
384
+ *,
385
+ page_url: str,
386
+ title: str,
387
+ text: Optional[str] = None,
388
+ image_url: Optional[str] = None,
389
+ public_name: Optional[str] = None,
390
+ public_id: Optional[str] = None
391
+ ) -> None: ...
392
+
393
+ @overload
394
+ def send(
395
+ self,
396
+ send_type: Literal[7],
397
+ receive_id: str,
398
+ send_id: Optional[int] = None,
399
+ *,
400
+ message_id: str
401
+ ) -> None: ...
402
+
403
+ @overload
404
+ def send(
405
+ self,
406
+ send_type: Any,
407
+ receive_id: str,
408
+ send_id: Optional[int] = None,
409
+ **params: Any
410
+ ) -> NoReturn: ...
411
+
412
+ def send(
413
+ self,
414
+ send_type: Optional[Literal[0, 1, 2, 3, 4, 5, 6, 7]] = None,
415
+ receive_id: Optional[str] = None,
416
+ send_id: Optional[int] = None,
417
+ **params: Any
418
+ ) -> None:
419
+ """
420
+ Put parameters into the send queue.
421
+
422
+ Parameters
423
+ ----------
424
+ send_type : Send type.
425
+ - `Literal[0]` Send text message, use `RClient.send_text`: method.
426
+ - `Literal[1]` Send text message with `@`, use `RClient.send_text_at`: method.
427
+ - `Literal[2]` Send file message, use `RClient.send_file`: method.
428
+ - `Literal[3]` Send image message, use `RClient.send_image`: method.
429
+ - `Literal[4]` Send emotion message, use `RClient.send_emotion`: method.
430
+ - `Literal[5]` Send pat message, use `RClient.send_pat`: method.
431
+ - `Literal[6]` Send public account message, use `RClient.send_public`: method.
432
+ - `Literal[7]` Forward message, use `RClient.send_forward`: method.
433
+ receive_id : User ID or chat room ID of receive message.
434
+ send_id : Send ID of database.
435
+ params : Send parameters.
436
+ - `Callable`: Use execute return value.
437
+ - `Any`: Use this value.
438
+ `Key 'file_name'`: Given file name.
439
+ """
440
+
441
+ # Check.
442
+ if send_type not in (0, 1, 2, 3, 4, 5, 6, 7):
443
+ throw(ValueError, send_type)
444
+
445
+ rsparam = RSendParam(
446
+ self,
447
+ send_type,
448
+ receive_id,
449
+ params,
450
+ send_id
451
+ )
452
+
453
+ # Put.
454
+ self.queue.put(rsparam)
455
+
456
+
457
+ def add_handler(
458
+ self,
459
+ handler: Callable[[RSendParam], Any]
460
+ ) -> None:
461
+ """
462
+ Add send handler function.
463
+
464
+ Parameters
465
+ ----------
466
+ handler : Handler method, input parameter is `RSendParam` instance.
467
+ """
468
+
469
+ # Add.
470
+ self.handlers.append(handler)
471
+
472
+
473
+ def add_at(
474
+ self,
475
+ text: str,
476
+ room_id: str
477
+ ) -> str:
478
+ """
479
+ Based on the user name in the text, automatic add `@` format.
480
+
481
+ Parameters
482
+ ----------
483
+ text : Text.
484
+ room_id : Chat room ID.
485
+
486
+ Returns
487
+ -------
488
+ Added text.
489
+ """
490
+
491
+ # Get parameter.
492
+ member_dict = self.rwechat.rclient.get_room_member_dict(room_id)
493
+ login_id = self.rwechat.rclient.login_info['id']
494
+ if login_id in member_dict:
495
+ del member_dict[login_id]
496
+
497
+ # Add.
498
+ names = [
499
+ re_escape(name)
500
+ for name in member_dict.values()
501
+ if len(name) != 1
502
+ ]
503
+ pattern = '(?<!@)(%s) *' % '|'.join(names)
504
+ replace = lambda match: '@%s ' % match[1]
505
+ text_at = sub(pattern, text, replace)
506
+
507
+ return text_at
508
+
509
+
510
+ def wrap_try_send(
511
+ self,
512
+ receive_id: Union[str, list[str]],
513
+ func: Callable
514
+ ) -> Callable:
515
+ """
516
+ Decorator, send exception information.
517
+
518
+ Parameters
519
+ ----------
520
+ receive_id : Receive user ID or chat room ID.
521
+ - `str`: An ID.
522
+ - `list[str]`: Multiple ID.
523
+ func : Function.
524
+
525
+ Returns
526
+ -------
527
+ Decorated function.
528
+ """
529
+
530
+ # Handle parameter.
531
+ if receive_id.__class__ == str:
532
+ receive_ids = [receive_id]
533
+ else:
534
+ receive_ids = receive_id
535
+
536
+ # Define.
537
+ @functools_wraps(func)
538
+ def wrap(
539
+ *arg: Any,
540
+ **kwargs: Any
541
+ ) -> Any:
542
+ """
543
+ Decorate.
544
+
545
+ Parameters
546
+ ----------
547
+ args : Position arguments of decorated function.
548
+ kwargs : Keyword arguments of decorated function.
549
+
550
+ Returns
551
+ -------
552
+ Function execution result.
553
+ """
554
+
555
+ # Execute.
556
+ try:
557
+ result = func(
558
+ *arg,
559
+ **kwargs
560
+ )
561
+ except:
562
+ *_, exc_instance, _ = catch_exc()
563
+
564
+ # Report.
565
+ if not isinstance(
566
+ exc_instance,
567
+ (RWeChatExecuteContinueError, RWeChatExecuteBreakError)
568
+ ):
569
+ text = '\n'.join(
570
+ [
571
+ str(arg)
572
+ for arg in exc_instance.args
573
+ ]
574
+ )
575
+ for receive_id in receive_ids:
576
+ self.send(
577
+ 0,
578
+ receive_id,
579
+ text=text
580
+ )
581
+
582
+ # Throw exception.
583
+ raise exc_instance
584
+
585
+ return result
586
+
587
+
588
+ return wrap
589
+
590
+
591
+ def start(self) -> None:
592
+ """
593
+ Start sender.
594
+ """
595
+
596
+ # Start.
597
+ self.started = True
598
+
599
+ # Report.
600
+ print('Start sender.')
601
+
602
+
603
+ def stop(self) -> None:
604
+ """
605
+ Stop sender.
606
+ """
607
+
608
+ # Stop.
609
+ self.started = False
610
+
611
+ # Report.
612
+ print('Stop sender.')
613
+
614
+
615
+ def end(self) -> None:
616
+ """
617
+ End sender.
618
+ """
619
+
620
+ # End.
621
+ self.started = None
622
+
623
+ # Report.
624
+ print('End sender.')
625
+
626
+
627
+ __call__ = send
628
+
629
+
630
+ __del__ = end