diff --git a/README.md b/README.md index 9eacdf0..e218ff4 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@

WeChatDataAnalysis - 微信数据库解密与分析工具

一个专门用于微信4.x版本数据库解密的工具(支持聊天记录实时更新)

-

特别致谢echotrace(本项目大量功能参考其实现,提供了重要技术支持)

+

特别致谢echotraceWeFlow(本项目大量功能参考其实现,提供了重要技术支持)

Version Stars Downloads @@ -66,7 +66,7 @@ ## 年度总结 -年度总结现在支持 4 种不同风格(style1-4)。如果你对某个风格有更好的修改建议,或有新风格的点子,欢迎到 Issue 区反馈:https://github.com/LifeArchiveProject/WeChatDataAnalysis/issues +年度总结现在支持 3 种不同风格(style1、style2、style3)。如果你对某个风格有更好的修改建议,或有新风格的点子,欢迎到 Issue 区反馈:https://github.com/LifeArchiveProject/WeChatDataAnalysis/issues > ⚠️ **提醒**:年度总结目前还不是最终版本,后续还会增加新总结或新风格。 @@ -82,12 +82,10 @@ 年度总结 Style 2 - Style 3 - Style 4 + Style 3 - 年度总结 Style 3 - 年度总结 Style 4 + 年度总结 Style 3 @@ -196,19 +194,22 @@ npm run dist 1. **[echotrace](https://github.com/ycccccccy/echotrace)** - 微信数据解析/取证工具 - 本项目大量功能参考并复用其实现思路,提供了重要技术支持 -2. **[wx_key](https://github.com/ycccccccy/wx_key)** - 微信数据库与图片密钥提取工具 +2. **[WeFlow](https://github.com/hicccc77/WeFlow)** - 微信数据分析工具 + - 提供了重要的功能参考和技术支持 + +3. **[wx_key](https://github.com/ycccccccy/wx_key)** - 微信数据库与图片密钥提取工具 - 支持获取微信 4.x 数据库密钥与缓存图片密钥 - 本项目推荐使用此工具获取密钥 -3. **[wechat-dump-rs](https://github.com/0xlane/wechat-dump-rs)** - Rust实现的微信数据库解密工具 +4. **[wechat-dump-rs](https://github.com/0xlane/wechat-dump-rs)** - Rust实现的微信数据库解密工具 - 提供了SQLCipher 4.0解密的正确实现参考 - 本项目的HMAC验证和页面处理逻辑基于此项目的实现 -4. **[oh-my-wechat](https://github.com/chclt/oh-my-wechat)** - 微信聊天记录查看工具 +5. **[oh-my-wechat](https://github.com/chclt/oh-my-wechat)** - 微信聊天记录查看工具 - 提供了优秀的聊天记录界面设计参考 - 本项目的聊天界面风格参考了此项目的实现 -5. **[vue3-wechat-tool](https://github.com/Ele-Cat/vue3-wechat-tool)** - 微信聊天记录工具(Vue3) +6. **[vue3-wechat-tool](https://github.com/Ele-Cat/vue3-wechat-tool)** - 微信聊天记录工具(Vue3) - 提供了聊天记录展示与交互的实现参考 ## Star History @@ -222,3 +223,4 @@ npm run dist --- **免责声明**: 本工具仅供学习研究使用,使用者需自行承担使用风险。开发者不对因使用本工具造成的任何损失负责。 + diff --git a/desktop/src/main.cjs b/desktop/src/main.cjs index a2321bc..25524c6 100644 --- a/desktop/src/main.cjs +++ b/desktop/src/main.cjs @@ -611,6 +611,25 @@ function registerWindowIpc() { return getCloseBehavior(); } }); + + ipcMain.handle("dialog:chooseDirectory", async (_event, options) => { + try { + const result = await dialog.showOpenDialog({ + title: String(options?.title || "选择文件夹"), + properties: ["openDirectory", "createDirectory"], + }); + return { + canceled: !!result?.canceled, + filePaths: Array.isArray(result?.filePaths) ? result.filePaths : [], + }; + } catch (err) { + logMain(`[main] dialog:chooseDirectory failed: ${err?.message || err}`); + return { + canceled: true, + filePaths: [], + }; + } + }); } async function main() { diff --git a/desktop/src/preload.cjs b/desktop/src/preload.cjs index 1f1eea2..f40b33d 100644 --- a/desktop/src/preload.cjs +++ b/desktop/src/preload.cjs @@ -11,4 +11,6 @@ contextBridge.exposeInMainWorld("wechatDesktop", { getCloseBehavior: () => ipcRenderer.invoke("app:getCloseBehavior"), setCloseBehavior: (behavior) => ipcRenderer.invoke("app:setCloseBehavior", String(behavior || "")), + + chooseDirectory: (options = {}) => ipcRenderer.invoke("dialog:chooseDirectory", options), }); diff --git a/frontend/app.vue b/frontend/app.vue index cb55b98..f1b0426 100644 --- a/frontend/app.vue +++ b/frontend/app.vue @@ -30,7 +30,7 @@ onBeforeUnmount(() => { }) const route = useRoute() -const isChatRoute = computed(() => route.path?.startsWith('/chat') || route.path?.startsWith('/sns')) +const isChatRoute = computed(() => route.path?.startsWith('/chat') || route.path?.startsWith('/sns') || route.path?.startsWith('/contacts')) const rootClass = computed(() => { const base = 'bg-gradient-to-br from-green-50 via-emerald-50 to-green-100' diff --git a/frontend/assets/css/tailwind.css b/frontend/assets/css/tailwind.css index 96d1bc2..3bce7b4 100644 --- a/frontend/assets/css/tailwind.css +++ b/frontend/assets/css/tailwind.css @@ -730,35 +730,39 @@ } .header-btn { - @apply flex items-center gap-1.5 text-xs px-3 py-1.5 rounded-lg bg-white border border-gray-200 text-gray-700 transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed; + @apply flex items-center gap-1.5 text-xs px-3 py-1.5 rounded-md bg-white border border-gray-200 text-gray-700 transition-all duration-150 disabled:opacity-50 disabled:cursor-not-allowed shadow-sm; } .header-btn:hover:not(:disabled) { - @apply bg-gray-50 border-gray-300; + @apply bg-gray-50 border-gray-300 shadow; } .header-btn:active:not(:disabled) { - @apply bg-gray-100; + @apply bg-gray-100 scale-95; + } + + .header-btn svg { + @apply w-3.5 h-3.5; } .header-btn-icon { - @apply w-8 h-8 flex items-center justify-center rounded-lg bg-white border border-gray-200 text-gray-600 transition-all duration-200; + @apply w-8 h-8 flex items-center justify-center rounded-lg bg-transparent border border-transparent text-gray-600 transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed; } .header-btn-icon:hover { - @apply bg-gray-50 border-gray-300 text-gray-800; + @apply bg-transparent border-transparent text-gray-800; } .header-btn-icon-active { - @apply bg-[#03C160]/10 border-[#03C160] text-[#03C160]; + @apply bg-transparent border-transparent text-[#03C160]; } .header-btn-icon-active:hover { - @apply bg-[#03C160]/15; + @apply bg-transparent; } .message-filter-select { - @apply text-xs px-2 py-1.5 rounded-lg bg-white border border-gray-200 text-gray-700 focus:outline-none focus:ring-2 focus:ring-[#03C160]/20 focus:border-[#03C160] transition-all disabled:opacity-50 disabled:cursor-not-allowed; + @apply text-xs px-2 py-1.5 rounded-lg bg-transparent border-0 text-gray-700 focus:outline-none focus:ring-0 transition-all disabled:opacity-50 disabled:cursor-not-allowed; } /* 搜索侧边栏样式 */ diff --git a/frontend/composables/useApi.js b/frontend/composables/useApi.js index 15c88cf..c1ce542 100644 --- a/frontend/composables/useApi.js +++ b/frontend/composables/useApi.js @@ -292,6 +292,7 @@ export const useApi = () => { message_types: Array.isArray(data.message_types) ? data.message_types : [], include_media: data.include_media == null ? true : !!data.include_media, media_kinds: Array.isArray(data.media_kinds) ? data.media_kinds : ['image', 'emoji', 'video', 'video_thumb', 'voice', 'file'], + output_dir: data.output_dir == null ? null : String(data.output_dir || '').trim(), allow_process_key_extract: !!data.allow_process_key_extract, privacy_mode: !!data.privacy_mode, file_name: data.file_name || null @@ -313,6 +314,36 @@ export const useApi = () => { return await request(`/chat/exports/${encodeURIComponent(String(exportId))}`, { method: 'DELETE' }) } + // 联系人 + const listChatContacts = async (params = {}) => { + const query = new URLSearchParams() + if (params && params.account) query.set('account', params.account) + if (params && params.keyword) query.set('keyword', params.keyword) + if (params && params.include_friends != null) query.set('include_friends', String(!!params.include_friends)) + if (params && params.include_groups != null) query.set('include_groups', String(!!params.include_groups)) + if (params && params.include_officials != null) query.set('include_officials', String(!!params.include_officials)) + const url = '/chat/contacts' + (query.toString() ? `?${query.toString()}` : '') + return await request(url) + } + + const exportChatContacts = async (payload = {}) => { + return await request('/chat/contacts/export', { + method: 'POST', + body: { + account: payload.account || null, + output_dir: payload.output_dir || '', + format: payload.format || 'json', + include_avatar_link: payload.include_avatar_link == null ? true : !!payload.include_avatar_link, + keyword: payload.keyword || null, + contact_types: { + friends: payload?.contact_types?.friends == null ? true : !!payload.contact_types.friends, + groups: payload?.contact_types?.groups == null ? true : !!payload.contact_types.groups, + officials: payload?.contact_types?.officials == null ? true : !!payload.contact_types.officials, + } + } + }) + } + // WeChat Wrapped(年度总结) const getWrappedAnnual = async (params = {}) => { const query = new URLSearchParams() @@ -388,6 +419,8 @@ export const useApi = () => { getChatExport, listChatExports, cancelChatExport, + listChatContacts, + exportChatContacts, getWrappedAnnual, getWrappedAnnualMeta, getWrappedAnnualCard, diff --git a/frontend/pages/chat/[[username]].vue b/frontend/pages/chat/[[username]].vue index 3bda422..03bf0de 100644 --- a/frontend/pages/chat/[[username]].vue +++ b/frontend/pages/chat/[[username]].vue @@ -60,6 +60,26 @@
+ +
+
+
+ +
+
+
+ + + +
+
+ +
+
-
+
@@ -199,7 +237,7 @@