diff --git a/.gitignore b/.gitignore index 0d3413b..6cf9ef4 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ wheels/ # Local config templates /wechat_db_config_template.json .ace-tool/ +pnpm-lock.yaml # Local dev repos and data /WxDatDecrypt/ diff --git a/frontend/composables/useApi.js b/frontend/composables/useApi.js index 63d1209..c1ce542 100644 --- a/frontend/composables/useApi.js +++ b/frontend/composables/useApi.js @@ -375,7 +375,22 @@ export const useApi = () => { const url = `/wrapped/annual/cards/${safeId}` + (query.toString() ? `?${query.toString()}` : '') return await request(url) } - + + // 获取微信进程状态 + const getWxStatus = async () => { + return await request('/wechat/status') + } + + // 获取数据库密钥 + const getDbKey = async () => { + return await request('/get_db_key') + } + + // 获取图片密钥 + const getImageKey = async () => { + return await request('/get_image_key') + } + return { detectWechat, detectCurrentAccount, @@ -408,6 +423,9 @@ export const useApi = () => { exportChatContacts, getWrappedAnnual, getWrappedAnnualMeta, - getWrappedAnnualCard + getWrappedAnnualCard, + getDbKey, + getImageKey, + getWxStatus, } } diff --git a/frontend/package.json b/frontend/package.json index aff4c28..b527734 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -19,5 +19,8 @@ "ogl": "^1.0.11", "vue": "^3.5.17", "vue-router": "^4.5.1" + }, + "devDependencies": { + "tailwindcss": "3.4.17" } } diff --git a/frontend/pages/decrypt.vue b/frontend/pages/decrypt.vue index fb76aec..ab21f26 100644 --- a/frontend/pages/decrypt.vue +++ b/frontend/pages/decrypt.vue @@ -26,24 +26,40 @@
-
- -
- {{ formData.key.length }}/64 + +
+
+ +
+ {{ formData.key.length }}/64 +
+ +

@@ -55,7 +71,7 @@ - 使用 wx_key 等工具获取的64位十六进制字符串 + 尝试自动获取,或者使用 wx_key 等工具获取的64位十六进制字符串

@@ -131,6 +147,26 @@
+ +
+ 手动输入或通过微信获取 + +
+
@@ -158,7 +194,7 @@ - 使用 wx_key 获取图片密钥;AES 可选(V4-V2 需要) + 尝试自动获取,或使用 wx_key 获取图片密钥;AES 可选(V4-V2 需要)

@@ -325,6 +361,19 @@
+ + + +
+ + + +
+

温馨提示

+

{{ warning }}

+
+
+
@@ -367,12 +416,15 @@ import { ref, reactive, computed, onMounted } from 'vue' import { useApi } from '~/composables/useApi' -const { decryptDatabase, saveMediaKeys, getSavedKeys } = useApi() +const { decryptDatabase, saveMediaKeys, getSavedKeys, getDbKey, getImageKey, getWxStatus } = useApi() const loading = ref(false) const error = ref('') +const warning = ref('') // 警告,用于密钥提示 const currentStep = ref(0) const mediaAccount = ref('') +const isGettingDbKey = ref(false) +const isGettingImageKey = ref(false) // 步骤定义 const steps = [ @@ -453,10 +505,89 @@ const prefillKeysForAccount = async (account) => { } } +const handleGetDbKey = async () => { + if (isGettingDbKey.value) return + isGettingDbKey.value = true + + error.value = '' + warning.value = '' + formErrors.key = '' + + try { + const statusRes = await getWxStatus() // pid不是主进程,但是没关系 + const wxStatus = statusRes?.wx_status + + if (wxStatus?.is_running) { + warning.value = '检测到微信正在运行,5秒后将终止进程并重启以获取密钥!!' + await new Promise(resolve => setTimeout(resolve, 5000)) + } else { + // 没有逻辑 + } + + warning.value = '正在启动微信以获取密钥,请确保微信未开启“自动登录”,并在启动后 1 分钟内完成登录操作。' + + const res = await getDbKey() + + if (res && res.status === 0) { + if (res.data?.db_key) { + formData.key = res.data.db_key + warning.value = '' + } + + if (res.errmsg && res.errmsg !== 'ok') { + warning.value = res.errmsg + } + } else { + error.value = '获取失败: ' + (res?.errmsg || '未知错误') + } + } catch (e) { + console.error(e) + error.value = '系统错误: ' + e.message + } finally { + isGettingDbKey.value = false + } +} + +const handleGetImageKey = async () => { + if (isGettingImageKey.value) return + isGettingImageKey.value = true + manualKeyErrors.xor_key = '' + manualKeyErrors.aes_key = '' + + error.value = '' + warning.value = '' + + try { + const res = await getImageKey() + + if (res && res.status === 0) { + if (res.data?.aes_key) { + manualKeys.aes_key = res.data.aes_key + } + if (res.data?.xor_key) { + // 后端记得处理为16进制再返回!!! + manualKeys.xor_key = res.data.xor_key + } + + if (res.errmsg && res.errmsg !== 'ok') { + error.value = res.errmsg + } + } else { + error.value = '获取失败: ' + (res?.errmsg || '未知错误') + } + } catch (e) { + console.error(e) + error.value = '系统错误: ' + e.message + } finally { + isGettingImageKey.value = false + } +} + const applyManualKeys = () => { manualKeyErrors.xor_key = '' manualKeyErrors.aes_key = '' error.value = '' + warning.value = '' const aes = normalizeAesKey(manualKeys.aes_key) if (!aes.ok) { @@ -550,6 +681,7 @@ const handleDecrypt = async () => { loading.value = true error.value = '' + warning.value = '' try { const result = await decryptDatabase({ @@ -596,6 +728,7 @@ const decryptAllImages = async () => { mediaDecrypting.value = true mediaDecryptResult.value = null error.value = '' + warning.value = '' // 重置进度 decryptProgress.current = 0 @@ -671,6 +804,7 @@ const decryptAllImages = async () => { // 从密钥步骤进入图片解密步骤 const goToMediaDecryptStep = async () => { error.value = '' + warning.value = '' // 校验并应用(未填写则允许直接进入,后端会使用已保存密钥或报错提示) const ok = applyManualKeys() if (!ok || manualKeyErrors.xor_key || manualKeyErrors.aes_key) return diff --git a/pyproject.toml b/pyproject.toml index 96d2d4e..8b3e106 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,6 +19,9 @@ dependencies = [ "zstandard>=0.23.0", "pilk>=0.2.4", "pypinyin>=0.53.0", + "wx_key", + "packaging", + "httpx", ] [project.optional-dependencies] @@ -40,3 +43,6 @@ include = [ "src/wechat_decrypt_tool/native/wcdb_api.dll", "src/wechat_decrypt_tool/native/WCDB.dll", ] + +[tool.uv] +find-links = ["./tools/key_wheels/"] diff --git a/src/wechat_decrypt_tool/key_service.py b/src/wechat_decrypt_tool/key_service.py new file mode 100644 index 0000000..e2dec04 --- /dev/null +++ b/src/wechat_decrypt_tool/key_service.py @@ -0,0 +1,357 @@ +# import sys +# import requests + +try: + import wx_key +except ImportError: + print('[!] 环境中未安装wx_key依赖,可能无法自动获取数据库密钥') + wx_key = None + # sys.exit(1) + +import time +import psutil +import subprocess +import hashlib +import os +import json +import random +import logging +import httpx +from pathlib import Path +from typing import Optional, List, Dict, Any +from dataclasses import dataclass +from packaging import version as pkg_version # 建议使用 packaging 库处理版本比较 +from .wechat_detection import detect_wechat_installation +from .key_store import upsert_account_keys_in_store +from .media_helpers import _resolve_account_dir, _resolve_account_wxid_dir + +logger = logging.getLogger(__name__) + + +# ====================== 以下是hook逻辑 ====================================== + +@dataclass +class HookConfig: + min_version: str + pattern: str # 用 00 不要用 ? !!!! 否则C++内存会炸 + mask: str + offset: int + + +class WeChatKeyFetcher: + def __init__(self): + self.process_name = "Weixin.exe" + self.timeout_seconds = 60 + + @staticmethod + def _hex_array_to_str(hex_array: List[int]) -> str: + return " ".join([f"{b:02X}" for b in hex_array]) + + def _get_hook_config(self, version_str: str) -> Optional[HookConfig]: + """搬运自wx_key代码,未来用ida脚本直接获取即可""" + try: + v_curr = pkg_version.parse(version_str) + except Exception as e: + logger.error(f"版本号解析失败: {version_str} || {e}") + return None + + if v_curr > pkg_version.parse("4.1.6.14"): + return HookConfig( + min_version=">4.1.6.14", + pattern=self._hex_array_to_str([ + 0x24, 0x50, 0x48, 0xC7, 0x45, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, + 0x44, 0x89, 0xCF, 0x44, 0x89, 0xC3, 0x49, 0x89, 0xD6, 0x48, + 0x89, 0xCE, 0x48, 0x89 + ]), + mask="xxxxxxxxxxxxxxxxxxxxxxxx", + offset=-3 + ) + + if pkg_version.parse("4.1.4") <= v_curr <= pkg_version.parse("4.1.6.14"): + return HookConfig( + min_version="4.1.4-4.1.6.14", + pattern=self._hex_array_to_str([ + 0x24, 0x08, 0x48, 0x89, 0x6c, 0x24, 0x10, 0x48, 0x89, 0x74, + 0x00, 0x18, 0x48, 0x89, 0x7c, 0x00, 0x20, 0x41, 0x56, 0x48, + 0x83, 0xec, 0x50, 0x41 + ]), + mask="xxxxxxxxxx?xxxx?xxxxxxxx", + offset=-3 + ) + + if v_curr < pkg_version.parse("4.1.4"): + return HookConfig( + min_version="<4.1.4", + pattern=self._hex_array_to_str([ + 0x24, 0x50, 0x48, 0xc7, 0x45, 0x00, 0xfe, 0xff, 0xff, 0xff, + 0x44, 0x89, 0xcf, 0x44, 0x89, 0xc3, 0x49, 0x89, 0xd6, 0x48, + 0x89, 0xce, 0x48, 0x89 + ]), + mask="xxxxxxxxxxxxxxxxxxxxxxxx", + offset=-15 # -0xf + ) + + return None + + def kill_wechat(self): + """检测并查杀微信进程""" + killed = False + for proc in psutil.process_iter(['pid', 'name']): + try: + if proc.info['name'] == self.process_name: + logger.info(f"Killing WeChat process: {proc.info['pid']}") + proc.terminate() + killed = True + except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess): + pass + + if killed: + time.sleep(1) # 等待完全退出 + + def launch_wechat(self, exe_path: str) -> int: + """启动微信并返回 PID""" + try: + + process = subprocess.Popen(exe_path) + + time.sleep(2) + candidates = [] + for proc in psutil.process_iter(['pid', 'name', 'create_time']): + if proc.info['name'] == self.process_name: + candidates.append(proc) + + if candidates: + + candidates.sort(key=lambda x: x.info['create_time'], reverse=True) + target_pid = candidates[0].info['pid'] + return target_pid + + return process.pid + + except Exception as e: + logger.error(f"启动微信失败: {e}") + raise RuntimeError(f"无法启动微信: {e}") + + def fetch_key(self) -> str: + """没有wx_key模块无法自动获取密钥""" + if wx_key is None: + raise RuntimeError("wx_key 模块未安装或加载失败") + + install_info = detect_wechat_installation() + + exe_path = install_info.get('wechat_exe_path') + version = install_info.get('wechat_version') + + if not exe_path or not version: + raise RuntimeError("无法自动定位微信安装路径或版本") + + logger.info(f"Detect WeChat: {version} at {exe_path}") + + config = self._get_hook_config(version) + if not config: + raise RuntimeError(f"不支持的微信版本: {version}") + + self.kill_wechat() + + pid = self.launch_wechat(exe_path) + logger.info(f"WeChat launched, PID: {pid}") + + logger.info(f"Initializing Hook with pattern: {config.pattern[:20]}... Offset: {config.offset}") + + if not wx_key.initialize_hook(pid, "", config.pattern, config.mask, config.offset): + err = wx_key.get_last_error_msg() + raise RuntimeError(f"Hook初始化失败: {err}") + + start_time = time.time() + + + try: + while True: + if time.time() - start_time > self.timeout_seconds: + raise TimeoutError("获取密钥超时 (60s)") + + key = wx_key.poll_key_data() + if key: + found_key = key + break + + while True: + msg, level = wx_key.get_status_message() + if msg is None: + break + if level == 2: + logger.error(f"[Hook Error] {msg}") + + time.sleep(0.1) + + finally: + logger.info("Cleaning up hook...") + wx_key.cleanup_hook() + + if found_key: + return found_key + else: + raise RuntimeError("未知错误,未获取到密钥") + +def get_db_key_workflow(): + fetcher = WeChatKeyFetcher() + return fetcher.fetch_key() + + +# ============================== 以下是图片密钥逻辑 ===================================== + + +# 远程 API 配置 +REMOTE_URL = "https://view.free.c3o.re/dashboard" +NEXT_ACTION_ID = "7c8f99280c70626ccf5960cc4a68f368197e15f8e9" + + +def get_wechat_internal_global_config(wx_dir: Path, file_name1) -> bytes: + """ + 读微信目录下的主配置文件 + """ + xwechat_files_root = wx_dir.parent + + target_path = os.path.join(xwechat_files_root, "all_users", "config", file_name1) + + if not os.path.exists(target_path): + logger.error(f"未找到微信内部 global_config: {target_path}") + raise FileNotFoundError(f"找不到文件: {target_path},请确认微信数据目录结构是否完整") + + return Path(target_path).read_bytes() + + +# def get_local_config_sha3_224() -> bytes: +# """ +# 不要在意,抽象的实现 哈哈哈 +# """ +# content = json.dumps({ +# "wxfile_dir": "C:\\Users\\17078\\xwechat_files", +# "weixin_id_folder": "wxid_lnyf4hdo9csb12_f1c4", +# "cache_dir": "C:\\Users\\17078\\Desktop\\wxDBHook\\test\\wx-dat\\wx-dat\\.cache", +# "db_key": "", +# "port": 8001 +# }, indent=4).encode("utf-8") +# +# # 计算 SHA3-224 +# digest = hashlib.sha3_224(content).digest() +# return digest + +# async def log_request(request): +# print(f"--- Request Raw ---") +# print(f"{request.method} {request.url} {request.extensions.get('http_version', b'HTTP/1.1').decode()}") +# for name, value in request.headers.items(): +# print(f"{name}: {value}") +# +# print() +# +# body = request.read() +# if body: +# print(body.decode(errors='replace')) +# print(f"-------------------\n") + + +async def fetch_and_save_remote_keys(account: Optional[str] = None) -> Dict[str, Any]: + account_dir = _resolve_account_dir(account) + wx_id_dir = _resolve_account_wxid_dir(account_dir) + wxid = wx_id_dir.name + + logger.info(f"正在为账号 {wxid} 获取密钥...") + + try: + blob1_bytes = get_wechat_internal_global_config(wx_id_dir, file_name1= "global_config") # 估计这是唯一有效的数据!! + logger.info(f"获取微信内部配置成功,大小: {len(blob1_bytes)} bytes") + except Exception as e: + raise RuntimeError(f"读取微信内部文件失败: {e}") + + try: + blob2_bytes = get_wechat_internal_global_config(wx_id_dir, file_name1= "global_config.crc") + logger.info(f"获取微信内部配置成功,大小: {len(blob2_bytes)} bytes") + except Exception as e: + raise RuntimeError(f"读取微信内部文件失败: {e}") + + blob3_bytes = b"" + + headers = { + "Accept": "text/x-component", + "Next-Action": NEXT_ACTION_ID, + "Next-Router-State-Tree": "%5B%22%22%2C%7B%22children%22%3A%5B%22dashboard%22%2C%7B%22children%22%3A%5B%22__PAGE__%22%2C%7B%7D%2Cnull%2Cnull%5D%7D%2Cnull%2Cnull%5D%7D%2Cnull%2Cnull%2Ctrue%5D", + "Origin": "https://view.free.c3o.re", + "Referer": "https://view.free.c3o.re/dashboard", + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36" + } + + files = { + '1': ('blob', blob1_bytes, 'application/octet-stream'), + '2': ('blob', blob2_bytes, 'application/octet-stream'), + '3': ('blob', blob3_bytes, 'application/octet-stream'), + '0': (None, json.dumps([wxid, "$A1", "$A2", "$A3", 0],separators=(",",":")).encode('utf-8')), + } + + + async with httpx.AsyncClient(timeout=30) as client: + logger.info("向远程服务器发送请求...") + response = await client.post(REMOTE_URL, headers=headers, files=files) + + if response.status_code != 200: + raise RuntimeError(f"远程服务器错误: {response.status_code} - {response.text[:100]}") + + + result_data = {} + lines = response.text.split('\n') + + found_config = False + for line in lines: + line = line.strip() + if not line: + continue + + if line.startswith('1:'): + try: + json_part = line[2:] # 去掉 "1:" + data_obj = json.loads(json_part) + + if "config" in data_obj: + config = data_obj["config"] + result_data = { + "xor_key": config.get("xor_key", ""), + "aes_key": config.get("aes_key", ""), + "nick_name": config.get("nick_name", ""), + "avatar_url": config.get("avatar_url", "") + } + found_config = True + break + except Exception as e: + logger.warning(f"解析响应行失败: {e}") + continue + + if not found_config or not result_data.get("aes_key"): + logger.error(f"响应中未找到密钥信息。Full Response: {response.text[:500]}") + raise RuntimeError("解析失败: 服务器未返回 config 数据") + + # 6. 处理并保存密钥 + xor_raw = str(result_data["xor_key"]) + aes_val = str(result_data["aes_key"]) + + try: + if xor_raw.startswith("0x"): + xor_int = int(xor_raw, 16) + else: + xor_int = int(xor_raw) + xor_hex_str = f"0x{xor_int:02X}" + except: + xor_hex_str = xor_raw + + upsert_account_keys_in_store( + account=wxid, + image_xor_key=xor_hex_str, + image_aes_key=aes_val + ) + + return { + "wxid": wxid, + "xor_key": xor_hex_str, + "aes_key": aes_val, + "nick_name": result_data["nick_name"] + } + diff --git a/src/wechat_decrypt_tool/routers/keys.py b/src/wechat_decrypt_tool/routers/keys.py index 20344e4..101f9e5 100644 --- a/src/wechat_decrypt_tool/routers/keys.py +++ b/src/wechat_decrypt_tool/routers/keys.py @@ -3,6 +3,7 @@ from typing import Optional from fastapi import APIRouter from ..key_store import get_account_keys_from_store +from ..key_service import get_db_key_workflow, fetch_and_save_remote_keys from ..media_helpers import _load_media_keys, _resolve_account_dir from ..path_fix import PathFixRoute @@ -51,3 +52,76 @@ async def get_saved_keys(account: Optional[str] = None): "keys": result, } + +@router.get("/api/get_db_key", summary="自动获取微信数据库密钥") +async def get_wechat_db_key(): + """ + 自动流程: + 1. 结束微信进程 + 2. 启动微信 + 3. 根据版本注入 Hook + 4. 抓取密钥并返回 + """ + try: + # 不需要async吧,我相信fastapi的线程池 + db_key = get_db_key_workflow() + + return { + "status": 0, + "errmsg": "ok", + "data": { + "db_key": db_key + } + } + + except TimeoutError: + return { + "status": -1, + "errmsg": "获取超时,请确保微信没有开启自动登录 或者 加快手速", + "data": {} + } + except Exception as e: + return { + "status": -1, + "errmsg": f"获取失败: {str(e)}", + "data": {} + } + + +@router.get("/api/get_image_key", summary="获取并保存微信图片密钥") +async def get_image_key(account: Optional[str] = None): + """ + 通过模拟 Next.js Server Action 协议,利用本地微信配置文件换取 AES/XOR 密钥。 + + 1. 读取 [wx_dir]/all_users/config/global_config (Blob 1) + 2. 读 同上目录下的global_config.crc + 3. 构造 Multipart 包发送至远程服务器 + 4. 解析返回流,自动存入本地数据库 + """ + try: + result = await fetch_and_save_remote_keys(account) + + return { + "status": 0, + "errmsg": "ok", + "data": { + "xor_key": result["xor_key"], + "aes_key": result["aes_key"], + "nick_name": result.get("nick_name"), + "account": result["wxid"] + } + } + except FileNotFoundError as e: + return { + "status": -1, + "errmsg": f"文件缺失: {str(e)}", + "data": {} + } + except Exception as e: + import traceback + traceback.print_exc() + return { + "status": -1, + "errmsg": f"获取失败: {str(e)}", + "data": {} + } diff --git a/src/wechat_decrypt_tool/routers/wechat_detection.py b/src/wechat_decrypt_tool/routers/wechat_detection.py index ae0077d..b2fe1c5 100644 --- a/src/wechat_decrypt_tool/routers/wechat_detection.py +++ b/src/wechat_decrypt_tool/routers/wechat_detection.py @@ -1,5 +1,5 @@ from typing import Optional - +import psutil from fastapi import APIRouter from ..logging_config import get_logger @@ -71,3 +71,49 @@ async def detect_current_account(data_root_path: Optional[str] = None): 'error': str(e), 'data': None, } + + +@router.get("/api/wechat/status", summary="检查微信运行状态") +async def check_wechat_status(): + """ + 检查系统中是否有 Weixin.exe 或 WeChat.exe 进程在运行 + 返回: status=0 成功, wx_status={is_running: bool, pid: int, ...} + """ + process_name_targets = ["Weixin.exe", "WeChat.exe"] + + wx_status = { + "is_running": False, + "pid": None, + "exe_path": None, + "memory_usage_mb": 0.0 + } + + try: + for proc in psutil.process_iter(['pid', 'name', 'exe', 'memory_info']): + try: + if proc.info['name'] and proc.info['name'] in process_name_targets: + wx_status["is_running"] = True + wx_status["pid"] = proc.info['pid'] + wx_status["exe_path"] = proc.info['exe'] + + mem = proc.info['memory_info'] + if mem: + wx_status["memory_usage_mb"] = round(mem.rss / (1024 * 1024), 2) + + break + except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess): + continue + + return { + "status": 0, + "errmsg": "ok", + "wx_status": wx_status + } + + except Exception as e: + # 即使出错也返回 JSON,但 status 非 0 + return { + "status": -1, + "errmsg": f"检查进程失败: {str(e)}", + "wx_status": wx_status + } diff --git a/tools/key_wheels/README.md b/tools/key_wheels/README.md new file mode 100644 index 0000000..fe0194f --- /dev/null +++ b/tools/key_wheels/README.md @@ -0,0 +1,2 @@ +> 这里放wx_key模块的python预编译wheel:https://github.com/H3CoF6/py_wx_key/releases/ +> 解压放入即可 \ No newline at end of file diff --git a/tools/key_wheels/wx_key-1.0.0-cp310-cp310-win_amd64.whl b/tools/key_wheels/wx_key-1.0.0-cp310-cp310-win_amd64.whl new file mode 100644 index 0000000..0276318 Binary files /dev/null and b/tools/key_wheels/wx_key-1.0.0-cp310-cp310-win_amd64.whl differ diff --git a/tools/key_wheels/wx_key-1.0.0-cp311-cp311-win_amd64.whl b/tools/key_wheels/wx_key-1.0.0-cp311-cp311-win_amd64.whl new file mode 100644 index 0000000..b286145 Binary files /dev/null and b/tools/key_wheels/wx_key-1.0.0-cp311-cp311-win_amd64.whl differ diff --git a/tools/key_wheels/wx_key-1.0.0-cp312-cp312-win_amd64.whl b/tools/key_wheels/wx_key-1.0.0-cp312-cp312-win_amd64.whl new file mode 100644 index 0000000..c049ea0 Binary files /dev/null and b/tools/key_wheels/wx_key-1.0.0-cp312-cp312-win_amd64.whl differ diff --git a/tools/key_wheels/wx_key-1.0.0-cp313-cp313-win_amd64.whl b/tools/key_wheels/wx_key-1.0.0-cp313-cp313-win_amd64.whl new file mode 100644 index 0000000..42afeda Binary files /dev/null and b/tools/key_wheels/wx_key-1.0.0-cp313-cp313-win_amd64.whl differ diff --git a/tools/key_wheels/wx_key-1.0.0-cp314-cp314-win_amd64.whl b/tools/key_wheels/wx_key-1.0.0-cp314-cp314-win_amd64.whl new file mode 100644 index 0000000..d19b6f4 Binary files /dev/null and b/tools/key_wheels/wx_key-1.0.0-cp314-cp314-win_amd64.whl differ diff --git a/uv.lock b/uv.lock index a3eb323..cc35501 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 2 +revision = 3 requires-python = ">=3.11" [[package]] @@ -230,6 +230,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, ] +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + [[package]] name = "httptools" version = "0.6.4" @@ -259,6 +272,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4d/dc/7decab5c404d1d2cdc1bb330b1bf70e83d6af0396fd4fc76fc60c0d522bf/httptools-0.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:28908df1b9bb8187393d5b5db91435ccc9c8e891657f9cbb42a2541b44c82fc8", size = 87682, upload-time = "2024-10-16T19:44:46.46Z" }, ] +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + [[package]] name = "idna" version = "3.10" @@ -844,7 +872,9 @@ dependencies = [ { name = "aiofiles" }, { name = "cryptography" }, { name = "fastapi" }, + { name = "httpx" }, { name = "loguru" }, + { name = "packaging" }, { name = "pilk" }, { name = "psutil" }, { name = "pycryptodome" }, @@ -854,6 +884,7 @@ dependencies = [ { name = "requests" }, { name = "typing-extensions" }, { name = "uvicorn", extra = ["standard"] }, + { name = "wx-key" }, { name = "zstandard" }, ] @@ -867,7 +898,9 @@ requires-dist = [ { name = "aiofiles", specifier = ">=23.2.1" }, { name = "cryptography", specifier = ">=41.0.0" }, { name = "fastapi", specifier = ">=0.104.0" }, + { name = "httpx" }, { name = "loguru", specifier = ">=0.7.0" }, + { name = "packaging" }, { name = "pilk", specifier = ">=0.2.4" }, { name = "psutil", specifier = ">=7.0.0" }, { name = "pycryptodome", specifier = ">=3.23.0" }, @@ -878,6 +911,7 @@ requires-dist = [ { name = "requests", specifier = ">=2.32.4" }, { name = "typing-extensions", specifier = ">=4.8.0" }, { name = "uvicorn", extras = ["standard"], specifier = ">=0.24.0" }, + { name = "wx-key" }, { name = "zstandard", specifier = ">=0.23.0" }, ] provides-extras = ["build"] @@ -891,6 +925,17 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e1/07/c6fe3ad3e685340704d314d765b7912993bcb8dc198f0e7a89382d37974b/win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390", size = 4083, upload-time = "2024-12-07T15:28:26.465Z" }, ] +[[package]] +name = "wx-key" +version = "1.0.0" +source = { registry = "tools/key_wheels" } +wheels = [ + { path = "wx_key-1.0.0-cp311-cp311-win_amd64.whl" }, + { path = "wx_key-1.0.0-cp312-cp312-win_amd64.whl" }, + { path = "wx_key-1.0.0-cp313-cp313-win_amd64.whl" }, + { path = "wx_key-1.0.0-cp314-cp314-win_amd64.whl" }, +] + [[package]] name = "zstandard" version = "0.25.0"