fix: fix merge conflict

This commit is contained in:
H3CoF6
2026-02-28 00:22:37 +08:00
Unverified
17 changed files with 158 additions and 268 deletions
+3 -3
View File
@@ -530,8 +530,8 @@ export const useApi = () => {
}
// 获取数据库密钥
const getDbKey = async () => {
return await request('/get_db_key')
const getKeys = async () => {
return await request('/get_keys')
}
// 获取图片密钥
@@ -589,7 +589,7 @@ export const useApi = () => {
getWrappedAnnual,
getWrappedAnnualMeta,
getWrappedAnnualCard,
getDbKey,
getKeys,
getImageKey,
getWxStatus,
}
+71 -83
View File
@@ -58,7 +58,7 @@
<svg v-else class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
</svg>
{{ isGettingDbKey ? '获取中...' : '自动获取' }}
{{ isGettingDbKey ? '获取中...' : '一键获取全部密钥' }}
</button>
</div>
<p v-if="formErrors.key" class="mt-1 text-sm text-red-600 flex items-center">
@@ -71,7 +71,7 @@
<svg class="w-4 h-4 mr-1 text-[#10AEEF]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
尝试自动获取或者使用 <a href="https://github.com/ycccccccy/wx_key" target="_blank" class="text-[#07C160] hover:text-[#06AD56]">wx_key</a> 等工具获取的64位十六进制字符串
点击按钮将自动获取数据库图片双重密钥您也可以手动输入已知的64位密钥使用<a href="https://github.com/ycccccccy/wx_key" target="_blank" class="text-[#07C160] hover:text-[#06AD56]">wx_key</a>等工具获取
</p>
</div>
@@ -183,53 +183,37 @@
<div class="bg-gray-50 rounded-lg p-4">
<div class="flex justify-between items-center mb-4 pb-3 border-b border-gray-200">
<span class="text-sm font-medium text-gray-500">手动输入或通过微信获取</span>
<button
type="button"
@click="handleGetImageKey"
:disabled="isGettingImageKey"
class="flex-none inline-flex items-center px-4 py-3 bg-[#07C160] text-white rounded-lg text-sm font-medium hover:bg-[#06AD56] transition-all duration-200 disabled:opacity-50 disabled:cursor-wait whitespace-nowrap"
>
<svg v-if="isGettingImageKey" class="animate-spin -ml-1 mr-2 h-4 w-4 text-white" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path>
</svg>
<svg v-else class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
</svg>
{{ isGettingImageKey ? '正在获取...' : '自动获取' }}
</button>
<span class="text-sm font-medium text-gray-500">此步骤将为您解密微信聊天中的图片</span>
</div>
<p class="mt-3 mb-4 text-xs text-[#7F7F7F] flex items-center">
<svg class="w-4 h-4 mr-1 text-[#10AEEF]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
如果您在第一步使用了一键获取或触发了云端解析下方输入框已被自动填充您也可可以使用<a href="https://github.com/ycccccccy/wx_key" target="_blank" class="text-[#07C160] hover:text-[#06AD56]">wx_key</a>等工具手动获取
</p>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-[#000000e6] mb-2">XOR必填</label>
<input
v-model="manualKeys.xor_key"
type="text"
placeholder="例如:0xA5"
class="w-full px-4 py-2 border border-[#EDEDED] rounded-lg focus:ring-2 focus:ring-[#10AEEF] focus:border-transparent font-mono"
v-model="manualKeys.xor_key"
type="text"
placeholder="例如:0xA5"
class="w-full px-4 py-2 border border-[#EDEDED] rounded-lg focus:ring-2 focus:ring-[#10AEEF] focus:border-transparent font-mono"
/>
<p v-if="manualKeyErrors.xor_key" class="text-xs text-red-500 mt-1">{{ manualKeyErrors.xor_key }}</p>
</div>
<div>
<label class="block text-sm font-medium text-[#000000e6] mb-2">AES可选</label>
<input
v-model="manualKeys.aes_key"
type="text"
placeholder="16 个字符(V4-V2 需要)"
class="w-full px-4 py-2 border border-[#EDEDED] rounded-lg focus:ring-2 focus:ring-[#10AEEF] focus:border-transparent font-mono"
v-model="manualKeys.aes_key"
type="text"
placeholder="16 个字符(V4-V2 需要)"
class="w-full px-4 py-2 border border-[#EDEDED] rounded-lg focus:ring-2 focus:ring-[#10AEEF] focus:border-transparent font-mono"
/>
<p v-if="manualKeyErrors.aes_key" class="text-xs text-red-500 mt-1">{{ manualKeyErrors.aes_key }}</p>
</div>
</div>
<p class="mt-3 text-xs text-[#7F7F7F] flex items-center">
<svg class="w-4 h-4 mr-1 text-[#10AEEF]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
尝试自动获取或使用 <a href="https://github.com/ycccccccy/wx_key" target="_blank" class="text-[#07C160] hover:text-[#06AD56]">wx_key</a> 获取图片密钥AES 可选V4-V2 需要
</p>
</div>
</div>
@@ -450,7 +434,7 @@
import { ref, reactive, computed, onMounted, onBeforeUnmount } from 'vue'
import { useApi } from '~/composables/useApi'
const { decryptDatabase, saveMediaKeys, getSavedKeys, getDbKey, getImageKey, getWxStatus } = useApi()
const { decryptDatabase, saveMediaKeys, getSavedKeys, getKeys, getImageKey, getWxStatus } = useApi()
const loading = ref(false)
const error = ref('')
@@ -458,7 +442,6 @@ const warning = ref('') // 警告,用于密钥提示
const currentStep = ref(0)
const mediaAccount = ref('')
const isGettingDbKey = ref(false)
const isGettingImageKey = ref(false)
// 步骤定义
const steps = [
@@ -548,74 +531,45 @@ const handleGetDbKey = async () => {
formErrors.key = ''
try {
const statusRes = await getWxStatus() // pid不是主进程,但是没关系
const statusRes = await getWxStatus()
const wxStatus = statusRes?.wx_status
if (wxStatus?.is_running) {
warning.value = '检测到微信正在运行,5秒后将终止进程并重启以获取密钥!'
warning.value = '检测到微信正在运行,5秒后将终止进程并重启以获取全套密钥!'
await new Promise(resolve => setTimeout(resolve, 5000))
} else {
// 没有逻辑
}
warning.value = '正在启动微信以获取密钥,请确保微信未开启“自动登录”,并在启动后 1 分钟内完成登录操作。'
warning.value = '正在启动微信,请确保微信未开启“自动登录”,并在弹窗中正常登录。'
const res = await getDbKey()
const res = await getKeys()
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
// 直接把图片密钥也存好
if (res.data?.xor_key) {
manualKeys.xor_key = res.data.xor_key
}
if (res.data?.aes_key) {
manualKeys.aes_key = res.data.aes_key
}
warning.value = '🎉 数据库与图片密钥均已获取成功!'
// 3秒后清除成功提示,保持 UI 干净
setTimeout(() => { if(warning.value.includes('获取成功')) warning.value = '' }, 3000)
} else {
error.value = '获取失败: ' + (res?.errmsg || '未知错误')
warning.value = ''
}
} catch (e) {
console.error(e)
error.value = '系统错误: ' + e.message
warning.value = ''
} 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 = ''
@@ -749,13 +703,13 @@ const handleDecrypt = async () => {
if (!validateForm()) {
return
}
loading.value = true
error.value = ''
warning.value = ''
resetDbDecryptProgress()
try {
const canSse = process.client && typeof window !== 'undefined' && typeof EventSource !== 'undefined'
@@ -776,9 +730,26 @@ const handleDecrypt = async () => {
if (accounts.length > 0) mediaAccount.value = accounts[0]
} catch (e) {}
clearManualKeys()
currentStep.value = 1
await prefillKeysForAccount(mediaAccount.value)
if (!manualKeys.xor_key && !manualKeys.aes_key) {
warning.value = '正在通过云端备选方案自动获取图片密钥,请稍候...'
try {
const imgRes = await getImageKey({ account: mediaAccount.value })
if (imgRes && imgRes.status === 0) {
if (imgRes.data?.xor_key) manualKeys.xor_key = imgRes.data.xor_key
if (imgRes.data?.aes_key) manualKeys.aes_key = imgRes.data.aes_key
warning.value = '已通过云端成功获取图片密钥!'
setTimeout(() => { if(warning.value.includes('成功获取')) warning.value = '' }, 3000)
} else {
warning.value = '云端获取图片密钥失败,您可以尝试手动填写。'
}
} catch (e) {
warning.value = '网络请求失败,请手动填写图片密钥。'
}
}
} else if (result.status === 'failed') {
if (result.failure_count > 0 && result.success_count === 0) {
error.value = result.message || '所有文件解密失败'
@@ -855,9 +826,26 @@ const handleDecrypt = async () => {
loading.value = false
if (data.status === 'completed') {
clearManualKeys()
currentStep.value = 1
await prefillKeysForAccount(mediaAccount.value)
// 【重点】如果刚才没有通过双 Hook 拿到图片密钥,触发云端 API 备用方案自动获取
if (!manualKeys.xor_key && !manualKeys.aes_key) {
warning.value = '正在通过云端备选方案自动获取图片密钥,请稍候...'
try {
const imgRes = await getImageKey({ account: mediaAccount.value })
if (imgRes && imgRes.status === 0) {
if (imgRes.data?.xor_key) manualKeys.xor_key = imgRes.data.xor_key
if (imgRes.data?.aes_key) manualKeys.aes_key = imgRes.data.aes_key
warning.value = '已通过云端成功获取图片密钥!'
setTimeout(() => { if(warning.value.includes('成功获取')) warning.value = '' }, 3000)
} else {
warning.value = '云端获取图片密钥失败,您可以尝试手动填写。'
}
} catch (e) {
warning.value = '网络请求失败,请手动填写图片密钥。'
}
}
} else if (data.status === 'failed') {
error.value = data.message || '所有文件解密失败'
} else {
+1 -1
View File
@@ -20,7 +20,7 @@ dependencies = [
"pilk>=0.2.4",
"pypinyin>=0.53.0",
"jieba>=0.42.1",
"wx_key",
"wx_key>=1.1.0",
"packaging",
"httpx",
]
+65 -160
View File
@@ -35,10 +35,12 @@ logger = logging.getLogger(__name__)
@dataclass
class HookConfig:
min_version: str
pattern: str # 用 00 不要用 ? !!!! 否则C++内存会炸
pattern: str
mask: str
offset: int
md5_pattern: str = ""
md5_mask: str = ""
md5_offset: int = 0
class WeChatKeyFetcher:
def __init__(self):
@@ -50,13 +52,13 @@ class WeChatKeyFetcher:
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",
@@ -66,7 +68,10 @@ class WeChatKeyFetcher:
0x89, 0xCE, 0x48, 0x89
]),
mask="xxxxxxxxxxxxxxxxxxxxxxxx",
offset=-3
offset=-3,
md5_pattern="48 8D 4D 00 48 89 4D B0 48 89 45 B8 48 8D 7D 00 48 8D 55 B0 48 89 F9",
md5_mask="xxx?xxxxxxxxxxx?xxxxxxx",
md5_offset=4
)
if pkg_version.parse("4.1.4") <= v_curr <= pkg_version.parse("4.1.6.14"):
@@ -78,10 +83,14 @@ class WeChatKeyFetcher:
0x83, 0xec, 0x50, 0x41
]),
mask="xxxxxxxxxx?xxxx?xxxxxxxx",
offset=-3
offset=-3,
md5_pattern="48 8D 4D 00 48 89 4D B0 48 89 45 B8 48 8D 7D 00 48 8D 55 B0 48 89 F9",
md5_mask="xxx?xxxxxxxxxxx?xxxxxxx",
md5_offset=4
)
if v_curr < pkg_version.parse("4.1.4"):
"""图片密钥可能是错的,版本过低没有测试"""
return HookConfig(
min_version="<4.1.4",
pattern=self._hex_array_to_str([
@@ -90,7 +99,10 @@ class WeChatKeyFetcher:
0x89, 0xce, 0x48, 0x89
]),
mask="xxxxxxxxxxxxxxxxxxxxxxxx",
offset=-15 # -0xf
offset=-15, # -0xf
md5_pattern="48 8D 4D 00 48 89 4D B0 48 89 45 B8 48 8D 7D 00 48 8D 55 B0 48 89 F9",
md5_mask="xxx?xxxxxxxxxxx?xxxxxxx",
md5_offset=4
)
return None
@@ -134,13 +146,12 @@ class WeChatKeyFetcher:
logger.error(f"启动微信失败: {e}")
raise RuntimeError(f"无法启动微信: {e}")
def fetch_key(self) -> str:
"""没有wx_key模块无法自动获取密钥"""
def fetch_key(self) -> dict:
"""调用 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')
@@ -151,30 +162,34 @@ class WeChatKeyFetcher:
config = self._get_hook_config(version)
if not config:
raise RuntimeError(f"不支持的微信版本: {version}")
raise RuntimeError(f"原生获取失败:当前微信版本 ({version}) 过低,为保证稳定性,仅支持 4.1.5 及以上版本使用原生获取。")
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):
if not wx_key.initialize_hook(pid, "", config.pattern, config.mask, config.offset,
config.md5_pattern, config.md5_mask, config.md5_offset):
err = wx_key.get_last_error_msg()
raise RuntimeError(f"Hook初始化失败: {err}")
start_time = time.time()
found_db_key = None
found_md5_data = None
try:
while True:
if time.time() - start_time > self.timeout_seconds:
raise TimeoutError("获取密钥超时 (60s)")
raise TimeoutError("获取密钥超时 (60s),请确保在弹出的微信中完成登录。")
key = wx_key.poll_key_data()
if key:
found_key = key
key_data = wx_key.poll_key_data()
if key_data:
if 'key' in key_data:
found_db_key = key_data['key']
if 'md5' in key_data:
found_md5_data = key_data['md5']
if found_db_key and found_md5_data:
break
while True:
@@ -185,15 +200,22 @@ class WeChatKeyFetcher:
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("未知错误,未获取到密钥")
aes_key = None # gemini !!! ???
xor_key = None
if found_md5_data and "|" in found_md5_data:
aes_key, xor_key_dec = found_md5_data.split("|")
xor_key = f"0x{int(xor_key_dec):02X}"
return {
"db_key": found_db_key,
"aes_key": aes_key,
"xor_key": xor_key
}
def get_db_key_workflow():
fetcher = WeChatKeyFetcher()
@@ -202,73 +224,11 @@ def get_db_key_workflow():
# ============================== 以下是图片密钥逻辑 =====================================
# 远程 API 配置
REMOTE_URL = "https://view.free.c3o.re/dashboard"
BASE_URL = "https://view.free.c3o.re" # 用于拼接js
# NEXT_ACTION_ID = "7c8f99280c70626ccf5960cc4a68f368197e15f8e9" # 不可以硬编码
async def fetch_js_and_scan(client: httpx.AsyncClient, js_path: str) -> Optional[str]:
"""
异步下载单个 JS 文件并匹配 Action ID
"""
full_url = f"{BASE_URL}{js_path}" if js_path.startswith("/") else js_path
try:
response = await client.get(full_url)
if response.status_code != 200:
return None
content = response.text
action_id_pattern = re.compile(r'createServerReference.*?["\']([a-f0-9]{42})["\'].*?["\']getUserConfigFromBytes["\']')
match = action_id_pattern.search(content)
if match:
found_id = match.group(1)
return found_id
except Exception as e:
logger.error(f"Error fetching {js_path}: {e}")
return None
async def _get_next_action_id_async() -> str:
async with httpx.AsyncClient(timeout=10.0) as client:
resp = await client.get(REMOTE_URL)
html = resp.text
js_file_pattern = re.compile(r'src="(/_next/static/chunks/[^"]+\.js)"')
js_files = set(js_file_pattern.findall(html))
if not js_files:
raise Exception("未找到任何 Next.js chunk 文件,可能页面结构已变动。")
tasks = [fetch_js_and_scan(client, js_path) for js_path in js_files]
results = await asyncio.gather(*tasks)
for res in results:
if res:
return res
raise Exception("遍历了所有 JS 文件,但未找到匹配的 createServerReference ID。")
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},请确认微信数据目录结构是否完整")
raise FileNotFoundError(f"找不到配置文件: {target_path},请确认微信数据目录结构是否完整")
return Path(target_path).read_bytes()
@@ -278,90 +238,36 @@ async def fetch_and_save_remote_keys(account: Optional[str] = None) -> Dict[str,
wx_id_dir = _resolve_account_wxid_dir(account_dir)
wxid = wx_id_dir.name
logger.info("尝试获取next_action_id")
try:
next_action_id = await _get_next_action_id_async()
logger.info(f"获取next_action_id成功: {next_action_id}")
except Exception as e:
raise RuntimeError(f"获取next_action_id失败:{e}")
url = "https://view.free.c3o.re/api/key"
data = {"weixinIDFolder": wxid}
logger.info(f"正在为账号 {wxid} 获取密钥...")
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")
blob1_bytes = get_wechat_internal_global_config(wx_id_dir, file_name1="global_config")
blob2_bytes = get_wechat_internal_global_config(wx_id_dir, file_name1="global_config.crc")
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')),
'fileBytes': ('file', blob1_bytes, 'application/octet-stream'),
'crcBytes': ('file.crc', blob2_bytes, 'application/octet-stream'),
}
async with httpx.AsyncClient(timeout=30) as client:
logger.info("远程服务器发送请求...")
response = await client.post(REMOTE_URL, headers=headers, files=files)
logger.info("云端 API 发送请求...")
response = await client.post(url, data=data, files=files)
if response.status_code != 200:
raise RuntimeError(f"远程服务器错误: {response.status_code} - {response.text[:100]}")
raise RuntimeError(f"云端服务器错误: {response.status_code} - {response.text[:100]}")
config = response.json()
if not config:
raise RuntimeError("云端解析失败: 返回数据为空")
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"])
# 新 API 的字段兼容处理
xor_raw = str(config.get("xorKey", config.get("xor_key", "")))
aes_val = str(config.get("aesKey", config.get("aes_key", "")))
try:
if xor_raw.startswith("0x"):
@@ -382,6 +288,5 @@ async def fetch_and_save_remote_keys(account: Optional[str] = None) -> Dict[str,
"wxid": wxid,
"xor_key": xor_hex_str,
"aes_key": aes_val,
"nick_name": result_data["nick_name"]
}
"nick_name": config.get("nickName", config.get("nick_name", ""))
}
+5 -6
View File
@@ -4114,8 +4114,7 @@ def _collect_chat_messages(
render_type = "system"
template = _extract_xml_tag_text(raw_text, "template")
if template:
import re
# import re
pat_usernames.update({m.group(1) for m in re.finditer(r"\$\{([^}]+)\}", template) if m.group(1)})
content_text = "[拍一拍]"
else:
@@ -4255,8 +4254,7 @@ def _collect_chat_messages(
elif local_type == 50:
render_type = "voip"
try:
import re
# import re
block = raw_text
m_voip = re.search(
r"(<VoIPBubbleMsg[^>]*>.*?</VoIPBubbleMsg>)",
@@ -5013,7 +5011,7 @@ def list_chat_messages(
render_type = "system"
template = _extract_xml_tag_text(raw_text, "template")
if template:
import re
# import re
pat_usernames.update({m.group(1) for m in re.finditer(r"\$\{([^}]+)\}", template) if m.group(1)})
content_text = "[拍一拍]"
@@ -5145,7 +5143,7 @@ def list_chat_messages(
elif local_type == 50:
render_type = "voip"
try:
import re
# import re
block = raw_text
m_voip = re.search(
@@ -5494,6 +5492,7 @@ def list_chat_messages(
if existing_local:
try:
# import re
cur = str(m.get("emojiUrl") or "")
if cur and re.match(r"^https?://", cur, flags=re.I) and ("/api/chat/media/emoji" not in cur):
m["emojiRemoteUrl"] = cur
+7 -9
View File
@@ -53,31 +53,28 @@ async def get_saved_keys(account: Optional[str] = None):
}
@router.get("/api/get_db_key", summary="自动获取微信数据库密钥")
@router.get("/api/get_keys", summary="自动获取微信数据库与图片密钥")
async def get_wechat_db_key():
"""
自动流程:
1. 结束微信进程
2. 启动微信
3. 根据版本注入 Hook
4. 抓取密钥并返回
3. 根据版本注入 Hook
4. 抓取 DB 与 图片密钥(AES + XOR)并返回
"""
try:
# 不需要async吧,我相信fastapi的线程池
db_key = get_db_key_workflow()
keys_data = get_db_key_workflow()
return {
"status": 0,
"errmsg": "ok",
"data": {
"db_key": db_key
}
"data": keys_data # 现在完美包含了 db_key, aes_key, xor_key
}
except TimeoutError:
return {
"status": -1,
"errmsg": "获取超时,请确保微信没有开启自动登录 或者 加快手速",
"errmsg": "获取超时,请确保微信没有开启自动登录并且在弹窗中完成了登录",
"data": {}
}
except Exception as e:
@@ -88,6 +85,7 @@ async def get_wechat_db_key():
}
@router.get("/api/get_image_key", summary="获取并保存微信图片密钥")
async def get_image_key(account: Optional[str] = None):
"""
Generated
+6 -6
View File
@@ -919,7 +919,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 = "wx-key", specifier = ">=1.1.0" },
{ name = "zstandard", specifier = ">=0.23.0" },
]
provides-extras = ["build"]
@@ -935,13 +935,13 @@ wheels = [
[[package]]
name = "wx-key"
version = "1.0.0"
version = "1.1.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" },
{ path = "wx_key-1.1.0-cp311-cp311-win_amd64.whl" },
{ path = "wx_key-1.1.0-cp312-cp312-win_amd64.whl" },
{ path = "wx_key-1.1.0-cp313-cp313-win_amd64.whl" },
{ path = "wx_key-1.1.0-cp314-cp314-win_amd64.whl" },
]
[[package]]