首頁 > 軟體

基於Python編寫微信清理工具的範例程式碼

2022-05-19 13:00:36

前幾天網上找了一款 PC 端微信自動清理工具,用了一下,電腦釋放了 30GB 的儲存空間,而且不會刪除文字的聊天記錄,很好用,感覺很多人都用得到,就在此分享一下,而且是用 Python 寫的,喜歡 Python 的小夥伴可以探究一下。

主要功能

它可以自動刪除 PC 端微信自動下載的大量檔案、視訊、圖片等資料內容,釋放幾十 G 的空間佔用,而且不會刪除文字的聊天記錄,可以放心使用。

工作以後,微信的群聊實在太多了,動不動就被拉入一個群中,然後群聊裡大部分都是與自己無關的各大群聊中的檔案、視訊、圖片等內容,會非常佔用儲存空間。

  • 自動識別微信賬號,支援使用者選擇自定義路徑;
  • 同時管理多個賬號,保留設定引數,開啟即用;
  • 自由設定想要刪除的檔案型別,包括圖片類快取、檔案、圖片、視訊;
  • 自由設定需要刪除的檔案的距離時間,預設 365 天;
  • 刪除後的檔案放置在回收站中,檢查後自行清空,防止刪錯檔案;
  • 支援刪除進度的顯示;

工具的主介面如下

執行環境

Windows,後續可能會支援 Mac。

核心程式碼

import sys

from PyQt5.QtWidgets import QApplication, QMainWindow, QGraphicsDropShadowEffect, QListWidgetItem, QListView, QWidget, 
    QLabel, QHBoxLayout, QFileDialog
from PyQt5.QtCore import Qt, QPropertyAnimation, QEasingCurve, QThread, pyqtSignal, QMutex, QSize, QEvent, QPoint
from PyQt5.QtGui import QMouseEvent, QCursor, QColor
from PyQt5.uic import loadUi

from pathlib import Path, PureWindowsPath
from dateutil import relativedelta
import utils.resources
import os, datetime, time, re, math, shutil, json

from utils.deleteThread import *
from utils.multiDeleteThread import multiDeleteThread
from utils.selectVersion import *
from utils.selectVersion import check_dir, existing_user_config

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:
            if Qt.LeftButton and self.m_drag:
                self.move(QMouseEvent.globalPos() - self.m_DragPosition)
                QMouseEvent.accept()
        except:
            pass

    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)
        self.animation.start()

    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)
        self.animation.start()

    def setWarninginfo(self, text):
        self.lab_info.setStyleSheet("""
            .QLabel {
                border:1px solid #ffccc7;
                border-radius:3px;
                line-height: 140px;
                padding: 5px;
                color: #434343;
                background: #fff2f0;
            }
            """)
        self.lab_info.setText(text)

    def setSuccessinfo(self, text):
        self.lab_info.setStyleSheet("""
            .QLabel {
                border:1px solid #b7eb8f;
                border-radius:3px;
                line-height: 140px;
                padding: 5px;
                color: #434343;
                background: #f6ffed;
            }
            """)
        self.lab_info.setText(text)


class ConfigWindow(Window):
    Signal_OneParameter = pyqtSignal(int)

    config = {}

    def _connect(self):
        self.combo_user.currentIndexChanged.connect(self.refresh_ui)
        self.btn_close.clicked.connect(self.save_config)
        self.btn_file.clicked.connect(self.open_file)

    def open_file(self):
        openfile_path = QFileDialog.getExistingDirectory(self, '選擇微信資料目錄', '')
        if not openfile_path or openfile_path == '':
            return False
        if check_dir(openfile_path) == 0:
            self.setSuccessinfo('讀取路徑成功!')
            list_ = os.listdir(openfile_path)
            user_list = [
                elem for elem in list_
                if elem != 'All Users' and elem != 'Applet'
            ]
            # 如果已有使用者設定,那麼寫入新的使用者設定,否則預設寫入新設定
            dir_list = []
            user_config = []
            existing_user_config_dic = existing_user_config()
            for user_wx_id in user_list:
                dir_list.append(os.path.join(openfile_path, user_wx_id))
                if user_wx_id in existing_user_config_dic:
                    user_config.append(existing_user_config_dic[user_wx_id])
                else:
                    user_config.append({
                        "wechat_id": user_wx_id,
                        "clean_days": "365",
                        "is_clean": False,
                        "clean_pic_cache": True,
                        "clean_file": False,
                        "clean_pic": True,
                        "clean_video": True,
                        "is_timer": True,
                        "timer": "0h"
                    })

            config = {"data_dir": dir_list, "users": user_config}

            with open(
                    working_dir + "/config.json", "w", encoding="utf-8") as f:
                json.dump(config, f)
            self.load_config()
        else:
            self.setWarninginfo('請選擇正確的資料夾!一般是WeChat Files資料夾。')

    def save_config(self):
        self.update_config()
        self.doFadeOut()

    def check_wechat_exists(self):
        self.selectVersion = selectVersion()
        self.version_scan = self.selectVersion.getAllPath()[0]
        self.users_scan = self.selectVersion.getAllPath()[1]
        if len(self.version_scan) == 0:
            return False
        else:
            return True

    def load_config(self):
        fd = open(working_dir + "/config.json", encoding="utf-8")
        self.config = json.load(fd)

        self.combo_user.clear()
        for value in self.config["users"]:
            self.combo_user.addItem(value["wechat_id"])

        self.line_gobackdays.setText(
            str(self.config["users"][0]["clean_days"]))
        self.check_is_clean.setChecked(self.config["users"][0]["is_clean"])
        self.check_picdown.setChecked(self.config["users"][0]["clean_pic"])
        self.check_files.setChecked(self.config["users"][0]["clean_file"])
        self.check_video.setChecked(self.config["users"][0]["clean_video"])
        self.check_picscache.setChecked(
            self.config["users"][0]["clean_pic_cache"])
        self.setSuccessinfo("載入組態檔成功")

    def refresh_ui(self):
        self.config = open(working_dir + "/config.json", encoding="utf-8")
        self.config = json.load(self.config)

        for value in self.config["users"]:
            if value["wechat_id"] == self.combo_user.currentText():
                self.line_gobackdays.setText(str(value["clean_days"]))
                self.check_is_clean.setChecked(value["is_clean"])
                self.check_picdown.setChecked(value["clean_pic"])
                self.check_files.setChecked(value["clean_file"])
                self.check_video.setChecked(value["clean_video"])
                self.check_picscache.setChecked(value["clean_pic_cache"])

    def create_config(self):
        true = True
        if not os.path.exists(working_dir + "/config.json"):
            if not self.check_wechat_exists():
                self.setWarninginfo("預設位置沒有微信,請自定義位置")
                return

            self.config = {"data_dir": self.version_scan, "users": []}
            for value in self.users_scan:
                self.config["users"].append({
                    "wechat_id": value,
                    "clean_days": 365,
                    "is_clean": False,
                    "clean_pic_cache": true,
                    "clean_file": False,
                    "clean_pic": true,
                    "clean_video": true,
                    "is_timer": true,
                    "timer": "0h"
                })
            with open(
                    working_dir + "/config.json", "w", encoding="utf-8") as f:
                json.dump(self.config, f)
            self.load_config()
            self.setSuccessinfo("載入組態檔成功")
        else:
            self.setSuccessinfo("載入組態檔成功")
            self.load_config()

    def update_config(self):
        if not len(self.config):
            return
        else:
            for value in self.config["users"]:
                if value["wechat_id"] == self.combo_user.currentText():
                    try:
                        days = int(self.line_gobackdays.text())
                        if days < 0:
                            value["clean_days"] = "0"
                        else:
                            value["clean_days"] = self.line_gobackdays.text()
                    except ValueError:
                        value["clean_days"] = "0"
                    value["is_clean"] = self.check_is_clean.isChecked()
                    value["clean_pic"] = self.check_picdown.isChecked()
                    value["clean_file"] = self.check_files.isChecked()
                    value["clean_video"] = self.check_video.isChecked()
                    value["clean_pic_cache"] = self.check_picscache.isChecked()

            with open(working_dir + "/config.json", "w", encoding="utf-8") as f:
                json.dump(self.config, f)
            self.setSuccessinfo("更新組態檔成功")
            self.Signal_OneParameter.emit(1)

    def __init__(self):
        super().__init__()
        loadUi(working_dir + "/images/config.ui", self)

        self._frame()
        self._connect()

        self.doFadeIn()
        self.create_config()

        self.show()


class MainWindow(Window):

    def deal_emit_slot(self, set_status):
        if set_status and not self.config_exists:
            self.setSuccessinfo("已經準備好,可以開始了!")
            self.config_exists = True

    def closeEvent(self, event):
        sys.exit(0)

    def eventFilter(self, object, event):
        if event.type() == QEvent.MouseButtonPress:
            if object == self.lab_close:
                self.doFadeOut()
                return True
            elif object == self.lab_clean:
                try:
                    self.setSuccessinfo("正在清理中...")
                    self.justdoit()
                except:
                    self.setWarninginfo("清理失敗,請檢查組態檔後重試")
                return True
            elif object == self.lab_config:
                cw = ConfigWindow()
                cw.Signal_OneParameter.connect(self.deal_emit_slot)
                return True
        return False

    def _eventfilter(self):
        # 事件過濾
        self.lab_close.installEventFilter(self)
        self.lab_clean.installEventFilter(self)
        self.lab_config.installEventFilter(self)

    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:
            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 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)):
                file_path = os.path.join(path, filelist[i])
                if os.path.isdir(file_path):
                    continue
                timestamp = datetime.datetime.fromtimestamp(
                    os.path.getmtime(file_path))
                diff = (now - timestamp).days
                if diff >= day:
                    file_list.append(file_path)

    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 = []
            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)):
                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)
                    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 callback(self, v):
        value = v / int((self.total_file + self.total_dir)) * 100
        self.bar_progress.setValue(value)
        if value == 100:
            out = "本次共清理檔案" + str(self.total_file) + "個,資料夾" + str(
                self.total_dir) + "個。請前往回收站檢查並清空。"
            self.setSuccessinfo(out)
            return

    def justdoit(self):  # 這個Api設計的太腦殘了,其實dir可以直接放在user裡的... 有時間改吧
        fd = open(working_dir + "/config.json", encoding="utf-8")
        self.config = json.load(fd)
        i = 0
        need_clean = False
        thread_list = []
        total_file = 0
        total_dir = 0
        share_thread_arr = [0]
        for value in self.config["users"]:
            file_list = []
            dir_list = []
            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)

            if len(file_list) + len(dir_list) != 0:
                need_clean = True
                total_file += len(file_list)
                total_dir += len(dir_list)
                thread_list.append(
                    multiDeleteThread(file_list, dir_list, share_thread_arr))
                thread_list[-1].delete_process_signal.connect(self.callback)
            i = i + 1

        if not need_clean:
            self.setWarninginfo("沒有需要清理的檔案")
        else:
            self.total_file = total_file
            self.total_dir = total_dir
            for thread in thread_list:
                thread.run()

    def __init__(self):
        super().__init__()
        loadUi(working_dir + "/images/main.ui", self)

        self._frame()
        self._eventfilter()
        self.doFadeIn()
        self.config_exists = True

        # 判斷組態檔是否存在
        if not os.path.exists(working_dir + "/config.json"):
            self.setWarninginfo("組態檔不存在!請單擊「設定」建立組態檔")
            self.config_exists = False

        self.show()


if __name__ == '__main__':
    app = QApplication([])
    win = MainWindow()
    app.exec_()

完整程式碼

原始碼獲取地址 提取碼:vuud

到此這篇關於基於Python編寫微信清理工具的範例程式碼的文章就介紹到這了,更多相關Python微信清理工具內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com