├── .github └── workflows │ └── publish.yml ├── .gitignore ├── LICENSE ├── README.md ├── app.go ├── build ├── README.md ├── appicon.png ├── darwin │ ├── Info.dev.plist │ └── Info.plist └── windows │ ├── icon.ico │ ├── info.json │ ├── installer │ ├── project.nsi │ └── wails_tools.nsh │ └── wails.exe.manifest ├── changelog.md ├── frontend └── dist │ ├── assets │ ├── 001_微笑.1ec7a344.png │ ├── 002_撇嘴.0279218b.png │ ├── 003_色.e92bb91a.png │ ├── 004_发呆.8d849292.png │ ├── 005_得意.72060784.png │ ├── 006_流泪.f52e5f23.png │ ├── 007_害羞.414541cc.png │ ├── 008_闭嘴.4b6c78a6.png │ ├── 009_睡.75e64219.png │ ├── 010_大哭.2cd2fee3.png │ ├── 011_尴尬.70cfea1c.png │ ├── 012_发怒.b88ce021.png │ ├── 013_调皮.f3363541.png │ ├── 014_呲牙.3cd1fb7c.png │ ├── 015_惊讶.c9eb5e15.png │ ├── 016_难过.5d872489.png │ ├── 017_囧.59ee6551.png │ ├── 018_抓狂.d1646df8.png │ ├── 019_吐.51bb226f.png │ ├── 020_偷笑.59941b0b.png │ ├── 021_愉快.47582f99.png │ ├── 022_白眼.ca492234.png │ ├── 023_傲慢.651b4c79.png │ ├── 024_困.4556c7db.png │ ├── 025_惊恐.ed5cfeab.png │ ├── 026_憨笑.6d317a05.png │ ├── 027_悠闲.cef28253.png │ ├── 028_咒骂.a26d48fa.png │ ├── 029_疑问.aaa09269.png │ ├── 030_嘘.40e8213d.png │ ├── 031_晕.44e3541a.png │ ├── 032_衰.1a3910a6.png │ ├── 033_骷髅.3c9202dc.png │ ├── 034_敲打.b2798ca7.png │ ├── 035_再见.db23652c.png │ ├── 036_擦汗.b46fa893.png │ ├── 037_抠鼻.64bc8033.png │ ├── 038_鼓掌.2a84e4c7.png │ ├── 039_坏笑.4998b91f.png │ ├── 040_右哼哼.27d8126d.png │ ├── 041_鄙视.7e22890d.png │ ├── 042_委屈.a5caf83a.png │ ├── 043_快哭了.62b1b67c.png │ ├── 044_阴险.3697222b.png │ ├── 045_亲亲.dfa6bbdf.png │ ├── 046_可怜.634845ad.png │ ├── 047_笑脸.ab25a28c.png │ ├── 048_生病.cd7aadb3.png │ ├── 049_脸红.9ecb5c1c.png │ ├── 050_破涕为笑.a3d2342d.png │ ├── 051_恐惧.7af18313.png │ ├── 052_失望.87e0479b.png │ ├── 053_无语.6220ee7c.png │ ├── 054_嘿哈.2116e692.png │ ├── 055_捂脸.28f3a0d3.png │ ├── 056_奸笑.9cf99423.png │ ├── 057_机智.93c3d05a.png │ ├── 058_皱眉.efe09ed7.png │ ├── 059_耶.a6bc3d2b.png │ ├── 060_吃瓜.a2a158de.png │ ├── 061_加油.77c81f5b.png │ ├── 062_汗.be95535c.png │ ├── 063_天啊.a8355bf9.png │ ├── 064_Emm.787be530.png │ ├── 065_社会社会.a5f5902a.png │ ├── 066_旺柴.7825a175.png │ ├── 067_好的.a9fffc64.png │ ├── 068_打脸.560c8d1f.png │ ├── 069_哇.74cdcc27.png │ ├── 070_翻白眼.ecb744e2.png │ ├── 071_666.281fb9b6.png │ ├── 072_让我看看.cee96a9f.png │ ├── 073_叹气.712846f3.png │ ├── 074_苦涩.4edf4f58.png │ ├── 075_裂开.3fb97804.png │ ├── 076_嘴唇.59b9c0be.png │ ├── 077_爱心.a09c823b.png │ ├── 078_心碎.9a4fb37d.png │ ├── 079_拥抱.e529a46b.png │ ├── 080_强.64fe98a8.png │ ├── 081_弱.07ddf3a5.png │ ├── 082_握手.aeb86265.png │ ├── 083_胜利.e9ff0663.png │ ├── 084_抱拳.0ae5f316.png │ ├── 085_勾引.a4c3a7b4.png │ ├── 086_拳头.2829fdbe.png │ ├── 087_OK.fc42db3d.png │ ├── 088_合十.58cd6a1d.png │ ├── 089_啤酒.2d022508.png │ ├── 090_咖啡.8f40dc95.png │ ├── 091_蛋糕.f01a91ed.png │ ├── 093_凋谢.aa715ee6.png │ ├── 095_炸弹.3dffd8e8.png │ ├── 096_便便.b0d5c50c.png │ ├── 097_月亮.47389834.png │ ├── 098_太阳.89c3d0ab.png │ ├── 099_庆祝.2d9e8f8a.png │ ├── 100_礼物.37ae5ec0.png │ ├── 102_發.f43fee5c.png │ ├── 103_福.58c94555.png │ ├── 104_烟花.61568e1e.png │ ├── 105_爆竹.35531687.png │ ├── 106_猪头.7eb8ff1d.png │ ├── 107_跳跳.24101efa.png │ ├── 108_发抖.3eabd306.png │ ├── 109_转圈.67669ca4.png │ ├── 110_怄火.896c41d9.png │ ├── 112_左哼哼.5425438e.png │ ├── 113_哈欠.fe9c8521.png │ ├── applet.ce6471b1.png │ ├── channels.33204285.png │ ├── channels_error.1d149df5.png │ ├── emoji.b5d5ea11.png │ ├── errorImg.719abbab.png │ ├── favorite.1b38cfe5.png │ ├── index.00f6955e.css │ ├── index.8be36b27.js │ ├── logo.8df6944e.png │ ├── map.b91d2cda.png │ ├── music_note.02e237d9.png │ ├── qq_music.b548e6a1.png │ ├── red_packet.704bd303.png │ ├── share.3d7abf39.png │ └── 思源黑体-Normal.df5ff3ec.otf │ └── index.html ├── go.mod ├── main.go ├── pkg ├── utils │ └── utils.go └── wechat │ ├── msg.pb.go │ ├── wechat.go │ ├── wechatDBDec.go │ ├── wechatDataProvider.go │ └── wechatIMGDec.go ├── res ├── logo.png ├── logo_128.png ├── logo_256.png ├── result.png ├── result2.png ├── tips.png └── wechatQR.png └── wails.json /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Wails build 2 | 3 | on: 4 | push: 5 | tags: 6 | # Match any new tag 7 | - '*' 8 | 9 | env: 10 | # Necessary for most environments as build failure can occur due to OOM issues 11 | NODE_OPTIONS: "--max-old-space-size=4096" 12 | 13 | jobs: 14 | build: 15 | strategy: 16 | # Failure in one platform build won't impact the others 17 | fail-fast: false 18 | matrix: 19 | build: 20 | - name: 'wechatDataBackup.exe' 21 | platform: 'windows/amd64' 22 | os: 'windows-latest' 23 | 24 | runs-on: ${{ matrix.build.os }} 25 | steps: 26 | - name: Checkout 27 | uses: actions/checkout@v2 28 | with: 29 | submodules: recursive 30 | 31 | - name: Build wails 32 | uses: dAppServer/wails-build-action@main 33 | id: build 34 | with: 35 | build-name: ${{ matrix.build.name }} 36 | sign: false 37 | build-platform: ${{ matrix.build.platform }} 38 | package: true 39 | go-version: '1.21' 40 | wails-version: "v2.9.1" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # After editing .gitignore to match the ignored files, you can do < git ls-files -ci --exclude-standard > 2 | # to see the files that are included in the exclude lists; you can then do 3 | # 4 | # @ Linux/MacOS: < git ls-files -ci --exclude-standard -z | xargs -0 git rm --cached > 5 | # @ Windows (PowerShell): < git ls-files -ci --exclude-standard | % { git rm --cached "$_" } > 6 | # @ Windows (cmd.exe): < for /F "tokens=*" %a in ('git ls-files -ci --exclude-standard') do @git rm --cached "%a" > 7 | # ...to re-init the ignore list 8 | 9 | # Wails directories 10 | build/bin 11 | frontend/wailsjs 12 | frontend/node_modules 13 | 14 | # Wails junk files 15 | .syso 16 | 17 | # Go files 18 | go.sum 19 | 20 | # IDEs 21 | .idea 22 | .vscode 23 | 24 | # System enviroment variables 25 | env 26 | */.DS_Store 27 | 28 | 29 | # runtime 30 | User 31 | config.json 32 | wechatDataBackup.exe 33 | app.log 34 | app-*.log -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 |
3 |

4 | 5 |

6 | wechatDataBackup: PC微信聊天记录数据导出工具 7 |
8 |
9 | 10 | GitHub Star 11 | 12 | 13 | downloads 14 | 15 | 16 | releases version 17 | 18 | 19 | last commit 20 | 21 | 22 | languages 23 | 24 | 25 | repo size 26 | 27 | 28 | license 29 | 30 |

31 | # wechatDataBackup 32 | 33 | * 基于wails开发 + React前端,实现PC端微信聊天记录一键导出功能。 34 | * 导出后数据可以做永久化保存,即使微信停止支持,聊天记录也可以随时查看。 35 | * 前端界面尽量与微信界面保持一致,减少使用成本。 36 | * 理论上支持所有Windows 32/64位微信版本。 37 | 38 | 效果图如下: 39 | 40 | ![](./res/result.png) 41 | ![](./res/result2.png) 42 | 43 | ## 演示视频 44 | [演示视频](https://www.bilibili.com/video/BV1bPH1eWEEy/?share_source=copy_web&vd_source=b5cfa9258a9ad9900a00e9c1ce3cb4b6) 45 | ## 使用方法 46 | 1. 下载release可执行文件直接打开 国内朋友也可以使用 [网盘下载](https://pan.quark.cn/s/fa157b13e762) 47 | 2. 下载源码自行编译可执行文件 [安装wails环境](https://wails.io/zh-Hans/docs/gettingstarted/installation) 48 | 49 | ```shell 50 | git clone https://github.com/git-jiadong/wechatDataBackup.git 51 | cd wechatDataBackup 52 | wails build 53 | ``` 54 | 55 | 编译成功后在可执行二进制文件路径`build\bin\wechatDataBackup.exe` 56 | 57 | 如果编译错误可能是没有gcc环境导致的,可以安装 [tdm-gcc](https://jmeubank.github.io/tdm-gcc/) 后在尝试。 58 | 59 | 3. 导出聊天记录 60 | 电脑登陆微信,然后打开`wechatDataBackup.exe`后按照如图提示导出 61 | ![](./res/tips.png) 62 | 63 | ## 功能 64 | 65 | 本项目目前的规划与实现进度: 66 | - [x] 支持图片消息 67 | - [x] 支持视频消息 68 | - [x] 支持链接消息 69 | - [x] 支持语音消息 70 | - [x] 支持文件消息 71 | - [x] 支持名片消息 72 | - [x] 支持定位消息 73 | - [x] 支持视频/语音通话消息 74 | - [x] 支持QQ音乐消息 75 | - [x] 支持第三方视频软件分享消息 76 | - [x] 支持分享表情集消息 77 | - [x] 支持小程序消息 78 | - [x] 支持视频号/直播消息 79 | - [x] 支持转账消息 80 | - [x] 支持腾讯游戏分享消息 81 | - [x] 支持原始表情显示 82 | - [x] 支持按类型检索 83 | - [x] 支持日期检索 84 | - [x] 支持按群成员检索 85 | - [x] 支持增量式导出 86 | - [x] 多开账号选择导出 87 | - [x] 多开账号数据切换 88 | - [x] 头像使用本地头像 89 | - [ ] 支持更多消息类型显示 90 | - [x] 图片查看器重绘 91 | - [x] 支持会话导出分享 92 | - [x] 支持自动定位到最后浏览位置 93 | - [x] 支持书签功能 94 | - [x] 支持单聊会话对话人位置调换功能 95 | - [ ] 实现表情预先下载(实现完全离线查看) 96 | - [ ] 聊天报告 97 | - [ ] AI本地模型应用 98 | - [ ] 导出数据本地加密 99 | - ... 100 | 如果遇到什么问题,或者有更好的建议与优化点欢迎给作者提 [ISSUE](https://github.com/git-jiadong/wechatDataBackup/issues) 101 | 102 | 103 | ### 常见问题 104 | **Q: 支持手机端的聊天记录备份吗?**
105 | A: 手机端可以使用聊天数据迁移功能,将手机的数据迁移到电脑后再将数据导出。 [微信迁移聊天记录功能](https://www.bilibili.com/opus/974795819172495381)
106 | **Q: 导出后界面是空白的、导出的数据比PC微信里面看到的少,数据不完整**
107 | A: 这是由于可能数据存在于内存中还没有回写到磁盘导致的,退出微信时会将内存的数据全部回写到磁盘,导出数据时最好退出重新登陆一次微信,保证数据都在磁盘中再导出即可。
108 | **Q: 有些图片、视频打不开**
109 | A: 这是电脑端微信没有点开过这个消息,默认只加载了预览图而已,如果手机有打开过可以把手机的记录迁移到电脑,迁移后重新退出登陆一次微信导出即可。
110 | **Q: Win7电脑不能使用**
111 | A: Win7电脑需要安装WebView2运行时才能正常使用。github release版本做了Windows版本限制,[Win7用户请安装专属的版本](https://pan.quark.cn/s/fa157b13e762) 112 | ## Star History 113 | 114 | [![Star History Chart](https://api.star-history.com/svg?repos=git-jiadong/wechatDataBackup&type=Date)](https://star-history.com/?utm_source=bestxtools.com#git-jiadong/wechatDataBackup&Date) 115 | 116 | ## 免责声明 117 | **⚠️ 本项目仅供学习、研究使用,严禁商业使用**
118 | **⚠️ 用于网络安全用途的,请确保在国家法律法规下使用**
119 | **⚠️ 本项目完全免费,问你要钱的都是骗子**
120 | **⚠️ 使用本项目初衷是作者研究微信数据库的运行使用,您使用本软件导致的后果,包含但不限于数据损坏,记录丢失等问题,作者不承担相关责任。**
121 | **⚠️ 因软件特殊性质,请在使用时获得微信账号所有人授权,你当确保不侵犯他人个人隐私权,后果自行承担**
122 | 123 | ## 前端代码 124 | 由于前端代码不成熟,前端界面代码暂时不公开。 125 | 126 | ## 参考/引用 127 | - 微信数据库解密和数据库的使用 [PyWxDump](https://github.com/xaoyaoo/PyWxDump/tree/master) 128 | - silk语音消息解码 [silk-v3-decoder](https://github.com/kn007/silk-v3-decoder) 129 | - PCM转MP3 [lame](https://github.com/viert/lame.git) 130 | - Dat图片解码 [wechatDatDecode](https://github.com/liuggchen/wechatDatDecode) 131 | 132 | ## 交流/讨论 133 | ![](./res/wechatQR.png) -------------------------------------------------------------------------------- /app.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "log" 8 | "mime" 9 | "net/http" 10 | "os" 11 | "path/filepath" 12 | "strconv" 13 | "strings" 14 | "wechatDataBackup/pkg/utils" 15 | "wechatDataBackup/pkg/wechat" 16 | 17 | "github.com/spf13/viper" 18 | "github.com/wailsapp/wails/v2/pkg/runtime" 19 | ) 20 | 21 | const ( 22 | defaultConfig = "config" 23 | configDefaultUserKey = "userConfig.defaultUser" 24 | configUsersKey = "userConfig.users" 25 | configExportPathKey = "exportPath" 26 | appVersion = "v1.2.4" 27 | ) 28 | 29 | type FileLoader struct { 30 | http.Handler 31 | FilePrefix string 32 | } 33 | 34 | func NewFileLoader(prefix string) *FileLoader { 35 | mime.AddExtensionType(".mp3", "audio/mpeg") 36 | return &FileLoader{FilePrefix: prefix} 37 | } 38 | 39 | func (h *FileLoader) SetFilePrefix(prefix string) { 40 | h.FilePrefix = prefix 41 | log.Println("SetFilePrefix", h.FilePrefix) 42 | } 43 | 44 | func (h *FileLoader) ServeHTTP(res http.ResponseWriter, req *http.Request) { 45 | requestedFilename := h.FilePrefix + "\\" + strings.TrimPrefix(req.URL.Path, "/") 46 | 47 | file, err := os.Open(requestedFilename) 48 | if err != nil { 49 | http.Error(res, fmt.Sprintf("Could not load file %s", requestedFilename), http.StatusBadRequest) 50 | return 51 | } 52 | defer file.Close() 53 | 54 | fileInfo, err := file.Stat() 55 | if err != nil { 56 | http.Error(res, "Could not retrieve file info", http.StatusInternalServerError) 57 | return 58 | } 59 | 60 | fileSize := fileInfo.Size() 61 | rangeHeader := req.Header.Get("Range") 62 | if rangeHeader == "" { 63 | // 无 Range 请求,直接返回整个文件 64 | res.Header().Set("Content-Length", strconv.FormatInt(fileSize, 10)) 65 | http.ServeContent(res, req, requestedFilename, fileInfo.ModTime(), file) 66 | return 67 | } 68 | 69 | var start, end int64 70 | if strings.HasPrefix(rangeHeader, "bytes=") { 71 | ranges := strings.Split(strings.TrimPrefix(rangeHeader, "bytes="), "-") 72 | start, _ = strconv.ParseInt(ranges[0], 10, 64) 73 | 74 | if len(ranges) > 1 && ranges[1] != "" { 75 | end, _ = strconv.ParseInt(ranges[1], 10, 64) 76 | } else { 77 | end = fileSize - 1 78 | } 79 | } else { 80 | http.Error(res, "Invalid Range header", http.StatusRequestedRangeNotSatisfiable) 81 | return 82 | } 83 | 84 | if start < 0 || end >= fileSize || start > end { 85 | http.Error(res, "Requested range not satisfiable", http.StatusRequestedRangeNotSatisfiable) 86 | return 87 | } 88 | 89 | contentType := mime.TypeByExtension(filepath.Ext(requestedFilename)) 90 | if contentType == "" { 91 | contentType = "application/octet-stream" 92 | } 93 | res.Header().Set("Content-Type", contentType) 94 | res.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", start, end, fileSize)) 95 | res.Header().Set("Content-Length", strconv.FormatInt(end-start+1, 10)) 96 | res.WriteHeader(http.StatusPartialContent) 97 | buffer := make([]byte, 102400) 98 | file.Seek(start, 0) 99 | for current := start; current <= end; { 100 | readSize := int64(len(buffer)) 101 | if end-current+1 < readSize { 102 | readSize = end - current + 1 103 | } 104 | 105 | n, err := file.Read(buffer[:readSize]) 106 | if err != nil { 107 | break 108 | } 109 | 110 | res.Write(buffer[:n]) 111 | current += int64(n) 112 | } 113 | } 114 | 115 | // App struct 116 | type App struct { 117 | ctx context.Context 118 | infoList *wechat.WeChatInfoList 119 | provider *wechat.WechatDataProvider 120 | defaultUser string 121 | users []string 122 | firstStart bool 123 | firstInit bool 124 | FLoader *FileLoader 125 | } 126 | 127 | type WeChatInfo struct { 128 | ProcessID uint32 `json:"PID"` 129 | FilePath string `json:"FilePath"` 130 | AcountName string `json:"AcountName"` 131 | Version string `json:"Version"` 132 | Is64Bits bool `json:"Is64Bits"` 133 | DBKey string `json:"DBkey"` 134 | } 135 | 136 | type WeChatInfoList struct { 137 | Info []WeChatInfo `json:"Info"` 138 | Total int `json:"Total"` 139 | } 140 | 141 | type WeChatAccountInfos struct { 142 | CurrentAccount string `json:"CurrentAccount"` 143 | Info []wechat.WeChatAccountInfo `json:"Info"` 144 | Total int `json:"Total"` 145 | } 146 | 147 | type ErrorMessage struct { 148 | ErrorStr string `json:"error"` 149 | } 150 | 151 | // NewApp creates a new App application struct 152 | func NewApp() *App { 153 | a := &App{} 154 | log.Println("App version:", appVersion) 155 | a.firstInit = true 156 | a.FLoader = NewFileLoader(".\\") 157 | viper.SetConfigName(defaultConfig) 158 | viper.SetConfigType("json") 159 | viper.AddConfigPath(".") 160 | if err := viper.ReadInConfig(); err == nil { 161 | a.defaultUser = viper.GetString(configDefaultUserKey) 162 | a.users = viper.GetStringSlice(configUsersKey) 163 | prefix := viper.GetString(configExportPathKey) 164 | if prefix != "" { 165 | log.Println("SetFilePrefix", prefix) 166 | a.FLoader.SetFilePrefix(prefix) 167 | } 168 | } else { 169 | log.Println("not config exist") 170 | } 171 | log.Printf("default: %s users: %v\n", a.defaultUser, a.users) 172 | if len(a.users) == 0 { 173 | a.firstStart = true 174 | } 175 | 176 | return a 177 | } 178 | 179 | // startup is called when the app starts. The context is saved 180 | // so we can call the runtime methods 181 | func (a *App) startup(ctx context.Context) { 182 | a.ctx = ctx 183 | } 184 | 185 | func (a *App) beforeClose(ctx context.Context) (prevent bool) { 186 | return false 187 | } 188 | 189 | func (a *App) shutdown(ctx context.Context) { 190 | if a.provider != nil { 191 | a.provider.WechatWechatDataProviderClose() 192 | a.provider = nil 193 | } 194 | log.Printf("App Version %s exit!", appVersion) 195 | } 196 | 197 | func (a *App) GetWeChatAllInfo() string { 198 | infoList := WeChatInfoList{} 199 | infoList.Info = make([]WeChatInfo, 0) 200 | infoList.Total = 0 201 | 202 | if a.provider != nil { 203 | a.provider.WechatWechatDataProviderClose() 204 | a.provider = nil 205 | } 206 | 207 | a.infoList = wechat.GetWeChatAllInfo() 208 | for i := range a.infoList.Info { 209 | var info WeChatInfo 210 | info.ProcessID = a.infoList.Info[i].ProcessID 211 | info.FilePath = a.infoList.Info[i].FilePath 212 | info.AcountName = a.infoList.Info[i].AcountName 213 | info.Version = a.infoList.Info[i].Version 214 | info.Is64Bits = a.infoList.Info[i].Is64Bits 215 | info.DBKey = a.infoList.Info[i].DBKey 216 | infoList.Info = append(infoList.Info, info) 217 | infoList.Total += 1 218 | log.Printf("ProcessID %d, FilePath %s, AcountName %s, Version %s, Is64Bits %t", info.ProcessID, info.FilePath, info.AcountName, info.Version, info.Is64Bits) 219 | } 220 | infoStr, _ := json.Marshal(infoList) 221 | // log.Println(string(infoStr)) 222 | 223 | return string(infoStr) 224 | } 225 | 226 | func (a *App) ExportWeChatAllData(full bool, acountName string) { 227 | 228 | if a.provider != nil { 229 | a.provider.WechatWechatDataProviderClose() 230 | a.provider = nil 231 | } 232 | 233 | progress := make(chan string) 234 | go func() { 235 | var pInfo *wechat.WeChatInfo 236 | for i := range a.infoList.Info { 237 | if a.infoList.Info[i].AcountName == acountName { 238 | pInfo = &a.infoList.Info[i] 239 | break 240 | } 241 | } 242 | 243 | if pInfo == nil { 244 | close(progress) 245 | runtime.EventsEmit(a.ctx, "exportData", fmt.Sprintf("{\"status\":\"error\", \"result\":\"%s error\"}", acountName)) 246 | return 247 | } 248 | 249 | prefixExportPath := a.FLoader.FilePrefix + "\\User\\" 250 | _, err := os.Stat(prefixExportPath) 251 | if err != nil { 252 | os.Mkdir(prefixExportPath, os.ModeDir) 253 | } 254 | 255 | expPath := prefixExportPath + pInfo.AcountName 256 | _, err = os.Stat(expPath) 257 | if err == nil { 258 | if !full { 259 | os.RemoveAll(expPath + "\\Msg") 260 | } else { 261 | os.RemoveAll(expPath) 262 | } 263 | } 264 | 265 | _, err = os.Stat(expPath) 266 | if err != nil { 267 | os.Mkdir(expPath, os.ModeDir) 268 | } 269 | 270 | go wechat.ExportWeChatAllData(*pInfo, expPath, progress) 271 | 272 | for p := range progress { 273 | log.Println(p) 274 | runtime.EventsEmit(a.ctx, "exportData", p) 275 | } 276 | 277 | a.defaultUser = pInfo.AcountName 278 | hasUser := false 279 | for _, user := range a.users { 280 | if user == pInfo.AcountName { 281 | hasUser = true 282 | break 283 | } 284 | } 285 | if !hasUser { 286 | a.users = append(a.users, pInfo.AcountName) 287 | } 288 | a.setCurrentConfig() 289 | }() 290 | } 291 | 292 | func (a *App) createWechatDataProvider(resPath string, prefix string) error { 293 | if a.provider != nil && a.provider.SelfInfo != nil && filepath.Base(resPath) == a.provider.SelfInfo.UserName { 294 | log.Println("WechatDataProvider not need create:", a.provider.SelfInfo.UserName) 295 | return nil 296 | } 297 | 298 | if a.provider != nil { 299 | a.provider.WechatWechatDataProviderClose() 300 | a.provider = nil 301 | log.Println("createWechatDataProvider WechatWechatDataProviderClose") 302 | } 303 | 304 | provider, err := wechat.CreateWechatDataProvider(resPath, prefix) 305 | if err != nil { 306 | log.Println("CreateWechatDataProvider failed:", resPath) 307 | return err 308 | } 309 | 310 | a.provider = provider 311 | // infoJson, _ := json.Marshal(a.provider.SelfInfo) 312 | // runtime.EventsEmit(a.ctx, "selfInfo", string(infoJson)) 313 | return nil 314 | } 315 | 316 | func (a *App) WeChatInit() { 317 | 318 | if a.firstInit { 319 | a.firstInit = false 320 | a.scanAccountByPath(a.FLoader.FilePrefix) 321 | log.Println("scanAccountByPath:", a.FLoader.FilePrefix) 322 | } 323 | 324 | if len(a.defaultUser) == 0 { 325 | log.Println("not defaultUser") 326 | return 327 | } 328 | 329 | expPath := a.FLoader.FilePrefix + "\\User\\" + a.defaultUser 330 | prefixPath := "\\User\\" + a.defaultUser 331 | wechat.ExportWeChatHeadImage(expPath) 332 | if a.createWechatDataProvider(expPath, prefixPath) == nil { 333 | infoJson, _ := json.Marshal(a.provider.SelfInfo) 334 | runtime.EventsEmit(a.ctx, "selfInfo", string(infoJson)) 335 | } 336 | } 337 | 338 | func (a *App) GetWechatSessionList(pageIndex int, pageSize int) string { 339 | if a.provider == nil { 340 | log.Println("provider not init") 341 | return "{\"Total\":0}" 342 | } 343 | log.Printf("pageIndex: %d\n", pageIndex) 344 | list, err := a.provider.WeChatGetSessionList(pageIndex, pageSize) 345 | if err != nil { 346 | return "{\"Total\":0}" 347 | } 348 | 349 | listStr, _ := json.Marshal(list) 350 | log.Println("GetWechatSessionList:", list.Total) 351 | return string(listStr) 352 | } 353 | 354 | func (a *App) GetWechatContactList(pageIndex int, pageSize int) string { 355 | if a.provider == nil { 356 | log.Println("provider not init") 357 | return "{\"Total\":0}" 358 | } 359 | log.Printf("pageIndex: %d\n", pageIndex) 360 | list, err := a.provider.WeChatGetContactList(pageIndex, pageSize) 361 | if err != nil { 362 | return "{\"Total\":0}" 363 | } 364 | 365 | listStr, _ := json.Marshal(list) 366 | log.Println("WeChatGetContactList:", list.Total) 367 | return string(listStr) 368 | } 369 | 370 | func (a *App) GetWechatMessageListByTime(userName string, time int64, pageSize int, direction string) string { 371 | log.Println("GetWechatMessageListByTime:", userName, pageSize, time, direction) 372 | if len(userName) == 0 { 373 | return "{\"Total\":0, \"Rows\":[]}" 374 | } 375 | dire := wechat.Message_Search_Forward 376 | if direction == "backward" { 377 | dire = wechat.Message_Search_Backward 378 | } else if direction == "both" { 379 | dire = wechat.Message_Search_Both 380 | } 381 | list, err := a.provider.WeChatGetMessageListByTime(userName, time, pageSize, dire) 382 | if err != nil { 383 | log.Println("GetWechatMessageListByTime failed:", err) 384 | return "" 385 | } 386 | listStr, _ := json.Marshal(list) 387 | log.Println("GetWechatMessageListByTime:", list.Total) 388 | 389 | return string(listStr) 390 | } 391 | 392 | func (a *App) GetWechatMessageListByType(userName string, time int64, pageSize int, msgType string, direction string) string { 393 | log.Println("GetWechatMessageListByType:", userName, pageSize, time, msgType, direction) 394 | if len(userName) == 0 { 395 | return "{\"Total\":0, \"Rows\":[]}" 396 | } 397 | dire := wechat.Message_Search_Forward 398 | if direction == "backward" { 399 | dire = wechat.Message_Search_Backward 400 | } else if direction == "both" { 401 | dire = wechat.Message_Search_Both 402 | } 403 | list, err := a.provider.WeChatGetMessageListByType(userName, time, pageSize, msgType, dire) 404 | if err != nil { 405 | log.Println("WeChatGetMessageListByType failed:", err) 406 | return "" 407 | } 408 | listStr, _ := json.Marshal(list) 409 | log.Println("WeChatGetMessageListByType:", list.Total) 410 | 411 | return string(listStr) 412 | } 413 | 414 | func (a *App) GetWechatMessageListByKeyWord(userName string, time int64, keyword string, msgType string, pageSize int) string { 415 | log.Println("GetWechatMessageListByKeyWord:", userName, pageSize, time, msgType) 416 | if len(userName) == 0 { 417 | return "{\"Total\":0, \"Rows\":[]}" 418 | } 419 | list, err := a.provider.WeChatGetMessageListByKeyWord(userName, time, keyword, msgType, pageSize) 420 | if err != nil { 421 | log.Println("WeChatGetMessageListByKeyWord failed:", err) 422 | return "" 423 | } 424 | listStr, _ := json.Marshal(list) 425 | log.Println("WeChatGetMessageListByKeyWord:", list.Total, list.KeyWord) 426 | 427 | return string(listStr) 428 | } 429 | 430 | func (a *App) GetWechatMessageDate(userName string) string { 431 | log.Println("GetWechatMessageDate:", userName) 432 | if len(userName) == 0 { 433 | return "{\"Total\":0, \"Date\":[]}" 434 | } 435 | 436 | messageData, err := a.provider.WeChatGetMessageDate(userName) 437 | if err != nil { 438 | log.Println("GetWechatMessageDate:", err) 439 | return "" 440 | } 441 | 442 | messageDataStr, _ := json.Marshal(messageData) 443 | log.Println("GetWechatMessageDate:", messageData.Total) 444 | 445 | return string(messageDataStr) 446 | } 447 | 448 | func (a *App) setCurrentConfig() { 449 | viper.Set(configDefaultUserKey, a.defaultUser) 450 | viper.Set(configUsersKey, a.users) 451 | viper.Set(configExportPathKey, a.FLoader.FilePrefix) 452 | err := viper.SafeWriteConfig() 453 | if err != nil { 454 | log.Println(err) 455 | err = viper.WriteConfig() 456 | if err != nil { 457 | log.Println(err) 458 | } 459 | } 460 | } 461 | 462 | type userList struct { 463 | Users []string `json:"Users"` 464 | } 465 | 466 | func (a *App) GetWeChatUserList() string { 467 | 468 | l := userList{} 469 | l.Users = a.users 470 | 471 | usersStr, _ := json.Marshal(l) 472 | str := string(usersStr) 473 | log.Println("users:", str) 474 | return str 475 | } 476 | 477 | func (a *App) OpenFileOrExplorer(filePath string, explorer bool) string { 478 | // if root, err := os.Getwd(); err == nil { 479 | // filePath = root + filePath[1:] 480 | // } 481 | // log.Println("OpenFileOrExplorer:", filePath) 482 | 483 | path := a.FLoader.FilePrefix + filePath 484 | err := utils.OpenFileOrExplorer(path, explorer) 485 | if err != nil { 486 | return "{\"result\": \"OpenFileOrExplorer failed\", \"status\":\"failed\"}" 487 | } 488 | 489 | return fmt.Sprintf("{\"result\": \"%s\", \"status\":\"OK\"}", "") 490 | } 491 | 492 | func (a *App) GetWeChatRoomUserList(roomId string) string { 493 | userlist, err := a.provider.WeChatGetChatRoomUserList(roomId) 494 | if err != nil { 495 | log.Println("WeChatGetChatRoomUserList:", err) 496 | return "" 497 | } 498 | 499 | userListStr, _ := json.Marshal(userlist) 500 | 501 | return string(userListStr) 502 | } 503 | 504 | func (a *App) GetAppVersion() string { 505 | return appVersion 506 | } 507 | 508 | func (a *App) GetAppIsFirstStart() bool { 509 | defer func() { a.firstStart = false }() 510 | return a.firstStart 511 | } 512 | 513 | func (a *App) GetWechatLocalAccountInfo() string { 514 | infos := WeChatAccountInfos{} 515 | infos.Info = make([]wechat.WeChatAccountInfo, 0) 516 | infos.Total = 0 517 | infos.CurrentAccount = a.defaultUser 518 | for i := range a.users { 519 | resPath := a.FLoader.FilePrefix + "\\User\\" + a.users[i] 520 | if _, err := os.Stat(resPath); err != nil { 521 | log.Println("GetWechatLocalAccountInfo:", resPath, err) 522 | continue 523 | } 524 | 525 | prefixResPath := "\\User\\" + a.users[i] 526 | info, err := wechat.WechatGetAccountInfo(resPath, prefixResPath, a.users[i]) 527 | if err != nil { 528 | log.Println("GetWechatLocalAccountInfo", err) 529 | continue 530 | } 531 | 532 | infos.Info = append(infos.Info, *info) 533 | infos.Total += 1 534 | } 535 | 536 | infoString, _ := json.Marshal(infos) 537 | log.Println(string(infoString)) 538 | 539 | return string(infoString) 540 | } 541 | 542 | func (a *App) WechatSwitchAccount(account string) bool { 543 | for i := range a.users { 544 | if a.users[i] == account { 545 | if a.provider != nil { 546 | a.provider.WechatWechatDataProviderClose() 547 | a.provider = nil 548 | } 549 | a.defaultUser = account 550 | a.setCurrentConfig() 551 | return true 552 | } 553 | } 554 | 555 | return false 556 | } 557 | 558 | func (a *App) GetExportPathStat() string { 559 | path := a.FLoader.FilePrefix 560 | log.Println("utils.GetPathStat ++") 561 | stat, err := utils.GetPathStat(path) 562 | log.Println("utils.GetPathStat --") 563 | if err != nil { 564 | log.Println("GetPathStat error:", path, err) 565 | var msg ErrorMessage 566 | msg.ErrorStr = fmt.Sprintf("%s:%v", path, err) 567 | msgStr, _ := json.Marshal(msg) 568 | return string(msgStr) 569 | } 570 | 571 | statString, _ := json.Marshal(stat) 572 | 573 | return string(statString) 574 | } 575 | 576 | func (a *App) ExportPathIsCanWrite() bool { 577 | path := a.FLoader.FilePrefix 578 | return utils.PathIsCanWriteFile(path) 579 | } 580 | 581 | func (a *App) OpenExportPath() { 582 | path := a.FLoader.FilePrefix 583 | runtime.BrowserOpenURL(a.ctx, path) 584 | } 585 | 586 | func (a *App) OpenDirectoryDialog() string { 587 | dialogOptions := runtime.OpenDialogOptions{ 588 | Title: "选择导出路径", 589 | } 590 | selectedDir, err := runtime.OpenDirectoryDialog(a.ctx, dialogOptions) 591 | if err != nil { 592 | log.Println("OpenDirectoryDialog:", err) 593 | return "" 594 | } 595 | 596 | if selectedDir == "" { 597 | log.Println("Cancel selectedDir") 598 | return "" 599 | } 600 | 601 | if selectedDir == a.FLoader.FilePrefix { 602 | log.Println("same path No need SetFilePrefix") 603 | return "" 604 | } 605 | 606 | if !utils.PathIsCanWriteFile(selectedDir) { 607 | log.Println("PathIsCanWriteFile:", selectedDir, "error") 608 | return "" 609 | } 610 | 611 | a.FLoader.SetFilePrefix(selectedDir) 612 | log.Println("OpenDirectoryDialog:", selectedDir) 613 | a.scanAccountByPath(selectedDir) 614 | return selectedDir 615 | } 616 | 617 | func (a *App) scanAccountByPath(path string) error { 618 | infos := WeChatAccountInfos{} 619 | infos.Info = make([]wechat.WeChatAccountInfo, 0) 620 | infos.Total = 0 621 | infos.CurrentAccount = "" 622 | 623 | userPath := path + "\\User\\" 624 | if _, err := os.Stat(userPath); err != nil { 625 | return err 626 | } 627 | 628 | dirs, err := os.ReadDir(userPath) 629 | if err != nil { 630 | log.Println("ReadDir", err) 631 | return err 632 | } 633 | 634 | for i := range dirs { 635 | if !dirs[i].Type().IsDir() { 636 | continue 637 | } 638 | log.Println("dirs[i].Name():", dirs[i].Name()) 639 | resPath := path + "\\User\\" + dirs[i].Name() 640 | prefixResPath := "\\User\\" + dirs[i].Name() 641 | info, err := wechat.WechatGetAccountInfo(resPath, prefixResPath, dirs[i].Name()) 642 | if err != nil { 643 | log.Println("GetWechatLocalAccountInfo", err) 644 | continue 645 | } 646 | 647 | infos.Info = append(infos.Info, *info) 648 | infos.Total += 1 649 | } 650 | 651 | users := make([]string, 0) 652 | for i := 0; i < infos.Total; i++ { 653 | users = append(users, infos.Info[i].AccountName) 654 | } 655 | 656 | a.users = users 657 | found := false 658 | for i := range a.users { 659 | if a.defaultUser == a.users[i] { 660 | found = true 661 | } 662 | } 663 | 664 | if !found { 665 | a.defaultUser = "" 666 | } 667 | if a.defaultUser == "" && len(a.users) > 0 { 668 | a.defaultUser = a.users[0] 669 | } 670 | 671 | if len(a.users) > 0 { 672 | a.setCurrentConfig() 673 | } 674 | 675 | return nil 676 | } 677 | 678 | func (a *App) OepnLogFileExplorer() { 679 | utils.OpenFileOrExplorer(".\\app.log", true) 680 | } 681 | 682 | func (a *App) SaveFileDialog(file string, alisa string) string { 683 | filePath := a.FLoader.FilePrefix + file 684 | if _, err := os.Stat(filePath); err != nil { 685 | log.Println("SaveFileDialog:", err) 686 | return err.Error() 687 | } 688 | 689 | savePath, err := runtime.SaveFileDialog(a.ctx, runtime.SaveDialogOptions{ 690 | DefaultFilename: alisa, 691 | Title: "选择保存路径", 692 | }) 693 | if err != nil { 694 | log.Println("SaveFileDialog:", err) 695 | return err.Error() 696 | } 697 | 698 | if savePath == "" { 699 | return "" 700 | } 701 | 702 | dirPath := filepath.Dir(savePath) 703 | if !utils.PathIsCanWriteFile(dirPath) { 704 | errStr := "Path Is Can't Write File: " + filepath.Dir(savePath) 705 | log.Println(errStr) 706 | return errStr 707 | } 708 | 709 | _, err = utils.CopyFile(filePath, savePath) 710 | if err != nil { 711 | log.Println("Error CopyFile", filePath, savePath, err) 712 | return err.Error() 713 | } 714 | 715 | return "" 716 | } 717 | 718 | func (a *App) GetSessionLastTime(userName string) string { 719 | if a.provider == nil || userName == "" { 720 | lastTime := &wechat.WeChatLastTime{} 721 | lastTimeString, _ := json.Marshal(lastTime) 722 | return string(lastTimeString) 723 | } 724 | 725 | lastTime := a.provider.WeChatGetSessionLastTime(userName) 726 | 727 | lastTimeString, _ := json.Marshal(lastTime) 728 | 729 | return string(lastTimeString) 730 | } 731 | 732 | func (a *App) SetSessionLastTime(userName string, stamp int64, messageId string) string { 733 | if a.provider == nil { 734 | return "" 735 | } 736 | 737 | lastTime := &wechat.WeChatLastTime{ 738 | UserName: userName, 739 | Timestamp: stamp, 740 | MessageId: messageId, 741 | } 742 | err := a.provider.WeChatSetSessionLastTime(lastTime) 743 | if err != nil { 744 | log.Println("WeChatSetSessionLastTime failed:", err.Error()) 745 | return err.Error() 746 | } 747 | 748 | return "" 749 | } 750 | 751 | func (a *App) SetSessionBookMask(userName, tag, info string) string { 752 | if a.provider == nil || userName == "" { 753 | return "invaild params" 754 | } 755 | err := a.provider.WeChatSetSessionBookMask(userName, tag, info) 756 | if err != nil { 757 | log.Println("WeChatSetSessionBookMask failed:", err.Error()) 758 | return err.Error() 759 | } 760 | 761 | return "" 762 | } 763 | 764 | func (a *App) DelSessionBookMask(markId string) string { 765 | if a.provider == nil || markId == "" { 766 | return "invaild params" 767 | } 768 | 769 | err := a.provider.WeChatDelSessionBookMask(markId) 770 | if err != nil { 771 | log.Println("WeChatDelSessionBookMask failed:", err.Error()) 772 | return err.Error() 773 | } 774 | 775 | return "" 776 | } 777 | 778 | func (a *App) GetSessionBookMaskList(userName string) string { 779 | if a.provider == nil || userName == "" { 780 | return "invaild params" 781 | } 782 | markLIst, err := a.provider.WeChatGetSessionBookMaskList(userName) 783 | if err != nil { 784 | log.Println("WeChatGetSessionBookMaskList failed:", err.Error()) 785 | _list := &wechat.WeChatBookMarkList{} 786 | _listString, _ := json.Marshal(_list) 787 | return string(_listString) 788 | } 789 | 790 | markLIstString, _ := json.Marshal(markLIst) 791 | return string(markLIstString) 792 | } 793 | 794 | func (a *App) SelectedDirDialog(title string) string { 795 | dialogOptions := runtime.OpenDialogOptions{ 796 | Title: title, 797 | } 798 | selectedDir, err := runtime.OpenDirectoryDialog(a.ctx, dialogOptions) 799 | if err != nil { 800 | log.Println("OpenDirectoryDialog:", err) 801 | return "" 802 | } 803 | 804 | if selectedDir == "" { 805 | return "" 806 | } 807 | 808 | return selectedDir 809 | } 810 | 811 | func (a *App) ExportWeChatDataByUserName(userName, path string) string { 812 | if a.provider == nil || userName == "" || path == "" { 813 | return "invaild params" + userName 814 | } 815 | 816 | if !utils.PathIsCanWriteFile(path) { 817 | log.Println("PathIsCanWriteFile: " + path) 818 | return "PathIsCanWriteFile: " + path 819 | } 820 | 821 | exPath := path + "\\" + "wechatDataBackup_" + userName 822 | if _, err := os.Stat(exPath); err != nil { 823 | os.MkdirAll(exPath, os.ModePerm) 824 | } else { 825 | return "path exist:" + exPath 826 | } 827 | 828 | log.Println("ExportWeChatDataByUserName:", userName, exPath) 829 | err := a.provider.WeChatExportDataByUserName(userName, exPath) 830 | if err != nil { 831 | log.Println("WeChatExportDataByUserName failed:", err) 832 | return "WeChatExportDataByUserName failed:" + err.Error() 833 | } 834 | 835 | config := map[string]interface{}{ 836 | "exportpath": ".\\", 837 | "userconfig": map[string]interface{}{ 838 | "defaultuser": a.defaultUser, 839 | "users": []string{a.defaultUser}, 840 | }, 841 | } 842 | 843 | configJson, err := json.MarshalIndent(config, "", " ") 844 | if err != nil { 845 | log.Println("MarshalIndent:", err) 846 | return "MarshalIndent:" + err.Error() 847 | } 848 | 849 | configPath := exPath + "\\" + "config.json" 850 | err = os.WriteFile(configPath, configJson, os.ModePerm) 851 | if err != nil { 852 | log.Println("WriteFile:", err) 853 | return "WriteFile:" + err.Error() 854 | } 855 | 856 | exeSrcPath, err := os.Executable() 857 | if err != nil { 858 | log.Println("Executable:", exeSrcPath) 859 | return "Executable:" + err.Error() 860 | } 861 | 862 | exeDstPath := exPath + "\\" + "wechatDataBackup.exe" 863 | log.Printf("Copy [%s] -> [%s]\n", exeSrcPath, exeDstPath) 864 | _, err = utils.CopyFile(exeSrcPath, exeDstPath) 865 | if err != nil { 866 | log.Println("CopyFile:", err) 867 | return "CopyFile:" + err.Error() 868 | } 869 | return "" 870 | 871 | return "" 872 | } 873 | 874 | func (a *App) GetAppIsShareData() bool { 875 | if a.provider != nil { 876 | return a.provider.IsShareData 877 | } 878 | return false 879 | } 880 | -------------------------------------------------------------------------------- /build/README.md: -------------------------------------------------------------------------------- 1 | # Build Directory 2 | 3 | The build directory is used to house all the build files and assets for your application. 4 | 5 | The structure is: 6 | 7 | * bin - Output directory 8 | * darwin - macOS specific files 9 | * windows - Windows specific files 10 | 11 | ## Mac 12 | 13 | The `darwin` directory holds files specific to Mac builds. 14 | These may be customised and used as part of the build. To return these files to the default state, simply delete them 15 | and 16 | build with `wails build`. 17 | 18 | The directory contains the following files: 19 | 20 | - `Info.plist` - the main plist file used for Mac builds. It is used when building using `wails build`. 21 | - `Info.dev.plist` - same as the main plist file but used when building using `wails dev`. 22 | 23 | ## Windows 24 | 25 | The `windows` directory contains the manifest and rc files used when building with `wails build`. 26 | These may be customised for your application. To return these files to the default state, simply delete them and 27 | build with `wails build`. 28 | 29 | - `icon.ico` - The icon used for the application. This is used when building using `wails build`. If you wish to 30 | use a different icon, simply replace this file with your own. If it is missing, a new `icon.ico` file 31 | will be created using the `appicon.png` file in the build directory. 32 | - `installer/*` - The files used to create the Windows installer. These are used when building using `wails build`. 33 | - `info.json` - Application details used for Windows builds. The data here will be used by the Windows installer, 34 | as well as the application itself (right click the exe -> properties -> details) 35 | - `wails.exe.manifest` - The main application manifest file. -------------------------------------------------------------------------------- /build/appicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/build/appicon.png -------------------------------------------------------------------------------- /build/darwin/Info.dev.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundlePackageType 5 | APPL 6 | CFBundleName 7 | {{.Info.ProductName}} 8 | CFBundleExecutable 9 | {{.Name}} 10 | CFBundleIdentifier 11 | com.wails.{{.Name}} 12 | CFBundleVersion 13 | {{.Info.ProductVersion}} 14 | CFBundleGetInfoString 15 | {{.Info.Comments}} 16 | CFBundleShortVersionString 17 | {{.Info.ProductVersion}} 18 | CFBundleIconFile 19 | iconfile 20 | LSMinimumSystemVersion 21 | 10.13.0 22 | NSHighResolutionCapable 23 | true 24 | NSHumanReadableCopyright 25 | {{.Info.Copyright}} 26 | {{if .Info.FileAssociations}} 27 | CFBundleDocumentTypes 28 | 29 | {{range .Info.FileAssociations}} 30 | 31 | CFBundleTypeExtensions 32 | 33 | {{.Ext}} 34 | 35 | CFBundleTypeName 36 | {{.Name}} 37 | CFBundleTypeRole 38 | {{.Role}} 39 | CFBundleTypeIconFile 40 | {{.IconName}} 41 | 42 | {{end}} 43 | 44 | {{end}} 45 | {{if .Info.Protocols}} 46 | CFBundleURLTypes 47 | 48 | {{range .Info.Protocols}} 49 | 50 | CFBundleURLName 51 | com.wails.{{.Scheme}} 52 | CFBundleURLSchemes 53 | 54 | {{.Scheme}} 55 | 56 | CFBundleTypeRole 57 | {{.Role}} 58 | 59 | {{end}} 60 | 61 | {{end}} 62 | NSAppTransportSecurity 63 | 64 | NSAllowsLocalNetworking 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /build/darwin/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundlePackageType 5 | APPL 6 | CFBundleName 7 | {{.Info.ProductName}} 8 | CFBundleExecutable 9 | {{.Name}} 10 | CFBundleIdentifier 11 | com.wails.{{.Name}} 12 | CFBundleVersion 13 | {{.Info.ProductVersion}} 14 | CFBundleGetInfoString 15 | {{.Info.Comments}} 16 | CFBundleShortVersionString 17 | {{.Info.ProductVersion}} 18 | CFBundleIconFile 19 | iconfile 20 | LSMinimumSystemVersion 21 | 10.13.0 22 | NSHighResolutionCapable 23 | true 24 | NSHumanReadableCopyright 25 | {{.Info.Copyright}} 26 | {{if .Info.FileAssociations}} 27 | CFBundleDocumentTypes 28 | 29 | {{range .Info.FileAssociations}} 30 | 31 | CFBundleTypeExtensions 32 | 33 | {{.Ext}} 34 | 35 | CFBundleTypeName 36 | {{.Name}} 37 | CFBundleTypeRole 38 | {{.Role}} 39 | CFBundleTypeIconFile 40 | {{.IconName}} 41 | 42 | {{end}} 43 | 44 | {{end}} 45 | {{if .Info.Protocols}} 46 | CFBundleURLTypes 47 | 48 | {{range .Info.Protocols}} 49 | 50 | CFBundleURLName 51 | com.wails.{{.Scheme}} 52 | CFBundleURLSchemes 53 | 54 | {{.Scheme}} 55 | 56 | CFBundleTypeRole 57 | {{.Role}} 58 | 59 | {{end}} 60 | 61 | {{end}} 62 | 63 | 64 | -------------------------------------------------------------------------------- /build/windows/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/build/windows/icon.ico -------------------------------------------------------------------------------- /build/windows/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "fixed": { 3 | "file_version": "{{.Info.ProductVersion}}" 4 | }, 5 | "info": { 6 | "0000": { 7 | "ProductVersion": "{{.Info.ProductVersion}}", 8 | "CompanyName": "{{.Info.CompanyName}}", 9 | "FileDescription": "{{.Info.ProductName}}", 10 | "LegalCopyright": "{{.Info.Copyright}}", 11 | "ProductName": "{{.Info.ProductName}}", 12 | "Comments": "{{.Info.Comments}}" 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /build/windows/installer/project.nsi: -------------------------------------------------------------------------------- 1 | Unicode true 2 | 3 | #### 4 | ## Please note: Template replacements don't work in this file. They are provided with default defines like 5 | ## mentioned underneath. 6 | ## If the keyword is not defined, "wails_tools.nsh" will populate them with the values from ProjectInfo. 7 | ## If they are defined here, "wails_tools.nsh" will not touch them. This allows to use this project.nsi manually 8 | ## from outside of Wails for debugging and development of the installer. 9 | ## 10 | ## For development first make a wails nsis build to populate the "wails_tools.nsh": 11 | ## > wails build --target windows/amd64 --nsis 12 | ## Then you can call makensis on this file with specifying the path to your binary: 13 | ## For a AMD64 only installer: 14 | ## > makensis -DARG_WAILS_AMD64_BINARY=..\..\bin\app.exe 15 | ## For a ARM64 only installer: 16 | ## > makensis -DARG_WAILS_ARM64_BINARY=..\..\bin\app.exe 17 | ## For a installer with both architectures: 18 | ## > makensis -DARG_WAILS_AMD64_BINARY=..\..\bin\app-amd64.exe -DARG_WAILS_ARM64_BINARY=..\..\bin\app-arm64.exe 19 | #### 20 | ## The following information is taken from the ProjectInfo file, but they can be overwritten here. 21 | #### 22 | ## !define INFO_PROJECTNAME "MyProject" # Default "{{.Name}}" 23 | ## !define INFO_COMPANYNAME "MyCompany" # Default "{{.Info.CompanyName}}" 24 | ## !define INFO_PRODUCTNAME "MyProduct" # Default "{{.Info.ProductName}}" 25 | ## !define INFO_PRODUCTVERSION "1.0.0" # Default "{{.Info.ProductVersion}}" 26 | ## !define INFO_COPYRIGHT "Copyright" # Default "{{.Info.Copyright}}" 27 | ### 28 | ## !define PRODUCT_EXECUTABLE "Application.exe" # Default "${INFO_PROJECTNAME}.exe" 29 | ## !define UNINST_KEY_NAME "UninstKeyInRegistry" # Default "${INFO_COMPANYNAME}${INFO_PRODUCTNAME}" 30 | #### 31 | ## !define REQUEST_EXECUTION_LEVEL "admin" # Default "admin" see also https://nsis.sourceforge.io/Docs/Chapter4.html 32 | #### 33 | ## Include the wails tools 34 | #### 35 | !include "wails_tools.nsh" 36 | 37 | # The version information for this two must consist of 4 parts 38 | VIProductVersion "${INFO_PRODUCTVERSION}.0" 39 | VIFileVersion "${INFO_PRODUCTVERSION}.0" 40 | 41 | VIAddVersionKey "CompanyName" "${INFO_COMPANYNAME}" 42 | VIAddVersionKey "FileDescription" "${INFO_PRODUCTNAME} Installer" 43 | VIAddVersionKey "ProductVersion" "${INFO_PRODUCTVERSION}" 44 | VIAddVersionKey "FileVersion" "${INFO_PRODUCTVERSION}" 45 | VIAddVersionKey "LegalCopyright" "${INFO_COPYRIGHT}" 46 | VIAddVersionKey "ProductName" "${INFO_PRODUCTNAME}" 47 | 48 | # Enable HiDPI support. https://nsis.sourceforge.io/Reference/ManifestDPIAware 49 | ManifestDPIAware true 50 | 51 | !include "MUI.nsh" 52 | 53 | !define MUI_ICON "..\icon.ico" 54 | !define MUI_UNICON "..\icon.ico" 55 | # !define MUI_WELCOMEFINISHPAGE_BITMAP "resources\leftimage.bmp" #Include this to add a bitmap on the left side of the Welcome Page. Must be a size of 164x314 56 | !define MUI_FINISHPAGE_NOAUTOCLOSE # Wait on the INSTFILES page so the user can take a look into the details of the installation steps 57 | !define MUI_ABORTWARNING # This will warn the user if they exit from the installer. 58 | 59 | !insertmacro MUI_PAGE_WELCOME # Welcome to the installer page. 60 | # !insertmacro MUI_PAGE_LICENSE "resources\eula.txt" # Adds a EULA page to the installer 61 | !insertmacro MUI_PAGE_DIRECTORY # In which folder install page. 62 | !insertmacro MUI_PAGE_INSTFILES # Installing page. 63 | !insertmacro MUI_PAGE_FINISH # Finished installation page. 64 | 65 | !insertmacro MUI_UNPAGE_INSTFILES # Uinstalling page 66 | 67 | !insertmacro MUI_LANGUAGE "English" # Set the Language of the installer 68 | 69 | ## The following two statements can be used to sign the installer and the uninstaller. The path to the binaries are provided in %1 70 | #!uninstfinalize 'signtool --file "%1"' 71 | #!finalize 'signtool --file "%1"' 72 | 73 | Name "${INFO_PRODUCTNAME}" 74 | OutFile "..\..\bin\${INFO_PROJECTNAME}-${ARCH}-installer.exe" # Name of the installer's file. 75 | InstallDir "$PROGRAMFILES64\${INFO_COMPANYNAME}\${INFO_PRODUCTNAME}" # Default installing folder ($PROGRAMFILES is Program Files folder). 76 | ShowInstDetails show # This will always show the installation details. 77 | 78 | Function .onInit 79 | !insertmacro wails.checkArchitecture 80 | FunctionEnd 81 | 82 | Section 83 | !insertmacro wails.setShellContext 84 | 85 | !insertmacro wails.webview2runtime 86 | 87 | SetOutPath $INSTDIR 88 | 89 | !insertmacro wails.files 90 | 91 | CreateShortcut "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}" 92 | CreateShortCut "$DESKTOP\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}" 93 | 94 | !insertmacro wails.associateFiles 95 | !insertmacro wails.associateCustomProtocols 96 | 97 | !insertmacro wails.writeUninstaller 98 | SectionEnd 99 | 100 | Section "uninstall" 101 | !insertmacro wails.setShellContext 102 | 103 | RMDir /r "$AppData\${PRODUCT_EXECUTABLE}" # Remove the WebView2 DataPath 104 | 105 | RMDir /r $INSTDIR 106 | 107 | Delete "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk" 108 | Delete "$DESKTOP\${INFO_PRODUCTNAME}.lnk" 109 | 110 | !insertmacro wails.unassociateFiles 111 | !insertmacro wails.unassociateCustomProtocols 112 | 113 | !insertmacro wails.deleteUninstaller 114 | SectionEnd 115 | -------------------------------------------------------------------------------- /build/windows/installer/wails_tools.nsh: -------------------------------------------------------------------------------- 1 | # DO NOT EDIT - Generated automatically by `wails build` 2 | 3 | !include "x64.nsh" 4 | !include "WinVer.nsh" 5 | !include "FileFunc.nsh" 6 | 7 | !ifndef INFO_PROJECTNAME 8 | !define INFO_PROJECTNAME "{{.Name}}" 9 | !endif 10 | !ifndef INFO_COMPANYNAME 11 | !define INFO_COMPANYNAME "{{.Info.CompanyName}}" 12 | !endif 13 | !ifndef INFO_PRODUCTNAME 14 | !define INFO_PRODUCTNAME "{{.Info.ProductName}}" 15 | !endif 16 | !ifndef INFO_PRODUCTVERSION 17 | !define INFO_PRODUCTVERSION "{{.Info.ProductVersion}}" 18 | !endif 19 | !ifndef INFO_COPYRIGHT 20 | !define INFO_COPYRIGHT "{{.Info.Copyright}}" 21 | !endif 22 | !ifndef PRODUCT_EXECUTABLE 23 | !define PRODUCT_EXECUTABLE "${INFO_PROJECTNAME}.exe" 24 | !endif 25 | !ifndef UNINST_KEY_NAME 26 | !define UNINST_KEY_NAME "${INFO_COMPANYNAME}${INFO_PRODUCTNAME}" 27 | !endif 28 | !define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINST_KEY_NAME}" 29 | 30 | !ifndef REQUEST_EXECUTION_LEVEL 31 | !define REQUEST_EXECUTION_LEVEL "admin" 32 | !endif 33 | 34 | RequestExecutionLevel "${REQUEST_EXECUTION_LEVEL}" 35 | 36 | !ifdef ARG_WAILS_AMD64_BINARY 37 | !define SUPPORTS_AMD64 38 | !endif 39 | 40 | !ifdef ARG_WAILS_ARM64_BINARY 41 | !define SUPPORTS_ARM64 42 | !endif 43 | 44 | !ifdef SUPPORTS_AMD64 45 | !ifdef SUPPORTS_ARM64 46 | !define ARCH "amd64_arm64" 47 | !else 48 | !define ARCH "amd64" 49 | !endif 50 | !else 51 | !ifdef SUPPORTS_ARM64 52 | !define ARCH "arm64" 53 | !else 54 | !error "Wails: Undefined ARCH, please provide at least one of ARG_WAILS_AMD64_BINARY or ARG_WAILS_ARM64_BINARY" 55 | !endif 56 | !endif 57 | 58 | !macro wails.checkArchitecture 59 | !ifndef WAILS_WIN10_REQUIRED 60 | !define WAILS_WIN10_REQUIRED "This product is only supported on Windows 10 (Server 2016) and later." 61 | !endif 62 | 63 | !ifndef WAILS_ARCHITECTURE_NOT_SUPPORTED 64 | !define WAILS_ARCHITECTURE_NOT_SUPPORTED "This product can't be installed on the current Windows architecture. Supports: ${ARCH}" 65 | !endif 66 | 67 | ${If} ${AtLeastWin10} 68 | !ifdef SUPPORTS_AMD64 69 | ${if} ${IsNativeAMD64} 70 | Goto ok 71 | ${EndIf} 72 | !endif 73 | 74 | !ifdef SUPPORTS_ARM64 75 | ${if} ${IsNativeARM64} 76 | Goto ok 77 | ${EndIf} 78 | !endif 79 | 80 | IfSilent silentArch notSilentArch 81 | silentArch: 82 | SetErrorLevel 65 83 | Abort 84 | notSilentArch: 85 | MessageBox MB_OK "${WAILS_ARCHITECTURE_NOT_SUPPORTED}" 86 | Quit 87 | ${else} 88 | IfSilent silentWin notSilentWin 89 | silentWin: 90 | SetErrorLevel 64 91 | Abort 92 | notSilentWin: 93 | MessageBox MB_OK "${WAILS_WIN10_REQUIRED}" 94 | Quit 95 | ${EndIf} 96 | 97 | ok: 98 | !macroend 99 | 100 | !macro wails.files 101 | !ifdef SUPPORTS_AMD64 102 | ${if} ${IsNativeAMD64} 103 | File "/oname=${PRODUCT_EXECUTABLE}" "${ARG_WAILS_AMD64_BINARY}" 104 | ${EndIf} 105 | !endif 106 | 107 | !ifdef SUPPORTS_ARM64 108 | ${if} ${IsNativeARM64} 109 | File "/oname=${PRODUCT_EXECUTABLE}" "${ARG_WAILS_ARM64_BINARY}" 110 | ${EndIf} 111 | !endif 112 | !macroend 113 | 114 | !macro wails.writeUninstaller 115 | WriteUninstaller "$INSTDIR\uninstall.exe" 116 | 117 | SetRegView 64 118 | WriteRegStr HKLM "${UNINST_KEY}" "Publisher" "${INFO_COMPANYNAME}" 119 | WriteRegStr HKLM "${UNINST_KEY}" "DisplayName" "${INFO_PRODUCTNAME}" 120 | WriteRegStr HKLM "${UNINST_KEY}" "DisplayVersion" "${INFO_PRODUCTVERSION}" 121 | WriteRegStr HKLM "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\${PRODUCT_EXECUTABLE}" 122 | WriteRegStr HKLM "${UNINST_KEY}" "UninstallString" "$\"$INSTDIR\uninstall.exe$\"" 123 | WriteRegStr HKLM "${UNINST_KEY}" "QuietUninstallString" "$\"$INSTDIR\uninstall.exe$\" /S" 124 | 125 | ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2 126 | IntFmt $0 "0x%08X" $0 127 | WriteRegDWORD HKLM "${UNINST_KEY}" "EstimatedSize" "$0" 128 | !macroend 129 | 130 | !macro wails.deleteUninstaller 131 | Delete "$INSTDIR\uninstall.exe" 132 | 133 | SetRegView 64 134 | DeleteRegKey HKLM "${UNINST_KEY}" 135 | !macroend 136 | 137 | !macro wails.setShellContext 138 | ${If} ${REQUEST_EXECUTION_LEVEL} == "admin" 139 | SetShellVarContext all 140 | ${else} 141 | SetShellVarContext current 142 | ${EndIf} 143 | !macroend 144 | 145 | # Install webview2 by launching the bootstrapper 146 | # See https://docs.microsoft.com/en-us/microsoft-edge/webview2/concepts/distribution#online-only-deployment 147 | !macro wails.webview2runtime 148 | !ifndef WAILS_INSTALL_WEBVIEW_DETAILPRINT 149 | !define WAILS_INSTALL_WEBVIEW_DETAILPRINT "Installing: WebView2 Runtime" 150 | !endif 151 | 152 | SetRegView 64 153 | # If the admin key exists and is not empty then webview2 is already installed 154 | ReadRegStr $0 HKLM "SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv" 155 | ${If} $0 != "" 156 | Goto ok 157 | ${EndIf} 158 | 159 | ${If} ${REQUEST_EXECUTION_LEVEL} == "user" 160 | # If the installer is run in user level, check the user specific key exists and is not empty then webview2 is already installed 161 | ReadRegStr $0 HKCU "Software\Microsoft\EdgeUpdate\Clients{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv" 162 | ${If} $0 != "" 163 | Goto ok 164 | ${EndIf} 165 | ${EndIf} 166 | 167 | SetDetailsPrint both 168 | DetailPrint "${WAILS_INSTALL_WEBVIEW_DETAILPRINT}" 169 | SetDetailsPrint listonly 170 | 171 | InitPluginsDir 172 | CreateDirectory "$pluginsdir\webview2bootstrapper" 173 | SetOutPath "$pluginsdir\webview2bootstrapper" 174 | File "tmp\MicrosoftEdgeWebview2Setup.exe" 175 | ExecWait '"$pluginsdir\webview2bootstrapper\MicrosoftEdgeWebview2Setup.exe" /silent /install' 176 | 177 | SetDetailsPrint both 178 | ok: 179 | !macroend 180 | 181 | # Copy of APP_ASSOCIATE and APP_UNASSOCIATE macros from here https://gist.github.com/nikku/281d0ef126dbc215dd58bfd5b3a5cd5b 182 | !macro APP_ASSOCIATE EXT FILECLASS DESCRIPTION ICON COMMANDTEXT COMMAND 183 | ; Backup the previously associated file class 184 | ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" "" 185 | WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "${FILECLASS}_backup" "$R0" 186 | 187 | WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "${FILECLASS}" 188 | 189 | WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}" "" `${DESCRIPTION}` 190 | WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\DefaultIcon" "" `${ICON}` 191 | WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell" "" "open" 192 | WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open" "" `${COMMANDTEXT}` 193 | WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open\command" "" `${COMMAND}` 194 | !macroend 195 | 196 | !macro APP_UNASSOCIATE EXT FILECLASS 197 | ; Backup the previously associated file class 198 | ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" `${FILECLASS}_backup` 199 | WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "$R0" 200 | 201 | DeleteRegKey SHELL_CONTEXT `Software\Classes\${FILECLASS}` 202 | !macroend 203 | 204 | !macro wails.associateFiles 205 | ; Create file associations 206 | {{range .Info.FileAssociations}} 207 | !insertmacro APP_ASSOCIATE "{{.Ext}}" "{{.Name}}" "{{.Description}}" "$INSTDIR\{{.IconName}}.ico" "Open with ${INFO_PRODUCTNAME}" "$INSTDIR\${PRODUCT_EXECUTABLE} $\"%1$\"" 208 | 209 | File "..\{{.IconName}}.ico" 210 | {{end}} 211 | !macroend 212 | 213 | !macro wails.unassociateFiles 214 | ; Delete app associations 215 | {{range .Info.FileAssociations}} 216 | !insertmacro APP_UNASSOCIATE "{{.Ext}}" "{{.Name}}" 217 | 218 | Delete "$INSTDIR\{{.IconName}}.ico" 219 | {{end}} 220 | !macroend 221 | 222 | !macro CUSTOM_PROTOCOL_ASSOCIATE PROTOCOL DESCRIPTION ICON COMMAND 223 | DeleteRegKey SHELL_CONTEXT "Software\Classes\${PROTOCOL}" 224 | WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}" "" "${DESCRIPTION}" 225 | WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}" "URL Protocol" "" 226 | WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\DefaultIcon" "" "${ICON}" 227 | WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\shell" "" "" 228 | WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\shell\open" "" "" 229 | WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\shell\open\command" "" "${COMMAND}" 230 | !macroend 231 | 232 | !macro CUSTOM_PROTOCOL_UNASSOCIATE PROTOCOL 233 | DeleteRegKey SHELL_CONTEXT "Software\Classes\${PROTOCOL}" 234 | !macroend 235 | 236 | !macro wails.associateCustomProtocols 237 | ; Create custom protocols associations 238 | {{range .Info.Protocols}} 239 | !insertmacro CUSTOM_PROTOCOL_ASSOCIATE "{{.Scheme}}" "{{.Description}}" "$INSTDIR\${PRODUCT_EXECUTABLE},0" "$INSTDIR\${PRODUCT_EXECUTABLE} $\"%1$\"" 240 | 241 | {{end}} 242 | !macroend 243 | 244 | !macro wails.unassociateCustomProtocols 245 | ; Delete app custom protocol associations 246 | {{range .Info.Protocols}} 247 | !insertmacro CUSTOM_PROTOCOL_UNASSOCIATE "{{.Scheme}}" 248 | {{end}} 249 | !macroend 250 | -------------------------------------------------------------------------------- /build/windows/wails.exe.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | true/pm 12 | permonitorv2,permonitor 13 | 14 | 15 | -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | ## v1.2.4 2 | 1. 修复FileStorage\Image文件没有导出的问题 3 | 2. 增加图片定位到聊天位置的功能 4 | 3. 导出界面增加提示 5 | 6 | ## v1.2.3 7 | 1. 修改程序ICON 8 | 9 | ## v1.2.2 10 | 1. UI统一使用“思源黑体”字体 11 | 2. 修复导出分享会话时可执行文件可能拷贝错误的情况 12 | 3. 修复微信存储路径为网盘时,解密会失败的情况 13 | 14 | ## v1.2.0 15 | 1. 实现最后浏览位置记录和书签功能 16 | 2. 实现会话导出分享功能 17 | 3. 增加语音消息和通话消息的筛选 18 | 4. 实现单聊会话对话人位置调换功能 19 | 5. 添加繁体和英语表情的解析 20 | 6. 修复相近时间的消息可能会出现顺序不对的问题 21 | 22 | ## v1.1.0 23 | 1. 支持转账、通话、链接消息的显示 24 | 2. 支持名片、视频号、QQ音乐、小程序、定位等消息的显示 25 | 3. 支持直播、游戏消息、语音通话、视频通话等消息的显示 26 | 4. 解密前判断对数据库是否有读权限 27 | 5. system消息html格式转文本 28 | 29 | ## v1.0.7 30 | 1. 修复出现空数据库时不显示,聊天显示不全的问题 31 | 2. 解决安卓平板扫码登陆的情况下解密失败的问题 32 | 3. 移除lame和silk代码,改用mod的方式引入 33 | 34 | ## v1.0.6 35 | 1. 图片/视频查看重新实现,保持与微信一致 36 | 2. 增加软件信息页面 37 | 3. 修复企业联系人头像和名称不显示问题 38 | 4. 修复撤回消息显示不正常的问题 39 | 40 | ## v1.0.5 41 | 1. 修复联系人搜索显示异常的问题 42 | 2. 修复启动时界面加载显示慢的问题 43 | 44 | ## v1.0.4 45 | 1. 优化头像显示,优先使用本地头像显示 46 | 2. 支持自定义导出路径、增加对导出路径的权限检测 47 | 3. 增加联系人显示 48 | 4. 滚动条滑块调整小大 49 | 5. 支持已经打开日志所在文件夹、日志增加回滚功能 50 | 51 | ## v1.0.3 52 | 1. 增加首次使用的引导功能 53 | 2. 增加多开微信可选择导出功能 54 | 3. 增加多账号数据可以切换查看功能 55 | 56 | ## v1.0.2 57 | 1. 对话列表按照导出时微信显示顺序显示 58 | 2. 增加版本更新检测按钮 59 | 60 | ## v1.0.1 61 | 1. 细化导出进度条的显示 -------------------------------------------------------------------------------- /frontend/dist/assets/001_微笑.1ec7a344.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/001_微笑.1ec7a344.png -------------------------------------------------------------------------------- /frontend/dist/assets/002_撇嘴.0279218b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/002_撇嘴.0279218b.png -------------------------------------------------------------------------------- /frontend/dist/assets/003_色.e92bb91a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/003_色.e92bb91a.png -------------------------------------------------------------------------------- /frontend/dist/assets/004_发呆.8d849292.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/004_发呆.8d849292.png -------------------------------------------------------------------------------- /frontend/dist/assets/005_得意.72060784.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/005_得意.72060784.png -------------------------------------------------------------------------------- /frontend/dist/assets/006_流泪.f52e5f23.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/006_流泪.f52e5f23.png -------------------------------------------------------------------------------- /frontend/dist/assets/007_害羞.414541cc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/007_害羞.414541cc.png -------------------------------------------------------------------------------- /frontend/dist/assets/008_闭嘴.4b6c78a6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/008_闭嘴.4b6c78a6.png -------------------------------------------------------------------------------- /frontend/dist/assets/009_睡.75e64219.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/009_睡.75e64219.png -------------------------------------------------------------------------------- /frontend/dist/assets/010_大哭.2cd2fee3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/010_大哭.2cd2fee3.png -------------------------------------------------------------------------------- /frontend/dist/assets/011_尴尬.70cfea1c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/011_尴尬.70cfea1c.png -------------------------------------------------------------------------------- /frontend/dist/assets/012_发怒.b88ce021.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/012_发怒.b88ce021.png -------------------------------------------------------------------------------- /frontend/dist/assets/013_调皮.f3363541.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/013_调皮.f3363541.png -------------------------------------------------------------------------------- /frontend/dist/assets/014_呲牙.3cd1fb7c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/014_呲牙.3cd1fb7c.png -------------------------------------------------------------------------------- /frontend/dist/assets/015_惊讶.c9eb5e15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/015_惊讶.c9eb5e15.png -------------------------------------------------------------------------------- /frontend/dist/assets/016_难过.5d872489.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/016_难过.5d872489.png -------------------------------------------------------------------------------- /frontend/dist/assets/017_囧.59ee6551.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/017_囧.59ee6551.png -------------------------------------------------------------------------------- /frontend/dist/assets/018_抓狂.d1646df8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/018_抓狂.d1646df8.png -------------------------------------------------------------------------------- /frontend/dist/assets/019_吐.51bb226f.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/019_吐.51bb226f.png -------------------------------------------------------------------------------- /frontend/dist/assets/020_偷笑.59941b0b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/020_偷笑.59941b0b.png -------------------------------------------------------------------------------- /frontend/dist/assets/021_愉快.47582f99.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/021_愉快.47582f99.png -------------------------------------------------------------------------------- /frontend/dist/assets/022_白眼.ca492234.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/022_白眼.ca492234.png -------------------------------------------------------------------------------- /frontend/dist/assets/023_傲慢.651b4c79.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/023_傲慢.651b4c79.png -------------------------------------------------------------------------------- /frontend/dist/assets/024_困.4556c7db.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/024_困.4556c7db.png -------------------------------------------------------------------------------- /frontend/dist/assets/025_惊恐.ed5cfeab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/025_惊恐.ed5cfeab.png -------------------------------------------------------------------------------- /frontend/dist/assets/026_憨笑.6d317a05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/026_憨笑.6d317a05.png -------------------------------------------------------------------------------- /frontend/dist/assets/027_悠闲.cef28253.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/027_悠闲.cef28253.png -------------------------------------------------------------------------------- /frontend/dist/assets/028_咒骂.a26d48fa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/028_咒骂.a26d48fa.png -------------------------------------------------------------------------------- /frontend/dist/assets/029_疑问.aaa09269.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/029_疑问.aaa09269.png -------------------------------------------------------------------------------- /frontend/dist/assets/030_嘘.40e8213d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/030_嘘.40e8213d.png -------------------------------------------------------------------------------- /frontend/dist/assets/031_晕.44e3541a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/031_晕.44e3541a.png -------------------------------------------------------------------------------- /frontend/dist/assets/032_衰.1a3910a6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/032_衰.1a3910a6.png -------------------------------------------------------------------------------- /frontend/dist/assets/033_骷髅.3c9202dc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/033_骷髅.3c9202dc.png -------------------------------------------------------------------------------- /frontend/dist/assets/034_敲打.b2798ca7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/034_敲打.b2798ca7.png -------------------------------------------------------------------------------- /frontend/dist/assets/035_再见.db23652c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/035_再见.db23652c.png -------------------------------------------------------------------------------- /frontend/dist/assets/036_擦汗.b46fa893.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/036_擦汗.b46fa893.png -------------------------------------------------------------------------------- /frontend/dist/assets/037_抠鼻.64bc8033.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/037_抠鼻.64bc8033.png -------------------------------------------------------------------------------- /frontend/dist/assets/038_鼓掌.2a84e4c7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/038_鼓掌.2a84e4c7.png -------------------------------------------------------------------------------- /frontend/dist/assets/039_坏笑.4998b91f.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/039_坏笑.4998b91f.png -------------------------------------------------------------------------------- /frontend/dist/assets/040_右哼哼.27d8126d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/040_右哼哼.27d8126d.png -------------------------------------------------------------------------------- /frontend/dist/assets/041_鄙视.7e22890d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/041_鄙视.7e22890d.png -------------------------------------------------------------------------------- /frontend/dist/assets/042_委屈.a5caf83a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/042_委屈.a5caf83a.png -------------------------------------------------------------------------------- /frontend/dist/assets/043_快哭了.62b1b67c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/043_快哭了.62b1b67c.png -------------------------------------------------------------------------------- /frontend/dist/assets/044_阴险.3697222b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/044_阴险.3697222b.png -------------------------------------------------------------------------------- /frontend/dist/assets/045_亲亲.dfa6bbdf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/045_亲亲.dfa6bbdf.png -------------------------------------------------------------------------------- /frontend/dist/assets/046_可怜.634845ad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/046_可怜.634845ad.png -------------------------------------------------------------------------------- /frontend/dist/assets/047_笑脸.ab25a28c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/047_笑脸.ab25a28c.png -------------------------------------------------------------------------------- /frontend/dist/assets/048_生病.cd7aadb3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/048_生病.cd7aadb3.png -------------------------------------------------------------------------------- /frontend/dist/assets/049_脸红.9ecb5c1c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/049_脸红.9ecb5c1c.png -------------------------------------------------------------------------------- /frontend/dist/assets/050_破涕为笑.a3d2342d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/050_破涕为笑.a3d2342d.png -------------------------------------------------------------------------------- /frontend/dist/assets/051_恐惧.7af18313.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/051_恐惧.7af18313.png -------------------------------------------------------------------------------- /frontend/dist/assets/052_失望.87e0479b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/052_失望.87e0479b.png -------------------------------------------------------------------------------- /frontend/dist/assets/053_无语.6220ee7c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/053_无语.6220ee7c.png -------------------------------------------------------------------------------- /frontend/dist/assets/054_嘿哈.2116e692.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/054_嘿哈.2116e692.png -------------------------------------------------------------------------------- /frontend/dist/assets/055_捂脸.28f3a0d3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/055_捂脸.28f3a0d3.png -------------------------------------------------------------------------------- /frontend/dist/assets/056_奸笑.9cf99423.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/056_奸笑.9cf99423.png -------------------------------------------------------------------------------- /frontend/dist/assets/057_机智.93c3d05a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/057_机智.93c3d05a.png -------------------------------------------------------------------------------- /frontend/dist/assets/058_皱眉.efe09ed7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/058_皱眉.efe09ed7.png -------------------------------------------------------------------------------- /frontend/dist/assets/059_耶.a6bc3d2b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/059_耶.a6bc3d2b.png -------------------------------------------------------------------------------- /frontend/dist/assets/060_吃瓜.a2a158de.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/060_吃瓜.a2a158de.png -------------------------------------------------------------------------------- /frontend/dist/assets/061_加油.77c81f5b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/061_加油.77c81f5b.png -------------------------------------------------------------------------------- /frontend/dist/assets/062_汗.be95535c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/062_汗.be95535c.png -------------------------------------------------------------------------------- /frontend/dist/assets/063_天啊.a8355bf9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/063_天啊.a8355bf9.png -------------------------------------------------------------------------------- /frontend/dist/assets/064_Emm.787be530.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/064_Emm.787be530.png -------------------------------------------------------------------------------- /frontend/dist/assets/065_社会社会.a5f5902a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/065_社会社会.a5f5902a.png -------------------------------------------------------------------------------- /frontend/dist/assets/066_旺柴.7825a175.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/066_旺柴.7825a175.png -------------------------------------------------------------------------------- /frontend/dist/assets/067_好的.a9fffc64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/067_好的.a9fffc64.png -------------------------------------------------------------------------------- /frontend/dist/assets/068_打脸.560c8d1f.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/068_打脸.560c8d1f.png -------------------------------------------------------------------------------- /frontend/dist/assets/069_哇.74cdcc27.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/069_哇.74cdcc27.png -------------------------------------------------------------------------------- /frontend/dist/assets/070_翻白眼.ecb744e2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/070_翻白眼.ecb744e2.png -------------------------------------------------------------------------------- /frontend/dist/assets/071_666.281fb9b6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/071_666.281fb9b6.png -------------------------------------------------------------------------------- /frontend/dist/assets/072_让我看看.cee96a9f.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/072_让我看看.cee96a9f.png -------------------------------------------------------------------------------- /frontend/dist/assets/073_叹气.712846f3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/073_叹气.712846f3.png -------------------------------------------------------------------------------- /frontend/dist/assets/074_苦涩.4edf4f58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/074_苦涩.4edf4f58.png -------------------------------------------------------------------------------- /frontend/dist/assets/075_裂开.3fb97804.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/075_裂开.3fb97804.png -------------------------------------------------------------------------------- /frontend/dist/assets/076_嘴唇.59b9c0be.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/076_嘴唇.59b9c0be.png -------------------------------------------------------------------------------- /frontend/dist/assets/077_爱心.a09c823b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/077_爱心.a09c823b.png -------------------------------------------------------------------------------- /frontend/dist/assets/078_心碎.9a4fb37d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/078_心碎.9a4fb37d.png -------------------------------------------------------------------------------- /frontend/dist/assets/079_拥抱.e529a46b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/079_拥抱.e529a46b.png -------------------------------------------------------------------------------- /frontend/dist/assets/080_强.64fe98a8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/080_强.64fe98a8.png -------------------------------------------------------------------------------- /frontend/dist/assets/081_弱.07ddf3a5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/081_弱.07ddf3a5.png -------------------------------------------------------------------------------- /frontend/dist/assets/082_握手.aeb86265.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/082_握手.aeb86265.png -------------------------------------------------------------------------------- /frontend/dist/assets/083_胜利.e9ff0663.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/083_胜利.e9ff0663.png -------------------------------------------------------------------------------- /frontend/dist/assets/084_抱拳.0ae5f316.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/084_抱拳.0ae5f316.png -------------------------------------------------------------------------------- /frontend/dist/assets/085_勾引.a4c3a7b4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/085_勾引.a4c3a7b4.png -------------------------------------------------------------------------------- /frontend/dist/assets/086_拳头.2829fdbe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/086_拳头.2829fdbe.png -------------------------------------------------------------------------------- /frontend/dist/assets/087_OK.fc42db3d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/087_OK.fc42db3d.png -------------------------------------------------------------------------------- /frontend/dist/assets/088_合十.58cd6a1d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/088_合十.58cd6a1d.png -------------------------------------------------------------------------------- /frontend/dist/assets/089_啤酒.2d022508.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/089_啤酒.2d022508.png -------------------------------------------------------------------------------- /frontend/dist/assets/090_咖啡.8f40dc95.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/090_咖啡.8f40dc95.png -------------------------------------------------------------------------------- /frontend/dist/assets/091_蛋糕.f01a91ed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/091_蛋糕.f01a91ed.png -------------------------------------------------------------------------------- /frontend/dist/assets/093_凋谢.aa715ee6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/093_凋谢.aa715ee6.png -------------------------------------------------------------------------------- /frontend/dist/assets/095_炸弹.3dffd8e8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/095_炸弹.3dffd8e8.png -------------------------------------------------------------------------------- /frontend/dist/assets/096_便便.b0d5c50c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/096_便便.b0d5c50c.png -------------------------------------------------------------------------------- /frontend/dist/assets/097_月亮.47389834.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/097_月亮.47389834.png -------------------------------------------------------------------------------- /frontend/dist/assets/098_太阳.89c3d0ab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/098_太阳.89c3d0ab.png -------------------------------------------------------------------------------- /frontend/dist/assets/099_庆祝.2d9e8f8a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/099_庆祝.2d9e8f8a.png -------------------------------------------------------------------------------- /frontend/dist/assets/100_礼物.37ae5ec0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/100_礼物.37ae5ec0.png -------------------------------------------------------------------------------- /frontend/dist/assets/102_發.f43fee5c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/102_發.f43fee5c.png -------------------------------------------------------------------------------- /frontend/dist/assets/103_福.58c94555.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/103_福.58c94555.png -------------------------------------------------------------------------------- /frontend/dist/assets/104_烟花.61568e1e.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/104_烟花.61568e1e.png -------------------------------------------------------------------------------- /frontend/dist/assets/105_爆竹.35531687.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/105_爆竹.35531687.png -------------------------------------------------------------------------------- /frontend/dist/assets/106_猪头.7eb8ff1d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/106_猪头.7eb8ff1d.png -------------------------------------------------------------------------------- /frontend/dist/assets/107_跳跳.24101efa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/107_跳跳.24101efa.png -------------------------------------------------------------------------------- /frontend/dist/assets/108_发抖.3eabd306.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/108_发抖.3eabd306.png -------------------------------------------------------------------------------- /frontend/dist/assets/109_转圈.67669ca4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/109_转圈.67669ca4.png -------------------------------------------------------------------------------- /frontend/dist/assets/110_怄火.896c41d9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/110_怄火.896c41d9.png -------------------------------------------------------------------------------- /frontend/dist/assets/112_左哼哼.5425438e.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/112_左哼哼.5425438e.png -------------------------------------------------------------------------------- /frontend/dist/assets/113_哈欠.fe9c8521.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/113_哈欠.fe9c8521.png -------------------------------------------------------------------------------- /frontend/dist/assets/applet.ce6471b1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/applet.ce6471b1.png -------------------------------------------------------------------------------- /frontend/dist/assets/channels.33204285.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/channels.33204285.png -------------------------------------------------------------------------------- /frontend/dist/assets/channels_error.1d149df5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/channels_error.1d149df5.png -------------------------------------------------------------------------------- /frontend/dist/assets/emoji.b5d5ea11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/emoji.b5d5ea11.png -------------------------------------------------------------------------------- /frontend/dist/assets/errorImg.719abbab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/errorImg.719abbab.png -------------------------------------------------------------------------------- /frontend/dist/assets/favorite.1b38cfe5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/favorite.1b38cfe5.png -------------------------------------------------------------------------------- /frontend/dist/assets/index.00f6955e.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8";html{background-color:#f5f5f5}body{margin:0;font-family:-apple-system,SourceHanSans,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji"}@font-face{font-family:SourceHanSans;font-style:normal;font-weight:400;src:url(/assets/\601d\6e90\9ed1\4f53-Normal.df5ff3ec.otf) format("opentype");font-display:swap}#app{height:100vh;text-align:center}.ant-tooltip,.ant-progress,.ant-picker-calendar,.ant-tag,.ant-card,.ant-btn,.ant-input,.ant-menu,.ant-table,.ant-modal-title,.ant-select,.ant-tabs,.ant-form-item-label label,.ant-typography{font-family:-apple-system,SourceHanSans,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif}#App{height:100vh;text-align:center;display:flex;flex-direction:row}::-webkit-scrollbar{width:10px}::-webkit-scrollbar-thumb{--tw-border-opacity: 1;background-color:#b6b4b4cc;border-color:rgba(181,180,178,var(--tw-border-opacity));border-radius:9999px;border-width:1px}.wechat-UserList{width:275px;height:100%;background-color:#eae8e7;display:flex;flex-direction:column;align-items:center}.wechat-SearchBar{height:60px;width:100%;background-color:#f7f7f7;--wails-draggable:drag}.wechat-SearchBar-Input{height:26px;width:240px;background-color:#e6e6e6;margin-top:22px;--wails-draggable:no-drag}.wechat-UserList-Items{width:100%;height:calc(100% - 60px);overflow:auto}.wechat-UserItem{display:flex;justify-content:space-between;height:64px}.wechat-UserItem{transition:background-color .3s ease}.wechat-UserItem:hover{background-color:#dcdad9}.wechat-UserItem:focus{background-color:#c9c8c6}.selectedItem{background-color:#c9c8c6}.wechat-UserItem-content-Avatar{margin:5px}.wechat-UserItem-content{width:55%;display:flex;flex-direction:column;justify-content:space-between}.wechat-UserItem-content-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:left}.wechat-UserItem-content-name{color:#000;font-size:.9rem;margin-top:12px;font-weight:500}.wechat-UserItem-content-msg{color:#999;font-size:80%;margin-bottom:10px}.wechat-UserItem-date{color:#999;font-size:80%;margin-top:12px;margin-right:9px}.wechat-ContactItem{display:flex;justify-content:flex-start}.wechat-ContactItem-content{display:flex;flex-direction:column;flex-grow:1;justify-content:center}.wechat-ContactItem-content>.wechat-UserItem-content-text{margin-top:0;margin-left:.5rem}.wechat-UserList-Loading{margin-top:80px}.wechat-UserList-End{padding:15px;text-align:center;font-size:.85rem}.MessageModal{background-color:#fff;position:fixed;top:33%;left:50%;transform:translate(-50%,-50%);border-radius:5px;box-shadow:2px 2px 2px #a2a2a2}.MessageModal-Overlay{position:fixed;top:0;left:0;right:0;bottom:0}.MessageModal-content{display:flex;flex-direction:column;align-items:center;padding:20px}.MessageModal-button{margin-top:10px}.Setting-Modal{width:500px;background-color:#f5f5f5;position:fixed;top:35%;left:50%;transform:translate(-50%,-50%);padding:20px;border-radius:5px;box-shadow:2px 2px 2px #a2a2a2}.Setting-Modal-Overlay{position:fixed;top:0;left:0;right:0;bottom:0}.WechatInfoTable{display:flex;flex-direction:column;font-size:.9rem;border-top:1px solid #b9b9b9}.WechatInfoTable-column{display:flex;margin-top:8px;max-width:100%}.WechatInfoTable-column-tag{margin-left:8px;max-width:50%;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.checkUpdateButtom,.WechatInfoTable-column-tag-buttom{cursor:pointer}.Setting-Modal-export-button{margin-top:10px}.Setting-Modal-button{position:absolute;top:10px;right:10px;padding:5px 10px;border:none;cursor:pointer}.Setting-Modal-confirm{background-color:#f5f5f5;position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);padding:20px;border-radius:5px;box-shadow:2px 2px 2px #a2a2a2}.Setting-Modal-confirm-Overlay{position:fixed;top:0;left:0;right:0;bottom:0}.Setting-Modal-confirm-title{text-align:center}.Setting-Modal-confirm-buttons{display:flex;gap:10px}.Setting-Modal-updateInfoWindow{background-color:#fff;position:fixed;top:33%;left:50%;transform:translate(-50%,-50%);border-radius:5px;box-shadow:2px 2px 2px #a2a2a2}.Setting-Modal-updateInfoWindow-Overlay{position:fixed;top:0;left:0;right:0;bottom:0}.Setting-Modal-updateInfoWindow-content{display:flex;flex-direction:column;align-items:center;padding:20px}.Setting-Modal-updateInfoWindow-button{margin-top:10px}.Setting-Modal-Select{display:flex;gap:10px;align-items:center;margin-bottom:8px}.Setting-Modal-Select-Text{font-size:1rem;font-weight:900}.UserSwitch-Modal{background-color:#fff;position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);padding:20px;border-radius:5px;box-shadow:2px 2px 2px #a2a2a2}.UserSwitch-Modal-Overlay{position:fixed;top:0;left:0;right:0;bottom:0}.UserSwitch-Modal-button{display:flex;justify-content:end}.UserSwitch-Modal-title{text-align:center;font-weight:900;margin-bottom:20px}.UserSwitch-Modal-buttons{margin-top:10px;display:flex;gap:10px}.UserSwitch-Modal-content{display:flex;flex-direction:column;gap:10px;max-height:300px;overflow:auto}.UserInfoItem{display:flex;background-color:#f7f7f7;width:500px;border-radius:5px;padding:8px;gap:10px}.UserInfoItem:hover{background-color:#ebebeb}.UserInfoItem-Info{display:flex;flex-direction:column;flex-grow:1;justify-content:space-between}.UserInfoItem-Info-part1{display:flex;justify-content:space-between}.UserInfoItem-Info-Nickname{font-size:1rem;font-weight:800}.UserInfoItem-Info-acountName{font-size:.9rem}.UserInfoItem-Info-isSelect{font-size:.8rem}.UserInfoItem-Info-isSelect-Dot{color:#56d54a;margin-right:5px}.UserInfoNull{display:flex;width:500px;height:100px;border-radius:5px;padding:8px;gap:10px;justify-content:center}.UserInfoItem-Info-null{text-align:center}.AboutModal-Modal{background-color:#fff;position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);padding:20px;border-radius:5px;box-shadow:2px 2px 2px #a2a2a2}.AboutModal-Modal-Overlay{position:fixed;top:0;left:0;right:0;bottom:0}.AboutModal-Modal-button{display:flex;justify-content:end}.AboutModal-Modal-body{height:350px;width:550px;display:flex;justify-items:center;align-items:start;flex-direction:column}.AboutModal-Modal-body{background-image:linear-gradient(rgba(255,255,255,.6),rgba(255,255,255,.6)),url(/assets/logo.8df6944e.png);background-size:contain;background-position:center;background-repeat:no-repeat}.AboutModal-title{font-size:1.5rem}.AboutModal-content{font-size:.9rem}.AboutModal-Apache{color:#0751f2;cursor:pointer}.AboutModal-home-page{margin-top:40px;width:100%;display:flex;justify-content:space-around}.AboutModal-home-page-item{display:flex;flex-direction:column;align-items:center;cursor:pointer;gap:5px;transition:transform .3s ease,box-shadow .3s ease}.AboutModal-home-page-item:hover{transform:scale(1.1)}.AboutModal-home-page-icon{font-size:3.5rem}.AboutModal-bili-icon{color:#00aeec}.AboutModal-wechat-icon{color:#07c160}.wechat-menu{width:52px;justify-content:flex-start;height:100%;background-color:#2e2e2e;--wails-draggable:drag}.wechat-menu-items{margin-top:30px;display:flex;flex-direction:column;gap:30px}.wechat-menu-item{margin-left:auto;margin-right:auto;--wails-draggable:no-drag}.wechat-menu-item-icon{background-color:#2e2e2e}.wechat-menu-selectedColor{color:#07c160}.ChatUi{background-color:#f3f3f3;height:calc(100% - 60px)}.ChatUiBar{display:flex;justify-content:space-between;align-items:center;padding:4% 2% 2%;border-bottom:.5px solid #dddbdb;background-color:#fff}.ChatUiBar--Text{flex:1;text-align:center;margin:1%}.ChatUiBar--Btn>.Icon{height:1.5em;width:1.5em}#scrollableDiv{height:100%;overflow:auto;display:flex;flex-direction:column-reverse}.MessageBubble{display:flex;flex-direction:column;justify-content:space-between;align-items:center;padding:10px}.MessageBubble>.Time{text-align:center;font-size:.8rem;margin-bottom:3px;background-color:#c9c8c6;color:#fff;padding:2px 3px;border-radius:4px}.MessageBubble-content-left{align-self:flex-start;display:flex;max-width:60%}.MessageBubble-content-right{align-self:flex-end;display:flex;max-width:60%}.Bubble-right>.Bubble{background-color:#95ec69}.MessageBubble-Avatar-left{margin-right:2px}.MessageBubble-Avatar-right{margin-left:2px}.Bubble-left{display:flex;flex-direction:column;align-items:flex-start;flex:1 1;max-width:100%}.MessageBubble-Name-left{color:#383838;font-size:small;margin-bottom:1px}.Bubble-right{display:flex;flex-direction:column;align-items:flex-end;flex:1 1;max-width:100%}.MessageBubble-Name-right{color:#383838;font-size:small;margin-bottom:1px}.CardMessageText{text-align:start;max-width:100%;word-break:break-all;padding:0}.MessageText-emoji{width:1.2rem;height:1.2rem;vertical-align:text-bottom}div.Bubble-right>div.CardMessageText{background-color:#95ec69}.System-Text{font-size:.8rem;color:#686868}.CardMessage{background-color:#fff;border-radius:3%;height:130px;width:260px;flex:1;display:flex;flex-direction:column;text-align:start;padding:12px 12px 5px;cursor:pointer}.CardMessage:hover{background-color:#ececec}.CardMessage-Content{display:flex;flex-direction:row;justify-content:space-between;max-width:100%;max-height:70%;padding-bottom:5px}.CardMessage-Title{display:-webkit-box;-webkit-box-orient:vertical;overflow:hidden;-webkit-line-clamp:2;word-wrap:break-word;line-height:1.5;max-height:3em;text-overflow:ellipsis}.CardMessage-Desc{display:-webkit-box;-webkit-box-orient:vertical;overflow:hidden;-webkit-line-clamp:3;word-wrap:break-word;text-overflow:ellipsis;font-size:.75rem;max-width:80%;padding-top:5px;color:#949292}.CardMessage-Desc-Span{color:#bd0b0b}.CardMessageLink-Line{height:1px;width:100%;background-color:#f6f4f4}.CardMessageLink-Line-None{display:none}.CardMessageLink-Name{padding-top:4px;font-size:.65rem}.MessageRefer-Right{display:flex;flex-direction:column;align-items:flex-end}.MessageRefer-Left{display:flex;flex-direction:column;align-items:flex-start}.MessageRefer-Content{margin-top:6px;font-size:.8rem;color:#595959;background-color:#c9c8c6;border-radius:2px;padding:4px}.MessageRefer-Content-Text{max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:left}.CardMessageFile-Icon{font-size:45px;color:#595959}.CardMessage-Menu{font-size:.8rem}.CardMessageAudio{max-width:100%}.MessageVoipCard{display:flex;justify-items:center;align-items:center;border-radius:8px;padding:10px;gap:8px;user-select:none}div.Bubble-right>div.MessageVoipCard{background-color:#95ec69;flex-direction:row-reverse}div.Bubble-left>div.MessageVoipCard{background-color:#fff}.MessageTransferCard{user-select:none}.MessageTransferCard-Up{width:240px;height:80px;border-radius:8px 8px 0 0;background-color:#fa9c3e;display:flex;justify-items:center;align-items:center;gap:10px}.MessageTransferCard-Up>.TransferIcon{margin-left:15px;width:40px;height:40px;color:#fff;font-size:30px;border-radius:50%;border:2px solid #ffffff}.MessageTransferCard-Up>.TransferContent{display:flex;flex-direction:column;justify-items:start;align-items:flex-start;color:#fff}.MessageTransferCard-Down{width:240px;height:20px;background-color:#fff;border-radius:0 0 8px 8px;display:flex;align-items:center}.MessageTransferCard-Down>p{margin-left:15px;font-size:10px;color:#595959}.MessageVisitCard{user-select:none}.MessageVisitCard>.content{width:240px;height:80px;background-color:#fff;border-radius:8px 8px 0 0;display:flex;align-items:center;gap:10px}.MessageVisitCard>.content>.wechat-Avatar{margin-left:10px}.MessageVisitCard>.tag{width:240px;height:20px;background-color:#fff;border-radius:0 0 8px 8px;border-top:1px solid rgb(234,234,234);display:flex;align-items:center}.MessageVisitCard>.tag>p{margin-left:15px;font-size:12px;color:#595959}.MessageChannles{max-height:210px;max-width:280px;object-fit:contain;position:relative;user-select:none}.MessageChannles>img{height:100%;width:100%;object-fit:contain;border-radius:8px}.MessageChannles>.NickName{display:flex;align-items:center;gap:5px;left:5px;bottom:5px;position:absolute;font-size:12px}.MessageChannles>.NickName>img{height:1.2rem;width:1.2rem}.MessageChannles>.NickName>div{color:#fff;height:14px;width:120px;overflow:hidden;text-align:start}.MessageChannles>.Tips{position:absolute;top:5px;left:5px;color:#fff;font-size:12px;background-color:#4f4f4f80}.MessageMusic{cursor:pointer}.MessageMusic-Up{width:280px;height:80px;border-radius:8px 8px 0 0;background-color:#0fbe73;display:flex;justify-content:start;align-items:center;gap:5px}.MessageMusic-Up>img{height:100%;object-fit:contain;border-radius:8px 0 0}.MessageMusic-Up>.content{color:#ece7e4;display:flex;flex-direction:column;align-items:start;width:160px}.MessageMusic-Up>.content>.title{display:-webkit-box;-webkit-box-orient:vertical;overflow:hidden;-webkit-line-clamp:3;word-wrap:break-word;line-height:1.5;max-height:3em;text-overflow:ellipsis;text-align:start}.MessageMusic-Up>.icon{height:40px;width:40px;display:flex;align-items:center;justify-content:center;font-size:1.5rem;color:#ece7e4}.MessageMusic-Down{width:280px;height:20px;background-color:#fff;border-radius:0 0 8px 8px;display:flex;align-items:center}.MessageMusic-Down>p{margin-left:15px;font-size:12px;color:#595959}.MessageTingListen{cursor:pointer}.MessageTingListen-Up{width:280px;height:80px;border-radius:8px;background-color:#a29d97;display:flex;justify-content:start;align-items:center;gap:5px}.MessageTingListen-Up>img{height:100%;object-fit:contain;border-radius:8px 0 0 8px}.MessageTingListen-Up>.content{color:#ece7e4;display:flex;flex-direction:column;align-items:start;width:160px}.MessageTingListen-Up>.content>.title{display:-webkit-box;-webkit-box-orient:vertical;overflow:hidden;-webkit-line-clamp:3;word-wrap:break-word;line-height:1.5;max-height:3em;text-overflow:ellipsis;text-align:start}.MessageTingListen-Up>.icon{height:40px;width:40px;display:flex;align-items:center;justify-content:center;font-size:1.5rem;color:#ece7e4}.MessageApplet{display:flex;flex-direction:column;align-items:start;padding:10px;background-color:#fff;border-radius:8px;gap:4px;cursor:pointer}.MessageApplet:hover{background-color:#ececec}.MessageApplet>.appname{font-size:12px;color:#949292}.MessageApplet>.title{font-size:15px;width:190px;height:22px;overflow:hidden;text-align:start}.MessageApplet>.content{width:190px;height:190px}.MessageApplet>.content>img{width:100%;height:100%;object-fit:cover}.MessageApplet>.line{width:100%;height:1px;background-color:#bababa;margin-top:8px;margin-bottom:6px}.MessageApplet>.icon{display:flex;align-items:center;gap:5px}.MessageApplet>.icon>img{width:14px;height:14px;object-fit:contain}.MessageApplet>.icon>div{font-size:12px;color:#bababa}.MessageLocation{display:flex;flex-direction:column;align-items:start;background-color:#fff;border-radius:8px;cursor:pointer;padding-top:5px}.MessageLocation>.name{font-size:15px;width:230px;text-align:start}.MessageLocation>.label{margin-top:5px;font-size:12px;color:#949292;width:230px;text-align:start}.MessageLocation>div{margin-left:10px}.MessageLocation>img{width:250px;height:180px;object-fit:cover;border-radius:0 0 8px 8px}div.Bubble-right>div.MessageVoice{background-color:#95ec69}div.Bubble-left>div.MessageVoice{background-color:#fff}.MessageVoice{border-radius:8px;padding:20px}.audioPlayer>.controls{display:flex;align-items:center;gap:8px}.audioPlayer>.controls>.play{cursor:pointer}.audioPlayer>.controls>.time{font-size:14px;user-select:none}.progress-slider{width:120px;height:6px;background:#E0F2E9;border-radius:2px;cursor:pointer}.progress-slider-track-0{height:6px;background:#07C060;border-radius:2px}.audioPlayer>.controls>.volume{position:relative}.audioPlayer>.controls>.volume>.btn{cursor:pointer}.audioPlayer>.controls>.volume>.progress{position:absolute;transform:rotate(-90deg)}.audioPlayer>.controls>.save{cursor:pointer}.szh-menu{margin:0;padding:0;list-style:none;box-sizing:border-box;width:max-content;z-index:100;border:1px solid rgba(0,0,0,.1);background-color:#fff}.szh-menu:focus{outline:none}.szh-menu__arrow{box-sizing:border-box;width:.75rem;height:.75rem;background-color:#fff;border:1px solid transparent;border-left-color:#0000001a;border-top-color:#0000001a;z-index:-1}.szh-menu__arrow--dir-left{right:-.375rem;transform:translateY(-50%) rotate(135deg)}.szh-menu__arrow--dir-right{left:-.375rem;transform:translateY(-50%) rotate(-45deg)}.szh-menu__arrow--dir-top{bottom:-.375rem;transform:translate(-50%) rotate(-135deg)}.szh-menu__arrow--dir-bottom{top:-.375rem;transform:translate(-50%) rotate(45deg)}.szh-menu__item{cursor:pointer}.szh-menu__item:focus{outline:none}.szh-menu__item--hover{background-color:#ebebeb}.szh-menu__item--focusable{cursor:default;background-color:inherit}.szh-menu__item--disabled{cursor:default;color:#aaa}.szh-menu__group{box-sizing:border-box}.szh-menu__radio-group{margin:0;padding:0;list-style:none}.szh-menu__divider{height:1px;margin:.5rem 0;background-color:#0000001f}.szh-menu-button{box-sizing:border-box}.szh-menu{user-select:none;color:#212529;border:none;border-radius:.25rem;box-shadow:0 3px 7px #0002,0 .6px 2px #0000001a;min-width:10rem;padding:.5rem 0}.szh-menu__item{display:flex;align-items:center;position:relative;padding:.375rem 1.5rem}.szh-menu-container--itemTransition .szh-menu__item{transition-property:background-color,color;transition-duration:.15s;transition-timing-function:ease-in-out}.szh-menu__item--type-radio{padding-left:2.2rem}.szh-menu__item--type-radio:before{content:"\25cb";position:absolute;left:.8rem;top:.55rem;font-size:.8rem}.szh-menu__item--type-radio.szh-menu__item--checked:before{content:"\25cf"}.szh-menu__item--type-checkbox{padding-left:2.2rem}.szh-menu__item--type-checkbox:before{position:absolute;left:.8rem}.szh-menu__item--type-checkbox.szh-menu__item--checked:before{content:"\2714"}.szh-menu__submenu>.szh-menu__item{padding-right:2.5rem}.szh-menu__submenu>.szh-menu__item:after{content:"\276f";position:absolute;right:1rem}.szh-menu__header{color:#888;font-size:.8rem;padding:.2rem 1.5rem;text-transform:uppercase}.MediaView-Modal{background-color:#fff;position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);padding:20px;border-radius:5px;box-shadow:2px 2px 2px #a2a2a2}.MediaView-Modal-Overlay{position:fixed;top:0;left:0;right:0;bottom:0}.MediaViewModal-body{height:100vh;width:100vw}.MediaViewModal-body-Bar{width:100%;height:30px}.MediaViewModal-Bar-body{height:100%;display:flex;justify-content:space-between;align-items:center;user-select:none}.MediaViewModal-Bar-func{display:flex;cursor:pointer}.MediaViewModal-Bar-Icon{height:30px;width:40px;display:flex;justify-content:center;align-items:center;color:#4f4f4f}.MediaViewModal-Bar-Icon:hover{background-color:#ebebeb}.MediaViewModal-Bar-Icon-disable{height:30px;width:40px;display:flex;justify-content:center;align-items:center;color:#c5c4c4}.MediaViewModal-body-content{height:calc(100% - 30px);width:100%;display:flex;flex-direction:row}.MediaViewModal-body-content-img{width:calc(100% - 200px);height:100%;display:flex;justify-content:center;align-items:center}.MediaView-thumb-div{position:relative}.MediaView-thumb{object-fit:"contain";border-radius:2%;cursor:pointer}.MediaView-thumb-mask{position:absolute;inset:0;top:0;left:0;display:flex;flex-direction:column;justify-content:center;align-items:center;width:100%;height:100%}.MediaView-thumb-mask-icon-div{border-radius:50%;width:2rem;height:2rem;background-color:#4f4f4f80}.MediaView-thumb-mask-icon{width:2rem;font-size:2rem;color:#fff}.MediaViewModal-body-content-img-display{width:98%;height:98%;position:relative;overflow:hidden}.MediaViewvideo{width:100%;height:100%;object-fit:contain;position:relative}.MediaViewvideo-video-wrap{width:100%;height:100%;display:flex;justify-content:center;align-items:center}.MediaViewvideo-video{max-width:100%;max-height:100%;object-fit:contain}.MediaView-Player-controls{position:absolute;width:90%;height:40px;top:calc(100% - 60px);left:5%;border-radius:15px;background-color:#00000080;color:#fff;display:flex;flex-direction:row;align-items:center;gap:10px;cursor:pointer;visibility:hidden}.MediaView-Player-controls-time{user-select:none}.MediaView-Player-controls-playpuase{margin-left:20px}.MediaView-Player-controls-progress{height:3px;flex-grow:1;cursor:pointer}.MediaView-Player-controls-sound{margin-right:20px;position:"relative",}.MediaView-Player-sound-ctrl{position:absolute;width:120px;height:20px;top:calc(100% - 90px);left:calc(95% - 120px);border-radius:15px;background-color:#00000080;display:flex;align-items:center;justify-content:center;cursor:pointer;visibility:hidden}.MediaView-Player-sound-ctrl-progress{width:100px;height:2px;cursor:pointer}.MediaView-Player-Puase-mask{position:absolute;inset:0;top:50%;left:50%;border-radius:50%;width:2rem;height:2rem;background-color:#4f4f4f80;visibility:hidden}.MediaView-Player-Puase-mask-icon{width:2rem;font-size:2rem;color:#fff}.MediaViewModal-body-img{width:100%;height:100%;object-fit:contain}.MediaViewModal-body-img-mask{position:absolute;inset:0;top:0;left:0;display:flex;flex-direction:column;gap:10px;justify-content:center;align-items:center;color:#fff;background-color:#00000080;cursor:pointer;opacity:.6;width:100%;height:100%;user-select:none}.MediaViewModal-body-img-up{position:absolute;width:2rem;height:2rem;font-size:1rem;top:50%;left:10px;border-radius:50%;color:#fff;background-color:#4f4f4f;opacity:.7;display:flex;justify-content:center;align-items:center;cursor:pointer;visibility:hidden}.MediaViewModal-body-img-down{position:absolute;width:2rem;height:2rem;font-size:1rem;top:50%;left:calc(100% - 2rem - 10px);border-radius:50%;color:#fff;background-color:#4f4f4f;opacity:.7;display:flex;justify-content:center;align-items:center;cursor:pointer;visibility:hidden}.MediaViewModal-body-img-up-disable,.MediaViewModal-body-img-down-disable{color:#6a6a6a}.MediaViewModal-body-img-mask-icon{font-size:3rem}.MediaViewModal-body-img-tip{position:absolute;border-radius:5px;padding:.5rem;color:#fff;background-color:#000000b3;top:30%;left:calc(50% - 3rem);visibility:hidden}.MediaViewModal-body-content-list{width:200px;height:100%}#MediaViewList-scrollableDiv{height:100%;overflow:auto}.MediaViewList-InfiniteScroll{display:flex;flex-wrap:wrap;gap:3px;align-content:flex-start}.MediaViewListThumb{height:58px;width:58px;box-sizing:border-box;transition:border .3s ease;position:relative}.MediaViewListThumb-Date{height:30px;width:100%;display:flex;justify-content:start;align-items:center;font-size:.8rem;user-select:none}.MediaViewListThumb-Blank{height:58px;width:58px}.MediaViewListThumb:hover,.MediaViewListThumb-selected{border:3px solid #07C160}.MediaViewListThumb-img{width:100%;height:100%;object-fit:cover}.MediaViewListThumb-img-mask{position:absolute;width:100%;height:100%;font-size:.8rem;top:calc(100% - 1.2rem);left:5px;color:#fff}.SearchInputIcon{background-color:#f5f4f4;display:flex;align-items:center}.SearchInputIcon-second{font-size:12px;margin:0 3px;cursor:default}#RecordsUiscrollableDiv{height:400px;overflow:auto;display:flex;flex-direction:column-reverse}#RecordsUiscrollableDiv>div>div>.MessageBubble:hover{background-color:#dcdad9}.RecordsUi-MessageBubble>.MessageBubble-content-left{max-width:95%}.RecordsUi-LoadingContent{display:flex;flex-direction:column;align-items:center}.RecordsUi-LoadingContent-noData{height:350px}.CalendarChoose{height:400px}.CalendarLoading{height:400px;display:flex;flex-direction:column;align-items:center;justify-content:center}.ChattingRecords-Modal{width:500px;background-color:#f5f5f5;position:fixed;top:45%;left:50%;transform:translate(-50%,-50%);padding:20px;border-radius:5px;box-shadow:2px 2px 2px #a2a2a2}.ChattingRecords-Modal-Overlay{position:fixed;top:0;left:0;right:0;bottom:0}.ChattingRecords-Modal-Bar-Tag{display:flex;flex-direction:row;justify-content:space-around;margin-top:5px;margin-bottom:5px;cursor:default;color:#576b95;font-size:.9rem}.ChattingRecords-Modal-Bar-Top{display:flex;flex-direction:row;justify-content:space-between;padding:0 5px 5px}.NotMessageContent{height:400px;display:flex;justify-content:center;align-items:center}.NotMessageContent-keyword{color:#07c160}.ChattingRecords-ChatRoomUserList{width:260px;height:300px;background-color:#f5f5f5;position:fixed;top:48%;left:70%;transform:translate(-50%,-50%);padding:20px;border-radius:5px;box-shadow:2px 2px 2px #a2a2a2}.ChattingRecords-ChatRoomUserList-Overlay{position:fixed;top:0;left:0;right:0;bottom:0}.ChattingRecords-ChatRoomUserList>.wechat-SearchInput{margin-bottom:10px}.ChattingRecords-ChatRoomUserList-List{overflow:auto;height:90%;display:flex;flex-direction:column;gap:5px}.ChattingRecords-ChatRoomUserList-User{display:flex;align-items:center}.ChattingRecords-ChatRoomUserList-User:hover{background-color:#dcdad9}.ChattingRecords-ChatRoomUserList-Name{padding-left:8px;font-size:.85rem}.ChattingRecords-ChatRoomUserList-Loading{padding:20px;text-align:center}.ChattingRecords-ChatRoomUserList-List-End{padding:5px;text-align:center;font-size:.85rem}.LoadingContent{height:400px;display:flex;flex-direction:column;align-items:center;justify-content:center}.ConfirmModal-Modal{background-color:#fff;position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);padding:20px;border-radius:5px;box-shadow:2px 2px 2px #a2a2a2}.ConfirmModal-Modal-Overlay{position:fixed;top:0;left:0;right:0;bottom:0}.ConfirmModal-Modal-title{text-align:center;font-weight:900}.ConfirmModal-Modal-buttons{margin-top:10px;display:flex;gap:10px}.wechat-UserDialog{flex-grow:1;background-color:#f5f5f5;flex:1;height:100%;display:flex;flex-direction:column;justify-content:space-between;width:calc(100% - 327px)}.wechat-UserDialog-Bar{height:60px;background-color:#f5f5f5;border-left:1px solid #E6E6E6;border-bottom:1px solid #E6E6E6;display:flex;flex-direction:row;justify-content:space-between;--wails-draggable:drag}.wechat-UserDialog-Bar-button-block{display:flex;flex-direction:column;align-items:end}.wechat-UserDialog-Bar-button-list{display:flex;flex-direction:row;--wails-draggable:no-drag}.wechat-UserDialog-Bar-button{width:34px;height:22px;color:#131212}.wechat-UserDialog-Bar-button-icon{width:12px;height:10px}.wechat-UserDialog-Bar-button-list-v2{margin-top:10px;display:flex;flex-direction:row;--wails-draggable:no-drag;gap:10px;align-items:center;justify-content:space-around;margin-right:5px}.wechat-UserDialog-Bar-button-list-v2 img{height:19px;width:19px}.wechat-UserDialog-Bar-button-list-v2 img:hover{transform:scale(1.08)}.wechat-UserDialog-Bar-button-list-v2 .button-icon{transition:transform .3s ease}.wechat-UserDialog-Bar-button-list-v2 .button-icon :hover{transform:scale(1.08)}.wechat-UserDialog-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:left}.wechat-UserDialog-name{color:#000;font-size:large;margin-top:20px;margin-left:16px;--wails-draggable:no-drag}.FavoriteModal-ChildrenWrapper{display:flex}.FavoriteModal-Modal{background-color:#fff;position:fixed;top:60px;right:10px;padding:20px;border-radius:5px;box-shadow:2px 2px 2px #a2a2a2}.FavoriteModal-Modal-Overlay{position:fixed;top:0;left:0;right:0;bottom:0}.FavoriteModal-Modal-body{display:flex;flex-direction:column;align-items:start;gap:10px;width:280px}.FavoriteModal-Modal-title{text-align:start;font-weight:700}.FavoriteModal-Modal-buttons{width:100%;display:flex;justify-content:flex-end;gap:10px}.FavoriteModal-Modal-input{display:flex;gap:10px}.FavoriteModal-Modal-input>input{width:230px}.FavoriteListModal-ChildrenWrapper{display:flex}.FavoriteListModal-Modal{background-color:#fff;position:fixed;top:60px;right:10px;padding:20px;border-radius:5px;box-shadow:2px 2px 2px #a2a2a2}.FavoriteListModal-Modal-Overlay{position:fixed;top:0;left:0;right:0;bottom:0}.FavoriteListModal-Modal-body{display:flex;flex-direction:column;align-items:start;gap:10px;width:220px}.FavoriteListModal-Modal-title{text-align:start;font-weight:700}.FavoriteListModal-Modal-list{height:300px;overflow-x:auto;gap:5px}.FavoriteList-item{cursor:pointer;user-select:none}.FavoriteList-item-text{padding-left:5px;padding-right:5px;width:210px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.FavoriteList-item:hover{background-color:#dcdad9;border-radius:2px}.FavoriteListModal-Loading{margin-top:30px;margin-left:78px}.FavoriteListModal-Modal-list-End{width:210px;text-align:center;font-size:.9rem}.ExportModal{background-color:#fff;position:fixed;top:33%;left:50%;transform:translate(-50%,-50%);border-radius:5px;box-shadow:2px 2px 2px #a2a2a2}.ExportModal-Overlay{position:fixed;top:0;left:0;right:0;bottom:0}.ExportModal-content{display:flex;flex-direction:column;align-items:center;padding:20px}.ExportModal-button{margin-top:10px}.tour-content-title{font-size:1.1rem;font-weight:1000}.tour-content-text{margin-top:10px;font-size:.9rem;width:300px} 2 | -------------------------------------------------------------------------------- /frontend/dist/assets/logo.8df6944e.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/logo.8df6944e.png -------------------------------------------------------------------------------- /frontend/dist/assets/map.b91d2cda.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/map.b91d2cda.png -------------------------------------------------------------------------------- /frontend/dist/assets/music_note.02e237d9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/music_note.02e237d9.png -------------------------------------------------------------------------------- /frontend/dist/assets/qq_music.b548e6a1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/qq_music.b548e6a1.png -------------------------------------------------------------------------------- /frontend/dist/assets/red_packet.704bd303.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/red_packet.704bd303.png -------------------------------------------------------------------------------- /frontend/dist/assets/share.3d7abf39.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/share.3d7abf39.png -------------------------------------------------------------------------------- /frontend/dist/assets/思源黑体-Normal.df5ff3ec.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/frontend/dist/assets/思源黑体-Normal.df5ff3ec.otf -------------------------------------------------------------------------------- /frontend/dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | wechatDataBackup 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module wechatDataBackup 2 | 3 | go 1.21.0 4 | 5 | require ( 6 | github.com/beevik/etree v1.3.0 7 | github.com/git-jiadong/go-lame v0.0.0-20241215065806-397455857191 8 | github.com/git-jiadong/go-silk v0.0.0-20241215085148-b8734e30c24b 9 | github.com/mattn/go-sqlite3 v1.14.22 10 | github.com/pierrec/lz4 v2.6.1+incompatible 11 | github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 12 | github.com/shirou/gopsutil v2.21.11+incompatible 13 | github.com/shirou/gopsutil/v3 v3.24.2 14 | github.com/spf13/viper v1.18.2 15 | github.com/wailsapp/wails/v2 v2.9.1 16 | golang.org/x/net v0.25.0 17 | golang.org/x/sys v0.20.0 18 | google.golang.org/protobuf v1.31.0 19 | gopkg.in/natefinch/lumberjack.v2 v2.2.1 20 | ) 21 | 22 | require ( 23 | github.com/bep/debounce v1.2.1 // indirect 24 | github.com/fsnotify/fsnotify v1.7.0 // indirect 25 | github.com/go-ole/go-ole v1.2.6 // indirect 26 | github.com/godbus/dbus/v5 v5.1.0 // indirect 27 | github.com/google/uuid v1.4.0 // indirect 28 | github.com/hashicorp/hcl v1.0.0 // indirect 29 | github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect 30 | github.com/labstack/echo/v4 v4.10.2 // indirect 31 | github.com/labstack/gommon v0.4.0 // indirect 32 | github.com/leaanthony/go-ansi-parser v1.6.0 // indirect 33 | github.com/leaanthony/gosod v1.0.3 // indirect 34 | github.com/leaanthony/slicer v1.6.0 // indirect 35 | github.com/leaanthony/u v1.1.0 // indirect 36 | github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect 37 | github.com/magiconair/properties v1.8.7 // indirect 38 | github.com/mattn/go-colorable v0.1.13 // indirect 39 | github.com/mattn/go-isatty v0.0.19 // indirect 40 | github.com/mitchellh/mapstructure v1.5.0 // indirect 41 | github.com/pelletier/go-toml/v2 v2.1.0 // indirect 42 | github.com/pkg/errors v0.9.1 // indirect 43 | github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect 44 | github.com/rivo/uniseg v0.4.4 // indirect 45 | github.com/sagikazarmark/locafero v0.4.0 // indirect 46 | github.com/sagikazarmark/slog-shim v0.1.0 // indirect 47 | github.com/samber/lo v1.38.1 // indirect 48 | github.com/shoenig/go-m1cpu v0.1.6 // indirect 49 | github.com/sourcegraph/conc v0.3.0 // indirect 50 | github.com/spf13/afero v1.11.0 // indirect 51 | github.com/spf13/cast v1.6.0 // indirect 52 | github.com/spf13/pflag v1.0.5 // indirect 53 | github.com/subosito/gotenv v1.6.0 // indirect 54 | github.com/tklauser/go-sysconf v0.3.13 // indirect 55 | github.com/tklauser/numcpus v0.7.0 // indirect 56 | github.com/tkrajina/go-reflector v0.5.6 // indirect 57 | github.com/valyala/bytebufferpool v1.0.0 // indirect 58 | github.com/valyala/fasttemplate v1.2.2 // indirect 59 | github.com/wailsapp/go-webview2 v1.0.10 // indirect 60 | github.com/wailsapp/mimetype v1.4.1 // indirect 61 | github.com/yusufpapurcu/wmi v1.2.4 // indirect 62 | go.uber.org/atomic v1.9.0 // indirect 63 | go.uber.org/multierr v1.9.0 // indirect 64 | golang.org/x/crypto v0.23.0 // indirect 65 | golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect 66 | golang.org/x/text v0.15.0 // indirect 67 | gopkg.in/ini.v1 v1.67.0 // indirect 68 | gopkg.in/yaml.v3 v3.0.1 // indirect 69 | ) 70 | 71 | // replace github.com/wailsapp/wails/v2 v2.8.0 => C:\Users\HAL\go\pkg\mod 72 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "embed" 5 | "io" 6 | "log" 7 | "os" 8 | 9 | "github.com/wailsapp/wails/v2" 10 | "github.com/wailsapp/wails/v2/pkg/options" 11 | "github.com/wailsapp/wails/v2/pkg/options/assetserver" 12 | "gopkg.in/natefinch/lumberjack.v2" 13 | ) 14 | 15 | //go:embed all:frontend/dist 16 | var assets embed.FS 17 | 18 | func init() { 19 | // log output format 20 | log.SetFlags(log.Ldate | log.Lmicroseconds | log.Lshortfile) 21 | } 22 | 23 | func main() { 24 | logJack := &lumberjack.Logger{ 25 | Filename: "./app.log", 26 | MaxSize: 5, 27 | MaxBackups: 1, 28 | MaxAge: 30, 29 | Compress: false, 30 | } 31 | defer logJack.Close() 32 | 33 | multiWriter := io.MultiWriter(logJack, os.Stdout) 34 | // 设置日志输出目标为文件 35 | log.SetOutput(multiWriter) 36 | log.Println("====================== wechatDataBackup ======================") 37 | // Create an instance of the app structure 38 | app := NewApp() 39 | 40 | // Create application with options 41 | err := wails.Run(&options.App{ 42 | Title: "wechatDataBackup", 43 | MinWidth: 800, 44 | MinHeight: 600, 45 | Width: 1024, 46 | Height: 768, 47 | AssetServer: &assetserver.Options{ 48 | Assets: assets, 49 | Handler: app.FLoader, 50 | }, 51 | BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1}, 52 | OnStartup: app.startup, 53 | OnBeforeClose: app.beforeClose, 54 | OnShutdown: app.shutdown, 55 | Bind: []interface{}{ 56 | app, 57 | }, 58 | Frameless: true, 59 | }) 60 | 61 | if err != nil { 62 | log.Println("Error:", err.Error()) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /pkg/utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "crypto/md5" 5 | "encoding/hex" 6 | "errors" 7 | "fmt" 8 | "io" 9 | "log" 10 | "os" 11 | "os/exec" 12 | "path/filepath" 13 | "regexp" 14 | "strings" 15 | 16 | "github.com/pkg/browser" 17 | "github.com/shirou/gopsutil/v3/disk" 18 | "golang.org/x/net/html" 19 | "golang.org/x/sys/windows/registry" 20 | ) 21 | 22 | type PathStat struct { 23 | Path string `json:"path"` 24 | Total uint64 `json:"total"` 25 | Free uint64 `json:"free"` 26 | Used uint64 `json:"used"` 27 | UsedPercent float64 `json:"usedPercent"` 28 | } 29 | 30 | func getDefaultProgram(fileExtension string) (string, error) { 31 | key, err := registry.OpenKey(registry.CLASSES_ROOT, fmt.Sprintf(`.%s`, fileExtension), registry.QUERY_VALUE) 32 | if err != nil { 33 | return "", err 34 | } 35 | defer key.Close() 36 | 37 | // 读取默认程序关联值 38 | defaultProgram, _, err := key.GetStringValue("") 39 | if err != nil { 40 | return "", err 41 | } 42 | 43 | return defaultProgram, nil 44 | } 45 | 46 | func hasDefaultProgram(fileExtension string) bool { 47 | prog, err := getDefaultProgram(fileExtension) 48 | if err != nil { 49 | log.Println("getDefaultProgram Error:", err) 50 | return false 51 | } 52 | 53 | if prog == "" { 54 | return false 55 | } 56 | 57 | return true 58 | } 59 | 60 | func OpenFileOrExplorer(filePath string, explorer bool) error { 61 | if _, err := os.Stat(filePath); err != nil { 62 | log.Printf("%s %v\n", filePath, err) 63 | return err 64 | } 65 | 66 | canOpen := false 67 | fileExtension := "" 68 | index := strings.LastIndex(filePath, ".") 69 | if index > 0 { 70 | fileExtension = filePath[index+1:] 71 | canOpen = hasDefaultProgram(fileExtension) 72 | } 73 | 74 | if canOpen && !explorer { 75 | return browser.OpenFile(filePath) 76 | } 77 | 78 | commandArgs := []string{"/select,", filePath} 79 | fmt.Println("cmd:", "explorer", commandArgs) 80 | 81 | // 创建一个Cmd结构体表示要执行的命令 82 | cmd := exec.Command("explorer", commandArgs...) 83 | 84 | // 执行命令并等待它完成 85 | err := cmd.Run() 86 | if err != nil { 87 | log.Printf("Error executing command: %s\n", err) 88 | // return err 89 | } 90 | 91 | fmt.Println("Command executed successfully") 92 | return nil 93 | } 94 | 95 | func GetPathStat(path string) (PathStat, error) { 96 | pathStat := PathStat{} 97 | absPath, err := filepath.Abs(path) 98 | if err != nil { 99 | return pathStat, err 100 | } 101 | 102 | stat, err := disk.Usage(absPath) 103 | if err != nil { 104 | return pathStat, err 105 | } 106 | 107 | pathStat.Path = stat.Path 108 | pathStat.Total = stat.Total 109 | pathStat.Used = stat.Used 110 | pathStat.Free = stat.Free 111 | pathStat.UsedPercent = stat.UsedPercent 112 | 113 | return pathStat, nil 114 | } 115 | 116 | func PathIsCanWriteFile(path string) bool { 117 | 118 | filepath := fmt.Sprintf("%s\\CanWrite.txt", path) 119 | file, err := os.OpenFile(filepath, os.O_CREATE|os.O_WRONLY, 0644) 120 | if err != nil { 121 | return false 122 | } 123 | 124 | file.Close() 125 | os.Remove(filepath) 126 | 127 | return true 128 | } 129 | 130 | func CopyFile(src, dst string) (int64, error) { 131 | stat, err := os.Stat(src) 132 | if err != nil { 133 | return 0, err 134 | } 135 | if stat.IsDir() { 136 | return 0, errors.New(src + " is dir") 137 | } 138 | sourceFile, err := os.Open(src) 139 | if err != nil { 140 | return 0, err 141 | } 142 | defer sourceFile.Close() 143 | 144 | destFile, err := os.Create(dst) 145 | if err != nil { 146 | return 0, err 147 | } 148 | defer destFile.Close() 149 | 150 | bytesWritten, err := io.Copy(destFile, sourceFile) 151 | if err != nil { 152 | return bytesWritten, err 153 | } 154 | 155 | return bytesWritten, nil 156 | } 157 | 158 | func extractTextFromHTML(htmlStr string) string { 159 | doc, err := html.Parse(strings.NewReader(htmlStr)) 160 | if err != nil { 161 | fmt.Println("Error parsing HTML:", err) 162 | return "" 163 | } 164 | 165 | var extractText func(*html.Node) string 166 | extractText = func(n *html.Node) string { 167 | if n.Type == html.TextNode { 168 | return n.Data 169 | } 170 | 171 | var text string 172 | for c := n.FirstChild; c != nil; c = c.NextSibling { 173 | text += extractText(c) 174 | } 175 | return text 176 | } 177 | 178 | return extractText(doc) 179 | } 180 | 181 | func removeCustomTags(input string) string { 182 | 183 | re := regexp.MustCompile(`<(_wc_custom_link_)[^>]*?>`) 184 | return re.ReplaceAllString(input, `$2`) 185 | } 186 | 187 | func Html2Text(htmlStr string) string { 188 | // if htmlStr == "" { 189 | // return "" 190 | // } 191 | 192 | if len(htmlStr) == 0 || htmlStr[0] != '<' { 193 | return htmlStr 194 | } 195 | 196 | text := extractTextFromHTML(htmlStr) 197 | if strings.Contains(text, `<_wc_custom_link_`) { 198 | text = "\U0001F9E7" + removeCustomTags(text) 199 | } 200 | 201 | return text 202 | } 203 | 204 | func HtmlMsgGetAttr(htmlStr, tag string) map[string]string { 205 | 206 | doc, err := html.Parse(strings.NewReader(htmlStr)) 207 | if err != nil { 208 | fmt.Println("Error parsing HTML:", err) 209 | return nil 210 | } 211 | 212 | var attributes map[string]string 213 | var findAttributes func(*html.Node) 214 | findAttributes = func(n *html.Node) { 215 | if n.Type == html.ElementNode && n.Data == tag { 216 | attributes = make(map[string]string) 217 | for _, attr := range n.Attr { 218 | attributes[attr.Key] = attr.Val 219 | } 220 | } 221 | for c := n.FirstChild; c != nil; c = c.NextSibling { 222 | findAttributes(c) 223 | } 224 | } 225 | 226 | findAttributes(doc) 227 | return attributes 228 | } 229 | 230 | func Hash256Sum(data []byte) string { 231 | hash := md5.New() 232 | hash.Write([]byte(data)) 233 | hashSum := hash.Sum(nil) 234 | 235 | return hex.EncodeToString(hashSum) 236 | } 237 | -------------------------------------------------------------------------------- /pkg/wechat/msg.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.33.0 4 | // protoc v3.6.1 5 | // source: msg.proto 6 | 7 | package wechat 8 | 9 | import ( 10 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 11 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 12 | reflect "reflect" 13 | sync "sync" 14 | ) 15 | 16 | const ( 17 | // Verify that this generated code is sufficiently up-to-date. 18 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 19 | // Verify that runtime/protoimpl is sufficiently up-to-date. 20 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 21 | ) 22 | 23 | type SubMessage1 struct { 24 | state protoimpl.MessageState 25 | sizeCache protoimpl.SizeCache 26 | unknownFields protoimpl.UnknownFields 27 | 28 | Field1 int32 `protobuf:"varint,1,opt,name=field1,proto3" json:"field1,omitempty"` 29 | Field2 int32 `protobuf:"varint,2,opt,name=field2,proto3" json:"field2,omitempty"` 30 | } 31 | 32 | func (x *SubMessage1) Reset() { 33 | *x = SubMessage1{} 34 | if protoimpl.UnsafeEnabled { 35 | mi := &file_msg_proto_msgTypes[0] 36 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 37 | ms.StoreMessageInfo(mi) 38 | } 39 | } 40 | 41 | func (x *SubMessage1) String() string { 42 | return protoimpl.X.MessageStringOf(x) 43 | } 44 | 45 | func (*SubMessage1) ProtoMessage() {} 46 | 47 | func (x *SubMessage1) ProtoReflect() protoreflect.Message { 48 | mi := &file_msg_proto_msgTypes[0] 49 | if protoimpl.UnsafeEnabled && x != nil { 50 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 51 | if ms.LoadMessageInfo() == nil { 52 | ms.StoreMessageInfo(mi) 53 | } 54 | return ms 55 | } 56 | return mi.MessageOf(x) 57 | } 58 | 59 | // Deprecated: Use SubMessage1.ProtoReflect.Descriptor instead. 60 | func (*SubMessage1) Descriptor() ([]byte, []int) { 61 | return file_msg_proto_rawDescGZIP(), []int{0} 62 | } 63 | 64 | func (x *SubMessage1) GetField1() int32 { 65 | if x != nil { 66 | return x.Field1 67 | } 68 | return 0 69 | } 70 | 71 | func (x *SubMessage1) GetField2() int32 { 72 | if x != nil { 73 | return x.Field2 74 | } 75 | return 0 76 | } 77 | 78 | type SubMessage2 struct { 79 | state protoimpl.MessageState 80 | sizeCache protoimpl.SizeCache 81 | unknownFields protoimpl.UnknownFields 82 | 83 | Field1 int32 `protobuf:"varint,1,opt,name=field1,proto3" json:"field1,omitempty"` 84 | Field2 string `protobuf:"bytes,2,opt,name=field2,proto3" json:"field2,omitempty"` 85 | } 86 | 87 | func (x *SubMessage2) Reset() { 88 | *x = SubMessage2{} 89 | if protoimpl.UnsafeEnabled { 90 | mi := &file_msg_proto_msgTypes[1] 91 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 92 | ms.StoreMessageInfo(mi) 93 | } 94 | } 95 | 96 | func (x *SubMessage2) String() string { 97 | return protoimpl.X.MessageStringOf(x) 98 | } 99 | 100 | func (*SubMessage2) ProtoMessage() {} 101 | 102 | func (x *SubMessage2) ProtoReflect() protoreflect.Message { 103 | mi := &file_msg_proto_msgTypes[1] 104 | if protoimpl.UnsafeEnabled && x != nil { 105 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 106 | if ms.LoadMessageInfo() == nil { 107 | ms.StoreMessageInfo(mi) 108 | } 109 | return ms 110 | } 111 | return mi.MessageOf(x) 112 | } 113 | 114 | // Deprecated: Use SubMessage2.ProtoReflect.Descriptor instead. 115 | func (*SubMessage2) Descriptor() ([]byte, []int) { 116 | return file_msg_proto_rawDescGZIP(), []int{1} 117 | } 118 | 119 | func (x *SubMessage2) GetField1() int32 { 120 | if x != nil { 121 | return x.Field1 122 | } 123 | return 0 124 | } 125 | 126 | func (x *SubMessage2) GetField2() string { 127 | if x != nil { 128 | return x.Field2 129 | } 130 | return "" 131 | } 132 | 133 | type MessageBytesExtra struct { 134 | state protoimpl.MessageState 135 | sizeCache protoimpl.SizeCache 136 | unknownFields protoimpl.UnknownFields 137 | 138 | Message1 *SubMessage1 `protobuf:"bytes,1,opt,name=message1,proto3" json:"message1,omitempty"` 139 | Message2 []*SubMessage2 `protobuf:"bytes,3,rep,name=message2,proto3" json:"message2,omitempty"` 140 | } 141 | 142 | func (x *MessageBytesExtra) Reset() { 143 | *x = MessageBytesExtra{} 144 | if protoimpl.UnsafeEnabled { 145 | mi := &file_msg_proto_msgTypes[2] 146 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 147 | ms.StoreMessageInfo(mi) 148 | } 149 | } 150 | 151 | func (x *MessageBytesExtra) String() string { 152 | return protoimpl.X.MessageStringOf(x) 153 | } 154 | 155 | func (*MessageBytesExtra) ProtoMessage() {} 156 | 157 | func (x *MessageBytesExtra) ProtoReflect() protoreflect.Message { 158 | mi := &file_msg_proto_msgTypes[2] 159 | if protoimpl.UnsafeEnabled && x != nil { 160 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 161 | if ms.LoadMessageInfo() == nil { 162 | ms.StoreMessageInfo(mi) 163 | } 164 | return ms 165 | } 166 | return mi.MessageOf(x) 167 | } 168 | 169 | // Deprecated: Use MessageBytesExtra.ProtoReflect.Descriptor instead. 170 | func (*MessageBytesExtra) Descriptor() ([]byte, []int) { 171 | return file_msg_proto_rawDescGZIP(), []int{2} 172 | } 173 | 174 | func (x *MessageBytesExtra) GetMessage1() *SubMessage1 { 175 | if x != nil { 176 | return x.Message1 177 | } 178 | return nil 179 | } 180 | 181 | func (x *MessageBytesExtra) GetMessage2() []*SubMessage2 { 182 | if x != nil { 183 | return x.Message2 184 | } 185 | return nil 186 | } 187 | 188 | var File_msg_proto protoreflect.FileDescriptor 189 | 190 | var file_msg_proto_rawDesc = []byte{ 191 | 0x0a, 0x09, 0x6d, 0x73, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0c, 0x61, 0x70, 0x70, 192 | 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x22, 0x3d, 0x0a, 0x0b, 0x53, 0x75, 0x62, 193 | 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x31, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x69, 0x65, 0x6c, 194 | 0x64, 0x31, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x31, 195 | 0x12, 0x16, 0x0a, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x32, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 196 | 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x32, 0x22, 0x3d, 0x0a, 0x0b, 0x53, 0x75, 0x62, 0x4d, 197 | 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x32, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 198 | 0x31, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x31, 0x12, 199 | 0x16, 0x0a, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x32, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 200 | 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x32, 0x22, 0x81, 0x01, 0x0a, 0x11, 0x4d, 0x65, 0x73, 0x73, 201 | 0x61, 0x67, 0x65, 0x42, 0x79, 0x74, 0x65, 0x73, 0x45, 0x78, 0x74, 0x72, 0x61, 0x12, 0x35, 0x0a, 202 | 0x08, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x31, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 203 | 0x19, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 204 | 0x75, 0x62, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x31, 0x52, 0x08, 0x6d, 0x65, 0x73, 0x73, 205 | 0x61, 0x67, 0x65, 0x31, 0x12, 0x35, 0x0a, 0x08, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x32, 206 | 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 207 | 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x75, 0x62, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 208 | 0x32, 0x52, 0x08, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x32, 0x42, 0x09, 0x5a, 0x07, 0x2e, 209 | 0x3b, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 210 | } 211 | 212 | var ( 213 | file_msg_proto_rawDescOnce sync.Once 214 | file_msg_proto_rawDescData = file_msg_proto_rawDesc 215 | ) 216 | 217 | func file_msg_proto_rawDescGZIP() []byte { 218 | file_msg_proto_rawDescOnce.Do(func() { 219 | file_msg_proto_rawDescData = protoimpl.X.CompressGZIP(file_msg_proto_rawDescData) 220 | }) 221 | return file_msg_proto_rawDescData 222 | } 223 | 224 | var file_msg_proto_msgTypes = make([]protoimpl.MessageInfo, 3) 225 | var file_msg_proto_goTypes = []interface{}{ 226 | (*SubMessage1)(nil), // 0: app.protobuf.SubMessage1 227 | (*SubMessage2)(nil), // 1: app.protobuf.SubMessage2 228 | (*MessageBytesExtra)(nil), // 2: app.protobuf.MessageBytesExtra 229 | } 230 | var file_msg_proto_depIdxs = []int32{ 231 | 0, // 0: app.protobuf.MessageBytesExtra.message1:type_name -> app.protobuf.SubMessage1 232 | 1, // 1: app.protobuf.MessageBytesExtra.message2:type_name -> app.protobuf.SubMessage2 233 | 2, // [2:2] is the sub-list for method output_type 234 | 2, // [2:2] is the sub-list for method input_type 235 | 2, // [2:2] is the sub-list for extension type_name 236 | 2, // [2:2] is the sub-list for extension extendee 237 | 0, // [0:2] is the sub-list for field type_name 238 | } 239 | 240 | func init() { file_msg_proto_init() } 241 | func file_msg_proto_init() { 242 | if File_msg_proto != nil { 243 | return 244 | } 245 | if !protoimpl.UnsafeEnabled { 246 | file_msg_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { 247 | switch v := v.(*SubMessage1); i { 248 | case 0: 249 | return &v.state 250 | case 1: 251 | return &v.sizeCache 252 | case 2: 253 | return &v.unknownFields 254 | default: 255 | return nil 256 | } 257 | } 258 | file_msg_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { 259 | switch v := v.(*SubMessage2); i { 260 | case 0: 261 | return &v.state 262 | case 1: 263 | return &v.sizeCache 264 | case 2: 265 | return &v.unknownFields 266 | default: 267 | return nil 268 | } 269 | } 270 | file_msg_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { 271 | switch v := v.(*MessageBytesExtra); i { 272 | case 0: 273 | return &v.state 274 | case 1: 275 | return &v.sizeCache 276 | case 2: 277 | return &v.unknownFields 278 | default: 279 | return nil 280 | } 281 | } 282 | } 283 | type x struct{} 284 | out := protoimpl.TypeBuilder{ 285 | File: protoimpl.DescBuilder{ 286 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 287 | RawDescriptor: file_msg_proto_rawDesc, 288 | NumEnums: 0, 289 | NumMessages: 3, 290 | NumExtensions: 0, 291 | NumServices: 0, 292 | }, 293 | GoTypes: file_msg_proto_goTypes, 294 | DependencyIndexes: file_msg_proto_depIdxs, 295 | MessageInfos: file_msg_proto_msgTypes, 296 | }.Build() 297 | File_msg_proto = out.File 298 | file_msg_proto_rawDesc = nil 299 | file_msg_proto_goTypes = nil 300 | file_msg_proto_depIdxs = nil 301 | } 302 | -------------------------------------------------------------------------------- /pkg/wechat/wechat.go: -------------------------------------------------------------------------------- 1 | package wechat 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "crypto/hmac" 7 | "crypto/sha1" 8 | "database/sql" 9 | "encoding/binary" 10 | "encoding/hex" 11 | "errors" 12 | "fmt" 13 | "io" 14 | "log" 15 | "os" 16 | "path/filepath" 17 | "strings" 18 | "sync" 19 | "sync/atomic" 20 | "time" 21 | "unsafe" 22 | 23 | "github.com/git-jiadong/go-lame" 24 | "github.com/git-jiadong/go-silk" 25 | _ "github.com/mattn/go-sqlite3" 26 | "github.com/shirou/gopsutil/v3/process" 27 | "golang.org/x/sys/windows" 28 | ) 29 | 30 | type WeChatInfo struct { 31 | ProcessID uint32 32 | FilePath string 33 | AcountName string 34 | Version string 35 | Is64Bits bool 36 | DllBaseAddr uintptr 37 | DllBaseSize uint32 38 | DBKey string 39 | } 40 | 41 | type WeChatInfoList struct { 42 | Info []WeChatInfo `json:"Info"` 43 | Total int `json:"Total"` 44 | } 45 | 46 | type wechatMediaMSG struct { 47 | Key string 48 | MsgSvrID int 49 | Buf []byte 50 | } 51 | 52 | type wechatHeadImgMSG struct { 53 | userName string 54 | Buf []byte 55 | } 56 | 57 | func GetWeChatAllInfo() *WeChatInfoList { 58 | list := GetWeChatInfo() 59 | 60 | for i := range list.Info { 61 | list.Info[i].DBKey = GetWeChatKey(&list.Info[i]) 62 | } 63 | 64 | return list 65 | } 66 | 67 | func ExportWeChatAllData(info WeChatInfo, expPath string, progress chan<- string) { 68 | defer close(progress) 69 | fileInfo, err := os.Stat(info.FilePath) 70 | if err != nil || !fileInfo.IsDir() { 71 | progress <- fmt.Sprintf("{\"status\":\"error\", \"result\":\"%s error\"}", info.FilePath) 72 | return 73 | } 74 | if !exportWeChatDateBase(info, expPath, progress) { 75 | return 76 | } 77 | 78 | exportWeChatBat(info, expPath, progress) 79 | exportWeChatVideoAndFile(info, expPath, progress) 80 | exportWeChatVoice(info, expPath, progress) 81 | exportWeChatHeadImage(info, expPath, progress) 82 | } 83 | 84 | func exportWeChatHeadImage(info WeChatInfo, expPath string, progress chan<- string) { 85 | progress <- "{\"status\":\"processing\", \"result\":\"export WeChat Head Image\", \"progress\": 81}" 86 | 87 | headImgPath := fmt.Sprintf("%s\\FileStorage\\HeadImage", expPath) 88 | if _, err := os.Stat(headImgPath); err != nil { 89 | if err := os.MkdirAll(headImgPath, 0644); err != nil { 90 | log.Printf("MkdirAll %s failed: %v\n", headImgPath, err) 91 | progress <- fmt.Sprintf("{\"status\":\"error\", \"result\":\"%v error\"}", err) 92 | return 93 | } 94 | } 95 | 96 | handleNumber := int64(0) 97 | fileNumber := int64(0) 98 | 99 | var wg sync.WaitGroup 100 | var reportWg sync.WaitGroup 101 | quitChan := make(chan struct{}) 102 | MSGChan := make(chan wechatHeadImgMSG, 100) 103 | go func() { 104 | for { 105 | miscDBPath := fmt.Sprintf("%s\\Msg\\Misc.db", expPath) 106 | _, err := os.Stat(miscDBPath) 107 | if err != nil { 108 | log.Println("no exist:", miscDBPath) 109 | break 110 | } 111 | 112 | db, err := sql.Open("sqlite3", miscDBPath) 113 | if err != nil { 114 | log.Printf("open %s failed: %v\n", miscDBPath, err) 115 | break 116 | } 117 | defer db.Close() 118 | 119 | err = db.QueryRow("select count(*) from ContactHeadImg1;").Scan(&fileNumber) 120 | if err != nil { 121 | log.Println("select count(*) failed", err) 122 | break 123 | } 124 | log.Println("ContactHeadImg1 fileNumber", fileNumber) 125 | rows, err := db.Query("select ifnull(usrName,'') as usrName, ifnull(smallHeadBuf,'') as smallHeadBuf from ContactHeadImg1;") 126 | if err != nil { 127 | log.Printf("Query failed: %v\n", err) 128 | break 129 | } 130 | 131 | msg := wechatHeadImgMSG{} 132 | for rows.Next() { 133 | err := rows.Scan(&msg.userName, &msg.Buf) 134 | if err != nil { 135 | log.Println("Scan failed: ", err) 136 | break 137 | } 138 | 139 | MSGChan <- msg 140 | } 141 | break 142 | } 143 | close(MSGChan) 144 | }() 145 | 146 | for i := 0; i < 20; i++ { 147 | wg.Add(1) 148 | go func() { 149 | defer wg.Done() 150 | for msg := range MSGChan { 151 | imgPath := fmt.Sprintf("%s\\%s.headimg", headImgPath, msg.userName) 152 | for { 153 | // log.Println("imgPath:", imgPath, len(msg.Buf)) 154 | _, err := os.Stat(imgPath) 155 | if err == nil { 156 | break 157 | } 158 | if len(msg.userName) == 0 || len(msg.Buf) == 0 { 159 | break 160 | } 161 | err = os.WriteFile(imgPath, msg.Buf[:], 0666) 162 | if err != nil { 163 | log.Println("WriteFile:", imgPath, err) 164 | } 165 | break 166 | } 167 | atomic.AddInt64(&handleNumber, 1) 168 | } 169 | }() 170 | } 171 | 172 | reportWg.Add(1) 173 | go func() { 174 | defer reportWg.Done() 175 | for { 176 | select { 177 | case <-quitChan: 178 | log.Println("WeChat Head Image report progress end") 179 | return 180 | default: 181 | if fileNumber != 0 { 182 | filePercent := float64(handleNumber) / float64(fileNumber) 183 | totalPercent := 81 + (filePercent * (100 - 81)) 184 | totalPercentStr := fmt.Sprintf("{\"status\":\"processing\", \"result\":\"export WeChat Head Image doing\", \"progress\": %d}", int(totalPercent)) 185 | progress <- totalPercentStr 186 | } 187 | time.Sleep(time.Second) 188 | } 189 | } 190 | }() 191 | 192 | wg.Wait() 193 | close(quitChan) 194 | reportWg.Wait() 195 | progress <- "{\"status\":\"processing\", \"result\":\"export WeChat Head Image end\", \"progress\": 100}" 196 | } 197 | 198 | func exportWeChatVoice(info WeChatInfo, expPath string, progress chan<- string) { 199 | progress <- "{\"status\":\"processing\", \"result\":\"export WeChat voice start\", \"progress\": 61}" 200 | 201 | voicePath := fmt.Sprintf("%s\\FileStorage\\Voice", expPath) 202 | if _, err := os.Stat(voicePath); err != nil { 203 | if err := os.MkdirAll(voicePath, 0644); err != nil { 204 | log.Printf("MkdirAll %s failed: %v\n", voicePath, err) 205 | progress <- fmt.Sprintf("{\"status\":\"error\", \"result\":\"%v error\"}", err) 206 | return 207 | } 208 | } 209 | 210 | handleNumber := int64(0) 211 | fileNumber := int64(0) 212 | index := 0 213 | for { 214 | mediaMSGDB := fmt.Sprintf("%s\\Msg\\Multi\\MediaMSG%d.db", expPath, index) 215 | _, err := os.Stat(mediaMSGDB) 216 | if err != nil { 217 | break 218 | } 219 | index += 1 220 | fileNumber += 1 221 | } 222 | 223 | var wg sync.WaitGroup 224 | var reportWg sync.WaitGroup 225 | quitChan := make(chan struct{}) 226 | index = -1 227 | MSGChan := make(chan wechatMediaMSG, 100) 228 | go func() { 229 | for { 230 | index += 1 231 | mediaMSGDB := fmt.Sprintf("%s\\Msg\\Multi\\MediaMSG%d.db", expPath, index) 232 | _, err := os.Stat(mediaMSGDB) 233 | if err != nil { 234 | break 235 | } 236 | 237 | db, err := sql.Open("sqlite3", mediaMSGDB) 238 | if err != nil { 239 | log.Printf("open %s failed: %v\n", mediaMSGDB, err) 240 | continue 241 | } 242 | defer db.Close() 243 | 244 | rows, err := db.Query("select Key, Reserved0, Buf from Media;") 245 | if err != nil { 246 | log.Printf("Query failed: %v\n", err) 247 | continue 248 | } 249 | 250 | msg := wechatMediaMSG{} 251 | for rows.Next() { 252 | err := rows.Scan(&msg.Key, &msg.MsgSvrID, &msg.Buf) 253 | if err != nil { 254 | log.Println("Scan failed: ", err) 255 | break 256 | } 257 | 258 | MSGChan <- msg 259 | } 260 | atomic.AddInt64(&handleNumber, 1) 261 | } 262 | close(MSGChan) 263 | }() 264 | 265 | for i := 0; i < 20; i++ { 266 | wg.Add(1) 267 | go func() { 268 | defer wg.Done() 269 | for msg := range MSGChan { 270 | mp3Path := fmt.Sprintf("%s\\%d.mp3", voicePath, msg.MsgSvrID) 271 | _, err := os.Stat(mp3Path) 272 | if err == nil { 273 | continue 274 | } 275 | 276 | err = silkToMp3(msg.Buf[:], mp3Path) 277 | if err != nil { 278 | log.Printf("silkToMp3 %s failed: %v\n", mp3Path, err) 279 | } 280 | } 281 | }() 282 | } 283 | 284 | reportWg.Add(1) 285 | go func() { 286 | defer reportWg.Done() 287 | for { 288 | select { 289 | case <-quitChan: 290 | log.Println("WeChat voice report progress end") 291 | return 292 | default: 293 | filePercent := float64(handleNumber) / float64(fileNumber) 294 | totalPercent := 61 + (filePercent * (80 - 61)) 295 | totalPercentStr := fmt.Sprintf("{\"status\":\"processing\", \"result\":\"export WeChat voice doing\", \"progress\": %d}", int(totalPercent)) 296 | progress <- totalPercentStr 297 | time.Sleep(time.Second) 298 | } 299 | } 300 | }() 301 | 302 | wg.Wait() 303 | close(quitChan) 304 | reportWg.Wait() 305 | progress <- "{\"status\":\"processing\", \"result\":\"export WeChat voice end\", \"progress\": 80}" 306 | } 307 | 308 | func exportWeChatVideoAndFile(info WeChatInfo, expPath string, progress chan<- string) { 309 | progress <- "{\"status\":\"processing\", \"result\":\"export WeChat Video and File start\", , \"progress\": 41}" 310 | videoRootPath := info.FilePath + "\\FileStorage\\Video" 311 | fileRootPath := info.FilePath + "\\FileStorage\\File" 312 | cacheRootPath := info.FilePath + "\\FileStorage\\Cache" 313 | 314 | rootPaths := []string{videoRootPath, fileRootPath, cacheRootPath} 315 | 316 | handleNumber := int64(0) 317 | fileNumber := int64(0) 318 | for _, path := range rootPaths { 319 | fileNumber += getPathFileNumber(path, "") 320 | } 321 | log.Println("VideoAndFile ", fileNumber) 322 | 323 | var wg sync.WaitGroup 324 | var reportWg sync.WaitGroup 325 | quitChan := make(chan struct{}) 326 | taskChan := make(chan [2]string, 100) 327 | go func() { 328 | for _, rootPath := range rootPaths { 329 | log.Println(rootPath) 330 | if _, err := os.Stat(rootPath); err != nil { 331 | continue 332 | } 333 | err := filepath.Walk(rootPath, func(path string, finfo os.FileInfo, err error) error { 334 | if err != nil { 335 | log.Printf("filepath.Walk:%v\n", err) 336 | return err 337 | } 338 | 339 | if !finfo.IsDir() { 340 | expFile := expPath + path[len(info.FilePath):] 341 | _, err := os.Stat(filepath.Dir(expFile)) 342 | if err != nil { 343 | os.MkdirAll(filepath.Dir(expFile), 0644) 344 | } 345 | 346 | task := [2]string{path, expFile} 347 | taskChan <- task 348 | return nil 349 | } 350 | 351 | return nil 352 | }) 353 | if err != nil { 354 | log.Println("filepath.Walk:", err) 355 | progress <- fmt.Sprintf("{\"status\":\"error\", \"result\":\"%v\"}", err) 356 | } 357 | } 358 | close(taskChan) 359 | }() 360 | 361 | for i := 1; i < 30; i++ { 362 | wg.Add(1) 363 | go func() { 364 | defer wg.Done() 365 | for task := range taskChan { 366 | _, err := os.Stat(task[1]) 367 | if err == nil { 368 | atomic.AddInt64(&handleNumber, 1) 369 | continue 370 | } 371 | _, err = copyFile(task[0], task[1]) 372 | if err != nil { 373 | log.Println("DecryptDat:", err) 374 | progress <- fmt.Sprintf("{\"status\":\"error\", \"result\":\"copyFile %v\"}", err) 375 | } 376 | atomic.AddInt64(&handleNumber, 1) 377 | } 378 | }() 379 | } 380 | reportWg.Add(1) 381 | go func() { 382 | defer reportWg.Done() 383 | for { 384 | select { 385 | case <-quitChan: 386 | log.Println("WeChat Video and File report progress end") 387 | return 388 | default: 389 | filePercent := float64(handleNumber) / float64(fileNumber) 390 | totalPercent := 41 + (filePercent * (60 - 41)) 391 | totalPercentStr := fmt.Sprintf("{\"status\":\"processing\", \"result\":\"export WeChat Video and File doing\", \"progress\": %d}", int(totalPercent)) 392 | progress <- totalPercentStr 393 | time.Sleep(time.Second) 394 | } 395 | } 396 | }() 397 | wg.Wait() 398 | close(quitChan) 399 | reportWg.Wait() 400 | progress <- "{\"status\":\"processing\", \"result\":\"export WeChat Video and File end\", \"progress\": 60}" 401 | } 402 | 403 | func exportWeChatBat(info WeChatInfo, expPath string, progress chan<- string) { 404 | progress <- "{\"status\":\"processing\", \"result\":\"export WeChat Dat start\", \"progress\": 21}" 405 | datRootPath := info.FilePath + "\\FileStorage\\MsgAttach" 406 | imageRootPath := info.FilePath + "\\FileStorage\\Image" 407 | rootPaths := []string{datRootPath, imageRootPath} 408 | 409 | handleNumber := int64(0) 410 | fileNumber := int64(0) 411 | for i := range rootPaths { 412 | fileNumber += getPathFileNumber(rootPaths[i], ".dat") 413 | } 414 | log.Println("DatFileNumber ", fileNumber) 415 | 416 | var wg sync.WaitGroup 417 | var reportWg sync.WaitGroup 418 | quitChan := make(chan struct{}) 419 | taskChan := make(chan [2]string, 100) 420 | go func() { 421 | for i := range rootPaths { 422 | if _, err := os.Stat(rootPaths[i]); err != nil { 423 | continue 424 | } 425 | 426 | err := filepath.Walk(rootPaths[i], func(path string, finfo os.FileInfo, err error) error { 427 | if err != nil { 428 | log.Printf("filepath.Walk:%v\n", err) 429 | return err 430 | } 431 | 432 | if !finfo.IsDir() && strings.HasSuffix(path, ".dat") { 433 | expFile := expPath + path[len(info.FilePath):] 434 | _, err := os.Stat(filepath.Dir(expFile)) 435 | if err != nil { 436 | os.MkdirAll(filepath.Dir(expFile), 0644) 437 | } 438 | 439 | task := [2]string{path, expFile} 440 | taskChan <- task 441 | return nil 442 | } 443 | 444 | return nil 445 | }) 446 | 447 | if err != nil { 448 | log.Println("filepath.Walk:", err) 449 | progress <- fmt.Sprintf("{\"status\":\"error\", \"result\":\"%v\"}", err) 450 | } 451 | } 452 | close(taskChan) 453 | }() 454 | 455 | for i := 1; i < 30; i++ { 456 | wg.Add(1) 457 | go func() { 458 | defer wg.Done() 459 | for task := range taskChan { 460 | _, err := os.Stat(task[1]) 461 | if err == nil { 462 | atomic.AddInt64(&handleNumber, 1) 463 | continue 464 | } 465 | err = DecryptDat(task[0], task[1]) 466 | if err != nil { 467 | log.Println("DecryptDat:", err) 468 | progress <- fmt.Sprintf("{\"status\":\"error\", \"result\":\"DecryptDat %v\"}", err) 469 | } 470 | atomic.AddInt64(&handleNumber, 1) 471 | } 472 | }() 473 | } 474 | reportWg.Add(1) 475 | go func() { 476 | defer reportWg.Done() 477 | for { 478 | select { 479 | case <-quitChan: 480 | log.Println("WeChat Dat report progress end") 481 | return 482 | default: 483 | filePercent := float64(handleNumber) / float64(fileNumber) 484 | totalPercent := 21 + (filePercent * (40 - 21)) 485 | totalPercentStr := fmt.Sprintf("{\"status\":\"processing\", \"result\":\"export WeChat Dat doing\", \"progress\": %d}", int(totalPercent)) 486 | progress <- totalPercentStr 487 | time.Sleep(time.Second) 488 | } 489 | } 490 | }() 491 | wg.Wait() 492 | close(quitChan) 493 | reportWg.Wait() 494 | progress <- "{\"status\":\"processing\", \"result\":\"export WeChat Dat end\", \"progress\": 40}" 495 | } 496 | 497 | func exportWeChatDateBase(info WeChatInfo, expPath string, progress chan<- string) bool { 498 | 499 | progress <- "{\"status\":\"processing\", \"result\":\"export WeChat DateBase start\", \"progress\": 1}" 500 | 501 | dbKey, err := hex.DecodeString(info.DBKey) 502 | if err != nil { 503 | log.Println("DecodeString:", err) 504 | progress <- fmt.Sprintf("{\"status\":\"error\", \"result\":\"%v\"}", err) 505 | return false 506 | } 507 | 508 | handleNumber := int64(0) 509 | fileNumber := getPathFileNumber(info.FilePath+"\\Msg", ".db") 510 | var wg sync.WaitGroup 511 | var reportWg sync.WaitGroup 512 | quitChan := make(chan struct{}) 513 | taskChan := make(chan [2]string, 20) 514 | go func() { 515 | err = filepath.Walk(info.FilePath+"\\Msg", func(path string, finfo os.FileInfo, err error) error { 516 | if err != nil { 517 | log.Printf("filepath.Walk:%v\n", err) 518 | return err 519 | } 520 | if !finfo.IsDir() && strings.HasSuffix(path, ".db") { 521 | expFile := expPath + path[len(info.FilePath):] 522 | _, err := os.Stat(filepath.Dir(expFile)) 523 | if err != nil { 524 | os.MkdirAll(filepath.Dir(expFile), 0644) 525 | } 526 | 527 | task := [2]string{path, expFile} 528 | taskChan <- task 529 | } 530 | 531 | return nil 532 | }) 533 | if err != nil { 534 | log.Println("filepath.Walk:", err) 535 | progress <- fmt.Sprintf("{\"status\":\"error\", \"result\":\"%v\"}", err) 536 | } 537 | close(taskChan) 538 | }() 539 | 540 | for i := 1; i < 20; i++ { 541 | wg.Add(1) 542 | go func() { 543 | defer wg.Done() 544 | for task := range taskChan { 545 | if filepath.Base(task[0]) == "xInfo.db" { 546 | copyFile(task[0], task[1]) 547 | } else { 548 | err = DecryptDataBase(task[0], dbKey, task[1]) 549 | if err != nil { 550 | log.Println("DecryptDataBase:", err) 551 | progress <- fmt.Sprintf("{\"status\":\"error\", \"result\":\"%s %v\"}", task[0], err) 552 | } 553 | } 554 | atomic.AddInt64(&handleNumber, 1) 555 | } 556 | }() 557 | } 558 | 559 | reportWg.Add(1) 560 | go func() { 561 | defer reportWg.Done() 562 | for { 563 | select { 564 | case <-quitChan: 565 | log.Println("WeChat DateBase report progress end") 566 | return 567 | default: 568 | filePercent := float64(handleNumber) / float64(fileNumber) 569 | totalPercent := 1 + (filePercent * (20 - 1)) 570 | totalPercentStr := fmt.Sprintf("{\"status\":\"processing\", \"result\":\"export WeChat DateBase doing\", \"progress\": %d}", int(totalPercent)) 571 | progress <- totalPercentStr 572 | time.Sleep(time.Second) 573 | } 574 | } 575 | }() 576 | wg.Wait() 577 | close(quitChan) 578 | reportWg.Wait() 579 | progress <- "{\"status\":\"processing\", \"result\":\"export WeChat DateBase end\", \"progress\": 20}" 580 | return true 581 | } 582 | 583 | func GetWeChatInfo() (list *WeChatInfoList) { 584 | list = &WeChatInfoList{} 585 | list.Info = make([]WeChatInfo, 0) 586 | list.Total = 0 587 | 588 | processes, err := process.Processes() 589 | if err != nil { 590 | log.Println("Error getting processes:", err) 591 | return 592 | } 593 | 594 | for _, p := range processes { 595 | name, err := p.Name() 596 | if err != nil { 597 | continue 598 | } 599 | info := WeChatInfo{} 600 | if name == "WeChat.exe" { 601 | info.ProcessID = uint32(p.Pid) 602 | info.Is64Bits, _ = Is64BitProcess(info.ProcessID) 603 | log.Println("ProcessID", info.ProcessID) 604 | files, err := p.OpenFiles() 605 | if err != nil { 606 | log.Println("OpenFiles failed") 607 | continue 608 | } 609 | 610 | for _, f := range files { 611 | if strings.HasSuffix(f.Path, "\\Media.db") { 612 | // fmt.Printf("opened %s\n", f.Path[4:]) 613 | filePath := f.Path 614 | parts := strings.Split(filePath, string(filepath.Separator)) 615 | if len(parts) < 4 { 616 | log.Println("Error filePath " + filePath) 617 | break 618 | } 619 | info.FilePath = strings.Join(parts[:len(parts)-2], string(filepath.Separator)) 620 | info.AcountName = strings.Join(parts[len(parts)-3:len(parts)-2], string(filepath.Separator)) 621 | } 622 | 623 | } 624 | 625 | if len(info.FilePath) == 0 { 626 | log.Println("wechat not log in") 627 | continue 628 | } 629 | 630 | hModuleSnap, err := windows.CreateToolhelp32Snapshot(windows.TH32CS_SNAPMODULE|windows.TH32CS_SNAPMODULE32, uint32(p.Pid)) 631 | if err != nil { 632 | log.Println("CreateToolhelp32Snapshot failed", err) 633 | continue 634 | } 635 | defer windows.CloseHandle(hModuleSnap) 636 | 637 | var me32 windows.ModuleEntry32 638 | me32.Size = uint32(windows.SizeofModuleEntry32) 639 | 640 | err = windows.Module32First(hModuleSnap, &me32) 641 | if err != nil { 642 | log.Println("Module32First failed", err) 643 | continue 644 | } 645 | 646 | for ; err == nil; err = windows.Module32Next(hModuleSnap, &me32) { 647 | if windows.UTF16ToString(me32.Module[:]) == "WeChatWin.dll" { 648 | // fmt.Printf("MODULE NAME: %s\n", windows.UTF16ToString(me32.Module[:])) 649 | // fmt.Printf("executable NAME: %s\n", windows.UTF16ToString(me32.ExePath[:])) 650 | // fmt.Printf("base address: 0x%08X\n", me32.ModBaseAddr) 651 | // fmt.Printf("base ModBaseSize: %d\n", me32.ModBaseSize) 652 | info.DllBaseAddr = me32.ModBaseAddr 653 | info.DllBaseSize = me32.ModBaseSize 654 | 655 | var zero windows.Handle 656 | driverPath := windows.UTF16ToString(me32.ExePath[:]) 657 | infoSize, err := windows.GetFileVersionInfoSize(driverPath, &zero) 658 | if err != nil { 659 | log.Println("GetFileVersionInfoSize failed", err) 660 | break 661 | } 662 | versionInfo := make([]byte, infoSize) 663 | if err = windows.GetFileVersionInfo(driverPath, 0, infoSize, unsafe.Pointer(&versionInfo[0])); err != nil { 664 | log.Println("GetFileVersionInfo failed", err) 665 | break 666 | } 667 | var fixedInfo *windows.VS_FIXEDFILEINFO 668 | fixedInfoLen := uint32(unsafe.Sizeof(*fixedInfo)) 669 | err = windows.VerQueryValue(unsafe.Pointer(&versionInfo[0]), `\`, (unsafe.Pointer)(&fixedInfo), &fixedInfoLen) 670 | if err != nil { 671 | log.Println("VerQueryValue failed", err) 672 | break 673 | } 674 | // fmt.Printf("%s: v%d.%d.%d.%d\n", windows.UTF16ToString(me32.Module[:]), 675 | // (fixedInfo.FileVersionMS>>16)&0xff, 676 | // (fixedInfo.FileVersionMS>>0)&0xff, 677 | // (fixedInfo.FileVersionLS>>16)&0xff, 678 | // (fixedInfo.FileVersionLS>>0)&0xff) 679 | 680 | info.Version = fmt.Sprintf("%d.%d.%d.%d", 681 | (fixedInfo.FileVersionMS>>16)&0xff, 682 | (fixedInfo.FileVersionMS>>0)&0xff, 683 | (fixedInfo.FileVersionLS>>16)&0xff, 684 | (fixedInfo.FileVersionLS>>0)&0xff) 685 | list.Info = append(list.Info, info) 686 | list.Total += 1 687 | break 688 | } 689 | } 690 | } 691 | } 692 | return 693 | } 694 | 695 | func Is64BitProcess(pid uint32) (bool, error) { 696 | is64Bit := false 697 | handle, err := windows.OpenProcess(windows.PROCESS_QUERY_INFORMATION, false, pid) 698 | if err != nil { 699 | log.Println("Error opening process:", err) 700 | return is64Bit, errors.New("OpenProcess failed") 701 | } 702 | defer windows.CloseHandle(handle) 703 | 704 | err = windows.IsWow64Process(handle, &is64Bit) 705 | if err != nil { 706 | log.Println("Error IsWow64Process:", err) 707 | } 708 | return !is64Bit, err 709 | } 710 | 711 | func GetWeChatKey(info *WeChatInfo) string { 712 | mediaDB := info.FilePath + "\\Msg\\Media.db" 713 | if _, err := os.Stat(mediaDB); err != nil { 714 | log.Printf("open db %s error: %v", mediaDB, err) 715 | return "" 716 | } 717 | 718 | handle, err := windows.OpenProcess(windows.PROCESS_QUERY_INFORMATION|windows.PROCESS_VM_READ, false, uint32(info.ProcessID)) 719 | if err != nil { 720 | log.Println("Error opening process:", err) 721 | return "" 722 | } 723 | defer windows.CloseHandle(handle) 724 | 725 | buffer := make([]byte, info.DllBaseSize) 726 | err = windows.ReadProcessMemory(handle, uintptr(info.DllBaseAddr), &buffer[0], uintptr(len(buffer)), nil) 727 | if err != nil { 728 | log.Println("Error ReadProcessMemory:", err) 729 | return "" 730 | } 731 | 732 | offset := 0 733 | // searchStr := []byte(info.AcountName) 734 | for { 735 | index := hasDeviceSybmol(buffer[offset:]) 736 | if index == -1 { 737 | log.Println("has not DeviceSybmol") 738 | break 739 | } 740 | fmt.Printf("hasDeviceSybmol: 0x%X\n", index) 741 | keys := findDBKeyPtr(buffer[offset:index], info.Is64Bits) 742 | // fmt.Println("keys:", keys) 743 | 744 | key, err := findDBkey(handle, info.FilePath+"\\Msg\\Media.db", keys) 745 | if err == nil { 746 | // fmt.Println("key:", key) 747 | return key 748 | } 749 | 750 | offset += (index + 20) 751 | } 752 | 753 | return "" 754 | } 755 | 756 | func hasDeviceSybmol(buffer []byte) int { 757 | sybmols := [...][]byte{ 758 | {'a', 'n', 'd', 'r', 'o', 'i', 'd', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00}, 759 | {'p', 'a', 'd', '-', 'a', 'n', 'd', 'r', 'o', 'i', 'd', 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00}, 760 | {'i', 'p', 'h', 'o', 'n', 'e', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00}, 761 | {'i', 'p', 'a', 'd', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00}, 762 | {'O', 'H', 'O', 'S', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00}, 763 | } 764 | for _, syb := range sybmols { 765 | if index := bytes.Index(buffer, syb); index != -1 { 766 | return index 767 | } 768 | } 769 | 770 | return -1 771 | } 772 | 773 | func findDBKeyPtr(buffer []byte, is64Bits bool) [][]byte { 774 | keys := make([][]byte, 0) 775 | step := 8 776 | keyLen := []byte{0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} 777 | if !is64Bits { 778 | keyLen = keyLen[:4] 779 | step = 4 780 | } 781 | 782 | offset := len(buffer) - step 783 | for { 784 | if bytes.Contains(buffer[offset:offset+step], keyLen) { 785 | keys = append(keys, buffer[offset-step:offset]) 786 | } 787 | 788 | offset -= step 789 | if offset <= 0 { 790 | break 791 | } 792 | } 793 | 794 | return keys 795 | } 796 | 797 | func findDBkey(handle windows.Handle, path string, keys [][]byte) (string, error) { 798 | var keyAddrPtr uint64 799 | addrBuffer := make([]byte, 0x08) 800 | for _, key := range keys { 801 | copy(addrBuffer, key) 802 | err := binary.Read(bytes.NewReader(addrBuffer), binary.LittleEndian, &keyAddrPtr) 803 | if err != nil { 804 | log.Println("binary.Read:", err) 805 | continue 806 | } 807 | if keyAddrPtr == 0x00 { 808 | continue 809 | } 810 | log.Printf("keyAddrPtr: 0x%X\n", keyAddrPtr) 811 | keyBuffer := make([]byte, 0x20) 812 | err = windows.ReadProcessMemory(handle, uintptr(keyAddrPtr), &keyBuffer[0], uintptr(len(keyBuffer)), nil) 813 | if err != nil { 814 | // fmt.Println("Error ReadProcessMemory:", err) 815 | continue 816 | } 817 | if checkDataBaseKey(path, keyBuffer) { 818 | return hex.EncodeToString(keyBuffer), nil 819 | } 820 | } 821 | 822 | return "", errors.New("not found key") 823 | } 824 | 825 | func checkDataBaseKey(path string, password []byte) bool { 826 | fp, err := os.Open(path) 827 | if err != nil { 828 | return false 829 | } 830 | defer fp.Close() 831 | 832 | fpReader := bufio.NewReaderSize(fp, defaultPageSize*100) 833 | 834 | buffer := make([]byte, defaultPageSize) 835 | 836 | n, err := fpReader.Read(buffer) 837 | if err != nil && n != defaultPageSize { 838 | log.Println("read failed:", err, n) 839 | return false 840 | } 841 | 842 | salt := buffer[:16] 843 | key := pbkdf2HMAC(password, salt, defaultIter, keySize) 844 | 845 | page1 := buffer[16:defaultPageSize] 846 | 847 | macSalt := xorBytes(salt, 0x3a) 848 | macKey := pbkdf2HMAC(key, macSalt, 2, keySize) 849 | 850 | hashMac := hmac.New(sha1.New, macKey) 851 | hashMac.Write(page1[:len(page1)-32]) 852 | hashMac.Write([]byte{1, 0, 0, 0}) 853 | 854 | return hmac.Equal(hashMac.Sum(nil), page1[len(page1)-32:len(page1)-12]) 855 | } 856 | 857 | func (info WeChatInfo) String() string { 858 | return fmt.Sprintf("PID: %d\nVersion: v%s\nBaseAddr: 0x%08X\nDllSize: %d\nIs 64Bits: %v\nFilePath %s\nAcountName: %s", 859 | info.ProcessID, info.Version, info.DllBaseAddr, info.DllBaseSize, info.Is64Bits, info.FilePath, info.AcountName) 860 | } 861 | 862 | func copyFile(src, dst string) (int64, error) { 863 | sourceFile, err := os.Open(src) 864 | if err != nil { 865 | return 0, err 866 | } 867 | defer sourceFile.Close() 868 | 869 | destFile, err := os.Create(dst) 870 | if err != nil { 871 | return 0, err 872 | } 873 | defer destFile.Close() 874 | 875 | bytesWritten, err := io.Copy(destFile, sourceFile) 876 | if err != nil { 877 | return bytesWritten, err 878 | } 879 | 880 | return bytesWritten, nil 881 | } 882 | 883 | func silkToMp3(amrBuf []byte, mp3Path string) error { 884 | amrReader := bytes.NewReader(amrBuf) 885 | 886 | var pcmBuffer bytes.Buffer 887 | sr := silk.NewWriter(&pcmBuffer) 888 | sr.Decoder.SetSampleRate(24000) 889 | amrReader.WriteTo(sr) 890 | sr.Close() 891 | 892 | if pcmBuffer.Len() == 0 { 893 | return errors.New("silk to mp3 failed " + mp3Path) 894 | } 895 | 896 | of, err := os.Create(mp3Path) 897 | if err != nil { 898 | return nil 899 | } 900 | defer of.Close() 901 | 902 | wr := lame.NewWriter(of) 903 | wr.Encoder.SetInSamplerate(24000) 904 | wr.Encoder.SetOutSamplerate(24000) 905 | wr.Encoder.SetNumChannels(1) 906 | wr.Encoder.SetQuality(5) 907 | // IMPORTANT! 908 | wr.Encoder.InitParams() 909 | 910 | pcmBuffer.WriteTo(wr) 911 | wr.Close() 912 | 913 | return nil 914 | } 915 | 916 | func getPathFileNumber(targetPath string, fileSuffix string) int64 { 917 | 918 | number := int64(0) 919 | err := filepath.Walk(targetPath, func(path string, finfo os.FileInfo, err error) error { 920 | if err != nil { 921 | log.Printf("filepath.Walk:%v\n", err) 922 | return err 923 | } 924 | if !finfo.IsDir() && strings.HasSuffix(path, fileSuffix) { 925 | number += 1 926 | } 927 | 928 | return nil 929 | }) 930 | if err != nil { 931 | log.Println("filepath.Walk:", err) 932 | } 933 | 934 | return number 935 | } 936 | 937 | func ExportWeChatHeadImage(exportPath string) { 938 | progress := make(chan string) 939 | info := WeChatInfo{} 940 | 941 | miscDBPath := fmt.Sprintf("%s\\Msg\\Misc.db", exportPath) 942 | _, err := os.Stat(miscDBPath) 943 | if err != nil { 944 | log.Println("no exist:", miscDBPath) 945 | return 946 | } 947 | 948 | headImgPath := fmt.Sprintf("%s\\FileStorage\\HeadImage", exportPath) 949 | if _, err := os.Stat(headImgPath); err == nil { 950 | log.Println("has HeadImage") 951 | return 952 | } 953 | 954 | go func() { 955 | exportWeChatHeadImage(info, exportPath, progress) 956 | close(progress) 957 | }() 958 | 959 | for p := range progress { 960 | log.Println(p) 961 | } 962 | log.Println("ExportWeChatHeadImage done") 963 | } 964 | -------------------------------------------------------------------------------- /pkg/wechat/wechatDBDec.go: -------------------------------------------------------------------------------- 1 | package wechat 2 | 3 | import ( 4 | "bufio" 5 | "crypto/aes" 6 | "crypto/cipher" 7 | "crypto/hmac" 8 | "crypto/sha1" 9 | "fmt" 10 | "io" 11 | "os" 12 | ) 13 | 14 | const ( 15 | keySize = 32 16 | defaultIter = 64000 17 | defaultPageSize = 4096 18 | ) 19 | 20 | func DecryptDataBase(path string, password []byte, expPath string) error { 21 | sqliteFileHeader := []byte("SQLite format 3") 22 | sqliteFileHeader = append(sqliteFileHeader, byte(0)) 23 | 24 | fp, err := os.Open(path) 25 | if err != nil { 26 | return err 27 | } 28 | defer fp.Close() 29 | 30 | fpReader := bufio.NewReaderSize(fp, defaultPageSize*100) 31 | // fpReader := bufio.NewReader(fp) 32 | 33 | buffer := make([]byte, defaultPageSize) 34 | 35 | n, err := fpReader.Read(buffer) 36 | if err != nil && n != defaultPageSize { 37 | return fmt.Errorf("read failed") 38 | } 39 | 40 | salt := buffer[:16] 41 | key := pbkdf2HMAC(password, salt, defaultIter, keySize) 42 | 43 | page1 := buffer[16:defaultPageSize] 44 | 45 | macSalt := xorBytes(salt, 0x3a) 46 | macKey := pbkdf2HMAC(key, macSalt, 2, keySize) 47 | 48 | hashMac := hmac.New(sha1.New, macKey) 49 | hashMac.Write(page1[:len(page1)-32]) 50 | hashMac.Write([]byte{1, 0, 0, 0}) 51 | 52 | if !hmac.Equal(hashMac.Sum(nil), page1[len(page1)-32:len(page1)-12]) { 53 | return fmt.Errorf("incorrect password") 54 | } 55 | 56 | outFilePath := expPath 57 | outFile, err := os.Create(outFilePath) 58 | if err != nil { 59 | return err 60 | } 61 | defer outFile.Close() 62 | 63 | // Write SQLite file header 64 | _, err = outFile.Write(sqliteFileHeader) 65 | if err != nil { 66 | return err 67 | } 68 | 69 | block, err := aes.NewCipher(key) 70 | if err != nil { 71 | return err 72 | } 73 | 74 | page1 = buffer[16:defaultPageSize] 75 | iv := page1[len(page1)-48 : len(page1)-32] 76 | stream := cipher.NewCBCDecrypter(block, iv) 77 | decrypted := make([]byte, len(page1)-48) 78 | stream.CryptBlocks(decrypted, page1[:len(page1)-48]) 79 | _, err = outFile.Write(decrypted) 80 | if err != nil { 81 | return err 82 | } 83 | _, err = outFile.Write(page1[len(page1)-48:]) 84 | if err != nil { 85 | return err 86 | } 87 | 88 | for { 89 | n, err = fpReader.Read(buffer) 90 | if err != nil { 91 | if err == io.EOF { 92 | break 93 | } 94 | return err 95 | } else if n < defaultPageSize { 96 | return fmt.Errorf("read data to short %d", n) 97 | } 98 | 99 | iv := buffer[len(buffer)-48 : len(buffer)-32] 100 | stream := cipher.NewCBCDecrypter(block, iv) 101 | decrypted := make([]byte, len(buffer)-48) 102 | stream.CryptBlocks(decrypted, buffer[:len(buffer)-48]) 103 | _, err = outFile.Write(decrypted) 104 | if err != nil { 105 | return err 106 | } 107 | _, err = outFile.Write(buffer[len(buffer)-48:]) 108 | if err != nil { 109 | return err 110 | } 111 | } 112 | 113 | return nil 114 | } 115 | 116 | func pbkdf2HMAC(password, salt []byte, iter, keyLen int) []byte { 117 | dk := make([]byte, keyLen) 118 | loop := (keyLen + sha1.Size - 1) / sha1.Size 119 | key := make([]byte, 0, len(salt)+4) 120 | u := make([]byte, sha1.Size) 121 | for i := 1; i <= loop; i++ { 122 | key = key[:0] 123 | key = append(key, salt...) 124 | key = append(key, byte(i>>24), byte(i>>16), byte(i>>8), byte(i)) 125 | hmac := hmac.New(sha1.New, password) 126 | hmac.Write(key) 127 | digest := hmac.Sum(nil) 128 | copy(u, digest) 129 | for j := 2; j <= iter; j++ { 130 | hmac.Reset() 131 | hmac.Write(digest) 132 | digest = hmac.Sum(digest[:0]) 133 | for k, di := range digest { 134 | u[k] ^= di 135 | } 136 | } 137 | copy(dk[(i-1)*sha1.Size:], u) 138 | } 139 | return dk 140 | } 141 | 142 | func xorBytes(a []byte, b byte) []byte { 143 | result := make([]byte, len(a)) 144 | for i := range a { 145 | result[i] = a[i] ^ b 146 | } 147 | return result 148 | } 149 | 150 | /* 151 | func main() { 152 | 153 | str := "82b1a210335140a1bc8a57397391186494abe666595b4f408095538b5518f7d5" 154 | // 将十六进制字符串解码为字节 155 | password, err := hex.DecodeString(str) 156 | if err != nil { 157 | fmt.Println("解码出错:", err) 158 | return 159 | } 160 | 161 | fmt.Println(hex.EncodeToString(password)) 162 | 163 | err = decryptMsg("Media.db", password) 164 | if err != nil { 165 | fmt.Println("Error:", err) 166 | } else { 167 | fmt.Println("Decryption successful!") 168 | } 169 | } 170 | */ 171 | -------------------------------------------------------------------------------- /pkg/wechat/wechatIMGDec.go: -------------------------------------------------------------------------------- 1 | package wechat 2 | 3 | import ( 4 | "bufio" 5 | "encoding/hex" 6 | "errors" 7 | "fmt" 8 | "io" 9 | "log" 10 | "os" 11 | "path/filepath" 12 | "sync" 13 | "time" 14 | ) 15 | 16 | /* 17 | from: https://github.com/liuggchen/wechatDatDecode.git 18 | */ 19 | 20 | var imagePrefixBtsMap = make(map[string][]byte) 21 | 22 | func DecryptDatByDir(inDir, outDir string) error { 23 | 24 | startTime := time.Now() 25 | f, er := os.Open(inDir) 26 | if er != nil { 27 | fmt.Println(er.Error()) 28 | return er 29 | } 30 | readdir, er := f.Readdir(0) 31 | if er != nil { 32 | fmt.Println(er.Error()) 33 | } 34 | 35 | if stat, er := os.Stat(outDir); os.IsNotExist(er) { 36 | er := os.MkdirAll(outDir, 0755) 37 | if er != nil { 38 | return er 39 | } 40 | } else if !stat.IsDir() { 41 | return errors.New(outDir + "is file") 42 | } 43 | 44 | var taskChan = make(chan os.FileInfo, 100) 45 | 46 | go func() { 47 | for _, info := range readdir { 48 | taskChan <- info 49 | } 50 | close(taskChan) 51 | }() 52 | 53 | var wg sync.WaitGroup 54 | for i := 0; i < 10; i++ { 55 | wg.Add(1) 56 | go func() { 57 | defer wg.Done() 58 | for info := range taskChan { 59 | handlerOne(info, inDir, outDir) 60 | } 61 | }() 62 | } 63 | 64 | wg.Wait() 65 | t := time.Since(startTime).Seconds() 66 | log.Printf("\nfinished time= %v s\n", t) 67 | 68 | return nil 69 | } 70 | 71 | func DecryptDat(inFile string, outFile string) error { 72 | 73 | sourceFile, err := os.Open(inFile) 74 | if err != nil { 75 | log.Println(err.Error()) 76 | return err 77 | } 78 | 79 | var preTenBts = make([]byte, 10) 80 | _, _ = sourceFile.Read(preTenBts) 81 | decodeByte, _, er := findDecodeByte(preTenBts) 82 | if er != nil { 83 | log.Println(er.Error()) 84 | return err 85 | } 86 | 87 | distFile, er := os.Create(outFile) 88 | if er != nil { 89 | log.Println(er.Error()) 90 | return err 91 | } 92 | writer := bufio.NewWriter(distFile) 93 | _, _ = sourceFile.Seek(0, 0) 94 | var rBts = make([]byte, 1024) 95 | for { 96 | n, er := sourceFile.Read(rBts) 97 | if er != nil { 98 | if er == io.EOF { 99 | break 100 | } 101 | log.Println("error: ", er.Error()) 102 | return err 103 | } 104 | for i := 0; i < n; i++ { 105 | _ = writer.WriteByte(rBts[i] ^ decodeByte) 106 | } 107 | } 108 | _ = writer.Flush() 109 | _ = distFile.Close() 110 | _ = sourceFile.Close() 111 | // fmt.Println("output file:", distFile.Name()) 112 | 113 | return nil 114 | } 115 | 116 | func handlerOne(info os.FileInfo, dir string, outputDir string) { 117 | if info.IsDir() || filepath.Ext(info.Name()) != ".dat" { 118 | return 119 | } 120 | fmt.Println("find file: ", info.Name()) 121 | fPath := dir + "/" + info.Name() 122 | sourceFile, err := os.Open(fPath) 123 | if err != nil { 124 | fmt.Println(err.Error()) 125 | return 126 | } 127 | 128 | var preTenBts = make([]byte, 10) 129 | _, _ = sourceFile.Read(preTenBts) 130 | decodeByte, _, er := findDecodeByte(preTenBts) 131 | if er != nil { 132 | fmt.Println(er.Error()) 133 | return 134 | } 135 | 136 | distFile, er := os.Create(outputDir + "/" + info.Name()) 137 | if er != nil { 138 | fmt.Println(er.Error()) 139 | return 140 | } 141 | writer := bufio.NewWriter(distFile) 142 | _, _ = sourceFile.Seek(0, 0) 143 | var rBts = make([]byte, 1024) 144 | for { 145 | n, er := sourceFile.Read(rBts) 146 | if er != nil { 147 | if er == io.EOF { 148 | break 149 | } 150 | fmt.Println("error: ", er.Error()) 151 | return 152 | } 153 | for i := 0; i < n; i++ { 154 | _ = writer.WriteByte(rBts[i] ^ decodeByte) 155 | } 156 | } 157 | _ = writer.Flush() 158 | _ = distFile.Close() 159 | 160 | fmt.Println("output file:", distFile.Name()) 161 | } 162 | 163 | func init() { 164 | //JPEG (jpg),文件头:FFD8FF 165 | //PNG (png),文件头:89504E47 166 | //GIF (gif),文件头:47494638 167 | //TIFF (tif),文件头:49492A00 168 | //Windows Bitmap (bmp),文件头:424D 169 | const ( 170 | Jpeg = "FFD8FF" 171 | Png = "89504E47" 172 | Gif = "47494638" 173 | Tif = "49492A00" 174 | Bmp = "424D" 175 | ) 176 | JpegPrefixBytes, _ := hex.DecodeString(Jpeg) 177 | PngPrefixBytes, _ := hex.DecodeString(Png) 178 | GifPrefixBytes, _ := hex.DecodeString(Gif) 179 | TifPrefixBytes, _ := hex.DecodeString(Tif) 180 | BmpPrefixBytes, _ := hex.DecodeString(Bmp) 181 | 182 | imagePrefixBtsMap = map[string][]byte{ 183 | ".jpeg": JpegPrefixBytes, 184 | ".png": PngPrefixBytes, 185 | ".gif": GifPrefixBytes, 186 | ".tif": TifPrefixBytes, 187 | ".bmp": BmpPrefixBytes, 188 | } 189 | } 190 | 191 | func findDecodeByte(bts []byte) (byte, string, error) { 192 | for ext, prefixBytes := range imagePrefixBtsMap { 193 | deCodeByte, err := testPrefix(prefixBytes, bts) 194 | if err == nil { 195 | return deCodeByte, ext, err 196 | } 197 | } 198 | return 0, "", errors.New("decode fail") 199 | } 200 | 201 | func testPrefix(prefixBytes []byte, bts []byte) (deCodeByte byte, error error) { 202 | var initDecodeByte = prefixBytes[0] ^ bts[0] 203 | for i, prefixByte := range prefixBytes { 204 | if b := prefixByte ^ bts[i]; b != initDecodeByte { 205 | return 0, errors.New("no") 206 | } 207 | } 208 | return initDecodeByte, nil 209 | } 210 | -------------------------------------------------------------------------------- /res/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/res/logo.png -------------------------------------------------------------------------------- /res/logo_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/res/logo_128.png -------------------------------------------------------------------------------- /res/logo_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/res/logo_256.png -------------------------------------------------------------------------------- /res/result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/res/result.png -------------------------------------------------------------------------------- /res/result2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/res/result2.png -------------------------------------------------------------------------------- /res/tips.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/res/tips.png -------------------------------------------------------------------------------- /res/wechatQR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/git-jiadong/wechatDataBackup/260c7306adf67963d346f399a483775db47a9107/res/wechatQR.png -------------------------------------------------------------------------------- /wails.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://wails.io/schemas/config.v2.json", 3 | "name": "wechatDataBackup", 4 | "outputfilename": "wechatDataBackup", 5 | "frontend:install": "", 6 | "frontend:build": "", 7 | "frontend:dev:watcher": "", 8 | "frontend:dev:serverUrl": "", 9 | "author": { 10 | "name": "hal", 11 | "email": "1174221722@qq.com" 12 | } 13 | } 14 | --------------------------------------------------------------------------------