Skip to content

Commit 08b6562

Browse files
authored
Merge pull request #24 from H3CoF6/feat/wx-key
feat(wx-key):support get db key and img key without other tools.
2 parents a38d99a + 1772998 commit 08b6562

15 files changed

Lines changed: 708 additions & 22 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ wheels/
1717
# Local config templates
1818
/wechat_db_config_template.json
1919
.ace-tool/
20+
pnpm-lock.yaml
2021

2122
# Local dev repos and data
2223
/WxDatDecrypt/

frontend/composables/useApi.js

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,22 @@ export const useApi = () => {
375375
const url = `/wrapped/annual/cards/${safeId}` + (query.toString() ? `?${query.toString()}` : '')
376376
return await request(url)
377377
}
378-
378+
379+
// 获取微信进程状态
380+
const getWxStatus = async () => {
381+
return await request('/wechat/status')
382+
}
383+
384+
// 获取数据库密钥
385+
const getDbKey = async () => {
386+
return await request('/get_db_key')
387+
}
388+
389+
// 获取图片密钥
390+
const getImageKey = async () => {
391+
return await request('/get_image_key')
392+
}
393+
379394
return {
380395
detectWechat,
381396
detectCurrentAccount,
@@ -408,6 +423,9 @@ export const useApi = () => {
408423
exportChatContacts,
409424
getWrappedAnnual,
410425
getWrappedAnnualMeta,
411-
getWrappedAnnualCard
426+
getWrappedAnnualCard,
427+
getDbKey,
428+
getImageKey,
429+
getWxStatus,
412430
}
413431
}

frontend/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,8 @@
1919
"ogl": "^1.0.11",
2020
"vue": "^3.5.17",
2121
"vue-router": "^4.5.1"
22+
},
23+
"devDependencies": {
24+
"tailwindcss": "3.4.17"
2225
}
2326
}

frontend/pages/decrypt.vue

Lines changed: 152 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -26,24 +26,40 @@
2626
<!-- 密钥输入 -->
2727
<div>
2828
<label for="key" class="block text-sm font-medium text-[#000000e6] mb-2">
29-
<svg class="w-4 h-4 inline mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
30-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z"/>
31-
</svg>
3229
解密密钥 <span class="text-red-500">*</span>
3330
</label>
34-
<div class="relative">
35-
<input
36-
id="key"
37-
v-model="formData.key"
38-
type="text"
39-
placeholder="请输入64位十六进制密钥"
40-
class="w-full px-4 py-3 bg-white border border-[#EDEDED] rounded-lg font-mono text-sm focus:outline-none focus:ring-2 focus:ring-[#07C160] focus:border-transparent transition-all duration-200"
41-
:class="{ 'border-red-500': formErrors.key }"
42-
required
43-
/>
44-
<div v-if="formData.key" class="absolute right-3 top-1/2 transform -translate-y-1/2">
45-
<span class="text-xs text-[#7F7F7F]">{{ formData.key.length }}/64</span>
31+
32+
<div class="flex gap-3">
33+
<div class="relative flex-1">
34+
<input
35+
id="key"
36+
v-model="formData.key"
37+
type="text"
38+
placeholder="请输入64位十六进制密钥"
39+
class="w-full px-4 py-3 bg-white border border-[#EDEDED] rounded-lg font-mono text-sm focus:outline-none focus:ring-2 focus:ring-[#07C160] focus:border-transparent transition-all duration-200"
40+
:class="{ 'border-red-500': formErrors.key }"
41+
required
42+
/>
43+
<div v-if="formData.key" class="absolute right-3 top-1/2 transform -translate-y-1/2">
44+
<span class="text-xs text-[#7F7F7F]">{{ formData.key.length }}/64</span>
45+
</div>
4646
</div>
47+
48+
<button
49+
type="button"
50+
@click="handleGetDbKey"
51+
:disabled="isGettingDbKey"
52+
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"
53+
>
54+
<svg v-if="isGettingDbKey" class="animate-spin -ml-1 mr-2 h-4 w-4 text-white" fill="none" viewBox="0 0 24 24">
55+
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
56+
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path>
57+
</svg>
58+
<svg v-else class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
59+
<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" />
60+
</svg>
61+
{{ isGettingDbKey ? '获取中...' : '自动获取' }}
62+
</button>
4763
</div>
4864
<p v-if="formErrors.key" class="mt-1 text-sm text-red-600 flex items-center">
4965
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -55,7 +71,7 @@
5571
<svg class="w-4 h-4 mr-1 text-[#10AEEF]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
5672
<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"/>
5773
</svg>
58-
使用 <a href="https://github.com/ycccccccy/wx_key" target="_blank" class="text-[#07C160] hover:text-[#06AD56]">wx_key</a> 等工具获取的64位十六进制字符串
74+
尝试自动获取,或者使用 <a href="https://github.com/ycccccccy/wx_key" target="_blank" class="text-[#07C160] hover:text-[#06AD56]">wx_key</a> 等工具获取的64位十六进制字符串
5975
</p>
6076
</div>
6177

@@ -131,6 +147,26 @@
131147
<!-- 填写密钥 -->
132148
<div class="mb-6">
133149
<div class="bg-gray-50 rounded-lg p-4">
150+
151+
<div class="flex justify-between items-center mb-4 pb-3 border-b border-gray-200">
152+
<span class="text-sm font-medium text-gray-500">手动输入或通过微信获取</span>
153+
<button
154+
type="button"
155+
@click="handleGetImageKey"
156+
:disabled="isGettingImageKey"
157+
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"
158+
>
159+
<svg v-if="isGettingImageKey" class="animate-spin -ml-1 mr-2 h-4 w-4 text-white" fill="none" viewBox="0 0 24 24">
160+
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
161+
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path>
162+
</svg>
163+
<svg v-else class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
164+
<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" />
165+
</svg>
166+
{{ isGettingImageKey ? '正在获取...' : '自动获取' }}
167+
</button>
168+
</div>
169+
134170
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
135171
<div>
136172
<label class="block text-sm font-medium text-[#000000e6] mb-2">XOR(必填)</label>
@@ -158,7 +194,7 @@
158194
<svg class="w-4 h-4 mr-1 text-[#10AEEF]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
159195
<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"/>
160196
</svg>
161-
使用 <a href="https://github.com/ycccccccy/wx_key" target="_blank" class="text-[#07C160] hover:text-[#06AD56]">wx_key</a> 获取图片密钥;AES 可选(V4-V2 需要)
197+
尝试自动获取,或使用 <a href="https://github.com/ycccccccy/wx_key" target="_blank" class="text-[#07C160] hover:text-[#06AD56]">wx_key</a> 获取图片密钥;AES 可选(V4-V2 需要)
162198
</p>
163199
</div>
164200
</div>
@@ -325,6 +361,19 @@
325361
</div>
326362
</div>
327363
</div>
364+
365+
<!-- 警告渲染 -->
366+
<transition name="fade">
367+
<div v-if="warning" class="bg-amber-50 border border-amber-200 rounded-lg p-4 mt-6 flex items-start">
368+
<svg class="h-5 w-5 mr-2 flex-shrink-0 text-amber-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
369+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
370+
</svg>
371+
<div>
372+
<p class="font-semibold text-amber-800">温馨提示</p>
373+
<p class="text-sm mt-1 text-amber-700">{{ warning }}</p>
374+
</div>
375+
</div>
376+
</transition>
328377

329378
<!-- 错误提示 -->
330379
<transition name="fade">
@@ -367,12 +416,15 @@
367416
import { ref, reactive, computed, onMounted } from 'vue'
368417
import { useApi } from '~/composables/useApi'
369418
370-
const { decryptDatabase, saveMediaKeys, getSavedKeys } = useApi()
419+
const { decryptDatabase, saveMediaKeys, getSavedKeys, getDbKey, getImageKey, getWxStatus } = useApi()
371420
372421
const loading = ref(false)
373422
const error = ref('')
423+
const warning = ref('') // 警告,用于密钥提示
374424
const currentStep = ref(0)
375425
const mediaAccount = ref('')
426+
const isGettingDbKey = ref(false)
427+
const isGettingImageKey = ref(false)
376428
377429
// 步骤定义
378430
const steps = [
@@ -453,10 +505,89 @@ const prefillKeysForAccount = async (account) => {
453505
}
454506
}
455507
508+
const handleGetDbKey = async () => {
509+
if (isGettingDbKey.value) return
510+
isGettingDbKey.value = true
511+
512+
error.value = ''
513+
warning.value = ''
514+
formErrors.key = ''
515+
516+
try {
517+
const statusRes = await getWxStatus() // pid不是主进程,但是没关系
518+
const wxStatus = statusRes?.wx_status
519+
520+
if (wxStatus?.is_running) {
521+
warning.value = '检测到微信正在运行,5秒后将终止进程并重启以获取密钥!!'
522+
await new Promise(resolve => setTimeout(resolve, 5000))
523+
} else {
524+
// 没有逻辑
525+
}
526+
527+
warning.value = '正在启动微信以获取密钥,请确保微信未开启“自动登录”,并在启动后 1 分钟内完成登录操作。'
528+
529+
const res = await getDbKey()
530+
531+
if (res && res.status === 0) {
532+
if (res.data?.db_key) {
533+
formData.key = res.data.db_key
534+
warning.value = ''
535+
}
536+
537+
if (res.errmsg && res.errmsg !== 'ok') {
538+
warning.value = res.errmsg
539+
}
540+
} else {
541+
error.value = '获取失败: ' + (res?.errmsg || '未知错误')
542+
}
543+
} catch (e) {
544+
console.error(e)
545+
error.value = '系统错误: ' + e.message
546+
} finally {
547+
isGettingDbKey.value = false
548+
}
549+
}
550+
551+
const handleGetImageKey = async () => {
552+
if (isGettingImageKey.value) return
553+
isGettingImageKey.value = true
554+
manualKeyErrors.xor_key = ''
555+
manualKeyErrors.aes_key = ''
556+
557+
error.value = ''
558+
warning.value = ''
559+
560+
try {
561+
const res = await getImageKey()
562+
563+
if (res && res.status === 0) {
564+
if (res.data?.aes_key) {
565+
manualKeys.aes_key = res.data.aes_key
566+
}
567+
if (res.data?.xor_key) {
568+
// 后端记得处理为16进制再返回!!!
569+
manualKeys.xor_key = res.data.xor_key
570+
}
571+
572+
if (res.errmsg && res.errmsg !== 'ok') {
573+
error.value = res.errmsg
574+
}
575+
} else {
576+
error.value = '获取失败: ' + (res?.errmsg || '未知错误')
577+
}
578+
} catch (e) {
579+
console.error(e)
580+
error.value = '系统错误: ' + e.message
581+
} finally {
582+
isGettingImageKey.value = false
583+
}
584+
}
585+
456586
const applyManualKeys = () => {
457587
manualKeyErrors.xor_key = ''
458588
manualKeyErrors.aes_key = ''
459589
error.value = ''
590+
warning.value = ''
460591
461592
const aes = normalizeAesKey(manualKeys.aes_key)
462593
if (!aes.ok) {
@@ -550,6 +681,7 @@ const handleDecrypt = async () => {
550681
551682
loading.value = true
552683
error.value = ''
684+
warning.value = ''
553685
554686
try {
555687
const result = await decryptDatabase({
@@ -596,6 +728,7 @@ const decryptAllImages = async () => {
596728
mediaDecrypting.value = true
597729
mediaDecryptResult.value = null
598730
error.value = ''
731+
warning.value = ''
599732
600733
// 重置进度
601734
decryptProgress.current = 0
@@ -671,6 +804,7 @@ const decryptAllImages = async () => {
671804
// 从密钥步骤进入图片解密步骤
672805
const goToMediaDecryptStep = async () => {
673806
error.value = ''
807+
warning.value = ''
674808
// 校验并应用(未填写则允许直接进入,后端会使用已保存密钥或报错提示)
675809
const ok = applyManualKeys()
676810
if (!ok || manualKeyErrors.xor_key || manualKeyErrors.aes_key) return

pyproject.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ dependencies = [
1919
"zstandard>=0.23.0",
2020
"pilk>=0.2.4",
2121
"pypinyin>=0.53.0",
22+
"wx_key",
23+
"packaging",
24+
"httpx",
2225
]
2326

2427
[project.optional-dependencies]
@@ -40,3 +43,6 @@ include = [
4043
"src/wechat_decrypt_tool/native/wcdb_api.dll",
4144
"src/wechat_decrypt_tool/native/WCDB.dll",
4245
]
46+
47+
[tool.uv]
48+
find-links = ["./tools/key_wheels/"]

0 commit comments

Comments
 (0)