diff --git a/images/main.ui b/images/main.ui index cfea79d5b..bc8ad450d 100644 --- a/images/main.ui +++ b/images/main.ui @@ -6,8 +6,8 @@ 0 0 - 499 - 466 + 600 + 550 @@ -34,7 +34,7 @@ - 40 + 20 20 @@ -47,7 +47,7 @@ - 40 + 20 20 @@ -61,7 +61,7 @@ 20 - 40 + 20 @@ -74,7 +74,7 @@ 20 - 40 + 20 @@ -127,56 +127,16 @@ - - - Qt::Vertical - - - - 20 - 40 - - - - - - + - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 200 - 200 - - - - .QLabel { - image: url(:/icon/wechat.png); -} - + - - - - Qt::AlignCenter + 全选 - + Qt::Horizontal @@ -191,14 +151,109 @@ - + + + + 0 + 200 + + + + .QTableWidget { + background-color: #fff; + border: 1px solid #d9d9d9; + border-radius: 3px; +} +QTableWidget::item { + padding: 5px; +} +QTableWidget::item:selected { + background-color: #1890ff; +} +QHeaderView::section { + background-color: #f0f0f0; + padding: 5px; + border: 1px solid #d9d9d9; + font-weight: bold; +} + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + true + + + Qt::SolidLine + + + 3 + + + 150 + + + true + + + 50 + + + false + + + true + + + false + + + 30 + + + true + + + 30 + + + false + + + false + + + + 选择 + + + + + 文件路径 + + + + + 大小 + + + + + + Qt::Vertical 20 - 40 + 10 @@ -215,7 +270,7 @@ - 40 + 20 20 @@ -277,7 +332,7 @@ QProgressBar::chunk { - 40 + 20 20 @@ -286,10 +341,7 @@ QProgressBar::chunk { - - - 0 - + @@ -300,12 +352,80 @@ QProgressBar::chunk { - 100 + 20 20 + + + + + 0 + 35 + + + + .QLabel { + color: rgba(0,0,0,.65); + background-color: #fff; + border: 1px solid #d9d9d9; + border-right: 0px; + height: 24px; + font-size: 14px; + padding: 0px 10px; +} +.QLabel:hover { + color: #40a9ff; + background-color: #fff; + border: 1px solid #40a9ff; + height: 24px; + font-size: 14px +} + + + 预览 + + + Qt::AlignCenter + + + + + + + + 0 + 35 + + + + .QLabel { + color: rgba(0,0,0,.65); + background-color: #fff; + border: 1px solid #d9d9d9; + border-right: 0px; + height: 24px; + font-size: 14px; + padding: 0px 10px; +} +.QLabel:hover { + color: #40a9ff; + background-color: #fff; + border: 1px solid #40a9ff; + height: 24px; + font-size: 14px +} + + + 执行删除 + + + Qt::AlignCenter + + + @@ -327,7 +447,7 @@ QProgressBar::chunk { } - <html><head/><body><p>开始</p></body></html> + 一键清理 Qt::AlignCenter @@ -361,7 +481,7 @@ QProgressBar::chunk { } - <html><head/><body><p>设置</p></body></html> + 设置 Qt::AlignCenter @@ -395,7 +515,7 @@ QProgressBar::chunk { } - <html><head/><body><p>退出</p></body></html> + 退出 Qt::AlignCenter @@ -412,7 +532,7 @@ QProgressBar::chunk { - 100 + 20 20 @@ -428,7 +548,7 @@ QProgressBar::chunk { 20 - 40 + 10 diff --git a/main.py b/main.py index 7b4bf31a8..95ba46d8b 100644 --- a/main.py +++ b/main.py @@ -1,7 +1,7 @@ import sys from PyQt5.QtWidgets import QApplication, QMainWindow, QGraphicsDropShadowEffect, QListWidgetItem, QListView, QWidget, \ - QLabel, QHBoxLayout, QFileDialog + QLabel, QHBoxLayout, QFileDialog, QTableWidgetItem, QHeaderView from PyQt5.QtCore import Qt, QPropertyAnimation, QEasingCurve, QThread, pyqtSignal, QMutex, QSize, QEvent, QPoint, QTimer from PyQt5.QtGui import QMouseEvent, QCursor, QColor from PyQt5.uic import loadUi @@ -14,26 +14,22 @@ from utils.deleteThread import * from utils.multiDeleteThread import multiDeleteThread from utils.selectVersion import * -from utils.selectVersion import check_dir, existing_user_config -# 设置应用程序在高DPI屏幕上启用高DPI缩放。Set the application to enable high DPI scaling on high DPI screens -# 注意事项:此行代码必须在QApplication实例化之前调用,否则会调用失败。Notes: This line of code must be called before the instantiation of the QApplication object; otherwise, it will fail +from utils.selectVersion import check_dir, existing_user_config, find_all_wechat_paths +from utils.scanThread import ScanThread + QApplication.setAttribute(Qt.AA_EnableHighDpiScaling) -# determine if application is a script file or frozen exe if getattr(sys, 'frozen', False): working_dir = os.path.dirname(os.path.realpath(sys.executable)) elif __file__: working_dir = os.path.split(os.path.realpath(__file__))[0] -# 主窗口 class Window(QMainWindow): def mousePressEvent(self, event): - # 重写一堆方法使其支持拖动 if event.button() == Qt.LeftButton: self.m_drag = True self.m_DragPosition = event.globalPos() - self.pos() event.accept() - # self.setCursor(QCursor(Qt.OpenHandCursor)) def mouseMoveEvent(self, QMouseEvent): try: @@ -45,29 +41,22 @@ def mouseMoveEvent(self, QMouseEvent): def mouseReleaseEvent(self, QMouseEvent): self.m_drag = False - # self.setCursor(QCursor(Qt.ArrowCursor)) def _frame(self): - # 边框 self.setWindowFlags(Qt.FramelessWindowHint) self.setAttribute(Qt.WA_TranslucentBackground, True) - # 阴影 effect = QGraphicsDropShadowEffect(blurRadius=12, xOffset=0, yOffset=0) effect.setColor(QColor(25, 25, 25, 170)) self.mainFrame.setGraphicsEffect(effect) def doFadeIn(self): - # 动画 self.animation = QPropertyAnimation(self, b'windowOpacity') - # 持续时间250ms self.animation.setDuration(250) try: - # 尝试先取消动画完成后关闭窗口的信号 self.animation.finished.disconnect(self.close) except: pass self.animation.stop() - # 透明度范围从0逐渐增加到1 self.animation.setEasingCurve(QEasingCurve.InOutCubic) self.animation.setStartValue(0) self.animation.setEndValue(1) @@ -75,9 +64,7 @@ def doFadeIn(self): def doFadeOut(self): self.animation.stop() - # 动画完成则关闭窗口 self.animation.finished.connect(self.close) - # 透明度范围从1逐渐减少到0s self.animation.setEasingCurve(QEasingCurve.InOutCubic) self.animation.setStartValue(1) self.animation.setEndValue(0) @@ -94,7 +81,7 @@ def setWarninginfo(self, text): background: #fff2f0; } """) - self.lab_info.setWordWrap(True) # 启用自动换行 + self.lab_info.setWordWrap(True) self.lab_info.setText(text) def setSuccessinfo(self, text): @@ -108,7 +95,7 @@ def setSuccessinfo(self, text): background: #f6ffed; } """) - self.lab_info.setWordWrap(True) # 启用自动换行 + self.lab_info.setWordWrap(True) self.lab_info.setText(text) @@ -133,7 +120,6 @@ def open_file(self): elem for elem in list_ if elem != 'All Users' and elem != 'Applet' and elem != 'WMPF' ] - # 如果已有用户配置,那么写入新的用户配置,否则默认写入新配置 dir_list = [] user_config = [] existing_user_config_dic = existing_user_config() @@ -282,6 +268,9 @@ def deal_emit_slot(self, set_status): self.config_exists = True def closeEvent(self, event): + if hasattr(self, 'scan_thread') and self.scan_thread.isRunning(): + self.scan_thread.stop() + self.scan_thread.wait() sys.exit(0) def eventFilter(self, object, event): @@ -291,7 +280,7 @@ def eventFilter(self, object, event): return True elif object == self.lab_clean: try: - self.setSuccessinfo("正在清理中...") + self.setSuccessinfo("正在一键清理中...") self.justdoit() except: self.setWarninginfo("清理失败,请检查配置文件后重试") @@ -300,18 +289,158 @@ def eventFilter(self, object, event): cw = ConfigWindow() cw.Signal_OneParameter.connect(self.deal_emit_slot) return True + elif object == self.lab_preview: + try: + self.start_preview() + except Exception as e: + self.setWarninginfo(f"预览失败:{str(e)}") + return True + elif object == self.lab_execute_delete: + try: + self.execute_delete() + except Exception as e: + self.setWarninginfo(f"删除失败:{str(e)}") + return True return False def _eventfilter(self): - # 事件过滤 self.lab_close.installEventFilter(self) self.lab_clean.installEventFilter(self) self.lab_config.installEventFilter(self) + self.lab_preview.installEventFilter(self) + self.lab_execute_delete.installEventFilter(self) + + def init_table(self): + self.table_files.setColumnCount(3) + self.table_files.setHorizontalHeaderLabels(["选择", "文件路径", "大小"]) + self.table_files.horizontalHeader().setSectionResizeMode(0, QHeaderView.Fixed) + self.table_files.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch) + self.table_files.horizontalHeader().setSectionResizeMode(2, QHeaderView.Fixed) + self.table_files.setColumnWidth(0, 50) + self.table_files.setColumnWidth(2, 100) + self.table_files.setRowCount(0) + self.file_data = [] + + def clear_table(self): + self.table_files.setRowCount(0) + self.file_data = [] + self.check_select_all.setChecked(False) + + def add_file_to_table(self, file_path, file_size, file_type): + row = self.table_files.rowCount() + self.table_files.insertRow(row) + + checkbox_item = QTableWidgetItem() + checkbox_item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled) + checkbox_item.setCheckState(Qt.Checked) + self.table_files.setItem(row, 0, checkbox_item) + + path_item = QTableWidgetItem(file_path) + path_item.setToolTip(file_path) + self.table_files.setItem(row, 1, path_item) + + size_item = QTableWidgetItem(file_size) + self.table_files.setItem(row, 2, size_item) + + self.file_data.append({"path": file_path, "type": file_type}) + + def start_preview(self): + if not os.path.exists(working_dir + "/config.json"): + self.setWarninginfo("请先配置微信数据目录") + return + + if hasattr(self, 'scan_thread') and self.scan_thread.isRunning(): + self.setWarninginfo("正在扫描中,请稍候...") + return + + self.clear_table() + self.bar_progress.setValue(0) + self.setSuccessinfo("正在扫描文件,请稍候...") + + fd = open(working_dir + "/config.json", encoding="utf-8") + config = json.load(fd) + + self.scan_thread = ScanThread(config) + self.scan_thread.scan_progress_signal.connect(self.on_scan_progress) + self.scan_thread.scan_file_found_signal.connect(self.on_scan_file_found) + self.scan_thread.scan_finished_signal.connect(self.on_scan_finished) + self.scan_thread.scan_error_signal.connect(self.on_scan_error) + self.scan_thread.start() + + def on_scan_progress(self, progress): + self.bar_progress.setValue(progress) + + def on_scan_file_found(self, file_path, file_size, file_type): + self.add_file_to_table(file_path, file_size, file_type) + + def on_scan_finished(self, file_count, dir_count): + total = file_count + dir_count + if total == 0: + self.setSuccessinfo("扫描完成,没有找到需要清理的文件") + else: + self.setSuccessinfo(f"扫描完成,共找到 {total} 个文件/文件夹") + self.bar_progress.setValue(100) + + def on_scan_error(self, error_msg): + self.setWarninginfo(f"扫描出错:{error_msg}") + + def toggle_select_all(self, state): + if state == Qt.Checked: + for row in range(self.table_files.rowCount()): + item = self.table_files.item(row, 0) + if item: + item.setCheckState(Qt.Checked) + else: + for row in range(self.table_files.rowCount()): + item = self.table_files.item(row, 0) + if item: + item.setCheckState(Qt.Unchecked) + + def execute_delete(self): + selected_files = [] + selected_dirs = [] + + for row in range(self.table_files.rowCount()): + item = self.table_files.item(row, 0) + if item and item.checkState() == Qt.Checked: + file_info = self.file_data[row] + if file_info["type"] == "file": + selected_files.append(file_info["path"]) + else: + selected_dirs.append(file_info["path"]) + + if len(selected_files) + len(selected_dirs) == 0: + self.setWarninginfo("请先选择要删除的文件") + return + + self.setSuccessinfo("正在删除选中的文件...") + + self.total_file = len(selected_files) + self.total_dir = len(selected_dirs) + + share_thread_arr = [0] + + if len(selected_files) + len(selected_dirs) > 0: + thread = multiDeleteThread(selected_files, selected_dirs, share_thread_arr) + thread.delete_process_signal.connect(self.callback) + thread.start() + + self.remove_deleted_rows(selected_files, selected_dirs) + + def remove_deleted_rows(self, deleted_files, deleted_dirs): + rows_to_remove = [] + for row in range(self.table_files.rowCount()): + file_info = self.file_data[row] + if file_info["path"] in deleted_files or file_info["path"] in deleted_dirs: + rows_to_remove.append(row) + + for row in sorted(rows_to_remove, reverse=True): + self.table_files.removeRow(row) + del self.file_data[row] def get_fileNum(self, path, day, picCacheCheck, fileCheck, picCheck, videoCheck, file_list, dir_list): dir_name = PureWindowsPath(path) - # Convert path to the right format for the current operating system correct_path = Path(dir_name) now = datetime.datetime.now() if picCacheCheck: @@ -353,12 +482,10 @@ def pathFileDeal(self, now, day, path, file_list, dir_list): def getPathFileNum(self, now, day, path_one, path_two, file_list, dir_list): - # caculate path_one self.pathFileDeal(now, day, path_one, file_list, dir_list) td = datetime.datetime.now() - datetime.timedelta(days=day) td_year = td.year td_month = td.month - # caculate path_two if os.path.exists(path_two): osdir = os.listdir(path_two) dirlist = [] @@ -432,33 +559,109 @@ def justdoit(self): self.total_file = total_file self.total_dir = total_dir for thread in thread_list: - thread.run() + thread.start() def show_config_window(self): self.config_window = ConfigWindow() self.setSuccessinfo("已经准备好,可以开始了!") + def smart_detect_wechat_path(self): + found_paths = find_all_wechat_paths() + + if len(found_paths) > 0: + config = self.create_config_from_paths(found_paths) + self.save_config(config) + self.setSuccessinfo(f"自动检测到微信数据目录:{found_paths[0]}") + self.config_exists = True + return True + else: + return False + + def create_config_from_paths(self, paths): + config = {"data_dir": [], "users": []} + + for path in paths: + dir_list, names = get_dir_name(path) + for i, user_dir in enumerate(dir_list): + config["data_dir"].append(path) + config["users"].append({ + "wechat_id": names[i], + "clean_days": 365, + "is_clean": True, + "clean_pic_cache": True, + "clean_file": False, + "clean_pic": True, + "clean_video": True, + "is_timer": True, + "timer": "0h" + }) + + return config + + def save_config(self, config): + if len(config["data_dir"]) > 0: + with open(working_dir + "/config.json", "w", encoding="utf-8") as f: + json.dump(config, f) + return True + return False + + def prompt_select_wechat_path(self): + self.setWarninginfo("未检测到微信数据目录,请手动选择") + + folder_path = QFileDialog.getExistingDirectory( + self, + "请选择微信数据目录(通常是 'WeChat Files' 文件夹)", + "" + ) + + if folder_path and folder_path != '': + if check_dir(folder_path) == 0: + self.setSuccessinfo(f"已选择微信数据目录:{folder_path}") + config = self.create_config_from_paths([folder_path]) + if self.save_config(config): + self.config_exists = True + return True + else: + self.setWarninginfo("保存配置失败") + return False + else: + self.setWarninginfo("选择的目录不是有效的微信数据目录,请重新选择") + return False + else: + self.setWarninginfo("未选择目录,部分功能可能无法使用") + return False + def __init__(self): super().__init__() loadUi(working_dir + "/images/main.ui", self) self._frame() self._eventfilter() + self.init_table() + + self.check_select_all.stateChanged.connect(self.toggle_select_all) + self.doFadeIn() self.config_exists = True self.show() - # 判断配置文件是否存在 if not os.path.exists(working_dir + "/config.json"): - self.setWarninginfo("首次使用,即将自动弹出配置窗口") + self.setSuccessinfo("正在智能检测微信数据目录...") self.config_exists = False - + timer = QTimer(self) - timer.timeout.connect(self.show_config_window) - timer.setSingleShot(True) # 只执行一次 - # 设置定时器的时间间隔,这里设置为 1000ms(1秒) - timer.start(1000) + def check_and_prompt(): + if self.smart_detect_wechat_path(): + self.setSuccessinfo("检测成功!已经准备好,可以开始了!") + else: + self.prompt_select_wechat_path() + + timer.timeout.connect(check_and_prompt) + timer.setSingleShot(True) + timer.start(500) + else: + self.setSuccessinfo("已经准备好,可以开始了!") if __name__ == '__main__': diff --git a/utils/__pycache__/deleteThread.cpython-310.pyc b/utils/__pycache__/deleteThread.cpython-310.pyc new file mode 100644 index 000000000..19bd4b18a Binary files /dev/null and b/utils/__pycache__/deleteThread.cpython-310.pyc differ diff --git a/utils/__pycache__/multiDeleteThread.cpython-310.pyc b/utils/__pycache__/multiDeleteThread.cpython-310.pyc new file mode 100644 index 000000000..48e94ec52 Binary files /dev/null and b/utils/__pycache__/multiDeleteThread.cpython-310.pyc differ diff --git a/utils/__pycache__/resources.cpython-310.pyc b/utils/__pycache__/resources.cpython-310.pyc new file mode 100644 index 000000000..ec867f47e Binary files /dev/null and b/utils/__pycache__/resources.cpython-310.pyc differ diff --git a/utils/__pycache__/scanThread.cpython-310.pyc b/utils/__pycache__/scanThread.cpython-310.pyc new file mode 100644 index 000000000..481c18057 Binary files /dev/null and b/utils/__pycache__/scanThread.cpython-310.pyc differ diff --git a/utils/__pycache__/selectVersion.cpython-310.pyc b/utils/__pycache__/selectVersion.cpython-310.pyc new file mode 100644 index 000000000..a89a81d73 Binary files /dev/null and b/utils/__pycache__/selectVersion.cpython-310.pyc differ diff --git a/utils/scanThread.py b/utils/scanThread.py new file mode 100644 index 000000000..c4c526d27 --- /dev/null +++ b/utils/scanThread.py @@ -0,0 +1,185 @@ +import os, datetime, re +from pathlib import Path, PureWindowsPath +from PyQt5.QtCore import QThread, pyqtSignal, QMutex + + +class ScanThread(QThread): + scan_progress_signal = pyqtSignal(int) + scan_file_found_signal = pyqtSignal(str, str, str) + scan_finished_signal = pyqtSignal(int, int) + scan_error_signal = pyqtSignal(str) + + def __init__(self, config): + super(ScanThread, self).__init__() + self.config = config + self.is_running = True + self.qmut = QMutex() + + def stop(self): + self.is_running = False + + def get_file_size_str(self, size): + if size < 1024: + return f"{size} B" + elif size < 1024 * 1024: + return f"{size / 1024:.2f} KB" + elif size < 1024 * 1024 * 1024: + return f"{size / (1024 * 1024):.2f} MB" + else: + return f"{size / (1024 * 1024 * 1024):.2f} GB" + + def get_file_size(self, path): + try: + if os.path.isfile(path): + return os.path.getsize(path) + elif os.path.isdir(path): + total_size = 0 + for dirpath, dirnames, filenames in os.walk(path): + for f in filenames: + fp = os.path.join(dirpath, f) + try: + total_size += os.path.getsize(fp) + except: + pass + return total_size + except: + pass + return 0 + + def get_fileNum(self, path, day, picCacheCheck, fileCheck, picCheck, videoCheck, file_list, dir_list): + if not self.is_running: + return + + dir_name = PureWindowsPath(path) + correct_path = Path(dir_name) + now = datetime.datetime.now() + + if picCacheCheck: + path_one = correct_path / 'Attachment' + path_two = correct_path / 'FileStorage/Cache' + self.getPathFileNum(now, day, path_one, path_two, file_list, dir_list) + + if fileCheck: + path_one = correct_path / 'Files' + path_two = correct_path / 'FileStorage/File' + self.getPathFileNum(now, day, path_one, path_two, file_list, dir_list) + + if picCheck: + path_one = correct_path / 'Image/Image' + path_two = correct_path / 'FileStorage/Image' + self.getPathFileNum(now, day, path_one, path_two, file_list, dir_list) + + if videoCheck: + path_one = correct_path / 'Video' + path_two = correct_path / 'FileStorage/Video' + self.getPathFileNum(now, day, path_one, path_two, file_list, dir_list) + + def pathFileDeal(self, now, day, path, file_list, dir_list): + if not self.is_running: + return + + if os.path.exists(path): + filelist = [ + f for f in os.listdir(path) + if os.path.isfile(os.path.join(path, f)) + ] + for i in range(0, len(filelist)): + if not self.is_running: + return + + file_path = os.path.join(path, filelist[i]) + if os.path.isdir(file_path): + continue + + try: + timestamp = datetime.datetime.fromtimestamp( + os.path.getmtime(file_path)) + diff = (now - timestamp).days + if diff >= day: + file_list.append(file_path) + file_size = self.get_file_size(file_path) + file_size_str = self.get_file_size_str(file_size) + self.scan_file_found_signal.emit(file_path, file_size_str, "file") + except Exception as e: + print(f"Error processing file {file_path}: {e}") + continue + + def getPathFileNum(self, now, day, path_one, path_two, file_list, dir_list): + if not self.is_running: + return + + self.pathFileDeal(now, day, path_one, file_list, dir_list) + + td = datetime.datetime.now() - datetime.timedelta(days=day) + td_year = td.year + td_month = td.month + + if os.path.exists(path_two): + osdir = os.listdir(path_two) + dirlist = [] + for i in range(0, len(osdir)): + file_path = os.path.join(path_two, osdir[i]) + if os.path.isdir(file_path): + dirlist.append(osdir[i]) + + for i in range(0, len(dirlist)): + if not self.is_running: + return + + file_path = os.path.join(path_two, dirlist[i]) + if os.path.isfile(file_path): + continue + + if re.match('\d{4}(\-)\d{2}', dirlist[i]) != None: + cyear = int(dirlist[i].split('-', 1)[0]) + cmonth = int(dirlist[i].split('-', 1)[1]) + if self.__before_deadline(cyear, cmonth, td_year, td_month): + dir_list.append(file_path) + file_size = self.get_file_size(file_path) + file_size_str = self.get_file_size_str(file_size) + self.scan_file_found_signal.emit(file_path, file_size_str, "dir") + else: + if cmonth == td_month: + self.pathFileDeal(now, day, file_path, file_list, dir_list) + + def __before_deadline(self, cyear, cmonth, td_year, td_month): + if cyear < td_year: + return True + elif cyear > td_year: + return False + elif cyear == td_year: + return cmonth < td_month + + def run(self): + try: + file_list = [] + dir_list = [] + + i = 0 + total_users = len(self.config["users"]) + + for value in self.config["users"]: + if not self.is_running: + break + + if value["is_clean"]: + self.get_fileNum( + self.config["data_dir"][i], + int(value["clean_days"]), + value["clean_pic_cache"], + value["clean_file"], + value["clean_pic"], + value["clean_video"], + file_list, + dir_list + ) + + progress = int((i + 1) / total_users * 100) + self.scan_progress_signal.emit(progress) + i = i + 1 + + if self.is_running: + self.scan_finished_signal.emit(len(file_list), len(dir_list)) + + except Exception as e: + self.scan_error_signal.emit(str(e)) diff --git a/utils/selectVersion.py b/utils/selectVersion.py index 96236907a..cc1dde214 100644 --- a/utils/selectVersion.py +++ b/utils/selectVersion.py @@ -7,10 +7,15 @@ working_dir = os.path.split(os.path.realpath(__file__))[0] def check_dir(file_path): - list_ = os.listdir(file_path) - if 'All Users' in list_ or 'Applet' in list_ or 'WMPF' in list_: - return 0 - else: + if not os.path.exists(file_path): + return 1 + try: + list_ = os.listdir(file_path) + if 'All Users' in list_ or 'Applet' in list_ or 'WMPF' in list_: + return 0 + else: + return 1 + except: return 1 @@ -43,15 +48,98 @@ def read_registry_value(key_path, value_name): def get_dir_name(filepath): dirlist = [] names = [] - list_ = os.listdir(filepath) - # 换用lambda 表达式更安全,remove函数如果不存在对象会抛出异常 - list_ = [element for element in list_ if element != 'All Users' and element != 'Applet' and element != 'WMPF'] - for i in range(0, len(list_)): - file_path = os.path.join(filepath, list_[i]) - if os.path.isdir(file_path): - dirlist.append(file_path) - names.append(list_[i]) - return (dirlist, names) + if not os.path.exists(filepath): + return ([], []) + try: + list_ = os.listdir(filepath) + list_ = [element for element in list_ if element != 'All Users' and element != 'Applet' and element != 'WMPF'] + for i in range(0, len(list_)): + file_path = os.path.join(filepath, list_[i]) + if os.path.isdir(file_path): + dirlist.append(file_path) + names.append(list_[i]) + return (dirlist, names) + except: + return ([], []) + +def find_all_wechat_paths(): + """ + 智能扫描所有可能的微信数据目录位置 + 返回所有找到的有效路径列表 + """ + user = getpass.getuser() + found_paths = [] + + common_paths = [ + f'C:\\Users\\{user}\\Documents\\WeChat Files', + f'C:\\Users\\{user}\\OneDrive\\Documents\\WeChat Files', + f'D:\\Documents\\WeChat Files', + f'E:\\Documents\\WeChat Files', + f'F:\\Documents\\WeChat Files', + f'C:\\Users\\{user}\\AppData\\Local\\Packages\\TencentWeChatLimited.forWindows10_sdtnhv12zgd7a\\LocalCache\\Roaming\\Tencent\\WeChatAppStore\\WeChatAppStore Files', + f'C:\\Users\\{user}\\AppData\\Local\\Packages\\TencentWeChatLimited.WeChatUWP_sdtnhv12zgd7a\\LocalCache\\Roaming\\Tencent\\WeChatAppStore\\WeChatAppStore Files', + ] + + for drive in ['C', 'D', 'E', 'F', 'G']: + common_paths.append(f'{drive}:\\WeChat Files') + common_paths.append(f'{drive}:\\WeChat\\WeChat Files') + common_paths.append(f'{drive}:\\Program Files\\Tencent\\WeChat\\WeChat Files') + common_paths.append(f'{drive}:\\Program Files (x86)\\Tencent\\WeChat\\WeChat Files') + common_paths.append(f'{drive}:\\Users\\{user}\\WeChat Files') + common_paths.append(f'{drive}:\\Users\\{user}\\Tencent Files\\WeChat Files') + + for path in common_paths: + if check_dir(path) == 0: + if path not in found_paths: + found_paths.append(path) + + registry_key_paths = [ + r"software\tencent\wechat", + r"Software\Tencent\WeChat", + r"SOFTWARE\Tencent\WeChat", + ] + + for key_path in registry_key_paths: + value_names = ["FileSavePath", "InstallPath"] + for value_name in value_names: + value = read_registry_value(key_path, value_name) + if value: + if value == 'MyDocument:': + doc_path = f'C:\\Users\\{user}\\Documents\\WeChat Files' + if check_dir(doc_path) == 0 and doc_path not in found_paths: + found_paths.append(doc_path) + elif os.path.isdir(value): + wechat_path = os.path.join(value, 'WeChat Files') + if check_dir(wechat_path) == 0 and wechat_path not in found_paths: + found_paths.append(wechat_path) + if check_dir(value) == 0 and value not in found_paths: + found_paths.append(value) + + try: + for key_path in registry_key_paths: + try: + key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, key_path) + i = 0 + while True: + try: + subkey_name = winreg.EnumKey(key, i) + if 'WeChat' in subkey_name or 'Wechat' in subkey_name or 'wechat' in subkey_name: + subkey_path = f"{key_path}\\{subkey_name}" + value = read_registry_value(subkey_path, "FileSavePath") + if value and os.path.isdir(value): + wechat_path = os.path.join(value, 'WeChat Files') + if check_dir(wechat_path) == 0 and wechat_path not in found_paths: + found_paths.append(wechat_path) + i += 1 + except OSError: + break + winreg.CloseKey(key) + except: + pass + except: + pass + + return found_paths class selectVersion: @@ -66,11 +154,9 @@ def getAllPath(self): if os.path.exists(dic[key]): return get_dir_name(dic[key]) - # 注册表路径和字段名 registry_key_path = r"software\tencent\wechat" value_name = "FileSavePath" - # 读取字段值 value = read_registry_value(registry_key_path, value_name) if value and value != 'MyDocument:' and os.path.isdir(value): @@ -79,4 +165,3 @@ def getAllPath(self): return get_dir_name(fpath) else: return ([], []) -