├── .github └── workflows │ └── gradle-publish.yml ├── .gitignore ├── LICENSE ├── Readme.md ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── images ├── image1.png ├── image10.png ├── image11.png ├── image12.png ├── image2.png ├── image3.png ├── image4.png ├── image5.png ├── image6.png ├── image7.png ├── image8.png └── image9.png ├── settings.gradle └── src └── main ├── java ├── app │ └── protobuf │ │ └── Msg.java ├── com │ └── ppsoft1991 │ │ └── chatViewTool │ │ ├── ChatAppApplication.java │ │ ├── GlobalConfig.java │ │ ├── controller │ │ ├── ChatAppController.java │ │ └── ChatController.java │ │ ├── fx │ │ ├── Bubble.java │ │ ├── BubbleBox.java │ │ ├── MessageBox.java │ │ ├── MessageFriendsLabel.java │ │ ├── MessageSelfLabel.java │ │ ├── NoticeBox.java │ │ └── TableList.java │ │ ├── mapper │ │ ├── microMsg │ │ │ ├── ChatRoomMapper.java │ │ │ ├── ContactMapper.java │ │ │ ├── HeaderIconMapper.java │ │ │ ├── SelfUserMap.java │ │ │ └── SessionMapper.java │ │ └── msg │ │ │ ├── Msg.java │ │ │ └── MsgMapper.java │ │ └── utils │ │ ├── CommonFunc.java │ │ ├── CommonUtils.java │ │ ├── GroupProtobuf.java │ │ ├── MicroMsgDB.java │ │ ├── MsgDB.java │ │ └── WechatDb.java └── module-info.java └── resources └── com └── ppsoft1991 └── chatViewTool └── ui ├── black.png ├── chat-application.fxml ├── chat-window.fxml ├── logo.png └── main.css /.github/workflows/gradle-publish.yml: -------------------------------------------------------------------------------- 1 | name: Build, Package, and Release Project 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags: 8 | - 'v*' 9 | 10 | jobs: 11 | build-and-release: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v3 16 | 17 | - name: Set up JDK 11 18 | uses: actions/setup-java@v2 19 | with: 20 | java-version: '11' 21 | distribution: 'adopt' 22 | 23 | - name: Grant execute permission for gradlew 24 | run: chmod +x ./gradlew 25 | 26 | - name: Build with Gradle 27 | run: ./gradlew build 28 | 29 | - name: Create custom Java runtime image with jlink 30 | run: ./gradlew jlink 31 | 32 | - name: Zip the runtime image 33 | run: zip -r CodeReviewTools.zip build/image 34 | 35 | - name: Create Release 36 | id: create_release 37 | uses: actions/create-release@v1 38 | env: 39 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 40 | with: 41 | tag_name: ${{ github.event.release.tag_name }} 42 | release_name: Release ${{ github.event.release.tag_name }} 43 | draft: false 44 | prerelease: false 45 | 46 | - name: Upload Release Asset 47 | uses: actions/upload-release-asset@v1 48 | env: 49 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 50 | with: 51 | upload_url: ${{ steps.create_release.outputs.upload_url }} 52 | asset_path: ./CodeReviewTools.zip 53 | asset_name: CodeReviewTools.zip 54 | asset_content_type: application/zip 55 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | build/ 3 | !gradle/wrapper/gradle-wrapper.jar 4 | !**/src/main/**/build/ 5 | !**/src/test/**/build/ 6 | 7 | ### IntelliJ IDEA ### 8 | .idea/modules.xml 9 | .idea/jarRepositories.xml 10 | .idea/compiler.xml 11 | .idea/libraries/ 12 | *.iws 13 | *.iml 14 | *.ipr 15 | out/ 16 | !**/src/main/**/out/ 17 | !**/src/test/**/out/ 18 | 19 | ### Eclipse ### 20 | .apt_generated 21 | .classpath 22 | .factorypath 23 | .project 24 | .settings 25 | .springBeans 26 | .sts4-cache 27 | bin/ 28 | !**/src/main/**/bin/ 29 | !**/src/test/**/bin/ 30 | 31 | ### NetBeans ### 32 | /nbproject/private/ 33 | /nbbuild/ 34 | /dist/ 35 | /nbdist/ 36 | /.nb-gradle/ 37 | 38 | ### VS Code ### 39 | .vscode/ 40 | 41 | ### Mac OS ### 42 | .DS_Store -------------------------------------------------------------------------------- /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 | # ChatViewTools 2 | 3 | ChatViewTools是一款微信聊天工具搜刮工具,逆向[Ormicron/chatViewTool](https://github.com/Ormicron/chatViewTool)后更改而来 4 | 5 | 做了大量的更新和性能优化,与原版相比优化了: 6 | 7 | - 解密速度 8 | - 打开聊天窗口的速度 9 | - 聊天记录搜索速度 10 | - 展示方式,可展示群头像、群友ID 11 | 12 | # 使用说明 13 | 首先找到DBPass.Bin文件,用nodepad++打开,如果不是UTF-8编码,需要改为UTF-8 14 | ![](images/image1.png) 15 | 16 | 点击数据库解密,选择文件夹 17 | 18 | ![](images/image2.png) 19 | 20 | Console端会有输出,解密完成后自动会从微信服务器下载头像 21 | 22 | ![](images/image3.png) 23 | 24 | 搜索聊天记录 25 | ![](images/image4.png) 26 | 27 | 搜索完查看console输出 28 | 29 | ![](images/image5.png) 30 | 31 | 需要查看聊天上下文的话,复制wxid,填入搜索框,点击进入聊天 32 | 33 | ![](images/image6.png) 34 | 35 | 就能弹出消息框 36 | 37 | ![](images/image7.png) 38 | 39 | 然后将刚才的关键字重新输入, 40 | 41 | ![](images/image8.png) 42 | 43 | 可以看到共找到5条 44 | 45 | 点击下一个 46 | 47 | ![](images/image9.png) 48 | 49 | 搜索结果会高亮显示 50 | 51 | ![](images/image10.png) 52 | 53 | 也可以填入页数,直接跳转到对应页面 54 | 55 | ![](images/image11.png) 56 | 57 | 复制聊天内容 58 | 遇到需要复制的内容,双击,会在console中显示,在console中复制就好了 59 | ![](images/image12.png) 60 | 61 | 62 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'application' 4 | id 'org.javamodularity.moduleplugin' version '1.8.12' 5 | id 'org.openjfx.javafxplugin' version '0.0.13' 6 | id 'org.beryx.jlink' version '2.25.0' 7 | } 8 | 9 | group 'com.ppsoft1991.chatViewTool' 10 | version '1.0-SNAPSHOT' 11 | 12 | repositories { 13 | mavenCentral() 14 | } 15 | 16 | ext { 17 | junitVersion = '5.10.0' 18 | } 19 | 20 | sourceCompatibility = '11' 21 | targetCompatibility = '11' 22 | 23 | tasks.withType(JavaCompile) { 24 | options.encoding = 'UTF-8' 25 | } 26 | 27 | application { 28 | mainModule = 'com.ppsoft1991.chatViewTool' 29 | mainClass = 'com.ppsoft1991.chatViewTool.ChatAppApplication' 30 | } 31 | 32 | javafx { 33 | version = '17.0.6' 34 | modules = ['javafx.controls', 'javafx.fxml', 'javafx.media'] 35 | } 36 | 37 | dependencies { 38 | testImplementation("org.junit.jupiter:junit-jupiter-api:${junitVersion}") 39 | testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:${junitVersion}") 40 | 41 | implementation 'org.bouncycastle:bcprov-jdk15on:1.70' 42 | implementation 'org.xerial:sqlite-jdbc:3.34.0' 43 | implementation 'org.springframework:spring-jdbc:5.1.10.RELEASE' 44 | implementation 'org.springframework:spring-tx:5.1.10.RELEASE' 45 | implementation 'com.mchange:c3p0:0.9.5.5' 46 | implementation 'com.mchange:mchange-commons-java:0.2.20' 47 | implementation 'com.github.albfernandez:juniversalchardet:2.4.0' 48 | implementation 'com.google.protobuf:protobuf-java:4.27.1' 49 | 50 | // Include only the javase module of ZXing and exclude core to avoid conflicts 51 | implementation('com.google.zxing:javase:3.3.3') { 52 | exclude group: 'com.google.zxing', module: 'core' 53 | } 54 | } 55 | 56 | test { 57 | useJUnitPlatform() 58 | testLogging { 59 | events "passed", "skipped", "failed" 60 | } 61 | } 62 | 63 | tasks.withType(Delete) { 64 | delete rootProject.buildDir 65 | } 66 | 67 | jlink { 68 | imageZip = project.file("${buildDir}/distributions/app-${javafx.platform.classifier}.zip") 69 | options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages', '--ignore-signing-information'] 70 | launcher { 71 | name = 'app' 72 | } 73 | } 74 | 75 | jlinkZip { 76 | group = 'distribution' 77 | } 78 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ppsoft1991/ChatViewTools/ee88ed393043d675c0ef0acdc4bc96ef94fe97b3/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 84 | 85 | APP_NAME="Gradle" 86 | APP_BASE_NAME=${0##*/} 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | MAX_FD=$( ulimit -H -n ) || 147 | warn "Could not query maximum file descriptor limit" 148 | esac 149 | case $MAX_FD in #( 150 | '' | soft) :;; #( 151 | *) 152 | ulimit -n "$MAX_FD" || 153 | warn "Could not set maximum file descriptor limit to $MAX_FD" 154 | esac 155 | fi 156 | 157 | # Collect all arguments for the java command, stacking in reverse order: 158 | # * args from the command line 159 | # * the main class name 160 | # * -classpath 161 | # * -D...appname settings 162 | # * --module-path (only if needed) 163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 164 | 165 | # For Cygwin or MSYS, switch paths to Windows format before running java 166 | if "$cygwin" || "$msys" ; then 167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 169 | 170 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 171 | 172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 173 | for arg do 174 | if 175 | case $arg in #( 176 | -*) false ;; # don't mess with options #( 177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 178 | [ -e "$t" ] ;; #( 179 | *) false ;; 180 | esac 181 | then 182 | arg=$( cygpath --path --ignore --mixed "$arg" ) 183 | fi 184 | # Roll the args list around exactly as many times as the number of 185 | # args, so each arg winds up back in the position where it started, but 186 | # possibly modified. 187 | # 188 | # NB: a `for` loop captures its iteration list before it begins, so 189 | # changing the positional parameters here affects neither the number of 190 | # iterations, nor the values presented in `arg`. 191 | shift # remove old arg 192 | set -- "$@" "$arg" # push replacement arg 193 | done 194 | fi 195 | 196 | # Collect all arguments for the java command; 197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 198 | # shell script including quotes and variable substitutions, so put them in 199 | # double quotes to make sure that they get re-expanded; and 200 | # * put everything else in single quotes, so that it's not re-expanded. 201 | 202 | set -- \ 203 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 204 | -classpath "$CLASSPATH" \ 205 | org.gradle.wrapper.GradleWrapperMain \ 206 | "$@" 207 | 208 | # Use "xargs" to parse quoted args. 209 | # 210 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 211 | # 212 | # In Bash we could simply go: 213 | # 214 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 215 | # set -- "${ARGS[@]}" "$@" 216 | # 217 | # but POSIX shell has neither arrays nor command substitution, so instead we 218 | # post-process each arg (as a line of input to sed) to backslash-escape any 219 | # character that might be a shell metacharacter, then use eval to reverse 220 | # that process (while maintaining the separation between arguments), and wrap 221 | # the whole thing up as a single "set" statement. 222 | # 223 | # This will of course break if any of these variables contains a newline or 224 | # an unmatched quote. 225 | # 226 | 227 | eval "set -- $( 228 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 229 | xargs -n1 | 230 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 231 | tr '\n' ' ' 232 | )" '"$@"' 233 | 234 | exec "$JAVACMD" "$@" 235 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /images/image1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ppsoft1991/ChatViewTools/ee88ed393043d675c0ef0acdc4bc96ef94fe97b3/images/image1.png -------------------------------------------------------------------------------- /images/image10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ppsoft1991/ChatViewTools/ee88ed393043d675c0ef0acdc4bc96ef94fe97b3/images/image10.png -------------------------------------------------------------------------------- /images/image11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ppsoft1991/ChatViewTools/ee88ed393043d675c0ef0acdc4bc96ef94fe97b3/images/image11.png -------------------------------------------------------------------------------- /images/image12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ppsoft1991/ChatViewTools/ee88ed393043d675c0ef0acdc4bc96ef94fe97b3/images/image12.png -------------------------------------------------------------------------------- /images/image2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ppsoft1991/ChatViewTools/ee88ed393043d675c0ef0acdc4bc96ef94fe97b3/images/image2.png -------------------------------------------------------------------------------- /images/image3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ppsoft1991/ChatViewTools/ee88ed393043d675c0ef0acdc4bc96ef94fe97b3/images/image3.png -------------------------------------------------------------------------------- /images/image4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ppsoft1991/ChatViewTools/ee88ed393043d675c0ef0acdc4bc96ef94fe97b3/images/image4.png -------------------------------------------------------------------------------- /images/image5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ppsoft1991/ChatViewTools/ee88ed393043d675c0ef0acdc4bc96ef94fe97b3/images/image5.png -------------------------------------------------------------------------------- /images/image6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ppsoft1991/ChatViewTools/ee88ed393043d675c0ef0acdc4bc96ef94fe97b3/images/image6.png -------------------------------------------------------------------------------- /images/image7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ppsoft1991/ChatViewTools/ee88ed393043d675c0ef0acdc4bc96ef94fe97b3/images/image7.png -------------------------------------------------------------------------------- /images/image8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ppsoft1991/ChatViewTools/ee88ed393043d675c0ef0acdc4bc96ef94fe97b3/images/image8.png -------------------------------------------------------------------------------- /images/image9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ppsoft1991/ChatViewTools/ee88ed393043d675c0ef0acdc4bc96ef94fe97b3/images/image9.png -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = "WxChat" -------------------------------------------------------------------------------- /src/main/java/com/ppsoft1991/chatViewTool/ChatAppApplication.java: -------------------------------------------------------------------------------- 1 | package com.ppsoft1991.chatViewTool; 2 | 3 | import com.ppsoft1991.chatViewTool.utils.CommonUtils; 4 | import javafx.application.Application; 5 | import javafx.fxml.FXMLLoader; 6 | import javafx.scene.Scene; 7 | import javafx.scene.image.Image; 8 | import javafx.stage.Stage; 9 | 10 | import java.io.IOException; 11 | import java.util.Objects; 12 | 13 | public class ChatAppApplication extends Application { 14 | @Override 15 | public void start(final Stage stage) throws IOException { 16 | stage.setResizable(false); 17 | final FXMLLoader fxmlLoader = new FXMLLoader(CommonUtils.getResource("ui/chat-application.fxml")); 18 | stage.getIcons().add(new Image(Objects.requireNonNull(CommonUtils.resource("ui/logo.png")))); 19 | 20 | final Scene scene = new Scene(fxmlLoader.load(), 970.0, 450.0); 21 | stage.setTitle("微信数据库解密查看器 " + GlobalConfig.VERSION); 22 | final String css = CommonUtils.getResource("ui/main.css").toExternalForm(); 23 | scene.getStylesheets().add(css); 24 | stage.setScene(scene); 25 | stage.show(); 26 | } 27 | 28 | public static void main(final String[] args) { 29 | Application.launch(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/ppsoft1991/chatViewTool/GlobalConfig.java: -------------------------------------------------------------------------------- 1 | package com.ppsoft1991.chatViewTool; 2 | 3 | public class GlobalConfig { 4 | public static boolean isLinux = true; 5 | public static String VERSION = "1.0"; 6 | public static String RESOURCE = "/com/ppsoft1991/chatViewTool/"; 7 | 8 | static { 9 | if ((System.getProperty("os.name")).toLowerCase().startsWith("win")){ 10 | isLinux = false; 11 | } 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/ppsoft1991/chatViewTool/controller/ChatAppController.java: -------------------------------------------------------------------------------- 1 | package com.ppsoft1991.chatViewTool.controller; 2 | 3 | import com.ppsoft1991.chatViewTool.mapper.microMsg.HeaderIconMapper; 4 | import com.ppsoft1991.chatViewTool.utils.*; 5 | import com.ppsoft1991.chatViewTool.fx.TableList; 6 | import com.ppsoft1991.chatViewTool.mapper.microMsg.ContactMapper; 7 | import com.ppsoft1991.chatViewTool.mapper.microMsg.SessionMapper; 8 | import com.ppsoft1991.chatViewTool.mapper.msg.MsgMapper; 9 | import javafx.event.ActionEvent; 10 | import javafx.fxml.FXML; 11 | import javafx.fxml.FXMLLoader; 12 | import javafx.fxml.Initializable; 13 | import javafx.geometry.Pos; 14 | import javafx.scene.Parent; 15 | import javafx.scene.Scene; 16 | import javafx.scene.control.*; 17 | import javafx.scene.control.cell.PropertyValueFactory; 18 | import javafx.scene.image.ImageView; 19 | import javafx.scene.input.MouseEvent; 20 | import javafx.scene.layout.AnchorPane; 21 | import javafx.scene.layout.VBox; 22 | import javafx.stage.DirectoryChooser; 23 | import javafx.stage.Stage; 24 | 25 | import java.beans.PropertyVetoException; 26 | import java.io.File; 27 | import java.net.URL; 28 | import java.nio.file.Files; 29 | import java.nio.file.Paths; 30 | import java.nio.file.attribute.FileAttribute; 31 | import java.util.*; 32 | 33 | public class ChatAppController implements Initializable{ 34 | public TableColumn image; 35 | @FXML 36 | private TableView tableVw; 37 | @FXML 38 | private Label labelMsg; 39 | @FXML 40 | private TableColumn wechatId; 41 | @FXML 42 | private TableColumn userName; 43 | @FXML 44 | private TableColumn nickName; 45 | @FXML 46 | private TableColumn nameAlias; 47 | @FXML 48 | private TextField filterText; 49 | @FXML 50 | private TableColumn strContent; 51 | private String absolutePath; 52 | @FXML 53 | private CheckBox loadHd; 54 | 55 | public ChatAppController() { 56 | this.absolutePath = null; 57 | } 58 | 59 | public void initTableView() { 60 | this.image.setCellValueFactory(new PropertyValueFactory<>("image")); 61 | this.wechatId.setCellValueFactory(new PropertyValueFactory<>("wechatId")); 62 | this.userName.setCellValueFactory(new PropertyValueFactory<>("userName")); 63 | this.nickName.setCellValueFactory(new PropertyValueFactory<>("nickName")); 64 | this.nameAlias.setCellValueFactory(new PropertyValueFactory<>("alias")); 65 | this.strContent.setCellValueFactory(new PropertyValueFactory<>("strContent")); 66 | } 67 | 68 | public void pushToView(final String header, final String strUserName, final String userName, final String strNickName, final String alias, final String strContent, final String contentFilter) { 69 | final TableList tabList = new TableList(header, strUserName, userName, strNickName, alias, strContent, contentFilter); 70 | this.tableVw.getItems().add(tabList); 71 | } 72 | 73 | @FXML 74 | public void showChatWindow(final MouseEvent event) throws Exception { 75 | if (event.getClickCount() == 2) { 76 | this.labelMsg.setText(""); 77 | MsgDB.init(this.absolutePath + "/MSG_ALL.db"); 78 | final String wxid = this.tableVw.getSelectionModel().getSelectedItem().getWechatId(); 79 | showChatWindow(wxid); 80 | } 81 | } 82 | 83 | public void showChatWindow(String wxid){ 84 | try { 85 | final FXMLLoader loader = new FXMLLoader(getClass().getResource("/com/ppsoft1991/chatViewTool/ui/chat-window.fxml")); 86 | final Parent root = loader.load(); 87 | final Scene scene = new Scene(root); 88 | final Stage stage = new Stage(); 89 | stage.setScene(scene); 90 | // stage.setResizable(false); 91 | final String count = String.valueOf(MsgDB.getMsgCount(wxid)); 92 | stage.setTitle(wxid + "("+ CommonUtils.getWxName(wxid) +")"+"-消息数:" + count); 93 | final ChatController chatWindowCtr = loader.getController(); 94 | chatWindowCtr.setInitValue(wxid, this.loadHd.isSelected(), Integer.parseInt(count), this.absolutePath); 95 | stage.showAndWait(); 96 | 97 | stage.setOnCloseRequest((WindowEvent)-> System.gc()); 98 | } 99 | catch (Exception err) { 100 | if (err.getMessage() == null) { 101 | System.out.println(":)"); 102 | } 103 | } 104 | } 105 | 106 | public void onMsgInfoButtonClick() throws Exception { 107 | final File dir = openFolderChooser("选择已解密的数据库文件目录"); 108 | if (dir != null) { 109 | this.getDbFileList(dir.getAbsolutePath(), false); 110 | this.absolutePath = dir.getAbsolutePath(); 111 | MicroMsgDB.init(this.absolutePath+"/MicroMsg.db_dec.db"); 112 | // 清空缓存 113 | CommonUtils.WXID_MAP = new HashMap<>(); 114 | CommonUtils.WXHEADER_MAP = new HashMap<>(); 115 | this.getSessionList(); 116 | } 117 | } 118 | 119 | public void onMsgInfoLoad(File dir) throws Exception { 120 | if (dir != null) { 121 | this.getDbFileList(dir.getAbsolutePath(), false); 122 | this.absolutePath = dir.getAbsolutePath(); 123 | MicroMsgDB.init(this.absolutePath+"/MicroMsg.db_dec.db"); 124 | this.getSessionList(); 125 | } 126 | } 127 | 128 | private void getSessionList() throws Exception { 129 | this.initTableView(); 130 | MicroMsgDB.init(this.absolutePath+"/MicroMsg.db_dec.db"); 131 | // MicroMsgDB.getAllUserHeadUrl(absolutePath); 132 | CommonUtils.SELF_WXID = MicroMsgDB.getSelfUserName(); 133 | List sessionMappers = MicroMsgDB.queryStrUsrName(); 134 | // 获取所有用户的wechatid,来判断有没有有头像 135 | HashMap wechatHeadImage = new HashMap<>(); 136 | List waitDownImage = new ArrayList<>(); 137 | if (!sessionMappers.isEmpty()){ 138 | for (SessionMapper mapper: sessionMappers){ 139 | String wxid = mapper.getStrUsrName(); 140 | final String imgPath = absolutePath + "/.imgcache/" + wxid + ".jpg"; 141 | if (Files.exists(Paths.get(imgPath))) { 142 | String headerImgPath = "file:"+imgPath; 143 | wechatHeadImage.put(wxid, headerImgPath); 144 | CommonUtils.WXHEADER_MAP.put(wxid, headerImgPath); 145 | }else { 146 | wechatHeadImage.put(wxid, CommonUtils.BLACK_Header); 147 | waitDownImage.add(wxid); 148 | } 149 | } 150 | } 151 | CommonUtils.SELF_WX_HEAD = CommonUtils.WXHEADER_MAP.get(CommonUtils.SELF_WXID); 152 | List allSessionMappers = MicroMsgDB.queryALLSession(); 153 | Files.createDirectories(Paths.get(this.absolutePath + "/.imgcache")); 154 | if (!allSessionMappers.isEmpty()) { 155 | this.loadHd.setVisible(true); 156 | this.tableVw.getItems().clear(); 157 | for (SessionMapper mapper: allSessionMappers){ 158 | final String strUsrName = mapper.getStrUsrName(); 159 | final String strContent = mapper.getStrContent(); 160 | final Integer Reserved2 = mapper.getReserved2(); 161 | if (!strUsrName.startsWith("gh_") && strContent.length() > 0 && !strUsrName.startsWith("@publicUser") && Reserved2 != 5) { 162 | final ContactMapper userInfo = this.getUserInfo(strUsrName); 163 | if (userInfo == null) { 164 | continue; 165 | } 166 | String wxName = ""; 167 | if (userInfo.getUserName()!=null){ 168 | if (!Objects.equals(userInfo.getAlias(), "")) { 169 | wxName = userInfo.getNickName() + "(" + userInfo.getAlias() + ")"; 170 | }else { 171 | wxName = userInfo.getNickName(); 172 | } 173 | } 174 | CommonUtils.WXID_MAP.put(userInfo.getUserName(), wxName); 175 | this.pushToView(wechatHeadImage.get(strUsrName), userInfo.getUserName(), userInfo.getAlias(), userInfo.getNickName(), userInfo.getRemark(), strContent, ""); 176 | } 177 | } 178 | // 遍历所有ID,下载头像 179 | (new Thread(()->{ 180 | for (String wxid: waitDownImage){ 181 | try { 182 | HeaderIconMapper userHeadUrl = MicroMsgDB.getUserHeadUrl(wxid); 183 | if (userHeadUrl.getSmallHeadImgUrl() != null) { 184 | // 如果有高清图就下高清 【-】 高清很多无法查看,撤回 185 | // if (headUrl[1]!=null && headUrl[1].startsWith("http")){ 186 | // headUrl[0] = headUrl[1]; 187 | // } 188 | final String headImagePath = absolutePath + "/.imgcache/" + wxid + ".jpg"; 189 | ChatController.downloadImg(userHeadUrl.getSmallHeadImgUrl(), headImagePath); 190 | List lst = this.tableVw.getItems(); 191 | for (int i = 0; i < lst.size(); i++) { 192 | if (wxid.equals(lst.get(i).getWechatId())) { 193 | lst.get(i).setImage("file:" + absolutePath + "/.imgcache/" + wxid + ".jpg"); 194 | this.tableVw.getItems().set(i, lst.get(i)); 195 | break; 196 | } 197 | } 198 | } 199 | }catch (Exception e){ 200 | System.out.println("[-] 下载头像错误,网络问题"); 201 | } 202 | 203 | } 204 | image.setCellFactory(param -> new TableCell(){ 205 | @Override 206 | public void updateItem(Object item, boolean empty) { 207 | super.updateItem(item, empty); 208 | if (item!=null&&!empty){ 209 | String image = item.toString(); 210 | ImageView view = ChatController.setImage(image); 211 | this.setGraphic(view); 212 | this.setAlignment(Pos.CENTER); 213 | this.setOnMouseClicked(event -> { 214 | showImage(image); 215 | }); 216 | } 217 | }; 218 | }); 219 | })).start(); 220 | } 221 | else { 222 | this.labelMsg.setText("查询数据库失败!"); 223 | } 224 | this.labelMsg.setText("加载完成!"); 225 | } 226 | 227 | public void showImage(String image){ 228 | AnchorPane root = new AnchorPane(); 229 | ScrollPane scrollPane = new ScrollPane(); 230 | root.getChildren().add(scrollPane); 231 | VBox vBox = new VBox(); 232 | vBox.setPrefHeight(480); 233 | vBox.setPrefWidth(480); 234 | vBox.setAlignment(Pos.CENTER); 235 | scrollPane.setContent(vBox); 236 | 237 | ImageView imgHead = new ImageView(image); 238 | imgHead.setFitHeight(480); 239 | imgHead.setFitWidth(480); 240 | vBox.getChildren().add(imgHead); 241 | 242 | Scene scene = new Scene(root); 243 | Stage stage = new Stage(); 244 | stage.setScene(scene); 245 | stage.show(); 246 | } 247 | 248 | @FXML 249 | public void onSearchButtonClicked() throws Exception { 250 | String user; 251 | String chatText; 252 | int count = 0; 253 | HashSet userList = new HashSet<>(); 254 | String text = this.filterText.getText(); 255 | MsgDB.init(this.absolutePath + "/MSG_ALL.db"); 256 | List msgMappers = MsgDB.searchMessageFromText(text); 257 | if (msgMappers.size()>0){ 258 | try { 259 | for(MsgMapper msgMapper: msgMappers){ 260 | user = msgMapper.getStrTalker(); 261 | chatText = msgMapper.getStrContent(); 262 | System.out.println("[+]\t搜索到用户\t"+user+" -> "+chatText); 263 | count = count+1; 264 | userList.add(user); 265 | } 266 | }catch (NullPointerException exception1){ 267 | System.out.println("[!] 未加载数据库!"); 268 | }catch (Exception exception2){ 269 | System.out.println("[!] 数据库未索引,开始自动索引,重新点击搜索"); 270 | } 271 | System.out.println("[!] 搜索结束,共涉及"+userList.size()+"个用户"); 272 | System.out.println("-> "+userList); 273 | } 274 | 275 | } 276 | 277 | 278 | public void onButtonClickDec() throws Exception { 279 | final File dir = openFolderChooser("选择包含有秘钥以及数据库的文件夹"); 280 | if (dir != null) { 281 | final String Path = dir.getAbsolutePath(); 282 | final String keyPath = Path + "/DBPass.Bin"; 283 | if (this.fileIsExists(Path + "/DBPass.Bin")) { 284 | if (this.fileIsExists(Path + "/MSG0.db")) { 285 | final List dbList = this.getDbFileList(Path, true); 286 | for (final String dbName : dbList) { 287 | final String dbPath = Path + "/" + dbName; 288 | this.decryptDb(keyPath, dbPath); 289 | } 290 | this.labelMsg.setText("完成,共解密数据库" + dbList.size() + "个"); 291 | CommonFunc.mergeDb(dbList, Path); 292 | // 解密完,直接打开 293 | onMsgInfoLoad(dir); 294 | } 295 | else { 296 | this.labelMsg.setText("数据库文件缺失!"); 297 | } 298 | } 299 | else { 300 | this.labelMsg.setText("秘钥文件不存在!"); 301 | } 302 | } 303 | } 304 | 305 | 306 | 307 | public void decryptDb(final String inKeyPath, final String inDbPath){ 308 | try { 309 | if (!inDbPath.contains("MSG_ALL")) { 310 | (new WechatDb(inKeyPath, inDbPath)).decryptDb(); 311 | } 312 | }catch (java.security.InvalidKeyException ignored){ 313 | System.out.println("[-] 出错,参考JCE无限制权限策略文件 参考https://blog.csdn.net/weixin_44857862/article/details/124589684"); 314 | }catch (NumberFormatException e){ 315 | System.out.println("[-] 出错,检查DBPass.Bin文件编码是否为UTF-8"); 316 | }catch (Exception e){ 317 | e.printStackTrace(); 318 | } 319 | 320 | } 321 | 322 | 323 | private ContactMapper getUserInfo(final String wxid){ 324 | ContactMapper contactMapper = null; 325 | try { 326 | contactMapper = MicroMsgDB.queryUserContact(wxid); 327 | } 328 | catch (Exception e) { 329 | System.out.println("[-]" + e.getMessage() + ": " + " Get user Info Error"); 330 | } 331 | return contactMapper; 332 | } 333 | 334 | public static File openFolderChooser(final String wTitle) { 335 | final DirectoryChooser directoryChooser = new DirectoryChooser(); 336 | directoryChooser.setTitle(wTitle); 337 | return directoryChooser.showDialog(new Stage()); 338 | } 339 | 340 | 341 | public List getDbFileList(final String path, final boolean encrypted) { 342 | final List deFileList = new ArrayList<>(); 343 | final List enFileList = new ArrayList<>(); 344 | final File dir = new File(path); 345 | final int len = Objects.requireNonNull(dir.list()).length; 346 | final String[] files = dir.list(); 347 | for (int i = 0; i < len; ++i) { 348 | assert files != null; 349 | if (files[i].endsWith("_dec.db")) { 350 | deFileList.add(files[i]); 351 | } 352 | else if (files[i].endsWith(".db")) { 353 | enFileList.add(files[i]); 354 | } 355 | } 356 | if (encrypted) { 357 | return enFileList; 358 | } 359 | return deFileList; 360 | } 361 | 362 | public boolean fileIsExists(final String Path) { 363 | return new File(Path).exists(); 364 | } 365 | 366 | @Override 367 | public void initialize(URL location, ResourceBundle resources) { 368 | loadHd.setSelected(true); 369 | } 370 | 371 | public void onUserChat(ActionEvent actionEvent) throws Exception { 372 | final String text = filterText.getText(); 373 | if (!text.isEmpty()){ 374 | MsgDB.init(this.absolutePath + "/MSG_ALL.db"); 375 | showChatWindow(text); 376 | }else { 377 | labelMsg.setText("[-] 搜索框输入的是空的呢!"); 378 | } 379 | } 380 | } 381 | -------------------------------------------------------------------------------- /src/main/java/com/ppsoft1991/chatViewTool/controller/ChatController.java: -------------------------------------------------------------------------------- 1 | package com.ppsoft1991.chatViewTool.controller; 2 | 3 | import com.ppsoft1991.chatViewTool.fx.BubbleBox; 4 | import com.ppsoft1991.chatViewTool.fx.MessageBox; 5 | import com.ppsoft1991.chatViewTool.fx.NoticeBox; 6 | import com.ppsoft1991.chatViewTool.mapper.microMsg.ChatRoomMapper; 7 | import com.ppsoft1991.chatViewTool.mapper.msg.MsgMapper; 8 | import com.ppsoft1991.chatViewTool.utils.CommonUtils; 9 | import com.ppsoft1991.chatViewTool.utils.MicroMsgDB; 10 | import com.ppsoft1991.chatViewTool.utils.MsgDB; 11 | import javafx.application.Platform; 12 | import javafx.collections.ObservableList; 13 | import javafx.event.ActionEvent; 14 | import javafx.fxml.FXML; 15 | import javafx.geometry.Insets; 16 | import javafx.scene.Node; 17 | import javafx.scene.control.Button; 18 | import javafx.scene.control.Label; 19 | import javafx.scene.control.ScrollPane; 20 | import javafx.scene.control.TextField; 21 | import javafx.scene.image.ImageView; 22 | import javafx.scene.layout.HBox; 23 | import javafx.scene.layout.Pane; 24 | import javafx.scene.layout.VBox; 25 | 26 | import java.beans.PropertyVetoException; 27 | import java.io.FileOutputStream; 28 | import java.io.InputStream; 29 | import java.net.URL; 30 | import java.net.URLConnection; 31 | import java.sql.SQLException; 32 | import java.text.SimpleDateFormat; 33 | import java.util.*; 34 | 35 | 36 | public class ChatController { 37 | 38 | public Label searchNumber; 39 | private ChatAppController chatApp; 40 | public Button searchButton; 41 | public Button jumpButton; 42 | public Button jumoSearchButton; 43 | @FXML 44 | private VBox listMsg; 45 | @FXML 46 | public Pane toolPane; 47 | @FXML 48 | private Label labelPage; 49 | @FXML 50 | private Button lastButton; 51 | @FXML 52 | private ScrollPane scrollPane; 53 | @FXML 54 | private Button nextButton; 55 | private boolean showHeader; 56 | public static String absolutePath; 57 | private int currentPage; 58 | private int msgCount; 59 | private String wxid; 60 | @FXML 61 | public TextField FieldText; 62 | public String filterStr; 63 | 64 | private ArrayList chatLocalId; 65 | private ArrayList chatLocalIdPage; 66 | private int jumpPoint=-1; 67 | 68 | 69 | final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 70 | 71 | private HashMap GROUP_USER_HEADER = new HashMap<>(); 72 | private HashMap GROUP_USER_NICKNAME = new HashMap<>(); 73 | 74 | private List wechatId; 75 | private List nickName; 76 | 77 | 78 | public ChatController() { 79 | this.currentPage = 0; 80 | this.msgCount = 0; 81 | this.filterStr = null; 82 | } 83 | 84 | public static ImageView setImage(final String uri) { 85 | ImageView imgHead = new ImageView(uri); 86 | imgHead.setFitHeight(60.0); 87 | imgHead.setFitWidth(60.0); 88 | return imgHead; 89 | } 90 | 91 | public void getBasicInfo() throws PropertyVetoException { 92 | ChatRoomMapper chatRoomMapper = MicroMsgDB.queryChatRoom(wxid); 93 | wechatId = Arrays.asList(chatRoomMapper.getUserNameList().split("\\^G")); 94 | nickName = Arrays.asList(chatRoomMapper.getDisplayNameList().split("\\^G")); 95 | System.out.println(String.join(",",wechatId)); 96 | System.out.println(String.join(",",nickName)); 97 | System.out.println(wechatId.size()); 98 | System.out.println(nickName.size()); 99 | for (String wx:wechatId){ 100 | CommonUtils.put(wx, CommonUtils.WXHEADER_MAP, GROUP_USER_HEADER); 101 | CommonUtils.put(wx, CommonUtils.WXID_MAP, GROUP_USER_NICKNAME); 102 | } 103 | } 104 | 105 | public void getGroupUser(){ 106 | // 聊天室,多搜索一个用户名显示 107 | if (wxid.contains("@chatroom")){ 108 | try { 109 | 110 | if (wechatId.size()==nickName.size()) { 111 | ArrayList list = new ArrayList<>(); 112 | for (int i = 0; i < wechatId.size(); i++) { 113 | String id = wechatId.get(i); 114 | String nick = nickName.get(i); 115 | list.add(id+" (" + nickName.get(i) + ")"); 116 | if (!GROUP_USER_NICKNAME.containsKey(id)){ 117 | GROUP_USER_NICKNAME.put(id, nick); 118 | } 119 | } 120 | String result = String.join(",", list); 121 | listMsg.getChildren().add(new NoticeBox(result)); 122 | }else { 123 | String result = String.join(",", wechatId); 124 | listMsg.getChildren().add(new NoticeBox(result)); 125 | } 126 | }catch (Exception ignored){ 127 | listMsg.getChildren().add(new NoticeBox("未读到群成员或者群成员数据库MicroMsg解析失败!")); 128 | } 129 | } 130 | } 131 | 132 | @FXML 133 | public void InsertContent(final String strContent,String wxid, final String createDate, final String selfSend, String image) { 134 | showChatContent(createDate,wxid, strContent, "1".equals(selfSend), setImage(image)); 135 | } 136 | 137 | public void setInitValue(final String wxid, final boolean loadHeadImg, final int msgCount, final String path) throws SQLException, PropertyVetoException { 138 | this.showHeader = loadHeadImg; 139 | absolutePath = path; 140 | this.msgCount = msgCount / 500 + 1; 141 | this.wxid = wxid; 142 | GROUP_USER_HEADER = new HashMap<>(); 143 | GROUP_USER_NICKNAME = new HashMap<>(); 144 | if (wxid.contains("@chatroom")){ 145 | getBasicInfo(); 146 | } 147 | this.getChatInfo(); 148 | } 149 | 150 | public String getUserHeader(String wxid){ 151 | if (GROUP_USER_HEADER.containsKey(wxid)){ 152 | return GROUP_USER_HEADER.get(wxid); 153 | }else { 154 | return CommonUtils.BLACK_Header; 155 | } 156 | } 157 | 158 | public String getNickName(String wxid){ 159 | if (GROUP_USER_NICKNAME.containsKey(wxid)){ 160 | return GROUP_USER_NICKNAME.get(wxid)+"("+wxid+")"; 161 | } 162 | return wxid; 163 | } 164 | 165 | public void getChatInfo() throws SQLException, PropertyVetoException { 166 | if (currentPage!=jumpPoint) { 167 | this.listMsg.getChildren().clear(); 168 | List msgMappers = MsgDB.queryChatMsgLimit(wxid, currentPage); 169 | String headPath = CommonUtils.BLACK_Header; 170 | final int count = this.currentPage + 1; 171 | if (this.msgCount > 1) { 172 | this.lastButton.setDisable(false); 173 | this.nextButton.setDisable(false); 174 | this.labelPage.setText("page " + count + " of " + this.msgCount); 175 | } 176 | if (this.showHeader) { 177 | headPath = CommonUtils.WXHEADER_MAP.get(wxid); 178 | } 179 | if (count == 1) { 180 | getGroupUser(); 181 | } 182 | if (!msgMappers.isEmpty()) { 183 | for (MsgMapper msgMapper : msgMappers) { 184 | String res = CommonUtils.parseChatStr(String.valueOf(msgMapper.getType()), msgMapper); 185 | if (res.startsWith("[系统消息]")) { 186 | listMsg.getChildren().add(new NoticeBox(res)); 187 | } else { 188 | String tmpName = ""; 189 | if (wxid.contains("@chatroom") && 1 != (msgMapper.getIsSender())) { 190 | try { 191 | tmpName = CommonUtils.getWxid(msgMapper.getBytesExtra()); 192 | } catch (Exception exception) { 193 | System.out.println("[-] Get group username error!!"); 194 | } 195 | headPath = getUserHeader(tmpName); 196 | tmpName = getNickName(tmpName); 197 | } 198 | InsertContent(res, tmpName, msgMapper.getCreateTime() + "", String.valueOf(msgMapper.getIsSender()), headPath); 199 | } 200 | } 201 | } 202 | } 203 | // 说明有搜索到聊天内容 204 | if (chatLocalIdPage!=null && chatLocalIdPage.contains(currentPage)){ 205 | cacheBubblePod(); 206 | } 207 | } 208 | 209 | public static String downloadImg(String url, final String fileName) { 210 | url = url.replace("http:", "https:"); 211 | if (!url.isEmpty()) { 212 | System.out.println("Downloading:" + url); 213 | try { 214 | final URL httpUrl = new URL(url); 215 | final URLConnection conn = httpUrl.openConnection(); 216 | final InputStream inputStream = conn.getInputStream(); 217 | final FileOutputStream fos = new FileOutputStream(fileName); 218 | int i; 219 | while ((i = inputStream.read()) != -1) { 220 | fos.write(i); 221 | } 222 | inputStream.close(); 223 | } catch (Exception e) { 224 | System.out.println("[-] download url error: " + url + e.getMessage()); 225 | } 226 | return CommonUtils.BLACK_Header; 227 | }return null; 228 | } 229 | 230 | 231 | 232 | @FXML 233 | public void onSearchButtonClick() throws PropertyVetoException, SQLException { 234 | this.filterStr = this.FieldText.getText(); 235 | if (this.filterStr.isEmpty()) { 236 | return; 237 | } 238 | jumpPoint = 0; 239 | chatLocalId = new ArrayList<>(); 240 | List> ids = MsgDB.searchFilterMessageFromWxid(wxid, filterStr); 241 | if (!ids.isEmpty()){ 242 | for (Map next : ids) { 243 | chatLocalId.add(Integer.parseInt(String.valueOf(next.get("id")))); 244 | } 245 | System.out.println("[+] find "+chatLocalId.size()+" chat record in total"); 246 | chatLocalIdPage = new ArrayList<>(); 247 | for (int i:chatLocalId){ 248 | chatLocalIdPage.add(getChatPage(i)); 249 | } 250 | System.out.println("in the page : "+chatLocalIdPage); 251 | } 252 | onNextButtonClicked(); 253 | } 254 | 255 | public int getChatPage(int id){ 256 | return (id / 500)+1; 257 | } 258 | 259 | private void setChatHighlight(int number){ 260 | Node node = listMsg.getChildren().get(number-1); 261 | listMsg.getChildren().get(number-1).setStyle("-fx-background-color: #c0c0c0"); 262 | if (node instanceof HBox) { 263 | for (final Node nodeIn : ((HBox)node).getChildren()) { 264 | if (nodeIn instanceof Label) { 265 | final Label tempLabel = (Label)nodeIn; 266 | System.out.println("匹配到:" + tempLabel.getText()); 267 | } 268 | } 269 | } 270 | } 271 | 272 | private void cacheBubblePod() { 273 | // 获取相对页数 274 | ArrayList pageList = new ArrayList<>(); 275 | int page=0; 276 | for (int i1=0;i1500){ 280 | page=page%500; 281 | } 282 | pageList.add(page); 283 | } 284 | } 285 | 286 | // 给相对页数着色 287 | for (int i2=0;i2 children = listMsg.getChildren(); 297 | if (number < 0 || number >= children.size()) { 298 | return; // 索引越界时直接返回 299 | } 300 | listMsg.applyCss(); 301 | listMsg.layout(); 302 | Node targetNode = children.get(number); 303 | double targetY = 0; 304 | for (int i = 0; i < number; i++) { 305 | targetY += children.get(i).getBoundsInParent().getHeight() + listMsg.getSpacing(); 306 | } 307 | double nodeHeight = targetNode.getBoundsInParent().getHeight(); 308 | 309 | double scrollHeight = scrollPane.getContent().getBoundsInParent().getHeight(); 310 | double viewportHeight = scrollPane.getViewportBounds().getHeight(); 311 | 312 | // 置中目标节点 313 | double newVvalue = (targetY + 0.5 * nodeHeight - 0.5 * viewportHeight) / (scrollHeight - viewportHeight); 314 | newVvalue = Math.max(0, Math.min(newVvalue, 1)); // 保证滚动值在合法范围内 315 | 316 | double finalNewVvalue = newVvalue; 317 | scrollPane.setVvalue(finalNewVvalue); 318 | searchNumber.setText("[page of "+jumpPoint+ ","+number+"]"); 319 | 320 | } 321 | 322 | 323 | @FXML 324 | private void onLastButtonClicked() throws SQLException, PropertyVetoException { 325 | if (currentPage>=0){ 326 | this.listMsg.getChildren().clear(); 327 | currentPage = currentPage-1; 328 | getChatInfo(); 329 | this.scrollPane.setVvalue(0.0); 330 | } 331 | System.gc(); 332 | } 333 | 334 | @FXML 335 | private void onNextButtonClicked() throws SQLException, PropertyVetoException { 336 | if (currentPage<=msgCount){ 337 | currentPage = currentPage+1; 338 | getChatInfo(); 339 | this.scrollPane.setVvalue(0.0); 340 | } 341 | System.gc(); 342 | } 343 | 344 | public void showChatContent(String createDate,String wxid, String strContent, boolean selfSend, ImageView imgHead){ 345 | // final String message = wxid+" [" + format.format(new Date(Integer.parseInt(createDate) * 1000L)) + "]\r\n"+strContent; 346 | //MessageBox messageBox = new MessageBox(message, selfSend, imgHead); 347 | //messageBox.prefWidthProperty().bind(scrollPane.widthProperty().subtract(20)); 348 | final String message = wxid+" [" + format.format(new Date(Integer.parseInt(createDate) * 1000L)) + "]"; 349 | BubbleBox bubbleBox = new BubbleBox(strContent, message, selfSend, imgHead); 350 | bubbleBox.prefWidthProperty().bind(scrollPane.widthProperty().subtract(20)); 351 | listMsg.getChildren().add(bubbleBox); 352 | } 353 | 354 | public void onJumpButtonClick(ActionEvent actionEvent) throws SQLException, PropertyVetoException { 355 | int i = Integer.parseInt(FieldText.getText()); 356 | if (i>=0 && i<=msgCount){ 357 | this.listMsg.getChildren().clear(); 358 | currentPage = i-1; 359 | this.scrollPane.setVvalue(0.0); 360 | getChatInfo(); 361 | } 362 | System.gc(); 363 | } 364 | 365 | public void jumpToPage(int pageNumber) throws SQLException, PropertyVetoException { 366 | if (pageNumber>=0 && pageNumber<=msgCount){ 367 | this.listMsg.getChildren().clear(); 368 | currentPage = pageNumber-1; 369 | this.scrollPane.setVvalue(0.0); 370 | getChatInfo(); 371 | } 372 | System.gc(); 373 | } 374 | 375 | public void onNextSearchButtonClick(ActionEvent actionEvent) throws PropertyVetoException, SQLException { 376 | if (chatLocalIdPage == null) { 377 | onSearchButtonClick(); 378 | } else { 379 | if (jumpPoint >= chatLocalIdPage.size()) { 380 | jumpPoint = 0; 381 | } 382 | Integer jumpTo = chatLocalIdPage.get(jumpPoint); 383 | jumpToPage(jumpTo); 384 | int page = chatLocalId.get(jumpPoint); 385 | if (page > 500) { 386 | page = page % 500; 387 | } 388 | setChatHighlight(page); 389 | //setChatScrollPane(chatLocalId.get(jumpPoint)); 390 | setChatScrollPane(page-1); 391 | System.out.println("[+] jump to " + jumpTo + " in the page " + page); 392 | jumpPoint = jumpPoint + 1; 393 | } 394 | } 395 | } 396 | -------------------------------------------------------------------------------- /src/main/java/com/ppsoft1991/chatViewTool/fx/Bubble.java: -------------------------------------------------------------------------------- 1 | package com.ppsoft1991.chatViewTool.fx; 2 | 3 | import javafx.scene.Group; 4 | import javafx.scene.control.Label; 5 | import javafx.scene.paint.Color; 6 | import javafx.scene.paint.Paint; 7 | import javafx.scene.shape.Rectangle; 8 | import javafx.scene.text.Font; 9 | import javafx.scene.text.Text; 10 | 11 | public class Bubble extends Group { 12 | 13 | private static final int p = 10; // Padding around the text 14 | private static final int s = 2; // Side space around text 15 | private static final int ps2 = 2*(p+s); 16 | private static final int p2 = 2*p; 17 | private static final int pm = 10; // Padding around meta text 18 | private static final int pm2 = 2*pm; 19 | private static final int sm = 2; // Side space around meta text 20 | private static final Font textFont = Font.font("Arial", 14); 21 | private static final Paint textColor = Color.WHITE; 22 | private static final Font metaFont = Font.font("Arial", 10); 23 | private static final Paint metaColor = Color.LIGHTGRAY; 24 | 25 | private Rectangle r; 26 | 27 | private Paint bubbleColor = Color.rgb(0, 126, 229); 28 | 29 | private int edgeRadius = 30; 30 | 31 | private double maxWidth = 260; // Default max width for the bubble 32 | 33 | public Bubble(String text) { 34 | super(); 35 | init(0, 0, text, ""); 36 | } 37 | 38 | public Bubble(String text, String meta, boolean quadratic) { 39 | super(); 40 | init(0, 0, text, meta); 41 | if (quadratic) { 42 | r.setHeight(r.getWidth()); 43 | } 44 | } 45 | 46 | public Bubble(String text, boolean quadratic) { 47 | this(text, "", quadratic); 48 | } 49 | 50 | public Bubble(String text, String meta) { 51 | super(); 52 | init(0, 0, text, meta); 53 | } 54 | 55 | public Bubble(int x, int y, String text, String meta) { 56 | super(); 57 | init(x, y, text, meta); 58 | } 59 | 60 | private void init(int x, int y, String text, String meta) { 61 | Text temp = new Text(text); 62 | temp.setFont(textFont); 63 | temp.setWrappingWidth(maxWidth - ps2); // Set wrapping width for measurement 64 | 65 | Label l = new Label(text); 66 | l.setFont(textFont); 67 | l.setTextFill(textColor); 68 | l.setWrapText(true); // Enable text wrapping 69 | l.setMaxWidth(maxWidth - ps2); // Set maximum width for text 70 | l.setTranslateX(x + p + s); 71 | l.setTranslateY(y + p); 72 | 73 | Text tmp = new Text(meta); 74 | tmp.setFont(metaFont); 75 | Label m = new Label(meta); 76 | m.setFont(metaFont); 77 | m.setTextFill(metaColor); 78 | m.setTranslateX(x + p + s); 79 | m.setTranslateY(y + temp.getLayoutBounds().getHeight() + pm2); 80 | 81 | int w = (int) maxWidth; 82 | double wTmp = tmp.getLayoutBounds().getWidth(); 83 | if (wTmp > w) { 84 | w = (int) wTmp + pm2; 85 | } 86 | int h = (int) (temp.getLayoutBounds().getHeight() + tmp.getLayoutBounds().getHeight() + p2 + pm2); 87 | 88 | r = new Rectangle(x, y, w, h); 89 | r.setArcHeight(edgeRadius); 90 | r.setArcWidth(edgeRadius); 91 | r.setFill(bubbleColor); 92 | 93 | getChildren().addAll(r, l, m); 94 | } 95 | 96 | 97 | 98 | 99 | public void setMaxWidth(double maxWidth) { 100 | this.maxWidth = maxWidth; 101 | // Adjust layout based on new width if necessary 102 | } 103 | 104 | public double getMaxWidth() { 105 | return this.maxWidth; 106 | } 107 | 108 | public void setBubbleColor(Color color) { 109 | bubbleColor = color; 110 | } 111 | 112 | // Other getters and setters remain the same 113 | } 114 | -------------------------------------------------------------------------------- /src/main/java/com/ppsoft1991/chatViewTool/fx/BubbleBox.java: -------------------------------------------------------------------------------- 1 | package com.ppsoft1991.chatViewTool.fx; 2 | 3 | import com.ppsoft1991.chatViewTool.controller.ChatController; 4 | import com.ppsoft1991.chatViewTool.utils.CommonUtils; 5 | import javafx.geometry.Insets; 6 | import javafx.geometry.Pos; 7 | import javafx.scene.Node; 8 | import javafx.scene.control.Label; 9 | import javafx.scene.image.ImageView; 10 | import javafx.scene.input.MouseEvent; 11 | import javafx.scene.layout.HBox; 12 | import javafx.scene.layout.Priority; 13 | import javafx.scene.media.Media; 14 | import javafx.scene.media.MediaPlayer; 15 | import javafx.scene.paint.Color; 16 | 17 | import java.io.File; 18 | 19 | public class BubbleBox extends HBox { 20 | private static final Insets insets = new Insets(10.0, 5.0, 10.0, 5.0); 21 | 22 | public BubbleBox(String message,String meta,boolean selfSend, ImageView imgHead){ 23 | super(); 24 | setPadding(insets); 25 | setSpacing(10.0); 26 | ImageView head = null; 27 | Bubble bubbleBox = new Bubble(message, meta); 28 | HBox.setHgrow(bubbleBox, Priority.ALWAYS); 29 | if (selfSend){ 30 | bubbleBox.setBubbleColor(Color.rgb(84, 225, 81)); 31 | head = CommonUtils.selfImage(); 32 | this.setAlignment(Pos.TOP_RIGHT); 33 | this.getChildren().addAll(bubbleBox, head); 34 | }else { 35 | head = imgHead; 36 | this.getChildren().addAll(head, bubbleBox); 37 | } 38 | // 新增点击播放 39 | this.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> { 40 | if (event.getClickCount()==2){ 41 | for (Node n:getChildren()){ 42 | if (n instanceof Label){ 43 | String[] s = ((Label)n).getText().split("\n"); 44 | if (s.length==2 && s[1].startsWith("[wave]")){ 45 | String msgSvrId = s[1].substring(6); 46 | try { 47 | String chatMedia = CommonUtils.getChatMedia(ChatController.absolutePath, msgSvrId); 48 | if (chatMedia!=null){ 49 | (new Thread(()->{ 50 | (new MediaPlayer(new Media((new File(chatMedia).toURI().toString())))).play(); 51 | })).start(); 52 | }else { 53 | System.out.println("[-] 数据库中没有此条语音"); 54 | } 55 | } catch (Exception e) { 56 | e.printStackTrace(); 57 | System.out.println("[-] 播放语音失败"); 58 | } 59 | }else { 60 | // 双击输出消息 61 | System.out.println(s[1]); 62 | } 63 | } 64 | } 65 | } 66 | }); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/com/ppsoft1991/chatViewTool/fx/MessageBox.java: -------------------------------------------------------------------------------- 1 | package com.ppsoft1991.chatViewTool.fx; 2 | 3 | import com.ppsoft1991.chatViewTool.controller.ChatController; 4 | import com.ppsoft1991.chatViewTool.utils.CommonUtils; 5 | import javafx.event.EventHandler; 6 | import javafx.geometry.Insets; 7 | import javafx.geometry.Pos; 8 | import javafx.scene.Node; 9 | import javafx.scene.control.Label; 10 | import javafx.scene.image.ImageView; 11 | import javafx.scene.input.MouseEvent; 12 | import javafx.scene.layout.HBox; 13 | import javafx.scene.layout.Priority; 14 | import javafx.scene.layout.VBox; 15 | import javafx.scene.media.Media; 16 | import javafx.scene.media.MediaPlayer; 17 | import javafx.scene.paint.Color; 18 | import javafx.scene.shape.Polygon; 19 | 20 | import java.io.File; 21 | 22 | public class MessageBox extends HBox { 23 | 24 | private static final Insets insets = new Insets(10.0, 5.0, 10.0, 5.0); 25 | private static final Insets friendInsets = new Insets(15.0, 0.0, 0.0, 10.0); 26 | private static final Insets selfInsets = new Insets(15.0, 10.0, 0.0, 0.0); 27 | 28 | public MessageBox(String message,boolean selfSend, ImageView imgHead){ 29 | super(); 30 | setPadding(insets); 31 | if (selfSend){ 32 | Polygon selfPolygon = new Polygon(0.0, 0.0, 0.0, 10.0, 10.0, 5.0); 33 | selfPolygon.setFill(Color.rgb(84, 225, 81)); 34 | HBox.setMargin(selfPolygon, selfInsets); 35 | MessageSelfLabel messageSelfLabel = new MessageSelfLabel(); 36 | messageSelfLabel.setText(message); 37 | HBox.setHgrow(messageSelfLabel, Priority.ALWAYS); 38 | this.getChildren().addAll(messageSelfLabel, selfPolygon, CommonUtils.selfImage()); 39 | this.setAlignment(Pos.CENTER_RIGHT); 40 | }else { 41 | Polygon friendPolygon = new Polygon(0.0, 5.0, 10.0, 0.0, 10.0, 10.0); 42 | friendPolygon.setFill(Color.rgb(179,231,244)); 43 | HBox.setMargin(friendPolygon, friendInsets); 44 | MessageFriendsLabel messageFriendsLabel = new MessageFriendsLabel(); 45 | HBox.setHgrow(messageFriendsLabel, Priority.ALWAYS); 46 | messageFriendsLabel.setText(message); 47 | this.getChildren().addAll(imgHead, friendPolygon, messageFriendsLabel); 48 | } 49 | // 新增点击播放 50 | this.addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler() { 51 | @Override 52 | public void handle(MouseEvent event) { 53 | if (event.getClickCount()==2){ 54 | for (Node n:getChildren()){ 55 | if (n instanceof Label){ 56 | String[] s = ((Label)n).getText().split("\n"); 57 | if (s.length==2 && s[1].startsWith("[wave]")){ 58 | String msgSvrId = s[1].substring(6); 59 | try { 60 | String chatMedia = CommonUtils.getChatMedia(ChatController.absolutePath, msgSvrId); 61 | if (chatMedia!=null){ 62 | (new Thread(()->{ 63 | (new MediaPlayer(new Media((new File(chatMedia).toURI().toString())))).play(); 64 | })).start(); 65 | }else { 66 | System.out.println("[-] 数据库中没有此条语音"); 67 | } 68 | } catch (Exception e) { 69 | e.printStackTrace(); 70 | System.out.println("[-] 播放语音失败"); 71 | } 72 | }else { 73 | // 双击输出消息 74 | System.out.println(s[1]); 75 | } 76 | } 77 | } 78 | } 79 | } 80 | }); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/com/ppsoft1991/chatViewTool/fx/MessageFriendsLabel.java: -------------------------------------------------------------------------------- 1 | package com.ppsoft1991.chatViewTool.fx; 2 | 3 | import javafx.geometry.Insets; 4 | import javafx.scene.control.Label; 5 | import javafx.scene.text.Font; 6 | 7 | public class MessageFriendsLabel extends Label { 8 | private static final Font messageFont = new Font(14.0); 9 | private static final Insets insets = new Insets(6.0); 10 | 11 | public MessageFriendsLabel(){ 12 | super(); 13 | this.setWrapText(true); 14 | this.setMaxWidth(220.0); 15 | this.setStyle("-fx-background-color: rgb(179,231,244); -fx-background-radius: 8px;"); 16 | this.setPadding(insets); 17 | this.setFont(messageFont); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/ppsoft1991/chatViewTool/fx/MessageSelfLabel.java: -------------------------------------------------------------------------------- 1 | package com.ppsoft1991.chatViewTool.fx; 2 | 3 | import javafx.geometry.Insets; 4 | import javafx.scene.control.Label; 5 | import javafx.scene.text.Font; 6 | 7 | 8 | public class MessageSelfLabel extends Label { 9 | private static final Font messageFont = new Font(14.0); 10 | private static final Insets insets = new Insets(6.0); 11 | 12 | public MessageSelfLabel(){ 13 | super(); 14 | this.setWrapText(true); 15 | this.setMaxWidth(220.0); 16 | this.setStyle("-fx-background-color: rgb(84,225,81); -fx-background-radius: 8px;"); 17 | this.setPadding(insets); 18 | this.setFont(messageFont); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/ppsoft1991/chatViewTool/fx/NoticeBox.java: -------------------------------------------------------------------------------- 1 | package com.ppsoft1991.chatViewTool.fx; 2 | 3 | import javafx.event.EventHandler; 4 | import javafx.geometry.Insets; 5 | import javafx.geometry.Pos; 6 | import javafx.scene.control.Label; 7 | import javafx.scene.input.MouseEvent; 8 | import javafx.scene.layout.HBox; 9 | import javafx.scene.text.Font; 10 | 11 | /** 12 | * @author roto 13 | */ 14 | public class NoticeBox extends HBox { 15 | private static final Insets insets = new Insets(10.0, 5.0, 10.0, 5.0); 16 | private static final Insets labelInsets = new Insets(6.0); 17 | 18 | public NoticeBox(String message) { 19 | super(); 20 | this.setPadding(insets); 21 | this.setPrefWidth(500.0); 22 | // TextArea tf = new TextArea(message); 23 | // tf.setEditable(false); 24 | // tf.setWrapText(true); 25 | // tf.setMaxWidth(400); 26 | // this.setStyle("-fx-background-color: #c0c0c0;-fx-opacity: 0;"); 27 | // tf.setPadding(new Insets(10.0, 5.0, 10.0, 5.0)); 28 | // tf.setFont(new Font(12.0)); 29 | Label label = new Label(); 30 | label.setWrapText(true); 31 | label.setText(message); 32 | label.setMaxWidth(400); 33 | label.setPadding(labelInsets); 34 | label.setStyle("-fx-background-color: #c0c0c0;-fx-background-radius: 8px;"); 35 | label.setFont(new Font(12.0)); 36 | this.getChildren().add(label); 37 | this.setAlignment(Pos.TOP_CENTER); 38 | this.addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler() { 39 | @Override 40 | public void handle(MouseEvent event) { 41 | if (event.getClickCount()==2){ 42 | System.out.println(label.getText()); 43 | } 44 | } 45 | }); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/ppsoft1991/chatViewTool/fx/TableList.java: -------------------------------------------------------------------------------- 1 | package com.ppsoft1991.chatViewTool.fx; 2 | 3 | 4 | public class TableList 5 | { 6 | private String image; 7 | private String wechatId; 8 | private String userName; 9 | private String nickName; 10 | private String alias; 11 | private String strContent; 12 | private String filterCol; 13 | 14 | public TableList(final String header, final String wechatId, final String userName, final String nickName, final String alias, final String strContent, final String filterCol) { 15 | this.wechatId = wechatId; 16 | this.image = header; 17 | this.userName = userName; 18 | this.nickName = nickName; 19 | this.alias = alias; 20 | this.strContent = strContent; 21 | this.filterCol = filterCol; 22 | } 23 | 24 | public String getWechatId() { 25 | return this.wechatId; 26 | } 27 | 28 | public void setImage(String image){ 29 | this.image = image; 30 | } 31 | 32 | public String getImage(){ 33 | return this.image; 34 | } 35 | public String getUserName() { 36 | return this.userName; 37 | } 38 | 39 | public String getNickName() { 40 | return this.nickName; 41 | } 42 | 43 | public String getAlias() { 44 | return this.alias; 45 | } 46 | 47 | public String getStrContent() { 48 | return this.strContent; 49 | } 50 | 51 | public String getFilterCol() { 52 | return this.filterCol; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/ppsoft1991/chatViewTool/mapper/microMsg/ChatRoomMapper.java: -------------------------------------------------------------------------------- 1 | package com.ppsoft1991.chatViewTool.mapper.microMsg; 2 | 3 | 4 | public class ChatRoomMapper { 5 | 6 | private String chatRoomName; 7 | private String userNameList; 8 | private String displayNameList; 9 | private Integer chatRoomFlag; 10 | private Integer owner; 11 | private Integer isShowName; 12 | private String selfDisplayName; 13 | private Integer reserved1; 14 | private String reserved2; 15 | private Integer reserved3; 16 | private String reserved4; 17 | private Integer reserved5; 18 | private String reserved6; 19 | private String roomData; 20 | private Integer reserved7; 21 | private String reserved8; 22 | 23 | @Override 24 | public String toString() { 25 | return "ChatRoomMapper{" + 26 | "chatRoomName='" + chatRoomName + '\'' + 27 | ", userNameList='" + userNameList + '\'' + 28 | ", displayNameList='" + displayNameList + '\'' + 29 | ", chatRoomFlag=" + chatRoomFlag + 30 | ", owner=" + owner + 31 | ", isShowName=" + isShowName + 32 | ", selfDisplayName='" + selfDisplayName + '\'' + 33 | ", reserved1=" + reserved1 + 34 | ", reserved2='" + reserved2 + '\'' + 35 | ", reserved3=" + reserved3 + 36 | ", reserved4='" + reserved4 + '\'' + 37 | ", reserved5=" + reserved5 + 38 | ", reserved6='" + reserved6 + '\'' + 39 | ", roomData='" + roomData + '\'' + 40 | ", reserved7=" + reserved7 + 41 | ", reserved8='" + reserved8 + '\'' + 42 | '}'; 43 | } 44 | 45 | public String getChatRoomName() { 46 | return chatRoomName; 47 | } 48 | 49 | public void setChatRoomName(String chatRoomName) { 50 | this.chatRoomName = chatRoomName; 51 | } 52 | 53 | 54 | public String getUserNameList() { 55 | return userNameList; 56 | } 57 | 58 | public void setUserNameList(String userNameList) { 59 | this.userNameList = userNameList; 60 | } 61 | 62 | 63 | public String getDisplayNameList() { 64 | return displayNameList; 65 | } 66 | 67 | public void setDisplayNameList(String displayNameList) { 68 | this.displayNameList = displayNameList; 69 | } 70 | 71 | 72 | public Integer getChatRoomFlag() { 73 | return chatRoomFlag; 74 | } 75 | 76 | public void setChatRoomFlag(Integer chatRoomFlag) { 77 | this.chatRoomFlag = chatRoomFlag; 78 | } 79 | 80 | 81 | public Integer getOwner() { 82 | return owner; 83 | } 84 | 85 | public void setOwner(Integer owner) { 86 | this.owner = owner; 87 | } 88 | 89 | 90 | public Integer getIsShowName() { 91 | return isShowName; 92 | } 93 | 94 | public void setIsShowName(Integer isShowName) { 95 | this.isShowName = isShowName; 96 | } 97 | 98 | 99 | public String getSelfDisplayName() { 100 | return selfDisplayName; 101 | } 102 | 103 | public void setSelfDisplayName(String selfDisplayName) { 104 | this.selfDisplayName = selfDisplayName; 105 | } 106 | 107 | 108 | public Integer getReserved1() { 109 | return reserved1; 110 | } 111 | 112 | public void setReserved1(Integer reserved1) { 113 | this.reserved1 = reserved1; 114 | } 115 | 116 | 117 | public String getReserved2() { 118 | return reserved2; 119 | } 120 | 121 | public void setReserved2(String reserved2) { 122 | this.reserved2 = reserved2; 123 | } 124 | 125 | 126 | public Integer getReserved3() { 127 | return reserved3; 128 | } 129 | 130 | public void setReserved3(Integer reserved3) { 131 | this.reserved3 = reserved3; 132 | } 133 | 134 | 135 | public String getReserved4() { 136 | return reserved4; 137 | } 138 | 139 | public void setReserved4(String reserved4) { 140 | this.reserved4 = reserved4; 141 | } 142 | 143 | 144 | public Integer getReserved5() { 145 | return reserved5; 146 | } 147 | 148 | public void setReserved5(Integer reserved5) { 149 | this.reserved5 = reserved5; 150 | } 151 | 152 | 153 | public String getReserved6() { 154 | return reserved6; 155 | } 156 | 157 | public void setReserved6(String reserved6) { 158 | this.reserved6 = reserved6; 159 | } 160 | 161 | 162 | public String getRoomData() { 163 | return roomData; 164 | } 165 | 166 | public void setRoomData(String roomData) { 167 | this.roomData = roomData; 168 | } 169 | 170 | 171 | public Integer getReserved7() { 172 | return reserved7; 173 | } 174 | 175 | public void setReserved7(Integer reserved7) { 176 | this.reserved7 = reserved7; 177 | } 178 | 179 | 180 | public String getReserved8() { 181 | return reserved8; 182 | } 183 | 184 | public void setReserved8(String reserved8) { 185 | this.reserved8 = reserved8; 186 | } 187 | 188 | 189 | } 190 | -------------------------------------------------------------------------------- /src/main/java/com/ppsoft1991/chatViewTool/mapper/microMsg/ContactMapper.java: -------------------------------------------------------------------------------- 1 | package com.ppsoft1991.chatViewTool.mapper.microMsg; 2 | 3 | 4 | public class ContactMapper { 5 | 6 | private String userName; 7 | private String alias; 8 | private String encryptUserName; 9 | private Integer delFlag; 10 | private Integer type; 11 | private Integer verifyFlag; 12 | private Integer reserved1; 13 | private Integer reserved2; 14 | private String reserved3; 15 | private String reserved4; 16 | private String remark; 17 | private String nickName; 18 | private String labelIdList; 19 | private String domainList; 20 | private Integer chatRoomType; 21 | private String pyInitial; 22 | private String quanPin; 23 | private String remarkPyInitial; 24 | private String remarkQuanPin; 25 | private String bigHeadImgUrl; 26 | private String smallHeadImgUrl; 27 | private String headImgMd5; 28 | private Integer chatRoomNotify; 29 | private Integer reserved5; 30 | private String reserved6; 31 | private String reserved7; 32 | private String extraBuf; 33 | private Integer reserved8; 34 | private Integer reserved9; 35 | private String reserved10; 36 | private String reserved11; 37 | 38 | @Override 39 | public String toString() { 40 | return "ContactMapper{" + 41 | "userName='" + userName + '\'' + 42 | ", alias='" + alias + '\'' + 43 | ", encryptUserName='" + encryptUserName + '\'' + 44 | ", delFlag=" + delFlag + 45 | ", type=" + type + 46 | ", verifyFlag=" + verifyFlag + 47 | ", reserved1=" + reserved1 + 48 | ", reserved2=" + reserved2 + 49 | ", reserved3='" + reserved3 + '\'' + 50 | ", reserved4='" + reserved4 + '\'' + 51 | ", remark='" + remark + '\'' + 52 | ", nickName='" + nickName + '\'' + 53 | ", labelIdList='" + labelIdList + '\'' + 54 | ", domainList='" + domainList + '\'' + 55 | ", chatRoomType=" + chatRoomType + 56 | ", pyInitial='" + pyInitial + '\'' + 57 | ", quanPin='" + quanPin + '\'' + 58 | ", remarkPyInitial='" + remarkPyInitial + '\'' + 59 | ", remarkQuanPin='" + remarkQuanPin + '\'' + 60 | ", bigHeadImgUrl='" + bigHeadImgUrl + '\'' + 61 | ", smallHeadImgUrl='" + smallHeadImgUrl + '\'' + 62 | ", headImgMd5='" + headImgMd5 + '\'' + 63 | ", chatRoomNotify=" + chatRoomNotify + 64 | ", reserved5=" + reserved5 + 65 | ", reserved6='" + reserved6 + '\'' + 66 | ", reserved7='" + reserved7 + '\'' + 67 | ", extraBuf='" + extraBuf + '\'' + 68 | ", reserved8=" + reserved8 + 69 | ", reserved9=" + reserved9 + 70 | ", reserved10='" + reserved10 + '\'' + 71 | ", reserved11='" + reserved11 + '\'' + 72 | '}'; 73 | } 74 | 75 | public String getUserName() { 76 | return userName; 77 | } 78 | 79 | public void setUserName(String userName) { 80 | this.userName = userName; 81 | } 82 | 83 | 84 | public String getAlias() { 85 | return alias; 86 | } 87 | 88 | public void setAlias(String alias) { 89 | this.alias = alias; 90 | } 91 | 92 | 93 | public String getEncryptUserName() { 94 | return encryptUserName; 95 | } 96 | 97 | public void setEncryptUserName(String encryptUserName) { 98 | this.encryptUserName = encryptUserName; 99 | } 100 | 101 | 102 | public Integer getDelFlag() { 103 | return delFlag; 104 | } 105 | 106 | public void setDelFlag(Integer delFlag) { 107 | this.delFlag = delFlag; 108 | } 109 | 110 | 111 | public Integer getType() { 112 | return type; 113 | } 114 | 115 | public void setType(Integer type) { 116 | this.type = type; 117 | } 118 | 119 | 120 | public Integer getVerifyFlag() { 121 | return verifyFlag; 122 | } 123 | 124 | public void setVerifyFlag(Integer verifyFlag) { 125 | this.verifyFlag = verifyFlag; 126 | } 127 | 128 | 129 | public Integer getReserved1() { 130 | return reserved1; 131 | } 132 | 133 | public void setReserved1(Integer reserved1) { 134 | this.reserved1 = reserved1; 135 | } 136 | 137 | 138 | public Integer getReserved2() { 139 | return reserved2; 140 | } 141 | 142 | public void setReserved2(Integer reserved2) { 143 | this.reserved2 = reserved2; 144 | } 145 | 146 | 147 | public String getReserved3() { 148 | return reserved3; 149 | } 150 | 151 | public void setReserved3(String reserved3) { 152 | this.reserved3 = reserved3; 153 | } 154 | 155 | 156 | public String getReserved4() { 157 | return reserved4; 158 | } 159 | 160 | public void setReserved4(String reserved4) { 161 | this.reserved4 = reserved4; 162 | } 163 | 164 | 165 | public String getRemark() { 166 | return remark; 167 | } 168 | 169 | public void setRemark(String remark) { 170 | this.remark = remark; 171 | } 172 | 173 | 174 | public String getNickName() { 175 | return nickName; 176 | } 177 | 178 | public void setNickName(String nickName) { 179 | this.nickName = nickName; 180 | } 181 | 182 | 183 | public String getLabelIdList() { 184 | return labelIdList; 185 | } 186 | 187 | public void setLabelIdList(String labelIdList) { 188 | this.labelIdList = labelIdList; 189 | } 190 | 191 | 192 | public String getDomainList() { 193 | return domainList; 194 | } 195 | 196 | public void setDomainList(String domainList) { 197 | this.domainList = domainList; 198 | } 199 | 200 | 201 | public Integer getChatRoomType() { 202 | return chatRoomType; 203 | } 204 | 205 | public void setChatRoomType(Integer chatRoomType) { 206 | this.chatRoomType = chatRoomType; 207 | } 208 | 209 | 210 | public String getPyInitial() { 211 | return pyInitial; 212 | } 213 | 214 | public void setPyInitial(String pyInitial) { 215 | this.pyInitial = pyInitial; 216 | } 217 | 218 | 219 | public String getQuanPin() { 220 | return quanPin; 221 | } 222 | 223 | public void setQuanPin(String quanPin) { 224 | this.quanPin = quanPin; 225 | } 226 | 227 | 228 | public String getRemarkPyInitial() { 229 | return remarkPyInitial; 230 | } 231 | 232 | public void setRemarkPyInitial(String remarkPyInitial) { 233 | this.remarkPyInitial = remarkPyInitial; 234 | } 235 | 236 | 237 | public String getRemarkQuanPin() { 238 | return remarkQuanPin; 239 | } 240 | 241 | public void setRemarkQuanPin(String remarkQuanPin) { 242 | this.remarkQuanPin = remarkQuanPin; 243 | } 244 | 245 | 246 | public String getBigHeadImgUrl() { 247 | return bigHeadImgUrl; 248 | } 249 | 250 | public void setBigHeadImgUrl(String bigHeadImgUrl) { 251 | this.bigHeadImgUrl = bigHeadImgUrl; 252 | } 253 | 254 | 255 | public String getSmallHeadImgUrl() { 256 | return smallHeadImgUrl; 257 | } 258 | 259 | public void setSmallHeadImgUrl(String smallHeadImgUrl) { 260 | this.smallHeadImgUrl = smallHeadImgUrl; 261 | } 262 | 263 | 264 | public String getHeadImgMd5() { 265 | return headImgMd5; 266 | } 267 | 268 | public void setHeadImgMd5(String headImgMd5) { 269 | this.headImgMd5 = headImgMd5; 270 | } 271 | 272 | 273 | public Integer getChatRoomNotify() { 274 | return chatRoomNotify; 275 | } 276 | 277 | public void setChatRoomNotify(Integer chatRoomNotify) { 278 | this.chatRoomNotify = chatRoomNotify; 279 | } 280 | 281 | 282 | public Integer getReserved5() { 283 | return reserved5; 284 | } 285 | 286 | public void setReserved5(Integer reserved5) { 287 | this.reserved5 = reserved5; 288 | } 289 | 290 | 291 | public String getReserved6() { 292 | return reserved6; 293 | } 294 | 295 | public void setReserved6(String reserved6) { 296 | this.reserved6 = reserved6; 297 | } 298 | 299 | 300 | public String getReserved7() { 301 | return reserved7; 302 | } 303 | 304 | public void setReserved7(String reserved7) { 305 | this.reserved7 = reserved7; 306 | } 307 | 308 | 309 | public String getExtraBuf() { 310 | return extraBuf; 311 | } 312 | 313 | public void setExtraBuf(String extraBuf) { 314 | this.extraBuf = extraBuf; 315 | } 316 | 317 | 318 | public Integer getReserved8() { 319 | return reserved8; 320 | } 321 | 322 | public void setReserved8(Integer reserved8) { 323 | this.reserved8 = reserved8; 324 | } 325 | 326 | 327 | public Integer getReserved9() { 328 | return reserved9; 329 | } 330 | 331 | public void setReserved9(Integer reserved9) { 332 | this.reserved9 = reserved9; 333 | } 334 | 335 | 336 | public String getReserved10() { 337 | return reserved10; 338 | } 339 | 340 | public void setReserved10(String reserved10) { 341 | this.reserved10 = reserved10; 342 | } 343 | 344 | 345 | public String getReserved11() { 346 | return reserved11; 347 | } 348 | 349 | public void setReserved11(String reserved11) { 350 | this.reserved11 = reserved11; 351 | } 352 | 353 | } 354 | -------------------------------------------------------------------------------- /src/main/java/com/ppsoft1991/chatViewTool/mapper/microMsg/HeaderIconMapper.java: -------------------------------------------------------------------------------- 1 | package com.ppsoft1991.chatViewTool.mapper.microMsg; 2 | 3 | public class HeaderIconMapper { 4 | private String usrName; 5 | 6 | public String getBigHeadImgUrl() { 7 | return bigHeadImgUrl; 8 | } 9 | 10 | public void setBigHeadImgUrl(String bigHeadImgUrl) { 11 | this.bigHeadImgUrl = bigHeadImgUrl; 12 | } 13 | 14 | public String getUsrName() { 15 | return usrName; 16 | } 17 | 18 | public void setUsrName(String usrName) { 19 | this.usrName = usrName; 20 | } 21 | 22 | public String getSmallHeadImgUrl() { 23 | return smallHeadImgUrl; 24 | } 25 | 26 | public void setSmallHeadImgUrl(String smallHeadImgUrl) { 27 | this.smallHeadImgUrl = smallHeadImgUrl; 28 | } 29 | 30 | private String smallHeadImgUrl; 31 | private String bigHeadImgUrl; 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/ppsoft1991/chatViewTool/mapper/microMsg/SelfUserMap.java: -------------------------------------------------------------------------------- 1 | package com.ppsoft1991.chatViewTool.mapper.microMsg; 2 | 3 | public class SelfUserMap { 4 | public String getUsername() { 5 | return username; 6 | } 7 | 8 | public void setUsername(String username) { 9 | this.username = username; 10 | } 11 | 12 | private String username; 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/ppsoft1991/chatViewTool/mapper/microMsg/SessionMapper.java: -------------------------------------------------------------------------------- 1 | package com.ppsoft1991.chatViewTool.mapper.microMsg; 2 | 3 | 4 | public class SessionMapper { 5 | 6 | private String strUsrName; 7 | private Integer nOrder; 8 | private Integer nUnReadCount; 9 | private String parentRef; 10 | private Integer reserved0; 11 | private String reserved1; 12 | private String strNickName; 13 | private Integer nStatus; 14 | private Integer nIsSend; 15 | private String strContent; 16 | private Integer nMsgType; 17 | private Integer nMsgLocalId; 18 | private Integer nMsgStatus; 19 | private Integer nTime; 20 | private String editContent; 21 | private Integer othersAtMe; 22 | private Integer reserved2; 23 | private String reserved3; 24 | private Integer reserved4; 25 | private String reserved5; 26 | private String bytesXml; 27 | 28 | @Override 29 | public String toString() { 30 | return "SessionMapper{" + 31 | "strUsrName='" + strUsrName + '\'' + 32 | ", nOrder=" + nOrder + 33 | ", nUnReadCount=" + nUnReadCount + 34 | ", parentRef='" + parentRef + '\'' + 35 | ", reserved0=" + reserved0 + 36 | ", reserved1='" + reserved1 + '\'' + 37 | ", strNickName='" + strNickName + '\'' + 38 | ", nStatus=" + nStatus + 39 | ", nIsSend=" + nIsSend + 40 | ", strContent='" + strContent + '\'' + 41 | ", nMsgType=" + nMsgType + 42 | ", nMsgLocalId=" + nMsgLocalId + 43 | ", nMsgStatus=" + nMsgStatus + 44 | ", nTime=" + nTime + 45 | ", editContent='" + editContent + '\'' + 46 | ", othersAtMe=" + othersAtMe + 47 | ", reserved2=" + reserved2 + 48 | ", reserved3='" + reserved3 + '\'' + 49 | ", reserved4=" + reserved4 + 50 | ", reserved5='" + reserved5 + '\'' + 51 | ", bytesXml='" + bytesXml + '\'' + 52 | '}'; 53 | } 54 | 55 | public String getStrUsrName() { 56 | return strUsrName; 57 | } 58 | 59 | public void setStrUsrName(String strUsrName) { 60 | this.strUsrName = strUsrName; 61 | } 62 | 63 | 64 | public Integer getNOrder() { 65 | return nOrder; 66 | } 67 | 68 | public void setNOrder(Integer nOrder) { 69 | this.nOrder = nOrder; 70 | } 71 | 72 | 73 | public Integer getNUnReadCount() { 74 | return nUnReadCount; 75 | } 76 | 77 | public void setNUnReadCount(Integer nUnReadCount) { 78 | this.nUnReadCount = nUnReadCount; 79 | } 80 | 81 | 82 | public String getParentRef() { 83 | return parentRef; 84 | } 85 | 86 | public void setParentRef(String parentRef) { 87 | this.parentRef = parentRef; 88 | } 89 | 90 | 91 | public Integer getReserved0() { 92 | return reserved0; 93 | } 94 | 95 | public void setReserved0(Integer reserved0) { 96 | this.reserved0 = reserved0; 97 | } 98 | 99 | 100 | public String getReserved1() { 101 | return reserved1; 102 | } 103 | 104 | public void setReserved1(String reserved1) { 105 | this.reserved1 = reserved1; 106 | } 107 | 108 | 109 | public String getStrNickName() { 110 | return strNickName; 111 | } 112 | 113 | public void setStrNickName(String strNickName) { 114 | this.strNickName = strNickName; 115 | } 116 | 117 | 118 | public Integer getNStatus() { 119 | return nStatus; 120 | } 121 | 122 | public void setNStatus(Integer nStatus) { 123 | this.nStatus = nStatus; 124 | } 125 | 126 | 127 | public Integer getNIsSend() { 128 | return nIsSend; 129 | } 130 | 131 | public void setNIsSend(Integer nIsSend) { 132 | this.nIsSend = nIsSend; 133 | } 134 | 135 | 136 | public String getStrContent() { 137 | return strContent; 138 | } 139 | 140 | public void setStrContent(String strContent) { 141 | this.strContent = strContent; 142 | } 143 | 144 | 145 | public Integer getNMsgType() { 146 | return nMsgType; 147 | } 148 | 149 | public void setNMsgType(Integer nMsgType) { 150 | this.nMsgType = nMsgType; 151 | } 152 | 153 | 154 | public Integer getNMsgLocalId() { 155 | return nMsgLocalId; 156 | } 157 | 158 | public void setNMsgLocalId(Integer nMsgLocalId) { 159 | this.nMsgLocalId = nMsgLocalId; 160 | } 161 | 162 | 163 | public Integer getNMsgStatus() { 164 | return nMsgStatus; 165 | } 166 | 167 | public void setNMsgStatus(Integer nMsgStatus) { 168 | this.nMsgStatus = nMsgStatus; 169 | } 170 | 171 | 172 | public Integer getNTime() { 173 | return nTime; 174 | } 175 | 176 | public void setNTime(Integer nTime) { 177 | this.nTime = nTime; 178 | } 179 | 180 | 181 | public String getEditContent() { 182 | return editContent; 183 | } 184 | 185 | public void setEditContent(String editContent) { 186 | this.editContent = editContent; 187 | } 188 | 189 | 190 | public Integer getOthersAtMe() { 191 | return othersAtMe; 192 | } 193 | 194 | public void setOthersAtMe(Integer othersAtMe) { 195 | this.othersAtMe = othersAtMe; 196 | } 197 | 198 | 199 | public Integer getReserved2() { 200 | return reserved2; 201 | } 202 | 203 | public void setReserved2(Integer reserved2) { 204 | this.reserved2 = reserved2; 205 | } 206 | 207 | 208 | public String getReserved3() { 209 | return reserved3; 210 | } 211 | 212 | public void setReserved3(String reserved3) { 213 | this.reserved3 = reserved3; 214 | } 215 | 216 | 217 | public Integer getReserved4() { 218 | return reserved4; 219 | } 220 | 221 | public void setReserved4(Integer reserved4) { 222 | this.reserved4 = reserved4; 223 | } 224 | 225 | 226 | public String getReserved5() { 227 | return reserved5; 228 | } 229 | 230 | public void setReserved5(String reserved5) { 231 | this.reserved5 = reserved5; 232 | } 233 | 234 | 235 | public String getBytesXml() { 236 | return bytesXml; 237 | } 238 | 239 | public void setBytesXml(String bytesXml) { 240 | this.bytesXml = bytesXml; 241 | } 242 | 243 | } 244 | -------------------------------------------------------------------------------- /src/main/java/com/ppsoft1991/chatViewTool/mapper/msg/MsgMapper.java: -------------------------------------------------------------------------------- 1 | package com.ppsoft1991.chatViewTool.mapper.msg; 2 | 3 | 4 | public class MsgMapper { 5 | 6 | private Integer Id; 7 | private Integer localId; 8 | private Integer talkerId; 9 | private Long msgSvrId; 10 | private Integer type; 11 | private Integer subType; 12 | private Integer isSender; 13 | private Integer createTime; 14 | private Integer sequence; 15 | private Integer statusEx; 16 | private Integer flagEx; 17 | private Integer status; 18 | private Integer msgServerSeq; 19 | private Integer msgSequence; 20 | private String strTalker; 21 | private String strContent; 22 | private String displayContent; 23 | private Integer reserved0; 24 | private Integer reserved1; 25 | private Integer reserved2; 26 | private Integer reserved3; 27 | private String reserved4; 28 | private String reserved5; 29 | private String reserved6; 30 | private String compressContent; 31 | private byte[] bytesExtra; 32 | private String bytesTrans; 33 | 34 | @Override 35 | public String toString() { 36 | return "Msg{" + 37 | "id=" + Id + 38 | "localId=" + localId + 39 | ", talkerId=" + talkerId + 40 | ", msgSvrId=" + msgSvrId + 41 | ", type=" + type + 42 | ", subType=" + subType + 43 | ", isSender=" + isSender + 44 | ", createTime=" + createTime + 45 | ", sequence=" + sequence + 46 | ", statusEx=" + statusEx + 47 | ", flagEx=" + flagEx + 48 | ", status=" + status + 49 | ", msgServerSeq=" + msgServerSeq + 50 | ", msgSequence=" + msgSequence + 51 | ", strTalker='" + strTalker + '\'' + 52 | ", strContent='" + strContent + '\'' + 53 | ", displayContent='" + displayContent + '\'' + 54 | ", reserved0=" + reserved0 + 55 | ", reserved1=" + reserved1 + 56 | ", reserved2=" + reserved2 + 57 | ", reserved3=" + reserved3 + 58 | ", reserved4='" + reserved4 + '\'' + 59 | ", reserved5='" + reserved5 + '\'' + 60 | ", reserved6='" + reserved6 + '\'' + 61 | ", compressContent='" + compressContent + '\'' + 62 | ", bytesExtra='" + bytesExtra + '\'' + 63 | ", bytesTrans='" + bytesTrans + '\'' + 64 | '}'; 65 | } 66 | 67 | public Integer getLocalId() { 68 | return localId; 69 | } 70 | 71 | public void setLocalId(Integer localId) { 72 | this.localId = localId; 73 | } 74 | 75 | 76 | public Integer getTalkerId() { 77 | return talkerId; 78 | } 79 | 80 | public void setTalkerId(Integer talkerId) { 81 | this.talkerId = talkerId; 82 | } 83 | 84 | 85 | public Long getMsgSvrId() { 86 | return msgSvrId; 87 | } 88 | 89 | public void setMsgSvrId(Long msgSvrId) { 90 | this.msgSvrId = msgSvrId; 91 | } 92 | 93 | 94 | public Integer getType() { 95 | return type; 96 | } 97 | 98 | public void setType(Integer type) { 99 | this.type = type; 100 | } 101 | 102 | 103 | public Integer getSubType() { 104 | return subType; 105 | } 106 | 107 | public void setSubType(Integer subType) { 108 | this.subType = subType; 109 | } 110 | 111 | 112 | public Integer getIsSender() { 113 | return isSender; 114 | } 115 | 116 | public void setIsSender(Integer isSender) { 117 | this.isSender = isSender; 118 | } 119 | 120 | 121 | public Integer getCreateTime() { 122 | return createTime; 123 | } 124 | 125 | public void setCreateTime(Integer createTime) { 126 | this.createTime = createTime; 127 | } 128 | 129 | 130 | public Integer getSequence() { 131 | return sequence; 132 | } 133 | 134 | public void setSequence(Integer sequence) { 135 | this.sequence = sequence; 136 | } 137 | 138 | 139 | public Integer getStatusEx() { 140 | return statusEx; 141 | } 142 | 143 | public void setStatusEx(Integer statusEx) { 144 | this.statusEx = statusEx; 145 | } 146 | 147 | 148 | public Integer getFlagEx() { 149 | return flagEx; 150 | } 151 | 152 | public void setFlagEx(Integer flagEx) { 153 | this.flagEx = flagEx; 154 | } 155 | 156 | 157 | public Integer getStatus() { 158 | return status; 159 | } 160 | 161 | public void setStatus(Integer status) { 162 | this.status = status; 163 | } 164 | 165 | 166 | public Integer getMsgServerSeq() { 167 | return msgServerSeq; 168 | } 169 | 170 | public void setMsgServerSeq(Integer msgServerSeq) { 171 | this.msgServerSeq = msgServerSeq; 172 | } 173 | 174 | 175 | public Integer getMsgSequence() { 176 | return msgSequence; 177 | } 178 | 179 | public void setMsgSequence(Integer msgSequence) { 180 | this.msgSequence = msgSequence; 181 | } 182 | 183 | 184 | public String getStrTalker() { 185 | return strTalker; 186 | } 187 | 188 | public void setStrTalker(String strTalker) { 189 | this.strTalker = strTalker; 190 | } 191 | 192 | 193 | public String getStrContent() { 194 | return strContent; 195 | } 196 | 197 | public void setStrContent(String strContent) { 198 | this.strContent = strContent; 199 | } 200 | 201 | 202 | public String getDisplayContent() { 203 | return displayContent; 204 | } 205 | 206 | public void setDisplayContent(String displayContent) { 207 | this.displayContent = displayContent; 208 | } 209 | 210 | 211 | public Integer getReserved0() { 212 | return reserved0; 213 | } 214 | 215 | public void setReserved0(Integer reserved0) { 216 | this.reserved0 = reserved0; 217 | } 218 | 219 | 220 | public Integer getReserved1() { 221 | return reserved1; 222 | } 223 | 224 | public void setReserved1(Integer reserved1) { 225 | this.reserved1 = reserved1; 226 | } 227 | 228 | 229 | public Integer getReserved2() { 230 | return reserved2; 231 | } 232 | 233 | public void setReserved2(Integer reserved2) { 234 | this.reserved2 = reserved2; 235 | } 236 | 237 | 238 | public Integer getReserved3() { 239 | return reserved3; 240 | } 241 | 242 | public void setReserved3(Integer reserved3) { 243 | this.reserved3 = reserved3; 244 | } 245 | 246 | 247 | public String getReserved4() { 248 | return reserved4; 249 | } 250 | 251 | public void setReserved4(String reserved4) { 252 | this.reserved4 = reserved4; 253 | } 254 | 255 | 256 | public String getReserved5() { 257 | return reserved5; 258 | } 259 | 260 | public void setReserved5(String reserved5) { 261 | this.reserved5 = reserved5; 262 | } 263 | 264 | 265 | public String getReserved6() { 266 | return reserved6; 267 | } 268 | 269 | public void setReserved6(String reserved6) { 270 | this.reserved6 = reserved6; 271 | } 272 | 273 | 274 | public String getCompressContent() { 275 | return compressContent; 276 | } 277 | 278 | public void setCompressContent(String compressContent) { 279 | this.compressContent = compressContent; 280 | } 281 | 282 | 283 | public byte[] getBytesExtra() { 284 | return bytesExtra; 285 | } 286 | 287 | public void setBytesExtra(byte[] bytesExtra) { 288 | this.bytesExtra = bytesExtra; 289 | } 290 | 291 | 292 | public String getBytesTrans() { 293 | return bytesTrans; 294 | } 295 | 296 | public void setBytesTrans(String bytesTrans) { 297 | this.bytesTrans = bytesTrans; 298 | } 299 | 300 | public Integer getId() { 301 | return Id; 302 | } 303 | 304 | public void setId(Integer id) { 305 | Id = id; 306 | } 307 | } 308 | -------------------------------------------------------------------------------- /src/main/java/com/ppsoft1991/chatViewTool/utils/CommonFunc.java: -------------------------------------------------------------------------------- 1 | package com.ppsoft1991.chatViewTool.utils; 2 | 3 | import java.sql.Connection; 4 | import java.sql.DriverManager; 5 | import java.sql.ResultSet; 6 | import java.sql.Statement; 7 | import java.util.List; 8 | 9 | public class CommonFunc { 10 | 11 | public static Connection openDB(final String dbPath) { 12 | final String driver = "jdbc:sqlite:" + dbPath; 13 | Connection conn = null; 14 | try { 15 | conn = DriverManager.getConnection(driver); 16 | conn.setAutoCommit(false); 17 | } catch (Exception err) { 18 | System.out.println("[-] " + err.getMessage()); 19 | } 20 | return conn; 21 | } 22 | 23 | public static void mergeDb(final List dbFileList, final String dir) throws Exception { 24 | // 删除缓存文件 25 | CommonUtils.deleteFile(dir + "/MSG_ALL.db"); 26 | CommonUtils.deleteFile(dir + "/MediaMSG_ALL.db"); 27 | // 生成聊天数据库 28 | generateDb(dir, "MSG_ALL.db"); 29 | generateMSG(dir, "MSG_ALL.db"); 30 | // 合并 MSG 数据库 31 | MsgDB.init(dir+"/MSG_ALL.db"); 32 | for (String dbFileName : dbFileList) { 33 | if (dbFileName.startsWith("MSG") && !dbFileName.startsWith("MSG_ALL")) { 34 | String dbFilePath = dir + "/" + dbFileName + "_dec.db"; 35 | System.out.println("Merge " + dbFilePath + " to MSG_ALL.db"); 36 | 37 | final String sql = "ATTACH DATABASE '" + dbFilePath + "' AS example; " + 38 | "INSERT INTO Msg (TalkerId,Type,IsSender,CreateTime,Sequence,MsgSequence,StrTalker,StrContent,MsgSvrID,BytesExtra) " + 39 | "SELECT TalkerId,Type,IsSender,CreateTime,Sequence,MsgSequence,StrTalker,StrContent,MsgSvrID,BytesExtra FROM example.Msg; " + 40 | "DETACH DATABASE example;"; 41 | try { 42 | MsgDB.getMsgTemplate().update(sql); 43 | }catch (Exception err) { 44 | System.out.println("[-] Merge MsgDB error: " + dbFilePath); 45 | err.printStackTrace(); 46 | } 47 | // try (Connection conn = DriverManager.getConnection("jdbc:sqlite:" + dir + "/MSG_ALL.db"); 48 | // Statement stmt = conn.createStatement()) { 49 | // conn.setAutoCommit(false); // 开启事务 50 | // stmt.executeUpdate(sql); 51 | // conn.commit(); // 提交事务 52 | // System.out.println("[+] 合并 " + dbFilePath + " 完成"); 53 | // } catch (Exception e) { 54 | // System.out.println("[-] Merge MsgDB error: " + dbFilePath); 55 | // e.printStackTrace(); 56 | // } 57 | } 58 | } 59 | MsgDB.close(); 60 | // 合并 MediaMSG 数据库 61 | generateDb(dir, "MediaMSG_ALL.db"); 62 | generateMediaMsg(dir, "MediaMSG_ALL.db"); 63 | MsgDB.init(dir+"/MediaMSG_ALL.db"); 64 | for (String dbFileName : dbFileList) { 65 | if (dbFileName.startsWith("MediaMSG") && !dbFileName.startsWith("MediaMSG_ALL")) { 66 | String dbFilePath = dir + "/" + dbFileName + "_dec.db"; 67 | System.out.println("Merge " + dbFilePath + " to MediaMSG_ALL.db"); 68 | 69 | final String sql = "ATTACH DATABASE '" + dbFilePath + "' AS example; " + 70 | "INSERT INTO Media (Key,Reserved0,Buf,Reserved1,Reserved2) " + 71 | "SELECT Key,Reserved0,Buf,Reserved1,Reserved2 FROM example.Media; " + 72 | "DETACH DATABASE example;"; 73 | try { 74 | MsgDB.getMsgTemplate().update(sql); 75 | }catch (Exception err) { 76 | System.out.println("[-] Merge MediaMsg error: " + dbFilePath); 77 | err.printStackTrace(); 78 | } 79 | // try (Connection conn = DriverManager.getConnection("jdbc:sqlite:" + dir + "/MediaMSG_ALL.db"); 80 | // Statement stmt = conn.createStatement()) { 81 | // conn.setAutoCommit(false); // 开启事务 82 | // stmt.executeUpdate(sql); 83 | // conn.commit(); // 提交事务 84 | // System.out.println("[+] 合并 " + dbFilePath + " 完成"); 85 | // } catch (Exception e) { 86 | // System.out.println("[-] 合并语音数据库失败: " + dbFilePath); 87 | // e.printStackTrace(); 88 | // } 89 | } 90 | } 91 | } 92 | 93 | 94 | public static void generateDb(final String dir, String dbName) throws Exception { 95 | 96 | CommonUtils.deleteFile(dir + "/" + dbName); 97 | CommonUtils.deleteFile(dir + "/" + dbName + "_dec.db"); 98 | //MsgDB.init(dir + "/" + dbName); 99 | //MsgDB.getMsgTemplate().execute("CREATE TABLE IF NOT EXISTS random (Key TEXT);"); 100 | try (Connection conn = DriverManager.getConnection("jdbc:sqlite:" + dir + "/" + dbName); 101 | Statement stmt = conn.createStatement()) { 102 | stmt.executeUpdate("CREATE TABLE IF NOT EXISTS random (Key TEXT);"); 103 | } catch (Exception e) { 104 | System.out.println("[-] 生成数据库失败"); 105 | e.printStackTrace(); 106 | } 107 | } 108 | 109 | public static void generateMSG(final String dir, String msgName) throws Exception { 110 | final String generateMsgSql = "create table MSG\n" + 111 | "(\n" + 112 | " localId INTEGER\n" + 113 | " primary key autoincrement,\n" + 114 | " TalkerId INT default 0,\n" + 115 | " MsgSvrID INT,\n" + 116 | " Type INT,\n" + 117 | " SubType INT,\n" + 118 | " IsSender INT,\n" + 119 | " CreateTime INT,\n" + 120 | " Sequence INT default 0,\n" + 121 | " StatusEx INT default 0,\n" + 122 | " FlagEx INT,\n" + 123 | " Status INT,\n" + 124 | " MsgServerSeq INT,\n" + 125 | " MsgSequence INT,\n" + 126 | " StrTalker TEXT,\n" + 127 | " StrContent TEXT,\n" + 128 | " DisplayContent TEXT,\n" + 129 | " Reserved0 INT default 0,\n" + 130 | " Reserved1 INT default 0,\n" + 131 | " Reserved2 INT default 0,\n" + 132 | " Reserved3 INT default 0,\n" + 133 | " Reserved4 TEXT,\n" + 134 | " Reserved5 TEXT,\n" + 135 | " Reserved6 TEXT,\n" + 136 | " CompressContent BLOB,\n" + 137 | " BytesExtra BLOB,\n" + 138 | " BytesTrans BLOB\n" + 139 | ");"; 140 | final String generateIndexSql = "CREATE INDEX IF NOT EXISTS search_chat on \"MSG\" (strTalker, CreateTime);"; 141 | try (Connection conn = DriverManager.getConnection("jdbc:sqlite:" + dir + "/" + msgName); 142 | Statement stmt = conn.createStatement()){ 143 | stmt.execute(generateMsgSql); 144 | stmt.execute(generateIndexSql); 145 | System.out.println("[+] 创建MSG数据库成功:"); 146 | System.out.println("[+] 创建MSG索引成功:"); 147 | }catch (Exception e){ 148 | System.out.println("[-] 创建MSG数据库失败:"); 149 | System.out.println("[-] 创建MSG索引失败:"); 150 | e.printStackTrace(); 151 | } 152 | } 153 | 154 | public static void generateMediaMsg(String dir, String msgName){ 155 | final String generateMediaMsgSql = "create table Media\n" + 156 | "(\n" + 157 | " Key TEXT\n" + 158 | " primary key,\n" + 159 | " Reserved0 INT,\n" + 160 | " Buf BLOB,\n" + 161 | " Reserved1 INT,\n" + 162 | " Reserved2 TEXT\n" + 163 | ");"; 164 | try (Connection conn = DriverManager.getConnection("jdbc:sqlite:" + dir + "/" + msgName); 165 | Statement stmt = conn.createStatement()){ 166 | stmt.execute(generateMediaMsgSql); 167 | System.out.println("[+] 创建MediaMsg数据库成功:"); 168 | }catch (Exception e){ 169 | System.out.println("[-] 创建MediaMsg数据库失败:"); 170 | e.printStackTrace(); 171 | } 172 | } 173 | 174 | public static ResultSet executeSql(final String dbPath, final String sql) { 175 | ResultSet result = null; 176 | try (Connection conn = openDB(dbPath); 177 | Statement stmt = conn.createStatement()) { 178 | result = stmt.executeQuery(sql); 179 | } catch (Exception err) { 180 | System.out.println("[-] " + err.getMessage()); 181 | } 182 | return result; 183 | } 184 | 185 | private static void enableWALMode(Connection conn) throws Exception { 186 | try (Statement stmt = conn.createStatement()) { 187 | stmt.execute("PRAGMA journal_mode=WAL;"); 188 | } 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /src/main/java/com/ppsoft1991/chatViewTool/utils/CommonUtils.java: -------------------------------------------------------------------------------- 1 | package com.ppsoft1991.chatViewTool.utils; 2 | 3 | import com.ppsoft1991.chatViewTool.GlobalConfig; 4 | import com.ppsoft1991.chatViewTool.controller.ChatController; 5 | import com.ppsoft1991.chatViewTool.mapper.microMsg.HeaderIconMapper; 6 | import com.ppsoft1991.chatViewTool.mapper.msg.MsgMapper; 7 | import javafx.scene.image.Image; 8 | import javafx.scene.image.ImageView; 9 | import org.mozilla.universalchardet.UniversalDetector; 10 | 11 | import java.io.*; 12 | import java.net.URL; 13 | import java.nio.ByteBuffer; 14 | import java.nio.charset.Charset; 15 | import java.nio.charset.StandardCharsets; 16 | import java.nio.file.Files; 17 | import java.nio.file.Paths; 18 | import java.sql.ResultSet; 19 | import java.sql.SQLException; 20 | import java.util.Arrays; 21 | import java.util.Formatter; 22 | import java.util.HashMap; 23 | import java.util.regex.Matcher; 24 | import java.util.regex.Pattern; 25 | 26 | public class CommonUtils { 27 | public static HashMap WXID_MAP = new HashMap<>(); 28 | public static HashMap WXHEADER_MAP = new HashMap<>(); 29 | public static String SELF_WXID; 30 | public static String SELF_WX_HEAD; 31 | 32 | public static void put(String key, HashMap from, HashMap to){ 33 | if (from.containsKey(key)){ 34 | to.put(key, from.get(key)); 35 | } 36 | } 37 | 38 | public static String BLACK_Header = String.valueOf(CommonUtils.class.getResource("/com/ppsoft1991/chatViewTool/ui/black.png")); 39 | 40 | private static final Pattern pattern = Pattern.compile("\\b(.*)?\u001a"); 41 | 42 | public static String getWxid(byte[] b){ 43 | Matcher matcher = pattern.matcher(new String(b, StandardCharsets.UTF_8)); 44 | if (matcher.find()) { 45 | String wxid = matcher.group(1); 46 | if (wxid!=null){ 47 | return wxid; 48 | } 49 | } 50 | return ""; 51 | } 52 | 53 | public static String getWxName(String wxid){ 54 | String wxName = WXID_MAP.get(wxid); 55 | if (wxName!=null){ 56 | return wxName; 57 | }else { 58 | return wxid; 59 | } 60 | } 61 | 62 | public static ImageView selfImage(){ 63 | return ChatController.setImage(SELF_WX_HEAD); 64 | } 65 | 66 | public static byte[] readByte(ByteBuffer bf, int length) { 67 | byte[] b = new byte[length]; 68 | b = Arrays.copyOfRange(bf.array(), bf.position(), bf.position() + length); 69 | bf.position(bf.position() + length); 70 | return b; 71 | } 72 | 73 | public static void deleteFile(String f){ 74 | File file = new File(f); 75 | if (file.exists()){ 76 | boolean delete = file.delete(); 77 | if (delete){ 78 | System.out.println("[+] DeleteFile "+f); 79 | }else { 80 | System.out.println("[-] Error,文件可能被占用了! DeleteFile "+f); 81 | } 82 | } 83 | } 84 | 85 | public static byte[] readFromJarToByte(String path) throws IOException { 86 | ByteArrayOutputStream bos = new ByteArrayOutputStream(); 87 | InputStream fs = CommonUtils.class.getResourceAsStream("/" + path); 88 | byte[] temp = new byte[4096]; 89 | int len = 0; 90 | while ((len = fs.read(temp)) != -1) { 91 | bos.write(temp, 0, len); 92 | } 93 | fs.close(); 94 | byte[] data = bos.toByteArray(); 95 | bos.close(); 96 | return data; 97 | } 98 | 99 | public static Image readImage(String path) throws Exception{ 100 | return new Image(new ByteArrayInputStream(readFromJarToByte(path))); 101 | } 102 | 103 | 104 | public static byte[] getWechatKey(String inKeyPath) { 105 | try { 106 | Charset charset = detectEncoding(inKeyPath); 107 | FileInputStream fi = new FileInputStream(inKeyPath); 108 | byte[] b = new byte[fi.available()]; 109 | fi.read(b); 110 | fi.close(); 111 | 112 | String fileContent = new String(removeBOM(b, charset),charset); 113 | return hexStringToByteArray(fileContent); 114 | } catch (Exception e) { 115 | e.printStackTrace(); 116 | } 117 | return new byte[]{}; 118 | } 119 | 120 | public static byte[] hexStringToByteArray(String hexString) { 121 | // 去掉字符串中的空格和0x前缀 122 | hexString = hexString.replaceAll("0x", "").replaceAll(",", "").replaceAll("\\s+", ""); 123 | 124 | // 计算字节数组的长度 125 | int length = hexString.length(); 126 | byte[] byteArray = new byte[length / 2]; 127 | 128 | // 将每两个字符转换为一个字节 129 | for (int i = 0; i < length; i += 2) { 130 | byteArray[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4) 131 | + Character.digit(hexString.charAt(i + 1), 16)); 132 | } 133 | 134 | return byteArray; 135 | } 136 | 137 | private static byte[] removeBOM(byte[] b, Charset charset) { 138 | if (charset.name().equals("UTF-8") && b.length >= 3 139 | && b[0] == (byte) 0xEF && b[1] == (byte) 0xBB && b[2] == (byte) 0xBF) { 140 | return Arrays.copyOfRange(b, 3, b.length); 141 | } else if (charset.name().equals("UTF-16LE") && b.length >= 2 142 | && b[0] == (byte) 0xFF && b[1] == (byte) 0xFE) { 143 | return Arrays.copyOfRange(b, 2, b.length); 144 | } else if (charset.name().equals("UTF-16BE") && b.length >= 2 145 | && b[0] == (byte) 0xFE && b[1] == (byte) 0xFF) { 146 | return Arrays.copyOfRange(b, 2, b.length); 147 | } 148 | return b; 149 | } 150 | 151 | private static Charset detectEncoding(String filePath) throws IOException { 152 | byte[] buf = new byte[4096]; 153 | FileInputStream fis = new FileInputStream(filePath); 154 | 155 | UniversalDetector detector = new UniversalDetector(null); 156 | int nread; 157 | while ((nread = fis.read(buf)) > 0 && !detector.isDone()) { 158 | detector.handleData(buf, 0, nread); 159 | } 160 | detector.dataEnd(); 161 | 162 | String encoding = detector.getDetectedCharset(); 163 | fis.close(); 164 | return encoding != null ? Charset.forName(encoding) : Charset.defaultCharset(); 165 | } 166 | 167 | public static String byteArray2Hex(final byte[] hash) { 168 | final Formatter formatter = new Formatter(); 169 | for (final byte b : hash) { 170 | formatter.format("%02x", b); 171 | } 172 | return formatter.toString(); 173 | } 174 | 175 | public static byte[] byteMerge(byte[] b1, byte[] b2) { 176 | byte[] b3 = new byte[b1.length + b2.length]; 177 | System.arraycopy(b1, 0, b3, 0, b1.length); 178 | System.arraycopy(b2, 0, b3, b1.length, b2.length); 179 | return b3; 180 | } 181 | 182 | public static String parseChatStr(String type, MsgMapper data) throws SQLException { 183 | switch (type){ 184 | case "1": 185 | return data.getStrContent(); 186 | case "3": 187 | return "[图片暂时无法展示]"; 188 | case "49": 189 | return "[文件暂时无法展示]"; 190 | case "50": 191 | return "[语音电话]"; 192 | case "47": 193 | return "[emoji]"; 194 | case "43": 195 | return "[视频暂时无法展示]"; 196 | case "34": 197 | return "[wave]"+data.getMsgSvrId(); 198 | case "10000": 199 | return "[系统消息]->"+data.getStrContent(); 200 | default: 201 | return ""; 202 | } 203 | } 204 | 205 | public static String getSilkPath(){ 206 | if (GlobalConfig.isLinux){ 207 | return ((new File("")).getAbsoluteFile()).getAbsolutePath()+ "/voice"; 208 | }else { 209 | return ((new File("")).getAbsoluteFile()).getAbsolutePath()+ "\\voice.exe"; 210 | } 211 | } 212 | 213 | public static byte[] getChatSound(String hex) throws Exception { 214 | return base64Decode(new String(runCmd(getSilkPath(),hex))); 215 | } 216 | 217 | public static byte[] runCmd(String env, String cmd) throws Exception { 218 | String[] c = {env, cmd}; 219 | Process p = Runtime.getRuntime().exec(c); 220 | byte[] b = bufferToByte(p.getInputStream()); 221 | byte[] b1 = bufferToByte(p.getErrorStream()); 222 | return byteMerge(b, b1); 223 | } 224 | 225 | public static byte[] base64Decode(String bs) { 226 | Class base64; 227 | byte[] value = null; 228 | try { 229 | base64 = Class.forName("java.util.Base64"); 230 | Object decoder = base64.getMethod("getDecoder", null).invoke(base64, null); 231 | value = (byte[]) decoder.getClass().getMethod("decode", new Class[]{String.class}).invoke(decoder, new Object[]{bs}); 232 | } catch (Exception e) { 233 | try { 234 | base64 = Class.forName("sun.misc.BASE64Decoder"); 235 | Object decoder = base64.newInstance(); 236 | value = (byte[]) decoder.getClass().getMethod("decodeBuffer", new Class[]{String.class}).invoke(decoder, new Object[]{bs}); 237 | } catch (Exception ignored) { 238 | } 239 | } 240 | return value; 241 | } 242 | 243 | public static String base64Encode(byte[] bs) { 244 | Class base64; 245 | String value = ""; 246 | try { 247 | base64 = Class.forName("java.util.Base64"); 248 | Object Encoder = base64.getMethod("getEncoder", null).invoke(base64, null); 249 | value = (String) Encoder.getClass().getMethod("encodeToString", new Class[]{byte[].class}).invoke(Encoder, new Object[]{bs}); 250 | } catch (Exception e) { 251 | try { 252 | base64 = Class.forName("sun.misc.BASE64Encoder"); 253 | Object Encoder = base64.newInstance(); 254 | value = (String) Encoder.getClass().getMethod("encode", new Class[]{byte[].class}).invoke(Encoder, new Object[]{bs}); 255 | } catch (Exception ignored) { 256 | } 257 | } 258 | return value.replaceAll("[\\s*\t\n\r]", ""); 259 | } 260 | private static byte[] bufferToByte(InputStream stream) throws IOException { 261 | int _byte; 262 | ByteArrayOutputStream stream1 = new ByteArrayOutputStream(); 263 | while ((_byte = stream.read()) != -1) { 264 | stream1.write(_byte); 265 | } 266 | stream1.close(); 267 | return stream1.toByteArray(); 268 | } 269 | 270 | public static String getChatMedia(String absolutePath, String msgSvrId) throws Exception { 271 | final String sql = "select * from Media where Media.Reserved0 = '"+msgSvrId+"'"; 272 | final ResultSet data = CommonFunc.executeSql(absolutePath + "/MediaMSG_ALL.db", sql); 273 | if (data.next()){ 274 | String imgPath; 275 | if (GlobalConfig.isLinux) { 276 | imgPath = absolutePath + "/.wavcache/" + msgSvrId + ".wav"; 277 | }else { 278 | imgPath = absolutePath + "\\.wavcache\\" + msgSvrId + ".wav"; 279 | } 280 | if (!Files.exists(Paths.get(imgPath))) { 281 | if (!(new File(imgPath).getParentFile().exists())){ 282 | (new File(imgPath).getParentFile()).mkdirs(); 283 | } 284 | System.out.println("生成语音文件..."); 285 | byte[] buf = getChatSound("0x" + byteArray2Hex(data.getBytes("Buf"))); 286 | FileOutputStream stream = new FileOutputStream(imgPath); 287 | stream.write(buf); 288 | stream.flush(); 289 | stream.close(); 290 | } 291 | return imgPath; 292 | } 293 | return null; 294 | } 295 | 296 | public static URL getResource(String resource) { 297 | if (resource.startsWith("/")) { 298 | resource = resource.substring(1); 299 | } 300 | URL res = GlobalConfig.class.getResource(GlobalConfig.RESOURCE + resource); 301 | if (res == null){ 302 | // log.error("getResource error URL:{}", resource); 303 | res = GlobalConfig.class.getResource(String.format("/%s", resource)); 304 | } 305 | assert res != null; 306 | // log.debug("getResource URL:{}, ClassName:{}", res, Config.class.getName()); 307 | return res; 308 | } 309 | 310 | public static InputStream resource(String resource) { 311 | if (new File(resource).exists()) { 312 | try { 313 | return new FileInputStream(resource); 314 | } catch (Exception FileNotFoundException) { 315 | } 316 | } else { 317 | if (resource.startsWith("/")) { 318 | resource = resource.substring(0, resource.length() - 1); 319 | } 320 | return CommonUtils.class.getResourceAsStream(GlobalConfig.RESOURCE + resource); 321 | } 322 | return null; 323 | } 324 | } 325 | -------------------------------------------------------------------------------- /src/main/java/com/ppsoft1991/chatViewTool/utils/GroupProtobuf.java: -------------------------------------------------------------------------------- 1 | package com.ppsoft1991.chatViewTool.utils; 2 | 3 | import com.ppsoft1991.chatViewTool.mapper.msg.Msg; 4 | 5 | public class GroupProtobuf { 6 | public static void deserializedExtra(byte[] data){ 7 | ; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/ppsoft1991/chatViewTool/utils/MicroMsgDB.java: -------------------------------------------------------------------------------- 1 | package com.ppsoft1991.chatViewTool.utils; 2 | 3 | import com.mchange.v2.c3p0.ComboPooledDataSource; 4 | import com.ppsoft1991.chatViewTool.mapper.microMsg.*; 5 | import org.springframework.jdbc.core.BeanPropertyRowMapper; 6 | import org.springframework.jdbc.core.JdbcTemplate; 7 | 8 | import java.beans.PropertyVetoException; 9 | import java.util.List; 10 | 11 | 12 | public class MicroMsgDB { 13 | private static JdbcTemplate MICRO_MSG_TEMPLATE = null; 14 | private static String MICRO_MSG_DB_PATH = null; 15 | public static JdbcTemplate getMicroMsgTemplate() throws PropertyVetoException { 16 | if (MICRO_MSG_TEMPLATE==null) { 17 | ComboPooledDataSource dataSource = new ComboPooledDataSource(); 18 | dataSource.setDriverClass("org.sqlite.JDBC"); 19 | dataSource.setJdbcUrl("jdbc:sqlite:"+MICRO_MSG_DB_PATH); 20 | MICRO_MSG_TEMPLATE = new JdbcTemplate(dataSource); 21 | } 22 | return MICRO_MSG_TEMPLATE; 23 | } 24 | 25 | public static void init(String db_path) throws PropertyVetoException { 26 | MICRO_MSG_DB_PATH = db_path; 27 | if (MICRO_MSG_TEMPLATE != null){ 28 | ComboPooledDataSource dataSource = new ComboPooledDataSource(); 29 | dataSource.setDriverClass("org.sqlite.JDBC"); 30 | dataSource.setJdbcUrl("jdbc:sqlite:"+MICRO_MSG_DB_PATH); 31 | MICRO_MSG_TEMPLATE = new JdbcTemplate(dataSource); 32 | } 33 | } 34 | 35 | public static HeaderIconMapper getUserHeadUrl(final String wxid) throws PropertyVetoException { 36 | final String sql = "SELECT usrName,smallHeadImgUrl,bigHeadImgUrl FROM ContactHeadImgUrl WHERE usrName ='" + wxid + "'"; 37 | return getMicroMsgTemplate().query(sql, new BeanPropertyRowMapper<>(HeaderIconMapper.class)).get(0); 38 | } 39 | 40 | public static void getAllUserHeadUrl(String absolutePath) throws PropertyVetoException { 41 | final String sql = "SELECT usrName,smallHeadImgUrl,bigHeadImgUrl FROM ContactHeadImgUrl"; 42 | for (HeaderIconMapper userHeader :getMicroMsgTemplate().query(sql, new BeanPropertyRowMapper<>(HeaderIconMapper.class))){ 43 | CommonUtils.WXHEADER_MAP.put(userHeader.getUsrName(), getHeaderUrl(absolutePath,userHeader)); 44 | } 45 | } 46 | 47 | public static String getSelfUserName() throws PropertyVetoException { 48 | final String sql="SELECT UserName FROM PatInfo"; 49 | return getMicroMsgTemplate().query(sql, new BeanPropertyRowMapper<>(SelfUserMap.class)).get(0).getUsername(); 50 | } 51 | 52 | public static String getHeaderUrl(String absolutePath,HeaderIconMapper userHeadUrl){ 53 | return "file:" + absolutePath + "/.imgcache/" + userHeadUrl.getUsrName() + ".jpg"; 54 | } 55 | 56 | public static List queryStrUsrName() throws PropertyVetoException { 57 | final String sql = "SELECT strUsrName from Session"; 58 | return getMicroMsgTemplate().query(sql, new BeanPropertyRowMapper<>(SessionMapper.class)); 59 | } 60 | 61 | public static List queryALLSession() throws PropertyVetoException { 62 | // 以前这里有个bug,order by 会报错 63 | final String sql = "SELECT * from Session ORDER BY nOrder DESC"; // ORDER BY nOrder DESC"; 64 | return getMicroMsgTemplate().query(sql, new BeanPropertyRowMapper<>(SessionMapper.class)); 65 | } 66 | 67 | public static ContactMapper queryUserContact(String wxid) throws PropertyVetoException { 68 | final String sql = "SELECT UserName,Alias,NickName,Remark FROM Contact WHERE UserName = '" + wxid + "'"; 69 | List mappers = getMicroMsgTemplate().query(sql, new BeanPropertyRowMapper<>(ContactMapper.class)); 70 | if (mappers.size()>0){ 71 | return getMicroMsgTemplate().query(sql, new BeanPropertyRowMapper<>(ContactMapper.class)).get(0); 72 | }else { 73 | return null; 74 | } 75 | 76 | } 77 | 78 | public static ChatRoomMapper queryChatRoom(String chatroom) throws PropertyVetoException { 79 | final String sql = "SELECT UserNameList,DisplayNameList FROM ChatRoom where ChatRoomName='" + chatroom + "'"; 80 | List mappers = getMicroMsgTemplate().query(sql, new BeanPropertyRowMapper<>(ChatRoomMapper.class)); 81 | if (mappers.size()>0){ 82 | return getMicroMsgTemplate().query(sql, new BeanPropertyRowMapper<>(ChatRoomMapper.class)).get(0); 83 | }else { 84 | return null; 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/com/ppsoft1991/chatViewTool/utils/MsgDB.java: -------------------------------------------------------------------------------- 1 | package com.ppsoft1991.chatViewTool.utils; 2 | 3 | import com.mchange.v2.c3p0.ComboPooledDataSource; 4 | import com.ppsoft1991.chatViewTool.mapper.msg.MsgMapper; 5 | import org.springframework.jdbc.core.BeanPropertyRowMapper; 6 | import org.springframework.jdbc.core.JdbcTemplate; 7 | 8 | import java.beans.PropertyVetoException; 9 | import java.util.List; 10 | import java.util.Map; 11 | 12 | public class MsgDB { 13 | private static JdbcTemplate MSG_TEMPLATE = null; 14 | private static String MSG_DB_PATH = null; 15 | private static ComboPooledDataSource dataSource; 16 | 17 | public static void init(String path) throws Exception { 18 | MSG_DB_PATH = path; 19 | if (MSG_TEMPLATE != null){ 20 | dataSource = new ComboPooledDataSource(); 21 | dataSource.setDriverClass("org.sqlite.JDBC"); 22 | dataSource.setJdbcUrl("jdbc:sqlite:"+MSG_DB_PATH); 23 | MSG_TEMPLATE.getDataSource().getConnection().close(); 24 | MSG_TEMPLATE = new JdbcTemplate(dataSource); 25 | } 26 | } 27 | 28 | public static void close(){ 29 | if (dataSource != null){ 30 | dataSource.close(); 31 | } 32 | } 33 | 34 | public static JdbcTemplate getMsgTemplate() throws PropertyVetoException { 35 | if (MSG_TEMPLATE == null){ 36 | if (MSG_DB_PATH == null){ 37 | System.out.println("[-] MSG_DB_PATH is null"); 38 | } 39 | ComboPooledDataSource dataSource = new ComboPooledDataSource(); 40 | dataSource.setDriverClass("org.sqlite.JDBC"); 41 | dataSource.setJdbcUrl("jdbc:sqlite:"+MSG_DB_PATH); 42 | MSG_TEMPLATE = new JdbcTemplate(dataSource); 43 | //generateStrTalkerIndex(); 44 | } 45 | return MSG_TEMPLATE; 46 | } 47 | 48 | 49 | public static List searchMessageFromText(final String text) throws PropertyVetoException { 50 | String sql = "select * FROM MSG INDEXED BY search_chat where StrContent like '%"+text+"%';"; 51 | return getMsgTemplate().query(sql, new BeanPropertyRowMapper<>(MsgMapper.class)); 52 | } 53 | 54 | public static List> searchFilterMessageFromWxid(final String wxid, final String filterStr) throws PropertyVetoException { 55 | final String sql = "select id from" + 56 | "(select row_number() over () as id,* FROM MSG INDEXED BY search_chat where StrTalker='"+wxid+"' ORDER BY CreateTime)" + 57 | "where StrContent like '%"+filterStr+"%';"; 58 | return getMsgTemplate().queryForList(sql); 59 | } 60 | 61 | public static Integer getMsgCount(String wxid) throws PropertyVetoException { 62 | final String sql = "SELECT count(*) AS num FROM MSG WHERE StrTalker='" + wxid + "'"; 63 | return MsgDB.getMsgTemplate().queryForObject(sql, Integer.class); 64 | } 65 | 66 | private static void generateStrTalkerIndex(){ 67 | final String sql = "CREATE INDEX IF NOT EXISTS search_chat on \"MSG\" (strTalker, CreateTime);"; 68 | try { 69 | getMsgTemplate().execute(sql); 70 | System.out.println("[+] 创建索引成功:"); 71 | }catch (Exception e){ 72 | System.out.println("[-] 创建索引失败:"); 73 | e.printStackTrace(); 74 | } 75 | } 76 | 77 | public static List queryChatMsgLimit(String wxid, int currentPage) throws PropertyVetoException { 78 | final String sql = "SELECT * FROM MSG INDEXED BY search_chat where StrTalker='" + wxid + "' ORDER BY CreateTime LIMIT " + currentPage * 500 + ",500"; 79 | return getMsgTemplate().query(sql, new BeanPropertyRowMapper<>(MsgMapper.class)); 80 | } 81 | 82 | public static void generateMSG() throws PropertyVetoException { 83 | final String generateMsgSql = "create table MSG\n" + 84 | "(\n" + 85 | " localId INTEGER\n" + 86 | " primary key autoincrement,\n" + 87 | " TalkerId INT default 0,\n" + 88 | " MsgSvrID INT,\n" + 89 | " Type INT,\n" + 90 | " SubType INT,\n" + 91 | " IsSender INT,\n" + 92 | " CreateTime INT,\n" + 93 | " Sequence INT default 0,\n" + 94 | " StatusEx INT default 0,\n" + 95 | " FlagEx INT,\n" + 96 | " Status INT,\n" + 97 | " MsgServerSeq INT,\n" + 98 | " MsgSequence INT,\n" + 99 | " StrTalker TEXT,\n" + 100 | " StrContent TEXT,\n" + 101 | " DisplayContent TEXT,\n" + 102 | " Reserved0 INT default 0,\n" + 103 | " Reserved1 INT default 0,\n" + 104 | " Reserved2 INT default 0,\n" + 105 | " Reserved3 INT default 0,\n" + 106 | " Reserved4 TEXT,\n" + 107 | " Reserved5 TEXT,\n" + 108 | " Reserved6 TEXT,\n" + 109 | " CompressContent BLOB,\n" + 110 | " BytesExtra BLOB,\n" + 111 | " BytesTrans BLOB\n" + 112 | ");"; 113 | final String generateIndexSql = "CREATE INDEX IF NOT EXISTS search_chat on \"MSG\" (strTalker, CreateTime);"; 114 | try { 115 | getMsgTemplate().execute(generateMsgSql); 116 | System.out.println("[+] 创建MSG数据库成功:"); 117 | }catch (Exception e){ 118 | System.out.println("[-] 创建MSG数据库失败:"); 119 | e.printStackTrace(); 120 | } 121 | 122 | try { 123 | getMsgTemplate().execute(generateIndexSql); 124 | System.out.println("[+] 创建MSG索引成功:"); 125 | }catch (Exception e){ 126 | System.out.println("[-] 创建MSG索引失败:"); 127 | e.printStackTrace(); 128 | } 129 | } 130 | 131 | 132 | 133 | } 134 | -------------------------------------------------------------------------------- /src/main/java/com/ppsoft1991/chatViewTool/utils/WechatDb.java: -------------------------------------------------------------------------------- 1 | package com.ppsoft1991.chatViewTool.utils; 2 | 3 | import org.bouncycastle.crypto.PBEParametersGenerator; 4 | import org.bouncycastle.crypto.digests.SHA1Digest; 5 | import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator; 6 | import org.bouncycastle.crypto.params.KeyParameter; 7 | 8 | import javax.crypto.Cipher; 9 | import javax.crypto.Mac; 10 | import javax.crypto.spec.IvParameterSpec; 11 | import javax.crypto.spec.SecretKeySpec; 12 | import java.io.*; 13 | import java.nio.charset.StandardCharsets; 14 | import java.nio.file.Files; 15 | import java.nio.file.Paths; 16 | import java.util.Arrays; 17 | 18 | public class WechatDb { 19 | private static final int IV_SIZE = 16; 20 | private static final int DEFAULT_PAGE_SIZE = 4096; 21 | private static final byte[] PE_HEADER = "SQLite format 3\0".getBytes(StandardCharsets.US_ASCII); 22 | 23 | private final byte[] salt = new byte[16]; 24 | private final byte[] mac_salt = new byte[16]; 25 | private final byte[] WECHAT_KEY; 26 | private final byte[] keyBytes; 27 | private final Mac HmacSHA1; 28 | private final byte[] iV = new byte[16]; 29 | private final String inputFile; 30 | private final String outputFile; 31 | 32 | public WechatDb(String dbPass, String dbFile) throws Exception { 33 | System.out.println("[+] 开始解密: " + dbFile); 34 | WECHAT_KEY = CommonUtils.getWechatKey(dbPass); 35 | inputFile = dbFile; 36 | outputFile = dbFile + "_dec.db"; 37 | 38 | try (FileInputStream in = new FileInputStream(dbFile)) { 39 | in.read(salt); 40 | for (int i = 0; i < mac_salt.length; ++i) { 41 | mac_salt[i] = (byte) (salt[i] ^ 0x3A); 42 | } 43 | 44 | PBEParametersGenerator generator = new PKCS5S2ParametersGenerator(new SHA1Digest()); 45 | generator.init(WECHAT_KEY, salt, 64000); 46 | keyBytes = ((KeyParameter) generator.generateDerivedParameters(256)).getKey(); 47 | 48 | PBEParametersGenerator generatorSha1 = new PKCS5S2ParametersGenerator(new SHA1Digest()); 49 | generatorSha1.init(keyBytes, mac_salt, 2); 50 | byte[] ctxCode = ((KeyParameter) generatorSha1.generateDerivedParameters(256)).getKey(); 51 | SecretKeySpec signingKey = new SecretKeySpec(ctxCode, "HmacSHA1"); 52 | HmacSHA1 = Mac.getInstance("HmacSHA1"); 53 | HmacSHA1.init(signingKey); 54 | } 55 | 56 | File outputF = new File(outputFile); 57 | if (outputF.exists()) { 58 | System.out.println("[+] 删除原先的解密文件" + outputFile); 59 | outputF.delete(); 60 | } 61 | } 62 | 63 | private Cipher initCipher(byte[] iv) throws Exception { 64 | Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding"); 65 | SecretKeySpec decKey = new SecretKeySpec(keyBytes, "AES"); 66 | cipher.init(Cipher.DECRYPT_MODE, decKey, new IvParameterSpec(iv)); 67 | return cipher; 68 | } 69 | 70 | public void decryptDb() throws Exception { 71 | byte[] page = new byte[DEFAULT_PAGE_SIZE]; 72 | byte[] data1 = new byte[4032]; 73 | byte[] padding = new byte[48]; 74 | 75 | try (BufferedInputStream in = new BufferedInputStream(Files.newInputStream(Paths.get(inputFile))); 76 | BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(outputFile, true))) { 77 | 78 | in.skip(16); // skip salt 79 | 80 | // 读取第一页数据 81 | readFully(in, data1); 82 | readFully(in, padding); 83 | System.arraycopy(padding, 0, iV, 0, iV.length); 84 | 85 | // 解密第一页数据 86 | Cipher cipher = initCipher(iV); 87 | data1 = cipher.doFinal(data1); 88 | out.write(PE_HEADER); 89 | out.write(data1); 90 | out.write(padding); 91 | 92 | // 处理后续页面 93 | while (in.read(page) != -1) { 94 | byte[] data2 = Arrays.copyOfRange(page, 0, 4048); 95 | System.arraycopy(page, 4048, iV, 0, iV.length); 96 | 97 | // 解密后续页面数据 98 | cipher = initCipher(iV); 99 | data2 = cipher.doFinal(data2); 100 | byte[] pagePadding = Arrays.copyOfRange(page, 4048, 4096); 101 | 102 | out.write(data2); 103 | out.write(pagePadding); 104 | } 105 | } 106 | } 107 | 108 | private void readFully(InputStream in, byte[] buffer) throws IOException { 109 | int bytesRead = 0; 110 | int offset = 0; 111 | while (offset < buffer.length && (bytesRead = in.read(buffer, offset, buffer.length - offset)) != -1) { 112 | offset += bytesRead; 113 | } 114 | if (offset < buffer.length) { 115 | throw new EOFException("Reached end of stream before reading fully."); 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module com.ppsoft1991.chatViewTool { 2 | requires javafx.fxml; 3 | requires org.bouncycastle.provider; 4 | requires java.desktop; 5 | requires java.sql; 6 | requires javafx.media; 7 | requires com.github.albfernandez.juniversalchardet; 8 | requires spring.jdbc; 9 | requires c3p0; 10 | requires javafx.controls; 11 | requires com.google.protobuf; 12 | 13 | 14 | opens com.ppsoft1991.chatViewTool to javafx.fxml; 15 | opens com.ppsoft1991.chatViewTool.fx to javafx.fxml, java.base; 16 | exports com.ppsoft1991.chatViewTool; 17 | exports com.ppsoft1991.chatViewTool.utils; 18 | exports com.ppsoft1991.chatViewTool.mapper.microMsg; 19 | exports com.ppsoft1991.chatViewTool.mapper.msg; 20 | opens com.ppsoft1991.chatViewTool.utils to javafx.fxml; 21 | opens com.ppsoft1991.chatViewTool.controller to javafx.fxml; 22 | exports com.ppsoft1991.chatViewTool.fx; 23 | } -------------------------------------------------------------------------------- /src/main/resources/com/ppsoft1991/chatViewTool/ui/black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ppsoft1991/ChatViewTools/ee88ed393043d675c0ef0acdc4bc96ef94fe97b3/src/main/resources/com/ppsoft1991/chatViewTool/ui/black.png -------------------------------------------------------------------------------- /src/main/resources/com/ppsoft1991/chatViewTool/ui/chat-application.fxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 |