基于Python编写微信清理工具的示例代码
作者:somenzz 发布时间:2022-03-04 09:02:54
标签:Python,微信,清理
前几天网上找了一款 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
来源:https://mp.weixin.qq.com/s/7qWjmla6bgayXSDDiuBBDQ


猜你喜欢
- 如果你使用过大部分,那么你的ASP功力应该是非常高的了ADO对象(太常用了):ConnectionCommandRecordSetRecor
- 一、问题描述 SQL Plus WorkSheet是一个窗口图形界面的SQL语句编辑器,对于那些喜欢窗口界面而不喜欢字符界面的用户,该工具相
- 前言centos 是自带python的。但是版本稍微旧一些。搞python开发,肯定要用新一点的稳定版。所以,要升级一下python。本文将
- 本文以一个简单的实例讲述了python实现斐波那契数列数列递归函数的方法,代码精简易懂。分享给大家供大家参考之用。主要函数代码如下:def
- 最近学习了一点python,那就试着做一做简单的编程练习。 首先是这个编程的指导图,如下:对的,类似一个简单区块链的模拟。 代码如下:cla
- 本文实例讲述了Python实现二维有序数组查找的方法。分享给大家供大家参考,具体如下:题目:在一个二维数组中,每一行都按照从左到右递增的顺序
- Python 语言里有许多(而且是越来越多)的高级特性,是 Python 发烧友们非常喜欢的。在这些人的眼里,能够写出那些一般开发者看不懂的
- 有多少次你在考虑怎样设置数据库时感到为难?其实,如果你在Linux上使用MySQL,就不会有这种情况了。在Linux上使用Webmin图形界
- 摘要:本篇文章介绍了ORACLE数据库的新特性—分区管理,并用例子说明使用方法。 关键词:ORACLE,分区 一、 分区概述: 为了简化数据
- pytorch的广播语义(broadcasting semantics),和numpy的很像,所以可以先看看numpy的文档:1、什么是广播
- Django中想要把模型类聚合得到想要的数据可以用F对象。比如有模型类A和B,A和B之间有外键关联在一起,A是子表,B是父表(反过来没试过。
- 这是解释器设置问题,在设置里找到interpreter 找到合适的目录就可以了。因为重装了python导致pycharm找不到路径了。另外,
- 本文实例为大家分享了vue实现全屏滚动效果(的具体代码,供大家参考,具体内容如下是什么网页的一个页面占据一屏的宽高,多个页面上下或者左右拼接
- set转成list方法如下: list转成set方法如下:s = set('12342212') &n
- 本文记录了MySQL下载安装详细教程,供大家参考,具体内容如下1.下载MySQL数据库可以访问官方网站:2.点击DOWNLOADS模块下的C
- sympy有个vector 模块,里面提供了求解标量场、向量场的梯度、散度、旋度等计算,官方参考连接:http://docs.sympy.o
- 版本:python3.7功能,开发一个用户访问的页面,支持图片上传,并将其保存在服务器。项目结构:app.py文件内容如下:from fla
- 一直以来,JS前端代码因为必须经过IE明文解析,某些加密的JS如:JScript.Encode也因为树大招风,早就被人破解了。还有些加密的手
- 看了不少朋友的个人网站,有一个小问题,似乎很多朋友都忽略了,那就是版权声明的写法。虽然那只是一小行字,不过作为设计师也好,作为个人的爱好也好
- 曾有位网友遇到这样一个问题:产品名称为“A&T Plastic”,在产品列表中需要做这样的超链接,<a href="