uel-toolcraft 0.1.0__tar.gz
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.
- uel_toolcraft-0.1.0/.gitignore +6 -0
- uel_toolcraft-0.1.0/LICENSE +21 -0
- uel_toolcraft-0.1.0/PKG-INFO +113 -0
- uel_toolcraft-0.1.0/README.md +71 -0
- uel_toolcraft-0.1.0/main.py +4 -0
- uel_toolcraft-0.1.0/pyproject.toml +62 -0
- uel_toolcraft-0.1.0/uel_toolcraft/__init__.py +12 -0
- uel_toolcraft-0.1.0/uel_toolcraft/__main__.py +19 -0
- uel_toolcraft-0.1.0/uel_toolcraft/models/AESCipher.py +134 -0
- uel_toolcraft-0.1.0/uel_toolcraft/models/BarkClient.py +120 -0
- uel_toolcraft-0.1.0/uel_toolcraft/models/PyQtAsyncHelper.py +91 -0
- uel_toolcraft-0.1.0/uel_toolcraft/models/SQLiteHelper.py +335 -0
- uel_toolcraft-0.1.0/uel_toolcraft/models/WAPPClient.py +198 -0
- uel_toolcraft-0.1.0/uel_toolcraft/models/__init__.py +29 -0
- uel_toolcraft-0.1.0/uel_toolcraft/models/logging_config.py +91 -0
- uel_toolcraft-0.1.0/uel_toolcraft/models/redis_upstash.py +61 -0
- uel_toolcraft-0.1.0/uel_toolcraft/tools/__init__.py +146 -0
- uel_toolcraft-0.1.0/uel_toolcraft/tools/config_tools.py +20 -0
- uel_toolcraft-0.1.0/uel_toolcraft/tools/ffmpeg_tools.py +261 -0
- uel_toolcraft-0.1.0/uel_toolcraft/tools/filesystem_tools.py +257 -0
- uel_toolcraft-0.1.0/uel_toolcraft/tools/git_tools.py +207 -0
- uel_toolcraft-0.1.0/uel_toolcraft/tools/json_tools.py +69 -0
- uel_toolcraft-0.1.0/uel_toolcraft/tools/pickle_tools.py +34 -0
- uel_toolcraft-0.1.0/uel_toolcraft/tools/rclone_tools.py +102 -0
- uel_toolcraft-0.1.0/uel_toolcraft/tools/supabase_tools.py +30 -0
- uel_toolcraft-0.1.0/uel_toolcraft/tools/txt_tools.py +126 -0
- uel_toolcraft-0.1.0/uel_toolcraft/tools/ui_tools.py +37 -0
- uel_toolcraft-0.1.0/uel_toolcraft/tools/utils.py +35 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Samuel
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: uel-toolcraft
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A Unified Essential Library toolcraft (uel-toolcraft) for personal usage.
|
|
5
|
+
Author: Samuel
|
|
6
|
+
License: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Keywords: models,tools,utilities
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Requires-Dist: json5>=0.9.0
|
|
20
|
+
Requires-Dist: pycryptodome>=3.15.0
|
|
21
|
+
Requires-Dist: requests>=2.28.0
|
|
22
|
+
Provides-Extra: all
|
|
23
|
+
Requires-Dist: ffmpeg-python>=0.2.0; extra == 'all'
|
|
24
|
+
Requires-Dist: gitpython>=3.1.0; extra == 'all'
|
|
25
|
+
Requires-Dist: pyqt6>=6.5.0; extra == 'all'
|
|
26
|
+
Requires-Dist: rclone-python>=0.1.0; extra == 'all'
|
|
27
|
+
Requires-Dist: supabase>=2.0.0; extra == 'all'
|
|
28
|
+
Requires-Dist: upstash-redis>=0.1.0; extra == 'all'
|
|
29
|
+
Provides-Extra: ffmpeg
|
|
30
|
+
Requires-Dist: ffmpeg-python>=0.2.0; extra == 'ffmpeg'
|
|
31
|
+
Provides-Extra: git
|
|
32
|
+
Requires-Dist: gitpython>=3.1.0; extra == 'git'
|
|
33
|
+
Provides-Extra: pyqt
|
|
34
|
+
Requires-Dist: pyqt6>=6.5.0; extra == 'pyqt'
|
|
35
|
+
Provides-Extra: rclone
|
|
36
|
+
Requires-Dist: rclone-python>=0.1.0; extra == 'rclone'
|
|
37
|
+
Provides-Extra: redis
|
|
38
|
+
Requires-Dist: upstash-redis>=0.1.0; extra == 'redis'
|
|
39
|
+
Provides-Extra: supabase
|
|
40
|
+
Requires-Dist: supabase>=2.0.0; extra == 'supabase'
|
|
41
|
+
Description-Content-Type: text/markdown
|
|
42
|
+
|
|
43
|
+
# uel-toolcraft
|
|
44
|
+
|
|
45
|
+
A Unified Essential Library toolcraft for personal usage.
|
|
46
|
+
|
|
47
|
+
## Installation
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pip install uel-toolcraft
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Install with optional dependencies:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
# All optional dependencies
|
|
57
|
+
pip install uel-toolcraft[all]
|
|
58
|
+
|
|
59
|
+
# Specific extras
|
|
60
|
+
pip install uel-toolcraft[pyqt,ffmpeg,git]
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Local development install:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
git clone https://github.com/iShengren/uel-toolcraft.git
|
|
67
|
+
cd uel-toolcraft
|
|
68
|
+
uv pip install -e ".[all]"
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Usage
|
|
72
|
+
|
|
73
|
+
### Models
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
from uel_toolcraft.models import AESCipher, SQLiteHelper, BarkClient
|
|
77
|
+
|
|
78
|
+
cipher = AESCipher("my-secret-key")
|
|
79
|
+
db = SQLiteHelper("mydb.sqlite")
|
|
80
|
+
bark = BarkClient("https://bark.example.com", "device_key")
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Tools
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
from uel_toolcraft.tools import read_json_file, write_json_file, md5sum
|
|
87
|
+
|
|
88
|
+
data = read_json_file("config.json")
|
|
89
|
+
checksum = md5sum("large_file.bin")
|
|
90
|
+
write_json_file({"key": "value"}, "output.json")
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### CLI
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
uel-toolcraft --version
|
|
97
|
+
python -m uel_toolcraft --version
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Optional Dependencies
|
|
101
|
+
|
|
102
|
+
| Extra | Packages | Used by |
|
|
103
|
+
|---|---|---|
|
|
104
|
+
| `pyqt` | PyQt6 | `PyQtAsyncHelper`, `ui_tools` |
|
|
105
|
+
| `ffmpeg` | ffmpeg-python | `ffmpeg_tools` |
|
|
106
|
+
| `rclone` | rclone-python | `rclone_tools` |
|
|
107
|
+
| `supabase` | supabase | `supabase_tools` |
|
|
108
|
+
| `git` | GitPython | `git_tools` |
|
|
109
|
+
| `redis` | upstash-redis | `redis_upstash` |
|
|
110
|
+
|
|
111
|
+
## License
|
|
112
|
+
|
|
113
|
+
MIT
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# uel-toolcraft
|
|
2
|
+
|
|
3
|
+
A Unified Essential Library toolcraft for personal usage.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install uel-toolcraft
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Install with optional dependencies:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# All optional dependencies
|
|
15
|
+
pip install uel-toolcraft[all]
|
|
16
|
+
|
|
17
|
+
# Specific extras
|
|
18
|
+
pip install uel-toolcraft[pyqt,ffmpeg,git]
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Local development install:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
git clone https://github.com/iShengren/uel-toolcraft.git
|
|
25
|
+
cd uel-toolcraft
|
|
26
|
+
uv pip install -e ".[all]"
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Usage
|
|
30
|
+
|
|
31
|
+
### Models
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
from uel_toolcraft.models import AESCipher, SQLiteHelper, BarkClient
|
|
35
|
+
|
|
36
|
+
cipher = AESCipher("my-secret-key")
|
|
37
|
+
db = SQLiteHelper("mydb.sqlite")
|
|
38
|
+
bark = BarkClient("https://bark.example.com", "device_key")
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Tools
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
from uel_toolcraft.tools import read_json_file, write_json_file, md5sum
|
|
45
|
+
|
|
46
|
+
data = read_json_file("config.json")
|
|
47
|
+
checksum = md5sum("large_file.bin")
|
|
48
|
+
write_json_file({"key": "value"}, "output.json")
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### CLI
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
uel-toolcraft --version
|
|
55
|
+
python -m uel_toolcraft --version
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Optional Dependencies
|
|
59
|
+
|
|
60
|
+
| Extra | Packages | Used by |
|
|
61
|
+
|---|---|---|
|
|
62
|
+
| `pyqt` | PyQt6 | `PyQtAsyncHelper`, `ui_tools` |
|
|
63
|
+
| `ffmpeg` | ffmpeg-python | `ffmpeg_tools` |
|
|
64
|
+
| `rclone` | rclone-python | `rclone_tools` |
|
|
65
|
+
| `supabase` | supabase | `supabase_tools` |
|
|
66
|
+
| `git` | GitPython | `git_tools` |
|
|
67
|
+
| `redis` | upstash-redis | `redis_upstash` |
|
|
68
|
+
|
|
69
|
+
## License
|
|
70
|
+
|
|
71
|
+
MIT
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "uel-toolcraft"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "A Unified Essential Library toolcraft (uel-toolcraft) for personal usage."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = {text = "MIT"}
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "Samuel"},
|
|
14
|
+
]
|
|
15
|
+
keywords = ["tools", "utilities", "models"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 3 - Alpha",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.10",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Programming Language :: Python :: 3.13",
|
|
25
|
+
"Programming Language :: Python :: 3.14",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
dependencies = [
|
|
29
|
+
"json5>=0.9.0",
|
|
30
|
+
"pycryptodome>=3.15.0",
|
|
31
|
+
"requests>=2.28.0",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[project.optional-dependencies]
|
|
35
|
+
pyqt = ["PyQt6>=6.5.0"]
|
|
36
|
+
ffmpeg = ["ffmpeg-python>=0.2.0"]
|
|
37
|
+
rclone = ["rclone-python>=0.1.0"]
|
|
38
|
+
supabase = ["supabase>=2.0.0"]
|
|
39
|
+
git = ["GitPython>=3.1.0"]
|
|
40
|
+
redis = ["upstash-redis>=0.1.0"]
|
|
41
|
+
all = [
|
|
42
|
+
"PyQt6>=6.5.0",
|
|
43
|
+
"ffmpeg-python>=0.2.0",
|
|
44
|
+
"rclone-python>=0.1.0",
|
|
45
|
+
"supabase>=2.0.0",
|
|
46
|
+
"GitPython>=3.1.0",
|
|
47
|
+
"upstash-redis>=0.1.0",
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
[project.scripts]
|
|
51
|
+
uel-toolcraft = "main:main"
|
|
52
|
+
|
|
53
|
+
[tool.hatch.build.targets.wheel]
|
|
54
|
+
packages = ["uel_toolcraft"]
|
|
55
|
+
|
|
56
|
+
[tool.hatch.build.targets.sdist]
|
|
57
|
+
include = [
|
|
58
|
+
"/uel_toolcraft",
|
|
59
|
+
"/main.py",
|
|
60
|
+
"/README.md",
|
|
61
|
+
"/LICENSE",
|
|
62
|
+
]
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""
|
|
2
|
+
uel-toolcraft — A Unified Essential Library toolcraft for personal usage.
|
|
3
|
+
|
|
4
|
+
Models: AES encryption, Bark push, logging, PyQt async, Redis, SQLite, WeCom.
|
|
5
|
+
Tools: config, ffmpeg, filesystem, git, json, pickle, rclone, supabase, text, UI, utils.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
try:
|
|
9
|
+
from importlib.metadata import version as _get_version
|
|
10
|
+
__version__ = _get_version("uel-toolcraft")
|
|
11
|
+
except Exception:
|
|
12
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Entry point for `python -m uel_toolcraft`."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
from importlib.metadata import version as _get_version
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def main():
|
|
8
|
+
parser = argparse.ArgumentParser(
|
|
9
|
+
description="uel-toolcraft — A Unified Essential Library toolcraft"
|
|
10
|
+
)
|
|
11
|
+
parser.add_argument(
|
|
12
|
+
"--version", action="version",
|
|
13
|
+
version=f"uel-toolcraft {_get_version('uel-toolcraft')}"
|
|
14
|
+
)
|
|
15
|
+
parser.parse_args()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
if __name__ == "__main__":
|
|
19
|
+
main()
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import logging
|
|
3
|
+
from .logging_config import LogConfig
|
|
4
|
+
from Crypto.Cipher import AES
|
|
5
|
+
from Crypto.Util.Padding import pad, unpad
|
|
6
|
+
from Crypto.Random import get_random_bytes
|
|
7
|
+
|
|
8
|
+
# 获取日志记录器
|
|
9
|
+
logger: logging.Logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AESCipher:
|
|
13
|
+
def __init__(self, key, iv=None, mode: str = 'CBC', padding_style: str = 'pkcs7'):
|
|
14
|
+
"""
|
|
15
|
+
初始化AES加密器
|
|
16
|
+
|
|
17
|
+
:param key: 密钥 bytes (16/24/32字节分别对应AES-128/192/256)
|
|
18
|
+
:param iv: 初始化向量 (CBC/GCM模式需要,16字节)
|
|
19
|
+
:param mode: 加密模式 'CBC'/'ECB'/'GCM'
|
|
20
|
+
"""
|
|
21
|
+
self.key = key if type(key) is bytes else key.encode('utf-8') # 将字符串转换为字节
|
|
22
|
+
self.mode_name = mode.upper()
|
|
23
|
+
if self.mode_name == 'CBC':
|
|
24
|
+
self.mode = AES.MODE_CBC
|
|
25
|
+
if iv is None:
|
|
26
|
+
raise ValueError("IV is required for CBC mode")
|
|
27
|
+
self.iv = iv
|
|
28
|
+
elif self.mode_name == 'ECB':
|
|
29
|
+
self.mode = AES.MODE_ECB
|
|
30
|
+
self.iv = None
|
|
31
|
+
elif self.mode_name == 'GCM':
|
|
32
|
+
self.mode = AES.MODE_GCM
|
|
33
|
+
if iv is None:
|
|
34
|
+
raise ValueError("IV is required for GCM mode")
|
|
35
|
+
self.iv = iv
|
|
36
|
+
else:
|
|
37
|
+
raise ValueError(f"Unsupported AES mode: {mode}")
|
|
38
|
+
if (self.iv is not None) and (type(self.iv) is str):
|
|
39
|
+
self.iv = self.iv.encode('utf-8') # 将字符串转换为字节
|
|
40
|
+
self.padding_style = padding_style # 默认填充方式
|
|
41
|
+
|
|
42
|
+
def encrypt(self, plaintext: str) -> str:
|
|
43
|
+
"""
|
|
44
|
+
加密数据并返回 base64 编码字符串
|
|
45
|
+
|
|
46
|
+
:param plaintext: 原文字符串
|
|
47
|
+
:return: base64加密结果
|
|
48
|
+
"""
|
|
49
|
+
plaintext_bytes = plaintext.encode('utf-8')
|
|
50
|
+
|
|
51
|
+
if self.mode_name in ['CBC', 'ECB']:
|
|
52
|
+
padded_data = pad(plaintext_bytes, AES.block_size, style=self.padding_style) # padding 填充
|
|
53
|
+
# 创建 AES 加密器
|
|
54
|
+
if self.iv:
|
|
55
|
+
cipher = AES.new(self.key, self.mode, iv=self.iv) # 忽略意外类型 "(Any, Literal[11], Any)"
|
|
56
|
+
else:
|
|
57
|
+
cipher = AES.new(self.key, self.mode)
|
|
58
|
+
encrypted = cipher.encrypt(padded_data) # 加密数据
|
|
59
|
+
elif self.mode_name == 'GCM':
|
|
60
|
+
# # GCM 模式不需要填充
|
|
61
|
+
# padded_data = pad(plaintext_bytes, AES.block_size, style=self.padding_style) # padding 填充
|
|
62
|
+
cipher = AES.new(self.key, self.mode, nonce=self.iv)
|
|
63
|
+
encrypted, tag = cipher.encrypt_and_digest(plaintext_bytes) # 加密数据
|
|
64
|
+
encrypted += tag # 将tag附加到密文末尾
|
|
65
|
+
else:
|
|
66
|
+
raise ValueError("Unsupported encryption mode.")
|
|
67
|
+
|
|
68
|
+
# 将加密结果转换为base64编码
|
|
69
|
+
base64_encrypted = base64.b64encode(encrypted).decode('utf-8')
|
|
70
|
+
|
|
71
|
+
return base64_encrypted
|
|
72
|
+
|
|
73
|
+
def decrypt(self, encrypted_base64: str) -> str:
|
|
74
|
+
"""
|
|
75
|
+
解密 base64 数据并返回原文字符串
|
|
76
|
+
|
|
77
|
+
:param encrypted_base64: base64编码的加密数据
|
|
78
|
+
:return: 解密后的原文
|
|
79
|
+
"""
|
|
80
|
+
encrypted_bytes = base64.b64decode(encrypted_base64)
|
|
81
|
+
|
|
82
|
+
if self.mode_name in ['CBC', 'ECB']:
|
|
83
|
+
if self.iv:
|
|
84
|
+
cipher = AES.new(self.key, self.mode, iv=self.iv) # 忽略意外类型 "(Any, Literal[11], Any)"
|
|
85
|
+
else:
|
|
86
|
+
cipher = AES.new(self.key, self.mode)
|
|
87
|
+
decrypted_padded = cipher.decrypt(encrypted_bytes)
|
|
88
|
+
plaintext = unpad(decrypted_padded, AES.block_size, self.padding_style).decode('utf-8') # 去除填充
|
|
89
|
+
elif self.mode_name == 'GCM':
|
|
90
|
+
cipher = AES.new(self.key, self.mode, nonce=self.iv) # 忽略意外类型
|
|
91
|
+
tag = encrypted_bytes[-AES.block_size:]
|
|
92
|
+
encrypted_data = encrypted_bytes[:-AES.block_size]
|
|
93
|
+
plaintext = cipher.decrypt_and_verify(encrypted_data, tag).decode('utf-8')
|
|
94
|
+
else:
|
|
95
|
+
raise ValueError("Unsupported decryption mode.")
|
|
96
|
+
|
|
97
|
+
return plaintext
|
|
98
|
+
|
|
99
|
+
@staticmethod
|
|
100
|
+
def generate_key(bits: int = 128, output_format: str = "str") -> bytes|str:
|
|
101
|
+
"""
|
|
102
|
+
生成随机AES密钥
|
|
103
|
+
|
|
104
|
+
:param bits: 密钥长度(128/192/256)
|
|
105
|
+
:param output_format: 输出格式("bytes" 或 "str")
|
|
106
|
+
:return: 随机密钥 bytes 或 str
|
|
107
|
+
"""
|
|
108
|
+
if bits not in [128, 192, 256]:
|
|
109
|
+
raise ValueError("Key length must be 128, 192, or 256 bits.")
|
|
110
|
+
# 生成随机密钥
|
|
111
|
+
random_bytes = get_random_bytes(bits // 8)
|
|
112
|
+
if output_format == "str":
|
|
113
|
+
# 将字节转换为字符串, 取前 bits // 8 字节,并使用 URL-safe base64 编码
|
|
114
|
+
return base64.urlsafe_b64encode(random_bytes).decode('utf-8')[:(bits // 8)]
|
|
115
|
+
else:
|
|
116
|
+
return random_bytes
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
if __name__ == "__main__":
|
|
120
|
+
LogConfig(level=logging.DEBUG) # 初始化日志配置
|
|
121
|
+
|
|
122
|
+
# 示例用法
|
|
123
|
+
key_ = AESCipher.generate_key(128, output_format="str") # 生成128位密钥
|
|
124
|
+
iv_ = get_random_bytes(16) # 生成16字节IV
|
|
125
|
+
aes_cipher_ = AESCipher(key_, iv_, mode='CBC', padding_style='pkcs7')
|
|
126
|
+
plaintext_ = '{"body": "This is a aes cipher test message", "title": "AES Cipher Test"}'
|
|
127
|
+
encrypted_ = aes_cipher_.encrypt(plaintext_)
|
|
128
|
+
decrypted_ = aes_cipher_.decrypt(encrypted_)
|
|
129
|
+
|
|
130
|
+
print(f"Key: {key_}")
|
|
131
|
+
print(f"IV: {iv_}")
|
|
132
|
+
print(f"Plaintext: {plaintext_}")
|
|
133
|
+
print(f"Encrypted: {encrypted_}")
|
|
134
|
+
print(f"Decrypted: {decrypted_}")
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
import json
|
|
3
|
+
from .AESCipher import AESCipher
|
|
4
|
+
import logging
|
|
5
|
+
from .logging_config import LogConfig
|
|
6
|
+
|
|
7
|
+
# 获取日志记录器
|
|
8
|
+
logger: logging.Logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class BarkClient:
|
|
12
|
+
def __init__(self, server_url: str, device_key: str | list, debug: bool = False):
|
|
13
|
+
"""
|
|
14
|
+
初始化 BarkClient
|
|
15
|
+
:param server_url: Bark服务器地址,例如 http://127.0.0.1:8080
|
|
16
|
+
:param device_key: 设备的推送key,例如 "your_bark_device_token" 或 ["device_key1", "device_key2"]
|
|
17
|
+
:param debug: 是否开启调试模式,默认为 False
|
|
18
|
+
"""
|
|
19
|
+
self.debug = debug # 是否开启调试模式
|
|
20
|
+
self.server_url = server_url.rstrip('/')
|
|
21
|
+
self.device_key = device_key
|
|
22
|
+
self.push_url = f"{self.server_url}/push"
|
|
23
|
+
self.headers = {
|
|
24
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
25
|
+
}
|
|
26
|
+
self.supported_parameters = [
|
|
27
|
+
"title", "subtitle", "body", "device_key", "device_keys", "level", "volume", "badge", "call", "autoCopy", "copy", "sound", "icon", "group", "ciphertext", "isArchive", "url", "action"
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
# 根据 device_key 的类型设置 payload 中的 device_key 键名称
|
|
31
|
+
if isinstance(self.device_key, list):
|
|
32
|
+
# 如果 device_key 是列表,parameters 中的 device_key 应该是 device_keys
|
|
33
|
+
self.device_key_payload_name = "device_keys"
|
|
34
|
+
if self.debug:
|
|
35
|
+
logger.debug(f"✅ [BarkClient] device_key is a list, using 'device_keys' in payload.")
|
|
36
|
+
elif isinstance(self.device_key, str):
|
|
37
|
+
# 如果 device_key 是字符串,parameters 中的 device_key 应该是 device_key
|
|
38
|
+
self.device_key_payload_name = "device_key"
|
|
39
|
+
if self.debug:
|
|
40
|
+
logger.debug(f"✅ [BarkClient] device_key is a string, using 'device_key' in payload.")
|
|
41
|
+
else:
|
|
42
|
+
logger.error("❌ [BarkClient] device_key must be a string or a list of strings.")
|
|
43
|
+
raise ValueError("device_key must be a string or a list of strings.")
|
|
44
|
+
|
|
45
|
+
def send(self, body: str, title: str = "", **extra_fields):
|
|
46
|
+
"""
|
|
47
|
+
发送推送,支持额外动态字段
|
|
48
|
+
:param body: 消息正文
|
|
49
|
+
:param title: 消息标题
|
|
50
|
+
:param extra_fields: 其他自定义参数(sound, badge, icon, group, url等, 帮助文档: "https://bark.day.app/#/tutorial")
|
|
51
|
+
"""
|
|
52
|
+
payload = {
|
|
53
|
+
"body": body,
|
|
54
|
+
self.device_key_payload_name: self.device_key,
|
|
55
|
+
"title": title
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
# 是否加密
|
|
59
|
+
encrypt = False
|
|
60
|
+
|
|
61
|
+
# 动态添加额外参数
|
|
62
|
+
for field_name, value in extra_fields.items():
|
|
63
|
+
if (field_name in self.supported_parameters) and (value is not None):
|
|
64
|
+
payload[field_name] = value
|
|
65
|
+
elif (field_name in ["encrypt", "aes"]) and (value is not None):
|
|
66
|
+
# 如果参数是 encrypt,则设置 encrypt 变量
|
|
67
|
+
encrypt = value
|
|
68
|
+
|
|
69
|
+
try:
|
|
70
|
+
if encrypt:
|
|
71
|
+
# 如果需要加密,则使用 AES 加密
|
|
72
|
+
key = extra_fields.get("key", None) # 默认密钥
|
|
73
|
+
if key is None:
|
|
74
|
+
logger.error("❌ [BarkClient] Key is required for encryption.")
|
|
75
|
+
raise ValueError("Key is required for encryption.")
|
|
76
|
+
# 从 extra_fields 中获取 IV, 如果没有提供则生成一个默认的 IV
|
|
77
|
+
iv = extra_fields.get("iv") or AESCipher.generate_key(128) # 默认 IV
|
|
78
|
+
mode = extra_fields.get("mode", "CBC") # 默认模式为 CBC
|
|
79
|
+
aes_cipher = AESCipher(key=key, iv=iv, mode=mode)
|
|
80
|
+
payload_str = json.dumps(payload)
|
|
81
|
+
encrypted_payload = aes_cipher.encrypt(payload_str)
|
|
82
|
+
# 构建含有加密文本的 payload
|
|
83
|
+
payload = {
|
|
84
|
+
self.device_key_payload_name: self.device_key,
|
|
85
|
+
"ciphertext": encrypted_payload,
|
|
86
|
+
"iv": iv,
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
# 发送请求
|
|
90
|
+
send_response = requests.post(
|
|
91
|
+
url=self.push_url,
|
|
92
|
+
headers=self.headers,
|
|
93
|
+
data=json.dumps(payload),
|
|
94
|
+
timeout=10
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
if self.debug:
|
|
98
|
+
# 检查响应状态码
|
|
99
|
+
logger.debug(f"✅ [BarkClient] Response Status Code: {send_response.status_code}")
|
|
100
|
+
logger.debug(f"✅ [BarkClient] Response Body: {send_response.content.decode('utf-8')}")
|
|
101
|
+
|
|
102
|
+
return send_response
|
|
103
|
+
except requests.exceptions.RequestException as e:
|
|
104
|
+
logger.error(f"❌ [BarkClient] Request failed: {e}")
|
|
105
|
+
return None
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
if __name__ == "__main__":
|
|
109
|
+
LogConfig(level=logging.DEBUG) # 初始化日志配置
|
|
110
|
+
bark = BarkClient(server_url="https://api.day.app/", device_key="your_bark_device_token", debug=True)
|
|
111
|
+
response = bark.send(
|
|
112
|
+
body="This is a test message.",
|
|
113
|
+
title="Test",
|
|
114
|
+
group="Test Group",
|
|
115
|
+
debug=False,
|
|
116
|
+
encrypt=False,
|
|
117
|
+
mode="CBC",
|
|
118
|
+
key="aes_16_byte_key",
|
|
119
|
+
iv="aes_16_byte_iv" # CBC 模式需要 16 字节 IV, 如果不指定则自动生成 16 字节 IV
|
|
120
|
+
)
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from threading import Thread
|
|
3
|
+
from PyQt6.QtCore import QObject, pyqtSignal, QCoreApplication
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class AsyncRunner:
|
|
7
|
+
"""
|
|
8
|
+
全局异步执行器,允许在非主线程中运行异步任务。
|
|
9
|
+
使用 asyncio.run_coroutine_threadsafe 方法将协程提交到事件循环。
|
|
10
|
+
通过线程安全的方式与 PyQt 信号系统交互。
|
|
11
|
+
该类在初始化时创建一个新的事件循环,并在独立线程中运行该循环。
|
|
12
|
+
可以通过 run_async 方法提交异步任务。
|
|
13
|
+
该类适用于需要在 PyQt 应用程序中执行异步操作的场景。
|
|
14
|
+
例如,可以在按钮点击事件中调用 run_async 方法来执行异步任务。
|
|
15
|
+
该类的设计使得异步任务可以在后台线程中运行,而不会阻塞主线程的 UI 更新。
|
|
16
|
+
通过使用 PyQt 的信号机制,可以在异步任务完成后安全地更新 UI。
|
|
17
|
+
该类的使用方式如下:
|
|
18
|
+
1. 创建 AsyncRunner 实例:`runner = AsyncRunner()`
|
|
19
|
+
2. 定义异步任务:`async def my_async_task(): ...`
|
|
20
|
+
3. 提交异步任务:`runner.run_async(my_async_task())`
|
|
21
|
+
4. 在异步任务中使用 `self.signals.result_ready.emit(result)`
|
|
22
|
+
来发送信号到主线程。
|
|
23
|
+
5. 在主线程中连接信号:`self.signals.result_ready.connect(self.display_result)`
|
|
24
|
+
6. 在 `display_result` 方法中处理结果并更新 UI。
|
|
25
|
+
该类的设计使得异步任务可以在 PyQt 应用程序中安全地执行,同时保持 UI 的响应性。
|
|
26
|
+
"""
|
|
27
|
+
def __init__(self):
|
|
28
|
+
self.loop = asyncio.new_event_loop()
|
|
29
|
+
self.thread = Thread(target=self._start_loop, daemon=True)
|
|
30
|
+
self.thread.start()
|
|
31
|
+
|
|
32
|
+
def _start_loop(self):
|
|
33
|
+
asyncio.set_event_loop(self.loop)
|
|
34
|
+
self.loop.run_forever()
|
|
35
|
+
|
|
36
|
+
def run_async(self, coro):
|
|
37
|
+
return asyncio.run_coroutine_threadsafe(coro, self.loop)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class SignalEmitter(QObject):
|
|
41
|
+
"""
|
|
42
|
+
信号发射器类,用于在 PyQt 应用程序中发出信号。
|
|
43
|
+
该类继承自 QObject,并定义了一个信号 result_ready。
|
|
44
|
+
该信号可以在异步任务完成后发出结果,以便在主线程中处理。
|
|
45
|
+
使用该类可以实现线程安全的信号发射,确保在 PyQt 应用程序中正确地更新 UI。
|
|
46
|
+
该类的使用方式如下:
|
|
47
|
+
1. 创建 SignalEmitter 实例:`self.signals = SignalEmitter()`
|
|
48
|
+
2. 在异步任务中发出信号:`self.signals.result_ready.emit(result)`
|
|
49
|
+
3. 在主线程中连接信号:`self.signals.result_ready.connect(self.display_result)`
|
|
50
|
+
4. 在 `display_result` 方法中处理结果并更新 UI。
|
|
51
|
+
该类的设计使得异步任务可以在后台线程中运行,而不会阻塞主线程的 UI 更新。
|
|
52
|
+
通过使用 PyQt 的信号机制,可以在异步任务完成后安全地更新 UI。
|
|
53
|
+
|
|
54
|
+
"""
|
|
55
|
+
result_ready = pyqtSignal(str)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class SignalEmitter2(QObject):
|
|
59
|
+
"""
|
|
60
|
+
信号发射器类,用于在 PyQt 应用程序中发出信号。
|
|
61
|
+
该类继承自 QObject,并定义了一个信号 result_ready。
|
|
62
|
+
该信号可以在异步任务完成后发出结果,以便在主线程中处理。
|
|
63
|
+
使用该类可以实现线程安全的信号发射,确保在 PyQt 应用程序中正确地更新 UI。
|
|
64
|
+
该类的使用方式如下:
|
|
65
|
+
1. 创建 SignalEmitter 实例:`self.signals = SignalEmitter()`
|
|
66
|
+
2. 在异步任务中发出信号:`self.signals.result_ready.emit(result)`
|
|
67
|
+
3. 在主线程中连接信号:`self.signals.result_ready.connect(self.display_result)`
|
|
68
|
+
4. 在 `display_result` 方法中处理结果并更新 UI。
|
|
69
|
+
该类的设计使得异步任务可以在后台线程中运行,而不会阻塞主线程的 UI 更新。
|
|
70
|
+
通过使用 PyQt 的信号机制,可以在异步任务完成后安全地更新 UI。
|
|
71
|
+
|
|
72
|
+
"""
|
|
73
|
+
result_ready = pyqtSignal(str, str)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class SignalEmitter3(QObject):
|
|
77
|
+
"""
|
|
78
|
+
信号发射器类,用于在 PyQt 应用程序中发出信号。
|
|
79
|
+
该类继承自 QObject,并定义了一个信号 result_ready。
|
|
80
|
+
该信号可以在异步任务完成后发出结果,以便在主线程中处理。
|
|
81
|
+
使用该类可以实现线程安全的信号发射,确保在 PyQt 应用程序中正确地更新 UI。
|
|
82
|
+
该类的使用方式如下:
|
|
83
|
+
1. 创建 SignalEmitter 实例:`self.signals = SignalEmitter()`
|
|
84
|
+
2. 在异步任务中发出信号:`self.signals.result_ready.emit(result)`
|
|
85
|
+
3. 在主线程中连接信号:`self.signals.result_ready.connect(self.display_result)`
|
|
86
|
+
4. 在 `display_result` 方法中处理结果并更新 UI。
|
|
87
|
+
该类的设计使得异步任务可以在后台线程中运行,而不会阻塞主线程的 UI 更新。
|
|
88
|
+
通过使用 PyQt 的信号机制,可以在异步任务完成后安全地更新 UI。
|
|
89
|
+
|
|
90
|
+
"""
|
|
91
|
+
result_ready = pyqtSignal(str, str, str)
|