import os import threading import tkinter import tkinter.font import tkinter.ttk from datetime import datetime, timedelta from pathlib import Path from typing import Optional from time import sleep import tkcalendar from pywxdump import read_info from decrypter.db_decrypt import DatabaseDecrypter from decrypter.video_decrypt import VideoDecrypter from gui.auto_scroll_guide import AutoScrollGuide from gui.auto_scrolls_single_guide import AutoScrollSingleGuide from gui.tool_tip import ToolTip from entity.contact import Contact from exporter.html_exporter import HtmlExporter from gui.listbox_with_search import ListboxWithSearch class Gui: def __init__(self): self.restart_note1 = None self.restart_note2 = None self.auto_scroll_single_guide = None self.auto_scroll_guide = None self.auto_scroll_frame = None self.search_username = None self.open_search_guide_image = None self.open_search_guide = None self.auto_scroll_button_single = None self.auto_scroll_button_single_text = None self.convert_video = None self.convert_video_var = None self.html_exporter_thread = None self.confirm_button_text = None self.succeed_label_2 = None self.succeed_label = None self.download_pic_var = Optional[tkinter.IntVar] self.download_pic = None self.auto_scroll_button_text = None self.warning_label = None self.root = None self.waiting_label = None self.listbox = None self.begin_calendar = None self.end_calendar = None self.end_calendar_label = None self.begin_calendar_label = None self.confirm_button = None self.decrypt_progressbar = None self.export_progressbar = None self.next_step_button = None self.decrypter = None self.auto_scroll_button = None self.auto_scrolling_thread = None self.decrypt_note = None self.decrypt_note_text = None self.account_info = None self.video_decrypter = None self.export_dir_name = None self.exporting = False # 1: 自动滚动数据 2: 解密数据库 3: 导出 self.page_stage = 1 def run_gui(self): self.root = tkinter.Tk() self.root.geometry('650x650') self.root.title('朋友圈导出') self.waiting_label = tkinter.ttk.Label(self.root, text="正在连接微信....", font=("微软雅黑", 16), anchor='center') self.waiting_label.place(relx=0.5, rely=0.05, anchor='center') self.root.mainloop() def wechat_logged_in(self, account_info): self.account_info = account_info self.waiting_label.config(text="微信已登录") self.auto_scroll_button_text = tkinter.StringVar() self.auto_scroll_button_text.set("读取全部朋友") self.auto_scroll_button = tkinter.ttk.Button(self.root, textvariable=self.auto_scroll_button_text, command=self.open_auto_scroll_guide) self.auto_scroll_button.place(relx=0.35, rely=0.15, anchor='center') self.auto_scroll_button_single_text = tkinter.StringVar() self.auto_scroll_button_single_text.set("读取单个朋友") self.auto_scroll_button_single = tkinter.ttk.Button(self.root, textvariable=self.auto_scroll_button_single_text, command=self.switch_auto_scroll_single) self.auto_scroll_button_single.place(relx=0.655, rely=0.15, anchor='center') self.next_step_button = tkinter.ttk.Button(self.root, text="下一步", command=self.next_step) self.next_step_button.place(relx=0.65, rely=0.8) def open_auto_scroll_guide(self): if self.auto_scroll_single_guide and self.auto_scroll_single_guide.frame: self.auto_scroll_single_guide.frame.place_forget() self.auto_scroll_guide = AutoScrollGuide(self.root) self.auto_scroll_guide.frame.place(relx=0.5, rely=0.5, anchor='center') def switch_auto_scroll_single(self): if self.auto_scroll_guide and self.auto_scroll_guide.frame: self.auto_scroll_guide.frame.place_forget() self.auto_scroll_single_guide = AutoScrollSingleGuide(self.root) self.auto_scroll_single_guide.frame.place(relx=0.5, rely=0.5, anchor='center') def next_step(self): if self.page_stage == 1: if self.auto_scroll_guide and self.auto_scroll_guide.auto_thread: self.auto_scroll_guide.auto_thread.set_scrolling(False) if self.auto_scroll_single_guide and self.auto_scroll_single_guide.auto_thread: self.auto_scroll_guide.auto_thread.set_scrolling(False) self.auto_scroll_button.place_forget() self.auto_scroll_button_single.place_forget() if self.auto_scroll_guide and self.auto_scroll_guide.frame: self.auto_scroll_guide.frame.place_forget() if self.auto_scroll_single_guide and self.auto_scroll_single_guide.frame: self.auto_scroll_single_guide.frame.place_forget() self.restart_note1 = tkinter.Label(self.root, text="请关闭微信客户端", fg="red") self.restart_note1.place(relx=0.5, rely=0.2, anchor='center') self.restart_note2 = tkinter.Label(self.root, text="然后点击下一步") self.restart_note2.place(relx=0.5, rely=0.3, anchor='center') if self.page_stage == 2: self.restart_note1.place_forget() self.restart_note2.place_forget() self.waiting_label.place_forget() self.decrypter = DatabaseDecrypter(self, self.account_info.get("filePath"), self.account_info.get("key")) self.decrypt_note_text = tkinter.StringVar() self.decrypt_note_text.set("正在复制数据.....") self.decrypt_note = tkinter.Label(self.root, textvariable=self.decrypt_note_text) self.decrypt_note.place(relx=0.5, rely=0.2, anchor='center') self.decrypt_progressbar = tkinter.ttk.Progressbar(self.root) self.decrypt_progressbar.place(relx=0.5, rely=0.3, anchor='center') # 进度值最大值 self.decrypt_progressbar['maximum'] = 100 # 进度值初始值 self.decrypt_progressbar['value'] = 0 # 解密过程禁用下一步按钮 self.next_step_button.config(state=tkinter.DISABLED) self.decrypter.decrypt() if self.page_stage == 3: self.decrypt_note.place_forget() self.decrypt_progressbar.place_forget() self.init_export_page() # 不再有下一步按钮 self.next_step_button.place_forget() # 初始化视频导出器 self.video_decrypter = VideoDecrypter(self, self.account_info.get("filePath")) self.page_stage = self.page_stage + 1 def init_export_page(self): from app.DataBase import micro_msg_db contact_datas = micro_msg_db.get_contact() contacts = [] for c in contact_datas: contact = Contact(c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7], c[8], c[9], c[10], c[11]) contacts.append(contact) def validate_contact(this_contact: Contact): c_type = this_contact.type user_name: str = this_contact.userName # 不是其他号码 is_misc_account = c_type == 1 or c_type == 33 or c_type == 513 # 不是公众号 is_gh_account = user_name.startswith("gh_") # 不是聊天群 is_chatroom = user_name.endswith("@chatroom") # 不是文件传输助手 is_filehelper = this_contact.userName == "filehelper" return (not is_misc_account) and (not is_gh_account) and (not is_chatroom) and (not is_filehelper) filtered = filter(validate_contact, contacts) contacts = list(filtered) self.listbox = ListboxWithSearch(self.root, contacts) self.listbox.frame.place(relx=0.05, rely=0.03) self.begin_calendar_label = tkinter.ttk.Label(text="开始日期") self.begin_calendar_label.place(relx=0.65, rely=0.15) # 默认开始时间是100天前 current_date = datetime.now() half_year_ago = current_date - timedelta(days=100) self.begin_calendar = tkcalendar.DateEntry(master=self.root, locale="zh_CN", year=half_year_ago.year, month=half_year_ago.month, day=half_year_ago.day, maxdate=datetime.now()) self.begin_calendar.place(relx=0.65, rely=0.2) self.end_calendar_label = tkinter.ttk.Label(text="截止日期") self.end_calendar_label.place(relx=0.65, rely=0.25) self.end_calendar = tkcalendar.DateEntry(master=self.root, locale="zh_CN", maxdate=datetime.now()) self.end_calendar.place(relx=0.65, rely=0.3) self.download_pic_var = tkinter.IntVar(value=0) self.download_pic = tkinter.ttk.Checkbutton(self.root, text='下载图片', variable=self.download_pic_var) self.download_pic.place(relx=0.65, rely=0.4) ToolTip(self.download_pic, "将图片下载到电脑上,网页\n可离线查看,导出速度变慢") self.convert_video_var = tkinter.IntVar(value=0) self.convert_video = tkinter.ttk.Checkbutton(self.root, text='视频转码', variable=self.convert_video_var) self.convert_video.place(relx=0.65, rely=0.45) ToolTip(self.convert_video, "视频原始格式为H265,只支持\nChrome浏览器播放,勾选后\n将视频转码为H264,支持大\n部分浏览器,但导出速度变慢") self.confirm_button_text = tkinter.StringVar() self.confirm_button_text.set("开始导出") self.confirm_button = tkinter.ttk.Button(self.root, textvariable=self.confirm_button_text, command=self.confirm_export) self.confirm_button.place(relx=0.65, rely=0.6) # 导出成功的提示 self.succeed_label = tkinter.Label(self.root, text="导出结束") self.succeed_label_2 = tkinter.Label(self.root, text="打开文件夹", fg="#0000FF", cursor="hand2") self.succeed_label_2.bind("", self.open_target_folder) # 进度条 self.export_progressbar = tkinter.ttk.Progressbar(self.root, length=150) def confirm_export(self): if self.html_exporter_thread and not self.html_exporter_thread.stop_flag: self.html_exporter_thread.stop() else: if not self.warning_label: self.warning_label = tkinter.Label(self.root, fg="red") self.warning_label.place(relx=0.65, rely=0.55) self.warning_label.config(text="") contacts = self.listbox.get_contacts() if not contacts: self.warning_label.config(text=f"请选择至少一个联系人") return if self.begin_calendar.get_date() > self.end_calendar.get_date(): self.warning_label.config(text=f"开始时间必须小于截止时间") return self.export_progressbar.place(relx=0.64, rely=0.68) # 进度值最大值 self.export_progressbar['maximum'] = 100 # 进度值初始值 self.export_progressbar['value'] = 0 current_time = datetime.now() self.export_dir_name = current_time.strftime("%Y_%m_%d_%H%M%S") contact_map = {contact.userName: contact for contact in contacts} self.confirm_button_text.set("停止导出") self.succeed_label.place_forget() self.succeed_label_2.place_forget() # 导出线程 self.html_exporter_thread = HtmlExporter(self, self.export_dir_name, contact_map, self.begin_calendar.get_date(), self.end_calendar.get_date(), self.download_pic_var.get(), self.convert_video_var.get()) self.html_exporter_thread.start() def update_decrypt_progressbar(self, progress): self.decrypt_progressbar['value'] = progress self.root.update() def update_export_progressbar(self, progress): self.export_progressbar['value'] = progress self.root.update() def export_succeed(self): self.confirm_button_text.set("开始导出") self.succeed_label.place(relx=0.64, rely=0.75) self.succeed_label_2.place(relx=0.76, rely=0.75) def open_target_folder(self, event): folder_path = Path(f"output/{self.export_dir_name}/") # 转换为绝对路径 absolute_path = folder_path.resolve() os.startfile(absolute_path)