├── .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 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | # wechatDataBackup
32 |
33 | * 基于wails开发 + React前端,实现PC端微信聊天记录一键导出功能。
34 | * 导出后数据可以做永久化保存,即使微信停止支持,聊天记录也可以随时查看。
35 | * 前端界面尽量与微信界面保持一致,减少使用成本。
36 | * 理论上支持所有Windows 32/64位微信版本。
37 |
38 | 效果图如下:
39 |
40 | 
41 | 
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 | 
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 | [](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 | 
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------