├── app ├── ui_pc │ ├── __init__.py │ ├── tool │ │ ├── __init__.py │ │ ├── pc_decrypt │ │ │ └── __init__.py │ │ ├── tool_window.py │ │ └── toolUI.py │ ├── chat │ │ ├── __init__.py │ │ ├── chatInfoUi.py │ │ ├── chatUi.py │ │ └── chatInfoUi.ui │ ├── contact │ │ ├── __init__.py │ │ ├── userinfo │ │ │ ├── __init__.py │ │ │ └── userinfo.py │ │ ├── contactUi.py │ │ ├── contactInfoUi.py │ │ └── contactInfo.py │ └── Icon.py ├── web_ui │ ├── __init__.py │ └── web.py ├── ImageBox │ ├── __init__.py │ ├── icons │ │ ├── zoom_in.jpg │ │ └── zoom_out.jpg │ ├── images │ │ ├── wallhaven-748705.jpg │ │ ├── wallhaven-753155.jpg │ │ └── wallhaven-vml6em.jpg │ ├── run.py │ ├── config.py │ └── ui.py ├── resources │ ├── __init__.py │ ├── icons │ │ ├── __init__.py │ │ ├── 404.png │ │ ├── resources.qrc │ │ ├── output.svg │ │ ├── emotion.svg │ │ ├── analysis.svg │ │ ├── chat.svg │ │ ├── myinfo.svg │ │ ├── back.svg │ │ ├── search.svg │ │ ├── word.svg │ │ ├── html.svg │ │ ├── contact (1).svg │ │ ├── logo.svg │ │ ├── contact (2).svg │ │ ├── csv.svg │ │ ├── 404.svg │ │ ├── contact.svg │ │ ├── annual_report.svg │ │ └── annual_report1.svg │ └── resource.qrc ├── Ui │ ├── contact │ │ ├── report │ │ │ ├── __init__.py │ │ │ ├── annual_report.py │ │ │ └── report.py │ │ ├── emotion │ │ │ ├── __init__.py │ │ │ ├── emotionUi.ui │ │ │ └── emotionUi.py │ │ ├── analysis │ │ │ └── charts - 副本.zip │ │ ├── __init__.py │ │ ├── userinfo │ │ │ ├── __init__.py │ │ │ └── userinfo.py │ │ ├── test.ui │ │ ├── contactUi.py │ │ ├── contact.py │ │ └── contactInfoUi.py │ ├── chat │ │ ├── myinfo.zip │ │ └── __init__.py │ ├── __init__.py │ ├── Icon.py │ ├── userinfo │ │ ├── userinfo.py │ │ ├── userinfoUi.py │ │ └── userinfoUi.ui │ └── decrypt │ │ ├── decryptUi.ui │ │ ├── decryptUi.py │ │ └── decrypt.py ├── util │ ├── __init__.py │ ├── search.py │ ├── path.py │ ├── dat2pic.py │ └── emoji.py ├── bg.png ├── components │ ├── __init__.py │ ├── prompt_bar.py │ ├── contact_info_ui.py │ └── Button_Contact.py ├── data │ ├── bg.gif │ ├── bg.png │ ├── icon.png │ ├── icon60x60.png │ └── __init__.py ├── log │ ├── __init__.py │ └── logger.py ├── __init__.py ├── DataBase │ ├── config.txt │ ├── __init__.py │ ├── misc.py │ ├── micro_msg.py │ ├── merge.py │ ├── msg.py │ └── hard_link.py ├── main │ └── __init__.py ├── config.py ├── person_pc.py ├── person.py └── decrypt │ └── decrypt.py ├── doc ├── 数据库介绍.md ├── images │ ├── MT │ ├── qq.jpg │ ├── chat_.png │ ├── dirs.png │ ├── logo.png │ ├── cv-opration │ ├── cv_process │ ├── setting.png │ ├── pc_contact.png │ ├── html_message.png │ ├── path_select.png │ ├── messages_demo.png │ ├── pc_decrypt_info.png │ ├── image-20230520235113261.png │ ├── image-20230520235220104.png │ ├── image-20230520235338305.png │ ├── image-20230520235351749.png │ ├── image-20230520235400772.png │ ├── image-20230520235409112.png │ ├── image-20230520235422128.png │ ├── image-20230520235431091.png │ ├── image-20230521001305274.png │ ├── image-20230521001547481.png │ └── image-20230521001726799.png ├── 获取个人文件 │ └── 使用说明.md └── 电脑端使用教程.md ├── resource ├── render │ ├── __init__.py │ ├── templates │ │ ├── nb_components.html │ │ ├── components.html │ │ ├── nb_nteract.html │ │ ├── simple_chart.html │ │ ├── nb_jupyter_lab.html │ │ ├── nb_jupyter_lab_tab.html │ │ ├── nb_jupyter_notebook.html │ │ ├── nb_jupyter_notebook_tab.html │ │ ├── simple_tab.html │ │ ├── simple_page.html │ │ ├── simple_globe.html │ │ └── nb_jupyter_globe.html │ ├── display.py │ ├── snapshot.py │ └── engine.py └── datasets │ ├── countries_regions_db.json │ └── __init__.py ├── logo.ico ├── logo16x16.ico ├── requirements.txt ├── sqlcipher-3.0.1 └── bin │ ├── bat使用说明.txt │ ├── sqlcipher.bat │ ├── sqlcipher - 副本.txt │ ├── sqlcipher-shell32.exe │ ├── sqlcipher-shell64.exe │ └── adb.txt ├── requirements_decrypt.txt ├── .idea ├── copyright │ └── copyright.xml ├── vcs.xml ├── inspectionProfiles │ └── profiles_settings.xml ├── misc.xml └── WeChatMsg.iml ├── requirements_pc.txt ├── .github └── ISSUE_TEMPLATE │ ├── custom.md │ ├── feature_request.md │ └── bug_report.md ├── .gitignore ├── hook-pyecharts.py ├── auth_info_key_prefs.xml ├── decrypt_window.py ├── main_pc.py ├── main.py └── main.spec /app/ui_pc/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/web_ui/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/ImageBox/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/resources/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/Ui/contact/report/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/resources/icons/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/ui_pc/tool/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/Ui/contact/emotion/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/util/__init__.py: -------------------------------------------------------------------------------- 1 | from .path import get_abs_path 2 | -------------------------------------------------------------------------------- /doc/数据库介绍.md: -------------------------------------------------------------------------------- 1 | # 微信数据库介绍 2 | 3 | **这个人比较懒,还什么都没写** 4 | 5 | -------------------------------------------------------------------------------- /app/ui_pc/chat/__init__.py: -------------------------------------------------------------------------------- 1 | from .chat_window import ChatWindow 2 | -------------------------------------------------------------------------------- /resource/render/__init__.py: -------------------------------------------------------------------------------- 1 | from .snapshot import make_snapshot 2 | -------------------------------------------------------------------------------- /app/ui_pc/contact/__init__.py: -------------------------------------------------------------------------------- 1 | from .contact_window import ContactWindow 2 | -------------------------------------------------------------------------------- /app/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wo1261931780/fork-WeChatMsg/HEAD/app/bg.png -------------------------------------------------------------------------------- /app/components/__init__.py: -------------------------------------------------------------------------------- 1 | from .contact_info_ui import ContactQListWidgetItem 2 | -------------------------------------------------------------------------------- /logo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wo1261931780/fork-WeChatMsg/HEAD/logo.ico -------------------------------------------------------------------------------- /app/data/bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wo1261931780/fork-WeChatMsg/HEAD/app/data/bg.gif -------------------------------------------------------------------------------- /app/data/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wo1261931780/fork-WeChatMsg/HEAD/app/data/bg.png -------------------------------------------------------------------------------- /app/log/__init__.py: -------------------------------------------------------------------------------- 1 | from .logger import log, logger 2 | 3 | __all__ = ["logger", "log"] 4 | -------------------------------------------------------------------------------- /doc/images/MT: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wo1261931780/fork-WeChatMsg/HEAD/doc/images/MT -------------------------------------------------------------------------------- /logo16x16.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wo1261931780/fork-WeChatMsg/HEAD/logo16x16.ico -------------------------------------------------------------------------------- /app/data/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wo1261931780/fork-WeChatMsg/HEAD/app/data/icon.png -------------------------------------------------------------------------------- /doc/images/qq.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wo1261931780/fork-WeChatMsg/HEAD/doc/images/qq.jpg -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wo1261931780/fork-WeChatMsg/HEAD/requirements.txt -------------------------------------------------------------------------------- /doc/images/chat_.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wo1261931780/fork-WeChatMsg/HEAD/doc/images/chat_.png -------------------------------------------------------------------------------- /doc/images/dirs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wo1261931780/fork-WeChatMsg/HEAD/doc/images/dirs.png -------------------------------------------------------------------------------- /doc/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wo1261931780/fork-WeChatMsg/HEAD/doc/images/logo.png -------------------------------------------------------------------------------- /app/Ui/chat/myinfo.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wo1261931780/fork-WeChatMsg/HEAD/app/Ui/chat/myinfo.zip -------------------------------------------------------------------------------- /app/data/icon60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wo1261931780/fork-WeChatMsg/HEAD/app/data/icon60x60.png -------------------------------------------------------------------------------- /doc/images/cv-opration: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wo1261931780/fork-WeChatMsg/HEAD/doc/images/cv-opration -------------------------------------------------------------------------------- /doc/images/cv_process: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wo1261931780/fork-WeChatMsg/HEAD/doc/images/cv_process -------------------------------------------------------------------------------- /doc/images/setting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wo1261931780/fork-WeChatMsg/HEAD/doc/images/setting.png -------------------------------------------------------------------------------- /doc/images/pc_contact.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wo1261931780/fork-WeChatMsg/HEAD/doc/images/pc_contact.png -------------------------------------------------------------------------------- /app/resources/icons/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wo1261931780/fork-WeChatMsg/HEAD/app/resources/icons/404.png -------------------------------------------------------------------------------- /app/ui_pc/tool/pc_decrypt/__init__.py: -------------------------------------------------------------------------------- 1 | from .pc_decrypt import DecryptControl 2 | 3 | __all__ = ['DecryptControl'] 4 | -------------------------------------------------------------------------------- /doc/images/html_message.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wo1261931780/fork-WeChatMsg/HEAD/doc/images/html_message.png -------------------------------------------------------------------------------- /doc/images/path_select.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wo1261931780/fork-WeChatMsg/HEAD/doc/images/path_select.png -------------------------------------------------------------------------------- /app/ImageBox/icons/zoom_in.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wo1261931780/fork-WeChatMsg/HEAD/app/ImageBox/icons/zoom_in.jpg -------------------------------------------------------------------------------- /doc/images/messages_demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wo1261931780/fork-WeChatMsg/HEAD/doc/images/messages_demo.png -------------------------------------------------------------------------------- /doc/images/pc_decrypt_info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wo1261931780/fork-WeChatMsg/HEAD/doc/images/pc_decrypt_info.png -------------------------------------------------------------------------------- /app/ImageBox/icons/zoom_out.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wo1261931780/fork-WeChatMsg/HEAD/app/ImageBox/icons/zoom_out.jpg -------------------------------------------------------------------------------- /sqlcipher-3.0.1/bin/bat使用说明.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wo1261931780/fork-WeChatMsg/HEAD/sqlcipher-3.0.1/bin/bat使用说明.txt -------------------------------------------------------------------------------- /sqlcipher-3.0.1/bin/sqlcipher.bat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wo1261931780/fork-WeChatMsg/HEAD/sqlcipher-3.0.1/bin/sqlcipher.bat -------------------------------------------------------------------------------- /app/ImageBox/images/wallhaven-748705.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wo1261931780/fork-WeChatMsg/HEAD/app/ImageBox/images/wallhaven-748705.jpg -------------------------------------------------------------------------------- /app/ImageBox/images/wallhaven-753155.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wo1261931780/fork-WeChatMsg/HEAD/app/ImageBox/images/wallhaven-753155.jpg -------------------------------------------------------------------------------- /app/ImageBox/images/wallhaven-vml6em.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wo1261931780/fork-WeChatMsg/HEAD/app/ImageBox/images/wallhaven-vml6em.jpg -------------------------------------------------------------------------------- /app/Ui/contact/analysis/charts - 副本.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wo1261931780/fork-WeChatMsg/HEAD/app/Ui/contact/analysis/charts - 副本.zip -------------------------------------------------------------------------------- /doc/images/image-20230520235113261.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wo1261931780/fork-WeChatMsg/HEAD/doc/images/image-20230520235113261.png -------------------------------------------------------------------------------- /doc/images/image-20230520235220104.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wo1261931780/fork-WeChatMsg/HEAD/doc/images/image-20230520235220104.png -------------------------------------------------------------------------------- /doc/images/image-20230520235338305.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wo1261931780/fork-WeChatMsg/HEAD/doc/images/image-20230520235338305.png -------------------------------------------------------------------------------- /doc/images/image-20230520235351749.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wo1261931780/fork-WeChatMsg/HEAD/doc/images/image-20230520235351749.png -------------------------------------------------------------------------------- /doc/images/image-20230520235400772.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wo1261931780/fork-WeChatMsg/HEAD/doc/images/image-20230520235400772.png -------------------------------------------------------------------------------- /doc/images/image-20230520235409112.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wo1261931780/fork-WeChatMsg/HEAD/doc/images/image-20230520235409112.png -------------------------------------------------------------------------------- /doc/images/image-20230520235422128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wo1261931780/fork-WeChatMsg/HEAD/doc/images/image-20230520235422128.png -------------------------------------------------------------------------------- /doc/images/image-20230520235431091.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wo1261931780/fork-WeChatMsg/HEAD/doc/images/image-20230520235431091.png -------------------------------------------------------------------------------- /doc/images/image-20230521001305274.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wo1261931780/fork-WeChatMsg/HEAD/doc/images/image-20230521001305274.png -------------------------------------------------------------------------------- /doc/images/image-20230521001547481.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wo1261931780/fork-WeChatMsg/HEAD/doc/images/image-20230521001547481.png -------------------------------------------------------------------------------- /doc/images/image-20230521001726799.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wo1261931780/fork-WeChatMsg/HEAD/doc/images/image-20230521001726799.png -------------------------------------------------------------------------------- /sqlcipher-3.0.1/bin/sqlcipher - 副本.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wo1261931780/fork-WeChatMsg/HEAD/sqlcipher-3.0.1/bin/sqlcipher - 副本.txt -------------------------------------------------------------------------------- /app/resources/icons/resources.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | logo.svg 4 | 5 | 6 | -------------------------------------------------------------------------------- /requirements_decrypt.txt: -------------------------------------------------------------------------------- 1 | PyQt5 2 | psutil 3 | pycryptodomex 4 | pywin32 5 | pymem 6 | silk-python 7 | pyaudio 8 | fuzzywuzzy 9 | python-Levenshtein -------------------------------------------------------------------------------- /sqlcipher-3.0.1/bin/sqlcipher-shell32.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wo1261931780/fork-WeChatMsg/HEAD/sqlcipher-3.0.1/bin/sqlcipher-shell32.exe -------------------------------------------------------------------------------- /sqlcipher-3.0.1/bin/sqlcipher-shell64.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wo1261931780/fork-WeChatMsg/HEAD/sqlcipher-3.0.1/bin/sqlcipher-shell64.exe -------------------------------------------------------------------------------- /.idea/copyright/copyright.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /resource/render/templates/nb_components.html: -------------------------------------------------------------------------------- 1 | {% import 'macro' as macro %} 2 | 3 | {% for chart in charts %} 4 | {{ macro.gen_components_content(chart) }} 5 | {% endfor %} 6 | -------------------------------------------------------------------------------- /requirements_pc.txt: -------------------------------------------------------------------------------- 1 | PyQt5 2 | psutil 3 | pycryptodomex 4 | pywin32 5 | pymem 6 | silk-python 7 | pyaudio 8 | fuzzywuzzy 9 | python-Levenshtein 10 | pillow 11 | requests 12 | -------------------------------------------------------------------------------- /sqlcipher-3.0.1/bin/adb.txt: -------------------------------------------------------------------------------- 1 | PRAGMA key = '10f35f1'; 2 | PRAGMA cipher_migrate; 3 | ATTACH DATABASE 'plaintext.db' AS plaintext KEY ''; 4 | SELECT sqlcipher_export('plaintext'); 5 | DETACH DATABASE plaintext; -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/custom.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Custom issue template 3 | about: Describe this issue template's purpose here. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/ImageBox/run.py: -------------------------------------------------------------------------------- 1 | from ui import MainDemo 2 | from config import * 3 | 4 | 5 | if __name__ == '__main__': 6 | app = QApplication(sys.argv) 7 | box = MainDemo() 8 | box.show() 9 | app.exec_() 10 | -------------------------------------------------------------------------------- /app/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | @File : __init__.py.py 4 | @Author : Shuaikang Zhou 5 | @Time : 2023/1/5 17:43 6 | @IDE : Pycharm 7 | @Version : Python3.10 8 | @comment : ··· 9 | """ 10 | -------------------------------------------------------------------------------- /app/DataBase/config.txt: -------------------------------------------------------------------------------- 1 | 2 | PRAGMA key = '10f35f1'; 3 | PRAGMA cipher_migrate; 4 | ATTACH DATABASE './app/DataBase/Msg.db' AS Msg KEY ''; 5 | SELECT sqlcipher_export('Msg'); 6 | DETACH DATABASE Msg; 7 | -------------------------------------------------------------------------------- /app/main/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | @File : __init__.py.py 4 | @Author : Shuaikang Zhou 5 | @Time : 2023/1/5 18:11 6 | @IDE : Pycharm 7 | @Version : Python3.10 8 | @comment : ··· 9 | """ 10 | -------------------------------------------------------------------------------- /app/Ui/chat/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | @File : __init__.py.py 4 | @Author : Shuaikang Zhou 5 | @Time : 2022/12/13 20:33 6 | @IDE : Pycharm 7 | @Version : Python3.10 8 | @comment : ··· 9 | """ 10 | -------------------------------------------------------------------------------- /app/data/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | @File : __init__.py.py 4 | @Author : Shuaikang Zhou 5 | @Time : 2022/12/13 14:19 6 | @IDE : Pycharm 7 | @Version : Python3.10 8 | @comment : ··· 9 | """ 10 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /app/Ui/contact/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | @File : __init__.py.py 4 | @Author : Shuaikang Zhou 5 | @Time : 2022/12/13 20:33 6 | @IDE : Pycharm 7 | @Version : Python3.10 8 | @comment : ··· 9 | """ 10 | -------------------------------------------------------------------------------- /app/Ui/contact/userinfo/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | @File : __init__.py.py 4 | @Author : Shuaikang Zhou 5 | @Time : 2022/12/24 10:34 6 | @IDE : Pycharm 7 | @Version : Python3.10 8 | @comment : ··· 9 | """ 10 | -------------------------------------------------------------------------------- /app/ui_pc/contact/userinfo/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | @File : __init__.py.py 4 | @Author : Shuaikang Zhou 5 | @Time : 2022/12/24 10:34 6 | @IDE : Pycharm 7 | @Version : Python3.10 8 | @comment : ··· 9 | """ 10 | -------------------------------------------------------------------------------- /app/config.py: -------------------------------------------------------------------------------- 1 | version = '0.2.5' 2 | contact = '474379264' 3 | description = [ 4 | '1. 支持获取个人信息
', 5 | '2. 支持显示聊天界面
', 6 | '3. 支持导出聊天记录
    * csv
    * html
', 7 | '4. 查找联系人
', 8 | ] 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | .idea 3 | build 4 | data 5 | sqlcipher-3.0.1 6 | dist 7 | venv 8 | venv_decrypt 9 | venv_main_pc 10 | TEST 11 | app/data/avatar 12 | app/data/image2 13 | app/data/emoji 14 | app/DataBase/Msg/* 15 | *.db 16 | *.pyc 17 | *.log 18 | *.spec 19 | test* -------------------------------------------------------------------------------- /resource/render/templates/components.html: -------------------------------------------------------------------------------- 1 | {% import 'macro' as macro %} 2 | 3 | 4 | 5 | 6 | {{ chart.page_title }} 7 | 8 | 9 | 10 | {{ macro.gen_components_content(chart) }} 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/DataBase/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | @File : __init__.py.py 4 | @Author : Shuaikang Zhou 5 | @Time : 2023/1/5 0:10 6 | @IDE : Pycharm 7 | @Version : Python3.10 8 | @comment : ··· 9 | """ 10 | # from . import data 11 | # from . import output 12 | 13 | __all__ = ["data", 'output'] 14 | -------------------------------------------------------------------------------- /app/ImageBox/config.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from PyQt5.QtWidgets import QWidget, QApplication, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QFileDialog 3 | from PyQt5.Qt import QPixmap, QPoint, Qt, QPainter, QIcon 4 | from PyQt5.QtCore import QSize 5 | from PyQt5 import QtCore, QtGui, QtWidgets 6 | from PyQt5.QtGui import QImageReader 7 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | -------------------------------------------------------------------------------- /resource/render/templates/nb_nteract.html: -------------------------------------------------------------------------------- 1 | {% import 'macro' as macro %} 2 | 3 | 4 | 5 | 6 | {{ macro.render_chart_dependencies(chart) }} 7 | 8 | 9 | {% for c in chart %} 10 | {{ macro.render_chart_content(c) }} 11 | {% endfor %} 12 | 13 | 14 | -------------------------------------------------------------------------------- /.idea/WeChatMsg.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 10 | -------------------------------------------------------------------------------- /resource/render/templates/simple_chart.html: -------------------------------------------------------------------------------- 1 | {% import 'macro' as macro %} 2 | 3 | 4 | 5 | 6 | {{ chart.page_title }} 7 | {{ macro.render_chart_dependencies(chart) }} 8 | 9 | 10 | {{ macro.render_chart_content(chart) }} 11 | 12 | 13 | -------------------------------------------------------------------------------- /resource/render/templates/nb_jupyter_lab.html: -------------------------------------------------------------------------------- 1 | {% import 'macro' as macro %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | {% for chart in charts %} 9 | {% if chart._component_type in ("table", "image") %} 10 | {{ macro.gen_components_content(chart) }} 11 | {% else %} 12 | {{ macro.render_chart_content(chart) }} 13 | {% endif %} 14 | {% endfor %} 15 | 16 | 17 | -------------------------------------------------------------------------------- /app/util/search.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from fuzzywuzzy import process 4 | 5 | 6 | def search_by_content(key, choices: List[List]): 7 | result = [] 8 | for i, choice in enumerate(choices): 9 | res = process.extractOne(key, choice) 10 | result.append((res, i)) 11 | result.sort(key=lambda x: x[0][1], reverse=True) 12 | k = result[0][1] 13 | item = result[0][0][0] 14 | return choices[k].index(item) 15 | -------------------------------------------------------------------------------- /app/Ui/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | @File : __init__.py.py 4 | @Author : Shuaikang Zhou 5 | @Time : 2022/12/13 14:19 6 | @IDE : Pycharm 7 | @Version : Python3.10 8 | @comment : ··· 9 | """ 10 | # from .ICON import Icon 11 | # from .chat import chat 12 | from app.Ui import mainview 13 | # 文件__init__.py 14 | # from login import login 15 | from app.Ui.decrypt import decrypt 16 | 17 | __all__ = ["decrypt", 'mainview', 'chat'] 18 | -------------------------------------------------------------------------------- /doc/获取个人文件/使用说明.md: -------------------------------------------------------------------------------- 1 | 教程参考 2 | [导出聊天记录](https://blog.csdn.net/m0_59452630/article/details/124222235?spm=1001.2014.3001.5501 "一文教会你导出微信聊天记录") 3 | 4 | 对于模拟器中信息提取,如果使用自带的Amaze复制到共享文件夹出现错误,无法导入可以使用MT管理器 5 | 6 | ![image-20231113143254986](..\images\MT) 7 | 8 | MT管理器打开界面如下图所示,右边打开微信文件存储位置,找到MicroMsg,左边打开共享文件位置,可以通过右边的电脑图案查看路径。 9 | 10 | 如图,长按MicroMsg文件夹,弹出弹窗,点击复制->确认。 11 | 12 | ![image-20231111001821854](..\images\cv-opration) 13 | 14 | 开始复制,稍等一会即可完成复制。 15 | 16 | ![image-20231111001132681](..\images\cv_process) 17 | -------------------------------------------------------------------------------- /resource/render/templates/nb_jupyter_lab_tab.html: -------------------------------------------------------------------------------- 1 | {% import 'macro' as macro %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | {{ macro.generate_tab_css() }} 9 | {{ macro.display_tablinks(charts) }} 10 | 11 | {% for chart in charts %} 12 | {% if chart._component_type in ("table", "image") %} 13 | {{ macro.gen_components_content(chart) }} 14 | {% else %} 15 | {{ macro.render_chart_content(chart) }} 16 | {% endif %} 17 | {% endfor %} 18 | {{ macro.switch_tabs() }} 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/Ui/contact/emotion/emotionUi.ui: -------------------------------------------------------------------------------- 1 | 2 | Dialog 3 | 4 | 5 | 6 | 0 7 | 0 8 | 400 9 | 300 10 | 11 | 12 | 13 | Dialog 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /resource/render/templates/nb_jupyter_notebook.html: -------------------------------------------------------------------------------- 1 | {% import 'macro' as macro %} 2 | 3 | 12 | 13 | {% for chart in charts %} 14 | {% if chart._component_type in ("table", "image") %} 15 | {{ macro.gen_components_content(chart) }} 16 | {% else %} 17 |
18 | {% endif %} 19 | {% endfor %} 20 | 21 | {{ macro.render_notebook_charts(charts, libraries) }} 22 | -------------------------------------------------------------------------------- /app/ui_pc/contact/userinfo/userinfo.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtWidgets import * 2 | 3 | from .userinfoUi import Ui_Frame 4 | 5 | 6 | class UserinfoController(QWidget, Ui_Frame): 7 | def __init__(self, contact, parent=None): 8 | super().__init__(parent) 9 | self.setupUi(self) 10 | self.l_remark.setText(contact.remark) 11 | self.l_avatar.setPixmap(contact.avatar) 12 | self.l_nickname.setText(f'昵称:{contact.nickName}') 13 | self.l_username.setText(f'微信号:{contact.alias}') 14 | self.lineEdit.setText(contact.remark) 15 | self.progressBar.setVisible(False) 16 | -------------------------------------------------------------------------------- /app/Ui/contact/userinfo/userinfo.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtWidgets import * 2 | 3 | from .userinfoUi import Ui_Frame 4 | 5 | 6 | class UserinfoController(QWidget, Ui_Frame): 7 | def __init__(self, contact, parent=None): 8 | super().__init__(parent) 9 | self.setupUi(self) 10 | self.l_remark.setText(contact.conRemark) 11 | self.l_avatar.setPixmap(contact.avatar) 12 | self.l_nickname.setText(f'昵称:{contact.nickname}') 13 | self.l_username.setText(f'微信号:{contact.alias}') 14 | self.lineEdit.setText(contact.conRemark) 15 | self.progressBar.setVisible(False) 16 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /hook-pyecharts.py: -------------------------------------------------------------------------------- 1 | #----------------------------------------------------------------------------- 2 | # Copyright (c) 2017-2020, PyInstaller Development Team. 3 | # 4 | # Distributed under the terms of the GNU General Public License (version 2 5 | # or later) with exception for distributing the bootloader. 6 | # 7 | # The full license is in the file COPYING.txt, distributed with this software. 8 | # 9 | # SPDX-License-Identifier: (GPL-2.0-or-later WITH Bootloader-exception) 10 | #----------------------------------------------------------------------------- 11 | # Hook for nanite: https://pypi.python.org/pypi/nanite 12 | from PyInstaller.utils.hooks import collect_data_files 13 | datas = collect_data_files('pyecharts') -------------------------------------------------------------------------------- /app/resources/icons/output.svg: -------------------------------------------------------------------------------- 1 | 3 | 5 | 7 | 9 | -------------------------------------------------------------------------------- /resource/render/templates/nb_jupyter_notebook_tab.html: -------------------------------------------------------------------------------- 1 | {% import 'macro' as macro %} 2 | 3 | 12 | 13 | {{ macro.generate_tab_css() }} 14 | {{ macro.display_tablinks(charts) }} 15 | 16 | {% for chart in charts %} 17 | {% if chart._component_type in ("table", "image") %} 18 | {{ macro.gen_components_content(chart) }} 19 | {% else %} 20 |
22 | {% endif %} 23 | {% endfor %} 24 | 25 | {{ macro.render_notebook_charts(charts, libraries) }} 26 | {{ macro.switch_tabs() }} 27 | -------------------------------------------------------------------------------- /app/Ui/contact/emotion/emotionUi.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Form implementation generated from reading ui file 'emotionUi.ui' 4 | # 5 | # Created by: PyQt5 UI code generator 5.15.7 6 | # 7 | # WARNING: Any manual changes made to this file will be lost when pyuic5 is 8 | # run again. Do not edit this file unless you know what you are doing. 9 | 10 | 11 | from PyQt5 import QtCore 12 | 13 | 14 | class Ui_Dialog(object): 15 | def setupUi(self, Dialog): 16 | Dialog.setObjectName("Dialog") 17 | Dialog.resize(400, 300) 18 | 19 | self.retranslateUi(Dialog) 20 | QtCore.QMetaObject.connectSlotsByName(Dialog) 21 | 22 | def retranslateUi(self, Dialog): 23 | _translate = QtCore.QCoreApplication.translate 24 | Dialog.setWindowTitle(_translate("Dialog", "Dialog")) 25 | -------------------------------------------------------------------------------- /app/resources/icons/emotion.svg: -------------------------------------------------------------------------------- 1 | 3 | 6 | 8 | 10 | 11 | -------------------------------------------------------------------------------- /app/Ui/contact/test.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MainWindow 6 | 7 | 8 | 9 | 0 10 | 0 11 | 800 12 | 600 13 | 14 | 15 | 16 | MainWindow 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/Ui/Icon.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtGui import QIcon 2 | 3 | 4 | class Icon: 5 | Default_avatar_path = './app/data/icons/default_avatar.svg' 6 | MainWindow_Icon = QIcon('./app/data/icons/logo.svg') 7 | Default_avatar = QIcon(Default_avatar_path) 8 | Output = QIcon('./app/data/icons/output.svg') 9 | Back = QIcon('./app/data/icons/back.svg') 10 | ToDocx = QIcon('app/data/icons/word.svg') 11 | ToCSV = QIcon('app/data/icons/csv.svg') 12 | ToHTML = QIcon('app/data/icons/html.svg') 13 | Chat_Icon = QIcon('./app/data/icons/chat.svg') 14 | Contact_Icon = QIcon('./app/data/icons/contact.svg') 15 | MyInfo_Icon = QIcon('./app/data/icons/myinfo.svg') 16 | Annual_Report_Icon = QIcon('./app/data/icons/annual_report.svg') 17 | Analysis_Icon = QIcon('./app/data/icons/analysis.svg') 18 | Emotion_Icon = QIcon('./app/data/icons/emotion.svg') 19 | -------------------------------------------------------------------------------- /auth_info_key_prefs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 2503180200000000220b96fd91b700 8 | 0a240820122089653ee1b7b77b1a53891bdeb13e3ebebd8c3f973cc8139973199b7549b4286212b80108b20112b20108994e12a601e6a7999e231ed0b9871fa0f9cb11835fb140ab40f308aaf1c81ca2e18619725c6875c908c1cfd43a890f91b772aaf8678e4daebc8f2979f6f3c003b9ded9da0b3e58ad33df24389b572aebe468059dfbb40a28fc5cb0af4c5bcc8b072759e7409cf2069595c90cdc1752163b43c2d576751d26e018a5895e2a9716d99c7bbe590af31e91e41b69bf67c1ad1f3c0d6c68fc770d90362913ecf7101d3de12e3aba0095cf98dafc18afadf79f0a 9 | 10 | -------------------------------------------------------------------------------- /app/resources/icons/analysis.svg: -------------------------------------------------------------------------------- 1 | 3 | 5 | 7 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /resource/render/templates/simple_tab.html: -------------------------------------------------------------------------------- 1 | {% import 'macro' as macro %} 2 | 3 | 4 | 5 | 6 | {{ chart.page_title }} 7 | {{ macro.render_chart_dependencies(chart) }} 8 | {{ macro.render_chart_css(chart) }} 9 | 10 | 11 | {% if chart.use_custom_tab_css is not true %} 12 | {{ macro.generate_tab_css() }} 13 | {% else %} 14 | 15 | {% endif %} 16 | {{ macro.display_tablinks(chart) }} 17 | 18 |
19 | {% for c in chart %} 20 | {% if c._component_type in ("table", "image") %} 21 | {{ macro.gen_components_content(c) }} 22 | {% else %} 23 | {{ macro.render_chart_content(c) }} 24 | {% endif %} 25 | {% endfor %} 26 |
27 | 28 | 34 | {{ macro.switch_tabs() }} 35 | 36 | 37 | -------------------------------------------------------------------------------- /app/resources/resource.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | icons/404.png 4 | icons/analysis.svg 5 | icons/annual_report.svg 6 | icons/back.svg 7 | icons/chat.svg 8 | icons/contact.svg 9 | icons/csv.svg 10 | icons/default_avatar.svg 11 | icons/emotion.svg 12 | icons/html.svg 13 | icons/loading.svg 14 | icons/logo.svg 15 | icons/myinfo.svg 16 | icons/output.svg 17 | icons/search.svg 18 | icons/word.svg 19 | version_list.json 20 | icons/logo.ico 21 | icons/logo.png 22 | icons/tool.svg 23 | icons/home.svg 24 | icons/help.svg 25 | 26 | 27 | version_list.json 28 | 29 | 30 | -------------------------------------------------------------------------------- /resource/render/templates/simple_page.html: -------------------------------------------------------------------------------- 1 | {% import 'macro' as macro %} 2 | 3 | 4 | 5 | 6 | {{ chart.page_title }} 7 | {{ macro.render_chart_dependencies(chart) }} 8 | {{ macro.render_chart_css(chart) }} 9 | 10 | 11 | 12 | {% if chart.download_button %} 13 | 14 | {% endif %} 15 |
16 | {% for c in chart %} 17 | {% if c._component_type in ("table", "image") %} 18 | {{ macro.gen_components_content(c) }} 19 | {% else %} 20 | {{ macro.render_chart_content(c) }} 21 | {% endif %} 22 | {% for _ in range(chart.page_interval) %} 23 | {% if chart.remove_br is false %}
{% endif %} 24 | {% endfor %} 25 | {% endfor %} 26 |
27 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /doc/电脑端使用教程.md: -------------------------------------------------------------------------------- 1 | # 一、解密微信数据库 2 | 3 | ## 主要功能 4 | 5 | 1. 解密微信数据库 6 | 2. 查看聊天记录 7 | 3. 导出聊天记录 8 | * CSV 9 | * docx(待实现) 10 | * HTML(待实现) 11 | 12 | ## 安装 13 | 14 | ```shell 15 | git clone https://github.com/LC044/WeChatMsg 16 | cd WeChatMsg 17 | pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple 18 | ``` 19 | 20 | ## 解密 21 | 22 |
23 | 24 | 解密步骤: 25 | 26 | 1. 登录微信 27 | 28 | 2. 运行程序 29 | 30 | ```shell 31 | python decrypt_window.py 32 | ``` 33 | 34 | 3. 点击获取信息 35 | 36 | ![](./images/pc_decrypt_info.png) 37 | 38 | 4. 设置微信安装路径 39 | 可以到微信->设置->文件管理查看 40 | 41 | ![](./images/setting.png) 42 | 43 | 点击**设置微信路径**按钮,选择该文件夹路径下的带有wxid_xxx的路径 44 | ![](./images/path_select.png) 45 | 46 | 5. 获取到密钥和微信路径之后点击开始解密 47 | 48 | 6. 解密后的数据库文件保存在./app/DataBase/Msg路径下 49 | 50 |
51 | 52 | ## 查看聊天记录 53 | 54 |
55 | 56 | 1. 运行程序 57 | 58 | ```shell 59 | python main_pc.py 60 | ``` 61 | 62 | 2. 选择联系人 63 | 64 | 运行图片 65 | 66 | 3. 导出聊天记录 67 | 68 | 聊天记录保存在 **/data/聊天记录/** 文件夹下 69 | 70 | 71 | 72 |
-------------------------------------------------------------------------------- /app/Ui/contact/report/annual_report.py: -------------------------------------------------------------------------------- 1 | from bs4 import BeautifulSoup 2 | 3 | 4 | def create_title_page(nickname, time, avatar_path): 5 | with open('D:\\Project\\Python\\WeChatMsg\\app\\data\\html\\0.html', 'r+', encoding='utf-8') as f: 6 | html_document = f.read() 7 | # 创建Beautiful Soup对象 8 | soup = BeautifulSoup(html_document, 'html.parser') 9 | # 找到需要替换的图片元素 10 | target_image = soup.find(id='avatar') 11 | # 替换图片元素的src属性 12 | if target_image: 13 | target_image['src'] = avatar_path 14 | # 找到需要替换的元素 15 | target_element = soup.find(id='nickname') 16 | # 替换元素的文本内容 17 | if target_element: 18 | target_element.string = nickname 19 | target_element = soup.find(id='first_time') 20 | # 替换元素的文本内容 21 | if target_element: 22 | target_element.string = time 23 | with open('./data/AnnualReport/0.html', 'w', encoding='utf-8') as f1: 24 | f1.write(soup.prettify()) 25 | 26 | 27 | if __name__ == '__main__': 28 | create_title_page('小学生', '2023-09-18 20:39:08', 'D:\Project\Python\WeChatMsg\\app\data\icons\default_avatar.svg') 29 | -------------------------------------------------------------------------------- /app/resources/icons/chat.svg: -------------------------------------------------------------------------------- 1 | 3 | 5 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/resources/icons/myinfo.svg: -------------------------------------------------------------------------------- 1 | 3 | 5 | 7 | -------------------------------------------------------------------------------- /app/ui_pc/Icon.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtGui import QIcon 2 | 3 | from app.resources import resource_rc 4 | 5 | var = resource_rc.qt_resource_name 6 | 7 | 8 | class Icon: 9 | Default_avatar_path = ':/icons/icons/default_avatar.svg' 10 | Default_image_path = ':/icons/icons/404.png' 11 | MainWindow_Icon = QIcon(':/icons/icons/logo.svg') 12 | Default_avatar = QIcon(Default_avatar_path) 13 | Output = QIcon(':/icons/icons/output.svg') 14 | Back = QIcon(':/icons/icons/back.svg') 15 | ToDocx = QIcon(':/icons/icons/word.svg') 16 | ToCSV = QIcon(':/icons/icons/csv.svg') 17 | ToHTML = QIcon(':/icons/icons/html.svg') 18 | Chat_Icon = QIcon(':/icons/icons/chat.svg') 19 | Contact_Icon = QIcon(':/icons/icons/contact.svg') 20 | MyInfo_Icon = QIcon(':/icons/icons/myinfo.svg') 21 | Annual_Report_Icon = QIcon(':/icons/icons/annual_report.svg') 22 | Analysis_Icon = QIcon(':/icons/icons/analysis.svg') 23 | Emotion_Icon = QIcon(':/icons/icons/emotion.svg') 24 | Search_Icon = QIcon(':/icons/icons/search.svg') 25 | Tool_Icon = QIcon(':/icons/icons/tool.svg') 26 | Home_Icon = QIcon(':/icons/icons/home.svg') 27 | Help_Icon = QIcon(':/icons/icons/help.svg') 28 | -------------------------------------------------------------------------------- /decrypt_window.py: -------------------------------------------------------------------------------- 1 | import ctypes 2 | import sys 3 | 4 | from PyQt5.QtGui import QIcon 5 | from PyQt5.QtWidgets import QApplication, QMessageBox, QWidget 6 | 7 | from app.resources import resource_rc 8 | from app.ui_pc.tool.pc_decrypt import pc_decrypt 9 | 10 | var = resource_rc.qt_resource_name 11 | ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID("WeChatReport") 12 | 13 | 14 | class ViewController(QWidget): 15 | def __init__(self): 16 | super().__init__() 17 | self.setWindowTitle('解密') 18 | self.setWindowIcon(QIcon(':/icons/icons/logo.svg')) 19 | self.viewMainWIn = None 20 | self.viewDecrypt = None 21 | 22 | def loadPCDecryptView(self): 23 | """ 24 | 登录界面 25 | :return: 26 | """ 27 | self.viewDecrypt = pc_decrypt.DecryptControl(self) 28 | self.viewDecrypt.DecryptSignal.connect(self.show_success) 29 | # self.viewDecrypt.show() 30 | 31 | def show_success(self): 32 | QMessageBox.about(self, "解密成功", "数据库文件存储在\napp/DataBase/Msg\n文件夹下") 33 | self.close() 34 | 35 | 36 | if __name__ == '__main__': 37 | app = QApplication(sys.argv) 38 | view = ViewController() 39 | view.loadPCDecryptView() 40 | view.show() 41 | sys.exit(app.exec_()) 42 | -------------------------------------------------------------------------------- /app/log/logger.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | import time 4 | import traceback 5 | from functools import wraps 6 | 7 | filename = time.strftime("%Y-%m-%d", time.localtime(time.time())) 8 | logger = logging.getLogger('test') 9 | logger.setLevel(level=logging.DEBUG) 10 | formatter = logging.Formatter('%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s') 11 | try: 12 | if not os.path.exists('./app/log/logs'): 13 | os.mkdir('./app/log/logs') 14 | file_handler = logging.FileHandler(f'./app/log/logs/{filename}-log.log') 15 | except: 16 | file_handler = logging.FileHandler(f'{filename}-log.log') 17 | 18 | file_handler.setLevel(level=logging.INFO) 19 | file_handler.setFormatter(formatter) 20 | stream_handler = logging.StreamHandler() 21 | stream_handler.setLevel(logging.DEBUG) 22 | stream_handler.setFormatter(formatter) 23 | logger.addHandler(file_handler) 24 | logger.addHandler(stream_handler) 25 | 26 | 27 | def log(func): 28 | @wraps(func) 29 | def log_(*args, **kwargs): 30 | try: 31 | return func(*args, **kwargs) 32 | except Exception as e: 33 | logger.error( 34 | f"\n{func.__qualname__} is error,params:{(args, kwargs)},here are details:\n{traceback.format_exc()}") 35 | 36 | return log_ 37 | -------------------------------------------------------------------------------- /app/Ui/userinfo/userinfo.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | @File : contact.py 4 | @Author : Shuaikang Zhou 5 | @Time : 2022/12/13 15:07 6 | @IDE : Pycharm 7 | @Version : Python3.10 8 | @comment : ··· 9 | """ 10 | from PyQt5.QtCore import * 11 | from PyQt5.QtGui import * 12 | from PyQt5.QtWidgets import * 13 | 14 | from .userinfoUi import * 15 | from ...DataBase import data 16 | 17 | 18 | class MyinfoController(QWidget, Ui_Dialog): 19 | exitSignal = pyqtSignal() 20 | urlSignal = pyqtSignal(QUrl) 21 | 22 | # username = '' 23 | 24 | def __init__(self, Me, parent=None): 25 | super(MyinfoController, self).__init__(parent) 26 | self.setupUi(self) 27 | self.setWindowTitle('WeChat') 28 | self.setWindowIcon(QIcon('./app/data/icon.png')) 29 | self.Me = Me 30 | self.initui() 31 | 32 | def initui(self): 33 | self.myinfo = data.get_myInfo() 34 | avatar = self.Me.my_avatar 35 | pixmap = QPixmap(avatar).scaled(80, 80) # 按指定路径找到图片 36 | self.label_avatar.setPixmap(pixmap) # 在label上显示图片 37 | self.label_name.setText(self.myinfo['name']) 38 | self.label_wxid.setText('微信号:' + self.myinfo['username']) 39 | city = f"地区:{self.myinfo['province']}{self.myinfo['city']}" 40 | self.label_city.setText(city) 41 | -------------------------------------------------------------------------------- /app/web_ui/web.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, render_template 2 | from pyecharts import options as opts 3 | from pyecharts.charts import Bar 4 | from pyecharts.globals import ThemeType 5 | 6 | app = Flask(__name__) 7 | 8 | 9 | @app.route("/") 10 | def index(): 11 | # 创建一个简单的柱状图 12 | bar = ( 13 | Bar(init_opts=opts.InitOpts(theme=ThemeType.LIGHT)) 14 | .add_xaxis(["A", "B", "C", "D", "E"]) 15 | .add_yaxis("Series", [5, 20, 36, 10, 75]) 16 | .set_global_opts(title_opts=opts.TitleOpts(title="Flask and Pyecharts Interaction")) 17 | ) 18 | 19 | # 将图表转换成 HTML 20 | chart_html = bar.render_embed() 21 | 22 | # 渲染模板,并传递图表的 HTML 到模板中 23 | return render_template("index.html", chart_html=chart_html) 24 | 25 | 26 | @app.route("/index") 27 | def index0(): 28 | return render_template("index.html") 29 | 30 | 31 | @app.route('/home') 32 | def home(): 33 | data = { 34 | 'sub_title': '二零二三年度报告', 35 | 'avatar_path': "static/my_resource/avatar.png", 36 | 'nickname': '司小远', 37 | 'first_time': '2023-09-18 20:39:08', 38 | } 39 | return render_template('home.html', **data) 40 | 41 | 42 | @app.route('/message_num') 43 | def one(): 44 | return "1hello world" 45 | 46 | 47 | if __name__ == "__main__": 48 | app.run(debug=True, host='0.0.0.0') 49 | -------------------------------------------------------------------------------- /app/resources/icons/back.svg: -------------------------------------------------------------------------------- 1 | 3 | 6 | 8 | -------------------------------------------------------------------------------- /app/resources/icons/search.svg: -------------------------------------------------------------------------------- 1 | 3 | 5 | 7 | -------------------------------------------------------------------------------- /app/DataBase/misc.py: -------------------------------------------------------------------------------- 1 | import os.path 2 | import sqlite3 3 | import threading 4 | import time 5 | 6 | lock = threading.Lock() 7 | DB = None 8 | cursor = None 9 | db_path = "./app/Database/Msg/Misc.db" 10 | # misc_path = './Msg/Misc.db' 11 | if os.path.exists(db_path): 12 | DB = sqlite3.connect(db_path, check_same_thread=False) 13 | # '''创建游标''' 14 | cursor = DB.cursor() 15 | 16 | 17 | def init_database(): 18 | global DB 19 | global cursor 20 | if not DB: 21 | if os.path.exists(db_path): 22 | DB = sqlite3.connect(db_path, check_same_thread=False) 23 | # '''创建游标''' 24 | cursor = DB.cursor() 25 | 26 | 27 | def get_avatar_buffer(userName): 28 | sql = ''' 29 | select smallHeadBuf 30 | from ContactHeadImg1 31 | where usrName=?; 32 | ''' 33 | try: 34 | lock.acquire(True) 35 | try: 36 | cursor.execute(sql, [userName]) 37 | except: 38 | time.sleep(0.5) 39 | init_database() 40 | finally: 41 | cursor.execute(sql, [userName]) 42 | result = cursor.fetchall() 43 | # print(result[0][0]) 44 | if result: 45 | return result[0][0] 46 | finally: 47 | lock.release() 48 | return None 49 | 50 | 51 | def close(): 52 | global DB 53 | if DB: 54 | DB.close() 55 | 56 | 57 | if __name__ == '__main__': 58 | get_avatar_buffer('wxid_al2oan01b6fn11') 59 | -------------------------------------------------------------------------------- /app/util/path.py: -------------------------------------------------------------------------------- 1 | import os 2 | import winreg 3 | 4 | from app.person_pc import MePC 5 | from app.util import dat2pic 6 | 7 | if not os.path.exists('./data/'): 8 | os.mkdir('./data/') 9 | if not os.path.exists('./data/image'): 10 | os.mkdir('./data/image') 11 | 12 | 13 | def get_abs_path(path): 14 | # return os.path.join(os.getcwd(), 'app/data/icons/404.png') 15 | if path: 16 | base_path = os.getcwd() + "/data/image" 17 | output_path = dat2pic.decode_dat(os.path.join(MePC().wx_dir, path), base_path) # './data/image') 18 | return output_path if output_path else ':/icons/icons/404.png' 19 | else: 20 | return ':/icons/icons/404.png' 21 | 22 | 23 | def wx_path(): 24 | ## 获取当前用户名 25 | users = os.path.expandvars('$HOMEPATH') 26 | ## 找到3ebffe94.ini配置文件 27 | f = open(r'C:' + users + '\\AppData\\Roaming\\Tencent\\WeChat\\All Users\\config\\3ebffe94.ini') 28 | txt = f.read() 29 | f.close() 30 | # 打开Windows注册表 31 | reg_key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, 32 | "Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders") 33 | # 获取“我的文档”路径的注册表键值 34 | documents_path_value = winreg.QueryValueEx(reg_key, "Personal") 35 | # 输出路径 36 | ##读取文件将路径放到wx_location变量里 37 | if txt == 'MyDocument:': 38 | wx_location = documents_path_value[0] + '\WeChat Files' 39 | else: 40 | wx_location = txt + "\WeChat Files" 41 | return wx_location 42 | -------------------------------------------------------------------------------- /app/DataBase/micro_msg.py: -------------------------------------------------------------------------------- 1 | import os.path 2 | import sqlite3 3 | import threading 4 | 5 | lock = threading.Lock() 6 | DB = None 7 | cursor = None 8 | micromsg_path = "./app/Database/Msg/MicroMsg.db" 9 | if os.path.exists(micromsg_path): 10 | DB = sqlite3.connect(micromsg_path, check_same_thread=False) 11 | # '''创建游标''' 12 | cursor = DB.cursor() 13 | 14 | 15 | def init_database(): 16 | global DB 17 | global cursor 18 | if not DB: 19 | if os.path.exists(micromsg_path): 20 | DB = sqlite3.connect(micromsg_path, check_same_thread=False) 21 | # '''创建游标''' 22 | cursor = DB.cursor() 23 | 24 | 25 | def is_database_exist(): 26 | return os.path.exists(micromsg_path) 27 | 28 | 29 | def get_contact(): 30 | try: 31 | lock.acquire(True) 32 | sql = '''select UserName,Alias,Type,Remark,NickName,PYInitial,RemarkPYInitial,ContactHeadImgUrl.smallHeadImgUrl,ContactHeadImgUrl.bigHeadImgUrl 33 | from Contact inner join ContactHeadImgUrl on Contact.UserName = ContactHeadImgUrl.usrName 34 | where Type%2=1 and Alias is not null 35 | order by PYInitial 36 | ''' 37 | cursor.execute(sql) 38 | result = cursor.fetchall() 39 | finally: 40 | lock.release() 41 | # DB.commit() 42 | return result 43 | 44 | 45 | def close(): 46 | global DB 47 | if DB: 48 | DB.close() 49 | 50 | 51 | if __name__ == '__main__': 52 | get_contact() 53 | -------------------------------------------------------------------------------- /app/components/prompt_bar.py: -------------------------------------------------------------------------------- 1 | from PyQt5 import QtGui 2 | from PyQt5.QtCore import * 3 | from PyQt5.QtGui import * 4 | from PyQt5.QtWidgets import * 5 | 6 | 7 | class PromptBar(QLabel): 8 | def __init__(self, parent=None): 9 | super().__init__(parent) 10 | 11 | def paintEvent(self, e): # 绘图事件 12 | qp = QPainter() 13 | qp.begin(self) 14 | self.drawRectangles1(qp) # 绘制线条矩形 15 | self.drawRectangles2(qp) # 绘制填充矩形 16 | self.drawRectangles3(qp) # 绘制线条+填充矩形 17 | self.drawRectangles4(qp) # 绘制线条矩形2 18 | qp.end() 19 | 20 | def drawRectangles1(self, qp): # 绘制填充矩形 21 | qp.setPen(QPen(Qt.black, 2, Qt.SolidLine)) # 颜色、线宽、线性 22 | qp.drawRect(*self.data) 23 | 24 | def drawRectangles2(self, qp): # 绘制填充矩形 25 | qp.setPen(QPen(Qt.black, 2, Qt.NoPen)) 26 | qp.setBrush(QColor(200, 0, 0)) 27 | qp.drawRect(220, 15, 200, 100) 28 | 29 | def drawRectangles3(self, qp): # 绘制线条+填充矩形 30 | qp.setPen(QPen(Qt.black, 2, Qt.SolidLine)) 31 | qp.setBrush(QColor(200, 0, 0)) 32 | qp.drawRect(430, 15, 200, 100) 33 | 34 | def drawRectangles4(self, qp): # 绘制线条矩形2 35 | path = QtGui.QPainterPath() 36 | qp.setPen(QPen(Qt.blue, 2, Qt.SolidLine)) 37 | qp.setBrush(QColor(0, 0, 0, 0)) # 设置画刷颜色透明 38 | path.addRect(100, 200, 200, 100) 39 | qp.drawPath(path) 40 | -------------------------------------------------------------------------------- /resource/render/templates/simple_globe.html: -------------------------------------------------------------------------------- 1 | {% import 'macro' as macro %} 2 | 3 | 4 | 5 | 6 | {{ chart.page_title }} 7 | {{ macro.render_chart_dependencies(chart) }} 8 | 9 | 10 |
11 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /resource/render/templates/nb_jupyter_globe.html: -------------------------------------------------------------------------------- 1 | 10 | 11 | {% for chart in charts %} 12 |
13 | {% endfor %} 14 | 15 | 16 | 50 | -------------------------------------------------------------------------------- /resource/render/display.py: -------------------------------------------------------------------------------- 1 | from ..types import Optional, Sequence, Union 2 | 3 | 4 | class HTML: 5 | def __init__(self, data: Optional[str] = None): 6 | self.data = data 7 | 8 | def _repr_html_(self): 9 | return self.data 10 | 11 | def __html__(self): 12 | return self._repr_html_() 13 | 14 | 15 | _lib_t1 = """new Promise(function(resolve, reject) { 16 | var script = document.createElement("script"); 17 | script.onload = resolve; 18 | script.onerror = reject; 19 | script.src = "%s"; 20 | document.head.appendChild(script); 21 | }).then(() => { 22 | """ 23 | 24 | _lib_t2 = """ 25 | });""" 26 | 27 | _css_t = """var link = document.createElement("link"); 28 | link.ref = "stylesheet"; 29 | link.type = "text/css"; 30 | link.href = "%s"; 31 | document.head.appendChild(link); 32 | """ 33 | 34 | 35 | class Javascript: 36 | def __init__( 37 | self, 38 | data: Optional[str] = None, 39 | lib: Optional[Union[str, Sequence]] = None, 40 | css: Optional[Union[str, Sequence]] = None, 41 | ): 42 | if isinstance(lib, str): 43 | lib = [lib] 44 | elif lib is None: 45 | lib = [] 46 | if isinstance(css, str): 47 | css = [css] 48 | elif css is None: 49 | css = [] 50 | self.lib = lib 51 | self.css = css 52 | self.data = data or "" 53 | 54 | def _repr_javascript_(self): 55 | r = "" 56 | for c in self.css: 57 | r += _css_t % c 58 | for d in self.lib: 59 | r += _lib_t1 % d 60 | r += self.data 61 | r += _lib_t2 * len(self.lib) 62 | return r 63 | -------------------------------------------------------------------------------- /main_pc.py: -------------------------------------------------------------------------------- 1 | import ctypes 2 | import sys 3 | import time 4 | 5 | from PyQt5.QtGui import QIcon 6 | from PyQt5.QtWidgets import * 7 | 8 | from app.ui_pc import mainview 9 | from app.ui_pc.tool.pc_decrypt import pc_decrypt 10 | 11 | ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID("WeChatReport") 12 | 13 | 14 | class ViewController(QWidget): 15 | def __init__(self): 16 | super().__init__() 17 | self.setWindowTitle('解密') 18 | self.setWindowIcon(QIcon(':/icons/icons/logo.png')) 19 | self.viewMainWIndow = None 20 | self.viewDecrypt = None 21 | 22 | def loadPCDecryptView(self): 23 | """ 24 | 登录界面 25 | :return: 26 | """ 27 | self.viewDecrypt = pc_decrypt.DecryptControl() 28 | self.viewDecrypt.DecryptSignal.connect(self.show_success) 29 | self.viewDecrypt.show() 30 | 31 | def loadMainWinView(self, username=None): 32 | """ 33 | 聊天界面 34 | :param username: 账号 35 | :return: 36 | """ 37 | username = '' 38 | start = time.time() 39 | self.viewMainWIndow = mainview.MainWinController(username=username) 40 | self.viewMainWIndow.setWindowTitle("Chat") 41 | self.viewMainWIndow.show() 42 | end = time.time() 43 | print('ok', end - start) 44 | self.viewMainWIndow.init_ui() 45 | 46 | def show_success(self): 47 | QMessageBox.about(self, "解密成功", "数据库文件存储在\napp/DataBase/Msg\n文件夹下") 48 | 49 | 50 | if __name__ == '__main__': 51 | app = QApplication(sys.argv) 52 | view = ViewController() 53 | # view.loadPCDecryptView() 54 | view.loadMainWinView() 55 | # view.show() 56 | # view.show_success() 57 | sys.exit(app.exec_()) 58 | -------------------------------------------------------------------------------- /app/resources/icons/word.svg: -------------------------------------------------------------------------------- 1 | 3 | 5 | 7 | 9 | 10 | 12 | 14 | 16 | 18 | -------------------------------------------------------------------------------- /app/DataBase/merge.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sqlite3 3 | 4 | 5 | def merge_databases(source_paths, target_path): 6 | # 创建目标数据库连接 7 | target_conn = sqlite3.connect(target_path) 8 | target_cursor = target_conn.cursor() 9 | try: 10 | # 开始事务 11 | target_conn.execute("BEGIN;") 12 | for i, source_path in enumerate(source_paths): 13 | if not os.path.exists(source_path): 14 | break 15 | db = sqlite3.connect(source_path) 16 | cursor = db.cursor() 17 | sql = ''' 18 | SELECT TalkerId,MsgsvrID,Type,SubType,IsSender,CreateTime,Sequence,StrTalker,StrContent,DisplayContent,BytesExtra 19 | FROM MSG; 20 | ''' 21 | cursor.execute(sql) 22 | result = cursor.fetchall() 23 | # 附加源数据库 24 | target_cursor.executemany( 25 | "INSERT INTO MSG " 26 | "(TalkerId,MsgsvrID,Type,SubType,IsSender,CreateTime,Sequence,StrTalker,StrContent,DisplayContent," 27 | "BytesExtra)" 28 | "VALUES(?,?,?,?,?,?,?,?,?,?,?)", 29 | result) 30 | cursor.close() 31 | db.close() 32 | # 提交事务 33 | target_conn.execute("COMMIT;") 34 | 35 | except Exception as e: 36 | # 发生异常时回滚事务 37 | target_conn.execute("ROLLBACK;") 38 | raise e 39 | 40 | finally: 41 | # 关闭目标数据库连接 42 | target_conn.close() 43 | 44 | 45 | if __name__ == "__main__": 46 | # 源数据库文件列表 47 | source_databases = ["Msg/MSG1.db", "Msg/MSG2.db", "Msg/MSG3.db"] 48 | 49 | # 目标数据库文件 50 | target_database = "Msg/MSG.db" 51 | import shutil 52 | 53 | shutil.copy('Msg/MSG0.db', target_database) # 使用一个数据库文件作为模板 54 | # 合并数据库 55 | merge_databases(source_databases, target_database) 56 | -------------------------------------------------------------------------------- /app/components/contact_info_ui.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from PyQt5.Qt import * 4 | from PyQt5.QtCore import * 5 | from PyQt5.QtWidgets import * 6 | 7 | from .CAvatar import CAvatar 8 | 9 | 10 | # 自定义的item 继承自QListWidgetItem 11 | class ContactQListWidgetItem(QListWidgetItem): 12 | def __init__(self, name, url, img_bytes=None): 13 | super().__init__() 14 | # 自定义item中的widget 用来显示自定义的内容 15 | self.widget = QWidget() 16 | # 用来显示name 17 | self.nameLabel = QLabel() 18 | self.nameLabel.setText(name) 19 | # 用来显示avator(图像) 20 | self.avatorLabel = CAvatar(None, shape=CAvatar.Rectangle, size=QSize(60, 60), 21 | url=url, img_bytes=img_bytes) 22 | # 设置布局用来对nameLabel和avatorLabel进行布局 23 | self.hbox = QHBoxLayout() 24 | self.hbox.addWidget(self.avatorLabel) 25 | self.hbox.addWidget(self.nameLabel) 26 | self.hbox.addStretch(1) 27 | # 设置widget的布局 28 | self.widget.setLayout(self.hbox) 29 | # 设置自定义的QListWidgetItem的sizeHint,不然无法显示 30 | self.setSizeHint(self.widget.sizeHint()) 31 | 32 | 33 | if __name__ == "__main__": 34 | app = QApplication(sys.argv) 35 | 36 | # 主窗口 37 | w = QWidget() 38 | w.setWindowTitle("QListWindow") 39 | # 新建QListWidget 40 | listWidget = QListWidget(w) 41 | listWidget.resize(300, 300) 42 | 43 | # 新建两个自定义的QListWidgetItem(customQListWidgetItem) 44 | item1 = ContactQListWidgetItem("鲤鱼王", "liyuwang.jpg") 45 | item2 = ContactQListWidgetItem("可达鸭", "kedaya.jpg") 46 | 47 | # 在listWidget中加入两个自定义的item 48 | listWidget.addItem(item1) 49 | listWidget.setItemWidget(item1, item1.widget) 50 | listWidget.addItem(item2) 51 | listWidget.setItemWidget(item2, item2.widget) 52 | 53 | # 绑定点击槽函数 点击显示对应item中的name 54 | listWidget.itemClicked.connect(lambda item: print(item.nameLabel.text())) 55 | 56 | w.show() 57 | sys.exit(app.exec_()) 58 | -------------------------------------------------------------------------------- /app/resources/icons/html.svg: -------------------------------------------------------------------------------- 1 | 3 | 5 | 6 | 8 | 9 | 11 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import ctypes 2 | import sys 3 | import time 4 | 5 | from PyQt5.QtWidgets import * 6 | 7 | import app.DataBase.data as DB 8 | from app.Ui import decrypt, mainview 9 | 10 | ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID("WeChatReport") 11 | 12 | 13 | class ViewController: 14 | def __init__(self): 15 | self.viewMainWIn = None 16 | self.viewDecrypt = None 17 | 18 | def loadDecryptView(self): 19 | """ 20 | 登录界面 21 | :return: 22 | """ 23 | if DB.is_db_exist(): 24 | self.loadMainWinView() 25 | else: 26 | self.viewDecrypt = decrypt.DecryptControl() # 需要将view login设为成员变量 27 | self.viewDecrypt.DecryptSignal.connect(self.loadMainWinView) 28 | self.viewDecrypt.show() 29 | self.viewDecrypt.db_exist() 30 | 31 | def loadPCDecryptView(self): 32 | """ 33 | 登录界面 34 | :return: 35 | """ 36 | self.viewDecrypt = pc_decrypt.DecryptControl() 37 | self.viewDecrypt.DecryptSignal.connect(self.loadMainWinView) 38 | self.viewDecrypt.show() 39 | 40 | def loadMainWinView(self, username=None): 41 | """ 42 | 聊天界面 43 | :param username: 账号 44 | :return: 45 | """ 46 | username = '' 47 | start = time.time() 48 | self.viewMainWIn = mainview.MainWinController(username=username) 49 | self.viewMainWIn.setWindowTitle("Chat") 50 | # print(username) 51 | self.viewMainWIn.username = username 52 | # self.viewMainWIn.exitSignal.connect(self.loadDecryptView) # 不需要回到登录界面可以省略 53 | self.viewMainWIn.show() 54 | end = time.time() 55 | print('ok', end - start) 56 | # self.viewMainWIn.signUp() 57 | 58 | 59 | if __name__ == '__main__': 60 | app = QApplication(sys.argv) 61 | view = ViewController() 62 | # view.loadPCDecryptView() 63 | view.loadDecryptView() # 进入登录界面,如果view login不是成员变量,则离开作用域后失效。 64 | # view.loadMainWinView('102') 65 | sys.exit(app.exec_()) 66 | -------------------------------------------------------------------------------- /app/ui_pc/chat/chatInfoUi.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Form implementation generated from reading ui file 'chatInfoUi.ui' 4 | # 5 | # Created by: PyQt5 UI code generator 5.15.7 6 | # 7 | # WARNING: Any manual changes made to this file will be lost when pyuic5 is 8 | # run again. Do not edit this file unless you know what you are doing. 9 | 10 | 11 | from PyQt5 import QtCore, QtWidgets 12 | 13 | 14 | class Ui_Form(object): 15 | def setupUi(self, Form): 16 | Form.setObjectName("Form") 17 | self.verticalLayout = QtWidgets.QVBoxLayout(Form) 18 | self.verticalLayout.setContentsMargins(0, 0, 0, 0) 19 | self.verticalLayout.setSpacing(0) 20 | self.verticalLayout.setObjectName("verticalLayout") 21 | self.frame = QtWidgets.QFrame(Form) 22 | self.frame.setFrameShape(QtWidgets.QFrame.NoFrame) 23 | self.frame.setFrameShadow(QtWidgets.QFrame.Raised) 24 | self.frame.setObjectName("frame") 25 | self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.frame) 26 | self.verticalLayout_2.setObjectName("verticalLayout_2") 27 | self.horizontalLayout_2 = QtWidgets.QHBoxLayout() 28 | self.horizontalLayout_2.setObjectName("horizontalLayout_2") 29 | self.label_reamrk = QtWidgets.QLabel(self.frame) 30 | self.label_reamrk.setObjectName("label_reamrk") 31 | self.horizontalLayout_2.addWidget(self.label_reamrk) 32 | spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) 33 | self.horizontalLayout_2.addItem(spacerItem) 34 | self.toolButton = QtWidgets.QToolButton(self.frame) 35 | self.toolButton.setObjectName("toolButton") 36 | self.horizontalLayout_2.addWidget(self.toolButton) 37 | self.verticalLayout_2.addLayout(self.horizontalLayout_2) 38 | 39 | self.verticalLayout.addWidget(self.frame) 40 | 41 | self.retranslateUi(Form) 42 | QtCore.QMetaObject.connectSlotsByName(Form) 43 | 44 | def retranslateUi(self, Form): 45 | _translate = QtCore.QCoreApplication.translate 46 | Form.setWindowTitle(_translate("Form", "Form")) 47 | self.label_reamrk.setText(_translate("Form", "TextLabel")) 48 | self.toolButton.setText(_translate("Form", "...")) 49 | -------------------------------------------------------------------------------- /app/person_pc.py: -------------------------------------------------------------------------------- 1 | from typing import Dict 2 | 3 | from PyQt5.QtCore import Qt 4 | from PyQt5.QtGui import QPixmap 5 | 6 | from app.ui_pc.Icon import Icon 7 | 8 | 9 | def singleton(cls): 10 | _instance = {} 11 | 12 | def inner(): 13 | if cls not in _instance: 14 | _instance[cls] = cls() 15 | return _instance[cls] 16 | 17 | return inner 18 | 19 | 20 | @singleton 21 | class MePC: 22 | def __init__(self): 23 | self.avatar = QPixmap(Icon.Default_avatar_path) 24 | self.avatar_path = ':/icons/icons/default_avatar.svg' 25 | self.wxid = '' 26 | self.wx_dir = '' 27 | self.name = '' 28 | self.mobile = '' 29 | 30 | def set_avatar(self, img_bytes): 31 | if not img_bytes: 32 | self.avatar.load(Icon.Default_avatar_path) 33 | return 34 | if img_bytes[:4] == b'\x89PNG': 35 | self.avatar.loadFromData(img_bytes, format='PNG') 36 | else: 37 | self.avatar.loadFromData(img_bytes, format='jfif') 38 | 39 | 40 | class ContactPC: 41 | def __init__(self, contact_info: Dict): 42 | self.wxid = contact_info.get('UserName') 43 | self.remark = contact_info.get('Remark') 44 | # Alias,Type,Remark,NickName,PYInitial,RemarkPYInitial,ContactHeadImgUrl.smallHeadImgUrl,ContactHeadImgUrl,bigHeadImgUrl 45 | self.alias = contact_info.get('Alias') 46 | self.nickName = contact_info.get('NickName') 47 | if not self.remark: 48 | self.remark = self.nickName 49 | self.smallHeadImgUrl = contact_info.get('smallHeadImgUrl') 50 | self.smallHeadImgBLOG = b'' 51 | self.avatar = QPixmap() 52 | self.avatar_path = ':/icons/icons/default_avatar.svg' 53 | 54 | def set_avatar(self, img_bytes): 55 | if not img_bytes: 56 | self.avatar.load(Icon.Default_avatar_path) 57 | return 58 | if img_bytes[:4] == b'\x89PNG': 59 | self.avatar.loadFromData(img_bytes, format='PNG') 60 | else: 61 | self.avatar.loadFromData(img_bytes, format='jfif') 62 | 63 | self.avatar.scaled(60, 60, Qt.IgnoreAspectRatio, Qt.SmoothTransformation) 64 | 65 | 66 | if __name__ == '__main__': 67 | p1 = MePC() 68 | p2 = MePC() 69 | print(p1 == p2) 70 | -------------------------------------------------------------------------------- /app/resources/icons/contact (1).svg: -------------------------------------------------------------------------------- 1 | 3 | 5 | 7 | -------------------------------------------------------------------------------- /app/resources/icons/logo.svg: -------------------------------------------------------------------------------- 1 | 3 | 5 | 7 | 9 | 11 | 13 | -------------------------------------------------------------------------------- /app/resources/icons/contact (2).svg: -------------------------------------------------------------------------------- 1 | 3 | 5 | 7 | -------------------------------------------------------------------------------- /app/resources/icons/csv.svg: -------------------------------------------------------------------------------- 1 | 3 | 5 | 7 | 9 | 11 | -------------------------------------------------------------------------------- /app/resources/icons/404.svg: -------------------------------------------------------------------------------- 1 | 3 | 5 | 7 | 9 | 11 | 13 | -------------------------------------------------------------------------------- /app/Ui/contact/report/report.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from PyQt5.QtCore import * 4 | from PyQt5.QtGui import * 5 | from PyQt5.QtWebEngineWidgets import QWebEngineView 6 | from PyQt5.QtWidgets import * 7 | 8 | from app import person 9 | from app.DataBase import data 10 | from . import annual_report 11 | 12 | 13 | class ReportController(QWidget): 14 | def __init__(self, contact: person.Contact, me: person.Me = None, parent=None): 15 | super().__init__(parent) 16 | self.ta_username = contact.wxid 17 | self.contact = contact 18 | self.Me = me 19 | # self.setStyleSheet('''QWidget{background-color:rgb(240, 240, 240);}''') 20 | # 加载动画 21 | self.center() 22 | self.label_01() 23 | 24 | def center(self): # 定义一个函数使得窗口居中显示 25 | # 获取屏幕坐标系 26 | screen = QDesktopWidget().screenGeometry() 27 | # 获取窗口坐标系 28 | size = self.geometry() 29 | newLeft = (screen.width() - size.width()) / 2 30 | newTop = (screen.height() - size.height()) / 2 31 | self.move(int(newLeft), int(newTop)) 32 | 33 | def label_01(self): 34 | w = self.size().width() 35 | h = self.size().height() 36 | self.label = QLabel(self) 37 | self.label.setGeometry(w // 2, h // 2, 100, 100) 38 | self.label.setToolTip("这是一个标签") 39 | # self.m_movie() 40 | self.initUI() 41 | 42 | def m_movie(self): 43 | movie = QMovie("./app/data/bg.gif") 44 | self.label.setMovie(movie) 45 | movie.start() 46 | 47 | def initUI(self): 48 | start_time = data.get_msg_start_time(self.contact.wxid) 49 | annual_report.create_title_page(self.contact.nickname, start_time, self.contact.avatar_path) 50 | self.label.setVisible(False) 51 | # self.setStyleSheet('''QWidget{background-color:rgb(244, 244, 244);}''') 52 | main_box = QHBoxLayout(self) 53 | self.browser1 = QWebEngineView() 54 | self.browser1.load(QUrl('file:///data/AnnualReport/index.html')) 55 | 56 | splitter1 = QSplitter(Qt.Vertical) 57 | splitter1.addWidget(self.browser1) 58 | main_box.addWidget(splitter1) 59 | self.setLayout(main_box) 60 | 61 | def setBackground(self): 62 | palette = QPalette() 63 | pix = QPixmap("./app/data/bg.png") 64 | pix = pix.scaled(self.width(), self.height(), Qt.IgnoreAspectRatio, Qt.SmoothTransformation) # 自适应图片大小 65 | palette.setBrush(self.backgroundRole(), QBrush(pix)) # 设置背景图片 66 | # palette.setColor(self.backgroundRole(), QColor(192, 253, 123)) # 设置背景颜色 67 | self.setPalette(palette) 68 | 69 | 70 | if __name__ == '__main__': 71 | app = QApplication(sys.argv) 72 | ex = ReportController(1) 73 | ex.show() 74 | sys.exit(app.exec_()) 75 | -------------------------------------------------------------------------------- /app/util/dat2pic.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | # 图片字节头信息, 4 | # [0][1]为jpg头信息, 5 | # [2][3]为png头信息, 6 | # [4][5]为gif头信息 7 | pic_head = [0xff, 0xd8, 0x89, 0x50, 0x47, 0x49] 8 | # 解密码 9 | decode_code = 0 10 | 11 | 12 | def get_code(file_path): 13 | """ 14 | 自动判断文件类型,并获取dat文件解密码 15 | :param file_path: dat文件路径 16 | :return: 如果文件为jpg/png/gif格式,则返回解密码,否则返回-1 17 | """ 18 | if os.path.isdir(file_path): 19 | return -1, -1 20 | # if file_path[-4:] != ".dat": 21 | # return -1, -1 22 | dat_file = open(file_path, "rb") 23 | dat_read = dat_file.read(2) 24 | # print(dat_read) 25 | head_index = 0 26 | while head_index < len(pic_head): 27 | # 使用第一个头信息字节来计算加密码 28 | # 第二个字节来验证解密码是否正确 29 | code = dat_read[0] ^ pic_head[head_index] 30 | idf_code = dat_read[1] ^ code 31 | head_index = head_index + 1 32 | # if idf_code == pic_head[head_index]: 33 | # dat_file.close() 34 | return head_index, code 35 | head_index = head_index + 1 36 | dat_file.close() 37 | print("not jpg, png, gif") 38 | return -1, -1 39 | 40 | 41 | def decode_dat(file_path, out_path): 42 | """ 43 | 解密文件,并生成图片 44 | :param file_path: dat文件路径 45 | :return: 无 46 | """ 47 | if not os.path.exists(file_path): 48 | return None 49 | file_type, decode_code = get_code(file_path) 50 | 51 | if decode_code == -1: 52 | return 53 | if file_type == 1: 54 | pic_name = os.path.basename(file_path)[:-4] + ".jpg" 55 | elif file_type == 3: 56 | pic_name = file_path[:-4] + ".png" 57 | elif file_type == 5: 58 | pic_name = file_path[:-4] + ".gif" 59 | else: 60 | pic_name = file_path[:-4] + ".jpg" 61 | file_outpath = os.path.join(out_path, pic_name) 62 | if os.path.exists(file_outpath): 63 | return file_outpath 64 | with open(file_path, 'rb') as file_in: 65 | data = file_in.read() 66 | # 对数据进行异或加密/解密 67 | encrypted_data = bytes([byte ^ decode_code for byte in data]) 68 | with open(file_outpath, 'wb') as file_out: 69 | file_out.write(encrypted_data) 70 | print(file_path, '->', file_outpath) 71 | return file_outpath 72 | 73 | 74 | def find_datfile(dir_path, out_path): 75 | """ 76 | 获取dat文件目录下所有的文件 77 | :param dir_path: dat文件目录 78 | :return: 无 79 | """ 80 | files_list = os.listdir(dir_path) 81 | for file_name in files_list: 82 | file_path = dir_path + "\\" + file_name 83 | decode_dat(file_path, out_path) 84 | 85 | 86 | if __name__ == "__main__": 87 | path = "E:\86390\Documents\WeChat Files\wxid_27hqbq7vx5hf22\FileStorage\CustomEmotion\\71\\" 88 | outpath = "D:\\test" 89 | if not os.path.exists(outpath): 90 | os.mkdir(outpath) 91 | find_datfile(path, outpath) 92 | -------------------------------------------------------------------------------- /app/DataBase/msg.py: -------------------------------------------------------------------------------- 1 | import os.path 2 | import sqlite3 3 | import threading 4 | from pprint import pprint 5 | 6 | DB = None 7 | cursor = None 8 | db_path = "./app/Database/Msg/MSG.db" 9 | lock = threading.Lock() 10 | 11 | # misc_path = './Msg/Misc.db' 12 | if os.path.exists(db_path): 13 | DB = sqlite3.connect(db_path, check_same_thread=False) 14 | # '''创建游标''' 15 | cursor = DB.cursor() 16 | 17 | 18 | def is_database_exist(): 19 | return os.path.exists(db_path) 20 | 21 | 22 | def init_database(): 23 | global DB 24 | global cursor 25 | if not DB: 26 | if os.path.exists(db_path): 27 | DB = sqlite3.connect(db_path, check_same_thread=False) 28 | # '''创建游标''' 29 | cursor = DB.cursor() 30 | 31 | 32 | def get_messages(username_): 33 | sql = ''' 34 | select localId,TalkerId,Type,SubType,IsSender,CreateTime,Status,StrContent,strftime('%Y-%m-%d %H:%M:%S',CreateTime,'unixepoch','localtime') as StrTime 35 | from MSG 36 | where StrTalker=? 37 | order by CreateTime 38 | ''' 39 | try: 40 | lock.acquire(True) 41 | cursor.execute(sql, [username_]) 42 | result = cursor.fetchall() 43 | finally: 44 | lock.release() 45 | result.sort(key=lambda x: x[5]) 46 | return result 47 | 48 | 49 | def get_messages_all(): 50 | sql = ''' 51 | select localId,TalkerId,Type,SubType,IsSender,CreateTime,Status,StrContent,strftime('%Y-%m-%d %H:%M:%S',CreateTime,'unixepoch','localtime') as StrTime 52 | from MSG 53 | order by CreateTime 54 | ''' 55 | try: 56 | lock.acquire(True) 57 | cursor.execute(sql) 58 | result = cursor.fetchall() 59 | finally: 60 | lock.release() 61 | result.sort(key=lambda x: x[5]) 62 | return result 63 | 64 | 65 | def get_message_by_num(username_, local_id): 66 | sql = ''' 67 | select localId,TalkerId,Type,SubType,IsSender,CreateTime,Status,StrContent,strftime('%Y-%m-%d %H:%M:%S',CreateTime,'unixepoch','localtime') as StrTime 68 | from MSG 69 | where StrTalker = ? and localId < ? 70 | order by CreateTime desc 71 | limit 10 72 | ''' 73 | try: 74 | lock.acquire(True) 75 | cursor.execute(sql, [username_, local_id]) 76 | result = cursor.fetchall() 77 | finally: 78 | lock.release() 79 | # result.sort(key=lambda x: x[5]) 80 | return result 81 | 82 | 83 | def close(): 84 | global DB 85 | if DB: 86 | DB.close() 87 | 88 | 89 | if __name__ == '__main__': 90 | msg_root_path = './Msg/' 91 | init_database() 92 | result = get_message_by_num('wxid_0o18ef858vnu22', 9999999) 93 | print(result) 94 | print(result[-1][0]) 95 | local_id = result[-1][0] 96 | pprint(get_message_by_num('wxid_0o18ef858vnu22', local_id)) 97 | -------------------------------------------------------------------------------- /app/ui_pc/tool/tool_window.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtCore import Qt, pyqtSignal 2 | from PyQt5.QtGui import QFont 3 | from PyQt5.QtWidgets import QWidget, QListWidgetItem, QLabel 4 | 5 | from app.ui_pc.Icon import Icon 6 | from .pc_decrypt import DecryptControl 7 | from .toolUI import Ui_Dialog 8 | 9 | # 美化样式表 10 | Stylesheet = """ 11 | 12 | /*去掉item虚线边框*/ 13 | QListWidget, QListView, QTreeWidget, QTreeView { 14 | outline: 0px; 15 | border:none; 16 | background-color:rgb(240,240,240) 17 | } 18 | /*设置左侧选项的最小最大宽度,文字颜色和背景颜色*/ 19 | QListWidget { 20 | min-width: 400px; 21 | max-width: 400px; 22 | min-height: 80px; 23 | max-height: 80px; 24 | color: black; 25 | border:none; 26 | } 27 | QListWidget::item{ 28 | height:80px; 29 | width:80px; 30 | } 31 | /*被选中时的背景颜色和左边框颜色*/ 32 | QListWidget::item:selected { 33 | background: rgb(204, 204, 204); 34 | border-bottom: 4px solid rgb(9, 187, 7); 35 | border-left:none; 36 | color: black; 37 | font-weight: bold; 38 | } 39 | /*鼠标悬停颜色*/ 40 | HistoryPanel::item:hover { 41 | background: rgb(52, 52, 52); 42 | } 43 | """ 44 | 45 | 46 | class ToolWindow(QWidget, Ui_Dialog): 47 | get_info_signal = pyqtSignal(str) 48 | decrypt_success_signal = pyqtSignal(bool) 49 | load_finish_signal = pyqtSignal(bool) 50 | 51 | def __init__(self, parent=None): 52 | super().__init__(parent) 53 | self.setupUi(self) 54 | self.setStyleSheet(Stylesheet) 55 | self.init_ui() 56 | self.load_finish_signal.emit(True) 57 | 58 | def init_ui(self): 59 | self.listWidget.clear() 60 | self.listWidget.currentRowChanged.connect(self.setCurrentIndex) 61 | chat_item = QListWidgetItem(Icon.Chat_Icon, '解密', self.listWidget) 62 | contact_item = QListWidgetItem(Icon.Contact_Icon, '别点', self.listWidget) 63 | myinfo_item = QListWidgetItem(Icon.MyInfo_Icon, '别点', self.listWidget) 64 | tool_item = QListWidgetItem(Icon.MyInfo_Icon, '别点', self.listWidget) 65 | decrypt_window = DecryptControl() 66 | decrypt_window.get_wxidSignal.connect(self.get_info_signal) 67 | decrypt_window.DecryptSignal.connect(self.decrypt_success_signal) 68 | self.stackedWidget.addWidget(decrypt_window) 69 | label = QLabel('都说了不让你点', self) 70 | label.setFont(QFont("微软雅黑", 50)) 71 | label.setAlignment(Qt.AlignCenter) 72 | # 设置label的背景颜色(这里随机) 73 | # 这里加了一个margin边距(方便区分QStackedWidget和QLabel的颜色) 74 | # label.setStyleSheet('background: rgb(%d, %d, %d);margin: 50px;' % ( 75 | # randint(0, 255), randint(0, 255), randint(0, 255))) 76 | self.stackedWidget.addWidget(label) 77 | self.stackedWidget.addWidget(label) 78 | self.stackedWidget.addWidget(label) 79 | self.listWidget.setCurrentRow(0) 80 | self.stackedWidget.setCurrentIndex(0) 81 | 82 | def setCurrentIndex(self, row): 83 | print(row) 84 | self.stackedWidget.setCurrentIndex(row) 85 | -------------------------------------------------------------------------------- /app/resources/icons/contact.svg: -------------------------------------------------------------------------------- 1 | 3 | 5 | 7 | -------------------------------------------------------------------------------- /main.spec: -------------------------------------------------------------------------------- 1 | # -*- mode: python ; coding: utf-8 -*- 2 | 3 | add_files = [ 4 | ("D:\\Project\\Python\\WeChatMsg\\app\\data\\icon.png",'.\\app\\data'), 5 | ("D:\\Project\\Python\\WeChatMsg\\app\\data\\stopwords.txt",'.\\app\\data'), 6 | ("D:\\Project\\Python\\WeChatMsg\\app\\data\\bg.gif",'.\\app\\data'), 7 | ("D:\\Project\\Python\\WeChatMsg\\app\\data\\icons",'.\\app\\data\\icons'), 8 | ("D:\\Project\\Python\\WeChatMsg\\app\\ImageBox",'.\\app\\ImageBox'), 9 | ("D:\\Project\\Python\\WeChatMsg\\app\\DataBase",'.\\app\\DataBase'), 10 | #("D:\\Project\\Python\\WeChatMsg\\app\\Ui",'.\\app\\Ui'), 11 | ("D:\\Project\\Python\\WeChatMsg\\sqlcipher-3.0.1",'.\\sqlcipher-3.0.1'), 12 | ('.\\resource\\datasets', 'pyecharts\\datasets\\.'), 13 | ('.\\resource\\render\\templates', 'pyecharts\\render\\templates\\.'), 14 | ('.\\data\\AnnualReport', 'data\\AnnualReport'), 15 | ("D:\\Program Files\\Python310\\Lib\\site-packages\\snownlp",'snownlp') 16 | 17 | ] 18 | block_cipher = None 19 | 20 | #("D:\\Project\\Python\\WeChatMsg\\sqlcipher-3.0.1",'.\\sqlcipher-3.0.1') 21 | 22 | a = Analysis( 23 | ['main.py', 24 | './app/DataBase/data.py','./app/DataBase/output.py', 25 | './app/Ui/mainview.py','./app/Ui/mainwindow.py', 26 | './app/Ui/__init__.py', 27 | './app/Ui/chat/chat.py','./app/Ui/chat/chatUi.py', 28 | './app/Ui/contact/contact.py','./app/Ui/contact/contactUi.py','./app/Ui/contact/analysis/analysis.py','./app/Ui/contact/analysis/charts.py','./app/Ui/contact/report/report.py', 29 | './app/Ui/contact/emotion/emotion.py','./app/Ui/contact/emotion/emotionUi.py', 30 | './app/Ui/contact/contactInfo.py','./app/Ui/contact/contactInfoUi.py', 31 | './app/Ui/contact/userinfo/userinfoUi.py','./app/Ui/contact/userinfo/userinfo.py', 32 | './app/Ui/decrypt/decrypt.py','./app/Ui/decrypt/decryptUi.py', 33 | './app/Ui/userinfo/userinfo.py','./app/Ui/userinfo/userinfoUi.py', 34 | './app/person.py', 35 | './app/Ui/ICON.py', 36 | './app/Ui/MyComponents/Button_Contact.py' 37 | ], 38 | pathex=[], 39 | binaries=[], 40 | datas=add_files, 41 | hiddenimports=[], 42 | hookspath=[], 43 | hooksconfig={}, 44 | runtime_hooks=[], 45 | excludes=[], 46 | win_no_prefer_redirects=False, 47 | win_private_assemblies=False, 48 | cipher=block_cipher, 49 | noarchive=False, 50 | ) 51 | pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) 52 | 53 | exe = EXE( 54 | pyz, 55 | a.scripts, 56 | [], 57 | exclude_binaries=True, 58 | name='main', 59 | debug=False, 60 | bootloader_ignore_signals=False, 61 | strip=False, 62 | upx=True, 63 | console=True, 64 | disable_windowed_traceback=True, 65 | argv_emulation=False, 66 | target_arch=None, 67 | codesign_identity=None, 68 | entitlements_file=None, 69 | icon='./app/data/icon.png' 70 | ) 71 | coll = COLLECT( 72 | exe, 73 | a.binaries, 74 | a.zipfiles, 75 | a.datas, 76 | strip=False, 77 | upx=True, 78 | upx_exclude=[], 79 | name='main', 80 | ) 81 | -------------------------------------------------------------------------------- /app/Ui/userinfo/userinfoUi.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Form implementation generated from reading ui file 'userinfoUi.ui' 4 | # 5 | # Created by: PyQt5 UI code generator 5.15.7 6 | # 7 | # WARNING: Any manual changes made to this file will be lost when pyuic5 is 8 | # run again. Do not edit this file unless you know what you are doing. 9 | 10 | 11 | from PyQt5 import QtCore, QtGui, QtWidgets 12 | 13 | 14 | class Ui_Dialog(object): 15 | def setupUi(self, Dialog): 16 | Dialog.setObjectName("Dialog") 17 | Dialog.resize(1120, 720) 18 | Dialog.setCursor(QtGui.QCursor(QtCore.Qt.ArrowCursor)) 19 | Dialog.setAutoFillBackground(False) 20 | self.frame_2 = QtWidgets.QFrame(Dialog) 21 | self.frame_2.setGeometry(QtCore.QRect(0, 0, 1120, 720)) 22 | self.frame_2.setFrameShape(QtWidgets.QFrame.StyledPanel) 23 | self.frame_2.setFrameShadow(QtWidgets.QFrame.Raised) 24 | self.frame_2.setObjectName("frame_2") 25 | self.horizontalLayoutWidget = QtWidgets.QWidget(self.frame_2) 26 | self.horizontalLayoutWidget.setGeometry(QtCore.QRect(340, 60, 291, 82)) 27 | self.horizontalLayoutWidget.setObjectName("horizontalLayoutWidget") 28 | self.horizontalLayout = QtWidgets.QHBoxLayout(self.horizontalLayoutWidget) 29 | self.horizontalLayout.setContentsMargins(0, 0, 0, 0) 30 | self.horizontalLayout.setObjectName("horizontalLayout") 31 | self.label_avatar = QtWidgets.QLabel(self.horizontalLayoutWidget) 32 | self.label_avatar.setMinimumSize(QtCore.QSize(80, 80)) 33 | self.label_avatar.setObjectName("label_avatar") 34 | self.horizontalLayout.addWidget(self.label_avatar) 35 | self.verticalLayout = QtWidgets.QVBoxLayout() 36 | self.verticalLayout.setObjectName("verticalLayout") 37 | self.label_name = QtWidgets.QLabel(self.horizontalLayoutWidget) 38 | self.label_name.setObjectName("label_name") 39 | self.verticalLayout.addWidget(self.label_name) 40 | self.label_wxid = QtWidgets.QLabel(self.horizontalLayoutWidget) 41 | self.label_wxid.setObjectName("label_wxid") 42 | self.verticalLayout.addWidget(self.label_wxid) 43 | self.label_city = QtWidgets.QLabel(self.horizontalLayoutWidget) 44 | self.label_city.setObjectName("label_city") 45 | self.verticalLayout.addWidget(self.label_city) 46 | self.horizontalLayout.addLayout(self.verticalLayout) 47 | self.horizontalLayout.setStretch(0, 1) 48 | self.horizontalLayout.setStretch(1, 3) 49 | 50 | self.retranslateUi(Dialog) 51 | QtCore.QMetaObject.connectSlotsByName(Dialog) 52 | 53 | def retranslateUi(self, Dialog): 54 | _translate = QtCore.QCoreApplication.translate 55 | Dialog.setWindowTitle(_translate("Dialog", "Dialog")) 56 | self.label_avatar.setText(_translate("Dialog", "TextLabel")) 57 | self.label_name.setText(_translate("Dialog", "TextLabel")) 58 | self.label_wxid.setText(_translate("Dialog", "TextLabel")) 59 | self.label_city.setText(_translate("Dialog", "TextLabel")) 60 | -------------------------------------------------------------------------------- /resource/render/snapshot.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import codecs 3 | import logging 4 | import os 5 | from io import BytesIO 6 | 7 | from ..types import Any 8 | 9 | logger = logging.getLogger(__name__) 10 | 11 | PNG_FORMAT = "png" 12 | JPG_FORMAT = "jpeg" 13 | GIF_FORMAT = "gif" 14 | PDF_FORMAT = "pdf" 15 | SVG_FORMAT = "svg" 16 | EPS_FORMAT = "eps" 17 | B64_FORMAT = "base64" 18 | 19 | 20 | def make_snapshot( 21 | engine: Any, 22 | file_name: str, 23 | output_name: str, 24 | delay: float = 2, 25 | pixel_ratio: int = 2, 26 | is_remove_html: bool = False, 27 | **kwargs, 28 | ): 29 | logger.info("Generating file ...") 30 | file_type = output_name.split(".")[-1] 31 | 32 | content = engine.make_snapshot( 33 | html_path=file_name, 34 | file_type=file_type, 35 | delay=delay, 36 | pixel_ratio=pixel_ratio, 37 | **kwargs, 38 | ) 39 | if file_type in [SVG_FORMAT, B64_FORMAT]: 40 | save_as_text(content, output_name) 41 | else: 42 | # pdf, gif, png, jpeg 43 | content_array = content.split(",") 44 | if len(content_array) != 2: 45 | raise OSError(content_array) 46 | 47 | image_data = decode_base64(content_array[1]) 48 | 49 | if file_type in [PDF_FORMAT, GIF_FORMAT, EPS_FORMAT]: 50 | save_as(image_data, output_name, file_type) 51 | elif file_type in [PNG_FORMAT, JPG_FORMAT]: 52 | save_as_png(image_data, output_name) 53 | else: 54 | raise TypeError(f"Not supported file type '{file_type}'") 55 | 56 | if "/" not in output_name: 57 | output_name = os.path.join(os.getcwd(), output_name) 58 | 59 | if is_remove_html and not file_name.startswith("http"): 60 | os.unlink(file_name) 61 | logger.info(f"File saved in {output_name}") 62 | 63 | 64 | def decode_base64(data: str) -> bytes: 65 | """Decode base64, padding being optional. 66 | 67 | :param data: Base64 data as an ASCII byte string 68 | :returns: The decoded byte string. 69 | """ 70 | missing_padding = len(data) % 4 71 | if missing_padding != 0: 72 | data += "=" * (4 - missing_padding) 73 | return base64.decodebytes(data.encode("utf-8")) 74 | 75 | 76 | def save_as_png(image_data: bytes, output_name: str): 77 | with open(output_name, "wb") as f: 78 | f.write(image_data) 79 | 80 | 81 | def save_as_text(image_data: str, output_name: str): 82 | with codecs.open(output_name, "w", encoding="utf-8") as f: 83 | f.write(image_data) 84 | 85 | 86 | def save_as(image_data: bytes, output_name: str, file_type: str): 87 | try: 88 | from PIL import Image 89 | 90 | m = Image.open(BytesIO(image_data)) 91 | m.load() 92 | color = (255, 255, 255) 93 | b = Image.new("RGB", m.size, color) 94 | # BUG for Mac: 95 | # b.paste(m, mask=m.split()[3]) 96 | b.paste(m) 97 | b.save(output_name, file_type, quality=100) 98 | except ModuleNotFoundError: 99 | raise Exception(f"Please install PIL for {file_type} image type") 100 | -------------------------------------------------------------------------------- /app/Ui/decrypt/decryptUi.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Dialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 400 10 | 300 11 | 12 | 13 | 14 | Dialog 15 | 16 | 17 | 18 | 19 | 110 20 | 20 21 | 221 22 | 51 23 | 24 | 25 | 26 | 27 | 一纸情书 28 | 20 29 | 30 | 31 | 32 | 解密数据库 33 | 34 | 35 | 36 | 37 | 38 | 90 39 | 260 40 | 271 41 | 23 42 | 43 | 44 | 45 | 50 46 | 47 | 48 | 49 | 50 | 51 | 80 52 | 230 53 | 241 54 | 20 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 80 65 | 80 66 | 245 67 | 134 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 点击加载xml文件 77 | 78 | 79 | 80 | 81 | 82 | 83 | xml未就绪 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 点击加载数据库文件 95 | 96 | 97 | 98 | 99 | 100 | 101 | 数据库未就绪 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 开始解密数据库 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /app/resources/icons/annual_report.svg: -------------------------------------------------------------------------------- 1 | 3 | 6 | 8 | 10 | 12 | -------------------------------------------------------------------------------- /app/DataBase/hard_link.py: -------------------------------------------------------------------------------- 1 | import binascii 2 | import os.path 3 | import sqlite3 4 | import threading 5 | import xml.etree.ElementTree as ET 6 | 7 | from app.log import log 8 | 9 | lock = threading.Lock() 10 | DB = None 11 | cursor = None 12 | db_path = "./app/Database/Msg/HardLinkImage.db" 13 | root_path = 'FileStorage/MsgAttach/' 14 | if os.path.exists(db_path): 15 | DB = sqlite3.connect(db_path, check_same_thread=False) 16 | # '''创建游标''' 17 | cursor = DB.cursor() 18 | 19 | 20 | def init_database(): 21 | global DB 22 | global cursor 23 | if not DB: 24 | if os.path.exists(db_path): 25 | DB = sqlite3.connect(db_path, check_same_thread=False) 26 | # '''创建游标''' 27 | cursor = DB.cursor() 28 | 29 | 30 | def get_image_by_md5(md5: bytes): 31 | sql = ''' 32 | select Md5Hash,MD5,FileName,HardLinkImageID.Dir as DirName1,HardLinkImageID2.Dir as DirName2 33 | from HardLinkImageAttribute 34 | join HardLinkImageID on HardLinkImageAttribute.DirID1 = HardLinkImageID.DirID 35 | join HardLinkImageID as HardLinkImageID2 on HardLinkImageAttribute.DirID2 = HardLinkImageID2.DirID 36 | where MD5 = ?; 37 | ''' 38 | try: 39 | lock.acquire(True) 40 | try: 41 | cursor.execute(sql, [md5]) 42 | except AttributeError: 43 | init_database() 44 | finally: 45 | cursor.execute(sql, [md5]) 46 | result = cursor.fetchone() 47 | return result 48 | finally: 49 | lock.release() 50 | 51 | 52 | def get_md5_from_xml(content): 53 | # 解析XML 54 | root = ET.fromstring(content) 55 | # 提取md5的值 56 | md5_value = root.find(".//img").get("md5") 57 | # print(md5_value) 58 | return md5_value 59 | 60 | 61 | @log 62 | def get_image(content, thumb=False): 63 | md5 = get_md5_from_xml(content) 64 | result = get_image_by_md5(binascii.unhexlify(md5)) 65 | if result: 66 | # print(result) 67 | dir1 = result[3] 68 | dir2 = result[4] 69 | data_image = result[2] 70 | dir0 = 'Thumb' if thumb else 'Image' 71 | dat_image = os.path.join(root_path, dir1, dir0, dir2, data_image) 72 | return dat_image 73 | 74 | 75 | def close(): 76 | if DB: 77 | DB.close() 78 | 79 | 80 | # 6b02292eecea118f06be3a5b20075afc_t 81 | 82 | if __name__ == '__main__': 83 | msg_root_path = './Msg/' 84 | db_path = "./Msg/HardLinkImage.db" 85 | init_database() 86 | content = '''\n\t\n\t\n\t\n\n''' 87 | print(get_image(content)) 88 | print(get_image(content, thumb=False)) 89 | result = get_md5_from_xml(content) 90 | print(result) 91 | -------------------------------------------------------------------------------- /app/Ui/decrypt/decryptUi.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Form implementation generated from reading ui file 'decryptUi.ui' 4 | # 5 | # Created by: PyQt5 UI code generator 5.15.7 6 | # 7 | # WARNING: Any manual changes made to this file will be lost when pyuic5 is 8 | # run again. Do not edit this file unless you know what you are doing. 9 | 10 | 11 | from PyQt5 import QtCore, QtGui, QtWidgets 12 | 13 | 14 | class Ui_Dialog(object): 15 | def setupUi(self, Dialog): 16 | Dialog.setObjectName("Dialog") 17 | Dialog.resize(400, 300) 18 | self.label_3 = QtWidgets.QLabel(Dialog) 19 | self.label_3.setGeometry(QtCore.QRect(110, 20, 221, 51)) 20 | font = QtGui.QFont() 21 | font.setFamily("一纸情书") 22 | font.setPointSize(20) 23 | self.label_3.setFont(font) 24 | self.label_3.setObjectName("label_3") 25 | self.progressBar = QtWidgets.QProgressBar(Dialog) 26 | self.progressBar.setGeometry(QtCore.QRect(90, 260, 271, 23)) 27 | self.progressBar.setProperty("value", 50) 28 | self.progressBar.setObjectName("progressBar") 29 | self.label_key = QtWidgets.QLabel(Dialog) 30 | self.label_key.setGeometry(QtCore.QRect(80, 230, 241, 20)) 31 | self.label_key.setText("") 32 | self.label_key.setObjectName("label_key") 33 | self.widget = QtWidgets.QWidget(Dialog) 34 | self.widget.setGeometry(QtCore.QRect(80, 80, 245, 134)) 35 | self.widget.setObjectName("widget") 36 | self.verticalLayout = QtWidgets.QVBoxLayout(self.widget) 37 | self.verticalLayout.setContentsMargins(0, 0, 0, 0) 38 | self.verticalLayout.setObjectName("verticalLayout") 39 | self.horizontalLayout = QtWidgets.QHBoxLayout() 40 | self.horizontalLayout.setObjectName("horizontalLayout") 41 | self.btn_xml = QtWidgets.QPushButton(self.widget) 42 | self.btn_xml.setObjectName("btn_xml") 43 | self.horizontalLayout.addWidget(self.btn_xml) 44 | self.label_xml = QtWidgets.QLabel(self.widget) 45 | self.label_xml.setObjectName("label_xml") 46 | self.horizontalLayout.addWidget(self.label_xml) 47 | self.verticalLayout.addLayout(self.horizontalLayout) 48 | self.horizontalLayout_2 = QtWidgets.QHBoxLayout() 49 | self.horizontalLayout_2.setObjectName("horizontalLayout_2") 50 | self.btn_db = QtWidgets.QPushButton(self.widget) 51 | self.btn_db.setObjectName("btn_db") 52 | self.horizontalLayout_2.addWidget(self.btn_db) 53 | self.label_db = QtWidgets.QLabel(self.widget) 54 | self.label_db.setObjectName("label_db") 55 | self.horizontalLayout_2.addWidget(self.label_db) 56 | self.verticalLayout.addLayout(self.horizontalLayout_2) 57 | self.pushButton_3 = QtWidgets.QPushButton(self.widget) 58 | self.pushButton_3.setObjectName("pushButton_3") 59 | self.verticalLayout.addWidget(self.pushButton_3) 60 | 61 | self.retranslateUi(Dialog) 62 | QtCore.QMetaObject.connectSlotsByName(Dialog) 63 | 64 | def retranslateUi(self, Dialog): 65 | _translate = QtCore.QCoreApplication.translate 66 | Dialog.setWindowTitle(_translate("Dialog", "Dialog")) 67 | self.label_3.setText(_translate("Dialog", "解密数据库")) 68 | self.btn_xml.setText(_translate("Dialog", "点击加载xml文件")) 69 | self.label_xml.setText(_translate("Dialog", "xml未就绪")) 70 | self.btn_db.setText(_translate("Dialog", "点击加载数据库文件")) 71 | self.label_db.setText(_translate("Dialog", "数据库未就绪")) 72 | self.pushButton_3.setText(_translate("Dialog", "开始解密数据库")) 73 | -------------------------------------------------------------------------------- /app/person.py: -------------------------------------------------------------------------------- 1 | import os.path 2 | from typing import Dict 3 | 4 | from PyQt5.QtCore import Qt 5 | from PyQt5.QtGui import QPixmap 6 | 7 | from app.DataBase import data 8 | from app.ui_pc.Icon import Icon 9 | 10 | 11 | # from app.Ui.Icon import Icon 12 | 13 | 14 | class Person: 15 | def __init__(self, wxid: str): 16 | 17 | self.wxid = wxid 18 | self.conRemark = data.get_conRemark(wxid) 19 | self.nickname, self.alias = data.get_nickname(wxid) 20 | self.avatar_path = data.get_avator(wxid) 21 | if os.path.exists(self.avatar_path): 22 | self.avatar = QPixmap(self.avatar_path).scaled(60, 60) 23 | else: 24 | self.avatar_path = './app/data/icons/default_avatar.svg' 25 | # self.avatar_path = Icon.Default_avatar_path 26 | self.avatar = QPixmap(self.avatar_path).scaled(60, 60) 27 | 28 | 29 | class Me(Person): 30 | def __init__(self, wxid: str): 31 | super(Me, self).__init__(wxid) 32 | self.city = None 33 | self.province = None 34 | 35 | 36 | class Contact(Person): 37 | def __init__(self, wxid: str): 38 | super(Contact, self).__init__(wxid) 39 | self.smallHeadImgUrl = '' 40 | self.bigHeadImgUrl = '' 41 | 42 | 43 | def singleton(cls): 44 | _instance = {} 45 | 46 | def inner(): 47 | if cls not in _instance: 48 | _instance[cls] = cls() 49 | return _instance[cls] 50 | 51 | return inner 52 | 53 | 54 | @singleton 55 | class MePC: 56 | def __init__(self): 57 | self.avatar = QPixmap(Icon.Default_avatar_path) 58 | self.avatar_path = 'D:\Project\Python\WeChatMsg\\app\data\icons\default_avatar.svg' 59 | self.wxid = '' 60 | self.wx_dir = '' 61 | self.name = '' 62 | self.mobile = '' 63 | 64 | def set_avatar(self, img_bytes): 65 | if not img_bytes: 66 | self.avatar.load(Icon.Default_avatar_path) 67 | return 68 | if img_bytes[:4] == b'\x89PNG': 69 | self.avatar.loadFromData(img_bytes, format='PNG') 70 | else: 71 | self.avatar.loadFromData(img_bytes, format='jfif') 72 | 73 | 74 | class ContactPC: 75 | def __init__(self, contact_info: Dict): 76 | self.wxid = contact_info.get('UserName') 77 | self.remark = contact_info.get('Remark') 78 | # Alias,Type,Remark,NickName,PYInitial,RemarkPYInitial,ContactHeadImgUrl.smallHeadImgUrl,ContactHeadImgUrl,bigHeadImgUrl 79 | self.alias = contact_info.get('Alias') 80 | self.nickName = contact_info.get('NickName') 81 | if not self.remark: 82 | self.remark = self.nickName 83 | self.smallHeadImgUrl = contact_info.get('smallHeadImgUrl') 84 | self.smallHeadImgBLOG = b'' 85 | self.avatar = QPixmap() 86 | self.avatar_path = 'D:\Project\Python\WeChatMsg\\app\data\icons\default_avatar.svg' 87 | 88 | def set_avatar(self, img_bytes): 89 | if not img_bytes: 90 | self.avatar.load(Icon.Default_avatar_path) 91 | return 92 | if img_bytes[:4] == b'\x89PNG': 93 | self.avatar.loadFromData(img_bytes, format='PNG') 94 | else: 95 | self.avatar.loadFromData(img_bytes, format='jfif') 96 | 97 | self.avatar.scaled(60, 60, Qt.IgnoreAspectRatio, Qt.SmoothTransformation) 98 | 99 | 100 | class Group(Person): 101 | def __init__(self, wxid: str): 102 | super(Group, self).__init__(wxid) 103 | 104 | 105 | if __name__ == '__main__': 106 | p1 = MePC() 107 | p2 = MePC() 108 | print(p1 == p2) 109 | -------------------------------------------------------------------------------- /app/ui_pc/chat/chatUi.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Form implementation generated from reading ui file 'chatUi.ui' 4 | # 5 | # Created by: PyQt5 UI code generator 5.15.7 6 | # 7 | # WARNING: Any manual changes made to this file will be lost when pyuic5 is 8 | # run again. Do not edit this file unless you know what you are doing. 9 | 10 | 11 | from PyQt5 import QtCore, QtWidgets 12 | 13 | 14 | class Ui_Form(object): 15 | def setupUi(self, Form): 16 | Form.setObjectName("Form") 17 | Form.resize(840, 752) 18 | Form.setStyleSheet("background: rgb(240, 240, 240);") 19 | self.horizontalLayout_2 = QtWidgets.QHBoxLayout(Form) 20 | self.horizontalLayout_2.setSpacing(6) 21 | self.horizontalLayout_2.setObjectName("horizontalLayout_2") 22 | self.verticalLayout_2 = QtWidgets.QVBoxLayout() 23 | self.verticalLayout_2.setSpacing(6) 24 | self.verticalLayout_2.setObjectName("verticalLayout_2") 25 | self.verticalLayout = QtWidgets.QVBoxLayout() 26 | self.verticalLayout.setObjectName("verticalLayout") 27 | self.horizontalLayout = QtWidgets.QHBoxLayout() 28 | self.horizontalLayout.setObjectName("horizontalLayout") 29 | self.label = QtWidgets.QLabel(Form) 30 | self.label.setText("") 31 | self.label.setObjectName("label") 32 | self.horizontalLayout.addWidget(self.label) 33 | self.lineEdit = QtWidgets.QLineEdit(Form) 34 | self.lineEdit.setMinimumSize(QtCore.QSize(200, 30)) 35 | self.lineEdit.setMaximumSize(QtCore.QSize(200, 16777215)) 36 | self.lineEdit.setStyleSheet("background:transparent;\n" 37 | "border-radius:5px;\n" 38 | "border-top: 0px solid #b2e281;\n" 39 | "border-bottom: 0px solid #b2e281;\n" 40 | "border-right: 0px solid #b2e281;\n" 41 | "border-left: 0px solid #b2e281;\n" 42 | "border-style:outset;\n" 43 | "background-color:rgb(226,226,226);\n" 44 | " ") 45 | self.lineEdit.setCursorMoveStyle(QtCore.Qt.VisualMoveStyle) 46 | self.lineEdit.setObjectName("lineEdit") 47 | self.horizontalLayout.addWidget(self.lineEdit) 48 | self.label_2 = QtWidgets.QLabel(Form) 49 | self.label_2.setMinimumSize(QtCore.QSize(30, 0)) 50 | self.label_2.setText("") 51 | self.label_2.setObjectName("label_2") 52 | self.horizontalLayout.addWidget(self.label_2) 53 | self.verticalLayout.addLayout(self.horizontalLayout) 54 | self.verticalLayout_2.addLayout(self.verticalLayout) 55 | self.listWidget = QtWidgets.QListWidget(Form) 56 | self.listWidget.setMinimumSize(QtCore.QSize(250, 0)) 57 | self.listWidget.setMaximumSize(QtCore.QSize(250, 16777215)) 58 | self.listWidget.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) 59 | self.listWidget.setObjectName("listWidget") 60 | self.verticalLayout_2.addWidget(self.listWidget) 61 | self.verticalLayout_2.setStretch(1, 1) 62 | self.horizontalLayout_2.addLayout(self.verticalLayout_2) 63 | self.stackedWidget = QtWidgets.QStackedWidget(Form) 64 | self.stackedWidget.setObjectName("stackedWidget") 65 | self.horizontalLayout_2.addWidget(self.stackedWidget) 66 | self.horizontalLayout_2.setStretch(1, 1) 67 | 68 | self.retranslateUi(Form) 69 | self.stackedWidget.setCurrentIndex(-1) 70 | QtCore.QMetaObject.connectSlotsByName(Form) 71 | 72 | def retranslateUi(self, Form): 73 | _translate = QtCore.QCoreApplication.translate 74 | Form.setWindowTitle(_translate("Form", "Form")) 75 | -------------------------------------------------------------------------------- /app/ui_pc/contact/contactUi.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Form implementation generated from reading ui file 'contactUi.ui' 4 | # 5 | # Created by: PyQt5 UI code generator 5.15.7 6 | # 7 | # WARNING: Any manual changes made to this file will be lost when pyuic5 is 8 | # run again. Do not edit this file unless you know what you are doing. 9 | 10 | 11 | from PyQt5 import QtCore, QtWidgets 12 | 13 | 14 | class Ui_Form(object): 15 | def setupUi(self, Form): 16 | Form.setObjectName("Form") 17 | Form.resize(840, 752) 18 | Form.setStyleSheet("background: rgb(240, 240, 240);") 19 | self.horizontalLayout_2 = QtWidgets.QHBoxLayout(Form) 20 | self.horizontalLayout_2.setObjectName("horizontalLayout_2") 21 | self.verticalLayout_2 = QtWidgets.QVBoxLayout() 22 | self.verticalLayout_2.setSpacing(6) 23 | self.verticalLayout_2.setObjectName("verticalLayout_2") 24 | self.verticalLayout = QtWidgets.QVBoxLayout() 25 | self.verticalLayout.setObjectName("verticalLayout") 26 | self.horizontalLayout = QtWidgets.QHBoxLayout() 27 | self.horizontalLayout.setObjectName("horizontalLayout") 28 | self.label = QtWidgets.QLabel(Form) 29 | self.label.setText("") 30 | self.label.setObjectName("label") 31 | self.horizontalLayout.addWidget(self.label) 32 | self.lineEdit = QtWidgets.QLineEdit(Form) 33 | self.lineEdit.setMinimumSize(QtCore.QSize(200, 30)) 34 | self.lineEdit.setMaximumSize(QtCore.QSize(200, 16777215)) 35 | self.lineEdit.setStyleSheet("background:transparent;\n" 36 | "border-radius:5px;\n" 37 | "border-top: 0px solid #b2e281;\n" 38 | "border-bottom: 0px solid #b2e281;\n" 39 | "border-right: 0px solid #b2e281;\n" 40 | "border-left: 0px solid #b2e281;\n" 41 | "border-style:outset;\n" 42 | "background-color:rgb(226,226,226);\n" 43 | " ") 44 | self.lineEdit.setCursorMoveStyle(QtCore.Qt.VisualMoveStyle) 45 | self.lineEdit.setObjectName("lineEdit") 46 | self.horizontalLayout.addWidget(self.lineEdit) 47 | self.label_2 = QtWidgets.QLabel(Form) 48 | self.label_2.setMinimumSize(QtCore.QSize(30, 0)) 49 | self.label_2.setMaximumSize(QtCore.QSize(30, 16777215)) 50 | self.label_2.setText("") 51 | self.label_2.setObjectName("label_2") 52 | self.horizontalLayout.addWidget(self.label_2) 53 | self.verticalLayout.addLayout(self.horizontalLayout) 54 | self.verticalLayout_2.addLayout(self.verticalLayout) 55 | self.listWidget = QtWidgets.QListWidget(Form) 56 | self.listWidget.setMinimumSize(QtCore.QSize(250, 0)) 57 | self.listWidget.setMaximumSize(QtCore.QSize(250, 16777215)) 58 | self.listWidget.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) 59 | self.listWidget.setObjectName("listWidget") 60 | self.verticalLayout_2.addWidget(self.listWidget) 61 | self.verticalLayout_2.setStretch(1, 1) 62 | self.horizontalLayout_2.addLayout(self.verticalLayout_2) 63 | self.stackedWidget = QtWidgets.QStackedWidget(Form) 64 | self.stackedWidget.setObjectName("stackedWidget") 65 | self.horizontalLayout_2.addWidget(self.stackedWidget) 66 | self.horizontalLayout_2.setStretch(1, 1) 67 | 68 | self.retranslateUi(Form) 69 | self.stackedWidget.setCurrentIndex(-1) 70 | QtCore.QMetaObject.connectSlotsByName(Form) 71 | 72 | def retranslateUi(self, Form): 73 | _translate = QtCore.QCoreApplication.translate 74 | Form.setWindowTitle(_translate("Form", "Form")) 75 | -------------------------------------------------------------------------------- /app/Ui/userinfo/userinfoUi.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Dialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 1120 10 | 720 11 | 12 | 13 | 14 | ArrowCursor 15 | 16 | 17 | Dialog 18 | 19 | 20 | false 21 | 22 | 23 | 24 | 25 | 0 26 | 0 27 | 1120 28 | 720 29 | 30 | 31 | 32 | QFrame::StyledPanel 33 | 34 | 35 | QFrame::Raised 36 | 37 | 38 | 39 | 40 | 340 41 | 60 42 | 291 43 | 82 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 80 52 | 80 53 | 54 | 55 | 56 | TextLabel 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | TextLabel 66 | 67 | 68 | 69 | 70 | 71 | 72 | TextLabel 73 | 74 | 75 | 76 | 77 | 78 | 79 | TextLabel 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /app/ui_pc/tool/toolUI.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Form implementation generated from reading ui file 'toolUI.ui' 4 | # 5 | # Created by: PyQt5 UI code generator 5.15.7 6 | # 7 | # WARNING: Any manual changes made to this file will be lost when pyuic5 is 8 | # run again. Do not edit this file unless you know what you are doing. 9 | 10 | 11 | from PyQt5 import QtCore, QtGui, QtWidgets 12 | 13 | 14 | class Ui_Dialog(object): 15 | def setupUi(self, Dialog): 16 | Dialog.setObjectName("Dialog") 17 | Dialog.resize(630, 547) 18 | font = QtGui.QFont() 19 | font.setFamily("微软雅黑") 20 | Dialog.setFont(font) 21 | self.verticalLayout = QtWidgets.QVBoxLayout(Dialog) 22 | self.verticalLayout.setObjectName("verticalLayout") 23 | self.horizontalLayout = QtWidgets.QHBoxLayout() 24 | self.horizontalLayout.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint) 25 | self.horizontalLayout.setSpacing(0) 26 | self.horizontalLayout.setObjectName("horizontalLayout") 27 | self.label = QtWidgets.QLabel(Dialog) 28 | self.label.setMaximumSize(QtCore.QSize(80, 80)) 29 | self.label.setText("") 30 | self.label.setObjectName("label") 31 | self.horizontalLayout.addWidget(self.label) 32 | self.listWidget = QtWidgets.QListWidget(Dialog) 33 | self.listWidget.setMinimumSize(QtCore.QSize(500, 80)) 34 | self.listWidget.setMaximumSize(QtCore.QSize(500, 80)) 35 | self.listWidget.setFrameShape(QtWidgets.QFrame.NoFrame) 36 | self.listWidget.setFrameShadow(QtWidgets.QFrame.Plain) 37 | self.listWidget.setLineWidth(0) 38 | self.listWidget.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) 39 | self.listWidget.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) 40 | self.listWidget.setFlow(QtWidgets.QListView.LeftToRight) 41 | self.listWidget.setObjectName("listWidget") 42 | item = QtWidgets.QListWidgetItem() 43 | self.listWidget.addItem(item) 44 | item = QtWidgets.QListWidgetItem() 45 | self.listWidget.addItem(item) 46 | item = QtWidgets.QListWidgetItem() 47 | self.listWidget.addItem(item) 48 | item = QtWidgets.QListWidgetItem() 49 | self.listWidget.addItem(item) 50 | item = QtWidgets.QListWidgetItem() 51 | self.listWidget.addItem(item) 52 | self.horizontalLayout.addWidget(self.listWidget) 53 | self.label_2 = QtWidgets.QLabel(Dialog) 54 | self.label_2.setMaximumSize(QtCore.QSize(80, 80)) 55 | self.label_2.setText("") 56 | self.label_2.setObjectName("label_2") 57 | self.horizontalLayout.addWidget(self.label_2) 58 | self.verticalLayout.addLayout(self.horizontalLayout) 59 | self.stackedWidget = QtWidgets.QStackedWidget(Dialog) 60 | self.stackedWidget.setObjectName("stackedWidget") 61 | self.verticalLayout.addWidget(self.stackedWidget) 62 | self.verticalLayout.setStretch(1, 1) 63 | 64 | self.retranslateUi(Dialog) 65 | self.stackedWidget.setCurrentIndex(-1) 66 | QtCore.QMetaObject.connectSlotsByName(Dialog) 67 | 68 | def retranslateUi(self, Dialog): 69 | _translate = QtCore.QCoreApplication.translate 70 | Dialog.setWindowTitle(_translate("Dialog", "Dialog")) 71 | __sortingEnabled = self.listWidget.isSortingEnabled() 72 | self.listWidget.setSortingEnabled(False) 73 | item = self.listWidget.item(0) 74 | item.setText(_translate("Dialog", "新建项目")) 75 | item = self.listWidget.item(1) 76 | item.setText(_translate("Dialog", "新建项目")) 77 | item = self.listWidget.item(2) 78 | item.setText(_translate("Dialog", "新建项目")) 79 | item = self.listWidget.item(3) 80 | item.setText(_translate("Dialog", "新建项目")) 81 | item = self.listWidget.item(4) 82 | item.setText(_translate("Dialog", "新建项目")) 83 | self.listWidget.setSortingEnabled(__sortingEnabled) 84 | -------------------------------------------------------------------------------- /app/Ui/contact/contactUi.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Form implementation generated from reading ui file 'contactUi.ui' 4 | # 5 | # Created by: PyQt5 UI code generator 5.15.7 6 | # 7 | # WARNING: Any manual changes made to this file will be lost when pyuic5 is 8 | # run again. Do not edit this file unless you know what you are doing. 9 | 10 | 11 | from PyQt5 import QtCore, QtGui, QtWidgets 12 | 13 | 14 | class Ui_Dialog(object): 15 | def setupUi(self, Dialog): 16 | Dialog.setObjectName("Dialog") 17 | Dialog.resize(1141, 740) 18 | Dialog.setCursor(QtGui.QCursor(QtCore.Qt.ArrowCursor)) 19 | Dialog.setAutoFillBackground(False) 20 | self.horizontalLayout = QtWidgets.QHBoxLayout(Dialog) 21 | self.horizontalLayout.setContentsMargins(0, 0, 0, 0) 22 | self.horizontalLayout.setSpacing(0) 23 | self.horizontalLayout.setObjectName("horizontalLayout") 24 | self.frame_2 = QtWidgets.QFrame(Dialog) 25 | self.frame_2.setFrameShape(QtWidgets.QFrame.StyledPanel) 26 | self.frame_2.setFrameShadow(QtWidgets.QFrame.Raised) 27 | self.frame_2.setObjectName("frame_2") 28 | self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.frame_2) 29 | self.horizontalLayout_2.setContentsMargins(0, 0, 0, 0) 30 | self.horizontalLayout_2.setSpacing(0) 31 | self.horizontalLayout_2.setObjectName("horizontalLayout_2") 32 | self.scrollArea = QtWidgets.QScrollArea(self.frame_2) 33 | self.scrollArea.setEnabled(True) 34 | self.scrollArea.setMinimumSize(QtCore.QSize(325, 0)) 35 | self.scrollArea.setMaximumSize(QtCore.QSize(325, 150000)) 36 | self.scrollArea.setAutoFillBackground(False) 37 | self.scrollArea.setFrameShape(QtWidgets.QFrame.NoFrame) 38 | self.scrollArea.setFrameShadow(QtWidgets.QFrame.Raised) 39 | self.scrollArea.setMidLineWidth(0) 40 | self.scrollArea.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn) 41 | self.scrollArea.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) 42 | self.scrollArea.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContentsOnFirstShow) 43 | self.scrollArea.setWidgetResizable(False) 44 | self.scrollArea.setObjectName("scrollArea") 45 | self.scrollAreaWidgetContents = QtWidgets.QWidget() 46 | self.scrollAreaWidgetContents.setGeometry(QtCore.QRect(0, 0, 300, 12000)) 47 | self.scrollAreaWidgetContents.setObjectName("scrollAreaWidgetContents") 48 | self.pushButton_2 = QtWidgets.QPushButton(self.scrollAreaWidgetContents) 49 | self.pushButton_2.setGeometry(QtCore.QRect(0, 0, 300, 80)) 50 | self.pushButton_2.setLayoutDirection(QtCore.Qt.LeftToRight) 51 | self.pushButton_2.setAutoFillBackground(False) 52 | self.pushButton_2.setText("") 53 | self.pushButton_2.setIconSize(QtCore.QSize(80, 80)) 54 | self.pushButton_2.setObjectName("pushButton_2") 55 | self.label = QtWidgets.QLabel(self.scrollAreaWidgetContents) 56 | self.label.setGeometry(QtCore.QRect(220, 10, 72, 15)) 57 | self.label.setObjectName("label") 58 | self.scrollArea.setWidget(self.scrollAreaWidgetContents) 59 | self.horizontalLayout_2.addWidget(self.scrollArea) 60 | self.stackedWidget = QtWidgets.QStackedWidget(self.frame_2) 61 | self.stackedWidget.setObjectName("stackedWidget") 62 | self.page = QtWidgets.QWidget() 63 | self.page.setObjectName("page") 64 | self.stackedWidget.addWidget(self.page) 65 | self.page_2 = QtWidgets.QWidget() 66 | self.page_2.setObjectName("page_2") 67 | self.stackedWidget.addWidget(self.page_2) 68 | self.horizontalLayout_2.addWidget(self.stackedWidget) 69 | self.horizontalLayout.addWidget(self.frame_2) 70 | 71 | self.retranslateUi(Dialog) 72 | QtCore.QMetaObject.connectSlotsByName(Dialog) 73 | 74 | def retranslateUi(self, Dialog): 75 | _translate = QtCore.QCoreApplication.translate 76 | Dialog.setWindowTitle(_translate("Dialog", "Dialog")) 77 | self.label.setText(_translate("Dialog", "TextLabel")) 78 | -------------------------------------------------------------------------------- /app/Ui/contact/contact.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | @File : contact.py 4 | @Author : Shuaikang Zhou 5 | @Time : 2022/12/13 15:07 6 | @IDE : Pycharm 7 | @Version : Python3.10 8 | @comment : ··· 9 | """ 10 | from typing import Dict 11 | 12 | from PyQt5 import QtCore 13 | from PyQt5.QtCore import * 14 | from PyQt5.QtWidgets import * 15 | 16 | import app.components.Button_Contact as MyLabel 17 | from app import person 18 | from app.DataBase import data 19 | from app.Ui.contact.contactInfo import ContactInfo 20 | from app.Ui.contact.contactUi import Ui_Dialog 21 | 22 | EMOTION = 1 23 | ANALYSIS = 2 24 | 25 | 26 | class StackedWidget(): 27 | def __init__(self): 28 | pass 29 | 30 | 31 | class ContactController(QWidget, Ui_Dialog): 32 | exitSignal = pyqtSignal() 33 | urlSignal = pyqtSignal(QUrl) 34 | 35 | # username = '' 36 | 37 | def __init__(self, Me: person.Me, parent=None): 38 | super(ContactController, self).__init__(parent) 39 | self.chatroomFlag = None 40 | self.ta_avatar = None 41 | self.setupUi(self) 42 | self.Me = Me 43 | self.contacts: Dict[str, MyLabel.ContactUi] = {} 44 | self.contactInfo: Dict[str, ContactInfo] = {} 45 | self.show_flag = False 46 | self.last_talkerId = None 47 | self.now_talkerId = None 48 | # self.showContact() 49 | self.show_thread = ShowContactThread() 50 | self.show_thread.showSingal.connect(self.showContact) 51 | self.show_thread.heightSingal.connect(self.setScreenAreaHeight) 52 | self.show_thread.start() 53 | 54 | def showContact(self, data_): 55 | """ 56 | data:Tuple[rconversation,index:int] 57 | 显示联系人 58 | :return: 59 | """ 60 | rconversation, i = data_ 61 | username = rconversation[1] 62 | # print(username) 63 | pushButton_2 = MyLabel.ContactUi(self.scrollAreaWidgetContents, i, rconversation) 64 | pushButton_2.setGeometry(QtCore.QRect(0, 80 * i, 300, 80)) 65 | pushButton_2.setLayoutDirection(QtCore.Qt.LeftToRight) 66 | pushButton_2.clicked.connect(pushButton_2.show_msg) 67 | pushButton_2.usernameSingal.connect(self.Contact) 68 | self.contacts[username] = pushButton_2 69 | self.contactInfo[username] = ContactInfo(username, self.Me) 70 | self.stackedWidget.addWidget(self.contactInfo[username]) 71 | 72 | def setScreenAreaHeight(self, height: int): 73 | self.scrollAreaWidgetContents.setGeometry( 74 | QtCore.QRect(0, 0, 300, height)) 75 | 76 | def Contact(self, talkerId): 77 | """ 78 | 聊天界面 点击联系人头像时候显示聊天数据 79 | :param talkerId: 80 | :return: 81 | """ 82 | self.now_talkerId = talkerId 83 | # 把当前按钮设置为灰色 84 | if self.last_talkerId and self.last_talkerId != talkerId: 85 | print('对方账号:', self.last_talkerId) 86 | self.contacts[self.last_talkerId].setStyleSheet( 87 | "QPushButton {background-color: rgb(220,220,220);}" 88 | "QPushButton:hover{background-color: rgb(208,208,208);}\n" 89 | ) 90 | self.last_talkerId = talkerId 91 | self.contacts[talkerId].setStyleSheet( 92 | "QPushButton {background-color: rgb(198,198,198);}" 93 | "QPushButton:hover{background-color: rgb(209,209,209);}\n" 94 | ) 95 | self.stackedWidget.setCurrentWidget(self.contactInfo[talkerId]) 96 | 97 | if '@chatroom' in talkerId: 98 | self.chatroomFlag = True 99 | else: 100 | self.chatroomFlag = False 101 | 102 | 103 | class ShowContactThread(QThread): 104 | showSingal = pyqtSignal(tuple) 105 | heightSingal = pyqtSignal(int) 106 | 107 | def __init__(self): 108 | super().__init__() 109 | 110 | def run(self) -> None: 111 | rconversations = data.get_rconversation() 112 | max_height = max(len(rconversations) * 80, 680) 113 | # 设置滚动区域的高度 114 | self.heightSingal.emit(max_height) 115 | for i in range(len(rconversations)): 116 | self.showSingal.emit((rconversations[i], i)) 117 | -------------------------------------------------------------------------------- /app/ui_pc/chat/chatInfoUi.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 817 10 | 748 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 0 19 | 20 | 21 | 0 22 | 23 | 24 | 0 25 | 26 | 27 | 0 28 | 29 | 30 | 0 31 | 32 | 33 | 34 | 35 | QFrame::NoFrame 36 | 37 | 38 | QFrame::Raised 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | TextLabel 47 | 48 | 49 | 50 | 51 | 52 | 53 | Qt::Horizontal 54 | 55 | 56 | 57 | 40 58 | 20 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | ... 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | true 76 | 77 | 78 | 79 | 80 | 0 81 | 0 82 | 797 83 | 700 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /resource/datasets/countries_regions_db.json: -------------------------------------------------------------------------------- 1 | { 2 | "蒲隆地": "BI", 3 | "圣卢西亚": "LC", 4 | "巴巴多斯": "BB", 5 | "安道尔": "AD", 6 | "利比里亚": "LR", 7 | "吉尔吉斯斯坦": "KG", 8 | "阿曼": "OM", 9 | "利比亚": "LY", 10 | "根西": "GG", 11 | "南非": "ZA", 12 | "阿塞拜疆": "AZ", 13 | "巴拉圭": "PY", 14 | "匈牙利": "HU", 15 | "巴勒斯坦": "PS", 16 | "泰国": "TH", 17 | "黎巴嫩": "LB", 18 | "英属维尔京群岛": "VG", 19 | "特立尼达和多巴哥": "TT", 20 | "巴基斯坦": "PK", 21 | "加拿大": "CA", 22 | "保加利亚": "BG", 23 | "马来西亚": "MY", 24 | "叙利亚": "SY", 25 | "毛里塔尼亚": "MR", 26 | "白俄罗斯": "BY", 27 | "秘鲁": "PE", 28 | "摩洛哥": "MA", 29 | "莱索托": "LS", 30 | "法罗群岛": "FO", 31 | "圣基茨和尼维斯": "KN", 32 | "阿根廷": "AR", 33 | "委内瑞拉": "VE", 34 | "塞舌尔": "SC", 35 | "英属印度洋领地": "IO", 36 | "瓦努阿图": "VU", 37 | "纳米比亚": "NA", 38 | "马拉维": "MW", 39 | "克罗地亚": "HR", 40 | "玻利维亚": "BO", 41 | "塞内加尔": "SN", 42 | "希腊": "GR", 43 | "蒙古": "MN", 44 | "东帝汶": "TL", 45 | "新加坡": "SG", 46 | "意大利": "IT", 47 | "皮特凱恩群島": "PN", 48 | "芬兰": "FI", 49 | "圭亚那": "GY", 50 | "福克兰群岛": "FK", 51 | "毛里求斯": "MU", 52 | "马其顿": "MK", 53 | "新西兰": "NZ", 54 | "波斯尼亚-黑塞哥维那": "BA", 55 | "吐瓦鲁": "TV", 56 | "以色列": "IL", 57 | "塞尔维亚": "RS", 58 | "密克罗尼西亚联邦": "FM", 59 | "斯洛文尼亚": "SI", 60 | "阿富汗": "AF", 61 | "科威特": "KW", 62 | "英国": "GB", 63 | "汤加": "TO", 64 | "贝宁": "BJ", 65 | "撒拉威阿拉伯民主共和国": "", 66 | "海地": "HT", 67 | "格鲁吉亚": "GE", 68 | "安提瓜和巴布达": "AG", 69 | "刚果金": "CD", 70 | "澳大利亚": "AU", 71 | "史瓦济兰": "SZ", 72 | "马达加斯加": "MG", 73 | "哥伦比亚": "CO", 74 | "多米尼加共和国": "DO", 75 | "爱沙尼亚": "EE", 76 | "哥斯达黎加": "CR", 77 | "塞浦路斯": "CY", 78 | "沙特阿拉伯": "SA", 79 | "南苏丹": "SS", 80 | "荷兰": "NL", 81 | "圣马力诺": "SM", 82 | "罗马尼亚": "RO", 83 | "智利": "CL", 84 | "几内亚比索": "GW", 85 | "尼加拉瓜": "NI", 86 | "特克斯和凯科斯群岛": "TC", 87 | "珊瑚海群岛领地": "", 88 | "泽西": "JE", 89 | "奥地利": "AT", 90 | "蒙特塞拉特": "MS", 91 | "纽埃": "NU", 92 | "圣文森特和格林纳丁斯": "VC", 93 | "伯利兹": "BZ", 94 | "斯洛伐克": "SK", 95 | "哈萨克斯坦": "KZ", 96 | "直布罗陀": "GI", 97 | "柬埔寨": "KH", 98 | "立陶宛": "LT", 99 | "中非共和国": "CF", 100 | "瑞典": "SE", 101 | "爱尔兰": "IE", 102 | "赤道几内亚": "GQ", 103 | "亚美尼亚": "AM", 104 | "捷克": "CZ", 105 | "冈比亚": "GM", 106 | "格陵兰": "GL", 107 | "科索沃": "", 108 | "印度": "IN", 109 | "多哥": "TG", 110 | "挪威": "NO", 111 | "土耳其": "TR", 112 | "拉脱维亚": "LV", 113 | "佛得角": "CV", 114 | "乌兹别克斯坦": "UZ", 115 | "肯尼亚": "KE", 116 | "博茨瓦纳": "BW", 117 | "加蓬": "GA", 118 | "塔吉克斯坦": "TJ", 119 | "尼日尔": "NE", 120 | "布基纳法索": "BF", 121 | "安圭拉": "AI", 122 | "老挝": "LA", 123 | "阿尔巴尼亚": "AL", 124 | "厄瓜多尔": "EC", 125 | "葡萄牙": "PT", 126 | "乍得": "TD", 127 | "南乔治亚和南桑威奇群岛": "GS", 128 | "几内亚": "GN", 129 | "冰岛": "IS", 130 | "托克劳": "TK", 131 | "摩纳哥": "MC", 132 | "埃及": "EG", 133 | "马绍尔群岛": "MH", 134 | "巴拿马": "PA", 135 | "巴林": "BH", 136 | "苏里南": "SR", 137 | "帕劳": "PW", 138 | "尼日利亚": "NG", 139 | "加纳": "GH", 140 | "牙买加": "JM", 141 | "巴哈马": "BS", 142 | "波兰": "PL", 143 | "土库曼": "TM", 144 | "古巴": "CU", 145 | "吉布提": "DJ", 146 | "马恩岛": "IM", 147 | "巴布亚新几内亚": "PG", 148 | "文莱": "BN", 149 | "摩尔多瓦": "MD", 150 | "开曼群岛": "KY", 151 | "安哥拉": "AO", 152 | "乌克兰": "UA", 153 | "喀麦隆": "CM", 154 | "萨尔瓦多": "SV", 155 | "萨摩亚": "WS", 156 | "黑山": "ME", 157 | "孟加拉国": "BD", 158 | "韩国": "KR", 159 | "苏丹": "SD", 160 | "厄立特里亚": "ER", 161 | "库克群岛": "CK", 162 | "马里": "ML", 163 | "伊朗": "IR", 164 | "伊拉克": "IQ", 165 | "日本": "JP", 166 | "突尼斯": "TN", 167 | "津巴布韦": "ZW", 168 | "菲律宾": "PH", 169 | "约旦": "JO", 170 | "埃塞俄比亚": "ET", 171 | "德国": "DE", 172 | "巴西": "BR", 173 | "卢旺达": "RW", 174 | "洪都拉斯": "HN", 175 | "梵蒂冈": "VA", 176 | "基里巴斯": "KI", 177 | "阿拉伯联合酋长国": "AE", 178 | "墨西哥": "MX", 179 | "塞拉利昂": "SL", 180 | "所罗门群岛": "SB", 181 | "斯里兰卡": "LK", 182 | "俄罗斯": "RU", 183 | "法国": "FR", 184 | "危地马拉": "GT", 185 | "越南": "VN", 186 | "坦桑尼亚": "TZ", 187 | "比利时": "BE", 188 | "丹麦": "DK", 189 | "索马里": "SO", 190 | "美国": "US", 191 | "马尔代夫": "MV", 192 | "列支敦士登": "LI", 193 | "科摩罗": "KM", 194 | "百慕达群岛": "BM", 195 | "不丹": "BT", 196 | "朝鲜": "KP", 197 | "莫桑比克": "MZ", 198 | "印度尼西亚, 印尼": "ID", 199 | "也门": "YE", 200 | "刚果-布拉柴维尔": "CG", 201 | "乌拉圭": "UY", 202 | "圣赫勒拿-阿森松和特里斯坦-达库尼亚": "SH", 203 | "尼泊尔": "NP", 204 | "多米尼克": "DM", 205 | "马耳他": "MT", 206 | "阿尔及利亚": "DZ", 207 | "卡塔尔": "QA", 208 | "赞比亚": "ZM", 209 | "缅甸": "MM", 210 | "卢森堡": "LU", 211 | "斐济": "FJ", 212 | "西班牙": "ES", 213 | "乌干达": "UG", 214 | "中国": "CN", 215 | "中国香港": "HK", 216 | "中国台湾": "TW", 217 | "china": "CN" 218 | } 219 | -------------------------------------------------------------------------------- /app/resources/icons/annual_report1.svg: -------------------------------------------------------------------------------- 1 | 3 | 5 | 6 | 8 | 10 | 12 | 14 | 16 | 17 | 19 | 20 | 22 | 23 | 25 | 26 | 28 | 29 | 31 | 33 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /resource/render/engine.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | try: 4 | from collections.abc import Iterable 5 | except ImportError: 6 | from collections import Iterable 7 | 8 | from jinja2 import Environment 9 | 10 | from ..commons import utils 11 | from ..datasets import EXTRA, FILENAMES 12 | from ..globals import CurrentConfig, NotebookType 13 | from ..types import Any, Optional 14 | from .display import HTML, Javascript 15 | 16 | 17 | def write_utf8_html_file(file_name: str, html_content: str): 18 | with open(file_name, "w+", encoding="utf-8") as html_file: 19 | html_file.write(html_content) 20 | 21 | 22 | class RenderEngine: 23 | def __init__(self, env: Optional[Environment] = None): 24 | self.env = env or CurrentConfig.GLOBAL_ENV 25 | 26 | @staticmethod 27 | def generate_js_link(chart: Any) -> Any: 28 | if not chart.js_host: 29 | chart.js_host = CurrentConfig.ONLINE_HOST 30 | links = [] 31 | for dep in chart.js_dependencies.items: 32 | # TODO: if? 33 | if dep.startswith("https://api.map.baidu.com"): 34 | links.append(dep) 35 | if dep in FILENAMES: 36 | f, ext = FILENAMES[dep] 37 | links.append("{}{}.{}".format(chart.js_host, f, ext)) 38 | else: 39 | for url, files in EXTRA.items(): 40 | if dep in files: 41 | f, ext = files[dep] 42 | links.append("{}{}.{}".format(url, f, ext)) 43 | break 44 | chart.dependencies = links 45 | return chart 46 | 47 | def render_chart_to_file(self, template_name: str, chart: Any, path: str, **kwargs): 48 | """ 49 | Render a chart or page to local html files. 50 | 51 | :param chart: A Chart or Page object 52 | :param path: The destination file which the html code write to 53 | :param template_name: The name of template file. 54 | """ 55 | tpl = self.env.get_template(template_name) 56 | html = utils.replace_placeholder( 57 | tpl.render(chart=self.generate_js_link(chart), **kwargs) 58 | ) 59 | write_utf8_html_file(path, html) 60 | 61 | def render_chart_to_template(self, template_name: str, chart: Any, **kwargs) -> str: 62 | tpl = self.env.get_template(template_name) 63 | return utils.replace_placeholder( 64 | tpl.render(chart=self.generate_js_link(chart), **kwargs) 65 | ) 66 | 67 | def render_chart_to_notebook(self, template_name: str, **kwargs) -> str: 68 | tpl = self.env.get_template(template_name) 69 | return utils.replace_placeholder(tpl.render(**kwargs)) 70 | 71 | 72 | def render( 73 | chart, path: str, template_name: str, env: Optional[Environment], **kwargs 74 | ) -> str: 75 | RenderEngine(env).render_chart_to_file( 76 | template_name=template_name, chart=chart, path=path, **kwargs 77 | ) 78 | return os.path.abspath(path) 79 | 80 | 81 | def render_embed( 82 | chart, template_name: str, env: Optional[Environment], **kwargs 83 | ) -> str: 84 | return RenderEngine(env).render_chart_to_template( 85 | template_name=template_name, chart=chart, **kwargs 86 | ) 87 | 88 | 89 | def render_notebook(self, notebook_template, lab_template): 90 | instance = self if isinstance(self, Iterable) else (self,) 91 | if CurrentConfig.NOTEBOOK_TYPE == NotebookType.JUPYTER_NOTEBOOK: 92 | require_config = utils.produce_require_dict(self.js_dependencies, self.js_host) 93 | return HTML( 94 | RenderEngine().render_chart_to_notebook( 95 | template_name=notebook_template, 96 | charts=instance, 97 | config_items=require_config["config_items"], 98 | libraries=require_config["libraries"], 99 | ) 100 | ) 101 | 102 | if CurrentConfig.NOTEBOOK_TYPE == NotebookType.JUPYTER_LAB: 103 | return HTML( 104 | RenderEngine().render_chart_to_notebook( 105 | template_name=lab_template, charts=instance 106 | ) 107 | ) 108 | 109 | if CurrentConfig.NOTEBOOK_TYPE == NotebookType.NTERACT: 110 | return HTML(self.render_embed()) 111 | 112 | if CurrentConfig.NOTEBOOK_TYPE == NotebookType.ZEPPELIN: 113 | print("%html " + self.render_embed()) 114 | 115 | 116 | def load_javascript(chart): 117 | scripts = [] 118 | for dep in chart.js_dependencies.items: 119 | f, ext = FILENAMES[dep] 120 | scripts.append("{}{}.{}".format(CurrentConfig.ONLINE_HOST, f, ext)) 121 | return Javascript(lib=scripts) 122 | -------------------------------------------------------------------------------- /app/Ui/contact/contactInfoUi.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Form implementation generated from reading ui file 'contactInfoUi.ui' 4 | # 5 | # Created by: PyQt5 UI code generator 5.15.7 6 | # 7 | # WARNING: Any manual changes made to this file will be lost when pyuic5 is 8 | # run again. Do not edit this file unless you know what you are doing. 9 | 10 | 11 | from PyQt5 import QtCore, QtGui, QtWidgets 12 | 13 | 14 | class Ui_Form(object): 15 | def setupUi(self, Form): 16 | Form.setObjectName("Form") 17 | Form.resize(817, 748) 18 | self.horizontalLayout = QtWidgets.QHBoxLayout(Form) 19 | self.horizontalLayout.setContentsMargins(0, 0, 0, 0) 20 | self.horizontalLayout.setSpacing(0) 21 | self.horizontalLayout.setObjectName("horizontalLayout") 22 | self.frame = QtWidgets.QFrame(Form) 23 | self.frame.setFrameShape(QtWidgets.QFrame.NoFrame) 24 | self.frame.setFrameShadow(QtWidgets.QFrame.Raised) 25 | self.frame.setObjectName("frame") 26 | self.verticalLayout = QtWidgets.QVBoxLayout(self.frame) 27 | self.verticalLayout.setContentsMargins(0, 0, 0, 0) 28 | self.verticalLayout.setSpacing(0) 29 | self.verticalLayout.setObjectName("verticalLayout") 30 | self.horizontalLayout_3 = QtWidgets.QHBoxLayout() 31 | self.horizontalLayout_3.setSpacing(0) 32 | self.horizontalLayout_3.setObjectName("horizontalLayout_3") 33 | self.label_remark = QtWidgets.QLabel(self.frame) 34 | self.label_remark.setMaximumSize(QtCore.QSize(16777215, 100)) 35 | font = QtGui.QFont() 36 | font.setPointSize(12) 37 | self.label_remark.setFont(font) 38 | self.label_remark.setText("") 39 | self.label_remark.setObjectName("label_remark") 40 | self.horizontalLayout_3.addWidget(self.label_remark) 41 | self.btn_analysis = QtWidgets.QPushButton(self.frame) 42 | self.btn_analysis.setStyleSheet("") 43 | self.btn_analysis.setFlat(True) 44 | self.btn_analysis.setObjectName("btn_analysis") 45 | self.horizontalLayout_3.addWidget(self.btn_analysis) 46 | self.btn_emotion = QtWidgets.QPushButton(self.frame) 47 | self.btn_emotion.setFlat(True) 48 | self.btn_emotion.setObjectName("btn_emotion") 49 | self.horizontalLayout_3.addWidget(self.btn_emotion) 50 | self.btn_report = QtWidgets.QPushButton(self.frame) 51 | self.btn_report.setFlat(True) 52 | self.btn_report.setObjectName("btn_report") 53 | self.horizontalLayout_3.addWidget(self.btn_report) 54 | self.btn_back = QtWidgets.QPushButton(self.frame) 55 | self.btn_back.setFlat(True) 56 | self.btn_back.setObjectName("btn_back") 57 | self.horizontalLayout_3.addWidget(self.btn_back) 58 | self.toolButton_output = QtWidgets.QToolButton(self.frame) 59 | icon = QtGui.QIcon() 60 | icon.addPixmap(QtGui.QPixmap("../../data/icons/output.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off) 61 | self.toolButton_output.setIcon(icon) 62 | self.toolButton_output.setCheckable(False) 63 | self.toolButton_output.setPopupMode(QtWidgets.QToolButton.MenuButtonPopup) 64 | self.toolButton_output.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon) 65 | self.toolButton_output.setAutoRaise(True) 66 | self.toolButton_output.setArrowType(QtCore.Qt.NoArrow) 67 | self.toolButton_output.setObjectName("toolButton_output") 68 | self.horizontalLayout_3.addWidget(self.toolButton_output) 69 | self.verticalLayout.addLayout(self.horizontalLayout_3) 70 | self.stackedWidget = QtWidgets.QStackedWidget(self.frame) 71 | self.stackedWidget.setObjectName("stackedWidget") 72 | self.page_3 = QtWidgets.QWidget() 73 | self.page_3.setObjectName("page_3") 74 | self.stackedWidget.addWidget(self.page_3) 75 | self.page_4 = QtWidgets.QWidget() 76 | self.page_4.setObjectName("page_4") 77 | self.stackedWidget.addWidget(self.page_4) 78 | self.verticalLayout.addWidget(self.stackedWidget) 79 | self.horizontalLayout.addWidget(self.frame) 80 | 81 | self.retranslateUi(Form) 82 | self.stackedWidget.setCurrentIndex(1) 83 | QtCore.QMetaObject.connectSlotsByName(Form) 84 | 85 | def retranslateUi(self, Form): 86 | _translate = QtCore.QCoreApplication.translate 87 | Form.setWindowTitle(_translate("Form", "Form")) 88 | self.btn_analysis.setText(_translate("Form", "统计信息")) 89 | self.btn_emotion.setText(_translate("Form", "情感分析")) 90 | self.btn_report.setText(_translate("Form", "年度报告")) 91 | self.btn_back.setText(_translate("Form", "退出")) 92 | self.toolButton_output.setText(_translate("Form", "导出聊天记录")) 93 | -------------------------------------------------------------------------------- /app/ui_pc/contact/contactInfoUi.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Form implementation generated from reading ui file 'contactInfoUi.ui' 4 | # 5 | # Created by: PyQt5 UI code generator 5.15.7 6 | # 7 | # WARNING: Any manual changes made to this file will be lost when pyuic5 is 8 | # run again. Do not edit this file unless you know what you are doing. 9 | 10 | 11 | from PyQt5 import QtCore, QtGui, QtWidgets 12 | 13 | 14 | class Ui_Form(object): 15 | def setupUi(self, Form): 16 | Form.setObjectName("Form") 17 | Form.resize(817, 748) 18 | self.horizontalLayout = QtWidgets.QHBoxLayout(Form) 19 | self.horizontalLayout.setContentsMargins(0, 0, 0, 0) 20 | self.horizontalLayout.setSpacing(0) 21 | self.horizontalLayout.setObjectName("horizontalLayout") 22 | self.frame = QtWidgets.QFrame(Form) 23 | self.frame.setFrameShape(QtWidgets.QFrame.NoFrame) 24 | self.frame.setFrameShadow(QtWidgets.QFrame.Raised) 25 | self.frame.setObjectName("frame") 26 | self.verticalLayout = QtWidgets.QVBoxLayout(self.frame) 27 | self.verticalLayout.setContentsMargins(0, 0, 0, 0) 28 | self.verticalLayout.setSpacing(0) 29 | self.verticalLayout.setObjectName("verticalLayout") 30 | self.horizontalLayout_3 = QtWidgets.QHBoxLayout() 31 | self.horizontalLayout_3.setSpacing(0) 32 | self.horizontalLayout_3.setObjectName("horizontalLayout_3") 33 | self.label_remark = QtWidgets.QLabel(self.frame) 34 | self.label_remark.setMaximumSize(QtCore.QSize(16777215, 100)) 35 | font = QtGui.QFont() 36 | font.setPointSize(12) 37 | self.label_remark.setFont(font) 38 | self.label_remark.setText("") 39 | self.label_remark.setObjectName("label_remark") 40 | self.horizontalLayout_3.addWidget(self.label_remark) 41 | self.btn_analysis = QtWidgets.QPushButton(self.frame) 42 | self.btn_analysis.setStyleSheet("") 43 | self.btn_analysis.setFlat(True) 44 | self.btn_analysis.setObjectName("btn_analysis") 45 | self.horizontalLayout_3.addWidget(self.btn_analysis) 46 | self.btn_emotion = QtWidgets.QPushButton(self.frame) 47 | self.btn_emotion.setFlat(True) 48 | self.btn_emotion.setObjectName("btn_emotion") 49 | self.horizontalLayout_3.addWidget(self.btn_emotion) 50 | self.btn_report = QtWidgets.QPushButton(self.frame) 51 | self.btn_report.setFlat(True) 52 | self.btn_report.setObjectName("btn_report") 53 | self.horizontalLayout_3.addWidget(self.btn_report) 54 | self.btn_back = QtWidgets.QPushButton(self.frame) 55 | self.btn_back.setFlat(True) 56 | self.btn_back.setObjectName("btn_back") 57 | self.horizontalLayout_3.addWidget(self.btn_back) 58 | self.toolButton_output = QtWidgets.QToolButton(self.frame) 59 | icon = QtGui.QIcon() 60 | icon.addPixmap(QtGui.QPixmap("../../data/icons/output.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off) 61 | self.toolButton_output.setIcon(icon) 62 | self.toolButton_output.setCheckable(False) 63 | self.toolButton_output.setPopupMode(QtWidgets.QToolButton.MenuButtonPopup) 64 | self.toolButton_output.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon) 65 | self.toolButton_output.setAutoRaise(True) 66 | self.toolButton_output.setArrowType(QtCore.Qt.NoArrow) 67 | self.toolButton_output.setObjectName("toolButton_output") 68 | self.horizontalLayout_3.addWidget(self.toolButton_output) 69 | self.verticalLayout.addLayout(self.horizontalLayout_3) 70 | self.stackedWidget = QtWidgets.QStackedWidget(self.frame) 71 | self.stackedWidget.setObjectName("stackedWidget") 72 | self.page_3 = QtWidgets.QWidget() 73 | self.page_3.setObjectName("page_3") 74 | self.stackedWidget.addWidget(self.page_3) 75 | self.page_4 = QtWidgets.QWidget() 76 | self.page_4.setObjectName("page_4") 77 | self.stackedWidget.addWidget(self.page_4) 78 | self.verticalLayout.addWidget(self.stackedWidget) 79 | self.horizontalLayout.addWidget(self.frame) 80 | 81 | self.retranslateUi(Form) 82 | self.stackedWidget.setCurrentIndex(1) 83 | QtCore.QMetaObject.connectSlotsByName(Form) 84 | 85 | def retranslateUi(self, Form): 86 | _translate = QtCore.QCoreApplication.translate 87 | Form.setWindowTitle(_translate("Form", "Form")) 88 | self.btn_analysis.setText(_translate("Form", "统计信息")) 89 | self.btn_emotion.setText(_translate("Form", "情感分析")) 90 | self.btn_report.setText(_translate("Form", "年度报告")) 91 | self.btn_back.setText(_translate("Form", "退出")) 92 | self.toolButton_output.setText(_translate("Form", "导出聊天记录")) 93 | -------------------------------------------------------------------------------- /app/components/Button_Contact.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from PyQt5 import QtWidgets, QtGui, QtCore 4 | from PyQt5.QtCore import * 5 | 6 | import app.DataBase.data as data 7 | from app import person 8 | 9 | 10 | class ContactUi(QtWidgets.QPushButton): 11 | """ 12 | 联系人类,继承自pyqt的按钮,里面封装了联系人头像等标签 13 | """ 14 | usernameSingal = pyqtSignal(str) 15 | 16 | def __init__(self, Ui, id=None, rconversation=None): 17 | super(ContactUi, self).__init__(Ui) 18 | self.contact: person.Contact = person.Contact(rconversation[1]) 19 | self.init_ui(Ui) 20 | self.msgCount = rconversation[0] 21 | self.username = rconversation[1] 22 | self.conversationTime = rconversation[6] 23 | self.msgType = rconversation[7] 24 | self.digest = rconversation[8] 25 | hasTrunc = rconversation[10] 26 | attrflag = rconversation[11] 27 | if hasTrunc == 0: 28 | if attrflag == 0: 29 | self.digest = '[动画表情]' 30 | elif attrflag == 67108864: 31 | try: 32 | remark = data.get_conRemark(rconversation[9]) 33 | msg = self.digest.split(':')[1].strip('\n').strip() 34 | self.digest = f'{remark}:{msg}' 35 | except Exception as e: 36 | pass 37 | else: 38 | pass 39 | self.show_info(id) 40 | 41 | def init_ui(self, Ui): 42 | self.layoutWidget = QtWidgets.QWidget(Ui) 43 | self.layoutWidget.setObjectName("layoutWidget") 44 | self.gridLayout1 = QtWidgets.QGridLayout(self.layoutWidget) 45 | self.gridLayout1.setSizeConstraint(QtWidgets.QLayout.SetMaximumSize) 46 | self.gridLayout1.setContentsMargins(10, 10, 10, 10) 47 | self.gridLayout1.setSpacing(10) 48 | self.gridLayout1.setObjectName("gridLayout1") 49 | self.label_time = QtWidgets.QLabel(self.layoutWidget) 50 | font = QtGui.QFont() 51 | font.setFamily("微软雅黑") 52 | font.setPointSize(8) 53 | self.label_time.setFont(font) 54 | self.label_time.setLayoutDirection(QtCore.Qt.RightToLeft) 55 | self.label_time.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignTrailing | QtCore.Qt.AlignVCenter) 56 | self.label_time.setObjectName("label_time") 57 | self.gridLayout1.addWidget(self.label_time, 0, 2, 1, 1) 58 | self.label_remark = QtWidgets.QLabel(self.layoutWidget) 59 | font = QtGui.QFont() 60 | font.setFamily("黑体") 61 | font.setPointSize(10) 62 | # font.setBold(True) 63 | self.label_remark.setFont(font) 64 | self.label_remark.setObjectName("label_remark") 65 | self.gridLayout1.addWidget(self.label_remark, 0, 1, 1, 1) 66 | self.label_msg = QtWidgets.QLabel(self.layoutWidget) 67 | font = QtGui.QFont() 68 | font.setFamily("微软雅黑") 69 | font.setPointSize(8) 70 | self.label_msg.setFont(font) 71 | self.label_msg.setObjectName("label_msg") 72 | self.gridLayout1.addWidget(self.label_msg, 1, 1, 1, 2) 73 | self.label_avatar = QtWidgets.QLabel(self.layoutWidget) 74 | self.label_avatar.setMinimumSize(QtCore.QSize(60, 60)) 75 | self.label_avatar.setMaximumSize(QtCore.QSize(60, 60)) 76 | self.label_avatar.setLayoutDirection(QtCore.Qt.RightToLeft) 77 | self.label_avatar.setAutoFillBackground(False) 78 | self.label_avatar.setStyleSheet("background-color: #ffffff;") 79 | self.label_avatar.setInputMethodHints(QtCore.Qt.ImhNone) 80 | self.label_avatar.setFrameShape(QtWidgets.QFrame.NoFrame) 81 | self.label_avatar.setFrameShadow(QtWidgets.QFrame.Plain) 82 | self.label_avatar.setAlignment(QtCore.Qt.AlignLeading | QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter) 83 | self.label_avatar.setObjectName("label_avatar") 84 | self.gridLayout1.addWidget(self.label_avatar, 0, 0, 2, 1) 85 | self.gridLayout1.setColumnStretch(0, 1) 86 | self.gridLayout1.setColumnStretch(1, 6) 87 | self.gridLayout1.setRowStretch(0, 5) 88 | self.gridLayout1.setRowStretch(1, 3) 89 | self.setLayout(self.gridLayout1) 90 | self.setStyleSheet( 91 | "QPushButton {background-color: rgb(220,220,220);}" 92 | "QPushButton:hover{background-color: rgb(208,208,208);}\n" 93 | ) 94 | 95 | def show_info(self, id): 96 | time = datetime.now().strftime("%m-%d %H:%M") 97 | msg = '还没说话' 98 | self.label_avatar.setPixmap(self.contact.avatar) # 在label上显示图片 99 | self.label_remark.setText(self.contact.conRemark) 100 | self.label_msg.setText(self.digest) 101 | self.label_time.setText(data.timestamp2str(self.conversationTime)[2:]) 102 | 103 | def show_msg(self): 104 | self.usernameSingal.emit(self.username) 105 | -------------------------------------------------------------------------------- /app/decrypt/decrypt.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | import hmac 3 | import os 4 | from typing import Union, List 5 | 6 | from Cryptodome.Cipher import AES 7 | 8 | from app.log import log 9 | 10 | # from Crypto.Cipher import AES # 如果上面的导入失败,可以尝试使用这个 11 | 12 | SQLITE_FILE_HEADER = "SQLite format 3\x00" # SQLite文件头 13 | 14 | KEY_SIZE = 32 15 | DEFAULT_PAGESIZE = 4096 16 | DEFAULT_ITER = 64000 17 | 18 | 19 | # 通过密钥解密数据库 20 | @log 21 | def decrypt(key: str, db_path, out_path): 22 | if not os.path.exists(db_path): 23 | return f"[-] db_path:'{db_path}' File not found!" 24 | if not os.path.exists(os.path.dirname(out_path)): 25 | return f"[-] out_path:'{out_path}' File not found!" 26 | if len(key) != 64: 27 | return f"[-] key:'{key}' Error!" 28 | password = bytes.fromhex(key.strip()) 29 | with open(db_path, "rb") as file: 30 | blist = file.read() 31 | 32 | salt = blist[:16] 33 | byteKey = hashlib.pbkdf2_hmac("sha1", password, salt, DEFAULT_ITER, KEY_SIZE) 34 | first = blist[16:DEFAULT_PAGESIZE] 35 | 36 | mac_salt = bytes([(salt[i] ^ 58) for i in range(16)]) 37 | mac_key = hashlib.pbkdf2_hmac("sha1", byteKey, mac_salt, 2, KEY_SIZE) 38 | hash_mac = hmac.new(mac_key, first[:-32], hashlib.sha1) 39 | hash_mac.update(b'\x01\x00\x00\x00') 40 | 41 | if hash_mac.digest() != first[-32:-12]: 42 | return f"[-] Password Error! (key:'{key}'; db_path:'{db_path}'; out_path:'{out_path}' )" 43 | 44 | newblist = [blist[i:i + DEFAULT_PAGESIZE] for i in range(DEFAULT_PAGESIZE, len(blist), DEFAULT_PAGESIZE)] 45 | 46 | with open(out_path, "wb") as deFile: 47 | deFile.write(SQLITE_FILE_HEADER.encode()) 48 | t = AES.new(byteKey, AES.MODE_CBC, first[-48:-32]) 49 | decrypted = t.decrypt(first[:-48]) 50 | deFile.write(decrypted) 51 | deFile.write(first[-48:]) 52 | 53 | for i in newblist: 54 | t = AES.new(byteKey, AES.MODE_CBC, i[-48:-32]) 55 | decrypted = t.decrypt(i[:-48]) 56 | deFile.write(decrypted) 57 | deFile.write(i[-48:]) 58 | return [True, db_path, out_path, key] 59 | 60 | 61 | @log 62 | def batch_decrypt(key: str, db_path: Union[str, List[str]], out_path: str): 63 | if not isinstance(key, str) or not isinstance(out_path, str) or not os.path.exists(out_path) or len(key) != 64: 64 | return f"[-] (key:'{key}' or out_path:'{out_path}') Error!" 65 | 66 | process_list = [] 67 | 68 | if isinstance(db_path, str): 69 | if not os.path.exists(db_path): 70 | return f"[-] db_path:'{db_path}' not found!" 71 | 72 | if os.path.isfile(db_path): 73 | inpath = db_path 74 | outpath = os.path.join(out_path, 'de_' + os.path.basename(db_path)) 75 | process_list.append([key, inpath, outpath]) 76 | 77 | elif os.path.isdir(db_path): 78 | for root, dirs, files in os.walk(db_path): 79 | for file in files: 80 | inpath = os.path.join(root, file) 81 | rel = os.path.relpath(root, db_path) 82 | outpath = os.path.join(out_path, rel, 'de_' + file) 83 | 84 | if not os.path.exists(os.path.dirname(outpath)): 85 | os.makedirs(os.path.dirname(outpath)) 86 | process_list.append([key, inpath, outpath]) 87 | else: 88 | return f"[-] db_path:'{db_path}' Error " 89 | elif isinstance(db_path, list): 90 | rt_path = os.path.commonprefix(db_path) 91 | if not os.path.exists(rt_path): 92 | rt_path = os.path.dirname(rt_path) 93 | 94 | for inpath in db_path: 95 | if not os.path.exists(inpath): 96 | return f"[-] db_path:'{db_path}' not found!" 97 | 98 | inpath = os.path.normpath(inpath) 99 | rel = os.path.relpath(os.path.dirname(inpath), rt_path) 100 | outpath = os.path.join(out_path, rel, 'de_' + os.path.basename(inpath)) 101 | if not os.path.exists(os.path.dirname(outpath)): 102 | os.makedirs(os.path.dirname(outpath)) 103 | process_list.append([key, inpath, outpath]) 104 | else: 105 | return f"[-] db_path:'{db_path}' Error " 106 | 107 | result = [] 108 | for i in process_list: 109 | result.append(decrypt(*i)) # 解密 110 | 111 | # 删除空文件夹 112 | for root, dirs, files in os.walk(out_path, topdown=False): 113 | for dir in dirs: 114 | if not os.listdir(os.path.join(root, dir)): 115 | os.rmdir(os.path.join(root, dir)) 116 | return result 117 | 118 | 119 | if __name__ == '__main__': 120 | # 调用 decrypt 函数,并传入参数 121 | key = "2aafab10af7940328bb92ac9d2a8ab5fc07a685646b14f2e9ae6948a7060c0fc" 122 | db_path = "E:\86390\Documents\WeChat Files\wxid_27hqbq7vx5hf22\FileStorage\CustomEmotion\\71\\71CE49ED3CE9E57E43E07F802983BF45" 123 | out_path = "./test/1.png" 124 | print(decrypt(key, db_path, out_path)) 125 | -------------------------------------------------------------------------------- /app/ui_pc/contact/contactInfo.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtCore import * 2 | from PyQt5.QtWidgets import * 3 | 4 | from app.DataBase.output_pc import Output 5 | from app.ui_pc.Icon import Icon 6 | from .contactInfoUi import Ui_Form 7 | from .userinfo import userinfo 8 | 9 | 10 | class ContactInfo(QWidget, Ui_Form): 11 | exitSignal = pyqtSignal() 12 | urlSignal = pyqtSignal(QUrl) 13 | 14 | # username = '' 15 | def __init__(self, contact, parent=None): 16 | super(ContactInfo, self).__init__(parent) 17 | self.setupUi(self) 18 | self.contact = contact 19 | self.view_userinfo = userinfo.UserinfoController(self.contact) 20 | self.btn_back.clicked.connect(self.back) 21 | self.init_ui() 22 | 23 | def init_ui(self): 24 | self.btn_back.setIcon(Icon.Back) 25 | self.btn_report.setIcon(Icon.Annual_Report_Icon) 26 | self.btn_analysis.setIcon(Icon.Analysis_Icon) 27 | self.btn_emotion.setIcon(Icon.Emotion_Icon) 28 | self.label_remark.setText(self.contact.remark) 29 | self.stackedWidget.addWidget(self.view_userinfo) 30 | self.stackedWidget.setCurrentWidget(self.view_userinfo) 31 | menu = QMenu(self) 32 | self.toDocxAct = QAction(Icon.ToDocx, '导出Docx', self) 33 | self.toCSVAct = QAction(Icon.ToCSV, '导出CSV', self) 34 | self.toHtmlAct = QAction(Icon.ToHTML, '导出HTML', self) 35 | self.toolButton_output.setPopupMode(QToolButton.MenuButtonPopup) 36 | self.toolButton_output.clicked.connect(self.toolButton_show) 37 | menu.addAction(self.toDocxAct) 38 | menu.addAction(self.toCSVAct) 39 | menu.addAction(self.toHtmlAct) 40 | self.toolButton_output.setMenu(menu) 41 | self.toolButton_output.setIcon(Icon.Output) 42 | # self.toolButton_output.addSeparator() 43 | self.toHtmlAct.triggered.connect(self.output) 44 | self.toDocxAct.triggered.connect(self.output) 45 | self.toCSVAct.triggered.connect(self.output) 46 | 47 | def toolButton_show(self): 48 | self.toolButton_output.showMenu() 49 | 50 | def analysis(self): 51 | self.stackedWidget.setCurrentWidget(self.view_analysis) 52 | if 'room' in self.contact.wxid: 53 | QMessageBox.warning( 54 | self, '警告', 55 | '暂不支持群组' 56 | ) 57 | return 58 | self.view_analysis.start() 59 | 60 | def annual_report(self): 61 | QMessageBox.warning( 62 | self, 63 | "提示", 64 | "敬请期待" 65 | ) 66 | return 67 | # self.report = report.ReportController(self.contact) 68 | # self.report.show() 69 | 70 | def emotionale_Analysis(self): 71 | self.stackedWidget.setCurrentWidget(self.view_emotion) 72 | if 'room' in self.contact.wxid: 73 | QMessageBox.warning( 74 | self, '警告', 75 | '暂不支持群组' 76 | ) 77 | return 78 | self.view_emotion.start() 79 | 80 | def back(self): 81 | """ 82 | 将userinfo界面设置为可见,其他界面设置为不可见 83 | """ 84 | self.stackedWidget.setCurrentWidget(self.view_userinfo) 85 | 86 | def output(self): 87 | """ 88 | 导出聊天记录 89 | :return: 90 | """ 91 | self.stackedWidget.setCurrentWidget(self.view_userinfo) 92 | if self.sender() == self.toDocxAct: 93 | print('功能暂未实现') 94 | QMessageBox.warning(self, 95 | "别急别急", 96 | "马上就实现该功能" 97 | ) 98 | return 99 | self.outputThread = Output(self.Me, self.contact.wxid) 100 | elif self.sender() == self.toCSVAct: 101 | self.outputThread = Output(self.contact, type_=Output.CSV) 102 | elif self.sender() == self.toHtmlAct: 103 | self.outputThread = Output(self.contact, type_=Output.HTML) 104 | 105 | self.outputThread.progressSignal.connect(self.output_progress) 106 | self.outputThread.rangeSignal.connect(self.set_progressBar_range) 107 | self.outputThread.okSignal.connect(self.hide_progress_bar) 108 | self.outputThread.start() 109 | 110 | def hide_progress_bar(self, int): 111 | reply = QMessageBox(self) 112 | reply.setIcon(QMessageBox.Information) 113 | reply.setWindowTitle('OK') 114 | reply.setText(f"导出聊天记录成功\n在.\\data\\目录下") 115 | reply.addButton("确认", QMessageBox.AcceptRole) 116 | reply.addButton("取消", QMessageBox.RejectRole) 117 | api = reply.exec_() 118 | self.view_userinfo.progressBar.setVisible(False) 119 | 120 | def output_progress(self, value): 121 | self.view_userinfo.progressBar.setProperty('value', value) 122 | 123 | def set_progressBar_range(self, value): 124 | print('进度条范围', value) 125 | self.view_userinfo.progressBar.setVisible(True) 126 | self.view_userinfo.progressBar.setRange(0, value) 127 | -------------------------------------------------------------------------------- /resource/datasets/__init__.py: -------------------------------------------------------------------------------- 1 | import difflib 2 | import os 3 | import typing 4 | import urllib.request 5 | 6 | import simplejson as json 7 | 8 | 9 | class FuzzyDict(dict): 10 | """Provides a dictionary that performs fuzzy lookup""" 11 | 12 | def __init__(self, cutoff: float = 0.6): 13 | """Construct a new FuzzyDict instance 14 | 15 | items is an dictionary to copy items from (optional) 16 | cutoff is the match ratio below which matches should not be considered 17 | cutoff needs to be a float between 0 and 1 (where zero is no match 18 | and 1 is a perfect match)""" 19 | super(FuzzyDict, self).__init__() 20 | self.cutoff = cutoff 21 | 22 | # short wrapper around some super (dict) methods 23 | self._dict_contains = lambda key: super(FuzzyDict, self).__contains__(key) 24 | self._dict_getitem = lambda key: super(FuzzyDict, self).__getitem__(key) 25 | 26 | def _search(self, lookfor: typing.Any, stop_on_first: bool = False): 27 | """Returns the value whose key best matches lookfor 28 | 29 | if stop_on_first is True then the method returns as soon 30 | as it finds the first item 31 | """ 32 | 33 | # if the item is in the dictionary then just return it 34 | if self._dict_contains(lookfor): 35 | return True, lookfor, self._dict_getitem(lookfor), 1 36 | 37 | # set up the fuzzy matching tool 38 | ratio_calc = difflib.SequenceMatcher() 39 | ratio_calc.set_seq1(lookfor) 40 | 41 | # test each key in the dictionary 42 | best_ratio = 0 43 | best_match = None 44 | best_key = None 45 | for key in self: 46 | # if the current key is not a string 47 | # then we just skip it 48 | try: 49 | # set up the SequenceMatcher with other text 50 | ratio_calc.set_seq2(key) 51 | except TypeError: 52 | continue 53 | 54 | # we get an error here if the item to look for is not a 55 | # string - if it cannot be fuzzy matched and we are here 56 | # this it is definitely not in the dictionary 57 | try: 58 | # calculate the match value 59 | ratio = ratio_calc.ratio() 60 | except TypeError: 61 | break 62 | 63 | # if this is the best ratio so far - save it and the value 64 | if ratio > best_ratio: 65 | best_ratio = ratio 66 | best_key = key 67 | best_match = self._dict_getitem(key) 68 | 69 | if stop_on_first and ratio >= self.cutoff: 70 | break 71 | 72 | return best_ratio >= self.cutoff, best_key, best_match, best_ratio 73 | 74 | def __contains__(self, item: typing.Any): 75 | if self._search(item, True)[0]: 76 | return True 77 | else: 78 | return False 79 | 80 | def __getitem__(self, lookfor: typing.Any): 81 | matched, key, item, ratio = self._search(lookfor) 82 | 83 | if not matched: 84 | raise KeyError( 85 | "'%s'. closest match: '%s' with ratio %.3f" 86 | % (str(lookfor), str(key), ratio) 87 | ) 88 | 89 | return item 90 | 91 | 92 | __HERE = os.path.abspath(os.path.dirname(__file__)) 93 | with open(os.path.join(__HERE, "map_filename.json"), "r", encoding="utf8") as f: 94 | FILENAMES: FuzzyDict = FuzzyDict() 95 | for k, v in json.load(f).items(): 96 | FILENAMES[k] = v 97 | 98 | with open(os.path.join(__HERE, "city_coordinates.json"), "r", encoding="utf8") as f: 99 | COORDINATES: FuzzyDict = FuzzyDict() 100 | for k, v in json.load(f).items(): 101 | COORDINATES[k] = v 102 | 103 | EXTRA = {} 104 | 105 | 106 | def register_url(asset_url: str): 107 | if asset_url: 108 | registry = asset_url + "/registry.json" 109 | try: 110 | contents = urllib.request.urlopen(registry).read() 111 | contents = json.loads(contents) 112 | except Exception as e: 113 | raise e 114 | files = {} 115 | pinyin_names = set() 116 | for name, pinyin in contents["PINYIN_MAP"].items(): 117 | file_name = contents["FILE_MAP"][pinyin] 118 | files[name] = [file_name, "js"] 119 | pinyin_names.add(pinyin) 120 | 121 | for key, file_name in contents["FILE_MAP"].items(): 122 | if key not in pinyin_names: 123 | # English names 124 | files[key] = [file_name, "js"] 125 | 126 | js_folder_name = contents["JS_FOLDER"] 127 | if js_folder_name == "/": 128 | js_file_prefix = f"{asset_url}/" 129 | else: 130 | js_file_prefix = f"{asset_url}/{js_folder_name}/" 131 | EXTRA[js_file_prefix] = files 132 | 133 | 134 | def register_files(asset_files: dict): 135 | if asset_files: 136 | FILENAMES.update(asset_files) 137 | 138 | 139 | def register_coords(coords: dict): 140 | if coords: 141 | COORDINATES.update(coords) 142 | -------------------------------------------------------------------------------- /app/util/emoji.py: -------------------------------------------------------------------------------- 1 | import os 2 | import xml.etree.ElementTree as ET 3 | 4 | import requests 5 | 6 | root_path = './data/emoji/' 7 | if not os.path.exists('./data'): 8 | os.mkdir('./data') 9 | if not os.path.exists(root_path): 10 | os.mkdir(root_path) 11 | 12 | 13 | def get_image_format(header): 14 | # 定义图片格式的 magic numbers 15 | image_formats = { 16 | b'\xFF\xD8\xFF': 'jpeg', 17 | b'\x89\x50\x4E\x47\x0D\x0A\x1A\x0A': 'png', 18 | b'\x47\x49\x46': 'gif', 19 | b'\x42\x4D': 'bmp', 20 | # 添加其他图片格式的 magic numbers 21 | } 22 | # 判断文件的图片格式 23 | for magic_number, image_format in image_formats.items(): 24 | if header.startswith(magic_number): 25 | return image_format 26 | # 如果无法识别格式,返回 None 27 | return None 28 | 29 | 30 | def parser_xml(xml_string): 31 | # Parse the XML string 32 | root = ET.fromstring(xml_string) 33 | emoji = root.find('./emoji') 34 | # Accessing attributes of the 'emoji' element 35 | fromusername = emoji.get('fromusername') 36 | tousername = emoji.get('tousername') 37 | md5 = emoji.get('md5') 38 | cdnurl = emoji.get('cdnurl') 39 | encrypturl = emoji.get('encrypturl') 40 | thumburl = emoji.get('thumburl') 41 | externurl = emoji.get('externurl') 42 | androidmd5 = emoji.get('androidmd5') 43 | width = emoji.get('width') 44 | height = emoji.get('height') 45 | return { 46 | 'width': width, 47 | 'height': height, 48 | 'cdnurl': cdnurl, 49 | 'thumburl': thumburl if thumburl else cdnurl, 50 | 'md5': md5 if md5 else androidmd5, 51 | } 52 | 53 | 54 | def download(url, output_dir, name, thumb=False): 55 | if not url: 56 | return ':/icons/icons/404.png' 57 | resp = requests.get(url) 58 | byte = resp.content 59 | image_format = get_image_format(byte[:8]) 60 | if image_format: 61 | if thumb: 62 | output_path = os.path.join(output_dir, 'th_' + name + '.' + image_format) 63 | else: 64 | output_path = os.path.join(output_dir, name + '.' + image_format) 65 | else: 66 | output_path = os.path.join(output_dir, name) 67 | with open(output_path, 'wb') as f: 68 | f.write(resp.content) 69 | return output_path 70 | 71 | 72 | def get_emoji(xml_string, thumb=True) -> str: 73 | emoji_info = parser_xml(xml_string) 74 | md5 = emoji_info['md5'] 75 | image_format = ['.png', '.gif', '.jpeg'] 76 | for f in image_format: 77 | prefix = 'th_' if thumb else '' 78 | file_path = os.path.join(root_path, prefix + md5 + f) 79 | if os.path.exists(file_path): 80 | return file_path 81 | url = emoji_info['thumburl'] if thumb else emoji_info['cdnurl'] 82 | print("下载表情包ing:", url) 83 | return download(url, root_path, md5, thumb) 84 | 85 | 86 | if __name__ == '__main__': 87 | xml_string = ' ' 88 | res1 = parser_xml(xml_string) 89 | print(res1, res1['md5']) 90 | # download(res1['cdnurl'], "./data/emoji/", res1['md5']) 91 | # download(res1['thumburl'], "./data/emoji/", res1['md5'], True) 92 | print(get_emoji(xml_string, True)) 93 | print(get_emoji(xml_string, False)) 94 | # http://vweixinf.tc.qq.com/110/20403/stodownload?m=3a4d439aba02dce4834b2c54e9f15597&filekey=3043020101042f302d02016e0402534804203361346434333961626130326463653438333462326335346539663135353937020213f0040d00000004627466730000000131&hy=SH&storeid=323032313037323030373236313130303039653236646365316535316534383236386234306230303030303036653033303034666233&ef=3&bizid=1022 95 | -------------------------------------------------------------------------------- /app/ImageBox/ui.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from .config import * 3 | 4 | 5 | class ImageBox(QWidget): 6 | def __init__(self): 7 | super(ImageBox, self).__init__() 8 | self.img = None 9 | self.scaled_img = None 10 | self.point = QPoint(100, 100) 11 | self.start_pos = None 12 | self.end_pos = None 13 | self.left_click = False 14 | self.scale = 1 15 | 16 | def init_ui(self): 17 | self.setWindowTitle("ImageBox") 18 | 19 | def set_image(self, img_path): 20 | """ 21 | open image file 22 | :param img_path: image file path 23 | :return: 24 | """ 25 | # img = QImageReader(img_path) 26 | # img.setScaledSize(QSize(self.size().width(), self.size().height())) 27 | # img = img.read() 28 | self.img = QPixmap(img_path) 29 | # print(self.img.size(),self.img.size().width(),self.img.size().height()) 30 | self.scaled_img = self.img 31 | # print(img_size) 32 | img_size = self.scaled_img.size() 33 | x = min(500, max((1000 - img_size.width()) // 2, 0)) 34 | y = min(300, max((600 - img_size.height()) // 2 - 60, 0)) 35 | # print(x,y) 36 | self.point = QPoint(x, y) 37 | 38 | def paintEvent(self, e): 39 | """ 40 | receive paint events 41 | :param e: QPaintEvent 42 | :return: 43 | """ 44 | if self.scaled_img: 45 | painter = QPainter() 46 | painter.begin(self) 47 | painter.scale(self.scale, self.scale) 48 | painter.drawPixmap(self.point, self.scaled_img) 49 | painter.end() 50 | 51 | def wheelEvent(self, event): 52 | angle = event.angleDelta() / 8 # 返回QPoint对象,为滚轮转过的数值,单位为1/8度 53 | angleY = angle.y() 54 | # 获取当前鼠标相对于view的位置 55 | if angleY > 0: 56 | self.scale *= 1.1 57 | else: # 滚轮下滚 58 | self.scale *= 0.9 59 | self.adjustSize() 60 | self.update() 61 | 62 | def mouseMoveEvent(self, e): 63 | """ 64 | mouse move events for the widget 65 | :param e: QMouseEvent 66 | :return: 67 | """ 68 | if self.left_click: 69 | self.end_pos = e.pos() - self.start_pos 70 | self.point = self.point + self.end_pos 71 | self.start_pos = e.pos() 72 | self.repaint() 73 | 74 | def mousePressEvent(self, e): 75 | """ 76 | mouse press events for the widget 77 | :param e: QMouseEvent 78 | :return: 79 | """ 80 | if e.button() == Qt.LeftButton: 81 | self.left_click = True 82 | self.start_pos = e.pos() 83 | 84 | def mouseReleaseEvent(self, e): 85 | """ 86 | mouse release events for the widget 87 | :param e: QMouseEvent 88 | :return: 89 | """ 90 | if e.button() == Qt.LeftButton: 91 | self.left_click = False 92 | 93 | 94 | class MainDemo(QWidget): 95 | def __init__(self): 96 | super(MainDemo, self).__init__() 97 | 98 | self.setWindowTitle("Image Viewer") 99 | self.setFixedSize(1000, 600) 100 | self.setWindowIcon(QIcon('./app/data/icons/logo.svg')) 101 | self.zoom_in = QPushButton("") 102 | self.zoom_in.clicked.connect(self.large_click) 103 | self.zoom_in.setFixedSize(30, 30) 104 | in_icon = QIcon("./app/ImageBox/icons/zoom_in.jpg") 105 | self.zoom_in.setIcon(in_icon) 106 | self.zoom_in.setIconSize(QSize(30, 30)) 107 | 108 | self.zoom_out = QPushButton("") 109 | self.zoom_out.clicked.connect(self.small_click) 110 | self.zoom_out.setFixedSize(30, 30) 111 | out_icon = QIcon("./app/ImageBox/icons/zoom_out.jpg") 112 | self.zoom_out.setIcon(out_icon) 113 | self.zoom_out.setIconSize(QSize(30, 30)) 114 | 115 | w = QWidget(self) 116 | layout = QHBoxLayout() 117 | layout.addWidget(self.zoom_in) 118 | layout.addWidget(self.zoom_out) 119 | layout.setAlignment(Qt.AlignLeft) 120 | w.setLayout(layout) 121 | w.setFixedSize(550, 50) 122 | 123 | self.box = ImageBox() 124 | self.box.resize(500, 300) 125 | 126 | layout = QVBoxLayout() 127 | layout.addWidget(w) 128 | layout.addWidget(self.box) 129 | self.setLayout(layout) 130 | 131 | def open_image(self): 132 | """ 133 | select image file and open it 134 | :return: 135 | """ 136 | img_name, _ = QFileDialog.getOpenFileName(self, "Open Image File", "*.jpg;;*.png;;*.jpeg") 137 | self.box.set_image(img_name) 138 | 139 | def large_click(self): 140 | """ 141 | used to enlarge image 142 | :return: 143 | """ 144 | if self.box.scale < 2: 145 | self.box.scale += 0.1 146 | self.box.adjustSize() 147 | self.update() 148 | 149 | def small_click(self): 150 | """ 151 | used to reduce image 152 | :return: 153 | """ 154 | if self.box.scale > 0.1: 155 | self.box.scale -= 0.2 156 | self.box.adjustSize() 157 | self.update() 158 | -------------------------------------------------------------------------------- /app/Ui/decrypt/decrypt.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | @File : decrypt.py 4 | @Author : Shuaikang Zhou 5 | @Time : 2023/1/5 18:13 6 | @IDE : Pycharm 7 | @Version : Python3.10 8 | @comment : ··· 解密数据库,导出原始数据库文件 9 | """ 10 | import hashlib 11 | import time 12 | import xml.etree.ElementTree as ET 13 | 14 | from PyQt5.QtCore import * 15 | from PyQt5.QtGui import * 16 | from PyQt5.QtWidgets import * 17 | 18 | from . import decryptUi 19 | from ...DataBase import data 20 | 21 | 22 | class DecryptControl(QWidget, decryptUi.Ui_Dialog): 23 | DecryptSignal = pyqtSignal(str) 24 | registerSignal = pyqtSignal(str) 25 | 26 | def __init__(self, parent=None): 27 | super(DecryptControl, self).__init__(parent) 28 | self.setupUi(self) 29 | self.setWindowTitle('解密') 30 | self.setWindowIcon(QIcon('./app/data/icons/logo.svg')) 31 | self.btn_db.clicked.connect(self.get_db) 32 | self.btn_xml.clicked.connect(self.get_xml) 33 | self.pushButton_3.clicked.connect(self.decrypt) 34 | self.xml_path: str = None 35 | self.db_path: str = None 36 | 37 | def db_exist(self): 38 | if data.is_db_exist(): 39 | self.btnEnterClicked() 40 | self.close() 41 | 42 | def get_xml(self): 43 | self.xml_path, _ = QFileDialog.getOpenFileName(self, 'Open file', r'..', "Xml files (*.xml)") 44 | if self.xml_path: 45 | self.label_xml.setText('xml已就绪') 46 | key = self.parser_xml() 47 | self.label_key.setText(f'数据库密钥:{key}') 48 | return self.xml_path 49 | return False 50 | 51 | def get_db(self): 52 | self.db_path, _ = QFileDialog.getOpenFileName(self, 'Open file', r'..', "Database files (*.db)") 53 | if self.db_path: 54 | if ' ' in self.db_path: 55 | self.label_db.setText('数据库未就绪') 56 | QMessageBox.critical(self, "错误", "db文件路径请不要带有空格\n可以放在D:\\\\data 目录下") 57 | self.db_path = '' 58 | elif self.db_path.isascii(): 59 | self.label_db.setText('数据库已就绪') 60 | return self.db_path 61 | else: 62 | self.label_db.setText('数据库未就绪') 63 | QMessageBox.critical(self, "错误", "db文件请不要带有中文路径\n可以放在D:\\\\data 目录下") 64 | self.db_path = '' 65 | return False 66 | 67 | def decrypt(self): 68 | if not (self.xml_path and self.db_path): 69 | QMessageBox.critical(self, "错误", "请把两个文件加载进来") 70 | return 71 | key = self.parser_xml() 72 | self.label_key.setText(f'数据库密钥:{key}') 73 | self.thread1 = MyThread() 74 | self.thread1.signal.connect(self.progressBar_view) 75 | self.thread1.start() 76 | self.thread2 = DecryptThread(self.db_path, key) 77 | self.thread2.signal.connect(self.progressBar_view) 78 | self.thread2.start() 79 | 80 | def parser_xml(self): 81 | if not self.xml_path: 82 | return False 83 | pid = self.pid(self.xml_path) 84 | if not pid: 85 | return False 86 | key = self.key(pid) 87 | return key 88 | 89 | def pid(self, xml_path): 90 | tree = ET.parse(xml_path) 91 | # 根节点 92 | root = tree.getroot() 93 | # 标签名 94 | for stu in root: 95 | if stu.attrib["name"] == '_auth_uin': 96 | return stu.attrib['value'] 97 | return False 98 | 99 | def key(self, uin, IMEI='1234567890ABCDEF'): 100 | m = hashlib.md5() 101 | m.update(bytes((IMEI + uin).encode('utf-8'))) 102 | psw = m.hexdigest() 103 | return psw[:7] 104 | 105 | def btnEnterClicked(self): 106 | # print("enter clicked") 107 | # 中间可以添加处理逻辑 108 | self.DecryptSignal.emit('ok') 109 | self.close() 110 | 111 | def progressBar_view(self, value): 112 | """ 113 | 进度条显示 114 | :param value: 进度0-100 115 | :return: None 116 | """ 117 | self.progressBar.setProperty('value', value) 118 | if value == '99': 119 | QMessageBox.information(self, "温馨提示", "我知道你很急\n但你先别急") 120 | if value == '100': 121 | QMessageBox.information(self, "解密成功", "请退出该界面", 122 | QMessageBox.Yes) 123 | self.btnExitClicked() 124 | data.init_database() 125 | 126 | def btnExitClicked(self): 127 | # print("Exit clicked") 128 | self.DecryptSignal.emit('ok') 129 | self.close() 130 | 131 | 132 | class DecryptThread(QThread): 133 | signal = pyqtSignal(str) 134 | 135 | def __init__(self, db_path, key): 136 | super(DecryptThread, self).__init__() 137 | self.db_path = db_path 138 | self.key = key 139 | self.textBrowser = None 140 | 141 | def __del__(self): 142 | pass 143 | 144 | def run(self): 145 | data.decrypt(self.db_path, self.key) 146 | self.signal.emit('100') 147 | 148 | 149 | class MyThread(QThread): 150 | signal = pyqtSignal(str) 151 | 152 | def __init__(self): 153 | super(MyThread, self).__init__() 154 | 155 | def __del__(self): 156 | pass 157 | 158 | def run(self): 159 | for i in range(100): 160 | self.signal.emit(str(i)) 161 | time.sleep(0.1) 162 | --------------------------------------------------------------------------------