pyxllib 0.3.96__py3-none-any.whl → 0.3.197__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.
- pyxllib/algo/geo.py +12 -0
- pyxllib/algo/intervals.py +1 -1
- pyxllib/algo/matcher.py +78 -0
- pyxllib/algo/pupil.py +187 -19
- pyxllib/algo/specialist.py +2 -1
- pyxllib/algo/stat.py +38 -2
- {pyxlpr → pyxllib/autogui}/__init__.py +1 -1
- pyxllib/autogui/activewin.py +246 -0
- pyxllib/autogui/all.py +9 -0
- pyxllib/{ext/autogui → autogui}/autogui.py +40 -11
- pyxllib/autogui/uiautolib.py +362 -0
- pyxllib/autogui/wechat.py +827 -0
- pyxllib/autogui/wechat_msg.py +421 -0
- pyxllib/autogui/wxautolib.py +84 -0
- pyxllib/cv/slidercaptcha.py +137 -0
- pyxllib/data/echarts.py +123 -12
- pyxllib/data/jsonlib.py +89 -0
- pyxllib/data/pglib.py +514 -30
- pyxllib/data/sqlite.py +231 -4
- pyxllib/ext/JLineViewer.py +14 -1
- pyxllib/ext/drissionlib.py +277 -0
- pyxllib/ext/kq5034lib.py +0 -1594
- pyxllib/ext/robustprocfile.py +497 -0
- pyxllib/ext/unixlib.py +6 -5
- pyxllib/ext/utools.py +108 -95
- pyxllib/ext/webhook.py +32 -14
- pyxllib/ext/wjxlib.py +88 -0
- pyxllib/ext/wpsapi.py +124 -0
- pyxllib/ext/xlwork.py +9 -0
- pyxllib/ext/yuquelib.py +1003 -71
- pyxllib/file/docxlib.py +1 -1
- pyxllib/file/libreoffice.py +165 -0
- pyxllib/file/movielib.py +9 -0
- pyxllib/file/packlib/__init__.py +112 -75
- pyxllib/file/pdflib.py +1 -1
- pyxllib/file/pupil.py +1 -1
- pyxllib/file/specialist/dirlib.py +1 -1
- pyxllib/file/specialist/download.py +10 -3
- pyxllib/file/specialist/filelib.py +266 -55
- pyxllib/file/xlsxlib.py +205 -50
- pyxllib/file/xlsyncfile.py +341 -0
- pyxllib/prog/cachetools.py +64 -0
- pyxllib/prog/filelock.py +42 -0
- pyxllib/prog/multiprogs.py +940 -0
- pyxllib/prog/newbie.py +9 -2
- pyxllib/prog/pupil.py +129 -60
- pyxllib/prog/specialist/__init__.py +176 -2
- pyxllib/prog/specialist/bc.py +5 -2
- pyxllib/prog/specialist/browser.py +11 -2
- pyxllib/prog/specialist/datetime.py +68 -0
- pyxllib/prog/specialist/tictoc.py +12 -13
- pyxllib/prog/specialist/xllog.py +5 -5
- pyxllib/prog/xlosenv.py +7 -0
- pyxllib/text/airscript.js +744 -0
- pyxllib/text/charclasslib.py +17 -5
- pyxllib/text/jiebalib.py +6 -3
- pyxllib/text/jinjalib.py +32 -0
- pyxllib/text/jsa_ai_prompt.md +271 -0
- pyxllib/text/jscode.py +159 -4
- pyxllib/text/nestenv.py +1 -1
- pyxllib/text/newbie.py +12 -0
- pyxllib/text/pupil/common.py +26 -0
- pyxllib/text/specialist/ptag.py +2 -2
- pyxllib/text/templates/echart_base.html +11 -0
- pyxllib/text/templates/highlight_code.html +17 -0
- pyxllib/text/templates/latex_editor.html +103 -0
- pyxllib/text/xmllib.py +76 -14
- pyxllib/xl.py +2 -1
- pyxllib-0.3.197.dist-info/METADATA +48 -0
- pyxllib-0.3.197.dist-info/RECORD +126 -0
- {pyxllib-0.3.96.dist-info → pyxllib-0.3.197.dist-info}/WHEEL +1 -2
- pyxllib/ext/autogui/__init__.py +0 -8
- pyxllib-0.3.96.dist-info/METADATA +0 -51
- pyxllib-0.3.96.dist-info/RECORD +0 -333
- pyxllib-0.3.96.dist-info/top_level.txt +0 -2
- pyxlpr/ai/__init__.py +0 -5
- pyxlpr/ai/clientlib.py +0 -1281
- pyxlpr/ai/specialist.py +0 -286
- pyxlpr/ai/torch_app.py +0 -172
- pyxlpr/ai/xlpaddle.py +0 -655
- pyxlpr/ai/xltorch.py +0 -705
- pyxlpr/data/__init__.py +0 -11
- pyxlpr/data/coco.py +0 -1325
- pyxlpr/data/datacls.py +0 -365
- pyxlpr/data/datasets.py +0 -200
- pyxlpr/data/gptlib.py +0 -1291
- pyxlpr/data/icdar/__init__.py +0 -96
- pyxlpr/data/icdar/deteval.py +0 -377
- pyxlpr/data/icdar/icdar2013.py +0 -341
- pyxlpr/data/icdar/iou.py +0 -340
- pyxlpr/data/icdar/rrc_evaluation_funcs_1_1.py +0 -463
- pyxlpr/data/imtextline.py +0 -473
- pyxlpr/data/labelme.py +0 -866
- pyxlpr/data/removeline.py +0 -179
- pyxlpr/data/specialist.py +0 -57
- pyxlpr/eval/__init__.py +0 -85
- pyxlpr/paddleocr.py +0 -776
- pyxlpr/ppocr/__init__.py +0 -15
- pyxlpr/ppocr/configs/rec/multi_language/generate_multi_language_configs.py +0 -226
- pyxlpr/ppocr/data/__init__.py +0 -135
- pyxlpr/ppocr/data/imaug/ColorJitter.py +0 -26
- pyxlpr/ppocr/data/imaug/__init__.py +0 -67
- pyxlpr/ppocr/data/imaug/copy_paste.py +0 -170
- pyxlpr/ppocr/data/imaug/east_process.py +0 -437
- pyxlpr/ppocr/data/imaug/gen_table_mask.py +0 -244
- pyxlpr/ppocr/data/imaug/iaa_augment.py +0 -114
- pyxlpr/ppocr/data/imaug/label_ops.py +0 -789
- pyxlpr/ppocr/data/imaug/make_border_map.py +0 -184
- pyxlpr/ppocr/data/imaug/make_pse_gt.py +0 -106
- pyxlpr/ppocr/data/imaug/make_shrink_map.py +0 -126
- pyxlpr/ppocr/data/imaug/operators.py +0 -433
- pyxlpr/ppocr/data/imaug/pg_process.py +0 -906
- pyxlpr/ppocr/data/imaug/randaugment.py +0 -143
- pyxlpr/ppocr/data/imaug/random_crop_data.py +0 -239
- pyxlpr/ppocr/data/imaug/rec_img_aug.py +0 -533
- pyxlpr/ppocr/data/imaug/sast_process.py +0 -777
- pyxlpr/ppocr/data/imaug/text_image_aug/__init__.py +0 -17
- pyxlpr/ppocr/data/imaug/text_image_aug/augment.py +0 -120
- pyxlpr/ppocr/data/imaug/text_image_aug/warp_mls.py +0 -168
- pyxlpr/ppocr/data/lmdb_dataset.py +0 -115
- pyxlpr/ppocr/data/pgnet_dataset.py +0 -104
- pyxlpr/ppocr/data/pubtab_dataset.py +0 -107
- pyxlpr/ppocr/data/simple_dataset.py +0 -372
- pyxlpr/ppocr/losses/__init__.py +0 -61
- pyxlpr/ppocr/losses/ace_loss.py +0 -52
- pyxlpr/ppocr/losses/basic_loss.py +0 -135
- pyxlpr/ppocr/losses/center_loss.py +0 -88
- pyxlpr/ppocr/losses/cls_loss.py +0 -30
- pyxlpr/ppocr/losses/combined_loss.py +0 -67
- pyxlpr/ppocr/losses/det_basic_loss.py +0 -208
- pyxlpr/ppocr/losses/det_db_loss.py +0 -80
- pyxlpr/ppocr/losses/det_east_loss.py +0 -63
- pyxlpr/ppocr/losses/det_pse_loss.py +0 -149
- pyxlpr/ppocr/losses/det_sast_loss.py +0 -121
- pyxlpr/ppocr/losses/distillation_loss.py +0 -272
- pyxlpr/ppocr/losses/e2e_pg_loss.py +0 -140
- pyxlpr/ppocr/losses/kie_sdmgr_loss.py +0 -113
- pyxlpr/ppocr/losses/rec_aster_loss.py +0 -99
- pyxlpr/ppocr/losses/rec_att_loss.py +0 -39
- pyxlpr/ppocr/losses/rec_ctc_loss.py +0 -44
- pyxlpr/ppocr/losses/rec_enhanced_ctc_loss.py +0 -70
- pyxlpr/ppocr/losses/rec_nrtr_loss.py +0 -30
- pyxlpr/ppocr/losses/rec_sar_loss.py +0 -28
- pyxlpr/ppocr/losses/rec_srn_loss.py +0 -47
- pyxlpr/ppocr/losses/table_att_loss.py +0 -109
- pyxlpr/ppocr/metrics/__init__.py +0 -44
- pyxlpr/ppocr/metrics/cls_metric.py +0 -45
- pyxlpr/ppocr/metrics/det_metric.py +0 -82
- pyxlpr/ppocr/metrics/distillation_metric.py +0 -73
- pyxlpr/ppocr/metrics/e2e_metric.py +0 -86
- pyxlpr/ppocr/metrics/eval_det_iou.py +0 -274
- pyxlpr/ppocr/metrics/kie_metric.py +0 -70
- pyxlpr/ppocr/metrics/rec_metric.py +0 -75
- pyxlpr/ppocr/metrics/table_metric.py +0 -50
- pyxlpr/ppocr/modeling/architectures/__init__.py +0 -32
- pyxlpr/ppocr/modeling/architectures/base_model.py +0 -88
- pyxlpr/ppocr/modeling/architectures/distillation_model.py +0 -60
- pyxlpr/ppocr/modeling/backbones/__init__.py +0 -54
- pyxlpr/ppocr/modeling/backbones/det_mobilenet_v3.py +0 -268
- pyxlpr/ppocr/modeling/backbones/det_resnet_vd.py +0 -246
- pyxlpr/ppocr/modeling/backbones/det_resnet_vd_sast.py +0 -285
- pyxlpr/ppocr/modeling/backbones/e2e_resnet_vd_pg.py +0 -265
- pyxlpr/ppocr/modeling/backbones/kie_unet_sdmgr.py +0 -186
- pyxlpr/ppocr/modeling/backbones/rec_mobilenet_v3.py +0 -138
- pyxlpr/ppocr/modeling/backbones/rec_mv1_enhance.py +0 -258
- pyxlpr/ppocr/modeling/backbones/rec_nrtr_mtb.py +0 -48
- pyxlpr/ppocr/modeling/backbones/rec_resnet_31.py +0 -210
- pyxlpr/ppocr/modeling/backbones/rec_resnet_aster.py +0 -143
- pyxlpr/ppocr/modeling/backbones/rec_resnet_fpn.py +0 -307
- pyxlpr/ppocr/modeling/backbones/rec_resnet_vd.py +0 -286
- pyxlpr/ppocr/modeling/heads/__init__.py +0 -54
- pyxlpr/ppocr/modeling/heads/cls_head.py +0 -52
- pyxlpr/ppocr/modeling/heads/det_db_head.py +0 -118
- pyxlpr/ppocr/modeling/heads/det_east_head.py +0 -121
- pyxlpr/ppocr/modeling/heads/det_pse_head.py +0 -37
- pyxlpr/ppocr/modeling/heads/det_sast_head.py +0 -128
- pyxlpr/ppocr/modeling/heads/e2e_pg_head.py +0 -253
- pyxlpr/ppocr/modeling/heads/kie_sdmgr_head.py +0 -206
- pyxlpr/ppocr/modeling/heads/multiheadAttention.py +0 -163
- pyxlpr/ppocr/modeling/heads/rec_aster_head.py +0 -393
- pyxlpr/ppocr/modeling/heads/rec_att_head.py +0 -202
- pyxlpr/ppocr/modeling/heads/rec_ctc_head.py +0 -88
- pyxlpr/ppocr/modeling/heads/rec_nrtr_head.py +0 -826
- pyxlpr/ppocr/modeling/heads/rec_sar_head.py +0 -402
- pyxlpr/ppocr/modeling/heads/rec_srn_head.py +0 -280
- pyxlpr/ppocr/modeling/heads/self_attention.py +0 -406
- pyxlpr/ppocr/modeling/heads/table_att_head.py +0 -246
- pyxlpr/ppocr/modeling/necks/__init__.py +0 -32
- pyxlpr/ppocr/modeling/necks/db_fpn.py +0 -111
- pyxlpr/ppocr/modeling/necks/east_fpn.py +0 -188
- pyxlpr/ppocr/modeling/necks/fpn.py +0 -138
- pyxlpr/ppocr/modeling/necks/pg_fpn.py +0 -314
- pyxlpr/ppocr/modeling/necks/rnn.py +0 -92
- pyxlpr/ppocr/modeling/necks/sast_fpn.py +0 -284
- pyxlpr/ppocr/modeling/necks/table_fpn.py +0 -110
- pyxlpr/ppocr/modeling/transforms/__init__.py +0 -28
- pyxlpr/ppocr/modeling/transforms/stn.py +0 -135
- pyxlpr/ppocr/modeling/transforms/tps.py +0 -308
- pyxlpr/ppocr/modeling/transforms/tps_spatial_transformer.py +0 -156
- pyxlpr/ppocr/optimizer/__init__.py +0 -61
- pyxlpr/ppocr/optimizer/learning_rate.py +0 -228
- pyxlpr/ppocr/optimizer/lr_scheduler.py +0 -49
- pyxlpr/ppocr/optimizer/optimizer.py +0 -160
- pyxlpr/ppocr/optimizer/regularizer.py +0 -52
- pyxlpr/ppocr/postprocess/__init__.py +0 -55
- pyxlpr/ppocr/postprocess/cls_postprocess.py +0 -33
- pyxlpr/ppocr/postprocess/db_postprocess.py +0 -234
- pyxlpr/ppocr/postprocess/east_postprocess.py +0 -143
- pyxlpr/ppocr/postprocess/locality_aware_nms.py +0 -200
- pyxlpr/ppocr/postprocess/pg_postprocess.py +0 -52
- pyxlpr/ppocr/postprocess/pse_postprocess/__init__.py +0 -15
- pyxlpr/ppocr/postprocess/pse_postprocess/pse/__init__.py +0 -29
- pyxlpr/ppocr/postprocess/pse_postprocess/pse/setup.py +0 -14
- pyxlpr/ppocr/postprocess/pse_postprocess/pse_postprocess.py +0 -118
- pyxlpr/ppocr/postprocess/rec_postprocess.py +0 -654
- pyxlpr/ppocr/postprocess/sast_postprocess.py +0 -355
- pyxlpr/ppocr/tools/__init__.py +0 -14
- pyxlpr/ppocr/tools/eval.py +0 -83
- pyxlpr/ppocr/tools/export_center.py +0 -77
- pyxlpr/ppocr/tools/export_model.py +0 -129
- pyxlpr/ppocr/tools/infer/predict_cls.py +0 -151
- pyxlpr/ppocr/tools/infer/predict_det.py +0 -300
- pyxlpr/ppocr/tools/infer/predict_e2e.py +0 -169
- pyxlpr/ppocr/tools/infer/predict_rec.py +0 -414
- pyxlpr/ppocr/tools/infer/predict_system.py +0 -204
- pyxlpr/ppocr/tools/infer/utility.py +0 -629
- pyxlpr/ppocr/tools/infer_cls.py +0 -83
- pyxlpr/ppocr/tools/infer_det.py +0 -134
- pyxlpr/ppocr/tools/infer_e2e.py +0 -122
- pyxlpr/ppocr/tools/infer_kie.py +0 -153
- pyxlpr/ppocr/tools/infer_rec.py +0 -146
- pyxlpr/ppocr/tools/infer_table.py +0 -107
- pyxlpr/ppocr/tools/program.py +0 -596
- pyxlpr/ppocr/tools/test_hubserving.py +0 -117
- pyxlpr/ppocr/tools/train.py +0 -163
- pyxlpr/ppocr/tools/xlprog.py +0 -748
- pyxlpr/ppocr/utils/EN_symbol_dict.txt +0 -94
- pyxlpr/ppocr/utils/__init__.py +0 -24
- pyxlpr/ppocr/utils/dict/ar_dict.txt +0 -117
- pyxlpr/ppocr/utils/dict/arabic_dict.txt +0 -162
- pyxlpr/ppocr/utils/dict/be_dict.txt +0 -145
- pyxlpr/ppocr/utils/dict/bg_dict.txt +0 -140
- pyxlpr/ppocr/utils/dict/chinese_cht_dict.txt +0 -8421
- pyxlpr/ppocr/utils/dict/cyrillic_dict.txt +0 -163
- pyxlpr/ppocr/utils/dict/devanagari_dict.txt +0 -167
- pyxlpr/ppocr/utils/dict/en_dict.txt +0 -63
- pyxlpr/ppocr/utils/dict/fa_dict.txt +0 -136
- pyxlpr/ppocr/utils/dict/french_dict.txt +0 -136
- pyxlpr/ppocr/utils/dict/german_dict.txt +0 -143
- pyxlpr/ppocr/utils/dict/hi_dict.txt +0 -162
- pyxlpr/ppocr/utils/dict/it_dict.txt +0 -118
- pyxlpr/ppocr/utils/dict/japan_dict.txt +0 -4399
- pyxlpr/ppocr/utils/dict/ka_dict.txt +0 -153
- pyxlpr/ppocr/utils/dict/korean_dict.txt +0 -3688
- pyxlpr/ppocr/utils/dict/latin_dict.txt +0 -185
- pyxlpr/ppocr/utils/dict/mr_dict.txt +0 -153
- pyxlpr/ppocr/utils/dict/ne_dict.txt +0 -153
- pyxlpr/ppocr/utils/dict/oc_dict.txt +0 -96
- pyxlpr/ppocr/utils/dict/pu_dict.txt +0 -130
- pyxlpr/ppocr/utils/dict/rs_dict.txt +0 -91
- pyxlpr/ppocr/utils/dict/rsc_dict.txt +0 -134
- pyxlpr/ppocr/utils/dict/ru_dict.txt +0 -125
- pyxlpr/ppocr/utils/dict/ta_dict.txt +0 -128
- pyxlpr/ppocr/utils/dict/table_dict.txt +0 -277
- pyxlpr/ppocr/utils/dict/table_structure_dict.txt +0 -2759
- pyxlpr/ppocr/utils/dict/te_dict.txt +0 -151
- pyxlpr/ppocr/utils/dict/ug_dict.txt +0 -114
- pyxlpr/ppocr/utils/dict/uk_dict.txt +0 -142
- pyxlpr/ppocr/utils/dict/ur_dict.txt +0 -137
- pyxlpr/ppocr/utils/dict/xi_dict.txt +0 -110
- pyxlpr/ppocr/utils/dict90.txt +0 -90
- pyxlpr/ppocr/utils/e2e_metric/Deteval.py +0 -574
- pyxlpr/ppocr/utils/e2e_metric/polygon_fast.py +0 -83
- pyxlpr/ppocr/utils/e2e_utils/extract_batchsize.py +0 -87
- pyxlpr/ppocr/utils/e2e_utils/extract_textpoint_fast.py +0 -457
- pyxlpr/ppocr/utils/e2e_utils/extract_textpoint_slow.py +0 -592
- pyxlpr/ppocr/utils/e2e_utils/pgnet_pp_utils.py +0 -162
- pyxlpr/ppocr/utils/e2e_utils/visual.py +0 -162
- pyxlpr/ppocr/utils/en_dict.txt +0 -95
- pyxlpr/ppocr/utils/gen_label.py +0 -81
- pyxlpr/ppocr/utils/ic15_dict.txt +0 -36
- pyxlpr/ppocr/utils/iou.py +0 -54
- pyxlpr/ppocr/utils/logging.py +0 -69
- pyxlpr/ppocr/utils/network.py +0 -84
- pyxlpr/ppocr/utils/ppocr_keys_v1.txt +0 -6623
- pyxlpr/ppocr/utils/profiler.py +0 -110
- pyxlpr/ppocr/utils/save_load.py +0 -150
- pyxlpr/ppocr/utils/stats.py +0 -72
- pyxlpr/ppocr/utils/utility.py +0 -80
- pyxlpr/ppstructure/__init__.py +0 -13
- pyxlpr/ppstructure/predict_system.py +0 -187
- pyxlpr/ppstructure/table/__init__.py +0 -13
- pyxlpr/ppstructure/table/eval_table.py +0 -72
- pyxlpr/ppstructure/table/matcher.py +0 -192
- pyxlpr/ppstructure/table/predict_structure.py +0 -136
- pyxlpr/ppstructure/table/predict_table.py +0 -221
- pyxlpr/ppstructure/table/table_metric/__init__.py +0 -16
- pyxlpr/ppstructure/table/table_metric/parallel.py +0 -51
- pyxlpr/ppstructure/table/table_metric/table_metric.py +0 -247
- pyxlpr/ppstructure/table/tablepyxl/__init__.py +0 -13
- pyxlpr/ppstructure/table/tablepyxl/style.py +0 -283
- pyxlpr/ppstructure/table/tablepyxl/tablepyxl.py +0 -118
- pyxlpr/ppstructure/utility.py +0 -71
- pyxlpr/xlai.py +0 -10
- /pyxllib/{ext/autogui → autogui}/virtualkey.py +0 -0
- {pyxllib-0.3.96.dist-info → pyxllib-0.3.197.dist-info/licenses}/LICENSE +0 -0
@@ -0,0 +1,421 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
# @Author : 陈坤泽
|
4
|
+
# @Email : 877362867@qq.com
|
5
|
+
# @Date : 2024/11/05
|
6
|
+
|
7
|
+
"""
|
8
|
+
微信 消息框中不同消息结构的解析器 工具
|
9
|
+
|
10
|
+
todo 这个文件很多值可以写成枚举类型更规范?
|
11
|
+
"""
|
12
|
+
|
13
|
+
import re
|
14
|
+
import json
|
15
|
+
|
16
|
+
# 定义全局字典
|
17
|
+
msg_parsers = {}
|
18
|
+
|
19
|
+
|
20
|
+
# 定义装饰器
|
21
|
+
def register_tool(key):
|
22
|
+
def decorator(func):
|
23
|
+
# 将函数添加到全局字典中,键为传入的key参数
|
24
|
+
msg_parsers[key] = func
|
25
|
+
return func
|
26
|
+
|
27
|
+
return decorator
|
28
|
+
|
29
|
+
|
30
|
+
def fmt_text(text):
|
31
|
+
return text.replace('\n', ' ')
|
32
|
+
|
33
|
+
|
34
|
+
def parse_time_string(time_str):
|
35
|
+
"""
|
36
|
+
解析不同格式的时间字符串为标准的 Python datetime 类型。
|
37
|
+
|
38
|
+
支持以下格式:
|
39
|
+
1. 完整的日期和时间,如 "2024年10月31日 22:03",解析为对应的 datetime 对象。
|
40
|
+
2. 仅包含时间的字符串,如 "19:18",会被解析为当天的时间点,日期部分设为当前日期。
|
41
|
+
3. 特殊字符串 "以下为新消息",解析为当前时间的 datetime 对象。
|
42
|
+
|
43
|
+
示例输入:
|
44
|
+
- "2024年10月31日 22:03"
|
45
|
+
- "2024年11月1日 20:09"
|
46
|
+
- "2024年11月1日 21:08"
|
47
|
+
- "19:18"
|
48
|
+
- "20:16"
|
49
|
+
- "以下为新消息"
|
50
|
+
"""
|
51
|
+
from datetime import datetime
|
52
|
+
|
53
|
+
# 定义当前日期
|
54
|
+
now = datetime.now()
|
55
|
+
|
56
|
+
# 定义不同的正则表达式模式
|
57
|
+
full_date_pattern = r"(\d{4})年(\d{1,2})月(\d{1,2})日\s+(\d{1,2}):(\d{2})"
|
58
|
+
partial_time_pattern = r"^(\d{1,2}):(\d{2})$"
|
59
|
+
new_message_pattern = r"^以下为新消息$"
|
60
|
+
|
61
|
+
# 解析完整的日期时间
|
62
|
+
match = re.match(full_date_pattern, time_str)
|
63
|
+
if match:
|
64
|
+
year, month, day, hour, minute = map(int, match.groups())
|
65
|
+
return datetime(year, month, day, hour, minute)
|
66
|
+
|
67
|
+
# 解析只有时间(小时:分钟),默认当天的日期
|
68
|
+
match = re.match(partial_time_pattern, time_str)
|
69
|
+
if match:
|
70
|
+
hour, minute = map(int, match.groups())
|
71
|
+
return datetime(now.year, now.month, now.day, hour, minute)
|
72
|
+
|
73
|
+
# 解析 "以下为新消息" 为当前时间
|
74
|
+
if re.match(new_message_pattern, time_str):
|
75
|
+
return now
|
76
|
+
|
77
|
+
# 如果未匹配到任何模式,返回 None 表示无法解析
|
78
|
+
return None
|
79
|
+
|
80
|
+
|
81
|
+
@register_tool("1b2p3p3b3p")
|
82
|
+
def 系统_查看更多消息(node):
|
83
|
+
node.msg_type = 'system'
|
84
|
+
node.content_type = 'button_more'
|
85
|
+
node.render_text = f'⚙️: 点击此处可 {node.text}'
|
86
|
+
|
87
|
+
|
88
|
+
@register_tool("1l2t")
|
89
|
+
def 系统_时间标签(node):
|
90
|
+
node.msg_type = 'system'
|
91
|
+
node.content_type = 'time'
|
92
|
+
node.time = parse_time_string(node.text)
|
93
|
+
node.render_text = f'⚙️: {node.text}'
|
94
|
+
|
95
|
+
|
96
|
+
# 刚刚发送的特殊时间标签
|
97
|
+
@register_tool("1l2p3p3p3t3p3p")
|
98
|
+
def 系统_时间标签2(node):
|
99
|
+
node.msg_type = 'system'
|
100
|
+
node.content_type = 'time'
|
101
|
+
node.time = parse_time_string(node.text)
|
102
|
+
node.render_text = f'⚙️: {node.text}'
|
103
|
+
|
104
|
+
|
105
|
+
@register_tool("1l2p3p3t3p")
|
106
|
+
def 系统_撤回消息(node):
|
107
|
+
node.msg_type = 'system'
|
108
|
+
|
109
|
+
if node.text.endswith('撤回了一条消息'):
|
110
|
+
node.content_type = 'recall'
|
111
|
+
if node.text == '你撤回了一条消息':
|
112
|
+
node.user = node.user2 = '你'
|
113
|
+
else:
|
114
|
+
node.user = re.search(r'"(.+)"', node.text).group(1)
|
115
|
+
node.user2 = node.user
|
116
|
+
node.render_text = f'⚙️: {node.text}'
|
117
|
+
elif node.text.endswith('现在可以开始聊天了。'):
|
118
|
+
node.content_type = 'add_friend'
|
119
|
+
node.user = re.search(r'你已添加了(.+?),现在可以开始聊天了。', node.text).group(1)
|
120
|
+
node.user2 = node.user
|
121
|
+
node.render_text = f'⚙️: {node.text}'
|
122
|
+
else:
|
123
|
+
raise ValueError
|
124
|
+
|
125
|
+
|
126
|
+
@register_tool("1l2p3p3p4p5p6p7t3b")
|
127
|
+
def 发送_文本(node):
|
128
|
+
node.msg_type = 'send'
|
129
|
+
node.content_type = 'text'
|
130
|
+
node.user = node[0][2].text
|
131
|
+
node.render_text = f'↑{node.user}: {fmt_text(node.text)}'
|
132
|
+
|
133
|
+
|
134
|
+
@register_tool("1l2p3p3p4p5p6p7p7p8p8b3b")
|
135
|
+
def 发送_图片(node):
|
136
|
+
node.msg_type = 'send'
|
137
|
+
node.content_type = 'image'
|
138
|
+
node.user = node[0][2].text
|
139
|
+
|
140
|
+
|
141
|
+
@register_tool("1l2p3p3p4p5p6p7p7p8p9t9p9b8b3b")
|
142
|
+
def 发送_视频(node):
|
143
|
+
node.msg_type = 'send'
|
144
|
+
node.content_type = 'video'
|
145
|
+
node.user = node[0][2].text
|
146
|
+
|
147
|
+
|
148
|
+
@register_tool("1l2p3p3p4p5p6p7p7p8p9p10p11t6p7p8p9p10p11t7b3b")
|
149
|
+
def 发送_文本_引用文本(node):
|
150
|
+
node.msg_type = 'send'
|
151
|
+
node.content_type = 'text'
|
152
|
+
node.user = node[0][2].text
|
153
|
+
quoted_node = node[0][1][0][0] # 引用的层级结构
|
154
|
+
node.text = quoted_node[0][1][0][0][0][0].text
|
155
|
+
node.cite_text = quoted_node[1][0][0][0][0][0].text
|
156
|
+
|
157
|
+
|
158
|
+
@register_tool("1l2p3p3p4p5p6p7p7p8p9p10p11t6p7p8p9p10p11t11t11p12b7b3b")
|
159
|
+
def 发送_文本_引用图片(node):
|
160
|
+
node.msg_type = 'send'
|
161
|
+
node.content_type = 'text'
|
162
|
+
node.user = node[0][2].text
|
163
|
+
quoted_node = node[0][1][0][0]
|
164
|
+
node.text = quoted_node[0][1][0][0][0][0].text
|
165
|
+
node.cite_text = f'{quoted_node[1][0][0][0][0][0].text} : [图片]'
|
166
|
+
|
167
|
+
|
168
|
+
# 引用文件
|
169
|
+
@register_tool("1l2p3p3p4p5p6p7p7p8p9p10p11t6p7p8p9p10p11p12t11p12b7b3b")
|
170
|
+
def 发送_文本_引用文件(node):
|
171
|
+
node.msg_type = 'send'
|
172
|
+
node.content_type = 'text'
|
173
|
+
node.user = node[0][2].text
|
174
|
+
quoted_node = node[0][1][0][0]
|
175
|
+
node.text = quoted_node[0][1][0][0][0][0].text
|
176
|
+
node.cite_text = f'[文件] {quoted_node[1][0][0][0][0][0][0].text}'
|
177
|
+
|
178
|
+
|
179
|
+
# 发送文件
|
180
|
+
@register_tool("1l2p3p3p4p5p6p7p8p9p10t10p11t11t9p10p10p8p9t7b7b3b")
|
181
|
+
def 发送_文本_引用文件(node):
|
182
|
+
node.msg_type = 'send'
|
183
|
+
node.content_type = 'file'
|
184
|
+
node.user = node[0][2].text
|
185
|
+
file_node = node[0][1][0][0][0][0]
|
186
|
+
desc = {
|
187
|
+
'name': file_node[0][0][0].text,
|
188
|
+
'size': file_node[0][0][1][0].text,
|
189
|
+
'platform': file_node[1][0].text
|
190
|
+
}
|
191
|
+
node.text = json.dumps(desc, ensure_ascii=False)
|
192
|
+
|
193
|
+
|
194
|
+
# 发送链接
|
195
|
+
@register_tool("1l2p3p3p4p5p6p7p8t8p9t9p9b8p9b9t7b3b")
|
196
|
+
def 发送_链接(node):
|
197
|
+
node.msg_type = 'send'
|
198
|
+
node.content_type = 'link'
|
199
|
+
node.user = node[0][2].text
|
200
|
+
link_node = node[0][1][0][0]
|
201
|
+
desc = {
|
202
|
+
'title': link_node[0][0][0].text,
|
203
|
+
'head': link_node[0][0][1][0].text,
|
204
|
+
'author': link_node[0][0][2][1].text
|
205
|
+
}
|
206
|
+
node.text = json.dumps(desc, ensure_ascii=False)
|
207
|
+
|
208
|
+
|
209
|
+
# 转发消息
|
210
|
+
@register_tool("1l2p3p3p4p5p6p7p8t8p9t9t9t8p9p9t7b3b")
|
211
|
+
def 发送_转发消息(node):
|
212
|
+
node.msg_type = 'send'
|
213
|
+
node.content_type = 'messages'
|
214
|
+
node.user = node[0][2].text
|
215
|
+
|
216
|
+
|
217
|
+
@register_tool("1l2p3b3p4p5p6p7t3p")
|
218
|
+
def 接收_文本(node):
|
219
|
+
node.msg_type = 'receive'
|
220
|
+
node.content_type = 'text'
|
221
|
+
node.user = node[0][0].text
|
222
|
+
node.render_text = f'↓{node.user}: {fmt_text(node.text)}'
|
223
|
+
|
224
|
+
|
225
|
+
@register_tool("1l2p3b3p4p5p6p7p7b3p")
|
226
|
+
def 接收_图片(node):
|
227
|
+
node.msg_type = 'receive'
|
228
|
+
node.content_type = 'image'
|
229
|
+
node.user = node[0][0].text
|
230
|
+
node.render_text = f'↓{node.user}: [图片]'
|
231
|
+
|
232
|
+
|
233
|
+
@register_tool("1l2p3b3p4p5p6p7p8p9p10t10p11t11t9p10p10p8p9t7b7b3p")
|
234
|
+
def 接收_文件(node):
|
235
|
+
node.msg_type = 'receive'
|
236
|
+
node.content_type = 'file'
|
237
|
+
node.user = node[0][0].text
|
238
|
+
node.render_text = f'↓{node.user}: {fmt_text(node.text)}'
|
239
|
+
|
240
|
+
|
241
|
+
@register_tool("1l2p3b3p4p5p6p7p8b8p8t7b3p")
|
242
|
+
def 接收_语音(node):
|
243
|
+
node.msg_type = 'receive'
|
244
|
+
node.content_type = 'voice'
|
245
|
+
node.user = node[0][0].text
|
246
|
+
|
247
|
+
|
248
|
+
@register_tool("1l2p3b3p4p5p6p7p8t8p9t9p9b8p9b9t7b3p")
|
249
|
+
def 接收_链接(node):
|
250
|
+
node.msg_type = 'receive'
|
251
|
+
node.content_type = 'link'
|
252
|
+
node.user = node[0][0].text
|
253
|
+
link_node = node[0][1][0][0][0][0] # 链接内容的层级结构
|
254
|
+
desc = {
|
255
|
+
'title': link_node[0].text,
|
256
|
+
'head': link_node[1][0].text, # 文章开头部分
|
257
|
+
'author': link_node[2][1].text
|
258
|
+
}
|
259
|
+
node.text = json.dumps(desc, ensure_ascii=False)
|
260
|
+
|
261
|
+
|
262
|
+
@register_tool("1l2p3b3p4p5t4p5p6p7t3p")
|
263
|
+
def 群接收_文本(node):
|
264
|
+
node.msg_type = 'receive'
|
265
|
+
node.content_type = 'text'
|
266
|
+
node.user = node[0][0].text
|
267
|
+
node.user2 = node[0][1][0][0].text
|
268
|
+
|
269
|
+
|
270
|
+
@register_tool("1l2p3b3p4p5t4p5p6p7p7b3p")
|
271
|
+
def 群接收_图片(node):
|
272
|
+
node.msg_type = 'receive'
|
273
|
+
node.content_type = 'image'
|
274
|
+
node.user = node[0][0].text
|
275
|
+
node.user2 = node[0][1][0][0].text
|
276
|
+
|
277
|
+
|
278
|
+
@register_tool("1l2p3b3p4p5t4p5p6p7p8p9p10t10p11t11t9p10p10p7b7b3p")
|
279
|
+
def 群接收_文件(node):
|
280
|
+
node.msg_type = 'receive'
|
281
|
+
node.content_type = 'file'
|
282
|
+
node.user = node[0][0].text
|
283
|
+
file_node = node[0][1] # 文件信息的节点
|
284
|
+
node.user2 = file_node[0][0].text
|
285
|
+
file_info_node = file_node[1][0][0][0][0]
|
286
|
+
desc = {
|
287
|
+
'name': file_info_node[0][0].text,
|
288
|
+
'size': file_info_node[0][1][0].text
|
289
|
+
}
|
290
|
+
node.text = json.dumps(desc, ensure_ascii=False)
|
291
|
+
|
292
|
+
|
293
|
+
@register_tool("1l2p3b3p4p5t4p5p6p7p8p9p10t10p11t11t9p10p10p8p9t7b7b3p")
|
294
|
+
def 群接收_文件2(node):
|
295
|
+
node.msg_type = 'receive'
|
296
|
+
node.content_type = 'file'
|
297
|
+
node.user = node[0][0].text
|
298
|
+
node.user2 = node[0][1][0][0].text
|
299
|
+
file_info_node = node[0][1][1][0][0][0]
|
300
|
+
desc = {
|
301
|
+
'name': file_info_node[0][0][0].text,
|
302
|
+
'size': file_info_node[0][0][1][0].text,
|
303
|
+
'platform': file_info_node[1][0].text
|
304
|
+
}
|
305
|
+
node.text = json.dumps(desc, ensure_ascii=False)
|
306
|
+
|
307
|
+
|
308
|
+
@register_tool("1l2p3b3p4p5t4p5p6p7b3p")
|
309
|
+
def 群接收_动画表情(node):
|
310
|
+
node.msg_type = 'receive'
|
311
|
+
node.content_type = 'emoji'
|
312
|
+
node.user = node[0][0].text
|
313
|
+
node.user2 = node[0][1][0][0].text
|
314
|
+
|
315
|
+
|
316
|
+
@register_tool("1l2p3b3p4p5t4p5p6p7p8t8p9t9p9b8p9b9t7b3p")
|
317
|
+
def 群接收_链接_有公众号作者名(node):
|
318
|
+
node.msg_type = 'receive'
|
319
|
+
node.content_type = 'link'
|
320
|
+
node.user = node[0][0].text
|
321
|
+
link_node = node[0][1]
|
322
|
+
node.user2 = link_node[0][0].text
|
323
|
+
link_info_node = link_node[1][0][0][0]
|
324
|
+
desc = {
|
325
|
+
'title': link_info_node[0].text,
|
326
|
+
'head': link_info_node[1][0].text, # 文章开头的一小部分
|
327
|
+
'author': link_info_node[2][0].text
|
328
|
+
}
|
329
|
+
node.text = json.dumps(desc, ensure_ascii=False)
|
330
|
+
|
331
|
+
|
332
|
+
@register_tool("1l2p3b3p4p5t4p5p6p7p8t8p9t9p9b7b3p")
|
333
|
+
def 群接收_链接_无公众号作者名(node):
|
334
|
+
node.msg_type = 'receive'
|
335
|
+
node.content_type = 'link'
|
336
|
+
node.user = node[0][0].text
|
337
|
+
link_node = node[0][1]
|
338
|
+
node.user2 = link_node[0][0].text
|
339
|
+
link_info_node = link_node[1][0][0][0]
|
340
|
+
desc = {
|
341
|
+
'title': link_info_node[0].text,
|
342
|
+
'head': link_info_node[1][0].text # 文章开头的一小部分
|
343
|
+
}
|
344
|
+
node.text = json.dumps(desc, ensure_ascii=False)
|
345
|
+
|
346
|
+
|
347
|
+
@register_tool("1l2p3b3p4p5t4p5p6p7p8b8p9p8p9p10p10t8p9p7b3p")
|
348
|
+
def 群接收_视频(node):
|
349
|
+
node.msg_type = 'receive'
|
350
|
+
node.content_type = 'video'
|
351
|
+
node.user = node[0][0].text
|
352
|
+
video_node = node[0][1]
|
353
|
+
node.user2 = video_node[0][0].text
|
354
|
+
video_info_node = video_node[1][0][0][0]
|
355
|
+
desc = {
|
356
|
+
'author': video_info_node[2][0][1].text
|
357
|
+
}
|
358
|
+
node.text = json.dumps(desc, ensure_ascii=False)
|
359
|
+
|
360
|
+
|
361
|
+
@register_tool("1l2p3b3p4p5t4p5p6p7p8t8p9t8t7b3p")
|
362
|
+
def 群接收_视频2(node):
|
363
|
+
node.msg_type = 'receive'
|
364
|
+
node.content_type = 'video'
|
365
|
+
node.user = node[0][0].text
|
366
|
+
video_node = node[0][1]
|
367
|
+
node.user2 = video_node[0][0].text
|
368
|
+
video_info_node = video_node[1][0][0][0]
|
369
|
+
desc = {
|
370
|
+
'title': video_info_node[0].text,
|
371
|
+
'head': video_info_node[1][0].text,
|
372
|
+
'duration': video_info_node[2].text
|
373
|
+
}
|
374
|
+
node.text = json.dumps(desc, ensure_ascii=False)
|
375
|
+
|
376
|
+
|
377
|
+
@register_tool("1l2p3b3p4p5t4p5p6p7p8p9b9t8t8b8p9t9t7b3p")
|
378
|
+
def 群接收_小程序(node):
|
379
|
+
node.msg_type = 'receive'
|
380
|
+
node.content_type = 'applet'
|
381
|
+
node.user = node[0][0].text
|
382
|
+
applet_node = node[0][1]
|
383
|
+
node.user2 = applet_node[0][0].text
|
384
|
+
applet_info_node = applet_node[1][0][0][0]
|
385
|
+
desc = {
|
386
|
+
'name': applet_info_node[0][1].text,
|
387
|
+
'title': applet_info_node[1].text,
|
388
|
+
'footnote': applet_info_node[3][1].text
|
389
|
+
}
|
390
|
+
node.text = json.dumps(desc, ensure_ascii=False)
|
391
|
+
|
392
|
+
|
393
|
+
@register_tool("1l2p3p3p4p5l6p7p7t7p3p")
|
394
|
+
def 群接收_拍一拍(node):
|
395
|
+
node.msg_type = 'receive'
|
396
|
+
node.content_type = 'shake'
|
397
|
+
node.text = node[0][1][0][0].text
|
398
|
+
node.user = re.search(r'"(.+?)" 拍了拍 "', node.text).group(1)
|
399
|
+
|
400
|
+
|
401
|
+
@register_tool("1l2p3b3p4p5t4p5p6p7p8t4p5p6p7p8p9t5b3p")
|
402
|
+
def 群接收_引用消息(node):
|
403
|
+
node.msg_type = 'receive'
|
404
|
+
node.content_type = 'text'
|
405
|
+
node.user = node[0][0].text
|
406
|
+
quoted_node = node[0][1]
|
407
|
+
node.user2 = quoted_node[0][0].text
|
408
|
+
node.text = quoted_node[1][0][0][0][0].text
|
409
|
+
node.cite_text = quoted_node[2][0][0][0][0][0].text
|
410
|
+
|
411
|
+
|
412
|
+
@register_tool("1l2p3b3p4p5t4p5p6p7p8t4p5p6p7p8p9p10t9p10b5b3p")
|
413
|
+
def 群接收_引用文件(node):
|
414
|
+
node.msg_type = 'receive'
|
415
|
+
node.content_type = 'text'
|
416
|
+
node.user = node[0][0].text
|
417
|
+
quoted_node = node[0][1]
|
418
|
+
node.user2 = quoted_node[0][0].text
|
419
|
+
node.text = quoted_node[1][0][0][0][0].text
|
420
|
+
file_node = quoted_node[2][0][0][0][0][0][0]
|
421
|
+
node.cite_text = f'[文件] {file_node.text}'
|
@@ -0,0 +1,84 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
# @Author : 陈坤泽
|
4
|
+
# @Email : 877362867@qq.com
|
5
|
+
# @Date : 2024/12/16
|
6
|
+
|
7
|
+
import sys
|
8
|
+
|
9
|
+
from loguru import logger
|
10
|
+
|
11
|
+
if sys.platform == 'win32':
|
12
|
+
try: # 尝试加载VIP版
|
13
|
+
from wxautox import WeChat
|
14
|
+
from wxautox.elements import WxParam # WxParam.DEFALUT_SAVEPATH 可以用来配置数据自动保存位置
|
15
|
+
except ModuleNotFoundError: # 否则用贫民版
|
16
|
+
from wxauto import WeChat
|
17
|
+
from wxauto.elements import WxParam
|
18
|
+
|
19
|
+
from pyxllib.prog.filelock import get_autoui_lock
|
20
|
+
|
21
|
+
|
22
|
+
class WeChatSingletonLock:
|
23
|
+
""" 基于 get_autoui_lock 的微信全局唯一单例控制器,确保同一时间仅有一个微信自动化程序在操作 """
|
24
|
+
|
25
|
+
def __init__(self, lock_timeout=-1, *, init=True):
|
26
|
+
# 初始化全局锁
|
27
|
+
self.lock = get_autoui_lock(timeout=lock_timeout)
|
28
|
+
self.wx = WeChat() if init else None
|
29
|
+
|
30
|
+
def __enter__(self):
|
31
|
+
# 获取锁并激活微信窗口
|
32
|
+
self.lock.acquire()
|
33
|
+
if self.wx:
|
34
|
+
self.wx._show()
|
35
|
+
return self.wx
|
36
|
+
|
37
|
+
def __exit__(self, exc_type, exc_value, traceback):
|
38
|
+
# 释放锁
|
39
|
+
self.lock.release()
|
40
|
+
|
41
|
+
|
42
|
+
def wechat_lock_send(user, text=None, files=None, *, timeout=-1):
|
43
|
+
""" 使用全局唯一单例锁,确保同一时间仅有一个微信自动化程序在操作 """
|
44
|
+
with WeChatSingletonLock(timeout) as we:
|
45
|
+
# 241223周一12:27,今天可被这个默认2秒坑惨了,往错误群一直发骚扰消息
|
46
|
+
# 22:07,但我复测,感觉不可能找不到啊,为什么会找到禅宗考勤管理群呢,太离谱了
|
47
|
+
status = we.ChatWith(user, timeout=5)
|
48
|
+
|
49
|
+
if status != user:
|
50
|
+
raise ValueError(f'无法找到用户:{user}')
|
51
|
+
|
52
|
+
if text:
|
53
|
+
we.SendMsg(text, user)
|
54
|
+
if files:
|
55
|
+
we.SendFiles(files, user)
|
56
|
+
|
57
|
+
|
58
|
+
def wechat_handler(message):
|
59
|
+
# 获取群名,如果没有指定,不使用此微信发送功能
|
60
|
+
user = message.record["extra"].get("wechat_user")
|
61
|
+
if user:
|
62
|
+
wechat_lock_send(user, message)
|
63
|
+
|
64
|
+
|
65
|
+
if sys.platform == 'win32':
|
66
|
+
# 创建专用的微信日志记录器,不绑定默认群名
|
67
|
+
wechat_logger = logger.bind(wechat_user='文件传输助手')
|
68
|
+
|
69
|
+
# 添加专用的微信处理器
|
70
|
+
wechat_logger.add(wechat_handler,
|
71
|
+
format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {name}:{function}:{line} - {message}")
|
72
|
+
|
73
|
+
""" 往微信发送特殊的日志格式报告
|
74
|
+
用法:wechat_logger.bind(wechat_user='文件传输助手').info(message)
|
75
|
+
|
76
|
+
或者:
|
77
|
+
# 先做好默认群名绑定
|
78
|
+
wechat_logger = wechat_logger.bind(wechat_user='考勤管理')
|
79
|
+
# 然后就能普通logger用法发送了
|
80
|
+
wechat_logger.info('测试')
|
81
|
+
"""
|
82
|
+
else:
|
83
|
+
# 降级为普通logger
|
84
|
+
wechat_logger = logger
|
@@ -0,0 +1,137 @@
|
|
1
|
+
import os
|
2
|
+
import cv2
|
3
|
+
import numpy as np
|
4
|
+
from xlproject.code4101 import XlPath
|
5
|
+
|
6
|
+
from pyxllib.file.specialist import download_file
|
7
|
+
from pyxllib.prog.pupil import is_url
|
8
|
+
|
9
|
+
|
10
|
+
class SliderCaptchaLocator:
|
11
|
+
""" 滑动验证码的位置定位
|
12
|
+
|
13
|
+
由于缺口一般是一个黑底图,整体颜色是偏暗的,可以利用这个特性来定位列所在位置
|
14
|
+
"""
|
15
|
+
|
16
|
+
def __init__(self, background_image_path, slider_image_path=None):
|
17
|
+
"""
|
18
|
+
:param background_image_path: 背景主图片
|
19
|
+
:param slider_image_path: 滑动条图片
|
20
|
+
"""
|
21
|
+
# 支持输入是url的情况
|
22
|
+
if is_url(background_image_path):
|
23
|
+
background_image_path = download_file(background_image_path, temp=True, ext='.png')
|
24
|
+
if is_url(slider_image_path):
|
25
|
+
slider_image_path = download_file(slider_image_path, temp=True, ext='.png')
|
26
|
+
# 初始化图片等数据
|
27
|
+
self.image_path = background_image_path
|
28
|
+
self.image = cv2.imread(self.image_path)
|
29
|
+
self.darkest_rows = None
|
30
|
+
self.column_averages = None
|
31
|
+
self.enhanced_averages = None
|
32
|
+
self.radius = 24
|
33
|
+
if slider_image_path is not None:
|
34
|
+
# 先读取图片
|
35
|
+
template_image = cv2.imread(slider_image_path)
|
36
|
+
# 取图片宽度的40%作为radius
|
37
|
+
self.radius = int(template_image.shape[1] * 0.4)
|
38
|
+
|
39
|
+
def select_darkest_part_rows(self, fraction=0.1):
|
40
|
+
"""
|
41
|
+
选择最暗的部分行,供下游计算列的平均暗度
|
42
|
+
|
43
|
+
:param float fraction: 最暗行的比例
|
44
|
+
:return: 最暗行的图像部分
|
45
|
+
"""
|
46
|
+
if self.darkest_rows is None:
|
47
|
+
average_row_values = np.mean(self.image, axis=(1, 2))
|
48
|
+
threshold_index = int(len(average_row_values) * fraction)
|
49
|
+
darkest_indices = np.argsort(average_row_values)[:threshold_index]
|
50
|
+
self.darkest_rows = self.image[darkest_indices]
|
51
|
+
return self.darkest_rows
|
52
|
+
|
53
|
+
def calculate_column_averages(self):
|
54
|
+
"""
|
55
|
+
计算列的平均强度。
|
56
|
+
|
57
|
+
:return: 列的平均强度
|
58
|
+
"""
|
59
|
+
if self.column_averages is None:
|
60
|
+
rows = self.select_darkest_part_rows()
|
61
|
+
column_values = np.mean(rows, axis=0)
|
62
|
+
column_intensity = np.mean(column_values, axis=1)
|
63
|
+
min_val, max_val = np.min(column_intensity), np.max(column_intensity)
|
64
|
+
self.column_averages = ((column_intensity - min_val) / (max_val - min_val) * 255).astype(
|
65
|
+
np.uint8) if max_val != min_val else np.zeros_like(column_intensity)
|
66
|
+
return self.column_averages
|
67
|
+
|
68
|
+
def enhance_column_averages(self):
|
69
|
+
"""
|
70
|
+
增强列平均值,利用验证码的黑度图的对称性,进行一个增强,使得其他非验证码的零散黑条变白
|
71
|
+
|
72
|
+
:param int radius: 对比半径,这个建议设成模版图宽度的40%~50%
|
73
|
+
:return: 增强后的列平均值
|
74
|
+
"""
|
75
|
+
if self.enhanced_averages is None:
|
76
|
+
averages = self.calculate_column_averages()
|
77
|
+
length = len(averages)
|
78
|
+
enhanced = np.zeros_like(averages)
|
79
|
+
for i in range(length):
|
80
|
+
max_value = averages[i]
|
81
|
+
for j in range(1, self.radius // 2 + 1):
|
82
|
+
if i - j >= 0 and i + j < length:
|
83
|
+
max_value = max(max_value, abs(int(averages[i - j]) - int(averages[i + j])))
|
84
|
+
enhanced[i] = max_value
|
85
|
+
self.enhanced_averages = enhanced
|
86
|
+
return self.enhanced_averages
|
87
|
+
|
88
|
+
def find_captcha_position(self, threshold=50):
|
89
|
+
"""
|
90
|
+
查找验证码的位置。
|
91
|
+
|
92
|
+
:param int threshold: 达到多少像素后,开始计算中位数
|
93
|
+
:return: 验证码的列中心位置
|
94
|
+
"""
|
95
|
+
intensities = self.enhance_column_averages()
|
96
|
+
indices = []
|
97
|
+
for intensity in range(256):
|
98
|
+
new_indices = np.where(intensities == intensity)[0]
|
99
|
+
if len(indices) + len(new_indices) > threshold:
|
100
|
+
indices.extend(new_indices[:threshold - len(indices)])
|
101
|
+
break
|
102
|
+
indices.extend(new_indices)
|
103
|
+
return int(np.median(indices))
|
104
|
+
|
105
|
+
def draw_red_line(self, position, line_thickness=3):
|
106
|
+
"""
|
107
|
+
在图像上绘制红线。
|
108
|
+
|
109
|
+
:param int position: 红线位置
|
110
|
+
:param int line_thickness: 线宽
|
111
|
+
:return: 绘制红线后的图像
|
112
|
+
"""
|
113
|
+
gray_image = np.tile(self.calculate_column_averages(), (100, 1)).astype(np.uint8)
|
114
|
+
color_image = cv2.cvtColor(gray_image, cv2.COLOR_GRAY2BGR)
|
115
|
+
cv2.line(color_image, (position, 0), (position, gray_image.shape[0]), (0, 0, 255), line_thickness)
|
116
|
+
return color_image
|
117
|
+
|
118
|
+
def debug(self):
|
119
|
+
position = self.find_captcha_position()
|
120
|
+
final_image = self.draw_red_line(position)
|
121
|
+
cv2.imshow('Image with Red Line', final_image)
|
122
|
+
cv2.waitKey(0)
|
123
|
+
cv2.destroyAllWindows()
|
124
|
+
|
125
|
+
|
126
|
+
def main():
|
127
|
+
os.chdir(XlPath.desktop())
|
128
|
+
locator = SliderCaptchaLocator('cap_union_new_getcapbysig.png')
|
129
|
+
position = locator.find_captcha_position()
|
130
|
+
final_image = locator.draw_red_line(position)
|
131
|
+
cv2.imshow('Image with Red Line', final_image)
|
132
|
+
cv2.waitKey(0)
|
133
|
+
cv2.destroyAllWindows()
|
134
|
+
|
135
|
+
|
136
|
+
if __name__ == '__main__':
|
137
|
+
main()
|