Skip to content

Commit 62de19d

Browse files
✨图片消息进行解密
1 parent 5c893f5 commit 62de19d

File tree

5 files changed

+116
-0
lines changed

5 files changed

+116
-0
lines changed

.env

+5
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,8 @@ cache_path = "./file_cache"
3434
# 文件缓存天数
3535
cache_days = 3
3636

37+
# 聊天图片解密地址
38+
image_path = "./image_decode"
39+
40+
# 下载pc图片超时时间(s),超时的图片消息将不会发送
41+
image_timeout = 30

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -130,3 +130,4 @@ dmypy.json
130130
*.code-workspace
131131
file_cache/
132132
logs/
133+
image_decode/

ntchat_client/config.py

+4
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,10 @@ class Config(BaseConfig):
158158
"""文件缓存目录"""
159159
cache_days: int = 3
160160
"""文件缓存天数"""
161+
image_path: str = "./image_decode"
162+
"""聊天图片解密地址"""
163+
image_timeout: int = 30
164+
"""下载pc图片超时时间(s),超时的图片消息将不会发送"""
161165

162166
class Config:
163167
extra = "allow"

ntchat_client/wechat/image_decode.py

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
from dataclasses import dataclass
2+
from pathlib import Path
3+
from typing import Optional
4+
5+
import numpy as np
6+
7+
8+
@dataclass
9+
class FileTypes:
10+
"""文件格式"""
11+
12+
file_type: str
13+
"""格式后缀"""
14+
key: int
15+
"""密钥"""
16+
17+
18+
class FileDecoder:
19+
"""文件解密类"""
20+
21+
file_map: dict[str, tuple[int, int]] = {
22+
"jpg": (0xFF, 0xD8),
23+
"png": (0x89, 0x50),
24+
"gif": (0x47, 0x49),
25+
}
26+
out_image_dir: Path
27+
"""输出文件夹"""
28+
out_image_thumb: Path
29+
"""缩略图输出文件夹"""
30+
31+
def __init__(self, image_path: str) -> None:
32+
self.out_image_dir = Path(image_path) / "image"
33+
self.out_image_thumb = Path(image_path) / "thumb"
34+
self.out_image_dir.mkdir(parents=True, exist_ok=True)
35+
self.out_image_thumb.mkdir(parents=True, exist_ok=True)
36+
37+
def get_file_type(self, byte0: int, byte1: int) -> Optional[FileTypes]:
38+
"""获取文件格式及密钥"""
39+
for type, value in self.file_map.items():
40+
result0 = value[0] ^ byte0
41+
result1 = value[1] ^ byte1
42+
if result0 == result1:
43+
return FileTypes(type, result0)
44+
return None
45+
46+
def decode_file(self, image_file: Path, is_thumb: bool) -> Optional[str]:
47+
"""
48+
说明:
49+
解密微信图片文件,并返回新的文件地址
50+
51+
参数:
52+
* `image_file`:dat文件路径
53+
* `is_thumb`:是否为缩略图
54+
55+
返回:
56+
* `str`:解密文件路径
57+
"""
58+
file_value = np.fromfile(image_file, dtype=np.uint8)
59+
file_type = self.get_file_type(file_value[0], file_value[1])
60+
if file_type is None:
61+
return None
62+
xor_array = np.full_like(file_value, fill_value=file_type.key)
63+
out_value = np.bitwise_xor(file_value, xor_array)
64+
if is_thumb:
65+
out_file = self.out_image_thumb / f"{image_file.stem}.{file_type.file_type}"
66+
else:
67+
out_file = self.out_image_dir / f"{image_file.stem}.{file_type.file_type}"
68+
with open(out_file, mode="wb") as f:
69+
f.write(out_value)
70+
71+
return out_file.absolute()

ntchat_client/wechat/wechat.py

+35
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import asyncio
2+
import time
23
from asyncio import AbstractEventLoop
4+
from pathlib import Path
35
from typing import Any, Callable, NoReturn, Optional
46

57
import ntchat
@@ -17,6 +19,7 @@
1719
from ntchat_client.utils import escape_tag, notify
1820

1921
from .cache import FileCache
22+
from .image_decode import FileDecoder
2023
from .qrcode import draw_qrcode
2124

2225
wechat_client: "WeChatManager" = None
@@ -69,6 +72,10 @@ class WeChatManager:
6972
"""http_post处理器"""
7073
file_cache: FileCache
7174
"""文件缓存管理器"""
75+
image_decoder: FileDecoder
76+
"""图片解密器"""
77+
image_timeout: int
78+
"""图片下载超时时间"""
7279
msg_fiter = {
7380
ntchat.MT_USER_LOGIN_MSG,
7481
ntchat.MT_USER_LOGOUT_MSG,
@@ -88,6 +95,8 @@ def __new__(cls, *args, **kwargs):
8895
def __init__(self, config: Config) -> None:
8996
self.config = config
9097
self.file_cache = FileCache(config.cache_path)
98+
self.image_decoder = FileDecoder(config.image_path)
99+
self.image_timeout = config.image_timeout
91100
self.msg_fiter |= config.msg_filter
92101
ntchat.set_wechat_exe_path(wechat_version="3.6.0.18")
93102

@@ -208,6 +217,23 @@ def handle_ws_api(self, request: WsRequest) -> WsRequest:
208217
echo=echo, status=response.status, msg=response.msg, data=response.data
209218
)
210219

220+
def _handle_image(self, message: dict) -> Optional[dict]:
221+
"""处理图片消息,并替换字段,如果超时将会返回None"""
222+
data: dict = message["data"]
223+
image_file = Path(data["image"])
224+
image_thumb = Path(data["image_thumb"])
225+
time_count = 0
226+
while True:
227+
if image_file.exists() and image_thumb.exists():
228+
break
229+
time_count += 1
230+
if time_count > self.image_timeout:
231+
return None
232+
time.sleep(1)
233+
data["image"] = self.image_decoder.decode_file(image_file, False)
234+
data["image_thumb"] = self.image_decoder.decode_file(image_thumb, True)
235+
return message
236+
211237
def on_message(self, _: ntchat.WeChat, message: dict) -> None:
212238
"""接收消息"""
213239
# 过滤事件
@@ -218,6 +244,15 @@ def on_message(self, _: ntchat.WeChat, message: dict) -> None:
218244
if wx_id == self.self_id and not self.config.report_self:
219245
return
220246
logger.success(f"<m>wechat</m> - <g>收到wechat消息:</g>{escape_tag(str(message))}")
247+
if msgtype == 11047:
248+
# 群图片消息,预处理
249+
logger.debug("正在解密图片地址...")
250+
message = self._handle_image(message)
251+
if message is None:
252+
logger.error("下载图片超时,本次消息不会发送...")
253+
return
254+
logger.debug("解密图片已保存...")
255+
221256
if self.loop is not None:
222257
if self.loop.is_running:
223258
if self.ws_message_handler:

0 commit comments

Comments
 (0)