From 9b492b5e0d46e937772a6cc2d85edf5067a58fde Mon Sep 17 00:00:00 2001 From: DLmaster361 Date: Fri, 18 Jul 2025 18:12:47 +0800 Subject: [PATCH] =?UTF-8?q?feat(core):=20=E9=87=8D=E6=9E=84=E6=97=A5?= =?UTF-8?q?=E5=BF=97=E8=AE=B0=E5=BD=95=EF=BC=8C=E8=BD=BD=E5=85=A5=E6=9B=B4?= =?UTF-8?q?=E5=A4=9A=E6=97=A5=E5=BF=97=E8=AE=B0=E5=BD=95=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/core/__init__.py | 2 + app/core/config.py | 460 +++++++++++++++++++++++++---------- app/core/logger.py | 34 +++ app/core/main_info_bar.py | 30 ++- app/core/network.py | 56 ++++- app/core/sound_player.py | 12 +- app/core/task_manager.py | 117 +++++++-- app/core/timer.py | 37 ++- app/models/MAA.py | 445 ++++++++++++++++++++++++--------- app/models/general.py | 247 +++++++++++++++---- app/services/notification.py | 141 +++++++---- app/services/security.py | 94 +++++-- app/services/skland.py | 52 ++-- app/services/system.py | 70 ++++-- app/ui/dispatch_center.py | 116 +++++++-- app/ui/downloader.py | 138 ++++++++--- app/ui/history.py | 49 +++- app/ui/home.py | 36 +-- app/ui/main_window.py | 74 +++--- app/ui/member_manager.py | 315 +++++++++++++++++------- app/ui/plan_manager.py | 81 +++--- app/ui/queue_manager.py | 72 +++--- app/ui/setting.py | 52 ++-- app/utils/ProcessManager.py | 9 +- main.py | 220 +++++++++++++++-- resources/version.json | 58 +---- 26 files changed, 2217 insertions(+), 800 deletions(-) create mode 100644 app/core/logger.py diff --git a/app/core/__init__.py b/app/core/__init__.py index 8a9058d..9240bc7 100644 --- a/app/core/__init__.py +++ b/app/core/__init__.py @@ -38,6 +38,7 @@ from .config import ( GeneralSubConfig, Config, ) +from .logger import logger from .main_info_bar import MainInfoBar from .network import Network from .sound_player import SoundPlayer @@ -52,6 +53,7 @@ __all__ = [ "MaaPlanConfig", "GeneralConfig", "GeneralSubConfig", + "logger", "MainInfoBar", "Network", "SoundPlayer", diff --git a/app/core/config.py b/app/core/config.py index fe19472..b57c1a1 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -25,7 +25,6 @@ v4.4 作者:DLmaster_361 """ -from loguru import logger from PySide6.QtCore import Signal import argparse import sqlite3 @@ -53,6 +52,7 @@ from qfluentwidgets import ( from urllib.parse import urlparse from typing import Union, Dict, List +from .logger import logger from .network import Network @@ -707,7 +707,7 @@ class GeneralSubConfig(LQConfig): class AppConfig(GlobalConfig): - VERSION = "4.4.0.0" + VERSION = "4.4.1.1" stage_refreshed = Signal() PASSWORD_refreshed = Signal() @@ -717,8 +717,8 @@ class AppConfig(GlobalConfig): def __init__(self) -> None: super().__init__() - self.app_path = Path(sys.argv[0]).resolve().parent # 获取软件根目录 - self.app_path_sys = Path(sys.argv[0]).resolve() # 获取软件自身的路径 + self.app_path = Path(sys.argv[0]).resolve().parent + self.app_path_sys = Path(sys.argv[0]).resolve() self.log_path = self.app_path / "debug/AUTO_MAA.log" self.database_path = self.app_path / "data/data.db" @@ -781,7 +781,7 @@ class AppConfig(GlobalConfig): self.init_logger() self.check_data() - logger.info("程序初始化完成") + logger.info("程序初始化完成", module="配置管理") def init_logger(self) -> None: """初始化日志记录器""" @@ -789,7 +789,7 @@ class AppConfig(GlobalConfig): logger.add( sink=self.log_path, level="DEBUG", - format="{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <8} | {message}", + format="{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <8} | {extra[module]} | {message}", enqueue=True, backtrace=True, diagnose=True, @@ -797,102 +797,28 @@ class AppConfig(GlobalConfig): retention="1 month", compression="zip", ) - logger.info("") - logger.info("===================================") - logger.info("AUTO_MAA 主程序") - logger.info(f"版本号: v{self.VERSION}") - logger.info(f"根目录: {self.app_path}") + logger.add( + sink=sys.stderr, + level="DEBUG", + format="{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <8} | {extra[module]} | {message}", + enqueue=True, + backtrace=True, + diagnose=True, + colorize=True, + ) + + logger.info("", module="配置管理") + logger.info("===================================", module="配置管理") + logger.info("AUTO_MAA 主程序", module="配置管理") + logger.info(f"版本号: v{self.VERSION}", module="配置管理") + logger.info(f"根目录: {self.app_path}", module="配置管理") logger.info( - f"运行模式: {'图形化界面' if self.args.mode == 'gui' else '命令行界面'}" + f"运行模式: {'图形化界面' if self.args.mode == 'gui' else '命令行界面'}", + module="配置管理", ) - logger.info("===================================") + logger.info("===================================", module="配置管理") - logger.info("日志记录器初始化完成") - - def get_stage(self) -> None: - """从MAA服务器获取活动关卡信息""" - - network = Network.add_task( - mode="get", - url="https://api.maa.plus/MaaAssistantArknights/api/gui/StageActivity.json", - ) - network.loop.exec() - network_result = Network.get_result(network) - if network_result["status_code"] == 200: - stage_infos: List[Dict[str, Union[str, Dict[str, Union[str, int]]]]] = ( - network_result["response_json"]["Official"]["sideStoryStage"] - ) - else: - logger.warning( - f"无法从MAA服务器获取活动关卡信息:{network_result['error_message']}" - ) - stage_infos = [] - - ss_stage_dict = {"value": [], "text": []} - - for stage_info in stage_infos: - - if ( - datetime.strptime( - stage_info["Activity"]["UtcStartTime"], "%Y/%m/%d %H:%M:%S" - ) - < datetime.now() - < datetime.strptime( - stage_info["Activity"]["UtcExpireTime"], "%Y/%m/%d %H:%M:%S" - ) - ): - ss_stage_dict["value"].append(stage_info["Value"]) - ss_stage_dict["text"].append(stage_info["Value"]) - - # 生成每日关卡信息 - stage_daily_info = [ - {"value": "-", "text": "当前/上次", "days": [1, 2, 3, 4, 5, 6, 7]}, - {"value": "1-7", "text": "1-7", "days": [1, 2, 3, 4, 5, 6, 7]}, - {"value": "R8-11", "text": "R8-11", "days": [1, 2, 3, 4, 5, 6, 7]}, - { - "value": "12-17-HARD", - "text": "12-17-HARD", - "days": [1, 2, 3, 4, 5, 6, 7], - }, - {"value": "CE-6", "text": "龙门币-6/5", "days": [2, 4, 6, 7]}, - {"value": "AP-5", "text": "红票-5", "days": [1, 4, 6, 7]}, - {"value": "CA-5", "text": "技能-5", "days": [2, 3, 5, 7]}, - {"value": "LS-6", "text": "经验-6/5", "days": [1, 2, 3, 4, 5, 6, 7]}, - {"value": "SK-5", "text": "碳-5", "days": [1, 3, 5, 6]}, - {"value": "PR-A-1", "text": "奶/盾芯片", "days": [1, 4, 5, 7]}, - {"value": "PR-A-2", "text": "奶/盾芯片组", "days": [1, 4, 5, 7]}, - {"value": "PR-B-1", "text": "术/狙芯片", "days": [1, 2, 5, 6]}, - {"value": "PR-B-2", "text": "术/狙芯片组", "days": [1, 2, 5, 6]}, - {"value": "PR-C-1", "text": "先/辅芯片", "days": [3, 4, 6, 7]}, - {"value": "PR-C-2", "text": "先/辅芯片组", "days": [3, 4, 6, 7]}, - {"value": "PR-D-1", "text": "近/特芯片", "days": [2, 3, 6, 7]}, - {"value": "PR-D-2", "text": "近/特芯片组", "days": [2, 3, 6, 7]}, - ] - - for day in range(0, 8): - - today_stage_dict = {"value": [], "text": []} - - for stage_info in stage_daily_info: - - if day in stage_info["days"] or day == 0: - today_stage_dict["value"].append(stage_info["value"]) - today_stage_dict["text"].append(stage_info["text"]) - - self.stage_dict[calendar.day_name[day - 1] if day > 0 else "ALL"] = { - "value": today_stage_dict["value"] + ss_stage_dict["value"], - "text": today_stage_dict["text"] + ss_stage_dict["text"], - } - - self.stage_refreshed.emit() - - def server_date(self) -> date: - """获取当前的服务器日期""" - - dt = datetime.now() - if dt.time() < datetime.min.time().replace(hour=4): - dt = dt - timedelta(days=1) - return dt.date() + logger.info("日志记录器初始化完成", module="配置管理") def check_data(self) -> None: """检查用户数据文件并处理数据文件版本更新""" @@ -914,11 +840,11 @@ class AppConfig(GlobalConfig): version = cur.fetchall() if version[0][0] != "v1.7": - logger.info("数据文件版本更新开始") + logger.info("数据文件版本更新开始", module="配置管理") if_streaming = False # v1.4-->v1.5 if version[0][0] == "v1.4" or if_streaming: - logger.info("数据文件版本更新:v1.4-->v1.5") + logger.info("数据文件版本更新:v1.4-->v1.5", module="配置管理") if_streaming = True member_dict: Dict[str, Dict[str, Union[str, Path]]] = {} @@ -1046,7 +972,7 @@ class AppConfig(GlobalConfig): # v1.5-->v1.6 if version[0][0] == "v1.5" or if_streaming: - logger.info("数据文件版本更新:v1.5-->v1.6") + logger.info("数据文件版本更新:v1.5-->v1.6", module="配置管理") if_streaming = True cur.execute("DELETE FROM version WHERE v = ?", ("v1.5",)) cur.execute("INSERT INTO version VALUES(?)", ("v1.6",)) @@ -1078,7 +1004,7 @@ class AppConfig(GlobalConfig): pass # v1.6-->v1.7 if version[0][0] == "v1.6" or if_streaming: - logger.info("数据文件版本更新:v1.6-->v1.7") + logger.info("数据文件版本更新:v1.6-->v1.7", module="配置管理") if_streaming = True if (self.app_path / "config/MaaConfig").exists(): @@ -1148,11 +1074,106 @@ class AppConfig(GlobalConfig): cur.close() db.close() - logger.info("数据文件版本更新完成") + logger.success("数据文件版本更新完成", module="配置管理") + + def get_stage(self) -> None: + """从MAA服务器更新活动关卡信息""" + + logger.info("开始获取活动关卡信息", module="配置管理") + network = Network.add_task( + mode="get", + url="https://api.maa.plus/MaaAssistantArknights/api/gui/StageActivity.json", + ) + network.loop.exec() + network_result = Network.get_result(network) + if network_result["status_code"] == 200: + stage_infos: List[Dict[str, Union[str, Dict[str, Union[str, int]]]]] = ( + network_result["response_json"]["Official"]["sideStoryStage"] + ) + else: + logger.warning( + f"无法从MAA服务器获取活动关卡信息:{network_result['error_message']}", + module="配置管理", + ) + stage_infos = [] + + ss_stage_dict = {"value": [], "text": []} + + for stage_info in stage_infos: + + if ( + datetime.strptime( + stage_info["Activity"]["UtcStartTime"], "%Y/%m/%d %H:%M:%S" + ) + < datetime.now() + < datetime.strptime( + stage_info["Activity"]["UtcExpireTime"], "%Y/%m/%d %H:%M:%S" + ) + ): + ss_stage_dict["value"].append(stage_info["Value"]) + ss_stage_dict["text"].append(stage_info["Value"]) + + # 生成每日关卡信息 + stage_daily_info = [ + {"value": "-", "text": "当前/上次", "days": [1, 2, 3, 4, 5, 6, 7]}, + {"value": "1-7", "text": "1-7", "days": [1, 2, 3, 4, 5, 6, 7]}, + {"value": "R8-11", "text": "R8-11", "days": [1, 2, 3, 4, 5, 6, 7]}, + { + "value": "12-17-HARD", + "text": "12-17-HARD", + "days": [1, 2, 3, 4, 5, 6, 7], + }, + {"value": "CE-6", "text": "龙门币-6/5", "days": [2, 4, 6, 7]}, + {"value": "AP-5", "text": "红票-5", "days": [1, 4, 6, 7]}, + {"value": "CA-5", "text": "技能-5", "days": [2, 3, 5, 7]}, + {"value": "LS-6", "text": "经验-6/5", "days": [1, 2, 3, 4, 5, 6, 7]}, + {"value": "SK-5", "text": "碳-5", "days": [1, 3, 5, 6]}, + {"value": "PR-A-1", "text": "奶/盾芯片", "days": [1, 4, 5, 7]}, + {"value": "PR-A-2", "text": "奶/盾芯片组", "days": [1, 4, 5, 7]}, + {"value": "PR-B-1", "text": "术/狙芯片", "days": [1, 2, 5, 6]}, + {"value": "PR-B-2", "text": "术/狙芯片组", "days": [1, 2, 5, 6]}, + {"value": "PR-C-1", "text": "先/辅芯片", "days": [3, 4, 6, 7]}, + {"value": "PR-C-2", "text": "先/辅芯片组", "days": [3, 4, 6, 7]}, + {"value": "PR-D-1", "text": "近/特芯片", "days": [2, 3, 6, 7]}, + {"value": "PR-D-2", "text": "近/特芯片组", "days": [2, 3, 6, 7]}, + ] + + for day in range(0, 8): + + today_stage_dict = {"value": [], "text": []} + + for stage_info in stage_daily_info: + + if day in stage_info["days"] or day == 0: + today_stage_dict["value"].append(stage_info["value"]) + today_stage_dict["text"].append(stage_info["text"]) + + self.stage_dict[calendar.day_name[day - 1] if day > 0 else "ALL"] = { + "value": today_stage_dict["value"] + ss_stage_dict["value"], + "text": today_stage_dict["text"] + ss_stage_dict["text"], + } + + self.stage_refreshed.emit() + + logger.success("活动关卡信息更新完成", module="配置管理") + + def server_date(self) -> date: + """ + 获取当前的服务器日期 + + :return: 当前的服务器日期 + :rtype: date + """ + + dt = datetime.now() + if dt.time() < datetime.min.time().replace(hour=4): + dt = dt - timedelta(days=1) + return dt.date() def search_member(self) -> None: - """搜索所有脚本实例""" + """更新脚本实例配置信息""" + logger.info("开始搜索并读入脚本实例配置", module="配置管理") self.member_dict: Dict[ str, Dict[ @@ -1198,7 +1219,20 @@ class AppConfig(GlobalConfig): sorted(self.member_dict.items(), key=lambda x: int(x[0][3:])) ) + logger.success( + f"脚本实例配置搜索完成,共找到 {len(self.member_dict)} 个实例", + module="配置管理", + ) + def search_maa_user(self, name: str) -> None: + """ + 更新指定 MAA 脚本实例的用户信息 + + :param name: 脚本实例名称 + :type name: str + """ + + logger.info(f"开始搜索并读入 MAA 脚本实例 {name} 的用户信息", module="配置管理") user_dict: Dict[str, Dict[str, Union[Path, MaaUserConfig]]] = {} for user_dir in (Config.member_dict[name]["Path"] / "UserData").iterdir(): @@ -1214,7 +1248,22 @@ class AppConfig(GlobalConfig): sorted(user_dict.items(), key=lambda x: int(x[0][3:])) ) + logger.success( + f"MAA 脚本实例 {name} 的用户信息搜索完成,共找到 {len(user_dict)} 个用户", + module="配置管理", + ) + def search_general_sub(self, name: str) -> None: + """ + 更新指定通用脚本实例的子配置信息 + + :param name: 脚本实例名称 + :type name: str + """ + + logger.info( + f"开始搜索并读入通用脚本实例 {name} 的子配置信息", module="配置管理" + ) user_dict: Dict[str, Dict[str, Union[Path, GeneralSubConfig]]] = {} for sub_dir in (Config.member_dict[name]["Path"] / "SubData").iterdir(): @@ -1230,8 +1279,15 @@ class AppConfig(GlobalConfig): sorted(user_dict.items(), key=lambda x: int(x[0][3:])) ) + logger.success( + f"通用脚本实例 {name} 的子配置信息搜索完成,共找到 {len(user_dict)} 个子配置", + module="配置管理", + ) + def search_plan(self) -> None: - """搜索所有计划表""" + """更新计划表配置信息""" + + logger.info("开始搜索并读入计划表配置", module="配置管理") self.plan_dict: Dict[str, Dict[str, Union[str, Path, MaaPlanConfig]]] = {} if (self.app_path / "config/MaaPlanConfig").exists(): @@ -1252,8 +1308,15 @@ class AppConfig(GlobalConfig): sorted(self.plan_dict.items(), key=lambda x: int(x[0][3:])) ) + logger.success( + f"计划表配置搜索完成,共找到 {len(self.plan_dict)} 个计划表", + module="配置管理", + ) + def search_queue(self): - """搜索所有调度队列实例""" + """更新调度队列实例配置信息""" + + logger.info("开始搜索并读入调度队列配置", module="配置管理") self.queue_dict: Dict[str, Dict[str, Union[Path, QueueConfig]]] = {} @@ -1273,8 +1336,20 @@ class AppConfig(GlobalConfig): sorted(self.queue_dict.items(), key=lambda x: int(x[0][5:])) ) + logger.success( + f"调度队列配置搜索完成,共找到 {len(self.queue_dict)} 个调度队列", + module="配置管理", + ) + def change_queue(self, old: str, new: str) -> None: - """修改调度队列配置文件的队列参数""" + """ + 修改调度队列配置文件的队列参数 + + :param old: 旧脚本名 + :param new: 新脚本名 + """ + + logger.info(f"开始修改调度队列参数:{old} -> {new}", module="配置管理") for queue in self.queue_dict.values(): @@ -1299,8 +1374,17 @@ class AppConfig(GlobalConfig): if queue["Config"].get(queue["Config"].queue_Member_10) == old: queue["Config"].set(queue["Config"].queue_Member_10, new) + logger.success(f"调度队列参数修改完成:{old} -> {new}", module="配置管理") + def change_plan(self, old: str, new: str) -> None: - """修改脚本管理所有下属用户的计划表配置参数""" + """ + 修改脚本管理所有下属用户的计划表配置参数 + + :param old: 旧计划表名 + :param new: 新计划表名 + """ + + logger.info(f"开始修改计划表参数:{old} -> {new}", module="配置管理") for member in self.member_dict.values(): @@ -1309,10 +1393,21 @@ class AppConfig(GlobalConfig): if user["Config"].get(user["Config"].Info_StageMode) == old: user["Config"].set(user["Config"].Info_StageMode, new) + logger.success(f"计划表参数修改完成:{old} -> {new}", module="配置管理") + def change_maa_user_info( self, name: str, user_data: Dict[str, Dict[str, Union[str, Path, dict]]] ) -> None: - """代理完成后保存改动的用户信息""" + """ + 保存代理完成后发生改动的用户信息 + + :param name: 脚本实例名称 + :type name: str + :param user_data: 用户信息字典,包含用户名称和对应的配置信息 + :type user_data: Dict[str, Dict[str, Union[str, Path, dict]]] + """ + + logger.info(f"开始保存 MAA 脚本实例 {name} 的用户信息变动", module="配置管理") for user, info in user_data.items(): @@ -1345,10 +1440,21 @@ class AppConfig(GlobalConfig): self.sub_info_changed.emit() + logger.success(f"MAA 脚本实例 {name} 的用户信息变动保存完成", module="配置管理") + def change_general_sub_info( self, name: str, sub_data: Dict[str, Dict[str, Union[str, Path, dict]]] ) -> None: - """代理完成后保存改动的配置信息""" + """ + 保存代理完成后发生改动的配置信息 + + :param name: 脚本实例名称 + :type name: str + :param sub_data: 子配置信息字典,包含子配置名称和对应的配置信息 + :type sub_data: Dict[str, Dict[str, Union[str, Path, dict]]] + """ + + logger.info(f"开始保存通用脚本实例 {name} 的子配置信息变动", module="配置管理") for sub, info in sub_data.items(): @@ -1366,27 +1472,62 @@ class AppConfig(GlobalConfig): self.sub_info_changed.emit() + logger.success( + f"通用脚本实例 {name} 的子配置信息变动保存完成", module="配置管理" + ) + def set_power_sign(self, sign: str) -> None: - """设置当前电源状态""" + """ + 设置当前电源状态 + + :param sign: 电源状态标志 + """ self.power_sign = sign self.power_sign_changed.emit() + logger.info(f"电源状态已更改为: {sign}", module="配置管理") + def save_history(self, key: str, content: dict) -> None: - """保存历史记录""" + """ + 保存历史记录 + + :param key: 调度队列的键 + :type key: str + :param content: 包含时间和历史记录内容的字典 + :type content: dict + """ if key in self.queue_dict: + logger.info(f"保存调度队列 {key} 的历史记录", module="配置管理") self.queue_dict[key]["Config"].set( self.queue_dict[key]["Config"].Data_LastProxyTime, content["Time"] ) self.queue_dict[key]["Config"].set( self.queue_dict[key]["Config"].Data_LastProxyHistory, content["History"] ) + logger.success(f"调度队列 {key} 的历史记录已保存", module="配置管理") else: logger.warning(f"保存历史记录时未找到调度队列: {key}") def save_maa_log(self, log_path: Path, logs: list, maa_result: str) -> bool: - """保存MAA日志并生成对应统计数据""" + """ + 保存MAA日志并生成对应统计数据 + + :param log_path: 日志文件保存路径 + :type log_path: Path + :param logs: 日志内容列表 + :type logs: list + :param maa_result: MAA 结果 + :type maa_result: str + :return: 是否包含6★招募 + :rtype: bool + """ + + logger.info( + f"开始处理 MAA 日志,日志长度: {len(logs)},日志标记:{maa_result}", + module="配置管理", + ) data: Dict[str, Union[str, Dict[str, Union[int, dict]]]] = { "recruit_statistics": defaultdict(int), @@ -1431,15 +1572,17 @@ class AppConfig(GlobalConfig): # 查找所有Fight任务的开始和结束位置 fight_tasks = [] for i, line in enumerate(logs): - if "开始任务: Fight" in line: + if "开始任务: Fight" in line or "开始任务: 刷理智" in line: # 查找对应的任务结束位置 end_index = -1 for j in range(i + 1, len(logs)): - if "完成任务: Fight" in logs[j]: + if "完成任务: Fight" in logs[j] or "完成任务: 刷理智" in logs[j]: end_index = j break # 如果遇到新的Fight任务开始,则当前任务没有正常结束 - if j < len(logs) and "开始任务: Fight" in logs[j]: + if j < len(logs) and ( + "开始任务: Fight" in logs[j] or "开始任务: 刷理智" in logs[j] + ): break # 如果找到了结束位置,记录这个任务的范围 @@ -1505,12 +1648,23 @@ class AppConfig(GlobalConfig): with log_path.with_suffix(".json").open("w", encoding="utf-8") as f: json.dump(data, f, ensure_ascii=False, indent=4) - logger.info(f"处理完成:{log_path}") + logger.success(f"MAA 日志统计完成,日志路径:{log_path}", module="配置管理") return if_six_star def save_general_log(self, log_path: Path, logs: list, general_result: str) -> None: - """保存通用日志并生成对应统计数据""" + """ + 保存通用日志并生成对应统计数据 + + :param log_path: 日志文件保存路径 + :param logs: 日志内容列表 + :param general_result: 待保存的日志结果信息 + """ + + logger.info( + f"开始处理通用日志,日志长度: {len(logs)},日志标记:{general_result}", + module="配置管理", + ) data: Dict[str, str] = {"general_result": general_result} @@ -1521,10 +1675,23 @@ class AppConfig(GlobalConfig): with log_path.with_suffix(".json").open("w", encoding="utf-8") as f: json.dump(data, f, ensure_ascii=False, indent=4) - logger.info(f"处理完成:{log_path}") + logger.success( + f"通用日志统计完成,日志路径:{log_path.with_suffix('.log')}", + module="配置管理", + ) def merge_statistic_info(self, statistic_path_list: List[Path]) -> dict: - """合并指定数据统计信息文件""" + """ + 合并指定数据统计信息文件 + + :param statistic_path_list: 需要合并的统计信息文件路径列表 + :return: 合并后的统计信息字典 + """ + + logger.info( + f"开始合并统计信息文件,共计 {len(statistic_path_list)} 个文件", + module="配置管理", + ) data = {"index": {}} @@ -1591,12 +1758,28 @@ class AppConfig(GlobalConfig): data["index"] = [data["index"][_] for _ in sorted(data["index"])] + logger.success( + f"统计信息合并完成,共计 {len(data['index'])} 条记录", module="配置管理" + ) + return {k: v for k, v in data.items() if v} def search_history( self, mode: str, start_date: datetime, end_date: datetime ) -> dict: - """搜索所有历史记录""" + """ + 搜索指定范围内的历史记录 + + :param mode: 合并模式(按日合并、按周合并、按月合并) + :param start_date: 开始日期 + :param end_date: 结束日期 + :return: 搜索到的历史记录字典 + """ + + logger.info( + f"开始搜索历史记录,合并模式:{mode},日期范围:{start_date} 至 {end_date}", + module="配置管理", + ) history_dict = {} @@ -1638,10 +1821,43 @@ class AppConfig(GlobalConfig): except ValueError: logger.warning(f"非日期格式的目录: {date_folder}") + logger.success( + f"历史记录搜索完成,共计 {len(history_dict)} 条记录", module="配置管理" + ) + return { k: v for k, v in sorted(history_dict.items(), key=lambda x: x[0], reverse=True) } + def clean_old_history(self): + """删除超过用户设定天数的历史记录文件(基于目录日期)""" + + if self.get(self.function_HistoryRetentionTime) == 0: + logger.info("历史记录永久保留,跳过历史记录清理", module="配置管理") + return + + logger.info("开始清理超过设定天数的历史记录", module="配置管理") + + deleted_count = 0 + + for date_folder in (self.app_path / "history").iterdir(): + if not date_folder.is_dir(): + continue # 只处理日期文件夹 + + try: + # 只检查 `YYYY-MM-DD` 格式的文件夹 + folder_date = datetime.strptime(date_folder.name, "%Y-%m-%d") + if datetime.now() - folder_date > timedelta( + days=self.get(self.function_HistoryRetentionTime) + ): + shutil.rmtree(date_folder, ignore_errors=True) + deleted_count += 1 + logger.info(f"已删除超期日志目录: {date_folder}", module="配置管理") + except ValueError: + logger.warning(f"非日期格式的目录: {date_folder}", module="配置管理") + + logger.success(f"清理完成: {deleted_count} 个日期目录", module="配置管理") + Config = AppConfig() diff --git a/app/core/logger.py b/app/core/logger.py new file mode 100644 index 0000000..aa942dd --- /dev/null +++ b/app/core/logger.py @@ -0,0 +1,34 @@ +# AUTO_MAA:A MAA Multi Account Management and Automation Tool +# Copyright © 2024-2025 DLmaster361 + +# This file is part of AUTO_MAA. + +# AUTO_MAA is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published +# by the Free Software Foundation, either version 3 of the License, +# or (at your option) any later version. + +# AUTO_MAA is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty +# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See +# the GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with AUTO_MAA. If not, see . + +# Contact: DLmaster_361@163.com + +""" +AUTO_MAA +AUTO_MAA日志组件 +v4.4 +作者:DLmaster_361 +""" + +from loguru import logger as _logger + +# 设置日志 module 字段默认值 +logger = _logger.patch( + lambda record: record["extra"].setdefault("module", "未知模块") or True +) +logger.remove(0) diff --git a/app/core/main_info_bar.py b/app/core/main_info_bar.py index 715ff25..745050a 100644 --- a/app/core/main_info_bar.py +++ b/app/core/main_info_bar.py @@ -25,10 +25,10 @@ v4.4 作者:DLmaster_361 """ -from loguru import logger from PySide6.QtCore import Qt from qfluentwidgets import InfoBar, InfoBarPosition +from .logger import logger from .config import Config from .sound_player import SoundPlayer @@ -46,20 +46,34 @@ class _MainInfoBar: def push_info_bar( self, mode: str, title: str, content: str, time: int, if_force: bool = False - ): - """推送到信息通知栏""" + ) -> None: + """ + 推送消息到吐司通知栏 + + :param mode: 通知栏模式,支持 "success", "warning", "error", "info" + :param title: 通知栏标题 + :type title: str + :param content: 通知栏内容 + :type content: str + :param time: 显示时长,单位为毫秒 + :type time: int + :param if_force: 是否强制推送 + :type if_force: bool + """ + if Config.main_window is None: - logger.error("信息通知栏未设置父窗口") + logger.error("信息通知栏未设置父窗口", module="吐司通知栏") return None # 根据 mode 获取对应的 InfoBar 方法 info_bar_method = self.mode_mapping.get(mode) if not info_bar_method: - logger.error(f"未知的通知栏模式: {mode}") + logger.error(f"未知的通知栏模式: {mode}", module="吐司通知栏") return None if Config.main_window.isVisible(): + # 主窗口可见时直接推送通知 info_bar_method( title=title, content=content, @@ -69,6 +83,7 @@ class _MainInfoBar: duration=time, parent=Config.main_window, ) + elif if_force: # 如果主窗口不可见且强制推送,则录入消息队列等待窗口显示后推送 info_bar_item = { @@ -80,6 +95,11 @@ class _MainInfoBar: if info_bar_item not in Config.info_bar_list: Config.info_bar_list.append(info_bar_item) + logger.info( + f"主窗口不可见,已将通知栏消息录入队列: {info_bar_item}", + module="吐司通知栏", + ) + if mode == "warning": SoundPlayer.play("发生异常") if mode == "error": diff --git a/app/core/network.py b/app/core/network.py index 003145a..15211ee 100644 --- a/app/core/network.py +++ b/app/core/network.py @@ -25,7 +25,6 @@ v4.4 作者:DLmaster_361 """ -from loguru import logger from PySide6.QtCore import QObject, QThread, QEventLoop import re import time @@ -33,6 +32,8 @@ import requests import truststore from pathlib import Path +from .logger import logger + class NetworkThread(QThread): """网络请求线程类""" @@ -48,6 +49,8 @@ class NetworkThread(QThread): f"NetworkThread-{mode}-{re.sub(r'(&cdk=)[^&]+(&)', r'\1******\2', url)}" ) + logger.info(f"创建网络请求线程: {self.objectName()}", module="网络请求子线程") + self.mode = mode self.url = url self.path = path @@ -65,7 +68,7 @@ class NetworkThread(QThread): self.loop = QEventLoop() - truststore.inject_into_ssl() + truststore.inject_into_ssl() # 信任系统证书 @logger.catch def run(self) -> None: @@ -77,7 +80,13 @@ class NetworkThread(QThread): self.get_file(self.url, self.path) def get_json(self, url: str) -> None: - """通过get方法获取json数据""" + """ + 通过get方法获取json数据 + + :param url: 请求的URL + """ + + logger.info(f"子线程 {self.objectName()} 开始网络请求", module="网络请求子线程") response = None @@ -92,12 +101,23 @@ class NetworkThread(QThread): self.status_code = response.status_code if response else None self.response_json = None self.error_message = str(e) + logger.exception( + f"子线程 {self.objectName()} 网络请求失败:{e}", + module="网络请求子线程", + ) time.sleep(self.backoff_factor) self.loop.quit() def get_file(self, url: str, path: Path) -> None: - """通过get方法下载文件""" + """ + 通过get方法下载文件到指定路径 + + :param url: 请求的URL + :param path: 下载文件的保存路径 + """ + + logger.info(f"子线程 {self.objectName()} 开始下载文件", module="网络请求子线程") response = None @@ -114,12 +134,15 @@ class NetworkThread(QThread): except Exception as e: self.status_code = response.status_code if response else None self.error_message = str(e) + logger.exception( + f"子线程 {self.objectName()} 网络请求失败:{e}", module="网络请求子线程" + ) self.loop.quit() class _Network(QObject): - """网络请求线程类""" + """网络请求线程管理类""" def __init__(self) -> None: super().__init__() @@ -127,7 +150,16 @@ class _Network(QObject): self.task_queue = [] def add_task(self, mode: str, url: str, path: Path = None) -> NetworkThread: - """添加网络请求任务""" + """ + 添加网络请求任务 + + :param mode: 请求模式,支持 "get", "get_file" + :param url: 请求的URL + :param path: 下载文件的保存路径,仅在 mode 为 "get_file" 时有效 + :return: 返回创建的 NetworkThread 实例 + """ + + logger.info(f"添加网络请求任务: {mode} {url} {path}", module="网络请求") network_thread = NetworkThread(mode, url, path) @@ -138,7 +170,12 @@ class _Network(QObject): return network_thread def get_result(self, network_thread: NetworkThread) -> dict: - """获取网络请求结果""" + """ + 获取网络请求结果 + + :param network_thread: 网络请求线程实例 + :return: 包含状态码、响应JSON和错误信息的字典 + """ result = { "status_code": network_thread.status_code, @@ -155,6 +192,11 @@ class _Network(QObject): self.task_queue.remove(network_thread) network_thread.deleteLater() + logger.info( + f"网络请求结果: {result['status_code']},请求子线程已结束", + module="网络请求", + ) + return result diff --git a/app/core/sound_player.py b/app/core/sound_player.py index 422ae0a..e8f5268 100644 --- a/app/core/sound_player.py +++ b/app/core/sound_player.py @@ -25,12 +25,12 @@ v4.4 作者:DLmaster_361 """ -from loguru import logger from PySide6.QtCore import QObject, QUrl from PySide6.QtMultimedia import QSoundEffect from pathlib import Path +from .logger import logger from .config import Config @@ -42,6 +42,11 @@ class _SoundPlayer(QObject): self.sounds_path = Config.app_path / "resources/sounds" def play(self, sound_name: str): + """ + 播放指定名称的音效 + + :param sound_name: 音效文件名(不带扩展名) + """ if not Config.get(Config.voice_Enabled): return @@ -59,6 +64,11 @@ class _SoundPlayer(QObject): ) def play_voice(self, sound_path: Path): + """ + 播放音效文件 + + :param sound_path: 音效文件的完整路径 + """ effect = QSoundEffect(self) effect.setVolume(1) diff --git a/app/core/task_manager.py b/app/core/task_manager.py index 67365b4..566b3ad 100644 --- a/app/core/task_manager.py +++ b/app/core/task_manager.py @@ -25,19 +25,18 @@ v4.4 作者:DLmaster_361 """ -from loguru import logger from PySide6.QtCore import QThread, QObject, Signal from qfluentwidgets import MessageBox from datetime import datetime from packaging import version from typing import Dict, Union +from .logger import logger from .config import Config from .main_info_bar import MainInfoBar from .network import Network from .sound_player import SoundPlayer from app.models import MaaManager, GeneralManager -from app.services import System class Task(QThread): @@ -77,7 +76,7 @@ class Task(QThread): if "设置MAA" in self.mode: - logger.info(f"任务开始:设置{self.name}") + logger.info(f"任务开始:设置{self.name}", module=f"业务 {self.name}") self.push_info_bar.emit("info", "设置MAA", self.name, 3000) self.task = MaaManager( @@ -93,12 +92,14 @@ class Task(QThread): try: self.task.run() except Exception as e: - logger.exception(f"任务异常:{self.name},错误信息:{e}") + logger.exception( + f"任务异常:{self.name},错误信息:{e}", module=f"业务 {self.name}" + ) self.push_info_bar.emit("error", "任务异常", self.name, -1) elif self.mode == "设置通用脚本": - logger.info(f"任务开始:设置{self.name}") + logger.info(f"任务开始:设置{self.name}", module=f"业务 {self.name}") self.push_info_bar.emit("info", "设置通用脚本", self.name, 3000) self.task = GeneralManager( @@ -113,11 +114,14 @@ class Task(QThread): try: self.task.run() except Exception as e: - logger.exception(f"任务异常:{self.name},错误信息:{e}") + logger.exception( + f"任务异常:{self.name},错误信息:{e}", module=f"业务 {self.name}" + ) self.push_info_bar.emit("error", "任务异常", self.name, -1) else: + logger.info(f"任务开始:{self.name}", module=f"业务 {self.name}") self.task_list = [ [ ( @@ -144,16 +148,21 @@ class Task(QThread): task[1] = "运行" self.update_task_list.emit(self.task_list) + # 检查任务是否在运行列表中 if task[2] in Config.running_list: task[1] = "跳过" self.update_task_list.emit(self.task_list) - logger.info(f"跳过任务:{task[0]}") + logger.info( + f"跳过任务:{task[0]},该任务已在运行列表中", + module=f"业务 {self.name}", + ) self.push_info_bar.emit("info", "跳过任务", task[0], 3000) continue + # 标记为运行中 Config.running_list.append(task[2]) - logger.info(f"任务开始:{task[0]}") + logger.info(f"任务开始:{task[0]}", module=f"业务 {self.name}") self.push_info_bar.emit("info", "任务开始", task[0], 3000) if Config.member_dict[task[2]]["Type"] == "Maa": @@ -198,11 +207,11 @@ class Task(QThread): ) try: - self.task.run() + self.task.run() # 运行任务业务 task[1] = "完成" self.update_task_list.emit(self.task_list) - logger.info(f"任务完成:{task[0]}") + logger.info(f"任务完成:{task[0]}", module=f"业务 {self.name}") self.push_info_bar.emit("info", "任务完成", task[0], 3000) except Exception as e: @@ -217,15 +226,29 @@ class Task(QThread): task[1] = "异常" self.update_task_list.emit(self.task_list) - logger.exception(f"任务异常:{task[0]},错误信息:{e}") + logger.exception( + f"任务异常:{task[0]},错误信息:{e}", + module=f"业务 {self.name}", + ) self.push_info_bar.emit("error", "任务异常", task[0], -1) + # 任务结束后从运行列表中移除 Config.running_list.remove(task[2]) self.accomplish.emit(self.logs) def task_accomplish(self, name: str, log: dict): - """保存任务结果""" + """ + 销毁任务线程并保存任务结果 + + :param name: 任务名称 + :param log: 任务日志记录 + """ + + logger.info( + f"任务完成:{name},日志记录:{list(log.values())}", + module=f"业务 {self.name}", + ) self.logs.append([name, log]) self.task.deleteLater() @@ -245,7 +268,13 @@ class _TaskManager(QObject): def add_task( self, mode: str, name: str, info: Dict[str, Dict[str, Union[str, int, bool]]] ): - """添加任务""" + """ + 添加任务 + + :param mode: 任务模式 + :param name: 任务名称 + :param info: 任务信息 + """ if name in Config.running_list or name in self.task_dict: @@ -253,11 +282,14 @@ class _TaskManager(QObject): MainInfoBar.push_info_bar("warning", "任务已存在", name, 5000) return None - logger.info(f"任务开始:{name}") + logger.info(f"任务开始:{name},模式:{mode}", module="业务调度") MainInfoBar.push_info_bar("info", "任务开始", name, 3000) SoundPlayer.play("任务开始") + # 标记任务为运行中 Config.running_list.append(name) + + # 创建任务实例并连接信号 self.task_dict[name] = Task(mode, name, info) self.task_dict[name].check_maa_version.connect(self.check_maa_version) self.task_dict[name].question.connect( @@ -273,18 +305,24 @@ class _TaskManager(QObject): lambda logs: self.remove_task(mode, name, logs) ) + # 向UI发送信号以创建或连接GUI if "新调度台" in mode: self.create_gui.emit(self.task_dict[name]) elif "主调度台" in mode: self.connect_gui.emit(self.task_dict[name]) + # 启动任务线程 self.task_dict[name].start() - def stop_task(self, name: str): - """中止任务""" + def stop_task(self, name: str) -> None: + """ + 中止任务 - logger.info(f"中止任务:{name}") + :param name: 任务名称 + """ + + logger.info(f"中止任务:{name}", module="业务调度") MainInfoBar.push_info_bar("info", "中止任务", name, 3000) if name == "ALL": @@ -303,19 +341,27 @@ class _TaskManager(QObject): self.task_dict[name].quit() self.task_dict[name].wait() - def remove_task(self, mode: str, name: str, logs: list): - """任务结束后的处理""" + def remove_task(self, mode: str, name: str, logs: list) -> None: + """ + 处理任务结束后的收尾工作 - logger.info(f"任务结束:{name}") + :param mode: 任务模式 + :param name: 任务名称 + :param logs: 任务日志 + """ + + logger.info(f"任务结束:{name}", module="业务调度") MainInfoBar.push_info_bar("info", "任务结束", name, 3000) SoundPlayer.play("任务结束") + # 删除任务线程,移除运行中标记 self.task_dict[name].deleteLater() self.task_dict.pop(name) Config.running_list.remove(name) if "调度队列" in name and "人工排查" not in mode: + # 保存调度队列历史记录 if len(logs) > 0: time = logs[0][1]["Time"] history = "" @@ -331,6 +377,7 @@ class _TaskManager(QObject): }, ) + # 根据调度队列情况设置电源状态 if ( Config.queue_dict[name]["Config"].get( Config.queue_dict[name]["Config"].queueSet_AfterAccomplish @@ -347,9 +394,14 @@ class _TaskManager(QObject): if Config.args.mode == "cli" and Config.power_sign == "NoAction": Config.set_power_sign("KillSelf") - def check_maa_version(self, v: str): - """检查MAA版本""" + def check_maa_version(self, v: str) -> None: + """ + 检查MAA版本,如果版本过低则推送通知 + :param v: 当前MAA版本 + """ + + logger.info(f"检查MAA版本:{v}", module="业务调度") network = Network.add_task( mode="get", url="https://mirrorchyan.com/api/resources/MAA/latest?user_agent=AutoMaaGui&os=win&arch=x64&channel=stable", @@ -359,7 +411,10 @@ class _TaskManager(QObject): if network_result["status_code"] == 200: maa_info = network_result["response_json"] else: - logger.warning(f"获取MAA版本信息时出错:{network_result['error_message']}") + logger.warning( + f"获取MAA版本信息时出错:{network_result['error_message']}", + module="业务调度", + ) MainInfoBar.push_info_bar( "warning", "获取MAA版本信息时出错", @@ -371,7 +426,8 @@ class _TaskManager(QObject): if version.parse(maa_info["data"]["version_name"]) > version.parse(v): logger.info( - f"检测到MAA版本过低:{v},最新版本:{maa_info['data']['version_name']}" + f"检测到MAA版本过低:{v},最新版本:{maa_info['data']['version_name']}", + module="业务调度", ) MainInfoBar.push_info_bar( "info", @@ -380,8 +436,19 @@ class _TaskManager(QObject): -1, ) + logger.success( + f"MAA版本检查完成:{v},最新版本:{maa_info['data']['version_name']}", + module="业务调度", + ) + def push_dialog(self, name: str, title: str, content: str): - """推送对话框""" + """ + 推送来自任务线程的对话框 + + :param name: 任务名称 + :param title: 对话框标题 + :param content: 对话框内容 + """ choice = MessageBox(title, content, Config.main_window) choice.yesButton.setText("是") diff --git a/app/core/timer.py b/app/core/timer.py index 4566d4d..537ce09 100644 --- a/app/core/timer.py +++ b/app/core/timer.py @@ -25,12 +25,11 @@ v4.4 作者:DLmaster_361 """ -from loguru import logger from PySide6.QtCore import QObject, QTimer from datetime import datetime -from pathlib import Path import keyboard +from .logger import logger from .config import Config from .task_manager import TaskManager from app.services import System @@ -45,14 +44,31 @@ class _MainTimer(QObject): self.Timer.timeout.connect(self.timed_start) self.Timer.timeout.connect(self.set_silence) self.Timer.timeout.connect(self.check_power) - self.Timer.start(1000) + self.LongTimer = QTimer() self.LongTimer.timeout.connect(self.long_timed_task) + + def start(self): + """启动定时器""" + + logger.info("启动主定时器", module="主业务定时器") + self.Timer.start(1000) self.LongTimer.start(3600000) + def stop(self): + """停止定时器""" + + logger.info("停止主定时器", module="主业务定时器") + self.Timer.stop() + self.Timer.deleteLater() + self.LongTimer.stop() + self.LongTimer.deleteLater() + def long_timed_task(self): """长时间定期检定任务""" + logger.info("执行长时间定期检定任务", module="主业务定时器") + Config.get_stage() Config.main_window.setting.show_notice() if Config.get(Config.update_IfAutoUpdate): @@ -82,7 +98,7 @@ class _MainTimer(QObject): and name not in Config.running_list ): - logger.info(f"定时任务:{name}") + logger.info(f"定时唤起任务:{name}。", module="主业务定时器") TaskManager.add_task("自动代理_新调度台", name, data) def set_silence(self): @@ -109,13 +125,20 @@ class _MainTimer(QObject): for _ in Config.get(Config.function_BossKey).split("+") ) ) + logger.info( + f"模拟按键:{Config.get(Config.function_BossKey)}", + module="主业务定时器", + ) except Exception as e: - logger.error(f"模拟按键时出错:{e}") + logger.exception(f"模拟按键时出错:{e}", module="主业务定时器") def check_power(self): + """检查电源操作""" if Config.power_sign != "NoAction" and not Config.running_list: + logger.info(f"触发电源操作:{Config.power_sign}", module="主业务定时器") + from app.ui import ProgressRingMessageBox mode_book = { @@ -129,9 +152,13 @@ class _MainTimer(QObject): Config.main_window, f"{mode_book[Config.power_sign]}倒计时" ) if choice.exec(): + logger.info( + f"确认执行电源操作:{Config.power_sign}", module="主业务定时器" + ) System.set_power(Config.power_sign) Config.set_power_sign("NoAction") else: + logger.info(f"取消电源操作:{Config.power_sign}", module="主业务定时器") Config.set_power_sign("NoAction") diff --git a/app/models/MAA.py b/app/models/MAA.py index 974ba65..3f7c2ef 100644 --- a/app/models/MAA.py +++ b/app/models/MAA.py @@ -25,7 +25,6 @@ v4.4 作者:DLmaster_361 """ -from loguru import logger from PySide6.QtCore import QObject, Signal, QEventLoop, QFileSystemWatcher, QTimer import json import subprocess @@ -38,7 +37,7 @@ from pathlib import Path from jinja2 import Environment, FileSystemLoader from typing import Union, List, Dict -from app.core import Config, MaaConfig, MaaUserConfig +from app.core import Config, MaaConfig, MaaUserConfig, logger from app.services import Notify, Crypto, System, skland_sign_in from app.utils import ProcessManager @@ -77,19 +76,21 @@ class MaaManager(QObject): self.user_list = "" self.mode = mode self.config_path = config["Path"] + self.name = config["Config"].get(config["Config"].MaaSet_Name) self.user_config_path = user_config_path self.emulator_process_manager = ProcessManager() self.maa_process_manager = ProcessManager() self.log_monitor = QFileSystemWatcher() + self.log_monitor.fileChanged.connect(self.check_maa_log) self.log_monitor_timer = QTimer() self.log_monitor_timer.timeout.connect(self.refresh_maa_log) self.monitor_loop = QEventLoop() + self.log_start_time = datetime.now() + self.log_check_mode = None - self.maa_process_manager.processClosed.connect( - lambda: self.log_monitor.fileChanged.emit("进程结束检查") - ) + self.maa_process_manager.processClosed.connect(self.check_maa_log) self.question_loop = QEventLoop() self.question_response.connect(self.__capture_response) @@ -118,10 +119,14 @@ class MaaManager(QObject): self.data = dict(sorted(self.data.items(), key=lambda x: int(x[0][3:]))) + logger.success( + f"MAA控制器初始化完成,当前模式: {self.mode}", + module=f"MAA调度器-{self.name}", + ) + def configure(self): """提取配置信息""" - self.name = self.set["MaaSet"]["Name"] self.maa_root_path = Path(self.set["MaaSet"]["Path"]) self.maa_set_path = self.maa_root_path / "config/gui.json" self.maa_log_path = self.maa_root_path / "debug/gui.log" @@ -132,6 +137,8 @@ class MaaManager(QObject): for i in range(0, 2 * self.set["RunSet"]["ADBSearchRange"]) ] + logger.success("MAA配置提取完成", module=f"MAA调度器-{self.name}") + def run(self): """主进程,运行MAA代理进程""" @@ -143,7 +150,9 @@ class MaaManager(QObject): # 检查MAA路径是否可用 if not self.maa_exe_path.exists() or not self.maa_set_path.exists(): - logger.error("未正确配置MAA路径,MAA代理进程中止") + logger.error( + "未正确配置MAA路径,MAA代理进程中止", module=f"MAA调度器-{self.name}" + ) self.push_info_bar.emit( "error", "启动MAA代理进程失败", "您还未正确配置MAA路径!", -1 ) @@ -174,6 +183,11 @@ class MaaManager(QObject): ] self.create_user_list.emit(self.user_list) + logger.info( + f"用户列表创建完成,已筛选用户数:{len(self.user_list)}", + module=f"MAA调度器-{self.name}", + ) + # 自动代理模式 if self.mode == "自动代理": @@ -208,7 +222,7 @@ class MaaManager(QObject): self.update_user_list.emit(self.user_list) continue - logger.info(f"{self.name} | 开始代理用户: {user[0]}") + logger.info(f"开始代理用户: {user[0]}", module=f"MAA调度器-{self.name}") # 初始化代理情况记录和模式替换表 run_book = {"Annihilation": False, "Routine": False} @@ -244,7 +258,8 @@ class MaaManager(QObject): if type != "总计" and len(user_list) > 0: logger.info( - f"{self.name} | 用户: {user[0]} - 森空岛签到{type}: {'、'.join(user_list)}" + f"用户: {user[0]} - 森空岛签到{type}: {'、'.join(user_list)}", + module=f"MAA调度器-{self.name}", ) self.push_info_bar.emit( "info", @@ -255,10 +270,7 @@ class MaaManager(QObject): if skland_result["总计"] == 0: self.push_info_bar.emit( - "info", - "森空岛签到失败", - user[0], - -1, + "info", "森空岛签到失败", user[0], -1 ) if ( @@ -274,7 +286,8 @@ class MaaManager(QObject): elif user_data["Info"]["IfSkland"]: logger.warning( - f"{self.name} | 用户: {user[0]} - 未配置森空岛签到Token,跳过森空岛签到" + f"用户: {user[0]} - 未配置森空岛签到Token,跳过森空岛签到", + module=f"MAA调度器-{self.name}", ) self.push_info_bar.emit( "warning", "森空岛签到失败", "未配置鹰角网络通行证登录凭证", -1 @@ -296,7 +309,8 @@ class MaaManager(QObject): == datetime.strptime(curdate, "%Y-%m-%d").isocalendar()[:2] ): logger.info( - f"{self.name} | 用户: {user_data["Info"]["Name"]} - 本周剿灭模式已达上限,跳过执行剿灭任务" + f"用户: {user_data['Info']['Name']} - 本周剿灭模式已达上限,跳过执行剿灭任务", + module=f"MAA调度器-{self.name}", ) run_book[mode] = True continue @@ -313,7 +327,8 @@ class MaaManager(QObject): self.data[user[2]]["Path"] / f"{mode}/gui.json" ).exists(): logger.error( - f"{self.name} | 用户: {user[0]} - 未找到{mode_book[mode][5:7]}配置文件" + f"用户: {user[0]} - 未找到{mode_book[mode][5:7]}配置文件", + module=f"MAA调度器-{self.name}", ) self.push_info_bar.emit( "error", @@ -399,6 +414,11 @@ class MaaManager(QObject): ], } + logger.info( + f"用户: {user[0]} - 模式: {mode_book[mode]} - 任务列表: {list(self.task_dict.values())}", + module=f"MAA调度器-{self.name}", + ) + # 尝试次数循环 for i in range(self.set["RunSet"]["RunTimesLimit"]): @@ -409,13 +429,13 @@ class MaaManager(QObject): break logger.info( - f"{self.name} | 用户: {user[0]} - 模式: {mode_book[mode]} - 尝试次数: {i + 1}/{self.set["RunSet"]["RunTimesLimit"]}" + f"用户: {user[0]} - 模式: {mode_book[mode]} - 尝试次数: {i + 1}/{self.set["RunSet"]["RunTimesLimit"]}" ) # 配置MAA set = self.set_maa(mode_book[mode], user[2]) # 记录当前时间 - start_time = datetime.now() + self.log_start_time = datetime.now() # 记录模拟器与ADB路径 self.emulator_path = Path( @@ -435,8 +455,9 @@ class MaaManager(QObject): self.emulator_path = Path(shortcut.TargetPath) self.emulator_arguments = shortcut.Arguments.split() except Exception as e: - logger.error( - f"{self.name} | 解析快捷方式时出现异常:{e}" + logger.exception( + f"解析快捷方式时出现异常:{e}", + module=f"MAA调度器-{self.name}", ) self.push_info_bar.emit( "error", @@ -448,7 +469,8 @@ class MaaManager(QObject): break elif not self.emulator_path.exists(): logger.error( - f"{self.name} | 模拟器快捷方式不存在:{self.emulator_path}" + f"模拟器快捷方式不存在:{self.emulator_path}", + module=f"MAA调度器-{self.name}", ) self.push_info_bar.emit( "error", @@ -489,16 +511,25 @@ class MaaManager(QObject): # 任务开始前释放ADB try: - logger.info(f"{self.name} | 释放ADB:{self.ADB_address}") + logger.info( + f"释放ADB:{self.ADB_address}", + module=f"MAA调度器-{self.name}", + ) subprocess.run( [self.ADB_path, "disconnect", self.ADB_address], creationflags=subprocess.CREATE_NO_WINDOW, ) except subprocess.CalledProcessError as e: # 忽略错误,因为可能本来就没有连接 - logger.warning(f"{self.name} | 释放ADB时出现异常:{e}") + logger.warning( + f"释放ADB时出现异常:{e}", + module=f"MAA调度器-{self.name}", + ) except Exception as e: - logger.error(f"{self.name} | 释放ADB时出现异常:{e}") + logger.exception( + f"释放ADB时出现异常:{e}", + module=f"MAA调度器-{self.name}", + ) self.push_info_bar.emit( "error", "释放ADB时出现异常", @@ -509,13 +540,17 @@ class MaaManager(QObject): if self.if_open_emulator_process: try: logger.info( - f"{self.name} | 启动模拟器:{self.emulator_path},参数:{self.emulator_arguments}" + f"启动模拟器:{self.emulator_path},参数:{self.emulator_arguments}", + module=f"MAA调度器-{self.name}", ) self.emulator_process_manager.open_process( self.emulator_path, self.emulator_arguments, 0 ) except Exception as e: - logger.error(f"{self.name} | 启动模拟器时出现异常:{e}") + logger.exception( + f"启动模拟器时出现异常:{e}", + module=f"MAA调度器-{self.name}", + ) self.push_info_bar.emit( "error", "启动模拟器时出现异常", @@ -526,14 +561,24 @@ class MaaManager(QObject): break # 添加静默进程标记 + logger.info( + f"添加静默进程标记:{self.emulator_path}", + module=f"MAA调度器-{self.name}", + ) Config.silence_list.append(self.emulator_path) self.search_ADB_address() # 创建MAA任务 - self.maa_process_manager.open_process(self.maa_exe_path, [], 10) + logger.info( + f"启动MAA进程:{self.maa_exe_path}", + module=f"MAA调度器-{self.name}", + ) + self.maa_process_manager.open_process(self.maa_exe_path, [], 0) + # 监测MAA运行状态 - self.start_monitor(start_time, mode_book[mode]) + self.log_check_mode = mode_book[mode] + self.start_monitor() if self.maa_result == "Success!": @@ -541,30 +586,41 @@ class MaaManager(QObject): run_book[mode] = True # 从配置文件中解析所需信息 - with self.maa_set_path.open( - mode="r", encoding="utf-8" - ) as f: - data = json.load(f) + while not self.isInterruptionRequested: + try: + with self.maa_set_path.open( + mode="r", encoding="utf-8" + ) as f: + data = json.load(f) + break + except Exception as e: + logger.exception( + f"解析MAA配置文件时出现异常:{e}", + module=f"MAA调度器-{self.name}", + ) + self.sleep(1) - # 记录自定义基建索引 - user_data["Data"]["CustomInfrastPlanIndex"] = data[ - "Configurations" - ]["Default"]["Infrast.CustomInfrastPlanIndex"] + if not self.isInterruptionRequested: + # 记录自定义基建索引 + user_data["Data"]["CustomInfrastPlanIndex"] = data[ + "Configurations" + ]["Default"]["Infrast.CustomInfrastPlanIndex"] - # 记录更新包路径 - if ( - data["Global"]["VersionUpdate.package"] - and ( - self.maa_root_path - / data["Global"]["VersionUpdate.package"] - ).exists() - ): - self.maa_update_package = data["Global"][ - "VersionUpdate.package" - ] + # 记录更新包路径 + if ( + data["Global"]["VersionUpdate.package"] + and ( + self.maa_root_path + / data["Global"]["VersionUpdate.package"] + ).exists() + ): + self.maa_update_package = data["Global"][ + "VersionUpdate.package" + ] logger.info( - f"{self.name} | 用户: {user[0]} - MAA进程完成代理任务" + f"用户: {user[0]} - MAA进程完成代理任务", + module=f"MAA调度器-{self.name}", ) self.update_log_text.emit( "检测到MAA进程完成代理任务\n正在等待相关程序结束\n请等待10s" @@ -573,7 +629,8 @@ class MaaManager(QObject): self.sleep(10) else: logger.error( - f"{self.name} | 用户: {user[0]} - 代理任务异常: {self.maa_result}" + f"用户: {user[0]} - 代理任务异常: {self.maa_result}", + module=f"MAA调度器-{self.name}", ) # 打印中止信息 # 此时,log变量内存储的就是出现异常的日志信息,可以保存或发送用于问题排查 @@ -581,37 +638,55 @@ class MaaManager(QObject): f"{self.maa_result}\n正在中止相关程序\n请等待10s" ) # 无命令行中止MAA与其子程序 + logger.info( + f"中止MAA进程:{self.maa_exe_path}", + module=f"MAA调度器-{self.name}", + ) self.maa_process_manager.kill(if_force=True) System.kill_process(self.maa_exe_path) # 中止模拟器进程 + logger.info( + f"中止模拟器进程:{list(self.emulator_process_manager.tracked_pids)}", + module=f"MAA调度器-{self.name}", + ) self.emulator_process_manager.kill() self.if_open_emulator = True # 从配置文件中解析所需信息 - with self.maa_set_path.open( - mode="r", encoding="utf-8" - ) as f: - data = json.load(f) + while not self.isInterruptionRequested: + try: + with self.maa_set_path.open( + mode="r", encoding="utf-8" + ) as f: + data = json.load(f) + break + except Exception as e: + logger.exception( + f"解析MAA配置文件时出现异常:{e}", + module=f"MAA调度器-{self.name}", + ) + self.sleep(1) - # 记录自定义基建索引 - if self.task_dict["Base"] == "False": - user_data["Data"]["CustomInfrastPlanIndex"] = data[ - "Configurations" - ]["Default"]["Infrast.CustomInfrastPlanIndex"] + if not self.isInterruptionRequested: + # 记录自定义基建索引 + if self.task_dict["Base"] == "False": + user_data["Data"]["CustomInfrastPlanIndex"] = data[ + "Configurations" + ]["Default"]["Infrast.CustomInfrastPlanIndex"] - # 记录更新包路径 - if ( - data["Global"]["VersionUpdate.package"] - and ( - self.maa_root_path - / data["Global"]["VersionUpdate.package"] - ).exists() - ): - self.maa_update_package = data["Global"][ - "VersionUpdate.package" - ] + # 记录更新包路径 + if ( + data["Global"]["VersionUpdate.package"] + and ( + self.maa_root_path + / data["Global"]["VersionUpdate.package"] + ).exists() + ): + self.maa_update_package = data["Global"][ + "VersionUpdate.package" + ] # 推送异常通知 Notify.push_plyer( @@ -628,16 +703,25 @@ class MaaManager(QObject): # 任务结束后释放ADB try: - logger.info(f"{self.name} | 释放ADB:{self.ADB_address}") + logger.info( + f"释放ADB:{self.ADB_address}", + module=f"MAA调度器-{self.name}", + ) subprocess.run( [self.ADB_path, "disconnect", self.ADB_address], creationflags=subprocess.CREATE_NO_WINDOW, ) except subprocess.CalledProcessError as e: # 忽略错误,因为可能本来就没有连接 - logger.warning(f"{self.name} | 释放ADB时出现异常:{e}") + logger.warning( + f"释放ADB时出现异常:{e}", + module=f"MAA调度器-{self.name}", + ) except Exception as e: - logger.error(f"{self.name} | 释放ADB时出现异常:{e}") + logger.exception( + f"释放ADB时出现异常:{e}", + module=f"MAA调度器-{self.name}", + ) self.push_info_bar.emit( "error", "释放ADB时出现异常", @@ -646,6 +730,10 @@ class MaaManager(QObject): ) # 任务结束后再次手动中止模拟器进程,防止退出不彻底 if self.if_kill_emulator: + logger.info( + f"任务结束后再次中止模拟器进程:{list(self.emulator_process_manager.tracked_pids)}", + module=f"MAA调度器-{self.name}", + ) self.emulator_process_manager.kill() self.if_open_emulator = True @@ -658,13 +746,13 @@ class MaaManager(QObject): # 保存运行日志以及统计信息 if_six_star = Config.save_maa_log( Config.app_path - / f"history/{curdate}/{user_data["Info"]["Name"]}/{start_time.strftime("%H-%M-%S")}.log", - self.check_maa_log(start_time, mode_book[mode]), + / f"history/{curdate}/{user_data["Info"]["Name"]}/{self.log_start_time.strftime("%H-%M-%S")}.log", + self.check_maa_log(), self.maa_result, ) user_logs_list.append( Config.app_path - / f"history/{curdate}/{user_data["Info"]["Name"]}/{start_time.strftime("%H-%M-%S")}.json", + / f"history/{curdate}/{user_data["Info"]["Name"]}/{self.log_start_time.strftime("%H-%M-%S")}.json", ) if if_six_star: self.push_notification( @@ -681,7 +769,8 @@ class MaaManager(QObject): if self.maa_update_package: logger.info( - f"{self.name} | 检测到MAA更新,正在执行更新动作" + f"检测到MAA更新,正在执行更新动作", + module=f"MAA调度器-{self.name}", ) self.update_log_text.emit( @@ -698,7 +787,9 @@ class MaaManager(QObject): self.maa_update_package = "" - logger.info(f"{self.name} | 更新动作结束") + logger.info( + f"更新动作结束", module=f"MAA调度器-{self.name}" + ) # 发送统计信息 statistics = Config.merge_statistic_info(user_logs_list) @@ -727,6 +818,10 @@ class MaaManager(QObject): user_data["Info"]["RemainedDay"] -= 1 user_data["Data"]["ProxyTimes"] += 1 user[1] = "完成" + logger.success( + f"用户 {user[0]} 的自动代理任务已完成", + module=f"MAA调度器-{self.name}", + ) Notify.push_plyer( "成功完成一个自动代理任务!", f"已完成用户 {user[0].replace("_", " 今天的")}任务", @@ -735,6 +830,10 @@ class MaaManager(QObject): ) else: # 录入代理失败的用户 + logger.error( + f"用户 {user[0]} 的自动代理任务未完成", + module=f"MAA调度器-{self.name}", + ) user[1] = "异常" self.update_user_list.emit(self.user_list) @@ -743,6 +842,9 @@ class MaaManager(QObject): elif self.mode == "人工排查": # 人工排查时,屏蔽静默操作 + logger.info( + "人工排查任务开始,屏蔽静默操作", module=f"MAA调度器-{self.name}" + ) Config.if_ignore_silence = True # 标记是否需要启动模拟器 @@ -759,7 +861,7 @@ class MaaManager(QObject): if self.isInterruptionRequested: break - logger.info(f"{self.name} | 开始排查用户: {user[0]}") + logger.info(f"开始排查用户: {user[0]}", module=f"MAA调度器-{self.name}") user[1] = "运行" self.update_user_list.emit(self.user_list) @@ -776,27 +878,38 @@ class MaaManager(QObject): self.set_maa("人工排查", user[2]) # 记录当前时间 - start_time = datetime.now() + self.log_start_time = datetime.now() # 创建MAA任务 - self.maa_process_manager.open_process(self.maa_exe_path, [], 10) + logger.info( + f"启动MAA进程:{self.maa_exe_path}", + module=f"MAA调度器-{self.name}", + ) + self.maa_process_manager.open_process(self.maa_exe_path, [], 0) # 监测MAA运行状态 - self.start_monitor(start_time, "人工排查") + self.log_check_mode = "人工排查" + self.start_monitor() if self.maa_result == "Success!": logger.info( - f"{self.name} | 用户: {user[0]} - MAA进程成功登录PRTS" + f"用户: {user[0]} - MAA进程成功登录PRTS", + module=f"MAA调度器-{self.name}", ) run_book[0] = True self.update_log_text.emit("检测到MAA进程成功登录PRTS") else: logger.error( - f"{self.name} | 用户: {user[0]} - MAA未能正确登录到PRTS: {self.maa_result}" + f"用户: {user[0]} - MAA未能正确登录到PRTS: {self.maa_result}", + module=f"MAA调度器-{self.name}", ) self.update_log_text.emit( f"{self.maa_result}\n正在中止相关程序\n请等待10s" ) # 无命令行中止MAA与其子程序 + logger.info( + f"中止MAA进程:{self.maa_exe_path}", + module=f"MAA调度器-{self.name}", + ) self.maa_process_manager.kill(if_force=True) System.kill_process(self.maa_exe_path) self.if_open_emulator = True @@ -825,17 +938,25 @@ class MaaManager(QObject): # 结果录入 if run_book[0] and run_book[1]: - logger.info(f"{self.name} | 用户 {user[0]} 通过人工排查") + logger.info( + f"用户 {user[0]} 通过人工排查", module=f"MAA调度器-{self.name}" + ) user_data["Data"]["IfPassCheck"] = True user[1] = "完成" else: - logger.info(f"{self.name} | 用户 {user[0]} 未通过人工排查") + logger.info( + f"用户 {user[0]} 未通过人工排查", + module=f"MAA调度器-{self.name}", + ) user_data["Data"]["IfPassCheck"] = False user[1] = "异常" self.update_user_list.emit(self.user_list) # 解除静默操作屏蔽 + logger.info( + "人工排查任务结束,解除静默操作屏蔽", module=f"MAA调度器-{self.name}" + ) Config.if_ignore_silence = False # 设置MAA模式 @@ -844,20 +965,32 @@ class MaaManager(QObject): # 配置MAA self.set_maa(self.mode, "") # 创建MAA任务 - self.maa_process_manager.open_process(self.maa_exe_path, [], 10) + logger.info( + f"启动MAA进程:{self.maa_exe_path}", module=f"MAA调度器-{self.name}" + ) + self.maa_process_manager.open_process(self.maa_exe_path, [], 0) # 记录当前时间 - start_time = datetime.now() + self.log_start_time = datetime.now() # 监测MAA运行状态 - self.start_monitor(start_time, "设置MAA") + self.log_check_mode = "设置MAA" + self.start_monitor() if "全局" in self.mode: (self.config_path / "Default").mkdir(parents=True, exist_ok=True) shutil.copy(self.maa_set_path, self.config_path / "Default") + logger.success( + f"全局MAA配置文件已保存到 {self.config_path / 'Default/gui.json'}", + module=f"MAA调度器-{self.name}", + ) elif "用户" in self.mode: self.user_config_path.mkdir(parents=True, exist_ok=True) shutil.copy(self.maa_set_path, self.user_config_path) + logger.success( + f"用户MAA配置文件已保存到 {self.user_config_path}", + module=f"MAA调度器-{self.name}", + ) result_text = "" @@ -866,10 +999,18 @@ class MaaManager(QObject): # 关闭可能未正常退出的MAA进程 if self.isInterruptionRequested: + logger.info( + f"关闭可能未正常退出的MAA进程:{self.maa_exe_path}", + module=f"MAA调度器-{self.name}", + ) self.maa_process_manager.kill(if_force=True) System.kill_process(self.maa_exe_path) # 复原MAA配置文件 + logger.info( + f"复原MAA配置文件:{self.maa_set_path}", + module=f"MAA调度器-{self.name}", + ) shutil.copy(self.config_path / "Default/gui.json", self.maa_set_path) # 更新用户数据 @@ -930,7 +1071,9 @@ class MaaManager(QObject): self.accomplish.emit({"Time": begin_time, "History": result_text}) def requestInterruption(self) -> None: - logger.info(f"{self.name} | 收到任务中止申请") + """请求中止任务""" + + logger.info(f"收到任务中止申请", module=f"MAA调度器-{self.name}") if len(self.log_monitor.files()) != 0: self.interrupt.emit() @@ -940,17 +1083,25 @@ class MaaManager(QObject): self.wait_loop.quit() def push_question(self, title: str, message: str) -> bool: + """推送询问窗口""" + + logger.info( + f"推送询问窗口:{title} - {message}", module=f"MAA调度器-{self.name}" + ) self.question.emit(title, message) self.question_loop.exec() return self.response def __capture_response(self, response: bool) -> None: + """捕获询问窗口的响应""" + logger.info(f"捕获询问窗口响应:{response}", module=f"MAA调度器-{self.name}") self.response = response def sleep(self, time: int) -> None: """非阻塞型等待""" + logger.info(f"等待 {time} 秒", module=f"MAA调度器-{self.name}") QTimer.singleShot(time * 1000, self.wait_loop.quit) self.wait_loop.exec() @@ -970,6 +1121,10 @@ class MaaManager(QObject): QTimer.singleShot( 10000, partial(Config.silence_list.remove, self.emulator_path) ) + logger.info( + f"10s后移除静默进程标记:{self.emulator_path}", + module=f"MAA调度器-{self.name}", + ) if "-" in self.ADB_address: ADB_ip = f"{self.ADB_address.split("-")[0]}-" @@ -980,7 +1135,8 @@ class MaaManager(QObject): ADB_port = int(self.ADB_address.split(":")[1]) logger.info( - f"{self.name} | 正在搜索ADB实际地址,ADB前缀:{ADB_ip},初始端口:{ADB_port},搜索范围:{self.port_range}" + f"正在搜索ADB实际地址,ADB前缀:{ADB_ip},初始端口:{ADB_port},搜索范围:{self.port_range}", + module=f"MAA调度器-{self.name}", ) for port in self.port_range: @@ -1010,9 +1166,14 @@ class MaaManager(QObject): ) if ADB_address in devices_result.stdout: - logger.info(f"{self.name} | ADB实际地址:{ADB_address}") + logger.info( + f"ADB实际地址:{ADB_address}", module=f"MAA调度器-{self.name}" + ) # 断开连接 + logger.info( + f"断开ADB连接:{ADB_address}", module=f"MAA调度器-{self.name}" + ) subprocess.run( [self.ADB_path, "disconnect", ADB_address], creationflags=subprocess.CREATE_NO_WINDOW, @@ -1021,6 +1182,10 @@ class MaaManager(QObject): self.ADB_address = ADB_address # 覆写当前ADB地址 + logger.info( + f"开始使用实际 ADB 地址覆写:{self.ADB_address}", + module=f"MAA调度器-{self.name}", + ) self.maa_process_manager.kill(if_force=True) System.kill_process(self.maa_exe_path) with self.maa_set_path.open(mode="r", encoding="utf-8") as f: @@ -1036,9 +1201,14 @@ class MaaManager(QObject): return None else: - logger.info(f"{self.name} | 无法连接到ADB地址:{ADB_address}") + logger.info( + f"无法连接到ADB地址:{ADB_address}", + module=f"MAA调度器-{self.name}", + ) else: - logger.info(f"{self.name} | 无法连接到ADB地址:{ADB_address}") + logger.info( + f"无法连接到ADB地址:{ADB_address}", module=f"MAA调度器-{self.name}" + ) if not self.isInterruptionRequested: self.play_sound.emit("ADB失败") @@ -1046,14 +1216,19 @@ class MaaManager(QObject): def refresh_maa_log(self) -> None: """刷新MAA日志""" + logger.debug( + f"刷新MAA日志:{self.maa_log_path}", module=f"MAA调度器-{self.name}" + ) + with self.maa_log_path.open(mode="r", encoding="utf-8") as f: pass # 一分钟内未执行日志变化检查,强制检查一次 if datetime.now() - self.last_check_time > timedelta(minutes=1): - self.log_monitor.fileChanged.emit("1分钟超时检查") + logger.info("触发 1 分钟超时检查", module=f"MAA调度器-{self.name}") + self.check_maa_log() - def check_maa_log(self, start_time: datetime, mode: str) -> list: + def check_maa_log(self) -> list: """获取MAA日志并检查以判断MAA程序运行状态""" self.last_check_time = datetime.now() @@ -1066,7 +1241,7 @@ class MaaManager(QObject): if not if_log_start: try: entry_time = datetime.strptime(entry[1:20], "%Y-%m-%d %H:%M:%S") - if entry_time > start_time: + if entry_time > self.log_start_time: if_log_start = True logs.append(entry) except ValueError: @@ -1093,11 +1268,15 @@ class MaaManager(QObject): if version_match: self.maa_version = f"v{version_match.group(1)}" self.check_maa_version.emit(self.maa_version) + logger.info( + f"检测到MAA版本:{self.maa_version}", + module=f"MAA调度器-{self.name}", + ) - if "自动代理" in mode: + if "自动代理" in self.log_check_mode: # 获取最近一条日志的时间 - latest_time = start_time + latest_time = self.log_start_time for _ in logs[::-1]: try: if "如果长时间无进一步日志更新,可能需要手动干预。" in _: @@ -1107,12 +1286,16 @@ class MaaManager(QObject): except ValueError: pass + logger.info( + f"MAA最近一条日志时间:{latest_time}", module=f"MAA调度器-{self.name}" + ) + time_book = { "自动代理_剿灭": "AnnihilationTimeLimit", "自动代理_日常": "RoutineTimeLimit", } - if mode == "自动代理_剿灭" and "剿灭任务失败" in log: + if self.log_check_mode == "自动代理_剿灭" and "剿灭任务失败" in log: self.weekly_annihilation_limit_reached = True else: self.weekly_annihilation_limit_reached = False @@ -1164,7 +1347,7 @@ class MaaManager(QObject): self.maa_result = "MAA在完成任务前退出" elif datetime.now() - latest_time > timedelta( - minutes=self.set["RunSet"][time_book[mode]] + minutes=self.set["RunSet"][time_book[self.log_check_mode]] ): self.maa_result = "MAA进程超时" @@ -1174,7 +1357,7 @@ class MaaManager(QObject): else: self.maa_result = "Wait" - elif mode == "人工排查": + elif self.log_check_mode == "人工排查": if "完成任务: StartUp" in log or "完成任务: 开始唤醒" in log: self.maa_result = "Success!" elif "请 「检查连接设置」 → 「尝试重启模拟器与 ADB」 → 「重启电脑」" in log: @@ -1193,7 +1376,7 @@ class MaaManager(QObject): else: self.maa_result = "Wait" - elif mode == "设置MAA": + elif self.log_check_mode == "设置MAA": if ( "MaaAssistantArknights GUI exited" in log or not self.maa_process_manager.is_running() @@ -1202,20 +1385,24 @@ class MaaManager(QObject): else: self.maa_result = "Wait" + logger.info( + f"MAA日志分析结果:{self.maa_result}", module=f"MAA调度器-{self.name}" + ) + if self.maa_result != "Wait": self.quit_monitor() return logs - def start_monitor(self, start_time: datetime, mode: str) -> None: + def start_monitor(self) -> None: """开始监视MAA日志""" - logger.info(f"{self.name} | 开始监视MAA日志") - self.log_monitor.addPath(str(self.maa_log_path)) - self.log_monitor.fileChanged.connect( - lambda: self.check_maa_log(start_time, mode) + logger.info( + f"开始监视MAA日志,路径:{self.maa_log_path},日志起始时间:{self.log_start_time},模式:{self.log_check_mode}", + module=f"MAA调度器-{self.name}", ) + self.log_monitor.addPath(str(self.maa_log_path)) self.log_monitor_timer.start(1000) self.last_check_time = datetime.now() self.monitor_loop.exec() @@ -1225,16 +1412,29 @@ class MaaManager(QObject): if len(self.log_monitor.files()) != 0: - logger.info(f"{self.name} | 退出MAA日志监视") + logger.info( + f"MAA日志监视器移除路径:{self.maa_log_path}", + module=f"MAA调度器-{self.name}", + ) self.log_monitor.removePath(str(self.maa_log_path)) - self.log_monitor.fileChanged.disconnect() - self.log_monitor_timer.stop() - self.last_check_time = None - self.monitor_loop.quit() + + else: + logger.warning( + f"MAA日志监视器没有正在监看的路径:{self.log_monitor.files()}", + module=f"MAA调度器-{self.name}", + ) + + self.log_monitor_timer.stop() + self.last_check_time = None + self.monitor_loop.quit() + + logger.info("MAA日志监视锁已释放", module=f"MAA调度器-{self.name}") def set_maa(self, mode, index) -> dict: """配置MAA运行参数""" - logger.info(f"{self.name} | 配置MAA运行参数: {mode}/{index}") + logger.info( + f"开始配置MAA运行参数: {mode}/{index}", module=f"MAA调度器-{self.name}" + ) if "设置MAA" not in self.mode and "更新MAA" not in mode: user_data = self.data[index]["Config"] @@ -1804,12 +2004,17 @@ class MaaManager(QObject): with self.maa_set_path.open(mode="w", encoding="utf-8") as f: json.dump(data, f, ensure_ascii=False, indent=4) + logger.success( + f"MAA运行参数配置完成: {mode}/{index}", module=f"MAA调度器-{self.name}" + ) + return data def agree_bilibili(self, if_agree): """向MAA写入Bilibili协议相关任务""" logger.info( - f"{self.name} | Bilibili协议相关任务状态: {"启用" if if_agree else "禁用"}" + f"Bilibili协议相关任务状态: {'启用' if if_agree else '禁用'}", + module=f"MAA调度器-{self.name}", ) with self.maa_tasks_path.open(mode="r", encoding="utf-8") as f: @@ -1843,6 +2048,10 @@ class MaaManager(QObject): user_data: Dict[str, Dict[str, Union[str, int, bool]]] = None, ) -> None: """通过所有渠道推送通知""" + logger.info( + f"开始推送通知,模式:{mode},标题:{title}", + module=f"MAA调度器-{self.name}", + ) env = Environment( loader=FileSystemLoader(str(Config.app_path / "resources/html")) @@ -1971,9 +2180,7 @@ class MaaManager(QObject): user_data["Notify"]["ToAddress"], ) else: - logger.error( - f"{self.name} | 用户邮箱地址为空,无法发送用户单独的邮件通知" - ) + logger.error(f"用户邮箱地址为空,无法发送用户单独的邮件通知") # 发送ServerChan通知 if user_data["Notify"]["IfServerChan"]: @@ -2051,9 +2258,7 @@ class MaaManager(QObject): user_data["Notify"]["ToAddress"], ) else: - logger.error( - f"{self.name} | 用户邮箱地址为空,无法发送用户单独的邮件通知" - ) + logger.error(f"用户邮箱地址为空,无法发送用户单独的邮件通知") # 发送ServerChan通知 if user_data["Notify"]["IfServerChan"]: diff --git a/app/models/general.py b/app/models/general.py index 5966bb2..17e21cc 100644 --- a/app/models/general.py +++ b/app/models/general.py @@ -25,7 +25,6 @@ v4.4 作者:DLmaster_361 """ -from loguru import logger from PySide6.QtCore import QObject, Signal, QEventLoop, QFileSystemWatcher, QTimer import os import sys @@ -37,7 +36,7 @@ from pathlib import Path from jinja2 import Environment, FileSystemLoader from typing import Union, List, Dict -from app.core import Config, GeneralConfig, GeneralSubConfig +from app.core import Config, GeneralConfig, GeneralSubConfig, logger from app.services import Notify, System from app.utils import ProcessManager @@ -75,19 +74,20 @@ class GeneralManager(QObject): self.sub_list = [] self.mode = mode self.config_path = config["Path"] + self.name = config["Config"].get(config["Config"].Script_Name) self.sub_config_path = sub_config_path self.game_process_manager = ProcessManager() self.script_process_manager = ProcessManager() self.log_monitor = QFileSystemWatcher() + self.log_monitor.fileChanged.connect(self.check_script_log) self.log_monitor_timer = QTimer() self.log_monitor_timer.timeout.connect(self.refresh_log) self.monitor_loop = QEventLoop() + self.loge_start_time = datetime.now() - self.script_process_manager.processClosed.connect( - lambda: self.log_monitor.fileChanged.emit("进程结束检查") - ) + self.script_process_manager.processClosed.connect(self.check_script_log) self.question_loop = QEventLoop() self.question_response.connect(self.__capture_response) @@ -111,6 +111,10 @@ class GeneralManager(QObject): self.data = dict(sorted(self.data.items(), key=lambda x: int(x[0][3:]))) + logger.success( + f"初始化通用调度器,模式:{self.mode}", module=f"通用调度器-{self.name}" + ) + def check_config_info(self) -> bool: """检查配置完整性""" @@ -124,7 +128,7 @@ class GeneralManager(QObject): ) or ( self.set["Game"]["Enabled"] and not Path(self.set["Game"]["Path"]).exists() ): - logger.error("脚本配置缺失") + logger.error("脚本配置缺失", module=f"通用调度器-{self.name}") self.push_info_bar.emit("error", "脚本配置缺失", "请检查脚本配置!", -1) return False @@ -133,7 +137,6 @@ class GeneralManager(QObject): def configure(self): """提取配置信息""" - self.name = self.set["Script"]["Name"] self.script_root_path = Path(self.set["Script"]["RootPath"]) self.script_exe_path = Path(self.set["Script"]["ScriptPath"]) self.script_config_path = Path(self.set["Script"]["ConfigPath"]) @@ -166,9 +169,23 @@ class GeneralManager(QObject): curdate = Config.server_date().strftime("%Y-%m-%d") begin_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + if self.mode == "人工排查": + + logger.error("通用脚本不支持人工排查模式", module=f"通用调度器-{self.name}") + self.accomplish.emit( + { + "Time": begin_time, + "History": "通用脚本不支持人工排查模式,通用代理进程中止", + } + ) + return None + # 检查配置完整性 if not self.check_config_info(): + logger.error( + "配置不完整,无法启动通用代理进程", module=f"通用调度器-{self.name}" + ) self.accomplish.emit( {"Time": begin_time, "History": "由于配置不完整,通用代理进程中止"} ) @@ -190,6 +207,11 @@ class GeneralManager(QObject): ] self.create_user_list.emit(self.sub_list) + logger.info( + f"配置列表创建完成,已筛选子配置数:{len(self.sub_list)}", + module=f"通用调度器-{self.name}", + ) + # 自动代理模式 if self.mode == "自动代理": @@ -222,14 +244,17 @@ class GeneralManager(QObject): self.update_user_list.emit(self.sub_list) continue - logger.info(f"{self.name} | 开始代理配置: {sub[0]}") + logger.info(f"开始代理配置: {sub[0]}", module=f"通用调度器-{self.name}") sub_start_time = datetime.now() run_book = False if not (self.data[sub[2]]["Path"] / "ConfigFiles").exists(): - logger.error(f"{self.name} | 配置: {sub[0]} - 未找到配置文件") + logger.error( + f"配置: {sub[0]} - 未找到配置文件", + module=f"通用调度器-{self.name}", + ) self.push_info_bar.emit( "error", "启动通用代理进程失败", @@ -246,11 +271,12 @@ class GeneralManager(QObject): break logger.info( - f"{self.name} | 用户: {sub[0]} - 尝试次数: {i + 1}/{self.set['Run']['RunTimesLimit']}" + f"用户: {sub[0]} - 尝试次数: {i + 1}/{self.set['Run']['RunTimesLimit']}", + module=f"通用调度器-{self.name}", ) # 记录当前时间 - start_time = datetime.now() + self.log_start_time = datetime.now() # 配置脚本 self.set_sub(sub[2]) # 执行任务前脚本 @@ -267,7 +293,8 @@ class GeneralManager(QObject): try: logger.info( - f"{self.name} | 启动游戏/模拟器:{self.game_path},参数:{self.set['Game']['Arguments']}" + f"启动游戏/模拟器:{self.game_path},参数:{self.set['Game']['Arguments']}", + module=f"通用调度器-{self.name}", ) self.game_process_manager.open_process( self.game_path, @@ -275,8 +302,9 @@ class GeneralManager(QObject): 0, ) except Exception as e: - logger.error( - f"{self.name} | 启动游戏/模拟器时出现异常:{e}" + logger.exception( + f"启动游戏/模拟器时出现异常:{e}", + module=f"通用调度器-{self.name}", ) self.push_info_bar.emit( "error", @@ -289,6 +317,10 @@ class GeneralManager(QObject): # 添加静默进程标记 if self.set["Game"]["Style"] == "Emulator": + logger.info( + f"添加静默进程标记:{self.game_path}", + module=f"通用调度器-{self.name}", + ) Config.silence_list.append(self.game_path) self.update_log_text.emit( @@ -299,6 +331,10 @@ class GeneralManager(QObject): # 10s后移除静默进程标记 if self.set["Game"]["Style"] == "Emulator": + logger.info( + f"10s后移除静默进程标记:{self.game_path}", + module=f"通用调度器-{self.name}", + ) QTimer.singleShot( 10000, partial(Config.silence_list.remove, self.game_path), @@ -306,7 +342,8 @@ class GeneralManager(QObject): # 运行脚本任务 logger.info( - f"{self.name} | 运行脚本任务:{self.script_exe_path},参数:{self.set['Script']['Arguments']}" + f"运行脚本任务:{self.script_exe_path},参数:{self.set['Script']['Arguments']}", + module=f"通用调度器-{self.name}", ) self.script_process_manager.open_process( self.script_exe_path, @@ -315,7 +352,7 @@ class GeneralManager(QObject): ) # 监测运行状态 - self.start_monitor(start_time) + self.start_monitor() if self.script_result == "Success!": @@ -323,15 +360,24 @@ class GeneralManager(QObject): run_book = True # 中止相关程序 + logger.info( + f"中止相关程序:{self.script_exe_path}", + module=f"通用调度器-{self.name}", + ) self.script_process_manager.kill() System.kill_process(self.script_exe_path) if self.set["Game"]["Enabled"]: + logger.info( + f"中止游戏/模拟器进程:{list(self.game_process_manager.tracked_pids)}", + module=f"通用调度器-{self.name}", + ) self.game_process_manager.kill() if self.set["Game"]["IfForceClose"]: System.kill_process(self.game_path) logger.info( - f"{self.name} | 配置: {sub[0]} - 通用脚本进程完成代理任务" + f"配置: {sub[0]} - 通用脚本进程完成代理任务", + module=f"通用调度器-{self.name}", ) self.update_log_text.emit( "检测到通用脚本进程完成代理任务\n正在等待相关程序结束\n请等待10s" @@ -340,7 +386,8 @@ class GeneralManager(QObject): self.sleep(10) else: logger.error( - f"{self.name} | 配置: {sub[0]} - 代理任务异常: {self.script_result}" + f"配置: {sub[0]} - 代理任务异常: {self.script_result}", + module=f"通用调度器-{self.name}", ) # 打印中止信息 # 此时,log变量内存储的就是出现异常的日志信息,可以保存或发送用于问题排查 @@ -349,8 +396,16 @@ class GeneralManager(QObject): ) # 中止相关程序 + logger.info( + f"中止相关程序:{self.script_exe_path}", + module=f"通用调度器-{self.name}", + ) self.script_process_manager.kill() if self.set["Game"]["Enabled"]: + logger.info( + f"中止游戏/模拟器进程:{list(self.game_process_manager.tracked_pids)}", + module=f"通用调度器-{self.name}", + ) self.game_process_manager.kill() if self.set["Game"]["IfForceClose"]: System.kill_process(self.game_path) @@ -380,8 +435,8 @@ class GeneralManager(QObject): # 保存运行日志以及统计信息 Config.save_general_log( Config.app_path - / f"history/{curdate}/{sub_data['Info']['Name']}/{start_time.strftime("%H-%M-%S")}.log", - self.check_script_log(start_time), + / f"history/{curdate}/{sub_data['Info']['Name']}/{self.log_start_time.strftime("%H-%M-%S")}.log", + self.check_script_log(), self.script_result, ) @@ -409,6 +464,10 @@ class GeneralManager(QObject): sub_data["Info"]["RemainedDay"] -= 1 sub_data["Data"]["ProxyTimes"] += 1 sub[1] = "完成" + logger.success( + f"配置: {sub[0]} - 代理任务完成", + module=f"通用调度器-{self.name}", + ) Notify.push_plyer( "成功完成一个自动代理任务!", f"已完成配置 {sub[0].replace("_", " 今天的")}任务", @@ -418,6 +477,10 @@ class GeneralManager(QObject): else: # 录入代理失败的用户 sub[1] = "异常" + logger.error( + f"配置: {sub[0]} - 代理任务异常: {self.script_result}", + module=f"通用调度器-{self.name}", + ) self.update_user_list.emit(self.sub_list) @@ -429,17 +492,20 @@ class GeneralManager(QObject): try: # 创建通用脚本任务 - logger.info(f"{self.name} | 无参数启动通用脚本:{self.script_exe_path}") + logger.info( + f"无参数启动通用脚本:{self.script_exe_path}", + module=f"通用调度器-{self.name}", + ) self.script_process_manager.open_process( self.script_exe_path, tracking_time=60 if self.set["Script"]["IfTrackProcess"] else 0, ) # 记录当前时间 - start_time = datetime.now() + self.log_start_time = datetime.now() # 监测通用脚本运行状态 - self.start_monitor(start_time) + self.start_monitor() self.sub_config_path.mkdir(parents=True, exist_ok=True) if self.set["Script"]["ConfigPathMode"] == "文件夹": @@ -448,16 +514,23 @@ class GeneralManager(QObject): self.sub_config_path, dirs_exist_ok=True, ) + logger.success( + f"通用脚本配置已保存到:{self.sub_config_path}", + module=f"通用调度器-{self.name}", + ) else: shutil.copy(self.script_config_path, self.sub_config_path) + logger.success( + f"通用脚本配置已保存到:{self.sub_config_path}", + module=f"通用调度器-{self.name}", + ) except Exception as e: - logger.error(f"{self.name} | 启动通用脚本时出现异常:{e}") + logger.exception( + f"启动通用脚本时出现异常:{e}", module=f"通用调度器-{self.name}" + ) self.push_info_bar.emit( - "error", - "启动通用脚本时出现异常", - "请检查相关设置", - -1, + "error", "启动通用脚本时出现异常", "请检查相关设置", -1 ) result_text = "" @@ -467,9 +540,17 @@ class GeneralManager(QObject): # 关闭可能未正常退出的通用脚本进程 if self.isInterruptionRequested: + logger.info( + f"关闭可能未正常退出的通用脚本进程:{self.script_exe_path}", + module=f"通用调度器-{self.name}", + ) self.script_process_manager.kill(if_force=True) System.kill_process(self.script_exe_path) if self.set["Game"]["Enabled"]: + logger.info( + f"关闭可能未正常退出的游戏/模拟器进程:{list(self.game_process_manager.tracked_pids)}", + module=f"通用调度器-{self.name}", + ) self.game_process_manager.kill(if_force=True) if self.set["Game"]["IfForceClose"]: System.kill_process(self.game_path) @@ -527,7 +608,9 @@ class GeneralManager(QObject): self.accomplish.emit({"Time": begin_time, "History": result_text}) def requestInterruption(self) -> None: - logger.info(f"{self.name} | 收到任务中止申请") + """请求中止通用脚本任务""" + + logger.info(f"收到任务中止申请", module=f"通用调度器-{self.name}") if len(self.log_monitor.files()) != 0: self.interrupt.emit() @@ -537,29 +620,45 @@ class GeneralManager(QObject): self.wait_loop.quit() def push_question(self, title: str, message: str) -> bool: + """推送问题询问""" + + logger.info( + f"推送问题询问:{title} - {message}", module=f"通用调度器-{self.name}" + ) self.question.emit(title, message) self.question_loop.exec() return self.response def __capture_response(self, response: bool) -> None: + """捕获问题询问的响应""" + + logger.info(f"捕获问题询问的响应:{response}", module=f"通用调度器-{self.name}") self.response = response def sleep(self, time: int) -> None: """非阻塞型等待""" + logger.info(f"等待 {time} 秒", module=f"通用调度器-{self.name}") + QTimer.singleShot(time * 1000, self.wait_loop.quit) self.wait_loop.exec() def refresh_log(self) -> None: """刷新脚本日志""" + logger.debug( + f"刷新通用脚本日志:{self.script_log_path}", + module=f"通用调度器-{self.name}", + ) + with self.script_log_path.open(mode="r", encoding="utf-8") as f: pass # 一分钟内未执行日志变化检查,强制检查一次 if (datetime.now() - self.last_check_time).total_seconds() > 60: - self.log_monitor.fileChanged.emit("1分钟超时检查") + logger.info("触发 1 分钟超时检查", module=f"通用调度器-{self.name}") + self.check_script_log() def strptime( self, date_string: str, format: str, default_date: datetime @@ -589,7 +688,7 @@ class GeneralManager(QObject): return datetime(**datetime_kwargs) - def check_script_log(self, start_time: datetime) -> list: + def check_script_log(self) -> list: """获取脚本日志并检查以判断脚本程序运行状态""" self.last_check_time = datetime.now() @@ -607,7 +706,7 @@ class GeneralManager(QObject): self.last_check_time, ) - if entry_time > start_time: + if entry_time > self.log_start_time: if_log_start = True logs.append(entry) except ValueError: @@ -625,7 +724,7 @@ class GeneralManager(QObject): if "自动代理" in self.mode: # 获取最近一条日志的时间 - latest_time = start_time + latest_time = self.log_start_time for _ in logs[::-1]: try: latest_time = self.strptime( @@ -637,6 +736,11 @@ class GeneralManager(QObject): except ValueError: pass + logger.info( + f"通用脚本最近一条日志时间:{latest_time}", + module=f"通用调度器-{self.name}", + ) + for success_sign in self.success_log: if success_sign in log: self.script_result = "Success!" @@ -668,18 +772,25 @@ class GeneralManager(QObject): else: self.script_result = "Success!" + logger.info( + f"通用脚本日志分析结果:{self.script_result}", + module=f"通用调度器-{self.name}", + ) + if self.script_result != "Wait": self.quit_monitor() return logs - def start_monitor(self, start_time: datetime) -> None: + def start_monitor(self) -> None: """开始监视通用脚本日志""" - logger.info(f"{self.name} | 开始监视通用脚本日志") + logger.info( + f"开始监视通用脚本日志,路径:{self.script_log_path},日志起始时间:{self.log_start_time}", + module=f"通用调度器-{self.name}", + ) self.log_monitor.addPath(str(self.script_log_path)) - self.log_monitor.fileChanged.connect(lambda: self.check_script_log(start_time)) self.log_monitor_timer.start(1000) self.last_check_time = datetime.now() self.monitor_loop.exec() @@ -689,16 +800,27 @@ class GeneralManager(QObject): if len(self.log_monitor.files()) != 0: - logger.info(f"{self.name} | 退出通用脚本日志监视") + logger.info( + f"通用脚本日志监视器移除路径:{self.script_log_path}", + module=f"通用调度器-{self.name}", + ) self.log_monitor.removePath(str(self.script_log_path)) - self.log_monitor.fileChanged.disconnect() - self.log_monitor_timer.stop() - self.last_check_time = None - self.monitor_loop.quit() + + else: + logger.warning( + f"通用脚本日志监视器没有正在监看的路径:{self.log_monitor.files()}", + module=f"通用调度器-{self.name}", + ) + + self.log_monitor_timer.stop() + self.last_check_time = None + self.monitor_loop.quit() + + logger.info("通用脚本日志监视锁已释放", module=f"通用调度器-{self.name}") def set_sub(self, index: str = "") -> dict: """配置通用脚本运行参数""" - logger.info(f"{self.name} | 配置脚本运行参数: {index}") + logger.info(f"开始配置脚本运行参数:{index}", module=f"通用调度器-{self.name}") # 配置前关闭可能未正常退出的脚本进程 System.kill_process(self.script_exe_path) @@ -732,11 +854,15 @@ class GeneralManager(QObject): self.script_config_path, ) + logger.info(f"脚本运行参数配置完成:{index}", module=f"通用调度器-{self.name}") + def execute_script_task(self, script_path: Path, task_name: str) -> bool: """执行脚本任务并等待结束""" try: - logger.info(f"{self.name} | 开始执行{task_name}: {script_path}") + logger.info( + f"开始执行{task_name}: {script_path}", module=f"通用调度器-{self.name}" + ) # 根据文件类型选择执行方式 if script_path.suffix.lower() == ".py": @@ -744,7 +870,10 @@ class GeneralManager(QObject): elif script_path.suffix.lower() in [".bat", ".cmd", ".exe"]: cmd = [str(script_path)] elif script_path.suffix.lower() == "": - logger.warning(f"{self.name} | {task_name}脚本没有指定后缀名,无法执行") + logger.warning( + f"{task_name}脚本没有指定后缀名,无法执行", + module=f"通用调度器-{self.name}", + ) return False else: # 使用系统默认程序打开 @@ -767,23 +896,32 @@ class GeneralManager(QObject): ) if result.returncode == 0: - logger.info(f"{self.name} | {task_name}执行成功") + logger.info(f"{task_name}执行成功", module=f"通用调度器-{self.name}") if result.stdout.strip(): - logger.info(f"{self.name} | {task_name}输出: {result.stdout}") + logger.info( + f"{task_name}输出: {result.stdout}", + module=f"通用调度器-{self.name}", + ) return True else: logger.error( - f"{self.name} | {task_name}执行失败,返回码: {result.returncode}" + f"{task_name}执行失败,返回码: {result.returncode}", + module=f"通用调度器-{self.name}", ) if result.stderr.strip(): - logger.error(f"{self.name} | {task_name}错误输出: {result.stderr}") + logger.error( + f"{task_name}错误输出: {result.stderr}", + module=f"通用调度器-{self.name}", + ) return False except subprocess.TimeoutExpired: - logger.error(f"{self.name} | {task_name}执行超时") + logger.error(f"{task_name}执行超时", module=f"通用调度器-{self.name}") return False except Exception as e: - logger.exception(f"{self.name} | 执行{task_name}时出现异常: {e}") + logger.exception( + f"执行{task_name}时出现异常: {e}", module=f"通用调度器-{self.name}" + ) return False def push_notification( @@ -795,6 +933,11 @@ class GeneralManager(QObject): ) -> None: """通过所有渠道推送通知""" + logger.info( + f"开始推送通知,模式:{mode},标题:{title}", + module=f"通用调度器-{self.name}", + ) + env = Environment( loader=FileSystemLoader(str(Config.app_path / "resources/html")) ) @@ -902,9 +1045,7 @@ class GeneralManager(QObject): sub_data["Notify"]["ToAddress"], ) else: - logger.error( - f"{self.name} | 用户邮箱地址为空,无法发送用户单独的邮件通知" - ) + logger.error(f"用户邮箱地址为空,无法发送用户单独的邮件通知") # 发送ServerChan通知 if sub_data["Notify"]["IfServerChan"]: diff --git a/app/services/notification.py b/app/services/notification.py index 56ddf04..0fd13f4 100644 --- a/app/services/notification.py +++ b/app/services/notification.py @@ -33,13 +33,14 @@ from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from email.utils import formataddr from pathlib import Path +from typing import Union import requests from PySide6.QtCore import QObject, Signal -from loguru import logger + from plyer import notification -from app.core import Config +from app.core import Config, logger from app.services.security import Crypto from app.utils.ImageUtils import ImageUtils @@ -51,11 +52,21 @@ class Notification(QObject): def __init__(self, parent=None): super().__init__(parent) - def push_plyer(self, title, message, ticker, t): - """推送系统通知""" + def push_plyer(self, title, message, ticker, t) -> bool: + """ + 推送系统通知 + + :param title: 通知标题 + :param message: 通知内容 + :param ticker: 通知横幅 + :param t: 通知持续时间 + :return: bool + """ if Config.get(Config.notify_IfPushPlyer): + logger.info(f"推送系统通知:{title}", module="通知服务") + notification.notify( title=title, message=message, @@ -69,7 +80,15 @@ class Notification(QObject): return True def send_mail(self, mode, title, content, to_address) -> None: - """推送邮件通知""" + """ + 推送邮件通知 + + :param mode: 邮件内容模式,支持 "文本" 和 "网页" + :param title: 邮件标题 + :param content: 邮件内容 + :param to_address: 收件人地址 + """ + if ( Config.get(Config.notify_SMTPServerAddress) == "" or Config.get(Config.notify_AuthorizationCode) == "" @@ -87,7 +106,8 @@ class Notification(QObject): ) ): logger.error( - "请正确设置邮件通知的SMTP服务器地址、授权码、发件人地址和收件人地址" + "请正确设置邮件通知的SMTP服务器地址、授权码、发件人地址和收件人地址", + module="通知服务", ) self.push_info_bar.emit( "error", @@ -110,42 +130,43 @@ class Notification(QObject): ) ) # 发件人显示的名字 message["To"] = formataddr( - ( - Header("AUTO_MAA用户", "utf-8").encode(), - to_address, - ) + (Header("AUTO_MAA用户", "utf-8").encode(), to_address) ) # 收件人显示的名字 message["Subject"] = Header(title, "utf-8") if mode == "网页": message.attach(MIMEText(content, "html", "utf-8")) - smtpObj = smtplib.SMTP_SSL( - Config.get(Config.notify_SMTPServerAddress), - 465, - ) + smtpObj = smtplib.SMTP_SSL(Config.get(Config.notify_SMTPServerAddress), 465) smtpObj.login( Config.get(Config.notify_FromAddress), Crypto.win_decryptor(Config.get(Config.notify_AuthorizationCode)), ) smtpObj.sendmail( - Config.get(Config.notify_FromAddress), - to_address, - message.as_string(), + Config.get(Config.notify_FromAddress), to_address, message.as_string() ) smtpObj.quit() - logger.success("邮件发送成功") - return None + logger.success(f"邮件发送成功:{title}", module="通知服务") except Exception as e: - logger.error(f"发送邮件时出错:\n{e}") + logger.exception(f"发送邮件时出错:{e}", module="通知服务") self.push_info_bar.emit("error", "发送邮件时出错", f"{e}", -1) - return None - return None - def ServerChanPush(self, title, content, send_key, tag, channel): - """使用Server酱推送通知""" + def ServerChanPush( + self, title, content, send_key, tag, channel + ) -> Union[bool, str]: + """ + 使用Server酱推送通知 + + :param title: 通知标题 + :param content: 通知内容 + :param send_key: Server酱的SendKey + :param tag: 通知标签 + :param channel: 通知频道 + :return: bool or str + """ + if not send_key: - logger.error("请正确设置Server酱的SendKey") + logger.error("请正确设置Server酱的SendKey", module="通知服务") self.push_info_bar.emit( "error", "Server酱通知推送异常", "请正确设置Server酱的SendKey", -1 ) @@ -176,7 +197,7 @@ class Notification(QObject): if is_valid(tags): options["tags"] = tags else: - logger.warning("Server酱 Tag 配置不正确,将被忽略") + logger.warning("Server酱 Tag 配置不正确,将被忽略", module="通知服务") self.push_info_bar.emit( "warning", "Server酱通知推送异常", @@ -187,7 +208,9 @@ class Notification(QObject): if is_valid(channels): options["channel"] = channels else: - logger.warning("Server酱 Channel 配置不正确,将被忽略") + logger.warning( + "Server酱 Channel 配置不正确,将被忽略", module="通知服务" + ) self.push_info_bar.emit( "warning", "Server酱通知推送异常", @@ -212,18 +235,20 @@ class Notification(QObject): result = response.json() if result.get("code") == 0: - logger.info("Server酱推送通知成功") + logger.success(f"Server酱推送通知成功:{title}", module="通知服务") return True else: error_code = result.get("code", "-1") - logger.error(f"Server酱通知推送失败:响应码:{error_code}") + logger.exception( + f"Server酱通知推送失败:响应码:{error_code}", module="通知服务" + ) self.push_info_bar.emit( "error", "Server酱通知推送失败", f"响应码:{error_code}", -1 ) return f"Server酱通知推送失败:{error_code}" except Exception as e: - logger.exception("Server酱通知推送异常") + logger.exception(f"Server酱通知推送异常:{e}", module="通知服务") self.push_info_bar.emit( "error", "Server酱通知推送异常", @@ -232,10 +257,18 @@ class Notification(QObject): ) return f"Server酱通知推送异常:{str(e)}" - def CompanyWebHookBotPush(self, title, content, webhook_url): - """使用企业微信群机器人推送通知""" + def CompanyWebHookBotPush(self, title, content, webhook_url) -> Union[bool, str]: + """ + 使用企业微信群机器人推送通知 + + :param title: 通知标题 + :param content: 通知内容 + :param webhook_url: 企业微信群机器人的WebHook地址 + :return: bool or str + """ + if webhook_url == "": - logger.error("请正确设置企业微信群机器人的WebHook地址") + logger.error("请正确设置企业微信群机器人的WebHook地址", module="通知服务") self.push_info_bar.emit( "error", "企业微信群机器人通知推送异常", @@ -264,7 +297,7 @@ class Notification(QObject): err = e time.sleep(0.1) else: - logger.error(f"推送企业微信群机器人时出错:{err}") + logger.error(f"推送企业微信群机器人时出错:{err}", module="通知服务") self.push_info_bar.emit( "error", "企业微信群机器人通知推送失败", @@ -274,10 +307,10 @@ class Notification(QObject): return None if info["errcode"] == 0: - logger.info("企业微信群机器人推送通知成功") + logger.success(f"企业微信群机器人推送通知成功:{title}", module="通知服务") return True else: - logger.error(f"企业微信群机器人推送通知失败:{info}") + logger.error(f"企业微信群机器人推送通知失败:{info}", module="通知服务") self.push_info_bar.emit( "error", "企业微信群机器人通知推送失败", @@ -287,7 +320,14 @@ class Notification(QObject): return f"使用企业微信群机器人推送通知时出错:{err}" def CompanyWebHookBotPushImage(self, image_path: Path, webhook_url: str) -> bool: - """使用企业微信群机器人推送图片通知""" + """ + 使用企业微信群机器人推送图片通知 + + :param image_path: 图片文件路径 + :param webhook_url: 企业微信群机器人的WebHook地址 + :return: bool + """ + try: # 压缩图片 ImageUtils.compress_image_if_needed(image_path) @@ -295,7 +335,8 @@ class Notification(QObject): # 检查图片是否存在 if not image_path.exists(): logger.error( - "图片推送异常 | 图片不存在或者压缩失败,请检查图片路径是否正确" + "图片推送异常 | 图片不存在或者压缩失败,请检查图片路径是否正确", + module="通知服务", ) self.push_info_bar.emit( "error", @@ -306,7 +347,9 @@ class Notification(QObject): return False if not webhook_url: - logger.error("请正确设置企业微信群机器人的WebHook地址") + logger.error( + "请正确设置企业微信群机器人的WebHook地址", module="通知服务" + ) self.push_info_bar.emit( "error", "企业微信群机器人通知推送异常", @@ -320,7 +363,7 @@ class Notification(QObject): image_base64 = ImageUtils.get_base64_from_file(str(image_path)) image_md5 = ImageUtils.calculate_md5_from_file(str(image_path)) except Exception as e: - logger.exception(f"图片编码或MD5计算失败:{e}") + logger.exception(f"图片编码或MD5计算失败:{e}", module="通知服务") self.push_info_bar.emit( "error", "企业微信群机器人通知推送异常", @@ -349,10 +392,12 @@ class Notification(QObject): break except requests.RequestException as e: err = e - logger.warning(f"推送企业微信群机器人图片第{_+1}次失败:{e}") + logger.exception( + f"推送企业微信群机器人图片第{_+1}次失败:{e}", module="通知服务" + ) time.sleep(0.1) else: - logger.error(f"推送企业微信群机器人图片时出错:{err}") + logger.error("推送企业微信群机器人图片时出错", module="通知服务") self.push_info_bar.emit( "error", "企业微信群机器人图片推送失败", @@ -362,10 +407,13 @@ class Notification(QObject): return False if info.get("errcode") == 0: - logger.info("企业微信群机器人推送图片成功") + logger.success( + f"企业微信群机器人推送图片成功:{image_path.name}", + module="通知服务", + ) return True else: - logger.error(f"企业微信群机器人推送图片失败:{info}") + logger.error(f"企业微信群机器人推送图片失败:{info}", module="通知服务") self.push_info_bar.emit( "error", "企业微信群机器人图片推送失败", @@ -386,6 +434,9 @@ class Notification(QObject): def send_test_notification(self): """发送测试通知到所有已启用的通知渠道""" + + logger.info("发送测试通知到所有已启用的通知渠道", module="通知服务") + # 发送系统通知 self.push_plyer( "测试通知", @@ -425,6 +476,8 @@ class Notification(QObject): Config.get(Config.notify_CompanyWebHookBotUrl), ) + logger.info("测试通知发送完成", module="通知服务") + return True diff --git a/app/services/security.py b/app/services/security.py index b7bcc1a..b811f95 100644 --- a/app/services/security.py +++ b/app/services/security.py @@ -25,18 +25,15 @@ v4.4 作者:DLmaster_361 """ -from loguru import logger import hashlib import random import secrets import base64 import win32crypt -from pathlib import Path from Crypto.Cipher import AES from Crypto.PublicKey import RSA from Crypto.Cipher import PKCS1_OAEP from Crypto.Util.Padding import pad, unpad -from typing import List, Dict, Union from app.core import Config @@ -44,7 +41,12 @@ from app.core import Config class CryptoHandler: def get_PASSWORD(self, PASSWORD: str) -> None: - """配置管理密钥""" + """ + 配置管理密钥 + + :param PASSWORD: 管理密钥 + :type PASSWORD: str + """ # 生成目录 Config.key_path.mkdir(parents=True, exist_ok=True) @@ -85,7 +87,12 @@ class CryptoHandler: (Config.app_path / "data/key/private_key.bin").write_bytes(private_key_local) def AUTO_encryptor(self, note: str) -> str: - """使用AUTO_MAA的算法加密数据""" + """ + 使用AUTO_MAA的算法加密数据 + + :param note: 数据明文 + :type note: str + """ if note == "": return "" @@ -100,7 +107,16 @@ class CryptoHandler: return base64.b64encode(encrypted).decode("utf-8") def AUTO_decryptor(self, note: str, PASSWORD: str) -> str: - """使用AUTO_MAA的算法解密数据""" + """ + 使用AUTO_MAA的算法解密数据 + + :param note: 数据密文 + :type note: str + :param PASSWORD: 管理密钥 + :type PASSWORD: str + :return: 解密后的明文 + :rtype: str + """ if note == "": return "" @@ -142,7 +158,14 @@ class CryptoHandler: return note def change_PASSWORD(self, PASSWORD_old: str, PASSWORD_new: str) -> None: - """修改管理密钥""" + """ + 修改管理密钥 + + :param PASSWORD_old: 旧管理密钥 + :type PASSWORD_old: str + :param PASSWORD_new: 新管理密钥 + :type PASSWORD_new: str + """ for member in Config.member_dict.values(): @@ -168,7 +191,12 @@ class CryptoHandler: del user["Password"] def reset_PASSWORD(self, PASSWORD_new: str) -> None: - """重置管理密钥""" + """ + 重置管理密钥 + + :param PASSWORD_new: 新管理密钥 + :type PASSWORD_new: str + """ self.get_PASSWORD(PASSWORD_new) @@ -176,12 +204,25 @@ class CryptoHandler: if member["Type"] == "Maa": for user in member["UserData"].values(): - user["Config"].set(user["Config"].Info_Password, "") + user["Config"].set( + user["Config"].Info_Password, self.AUTO_encryptor("数据已重置") + ) def win_encryptor( self, note: str, description: str = None, entropy: bytes = None ) -> str: - """使用Windows DPAPI加密数据""" + """ + 使用Windows DPAPI加密数据 + + :param note: 数据明文 + :type note: str + :param description: 描述信息 + :type description: str + :param entropy: 随机熵 + :type entropy: bytes + :return: 加密后的数据 + :rtype: str + """ if note == "": return "" @@ -192,7 +233,16 @@ class CryptoHandler: return base64.b64encode(encrypted).decode("utf-8") def win_decryptor(self, note: str, entropy: bytes = None) -> str: - """使用Windows DPAPI解密数据""" + """ + 使用Windows DPAPI解密数据 + + :param note: 数据密文 + :type note: str + :param entropy: 随机熵 + :type entropy: bytes + :return: 解密后的明文 + :rtype: str + """ if note == "": return "" @@ -202,21 +252,15 @@ class CryptoHandler: ) return decrypted[1].decode("utf-8") - def search_member(self) -> List[Dict[str, Union[Path, list]]]: - """搜索所有脚本实例及其用户数据库路径""" - - member_list = [] - - if (Config.app_path / "config/MaaConfig").exists(): - for subdir in (Config.app_path / "config/MaaConfig").iterdir(): - if subdir.is_dir(): - - member_list.append({"Path": subdir / "user_data.db"}) - - return member_list - def check_PASSWORD(self, PASSWORD: str) -> bool: - """验证管理密钥""" + """ + 验证管理密钥 + + :param PASSWORD: 管理密钥 + :type PASSWORD: str + :return: 是否验证通过 + :rtype: bool + """ return bool( self.AUTO_decryptor(self.AUTO_encryptor("-"), PASSWORD) != "管理密钥错误" diff --git a/app/services/skland.py b/app/services/skland.py index d752662..901a352 100644 --- a/app/services/skland.py +++ b/app/services/skland.py @@ -32,7 +32,6 @@ v4.4 作者:DLmaster_361、ClozyA """ -from loguru import logger import time import json import hmac @@ -40,7 +39,7 @@ import hashlib import requests from urllib import parse -from app.core import Config +from app.core import Config, logger def skland_sign_in(token) -> dict: @@ -71,15 +70,16 @@ def skland_sign_in(token) -> dict: "vName": "1.5.1", } - # 生成签名 def generate_signature(token_for_sign: str, path, body_or_query): """ 生成请求签名 + :param token_for_sign: 用于加密的token :param path: 请求路径(如 /api/v1/game/player/binding) :param body_or_query: GET用query字符串,POST用body字符串 :return: (sign, 新的header_for_sign字典) """ + t = str(int(time.time()) - 2) # 时间戳,-2秒以防服务器时间不一致 token_bytes = token_for_sign.encode("utf-8") header_ca = dict(header_for_sign) @@ -91,10 +91,10 @@ def skland_sign_in(token) -> dict: md5 = hashlib.md5(hex_s.encode("utf-8")).hexdigest() return md5, header_ca - # 获取带签名的header def get_sign_header(url: str, method, body, old_header, sign_token): """ 获取带签名的请求头 + :param url: 请求完整url :param method: 请求方式 GET/POST :param body: POST请求体或GET时为None @@ -102,6 +102,7 @@ def skland_sign_in(token) -> dict: :param sign_token: 当前会话的签名token :return: 新请求头 """ + h = json.loads(json.dumps(old_header)) p = parse.urlparse(url) if method.lower() == "get": @@ -115,15 +116,21 @@ def skland_sign_in(token) -> dict: h[i] = header_ca[i] return h - # 复制请求头并添加cred def copy_header(cred): + """ + 复制请求头并添加cred + + :param cred: 当前会话的cred + :return: 新的请求头 + """ v = json.loads(json.dumps(header)) v["cred"] = cred return v - # 使用token一步步拿到cred和sign_token def login_by_token(token_code): """ + 使用token一步步拿到cred和sign_token + :param token_code: 你的skyland token :return: (cred, sign_token) """ @@ -136,8 +143,14 @@ def skland_sign_in(token) -> dict: grant_code = get_grant_code(token_code) return get_cred(grant_code) - # 通过grant code换cred和sign_token def get_cred(grant): + """ + 通过grant code获取cred和sign_token + + :param grant: grant code + :return: (cred, sign_token) + """ + rsp = requests.post( cred_code_url, json={"code": grant, "kind": 1}, @@ -153,8 +166,13 @@ def skland_sign_in(token) -> dict: cred = rsp["data"]["cred"] return cred, sign_token - # 通过token换grant code def get_grant_code(token): + """ + 通过token获取grant code + + :param token: 你的skyland token + :return: grant code + """ rsp = requests.post( grant_code_url, json={"appCode": app_code, "token": token, "type": 0}, @@ -170,10 +188,10 @@ def skland_sign_in(token) -> dict: ) return rsp["data"]["code"] - # 获取已绑定的角色列表 def get_binding_list(cred, sign_token): """ - 查询绑定的角色 + 查询已绑定的角色列表 + :param cred: 当前cred :param sign_token: 当前sign_token :return: 角色列表 @@ -190,9 +208,15 @@ def skland_sign_in(token) -> dict: }, ).json() if rsp["code"] != 0: - logger.error(f"森空岛服务 | 请求角色列表出现问题:{rsp['message']}") + logger.error( + f"森空岛服务 | 请求角色列表出现问题:{rsp['message']}", + module="森空岛签到", + ) if rsp.get("message") == "用户未登录": - logger.error(f"森空岛服务 | 用户登录可能失效了,请重新登录!") + logger.error( + f"森空岛服务 | 用户登录可能失效了,请重新登录!", + module="森空岛签到", + ) return v # 只取明日方舟(arknights)的绑定账号 for i in rsp["data"]["list"]: @@ -201,10 +225,10 @@ def skland_sign_in(token) -> dict: v.extend(i.get("bindingList")) return v - # 执行签到 def do_sign(cred, sign_token) -> dict: """ 对所有绑定的角色进行签到 + :param cred: 当前cred :param sign_token: 当前sign_token :return: 签到结果字典 @@ -257,5 +281,5 @@ def skland_sign_in(token) -> dict: # 依次签到 return do_sign(cred, sign_token) except Exception as e: - logger.error(f"森空岛服务 | 森空岛签到失败: {e}") + logger.exception(f"森空岛服务 | 森空岛签到失败: {e}", module="森空岛签到") return {"成功": [], "重复": [], "失败": [], "总计": 0} diff --git a/app/services/system.py b/app/services/system.py index e856061..734cda1 100644 --- a/app/services/system.py +++ b/app/services/system.py @@ -25,7 +25,6 @@ v4.4 作者:DLmaster_361 """ -from loguru import logger from PySide6.QtWidgets import QApplication import sys import ctypes @@ -38,7 +37,7 @@ import getpass from datetime import datetime from pathlib import Path -from app.core import Config +from app.core import Config, logger class _SystemHandler: @@ -147,9 +146,15 @@ class _SystemHandler: ) if result.returncode == 0: - logger.info(f"任务计划程序自启动已创建: {Config.app_path_sys}") + logger.success( + f"程序自启动任务计划已创建: {Config.app_path_sys}", + module="系统服务", + ) else: - logger.error(f"创建任务计划失败: {result.stderr}") + logger.error( + f"程序自启动任务计划创建失败: {result.stderr}", + module="系统服务", + ) finally: # 删除临时文件 @@ -159,7 +164,7 @@ class _SystemHandler: pass except Exception as e: - logger.exception(f"设置任务计划程序自启动失败: {e}") + logger.exception(f"程序自启动任务计划创建失败: {e}", module="系统服务") elif not Config.get(Config.start_IfSelfStart) and self.is_startup(): @@ -174,40 +179,49 @@ class _SystemHandler: ) if result.returncode == 0: - logger.info("任务计划程序自启动已删除") + logger.success("程序自启动任务计划已删除", module="系统服务") else: - logger.error(f"删除任务计划失败: {result.stderr}") + logger.error( + f"程序自启动任务计划删除失败: {result.stderr}", + module="系统服务", + ) except Exception as e: - logger.exception(f"删除任务计划程序自启动失败: {e}") + logger.exception(f"程序自启动任务计划删除失败: {e}", module="系统服务") def set_power(self, mode) -> None: + """ + 执行系统电源操作 + + :param mode: 电源操作模式,支持 "NoAction", "Shutdown", "Hibernate", "Sleep", "KillSelf" + """ if sys.platform.startswith("win"): if mode == "NoAction": - logger.info("不执行系统电源操作") + logger.info("不执行系统电源操作", module="系统服务") elif mode == "Shutdown": - logger.info("执行关机操作") + logger.info("执行关机操作", module="系统服务") subprocess.run(["shutdown", "/s", "/t", "0"]) elif mode == "Hibernate": - logger.info("执行休眠操作") + logger.info("执行休眠操作", module="系统服务") subprocess.run(["shutdown", "/h"]) elif mode == "Sleep": - logger.info("执行睡眠操作") + logger.info("执行睡眠操作", module="系统服务") subprocess.run( ["rundll32.exe", "powrprof.dll,SetSuspendState", "0,1,0"] ) elif mode == "KillSelf": + logger.info("执行退出主程序操作", module="系统服务") Config.main_window.close() QApplication.quit() sys.exit(0) @@ -216,25 +230,26 @@ class _SystemHandler: if mode == "NoAction": - logger.info("不执行系统电源操作") + logger.info("不执行系统电源操作", module="系统服务") elif mode == "Shutdown": - logger.info("执行关机操作") + logger.info("执行关机操作", module="系统服务") subprocess.run(["shutdown", "-h", "now"]) elif mode == "Hibernate": - logger.info("执行休眠操作") + logger.info("执行休眠操作", module="系统服务") subprocess.run(["systemctl", "hibernate"]) elif mode == "Sleep": - logger.info("执行睡眠操作") + logger.info("执行睡眠操作", module="系统服务") subprocess.run(["systemctl", "suspend"]) elif mode == "KillSelf": + logger.info("执行退出主程序操作", module="系统服务") Config.main_window.close() QApplication.quit() sys.exit(0) @@ -252,11 +267,11 @@ class _SystemHandler: ) return result.returncode == 0 except Exception as e: - logger.error(f"检查任务计划程序失败: {e}") + logger.exception(f"检查任务计划程序失败: {e}", module="系统服务") return False def get_window_info(self) -> list: - """获取当前窗口信息""" + """获取当前前台窗口信息""" def callback(hwnd, window_info): if win32gui.IsWindowVisible(hwnd) and win32gui.GetWindowText(hwnd): @@ -270,7 +285,13 @@ class _SystemHandler: return window_info def kill_process(self, path: Path) -> None: - """根据路径中止进程""" + """ + 根据路径中止进程 + + :param path: 进程路径 + """ + + logger.info(f"开始中止进程: {path}", module="系统服务") for pid in self.search_pids(path): killprocess = subprocess.Popen( @@ -280,8 +301,17 @@ class _SystemHandler: ) killprocess.wait() + logger.success(f"进程已中止: {path}", module="系统服务") + def search_pids(self, path: Path) -> list: - """根据路径查找进程PID""" + """ + 根据路径查找进程PID + + :param path: 进程路径 + :return: 匹配的进程PID列表 + """ + + logger.info(f"开始查找进程 PID: {path}", module="系统服务") pids = [] for proc in psutil.process_iter(["pid", "exe"]): diff --git a/app/ui/dispatch_center.py b/app/ui/dispatch_center.py index d216411..1d9d531 100644 --- a/app/ui/dispatch_center.py +++ b/app/ui/dispatch_center.py @@ -25,7 +25,6 @@ v4.4 作者:DLmaster_361 """ -from loguru import logger from PySide6.QtWidgets import ( QWidget, QVBoxLayout, @@ -48,7 +47,7 @@ from PySide6.QtGui import QTextCursor from typing import List, Dict -from app.core import Config, TaskManager, Task, MainInfoBar, SoundPlayer +from app.core import Config, TaskManager, Task, MainInfoBar, logger from .Widget import StatefulItemCard, ComboBoxMessageBox, PivotArea @@ -59,10 +58,12 @@ class DispatchCenter(QWidget): self.setObjectName("调度中枢") + # 添加任务按钮 self.multi_button = PushButton(FluentIcon.ADD, "添加任务", self) self.multi_button.setToolTip("添加任务") self.multi_button.clicked.connect(self.start_multi_task) + # 电源动作设置组件 self.power_combox = ComboBox() self.power_combox.addItem("无动作", userData="NoAction") self.power_combox.addItem("退出软件", userData="KillSelf") @@ -72,15 +73,18 @@ class DispatchCenter(QWidget): self.power_combox.setCurrentText("无动作") self.power_combox.currentIndexChanged.connect(self.set_power_sign) + # 导航栏 self.pivotArea = PivotArea(self) self.pivot = self.pivotArea.pivot + # 导航页面组 self.stackedWidget = QStackedWidget(self) self.stackedWidget.setContentsMargins(0, 0, 0, 0) self.stackedWidget.setStyleSheet("background: transparent; border: none;") self.script_list: Dict[str, DispatchCenter.DispatchBox] = {} + # 添加主调度台 dispatch_box = self.DispatchBox("主调度台", self) self.script_list["主调度台"] = dispatch_box self.stackedWidget.addWidget(self.script_list["主调度台"]) @@ -91,6 +95,7 @@ class DispatchCenter(QWidget): icon=FluentIcon.CAFE, ) + # 顶栏组合 h_layout = QHBoxLayout() h_layout.addWidget(self.multi_button) h_layout.addWidget(self.pivotArea) @@ -108,7 +113,13 @@ class DispatchCenter(QWidget): ) def add_board(self, task: Task) -> None: - """添加一个调度台界面""" + """ + 为任务添加一个调度台界面并绑定信号 + + :param task: 任务对象 + """ + + logger.info(f"添加调度台:{task.name}", module="调度中枢") dispatch_box = self.DispatchBox(task.name, self) @@ -129,19 +140,36 @@ class DispatchCenter(QWidget): self.pivot.addItem(routeKey=f"调度台_{task.name}", text=f"调度台 {task.name}") + logger.success(f"调度台 {task.name} 添加成功", module="调度中枢") + def del_board(self, name: str) -> None: - """删除指定子界面""" + """ + 删除指定子界面 + + :param name: 子界面名称 + """ + + logger.info(f"删除调度台:{name}", module="调度中枢") self.pivot.setCurrentItem("主调度台") self.stackedWidget.removeWidget(self.script_list[name]) self.script_list[name].deleteLater() + self.script_list.pop(name) self.pivot.removeWidget(name) + logger.success(f"调度台 {name} 删除成功", module="调度中枢") + def connect_main_board(self, task: Task) -> None: - """连接主调度台""" + """ + 将任务连接到主调度台 + + :param task: 任务对象 + """ + + logger.info(f"主调度台载入任务:{task.name}", module="调度中枢") self.script_list["主调度台"].top_bar.Lable.setText( - f"{task.name} - {task.mode.replace("_主调度台","")}模式" + f"{task.name} - {task.mode.replace('_主调度台','')}模式" ) self.script_list["主调度台"].top_bar.Lable.show() self.script_list["主调度台"].top_bar.object.hide() @@ -170,8 +198,17 @@ class DispatchCenter(QWidget): lambda logs: self.disconnect_main_board(task.name, logs) ) + logger.success(f"主调度台成功载入:{task.name} ", module="调度中枢") + def disconnect_main_board(self, name: str, logs: list) -> None: - """断开主调度台""" + """ + 断开主调度台 + + :param name: 任务名称 + :param logs: 任务日志列表 + """ + + logger.info(f"主调度台断开任务:{name}", module="调度中枢") self.script_list["主调度台"].top_bar.Lable.hide() self.script_list["主调度台"].top_bar.object.show() @@ -191,6 +228,8 @@ class DispatchCenter(QWidget): else: self.script_list["主调度台"].info.log_text.text.setText("没有任务被执行") + logger.success(f"主调度台成功断开:{name}", module="调度中枢") + def update_top_bar(self): """更新顶栏""" @@ -253,10 +292,7 @@ class DispatchCenter(QWidget): self.power_combox.currentIndexChanged.connect(self.set_power_sign) logger.warning("没有正在运行的任务,无法设置任务完成后动作") MainInfoBar.push_info_bar( - "warning", - "没有正在运行的任务", - "无法设置任务完成后动作", - 5000, + "warning", "没有正在运行的任务", "无法设置任务完成后动作", 5000 ) else: @@ -264,7 +300,7 @@ class DispatchCenter(QWidget): Config.set_power_sign(self.power_combox.currentData()) def start_multi_task(self) -> None: - """开始任务""" + """开始多开任务""" # 获取所有可用的队列和实例 text_list = [] @@ -300,7 +336,9 @@ class DispatchCenter(QWidget): if choice.exec() and choice.input[0].currentIndex() != -1: if choice.input[0].currentData() in Config.running_list: - logger.warning(f"任务已存在:{choice.input[0].currentData()}") + logger.warning( + f"任务已存在:{choice.input[0].currentData()}", module="调度中枢" + ) MainInfoBar.push_info_bar( "warning", "任务已存在", choice.input[0].currentData(), 5000 ) @@ -308,7 +346,9 @@ class DispatchCenter(QWidget): if "调度队列" in choice.input[0].currentData(): - logger.info(f"用户添加任务:{choice.input[0].currentData()}") + logger.info( + f"用户添加任务:{choice.input[0].currentData()}", module="调度中枢" + ) TaskManager.add_task( "自动代理_新调度台", choice.input[0].currentData(), @@ -317,7 +357,9 @@ class DispatchCenter(QWidget): elif "脚本" in choice.input[0].currentData(): - logger.info(f"用户添加任务:{choice.input[0].currentData()}") + logger.info( + f"用户添加任务:{choice.input[0].currentData()}", module="调度中枢" + ) TaskManager.add_task( "自动代理_新调度台", f"自定义队列 - {choice.input[0].currentData()}", @@ -384,24 +426,26 @@ class DispatchCenter(QWidget): Layout.addWidget(self.main_button) def start_main_task(self): - """开始任务""" + """从主调度台开始任务""" if self.object.currentIndex() == -1: - logger.warning("未选择调度对象") + logger.warning("未选择调度对象", module="调度中枢") MainInfoBar.push_info_bar( "warning", "未选择调度对象", "请选择后再开始任务", 5000 ) return None if self.mode.currentIndex() == -1: - logger.warning("未选择调度模式") + logger.warning("未选择调度模式", module="调度中枢") MainInfoBar.push_info_bar( "warning", "未选择调度模式", "请选择后再开始任务", 5000 ) return None if self.object.currentData() in Config.running_list: - logger.warning(f"任务已存在:{self.object.currentData()}") + logger.warning( + f"任务已存在:{self.object.currentData()}", module="调度中枢" + ) MainInfoBar.push_info_bar( "warning", "任务已存在", self.object.currentData(), 5000 ) @@ -413,7 +457,7 @@ class DispatchCenter(QWidget): == "General" and self.mode.currentData() == "人工排查" ): - logger.warning("通用脚本类型不存在人工排查功能") + logger.warning("通用脚本类型不存在人工排查功能", module="调度中枢") MainInfoBar.push_info_bar( "warning", "不支持的任务", "通用脚本无人工排查功能", 5000 ) @@ -421,7 +465,9 @@ class DispatchCenter(QWidget): if "调度队列" in self.object.currentData(): - logger.info(f"用户添加任务:{self.object.currentData()}") + logger.info( + f"用户添加任务:{self.object.currentData()}", module="调度中枢" + ) TaskManager.add_task( f"{self.mode.currentText()}_主调度台", self.object.currentData(), @@ -430,7 +476,9 @@ class DispatchCenter(QWidget): elif "脚本" in self.object.currentData(): - logger.info(f"用户添加任务:{self.object.currentData()}") + logger.info( + f"用户添加任务:{self.object.currentData()}", module="调度中枢" + ) TaskManager.add_task( f"{self.mode.currentText()}_主调度台", "自定义队列", @@ -476,7 +524,11 @@ class DispatchCenter(QWidget): self.task_cards: List[StatefulItemCard] = [] def create_task(self, task_list: list): - """创建任务队列""" + """ + 创建任务队列 + + :param task_list: 包含任务信息的任务列表 + """ while self.Layout.count() > 0: item = self.Layout.takeAt(0) @@ -495,7 +547,11 @@ class DispatchCenter(QWidget): self.Layout.addStretch(1) def update_task(self, task_list: list): - """更新任务队列""" + """ + 更新任务队列信息 + + :param task_list: 包含任务信息的任务列表 + """ for i in range(len(task_list)): @@ -514,7 +570,11 @@ class DispatchCenter(QWidget): self.user_cards: List[StatefulItemCard] = [] def create_user(self, user_list: list): - """创建用户队列""" + """ + 创建用户队列 + + :param user_list: 包含用户信息的用户列表 + """ while self.Layout.count() > 0: item = self.Layout.takeAt(0) @@ -533,7 +593,11 @@ class DispatchCenter(QWidget): self.Layout.addStretch(1) def update_user(self, user_list: list): - """更新用户队列""" + """ + 更新用户队列信息 + + :param user_list: 包含用户信息的用户列表 + """ for i in range(len(user_list)): diff --git a/app/ui/downloader.py b/app/ui/downloader.py index ae3aac1..17e7920 100644 --- a/app/ui/downloader.py +++ b/app/ui/downloader.py @@ -25,7 +25,6 @@ v4.4 作者:DLmaster_361 """ -from loguru import logger import zipfile import requests import subprocess @@ -46,7 +45,7 @@ from PySide6.QtCore import QThread, Signal, QTimer, QEventLoop from typing import List, Dict, Union -from app.core import Config +from app.core import Config, logger from app.services import System @@ -83,6 +82,8 @@ class DownloadProcess(QThread): self.setObjectName(f"DownloadProcess-{url}-{start_byte}-{end_byte}") + logger.info(f"创建下载子线程:{self.objectName()}", module="下载子线程") + self.url = url self.start_byte = start_byte self.end_byte = end_byte @@ -97,7 +98,8 @@ class DownloadProcess(QThread): self.download_path.unlink() logger.info( - f"开始下载:{self.url},范围:{self.start_byte}-{self.end_byte},存储地址:{self.download_path}" + f"开始下载:{self.url},范围:{self.start_byte}-{self.end_byte},存储地址:{self.download_path}", + module="下载子线程", ) headers = ( @@ -129,13 +131,17 @@ class DownloadProcess(QThread): self.check_times -= 1 logger.error( - f"连接失败:{self.url},状态码:{response.status_code},剩余重试次数:{self.check_times}" + f"连接失败:{self.url},状态码:{response.status_code},剩余重试次数:{self.check_times}", + module="下载子线程", ) time.sleep(1) continue - logger.info(f"连接成功:{self.url},状态码:{response.status_code}") + logger.info( + f"连接成功:{self.url},状态码:{response.status_code}", + module="下载子线程", + ) downloaded_size = 0 with self.download_path.open(mode="wb") as f: @@ -155,13 +161,14 @@ class DownloadProcess(QThread): if self.download_path.exists(): self.download_path.unlink() self.accomplish.emit(0) - logger.info(f"下载中止:{self.url}") + logger.info(f"下载中止:{self.url}", module="下载子线程") else: self.accomplish.emit(time.time() - start_time) logger.success( - f"下载完成:{self.url},实际下载大小:{downloaded_size} 字节,耗时:{time.time() - start_time:.2f} 秒" + f"下载完成:{self.url},实际下载大小:{downloaded_size} 字节,耗时:{time.time() - start_time:.2f} 秒", + module="下载子线程", ) break @@ -172,7 +179,8 @@ class DownloadProcess(QThread): self.check_times -= 1 logger.exception( - f"下载出错:{self.url},错误信息:{e},剩余重试次数:{self.check_times}" + f"下载出错:{self.url},错误信息:{e},剩余重试次数:{self.check_times}", + module="下载子线程", ) time.sleep(1) @@ -181,7 +189,7 @@ class DownloadProcess(QThread): if self.download_path.exists(): self.download_path.unlink() self.accomplish.emit(0) - logger.error(f"下载失败:{self.url}") + logger.error(f"下载失败:{self.url}", module="下载子线程") class ZipExtractProcess(QThread): @@ -195,6 +203,8 @@ class ZipExtractProcess(QThread): self.setObjectName(f"ZipExtractProcess-{name}") + logger.info(f"创建解压子线程:{self.objectName()}", module="解压子线程") + self.name = name self.app_path = app_path self.download_path = download_path @@ -204,7 +214,10 @@ class ZipExtractProcess(QThread): try: - logger.info(f"开始解压:{self.download_path} 到 {self.app_path}") + logger.info( + f"开始解压:{self.download_path} 到 {self.app_path}", + module="解压子线程", + ) while True: @@ -215,7 +228,10 @@ class ZipExtractProcess(QThread): with zipfile.ZipFile(self.download_path, "r") as zip_ref: zip_ref.extractall(self.app_path) self.accomplish.emit() - logger.success(f"解压完成:{self.download_path} 到 {self.app_path}") + logger.success( + f"解压完成:{self.download_path} 到 {self.app_path}", + module="解压子线程", + ) break except PermissionError: if self.name == "AUTO_MAA": @@ -223,7 +239,10 @@ class ZipExtractProcess(QThread): System.kill_process(self.app_path / "AUTO_MAA.exe") else: self.info.emit(f"解压出错:{self.name}正在运行,正在等待其关闭") - logger.warning(f"解压出错:{self.name}正在运行,正在等待其关闭") + logger.warning( + f"解压出错:{self.name}正在运行,正在等待其关闭", + module="解压子线程", + ) time.sleep(1) except Exception as e: @@ -231,7 +250,7 @@ class ZipExtractProcess(QThread): e = str(e) e = "\n".join([e[_ : _ + 75] for _ in range(0, len(e), 75)]) self.info.emit(f"解压更新时出错:\n{e}") - logger.exception(f"解压更新时出错:{e}") + logger.exception(f"解压更新时出错:{e}", module="解压子线程") return None @@ -277,17 +296,27 @@ class DownloadManager(QDialog): def run(self) -> None: + logger.info( + f"开始执行下载任务:{self.name},版本:{version_text(self.version)}", + module="下载管理器", + ) + if self.name == "AUTO_MAA": if self.config["mode"] == "Proxy": - self.test_speed_task1() - self.speed_test_accomplish.connect(self.download_task1) + self.start_test_speed() + self.speed_test_accomplish.connect(self.start_download) elif self.config["mode"] == "MirrorChyan": - self.download_task1() + self.start_download() elif self.config["mode"] == "MirrorChyan": - self.download_task1() + self.start_download() def get_download_url(self, mode: str) -> Union[str, Dict[str, str]]: - """获取下载链接""" + """ + 生成下载链接 + + :param mode: "测速" 或 "下载" + :return: 测速模式返回 url 字典,下载模式返回 url 字符串 + """ url_dict = {} @@ -362,7 +391,8 @@ class DownloadManager(QDialog): if response.status_code == 200: return response.url - def test_speed_task1(self) -> None: + def start_test_speed(self) -> None: + """启动测速任务,下载4MB文件以测试下载速度""" if self.isInterruptionRequested: return None @@ -370,7 +400,7 @@ class DownloadManager(QDialog): url_dict = self.get_download_url("测速") self.test_speed_result: Dict[str, float] = {} - logger.info(f"测速链接:{url_dict}") + logger.info(f"开始测速任务,链接:{url_dict}", module="下载管理器") for name, url in url_dict.items(): @@ -387,10 +417,11 @@ class DownloadManager(QDialog): ) self.test_speed_result[name] = -1 self.download_process_dict[name].accomplish.connect( - partial(self.test_speed_task2, name) + partial(self.check_test_speed, name) ) - self.download_process_dict[name].start() + + # 创建防超时定时器,30秒后强制停止测速 timer = QTimer(self) timer.setSingleShot(True) timer.timeout.connect(partial(self.kill_speed_test, name)) @@ -401,11 +432,22 @@ class DownloadManager(QDialog): self.update_progress(0, 1, 0) def kill_speed_test(self, name: str) -> None: + """ + 强制停止测速任务 + + :param name: 测速任务的名称 + """ if name in self.download_process_dict: self.download_process_dict[name].requestInterruption() - def test_speed_task2(self, name: str, t: float) -> None: + def check_test_speed(self, name: str, t: float) -> None: + """ + 更新测速子任务wc信息,并检查测速任务是否允许结束 + + :param name: 测速任务的名称 + :param t: 测速任务的耗时 + """ # 计算下载速度 if self.isInterruptionRequested: @@ -453,12 +495,13 @@ class DownloadManager(QDialog): # 保存测速结果 self.config["speed_result"] = self.test_speed_result - logger.info(f"测速结果:{self.test_speed_result}") + logger.success(f"测速完成,结果:{self.test_speed_result}", module="下载管理器") self.update_info("测速完成!") self.speed_test_accomplish.emit() - def download_task1(self) -> None: + def start_download(self) -> None: + """开始下载任务""" if self.isInterruptionRequested: return None @@ -466,6 +509,8 @@ class DownloadManager(QDialog): url = self.get_download_url("下载") self.downloaded_size_list: List[List[int, bool]] = [] + logger.info(f"开始下载任务,链接:{url}", module="下载管理器") + response = requests.head( url, timeout=10, @@ -506,20 +551,27 @@ class DownloadManager(QDialog): ) self.downloaded_size_list.append([0, False]) self.download_process_dict[f"part{i}"].progress.connect( - partial(self.download_task2, i) + partial(self.update_download, i) ) self.download_process_dict[f"part{i}"].accomplish.connect( - partial(self.download_task3, i) + partial(self.check_download, i) ) self.download_process_dict[f"part{i}"].start() - def download_task2(self, index: str, current: int) -> None: - """更新下载进度""" + def update_download(self, index: str, current: int) -> None: + """ + 更新子任务下载进度,将信息更新到 UI 上 + :param index: 下载任务的索引 + :param current: 当前下载大小 + """ + + # 更新指定线程的下载进度 self.downloaded_size_list[index][0] = current self.downloaded_size = sum([_[0] for _ in self.downloaded_size_list]) self.update_progress(0, self.file_size, self.downloaded_size) + # 速度每秒更新一次 if time.time() - self.last_time >= 1.0: self.speed = ( (self.downloaded_size - self.last_download_size) @@ -538,7 +590,13 @@ class DownloadManager(QDialog): f"正在下载:{self.name} 已下载:{self.downloaded_size / 1048576:.2f}/{self.file_size / 1048576:.2f} MB ({self.downloaded_size / self.file_size * 100:.2f}%) 下载速度:{self.speed:.2f} KB/s", ) - def download_task3(self, index: str, t: float) -> None: + def check_download(self, index: str, t: float) -> None: + """ + 更新下载子任务完成信息,检查下载任务是否完成,完成后自动执行后续处理任务 + + :param index: 下载任务的索引 + :param t: 下载任务的耗时 + """ # 标记下载线程完成 self.downloaded_size_list[index][1] = True @@ -560,7 +618,8 @@ class DownloadManager(QDialog): # 合并下载的分段文件 logger.info( - f"所有分段下载完成:{self.name},开始合并分段文件到 {self.download_path}" + f"所有分段下载完成:{self.name},开始合并分段文件到 {self.download_path}", + module="下载管理器", ) with self.download_path.open(mode="wb") as outfile: for i in range(self.config["thread_numb"]): @@ -571,7 +630,8 @@ class DownloadManager(QDialog): self.download_path.with_suffix(f".part{i}").unlink() logger.success( - f"合并完成:{self.name},下载文件大小:{self.download_path.stat().st_size} 字节" + f"合并完成:{self.name},下载文件大小:{self.download_path.stat().st_size} 字节", + module="下载管理器", ) self.update_info("正在解压更新文件") @@ -610,9 +670,21 @@ class DownloadManager(QDialog): self.download_accomplish.emit() def update_info(self, text: str) -> None: + """ + 更新信息文本 + + :param text: 要显示的信息文本 + """ self.info.setText(text) def update_progress(self, begin: int, end: int, current: int) -> None: + """ + 更新进度条 + + :param begin: 进度条起始值 + :param end: 进度条结束值 + :param current: 进度条当前值 + """ if begin == 0 and end == 0: self.progress_2.setVisible(False) @@ -626,7 +698,7 @@ class DownloadManager(QDialog): def requestInterruption(self) -> None: """请求中断下载任务""" - logger.info("收到下载任务中止请求") + logger.info("收到下载任务中止请求", module="下载管理器") self.isInterruptionRequested = True diff --git a/app/ui/history.py b/app/ui/history.py index b253f1d..d0d45b3 100644 --- a/app/ui/history.py +++ b/app/ui/history.py @@ -25,7 +25,6 @@ v4.4 作者:DLmaster_361 """ -from loguru import logger from PySide6.QtWidgets import ( QWidget, QVBoxLayout, @@ -51,11 +50,12 @@ from pathlib import Path from typing import List, Dict -from app.core import Config, SoundPlayer +from app.core import Config, SoundPlayer, logger from .Widget import StatefulItemCard, QuantifiedItemCard, QuickExpandGroupCard class History(QWidget): + """历史记录界面""" def __init__(self, parent=None): super().__init__(parent) @@ -81,10 +81,21 @@ class History(QWidget): self.history_card_list = [] def reload_history(self, mode: str, start_date: QDate, end_date: QDate) -> None: - """加载历史记录界面""" + """ + 加载历史记录界面 + :param mode: 查询模式 + :param start_date: 查询范围起始日期 + :param end_date: 查询范围结束日期 + """ + + logger.info( + f"查询历史记录: {mode}, {start_date.toString()}, {end_date.toString()}", + module="历史记录", + ) SoundPlayer.play("历史记录查询") + # 清空已有的历史记录卡片 while self.content_layout.count() > 0: item = self.content_layout.takeAt(0) if item.spacerItem(): @@ -100,6 +111,7 @@ class History(QWidget): datetime(end_date.year(), end_date.month(), end_date.day()), ) + # 生成历史记录卡片并添加到布局中 for date, user_dict in history_dict.items(): self.history_card_list.append(self.HistoryCard(date, user_dict, self)) @@ -154,7 +166,13 @@ class History(QWidget): Layout.addWidget(self.search) def select_date(self, date: str) -> None: - """选中最近一段时间并启动查询""" + """ + 选中最近一段时间并启动查询 + + :param date: 选择的时间段("week" 或 "month") + """ + + logger.info(f"选择最近{date}的记录并开始查询", module="历史记录") server_date = Config.server_date() if date == "week": @@ -187,6 +205,7 @@ class History(QWidget): self.user_history_card_list = [] + # 生成用户历史记录卡片并添加到布局中 for user, info in user_dict.items(): self.user_history_card_list.append( self.UserHistoryCard(user, info, self) @@ -219,7 +238,12 @@ class History(QWidget): self.update_info("数据总览") def get_statistics(self, mode: str) -> dict: - """生成GUI相应结构化统计数据""" + """ + 生成GUI相应结构化统计数据 + + :param mode: 查询模式 + :return: 结构化统计数据 + """ history_info = Config.merge_statistic_info( self.user_history if mode == "数据总览" else [Path(mode)] @@ -244,7 +268,11 @@ class History(QWidget): return statistics_info def update_info(self, index: str) -> None: - """更新信息""" + """ + 更新信息到UI界面 + + :param index: 选择的索引 + """ # 移除已有统计信息UI组件 while self.statistics_card.count() > 0: @@ -254,8 +282,10 @@ class History(QWidget): elif item.widget(): item.widget().deleteLater() + # 统计信息上传至 UI if index == "数据总览": + # 生成数据统计信息卡片组 for name, item_list in self.get_statistics("数据总览").items(): statistics_card = self.StatisticsCard(name, item_list, self) @@ -268,10 +298,12 @@ class History(QWidget): single_history = self.get_statistics(index) log_path = Path(index).with_suffix(".log") + # 生成单个历史记录的统计信息卡片组 for name, item_list in single_history.items(): statistics_card = self.StatisticsCard(name, item_list, self) self.statistics_card.addWidget(statistics_card) + # 显示日志信息并绑定点击事件 with log_path.open("r", encoding="utf-8") as f: log = f.read() @@ -291,6 +323,7 @@ class History(QWidget): self.setMinimumHeight(300) class IndexCard(HeaderCardWidget): + """历史记录索引卡片组""" index_changed = Signal(str) @@ -304,9 +337,11 @@ class History(QWidget): self.index_cards: List[StatefulItemCard] = [] + # 生成索引卡片信息 index_list = Config.merge_statistic_info(history_list)["index"] index_list.insert(0, ["数据总览", "运行", "数据总览"]) + # 生成索引卡片组件并绑定点击事件 for index in index_list: self.index_cards.append(StatefulItemCard(index[:2])) @@ -318,6 +353,7 @@ class History(QWidget): self.Layout.addStretch(1) class StatisticsCard(HeaderCardWidget): + """历史记录统计信息卡片组""" def __init__(self, name: str, item_list: list, parent=None): super().__init__(parent) @@ -340,6 +376,7 @@ class History(QWidget): self.Layout.addStretch(1) class LogCard(HeaderCardWidget): + """历史记录日志卡片""" def __init__(self, parent=None): super().__init__(parent) diff --git a/app/ui/home.py b/app/ui/home.py index fa3dba5..0e332f7 100644 --- a/app/ui/home.py +++ b/app/ui/home.py @@ -25,7 +25,6 @@ v4.4 作者:DLmaster_361 """ -from loguru import logger from PySide6.QtWidgets import ( QWidget, QVBoxLayout, @@ -49,7 +48,7 @@ import json from datetime import datetime from pathlib import Path -from app.core import Config, MainInfoBar, Network +from app.core import Config, MainInfoBar, Network, logger from .Widget import Banner, IconButton @@ -160,8 +159,12 @@ class Home(QWidget): def get_home_image(self) -> None: """获取主页图片""" + logger.info("获取主页图片", module="主页") + if Config.get(Config.function_HomeImageMode) == "默认": - pass + + logger.info("使用默认主页图片", module="主页") + elif Config.get(Config.function_HomeImageMode) == "自定义": file_path, _ = QFileDialog.getOpenFileName( @@ -180,7 +183,7 @@ class Home(QWidget): / f"resources/images/Home/BannerCustomize{Path(file_path).suffix}", ) - logger.info(f"自定义主页图片更换成功:{file_path}") + logger.info(f"自定义主页图片更换成功:{file_path}", module="主页") MainInfoBar.push_info_bar( "success", "主页图片更换成功", @@ -189,7 +192,7 @@ class Home(QWidget): ) else: - logger.warning("自定义主页图片更换失败:未选择图片文件") + logger.warning("自定义主页图片更换失败:未选择图片文件", module="主页") MainInfoBar.push_info_bar( "warning", "主页图片更换失败", @@ -198,7 +201,7 @@ class Home(QWidget): ) elif Config.get(Config.function_HomeImageMode) == "主题图像": - # 从远程服务器获取最新主题图像 + # 从远程服务器获取最新主题图像信息 network = Network.add_task( mode="get", url="https://gitee.com/DLmaster_361/AUTO_MAA/raw/server/theme_image.json", @@ -209,7 +212,8 @@ class Home(QWidget): theme_image = network_result["response_json"] else: logger.warning( - f"获取最新主题图像时出错:{network_result['error_message']}" + f"获取最新主题图像时出错:{network_result['error_message']}", + module="主页", ) MainInfoBar.push_info_bar( "warning", @@ -230,6 +234,7 @@ class Home(QWidget): else: time_local = datetime.strptime("2000-01-01 00:00", "%Y-%m-%d %H:%M") + # 检查主题图像是否需要更新 if not ( Config.app_path / "resources/images/Home/BannerTheme.jpg" ).exists() or ( @@ -253,7 +258,9 @@ class Home(QWidget): ) as f: json.dump(theme_image, f, ensure_ascii=False, indent=4) - logger.success(f"主题图像「{theme_image["name"]}」下载成功") + logger.success( + f"主题图像「{theme_image["name"]}」下载成功", module="主页" + ) MainInfoBar.push_info_bar( "success", "主题图像下载成功", @@ -264,7 +271,8 @@ class Home(QWidget): else: logger.warning( - f"下载最新主题图像时出错:{network_result['error_message']}" + f"下载最新主题图像时出错:{network_result['error_message']}", + module="主页", ) MainInfoBar.push_info_bar( "warning", @@ -275,18 +283,16 @@ class Home(QWidget): else: - logger.info("主题图像已是最新") + logger.info("主题图像已是最新", module="主页") MainInfoBar.push_info_bar( - "info", - "主题图像已是最新", - "主题图像已是最新!", - 3000, + "info", "主题图像已是最新", "主题图像已是最新!", 3000 ) self.set_banner() def set_banner(self): """设置主页图像""" + if Config.get(Config.function_HomeImageMode) == "默认": self.banner.set_banner_image( str(Config.app_path / "resources/images/Home/BannerDefault.png") @@ -366,7 +372,7 @@ class ButtonGroup(SimpleCardWidget): doc_button = IconButton( FluentIcon.CHAT.icon(color=QColor("#fff")), tip_title="官方社群", - tip_content="加入官方群聊【AUTO_MAA绝赞DeBug中!】", + tip_content="加入官方群聊「AUTO_MAA绝赞DeBug中!」", isTooltip=True, ) doc_button.setIconSize(QSize(32, 32)) diff --git a/app/ui/main_window.py b/app/ui/main_window.py index 3a97d12..3f37120 100644 --- a/app/ui/main_window.py +++ b/app/ui/main_window.py @@ -25,7 +25,6 @@ v4.4 作者:DLmaster_361 """ -from loguru import logger from PySide6.QtWidgets import QApplication, QSystemTrayIcon from qfluentwidgets import ( Action, @@ -45,7 +44,7 @@ from datetime import datetime, timedelta import shutil import darkdetect -from app.core import Config, TaskManager, MainTimer, MainInfoBar, SoundPlayer +from app.core import Config, logger, TaskManager, MainTimer, MainInfoBar, SoundPlayer from app.services import Notify, Crypto, System from .home import Home from .member_manager import MemberManager @@ -57,6 +56,7 @@ from .setting import Setting class AUTO_MAA(MSFluentWindow): + """AUTO_MAA主界面""" def __init__(self): super().__init__() @@ -77,9 +77,11 @@ class AUTO_MAA(MSFluentWindow): self.splashScreen = SplashScreen(self.windowIcon(), self) self.show_ui("显示主窗口", if_quick=True) + # 设置主窗口的引用,便于各组件访问 Config.main_window = self.window() - # 创建主窗口 + # 创建各子窗口 + logger.info("正在创建各子窗口", module="主窗口") self.home = Home(self) self.plan_manager = PlanManager(self) self.member_manager = MemberManager(self) @@ -138,8 +140,10 @@ class AUTO_MAA(MSFluentWindow): NavigationItemPosition.BOTTOM, ) self.stackedWidget.currentChanged.connect(self.__currentChanged) + logger.success("各子窗口创建完成", module="主窗口") # 创建系统托盘及其菜单 + logger.info("正在创建系统托盘", module="主窗口") self.tray = QSystemTrayIcon( QIcon(str(Config.app_path / "resources/icons/AUTO_MAA.ico")), self ) @@ -181,9 +185,11 @@ class AUTO_MAA(MSFluentWindow): # 设置托盘菜单 self.tray.setContextMenu(self.tray_menu) self.tray.activated.connect(self.on_tray_activated) + logger.success("系统托盘创建完成", module="主窗口") self.set_min_method() + # 绑定各组件信号 Config.sub_info_changed.connect(self.member_manager.refresh_dashboard) Config.power_sign_changed.connect(self.dispatch_center.update_power_sign) TaskManager.create_gui.connect(self.dispatch_center.add_board) @@ -205,6 +211,8 @@ class AUTO_MAA(MSFluentWindow): self.themeListener.systemThemeChanged.connect(self.switch_theme) self.themeListener.start() + logger.success("AUTO_MAA主程序初始化完成", module="主窗口") + def switch_theme(self) -> None: """切换主题""" @@ -348,8 +356,10 @@ class AUTO_MAA(MSFluentWindow): def start_up_task(self) -> None: """启动时任务""" - # 清理旧日志 - self.clean_old_logs() + logger.info("开始执行启动时任务", module="主窗口") + + # 清理旧历史记录 + Config.clean_old_history() # 清理安装包 if (Config.app_path / "AUTO_MAA-Setup.exe").exists(): @@ -373,6 +383,9 @@ class AUTO_MAA(MSFluentWindow): self.start_main_task() + # 启动定时器 + MainTimer.start() + # 获取公告 self.setting.show_notice(if_first=True) @@ -420,41 +433,16 @@ class AUTO_MAA(MSFluentWindow): ) System.set_power("KillSelf") - def clean_old_logs(self): - """ - 删除超过用户设定天数的日志文件(基于目录日期) - """ - - if Config.get(Config.function_HistoryRetentionTime) == 0: - logger.info("由于用户设置日志永久保留,跳过日志清理") - return - - deleted_count = 0 - - for date_folder in (Config.app_path / "history").iterdir(): - if not date_folder.is_dir(): - continue # 只处理日期文件夹 - - try: - # 只检查 `YYYY-MM-DD` 格式的文件夹 - folder_date = datetime.strptime(date_folder.name, "%Y-%m-%d") - if datetime.now() - folder_date > timedelta( - days=Config.get(Config.function_HistoryRetentionTime) - ): - shutil.rmtree(date_folder, ignore_errors=True) - deleted_count += 1 - logger.info(f"已删除超期日志目录: {date_folder}") - except ValueError: - logger.warning(f"非日期格式的目录: {date_folder}") - - logger.info(f"清理完成: {deleted_count} 个日期目录") + logger.success("启动时任务执行完成", module="主窗口") def start_main_task(self) -> None: """启动主任务""" + logger.info("正在启动主任务", module="主窗口") + if "调度队列_1" in Config.queue_dict: - logger.info("自动添加任务:调度队列_1") + logger.info("自动添加任务:调度队列_1", module="主窗口") TaskManager.add_task( "自动代理_主调度台", "调度队列_1", @@ -463,18 +451,22 @@ class AUTO_MAA(MSFluentWindow): elif "脚本_1" in Config.member_dict: - logger.info("自动添加任务:脚本_1") + logger.info("自动添加任务:脚本_1", module="主窗口") TaskManager.add_task( "自动代理_主调度台", "自定义队列", {"Queue": {"Member_1": "脚本_1"}} ) else: - logger.warning("启动主任务失败:未找到有效的主任务配置文件") + logger.warning( + "启动主任务失败:未找到有效的主任务配置文件", module="主窗口" + ) MainInfoBar.push_info_bar( "warning", "启动主任务失败", "「调度队列_1」与「脚本_1」均不存在", -1 ) + logger.success("主任务启动完成", module="主窗口") + def __currentChanged(self, index: int) -> None: """切换界面时任务""" @@ -489,20 +481,18 @@ class AUTO_MAA(MSFluentWindow): def closeEvent(self, event: QCloseEvent): """清理残余进程""" + logger.info("保存窗口位置与大小信息", module="主窗口") self.show_ui("隐藏到托盘", if_quick=True) # 清理各功能线程 - MainTimer.Timer.stop() - MainTimer.Timer.deleteLater() - MainTimer.LongTimer.stop() - MainTimer.LongTimer.deleteLater() + MainTimer.stop() TaskManager.stop_task("ALL") # 关闭主题监听 self.themeListener.terminate() self.themeListener.deleteLater() - logger.info("AUTO_MAA主程序关闭") - logger.info("----------------END----------------") + logger.info("AUTO_MAA主程序关闭", module="主窗口") + logger.info("----------------END----------------", module="主窗口") event.accept() diff --git a/app/ui/member_manager.py b/app/ui/member_manager.py index 13d5f07..3fa4146 100644 --- a/app/ui/member_manager.py +++ b/app/ui/member_manager.py @@ -25,7 +25,6 @@ v4.4 作者:DLmaster_361 """ -from loguru import logger from PySide6.QtWidgets import ( QWidget, QFileDialog, @@ -61,6 +60,7 @@ import json from app.core import ( Config, + logger, MainInfoBar, TaskManager, MaaConfig, @@ -109,31 +109,22 @@ class MemberManager(QWidget): layout = QVBoxLayout(self) self.tools = CommandBar() - self.member_manager = self.MemberSettingBox(self) # 逐个添加动作 self.tools.addActions( [ + Action(FluentIcon.ADD_TO, "新建脚本实例", triggered=self.add_member), Action( - FluentIcon.ADD_TO, "新建脚本实例", triggered=self.add_setting_box - ), - Action( - FluentIcon.REMOVE_FROM, - "删除脚本实例", - triggered=self.del_setting_box, + FluentIcon.REMOVE_FROM, "删除脚本实例", triggered=self.del_member ), ] ) self.tools.addSeparator() self.tools.addActions( [ - Action( - FluentIcon.LEFT_ARROW, "向左移动", triggered=self.left_setting_box - ), - Action( - FluentIcon.RIGHT_ARROW, "向右移动", triggered=self.right_setting_box - ), + Action(FluentIcon.LEFT_ARROW, "向左移动", triggered=self.left_member), + Action(FluentIcon.RIGHT_ARROW, "向右移动", triggered=self.right_member), ] ) self.tools.addSeparator() @@ -156,7 +147,7 @@ class MemberManager(QWidget): layout.addWidget(self.tools) layout.addWidget(self.member_manager) - def add_setting_box(self): + def add_member(self): """添加一个脚本实例""" choice = ComboBoxMessageBox( @@ -167,10 +158,15 @@ class MemberManager(QWidget): ) if choice.exec() and choice.input[0].currentIndex() != -1: + logger.info( + f"添加脚本实例: {choice.input[0].currentText()}", module="脚本管理" + ) + if choice.input[0].currentText() == "MAA": index = len(Config.member_dict) + 1 + # 初始化 MAA 配置 maa_config = MaaConfig() maa_config.load( Config.app_path / f"config/MaaConfig/脚本_{index}/config.json", @@ -188,12 +184,13 @@ class MemberManager(QWidget): "UserData": {}, } + # 添加 MAA 实例设置界面 self.member_manager.add_SettingBox( index, self.MemberSettingBox.MaaSettingBox ) self.member_manager.switch_SettingBox(index) - logger.success(f"MAA实例 脚本_{index} 添加成功") + logger.success(f"MAA实例 脚本_{index} 添加成功", module="脚本管理") MainInfoBar.push_info_bar( "success", "操作成功", f"添加 MAA 实例 脚本_{index}", 3000 ) @@ -203,6 +200,7 @@ class MemberManager(QWidget): index = len(Config.member_dict) + 1 + # 初始化通用配置 general_config = GeneralConfig() general_config.load( Config.app_path / f"config/GeneralConfig/脚本_{index}/config.json", @@ -220,31 +218,32 @@ class MemberManager(QWidget): "SubData": {}, } + # 添加通用实例设置界面 self.member_manager.add_SettingBox( index, self.MemberSettingBox.GeneralSettingBox ) self.member_manager.switch_SettingBox(index) - logger.success(f"通用实例 脚本_{index} 添加成功") + logger.success(f"通用实例 脚本_{index} 添加成功", module="脚本管理") MainInfoBar.push_info_bar( "success", "操作成功", f"添加通用实例 脚本_{index}", 3000 ) SoundPlayer.play("添加脚本实例") - def del_setting_box(self): + def del_member(self): """删除一个脚本实例""" name = self.member_manager.pivot.currentRouteKey() if name is None: - logger.warning("删除脚本实例时未选择脚本实例") + logger.warning("删除脚本实例时未选择脚本实例", module="脚本管理") MainInfoBar.push_info_bar( "warning", "未选择脚本实例", "请选择一个脚本实例", 5000 ) return None if len(Config.running_list) > 0: - logger.warning("删除脚本实例时调度队列未停止运行") + logger.warning("删除脚本实例时调度队列未停止运行", module="脚本管理") MainInfoBar.push_info_bar( "warning", "调度中心正在执行任务", "请等待或手动中止任务", 5000 ) @@ -253,8 +252,11 @@ class MemberManager(QWidget): choice = MessageBox("确认", f"确定要删除 {name} 实例吗?", self.window()) if choice.exec(): + logger.info(f"正在删除脚本实例: {name}", module="脚本管理") + self.member_manager.clear_SettingBox() + # 删除脚本实例的配置文件并同步修改相应配置项 shutil.rmtree(Config.member_dict[name]["Path"]) Config.change_queue(name, "禁用") for i in range(int(name[3:]) + 1, len(Config.member_dict) + 1): @@ -266,19 +268,19 @@ class MemberManager(QWidget): self.member_manager.show_SettingBox(max(int(name[3:]) - 1, 1)) - logger.success(f"脚本实例 {name} 删除成功") + logger.success(f"脚本实例 {name} 删除成功", module="脚本管理") MainInfoBar.push_info_bar( "success", "操作成功", f"删除脚本实例 {name}", 3000 ) SoundPlayer.play("删除脚本实例") - def left_setting_box(self): + def left_member(self): """向左移动脚本实例""" name = self.member_manager.pivot.currentRouteKey() if name is None: - logger.warning("向左移动脚本实例时未选择脚本实例") + logger.warning("向左移动脚本实例时未选择脚本实例", module="脚本管理") MainInfoBar.push_info_bar( "warning", "未选择脚本实例", "请选择一个脚本实例", 5000 ) @@ -287,21 +289,24 @@ class MemberManager(QWidget): index = int(name[3:]) if index == 1: - logger.warning("向左移动脚本实例时已到达最左端") + logger.warning("向左移动脚本实例时已到达最左端", module="脚本管理") MainInfoBar.push_info_bar( "warning", "已经是第一个脚本实例", "无法向左移动", 5000 ) return None if len(Config.running_list) > 0: - logger.warning("向左移动脚本实例时调度队列未停止运行") + logger.warning("向左移动脚本实例时调度队列未停止运行", module="脚本管理") MainInfoBar.push_info_bar( "warning", "调度中心正在执行任务", "请等待或手动中止任务", 5000 ) return None + logger.info(f"正在向左移动脚本实例: {name}", module="脚本管理") + self.member_manager.clear_SettingBox() + # 移动脚本实例配置文件并同步修改配置项 Config.member_dict[name]["Path"].rename( Config.member_dict[name]["Path"].with_name("脚本_0") ) @@ -317,16 +322,16 @@ class MemberManager(QWidget): self.member_manager.show_SettingBox(index - 1) - logger.success(f"脚本实例 {name} 左移成功") + logger.success(f"脚本实例 {name} 左移成功", module="脚本管理") MainInfoBar.push_info_bar("success", "操作成功", f"左移脚本实例 {name}", 3000) - def right_setting_box(self): + def right_member(self): """向右移动脚本实例""" name = self.member_manager.pivot.currentRouteKey() if name is None: - logger.warning("向右移动脚本实例时未选择脚本实例") + logger.warning("向右移动脚本实例时未选择脚本实例", module="脚本管理") MainInfoBar.push_info_bar( "warning", "未选择脚本实例", "请选择一个脚本实例", 5000 ) @@ -335,21 +340,24 @@ class MemberManager(QWidget): index = int(name[3:]) if index == len(Config.member_dict): - logger.warning("向右移动脚本实例时已到达最右端") + logger.warning("向右移动脚本实例时已到达最右端", module="脚本管理") MainInfoBar.push_info_bar( "warning", "已经是最后一个脚本实例", "无法向右移动", 5000 ) return None if len(Config.running_list) > 0: - logger.warning("向右移动脚本实例时调度队列未停止运行") + logger.warning("向右移动脚本实例时调度队列未停止运行", module="脚本管理") MainInfoBar.push_info_bar( "warning", "调度中心正在执行任务", "请等待或手动中止任务", 5000 ) return None + logger.info(f"正在向右移动脚本实例: {name}", module="脚本管理") + self.member_manager.clear_SettingBox() + # 移动脚本实例配置文件并同步修改配置项 Config.member_dict[name]["Path"].rename( Config.member_dict[name]["Path"].with_name("脚本_0") ) @@ -365,7 +373,7 @@ class MemberManager(QWidget): self.member_manager.show_SettingBox(index + 1) - logger.success(f"脚本实例 {name} 右移成功") + logger.success(f"脚本实例 {name} 右移成功", module="脚本管理") MainInfoBar.push_info_bar("success", "操作成功", f"右移脚本实例 {name}", 3000) def member_downloader(self): @@ -373,7 +381,7 @@ class MemberManager(QWidget): if not Config.get(Config.update_MirrorChyanCDK): - logger.warning("脚本下载器未设置CDK") + logger.warning("脚本下载器未设置CDK", module="脚本管理") MainInfoBar.push_info_bar( "warning", "未设置Mirror酱CDK", @@ -392,7 +400,10 @@ class MemberManager(QWidget): if network_result["status_code"] == 200: apps_info = network_result["response_json"] else: - logger.warning(f"获取应用列表时出错:{network_result['error_message']}") + logger.warning( + f"获取应用列表时出错:{network_result['error_message']}", + module="脚本管理", + ) MainInfoBar.push_info_bar( "warning", "获取应用列表时出错", @@ -419,7 +430,9 @@ class MemberManager(QWidget): str(Config.app_path / f"script/{app_rid}"), ) if not folder: - logger.warning(f"选择{app_name}下载目录时未选择文件夹") + logger.warning( + f"选择{app_name}下载目录时未选择文件夹", module="脚本管理" + ) MainInfoBar.push_info_bar( "warning", "警告", f"未选择{app_name}下载目录", 5000 ) @@ -442,7 +455,10 @@ class MemberManager(QWidget): if app_info["code"] != 0: - logger.error(f"获取版本信息时出错:{app_info["msg"]}") + logger.error( + f"获取应用版本信息时出错:{app_info["msg"]}", + module="脚本管理", + ) error_remark_dict = { 1001: "获取版本信息的URL参数不正确", @@ -475,7 +491,10 @@ class MemberManager(QWidget): return None - logger.warning(f"获取版本信息时出错:{network_result['error_message']}") + logger.warning( + f"获取版本信息时出错:{network_result['error_message']}", + module="脚本管理", + ) MainInfoBar.push_info_bar( "warning", "获取版本信息时出错", @@ -484,6 +503,8 @@ class MemberManager(QWidget): ) return None + # 创建下载管理器并开始下载 + logger.info(f"开始下载{app_name},下载目录:{folder}", module="脚本管理") self.downloader = DownloadManager( Path(folder), app_rid, @@ -502,6 +523,7 @@ class MemberManager(QWidget): self.downloader.run() def show_password(self): + """显示或隐藏密码""" if Config.PASSWORD == "": choice = LineEditMessageBox( @@ -529,6 +551,7 @@ class MemberManager(QWidget): def reload_plan_name(self): """刷新计划表名称""" + # 生成计划列表信息 plan_list = [ ["固定"] + [_ for _ in Config.plan_dict.keys()], ["固定"] @@ -541,6 +564,8 @@ class MemberManager(QWidget): for k, v in Config.plan_dict.items() ], ] + + # 刷新所有脚本实例的计划表名称 for member in self.member_manager.script_list: if isinstance(member, MemberManager.MemberSettingBox.MaaSettingBox): @@ -616,7 +641,12 @@ class MemberManager(QWidget): self.show_SettingBox(1) def show_SettingBox(self, index) -> None: - """加载所有子界面""" + """ + 加载所有子界面并切换到指定子界面 + + :param index: 要切换到的子界面索引 + :type index: int + """ Config.search_member() @@ -629,7 +659,14 @@ class MemberManager(QWidget): self.switch_SettingBox(index) def switch_SettingBox(self, index: int, if_chang_pivot: bool = True) -> None: - """切换到指定的子界面""" + """ + 切换到指定的子界面 + + :param index: 要切换到的子界面索引 + :type index: int + :param if_chang_pivot: 是否更改导航栏的当前项 + :type if_chang_pivot: bool + """ if len(Config.member_dict) == 0: return None @@ -666,7 +703,14 @@ class MemberManager(QWidget): self.pivot.clear() def add_SettingBox(self, uid: int, type: Type) -> None: - """添加指定类型设置子界面""" + """ + 添加指定类型设置子界面 + + :param uid: 脚本实例的唯一标识符 + :type uid: int + :param type: 要添加的设置子界面类型 + :type type: Type + """ if type == self.MaaSettingBox: setting_box = self.MaaSettingBox(uid, self) @@ -762,6 +806,7 @@ class MemberManager(QWidget): self.viewLayout.addLayout(Layout) def PathClicked(self): + """选择MAA目录并验证""" folder = QFileDialog.getExistingDirectory( self, @@ -769,7 +814,9 @@ class MemberManager(QWidget): self.config.get(self.config.MaaSet_Path), ) if not folder or self.config.get(self.config.MaaSet_Path) == folder: - logger.warning("选择MAA目录时未选择文件夹或未更改文件夹") + logger.warning( + "选择MAA目录时未选择文件夹或未更改文件夹", module="脚本管理" + ) MainInfoBar.push_info_bar( "warning", "警告", "未选择文件夹或未更改文件夹", 5000 ) @@ -778,7 +825,9 @@ class MemberManager(QWidget): not (Path(folder) / "config/gui.json").exists() or not (Path(folder) / "MAA.exe").exists() ): - logger.warning("选择MAA目录时未找到MAA程序或配置文件") + logger.warning( + "选择MAA目录时未找到MAA程序或配置文件", module="脚本管理" + ) MainInfoBar.push_info_bar( "warning", "警告", "未找到MAA程序或配置文件", 5000 ) @@ -938,6 +987,9 @@ class MemberManager(QWidget): index = len(Config.member_dict[self.name]["UserData"]) + 1 + logger.info(f"正在添加 {self.name} 用户_{index}", module="脚本管理") + + # 初始化用户配置信息 user_config = MaaUserConfig() user_config.load( Config.member_dict[self.name]["Path"] @@ -952,10 +1004,13 @@ class MemberManager(QWidget): "Config": user_config, } + # 添加用户设置面板 self.user_manager.add_userSettingBox(index) self.user_manager.switch_SettingBox(f"用户_{index}") - logger.success(f"{self.name} 用户_{index} 添加成功") + logger.success( + f"{self.name} 用户_{index} 添加成功", module="脚本管理" + ) MainInfoBar.push_info_bar( "success", "操作成功", f"{self.name} 添加 用户_{index}", 3000 ) @@ -967,20 +1022,20 @@ class MemberManager(QWidget): name = self.user_manager.pivot.currentRouteKey() if name is None: - logger.warning("未选择用户") + logger.warning("未选择用户", module="脚本管理") MainInfoBar.push_info_bar( "warning", "未选择用户", "请先选择一个用户", 5000 ) return None if name == "用户仪表盘": - logger.warning("试图删除用户仪表盘") + logger.warning("试图删除用户仪表盘", module="脚本管理") MainInfoBar.push_info_bar( "warning", "未选择用户", "请勿尝试删除用户仪表盘", 5000 ) return None if self.name in Config.running_list: - logger.warning("所属脚本正在运行") + logger.warning("所属脚本正在运行", module="脚本管理") MainInfoBar.push_info_bar( "warning", "所属脚本正在运行", "请先停止任务", 5000 ) @@ -991,8 +1046,11 @@ class MemberManager(QWidget): ) if choice.exec(): + logger.info(f"正在删除 {self.name} {name}", module="脚本管理") + self.user_manager.clear_SettingBox() + # 删除用户配置文件并同步修改相应配置项 shutil.rmtree( Config.member_dict[self.name]["UserData"][name]["Path"] ) @@ -1015,7 +1073,9 @@ class MemberManager(QWidget): f"用户_{max(int(name[3:]) - 1, 1)}" ) - logger.success(f"{self.name} {name} 删除成功") + logger.success( + f"{self.name} {name} 删除成功", module="脚本管理" + ) MainInfoBar.push_info_bar( "success", "操作成功", f"{self.name} 删除 {name}", 3000 ) @@ -1027,13 +1087,13 @@ class MemberManager(QWidget): name = self.user_manager.pivot.currentRouteKey() if name is None: - logger.warning("未选择用户") + logger.warning("未选择用户", module="脚本管理") MainInfoBar.push_info_bar( "warning", "未选择用户", "请先选择一个用户", 5000 ) return None if name == "用户仪表盘": - logger.warning("试图移动用户仪表盘") + logger.warning("试图移动用户仪表盘", module="脚本管理") MainInfoBar.push_info_bar( "warning", "未选择用户", "请勿尝试移动用户仪表盘", 5000 ) @@ -1042,21 +1102,24 @@ class MemberManager(QWidget): index = int(name[3:]) if index == 1: - logger.warning("向前移动用户时已到达最左端") + logger.warning("向前移动用户时已到达最左端", module="脚本管理") MainInfoBar.push_info_bar( "warning", "已经是第一个用户", "无法向前移动", 5000 ) return None if self.name in Config.running_list: - logger.warning("所属脚本正在运行") + logger.warning("所属脚本正在运行", module="脚本管理") MainInfoBar.push_info_bar( "warning", "所属脚本正在运行", "请先停止任务", 5000 ) return None + logger.info(f"正在向前移动 {self.name} {name}", module="脚本管理") + self.user_manager.clear_SettingBox() + # 移动用户配置文件并同步修改配置项 Config.member_dict[self.name]["UserData"][name]["Path"].rename( Config.member_dict[self.name]["UserData"][name][ "Path" @@ -1075,7 +1138,7 @@ class MemberManager(QWidget): self.user_manager.show_SettingBox(f"用户_{index - 1}") - logger.success(f"{self.name} {name} 前移成功") + logger.success(f"{self.name} {name} 前移成功", module="脚本管理") MainInfoBar.push_info_bar( "success", "操作成功", f"{self.name} 前移 {name}", 3000 ) @@ -1086,13 +1149,13 @@ class MemberManager(QWidget): name = self.user_manager.pivot.currentRouteKey() if name is None: - logger.warning("未选择用户") + logger.warning("未选择用户", module="脚本管理") MainInfoBar.push_info_bar( "warning", "未选择用户", "请先选择一个用户", 5000 ) return None if name == "用户仪表盘": - logger.warning("试图删除用户仪表盘") + logger.warning("试图删除用户仪表盘", module="脚本管理") MainInfoBar.push_info_bar( "warning", "未选择用户", "请勿尝试移动用户仪表盘", 5000 ) @@ -1101,19 +1164,21 @@ class MemberManager(QWidget): index = int(name[3:]) if index == len(Config.member_dict[self.name]["UserData"]): - logger.warning("向后移动用户时已到达最右端") + logger.warning("向后移动用户时已到达最右端", module="脚本管理") MainInfoBar.push_info_bar( "warning", "已经是最后一个用户", "无法向后移动", 5000 ) return None if self.name in Config.running_list: - logger.warning("所属脚本正在运行") + logger.warning("所属脚本正在运行", module="脚本管理") MainInfoBar.push_info_bar( "warning", "所属脚本正在运行", "请先停止任务", 5000 ) return None + logger.info(f"正在向后移动 {self.name} {name}", module="脚本管理") + self.user_manager.clear_SettingBox() Config.member_dict[self.name]["UserData"][name]["Path"].rename( @@ -1134,7 +1199,7 @@ class MemberManager(QWidget): self.user_manager.show_SettingBox(f"用户_{index + 1}") - logger.success(f"{self.name} {name} 后移成功") + logger.success(f"{self.name} {name} 后移成功", module="脚本管理") MainInfoBar.push_info_bar( "success", "操作成功", f"{self.name} 后移 {name}", 3000 ) @@ -1180,7 +1245,12 @@ class MemberManager(QWidget): self.show_SettingBox("用户仪表盘") def show_SettingBox(self, index: str) -> None: - """加载所有子界面""" + """ + 加载所有子界面并切换到指定子界面 + + :param index: 要切换到的子界面索引或名称 + :type index: str + """ Config.search_maa_user(self.name) @@ -1192,7 +1262,14 @@ class MemberManager(QWidget): def switch_SettingBox( self, index: str, if_change_pivot: bool = True ) -> None: - """切换到指定的子界面""" + """ + 切换到指定的子界面 + + :param index: 要切换到的子界面索引或名称 + :type index: str + :param if_change_pivot: 是否更改导航栏的当前项 + :type if_change_pivot: bool + """ if len(Config.member_dict[self.name]["UserData"]) == 0: index = "用户仪表盘" @@ -1214,7 +1291,7 @@ class MemberManager(QWidget): ) def clear_SettingBox(self) -> None: - """清空所有子界面""" + """清空除用户仪表盘外所有子界面""" for sub_interface in self.script_list: Config.stage_refreshed.disconnect( @@ -1232,7 +1309,12 @@ class MemberManager(QWidget): self.pivot.addItem(routeKey="用户仪表盘", text="用户仪表盘") def add_userSettingBox(self, uid: int) -> None: - """添加一个用户设置界面""" + """ + 添加一个用户设置界面 + + :param uid: 用户的唯一标识符 + :type uid: int + """ setting_box = self.UserMemberSettingBox(self.name, uid, self) @@ -1292,6 +1374,12 @@ class MemberManager(QWidget): Config.PASSWORD_refreshed.connect(self.load_info) def load_info(self): + """加载用户信息到仪表盘""" + + logger.info( + f"正在加载 {self.name} 用户信息到仪表盘", + module="脚本管理", + ) self.user_data = Config.member_dict[self.name]["UserData"] @@ -1452,6 +1540,10 @@ class MemberManager(QWidget): int(name[3:]) - 1, 11, button ) + logger.success( + f"{self.name} 用户仪表盘成功加载信息", module="脚本管理" + ) + class UserMemberSettingBox(HeaderCardWidget): """用户管理子页面""" @@ -1915,6 +2007,7 @@ class MemberManager(QWidget): self.switch_infrastructure() def switch_mode(self) -> None: + """切换用户配置模式""" if self.config.get(self.config.Info_Mode) == "简洁": @@ -1931,6 +2024,7 @@ class MemberManager(QWidget): self.card_Routine.setVisible(True) def switch_stage_mode(self) -> None: + """切换关卡配置模式""" for card, name in zip( [ @@ -1967,6 +2061,7 @@ class MemberManager(QWidget): ) def switch_infrastructure(self) -> None: + """切换基建配置模式""" if ( self.config.get(self.config.Info_InfrastMode) @@ -1988,6 +2083,7 @@ class MemberManager(QWidget): ) def refresh_stage(self): + """刷新关卡配置""" self.card_Stage.reLoadOptions( Config.stage_dict["ALL"]["value"], @@ -2011,6 +2107,7 @@ class MemberManager(QWidget): ) def refresh_password(self): + """刷新密码配置""" self.card_Password.setValue( self.card_Password.qconfig.get( @@ -2054,7 +2151,7 @@ class MemberManager(QWidget): """配置MAA子配置""" if self.name in Config.running_list: - logger.warning("所属脚本正在运行") + logger.warning("所属脚本正在运行", module="脚本管理") MainInfoBar.push_info_bar( "warning", "所属脚本正在运行", "请先停止任务", 5000 ) @@ -2463,7 +2560,12 @@ class MemberManager(QWidget): self.addGroupWidget(widget) def change_path(self, old_path: Path, new_path: Path) -> None: - """根据脚本根目录重新计算配置文件路径""" + """ + 根据脚本根目录重新计算配置文件路径 + + :param old_path: 旧路径 + :param new_path: 新路径 + """ path_list = [ self.config.Script_ScriptPath, @@ -2491,7 +2593,8 @@ class MemberManager(QWidget): self.config.set(configItem, str(old_path)) logger.warning( - f"配置路径 {new_path} 不在脚本根目录下,已重置为 {old_path}" + f"配置路径 {new_path} 不在脚本根目录下,已重置为 {old_path}", + module="脚本管理", ) MainInfoBar.push_info_bar( "warning", "路径异常", "所选路径不在脚本根目录下", 5000 @@ -2738,6 +2841,11 @@ class MemberManager(QWidget): index = len(Config.member_dict[self.name]["SubData"]) + 1 + logger.info( + f"正在添加 {self.name} 的配置_{index}", module="脚本管理" + ) + + # 初始化通用配置 sub_config = GeneralSubConfig() sub_config.load( Config.member_dict[self.name]["Path"] @@ -2752,10 +2860,13 @@ class MemberManager(QWidget): "Config": sub_config, } + # 添加通用配置页面 self.sub_manager.add_SettingBox(index) self.sub_manager.switch_SettingBox(f"配置_{index}") - logger.success(f"{self.name} 配置_{index} 添加成功") + logger.success( + f"{self.name} 配置_{index} 添加成功", module="脚本管理" + ) MainInfoBar.push_info_bar( "success", "操作成功", f"{self.name} 添加 配置_{index}", 3000 ) @@ -2767,20 +2878,20 @@ class MemberManager(QWidget): name = self.sub_manager.pivot.currentRouteKey() if name is None: - logger.warning("未选择配置") + logger.warning("未选择配置", module="脚本管理") MainInfoBar.push_info_bar( "warning", "未选择配置", "请先选择一个配置", 5000 ) return None if name == "配置仪表盘": - logger.warning("试图删除配置仪表盘") + logger.warning("试图删除配置仪表盘", module="脚本管理") MainInfoBar.push_info_bar( "warning", "未选择配置", "请勿尝试删除配置仪表盘", 5000 ) return None if self.name in Config.running_list: - logger.warning("所属脚本正在运行") + logger.warning("所属脚本正在运行", module="脚本管理") MainInfoBar.push_info_bar( "warning", "所属脚本正在运行", "请先停止任务", 5000 ) @@ -2791,8 +2902,13 @@ class MemberManager(QWidget): ) if choice.exec(): + logger.info( + f"正在删除 {self.name} 的配置_{name}", module="脚本管理" + ) + self.sub_manager.clear_SettingBox() + # 删除配置文件并同步到相关配置项 shutil.rmtree( Config.member_dict[self.name]["SubData"][name]["Path"] ) @@ -2815,7 +2931,9 @@ class MemberManager(QWidget): f"配置_{max(int(name[3:]) - 1, 1)}" ) - logger.success(f"{self.name} {name} 删除成功") + logger.success( + f"{self.name} {name} 删除成功", module="脚本管理" + ) MainInfoBar.push_info_bar( "success", "操作成功", f"{self.name} 删除 {name}", 3000 ) @@ -2827,13 +2945,13 @@ class MemberManager(QWidget): name = self.sub_manager.pivot.currentRouteKey() if name is None: - logger.warning("未选择配置") + logger.warning("未选择配置", module="脚本管理") MainInfoBar.push_info_bar( "warning", "未选择配置", "请先选择一个配置", 5000 ) return None if name == "配置仪表盘": - logger.warning("试图移动配置仪表盘") + logger.warning("试图移动配置仪表盘", module="脚本管理") MainInfoBar.push_info_bar( "warning", "未选择配置", "请勿尝试移动配置仪表盘", 5000 ) @@ -2842,21 +2960,26 @@ class MemberManager(QWidget): index = int(name[3:]) if index == 1: - logger.warning("向前移动配置时已到达最左端") + logger.warning("向前移动配置时已到达最左端", module="脚本管理") MainInfoBar.push_info_bar( "warning", "已经是第一个配置", "无法向前移动", 5000 ) return None if self.name in Config.running_list: - logger.warning("所属脚本正在运行") + logger.warning("所属脚本正在运行", module="脚本管理") MainInfoBar.push_info_bar( "warning", "所属脚本正在运行", "请先停止任务", 5000 ) return None + logger.info( + f"正在将 {self.name} 的配置_{name} 前移", module="脚本管理" + ) + self.sub_manager.clear_SettingBox() + # 移动配置文件并同步到相关配置项 Config.member_dict[self.name]["SubData"][name]["Path"].rename( Config.member_dict[self.name]["SubData"][name][ "Path" @@ -2875,7 +2998,7 @@ class MemberManager(QWidget): self.sub_manager.show_SettingBox(f"配置_{index - 1}") - logger.success(f"{self.name} {name} 前移成功") + logger.success(f"{self.name} {name} 前移成功", module="脚本管理") MainInfoBar.push_info_bar( "success", "操作成功", f"{self.name} 前移 {name}", 3000 ) @@ -2886,13 +3009,13 @@ class MemberManager(QWidget): name = self.sub_manager.pivot.currentRouteKey() if name is None: - logger.warning("未选择配置") + logger.warning("未选择配置", module="脚本管理") MainInfoBar.push_info_bar( "warning", "未选择配置", "请先选择一个配置", 5000 ) return None if name == "配置仪表盘": - logger.warning("试图删除配置仪表盘") + logger.warning("试图删除配置仪表盘", module="脚本管理") MainInfoBar.push_info_bar( "warning", "未选择配置", "请勿尝试移动配置仪表盘", 5000 ) @@ -2901,21 +3024,26 @@ class MemberManager(QWidget): index = int(name[3:]) if index == len(Config.member_dict[self.name]["SubData"]): - logger.warning("向后移动配置时已到达最右端") + logger.warning("向后移动配置时已到达最右端", module="脚本管理") MainInfoBar.push_info_bar( "warning", "已经是最后一个配置", "无法向后移动", 5000 ) return None if self.name in Config.running_list: - logger.warning("所属脚本正在运行") + logger.warning("所属脚本正在运行", module="脚本管理") MainInfoBar.push_info_bar( "warning", "所属脚本正在运行", "请先停止任务", 5000 ) return None + logger.info( + f"正在将 {self.name} 的配置_{name} 后移", module="脚本管理" + ) + self.sub_manager.clear_SettingBox() + # 移动配置文件并同步到相关配置项 Config.member_dict[self.name]["SubData"][name]["Path"].rename( Config.member_dict[self.name]["SubData"][name][ "Path" @@ -2934,7 +3062,7 @@ class MemberManager(QWidget): self.sub_manager.show_SettingBox(f"配置_{index + 1}") - logger.success(f"{self.name} {name} 后移成功") + logger.success(f"{self.name} {name} 后移成功", module="脚本管理") MainInfoBar.push_info_bar( "success", "操作成功", f"{self.name} 后移 {name}", 3000 ) @@ -2980,7 +3108,11 @@ class MemberManager(QWidget): self.show_SettingBox("配置仪表盘") def show_SettingBox(self, index: str) -> None: - """加载所有子界面""" + """ + 加载所有子界面 + + :param index: 要显示的子界面索引 + """ Config.search_general_sub(self.name) @@ -2992,7 +3124,12 @@ class MemberManager(QWidget): def switch_SettingBox( self, index: str, if_change_pivot: bool = True ) -> None: - """切换到指定的子界面""" + """ + 切换到指定的子界面 + + :param index: 要切换到的子界面索引 + :param if_change_pivot: 是否更改 pivot 的当前项 + """ if len(Config.member_dict[self.name]["SubData"]) == 0: index = "配置仪表盘" @@ -3026,7 +3163,11 @@ class MemberManager(QWidget): self.pivot.addItem(routeKey="配置仪表盘", text="配置仪表盘") def add_SettingBox(self, uid: int) -> None: - """添加一个配置设置界面""" + """ + 添加一个配置设置界面 + + :param uid: 配置的唯一标识符 + """ setting_box = self.SubMemberSettingBox(self.name, uid, self) @@ -3073,6 +3214,12 @@ class MemberManager(QWidget): Config.PASSWORD_refreshed.connect(self.load_info) def load_info(self): + """加载配置仪表盘信息""" + + logger.info( + f"正在加载 {self.name} 的配置仪表盘信息", + module="脚本管理", + ) self.sub_data = Config.member_dict[self.name]["SubData"] @@ -3127,6 +3274,10 @@ class MemberManager(QWidget): int(name[3:]) - 1, 4, button ) + logger.success( + f"{self.name} 配置仪表盘信息加载成功", module="脚本管理" + ) + class SubMemberSettingBox(HeaderCardWidget): """配置管理子页面""" @@ -3302,7 +3453,7 @@ class MemberManager(QWidget): """配置子配置""" if self.name in Config.running_list: - logger.warning("所属脚本正在运行") + logger.warning("所属脚本正在运行", module="脚本管理") MainInfoBar.push_info_bar( "warning", "所属脚本正在运行", "请先停止任务", 5000 ) diff --git a/app/ui/plan_manager.py b/app/ui/plan_manager.py index 5c6c84d..3ee8cc0 100644 --- a/app/ui/plan_manager.py +++ b/app/ui/plan_manager.py @@ -25,7 +25,6 @@ v4.4 作者:DLmaster_361 """ -from loguru import logger from PySide6.QtWidgets import ( QWidget, QVBoxLayout, @@ -43,7 +42,7 @@ from qfluentwidgets import ( from typing import List, Dict, Union import shutil -from app.core import Config, MainInfoBar, MaaPlanConfig, SoundPlayer +from app.core import Config, MainInfoBar, MaaPlanConfig, SoundPlayer, logger from .Widget import ( ComboBoxMessageBox, LineEditSettingCard, @@ -66,27 +65,20 @@ class PlanManager(QWidget): layout = QVBoxLayout(self) self.tools = CommandBar() - self.plan_manager = self.PlanSettingBox(self) # 逐个添加动作 self.tools.addActions( [ - Action(FluentIcon.ADD_TO, "新建计划表", triggered=self.add_setting_box), - Action( - FluentIcon.REMOVE_FROM, "删除计划表", triggered=self.del_setting_box - ), + Action(FluentIcon.ADD_TO, "新建计划表", triggered=self.add_plan), + Action(FluentIcon.REMOVE_FROM, "删除计划表", triggered=self.del_plan), ] ) self.tools.addSeparator() self.tools.addActions( [ - Action( - FluentIcon.LEFT_ARROW, "向左移动", triggered=self.left_setting_box - ), - Action( - FluentIcon.RIGHT_ARROW, "向右移动", triggered=self.right_setting_box - ), + Action(FluentIcon.LEFT_ARROW, "向左移动", triggered=self.left_plan), + Action(FluentIcon.RIGHT_ARROW, "向右移动", triggered=self.right_plan), ] ) self.tools.addSeparator() @@ -94,7 +86,7 @@ class PlanManager(QWidget): layout.addWidget(self.tools) layout.addWidget(self.plan_manager) - def add_setting_box(self): + def add_plan(self): """添加一个计划表""" choice = ComboBoxMessageBox( @@ -109,6 +101,7 @@ class PlanManager(QWidget): index = len(Config.plan_dict) + 1 + # 初始化 MaaPlanConfig maa_plan_config = MaaPlanConfig() maa_plan_config.load( Config.app_path / f"config/MaaPlanConfig/计划_{index}/config.json", @@ -122,29 +115,30 @@ class PlanManager(QWidget): "Config": maa_plan_config, } + # 添加计划表到界面 self.plan_manager.add_MaaPlanSettingBox(index) self.plan_manager.switch_SettingBox(index) - logger.success(f"计划管理 计划_{index} 添加成功") + logger.success(f"计划管理 计划_{index} 添加成功", module="计划管理") MainInfoBar.push_info_bar( "success", "操作成功", f"添加计划表 计划_{index}", 3000 ) SoundPlayer.play("添加计划表") - def del_setting_box(self): + def del_plan(self): """删除一个计划表""" name = self.plan_manager.pivot.currentRouteKey() if name is None: - logger.warning("删除计划表时未选择计划表") + logger.warning("删除计划表时未选择计划表", module="计划管理") MainInfoBar.push_info_bar( "warning", "未选择计划表", "请选择一个计划表", 5000 ) return None if len(Config.running_list) > 0: - logger.warning("删除计划表时调度队列未停止运行") + logger.warning("删除计划表时调度队列未停止运行", module="计划管理") MainInfoBar.push_info_bar( "warning", "调度中心正在执行任务", "请等待或手动中止任务", 5000 ) @@ -153,8 +147,11 @@ class PlanManager(QWidget): choice = MessageBox("确认", f"确定要删除 {name} 吗?", self.window()) if choice.exec(): + logger.info(f"正在删除计划表 {name}", module="计划管理") + self.plan_manager.clear_SettingBox() + # 删除计划表配置文件并同步到相关配置项 shutil.rmtree(Config.plan_dict[name]["Path"]) Config.change_plan(name, "固定") for i in range(int(name[3:]) + 1, len(Config.plan_dict) + 1): @@ -166,17 +163,17 @@ class PlanManager(QWidget): self.plan_manager.show_SettingBox(max(int(name[3:]) - 1, 1)) - logger.success(f"计划表 {name} 删除成功") + logger.success(f"计划表 {name} 删除成功", module="计划管理") MainInfoBar.push_info_bar("success", "操作成功", f"删除计划表 {name}", 3000) SoundPlayer.play("删除计划表") - def left_setting_box(self): + def left_plan(self): """向左移动计划表""" name = self.plan_manager.pivot.currentRouteKey() if name is None: - logger.warning("向左移动计划表时未选择计划表") + logger.warning("向左移动计划表时未选择计划表", module="计划管理") MainInfoBar.push_info_bar( "warning", "未选择计划表", "请选择一个计划表", 5000 ) @@ -185,21 +182,24 @@ class PlanManager(QWidget): index = int(name[3:]) if index == 1: - logger.warning("向左移动计划表时已到达最左端") + logger.warning("向左移动计划表时已到达最左端", module="计划管理") MainInfoBar.push_info_bar( "warning", "已经是第一个计划表", "无法向左移动", 5000 ) return None if len(Config.running_list) > 0: - logger.warning("向左移动计划表时调度队列未停止运行") + logger.warning("向左移动计划表时调度队列未停止运行", module="计划管理") MainInfoBar.push_info_bar( "warning", "调度中心正在执行任务", "请等待或手动中止任务", 5000 ) return None + logger.info(f"正在左移计划表 {name}", module="计划管理") + self.plan_manager.clear_SettingBox() + # 移动配置文件并同步到相关配置项 Config.plan_dict[name]["Path"].rename( Config.plan_dict[name]["Path"].with_name("计划_0") ) @@ -215,16 +215,16 @@ class PlanManager(QWidget): self.plan_manager.show_SettingBox(index - 1) - logger.success(f"计划表 {name} 左移成功") + logger.success(f"计划表 {name} 左移成功", module="计划管理") MainInfoBar.push_info_bar("success", "操作成功", f"左移计划表 {name}", 3000) - def right_setting_box(self): + def right_plan(self): """向右移动计划表""" name = self.plan_manager.pivot.currentRouteKey() if name is None: - logger.warning("向右移动计划表时未选择计划表") + logger.warning("向右移动计划表时未选择计划表", module="计划管理") MainInfoBar.push_info_bar( "warning", "未选择计划表", "请选择一个计划表", 5000 ) @@ -233,21 +233,24 @@ class PlanManager(QWidget): index = int(name[3:]) if index == len(Config.plan_dict): - logger.warning("向右移动计划表时已到达最右端") + logger.warning("向右移动计划表时已到达最右端", module="计划管理") MainInfoBar.push_info_bar( "warning", "已经是最后一个计划表", "无法向右移动", 5000 ) return None if len(Config.running_list) > 0: - logger.warning("向右移动计划表时调度队列未停止运行") + logger.warning("向右移动计划表时调度队列未停止运行", module="计划管理") MainInfoBar.push_info_bar( "warning", "调度中心正在执行任务", "请等待或手动中止任务", 5000 ) return None + logger.info(f"正在右移计划表 {name}", module="计划管理") + self.plan_manager.clear_SettingBox() + # 移动配置文件并同步到相关配置项 Config.plan_dict[name]["Path"].rename( Config.plan_dict[name]["Path"].with_name("计划_0") ) @@ -263,7 +266,7 @@ class PlanManager(QWidget): self.plan_manager.show_SettingBox(index + 1) - logger.success(f"计划表 {name} 右移成功") + logger.success(f"计划表 {name} 右移成功", module="计划管理") MainInfoBar.push_info_bar("success", "操作成功", f"右移计划表 {name}", 3000) class PlanSettingBox(QWidget): @@ -297,7 +300,11 @@ class PlanManager(QWidget): self.show_SettingBox(1) def show_SettingBox(self, index) -> None: - """加载所有子界面""" + """ + 加载所有子界面并切换到指定的子界面 + + :param index: 要显示的子界面索引 + """ Config.search_plan() @@ -308,7 +315,12 @@ class PlanManager(QWidget): self.switch_SettingBox(index) def switch_SettingBox(self, index: int, if_chang_pivot: bool = True) -> None: - """切换到指定的子界面""" + """ + 切换到指定的子界面 + + :param index: 要切换到的子界面索引 + :param if_chang_pivot: 是否更改 pivot 的当前项 + """ if len(Config.plan_dict) == 0: return None @@ -331,7 +343,11 @@ class PlanManager(QWidget): self.pivot.clear() def add_MaaPlanSettingBox(self, uid: int) -> None: - """添加一个MAA设置界面""" + """ + 添加一个MAA设置界面 + + :param uid: MAA计划表的唯一标识符 + """ maa_plan_setting_box = self.MaaPlanSettingBox(uid, self) @@ -475,6 +491,7 @@ class PlanManager(QWidget): ) def refresh_stage(self): + """刷新关卡列表""" for group, name_dict in self.item_dict.items(): diff --git a/app/ui/queue_manager.py b/app/ui/queue_manager.py index 303edda..a022abd 100644 --- a/app/ui/queue_manager.py +++ b/app/ui/queue_manager.py @@ -25,7 +25,6 @@ v4.4 作者:DLmaster_361 """ -from loguru import logger from PySide6.QtWidgets import ( QWidget, QVBoxLayout, @@ -42,7 +41,7 @@ from qfluentwidgets import ( ) from typing import List -from app.core import QueueConfig, Config, MainInfoBar, SoundPlayer +from app.core import QueueConfig, Config, MainInfoBar, SoundPlayer, logger from .Widget import ( SwitchSettingCard, ComboBoxSettingCard, @@ -70,36 +69,31 @@ class QueueManager(QWidget): # 逐个添加动作 self.tools.addActions( [ + Action(FluentIcon.ADD_TO, "新建调度队列", triggered=self.add_queue), Action( - FluentIcon.ADD_TO, "新建调度队列", triggered=self.add_setting_box - ), - Action( - FluentIcon.REMOVE_FROM, - "删除调度队列", - triggered=self.del_setting_box, + FluentIcon.REMOVE_FROM, "删除调度队列", triggered=self.del_queue ), ] ) self.tools.addSeparator() self.tools.addActions( [ - Action( - FluentIcon.LEFT_ARROW, "向左移动", triggered=self.left_setting_box - ), - Action( - FluentIcon.RIGHT_ARROW, "向右移动", triggered=self.right_setting_box - ), + Action(FluentIcon.LEFT_ARROW, "向左移动", triggered=self.left_queue), + Action(FluentIcon.RIGHT_ARROW, "向右移动", triggered=self.right_queue), ] ) layout.addWidget(self.tools) layout.addWidget(self.queue_manager) - def add_setting_box(self): + def add_queue(self): """添加一个调度队列""" index = len(Config.queue_dict) + 1 + logger.info(f"正在添加调度队列_{index}", module="队列管理") + + # 初始化队列配置 queue_config = QueueConfig() queue_config.load( Config.app_path / f"config/QueueConfig/调度队列_{index}.json", queue_config @@ -111,27 +105,28 @@ class QueueManager(QWidget): "Config": queue_config, } + # 添加到配置界面 self.queue_manager.add_SettingBox(index) self.queue_manager.switch_SettingBox(index) - logger.success(f"调度队列_{index} 添加成功") + logger.success(f"调度队列_{index} 添加成功", module="队列管理") MainInfoBar.push_info_bar("success", "操作成功", f"添加 调度队列_{index}", 3000) SoundPlayer.play("添加调度队列") - def del_setting_box(self): + def del_queue(self): """删除一个调度队列实例""" name = self.queue_manager.pivot.currentRouteKey() if name is None: - logger.warning("未选择调度队列") + logger.warning("未选择调度队列", module="队列管理") MainInfoBar.push_info_bar( "warning", "未选择调度队列", "请先选择一个调度队列", 5000 ) return None if name in Config.running_list: - logger.warning("调度队列正在运行") + logger.warning("调度队列正在运行", module="队列管理") MainInfoBar.push_info_bar( "warning", "调度队列正在运行", "请先停止调度队列", 5000 ) @@ -140,8 +135,11 @@ class QueueManager(QWidget): choice = MessageBox("确认", f"确定要删除 {name} 吗?", self.window()) if choice.exec(): + logger.info(f"正在删除调度队列 {name}", module="队列管理") + self.queue_manager.clear_SettingBox() + # 删除队列配置文件并同步到相关配置项 Config.queue_dict[name]["Path"].unlink() for i in range(int(name[5:]) + 1, len(Config.queue_dict) + 1): if Config.queue_dict[f"调度队列_{i}"]["Path"].exists(): @@ -153,17 +151,17 @@ class QueueManager(QWidget): self.queue_manager.show_SettingBox(max(int(name[5:]) - 1, 1)) - logger.success(f"{name} 删除成功") + logger.success(f"{name} 删除成功", module="队列管理") MainInfoBar.push_info_bar("success", "操作成功", f"删除 {name}", 3000) SoundPlayer.play("删除调度队列") - def left_setting_box(self): + def left_queue(self): """向左移动调度队列实例""" name = self.queue_manager.pivot.currentRouteKey() if name is None: - logger.warning("未选择调度队列") + logger.warning("未选择调度队列", module="队列管理") MainInfoBar.push_info_bar( "warning", "未选择调度队列", "请先选择一个调度队列", 5000 ) @@ -172,21 +170,24 @@ class QueueManager(QWidget): index = int(name[5:]) if index == 1: - logger.warning("向左移动调度队列时已到达最左端") + logger.warning("向左移动调度队列时已到达最左端", module="队列管理") MainInfoBar.push_info_bar( "warning", "已经是第一个调度队列", "无法向左移动", 5000 ) return None if name in Config.running_list or f"调度队列_{index-1}" in Config.running_list: - logger.warning("相关调度队列正在运行") + logger.warning("相关调度队列正在运行", module="队列管理") MainInfoBar.push_info_bar( "warning", "相关调度队列正在运行", "请先停止调度队列", 5000 ) return None + logger.info(f"正在左移调度队列 {name}", module="队列管理") + self.queue_manager.clear_SettingBox() + # 移动配置文件并同步到相关配置项 Config.queue_dict[name]["Path"].rename( Config.queue_dict[name]["Path"].with_name("调度队列_0.json") ) @@ -199,16 +200,16 @@ class QueueManager(QWidget): self.queue_manager.show_SettingBox(index - 1) - logger.success(f"{name} 左移成功") + logger.success(f"{name} 左移成功", module="队列管理") MainInfoBar.push_info_bar("success", "操作成功", f"左移 {name}", 3000) - def right_setting_box(self): + def right_queue(self): """向右移动调度队列实例""" name = self.queue_manager.pivot.currentRouteKey() if name is None: - logger.warning("未选择调度队列") + logger.warning("未选择调度队列", module="队列管理") MainInfoBar.push_info_bar( "warning", "未选择调度队列", "请先选择一个调度队列", 5000 ) @@ -217,21 +218,24 @@ class QueueManager(QWidget): index = int(name[5:]) if index == len(Config.queue_dict): - logger.warning("向右移动调度队列时已到达最右端") + logger.warning("向右移动调度队列时已到达最右端", module="队列管理") MainInfoBar.push_info_bar( "warning", "已经是最后一个调度队列", "无法向右移动", 5000 ) return None if name in Config.running_list or f"调度队列_{index+1}" in Config.running_list: - logger.warning("相关调度队列正在运行") + logger.warning("相关调度队列正在运行", module="队列管理") MainInfoBar.push_info_bar( "warning", "相关调度队列正在运行", "请先停止调度队列", 5000 ) return None + logger.info(f"正在右移调度队列 {name}", module="队列管理") + self.queue_manager.clear_SettingBox() + # 移动配置文件并同步到相关配置项 Config.queue_dict[name]["Path"].rename( Config.queue_dict[name]["Path"].with_name("调度队列_0.json") ) @@ -244,12 +248,13 @@ class QueueManager(QWidget): self.queue_manager.show_SettingBox(index + 1) - logger.success(f"{name} 右移成功") + logger.success(f"{name} 右移成功", module="队列管理") MainInfoBar.push_info_bar("success", "操作成功", f"右移 {name}", 3000) def reload_member_name(self): """刷新调度队列成员""" + # 获取成员列表 member_list = [ ["禁用"] + [_ for _ in Config.member_dict.keys()], ["未启用"] @@ -337,7 +342,12 @@ class QueueManager(QWidget): self.switch_SettingBox(index) def switch_SettingBox(self, index: int, if_change_pivot: bool = True) -> None: - """切换到指定的子界面""" + """ + 切换到指定的子界面并切换到指定的子页面 + + :param index: 要切换到的子界面索引 + :param if_change_pivot: 是否更改导航栏当前项 + """ if len(Config.queue_dict) == 0: return None diff --git a/app/ui/setting.py b/app/ui/setting.py index 71c35c7..cada615 100644 --- a/app/ui/setting.py +++ b/app/ui/setting.py @@ -25,7 +25,6 @@ v4.4 作者:DLmaster_361 """ -from loguru import logger from PySide6.QtWidgets import QWidget, QVBoxLayout from PySide6.QtGui import QIcon from PySide6.QtCore import Qt @@ -49,7 +48,7 @@ from packaging import version from pathlib import Path from typing import Dict, Union -from app.core import Config, MainInfoBar, Network, SoundPlayer +from app.core import Config, MainInfoBar, Network, SoundPlayer, logger from app.services import Crypto, System, Notify from .downloader import DownloadManager from .Widget import ( @@ -124,7 +123,7 @@ class Setting(QWidget): self.window(), ) if choice.exec(): - logger.success("确认授权bilibili游戏隐私政策") + logger.success("确认授权bilibili游戏隐私政策", module="设置界面") MainInfoBar.push_info_bar( "success", "操作成功", "已确认授权bilibili游戏隐私政策", 3000 ) @@ -132,7 +131,7 @@ class Setting(QWidget): Config.set(Config.function_IfAgreeBilibili, False) else: - logger.info("取消授权bilibili游戏隐私政策") + logger.info("取消授权bilibili游戏隐私政策", module="设置界面") MainInfoBar.push_info_bar( "info", "操作成功", "已取消授权bilibili游戏隐私政策", 3000 ) @@ -158,7 +157,7 @@ class Setting(QWidget): MuMu_splash_ads_path.touch() - logger.success("开启跳过MuMu启动广告功能") + logger.success("开启跳过MuMu启动广告功能", module="设置界面") MainInfoBar.push_info_bar( "success", "操作成功", "已开启跳过MuMu启动广告功能", 3000 ) @@ -170,7 +169,7 @@ class Setting(QWidget): if MuMu_splash_ads_path.exists() and MuMu_splash_ads_path.is_file(): MuMu_splash_ads_path.unlink() - logger.info("关闭跳过MuMu启动广告功能") + logger.info("关闭跳过MuMu启动广告功能", module="设置界面") MainInfoBar.push_info_bar( "info", "操作成功", "已关闭跳过MuMu启动广告功能", 3000 ) @@ -181,6 +180,8 @@ class Setting(QWidget): if Config.key_path.exists(): return None + logger.info("未设置管理密钥,开始要求用户进行设置", module="设置界面") + while True: choice = LineEditMessageBox( @@ -188,6 +189,7 @@ class Setting(QWidget): ) if choice.exec() and choice.input.text() != "": Crypto.get_PASSWORD(choice.input.text()) + logger.success("成功设置管理密钥", module="设置界面") break else: choice = MessageBox( @@ -207,10 +209,7 @@ class Setting(QWidget): while if_change: choice = LineEditMessageBox( - self.window(), - "请输入旧的管理密钥", - "旧管理密钥", - "密码", + self.window(), "请输入旧的管理密钥", "旧管理密钥", "密码" ) if choice.exec() and choice.input.text() != "": @@ -231,6 +230,7 @@ class Setting(QWidget): # 修改管理密钥 Crypto.change_PASSWORD(PASSWORD_old, choice.input.text()) + logger.success("成功修改管理密钥", module="设置界面") MainInfoBar.push_info_bar( "success", "操作成功", "管理密钥修改成功", 3000 ) @@ -291,6 +291,7 @@ class Setting(QWidget): # 重置管理密钥 Crypto.reset_PASSWORD(choice.input.text()) + logger.success("成功重置管理密钥", module="设置界面") MainInfoBar.push_info_bar( "success", "操作成功", "管理密钥重置成功", 3000 ) @@ -316,7 +317,12 @@ class Setting(QWidget): ) def check_update(self, if_show: bool = False, if_first: bool = False) -> None: - """检查版本更新,调起文件下载进程""" + """ + 检查版本更新,调起更新线程 + + :param if_show: 是否显示更新信息 + :param if_first: 是否为启动时检查更新 + """ current_version = list(map(int, Config.VERSION.split("."))) @@ -339,7 +345,9 @@ class Setting(QWidget): if version_info["code"] != 0: - logger.error(f"获取版本信息时出错:{version_info['msg']}") + logger.error( + f"获取版本信息时出错:{version_info['msg']}", module="设置界面" + ) error_remark_dict = { 1001: "获取版本信息的URL参数不正确", @@ -372,7 +380,10 @@ class Setting(QWidget): return None - logger.warning(f"获取版本信息时出错:{network_result['error_message']}") + logger.warning( + f"获取版本信息时出错:{network_result['error_message']}", + module="设置界面", + ) MainInfoBar.push_info_bar( "warning", "获取版本信息时出错", @@ -470,11 +481,12 @@ class Setting(QWidget): download_info = network_result["response_json"] else: logger.warning( - f"获取应用列表时出错:{network_result['error_message']}" + f"获取下载信息时出错:{network_result['error_message']}", + module="设置界面", ) MainInfoBar.push_info_bar( "warning", - "获取应用列表时出错", + "获取下载信息时出错", f"网络错误:{network_result['status_code']}", 5000, ) @@ -492,6 +504,8 @@ class Setting(QWidget): "download_dict": download_info["download_dict"], } + logger.info("开始执行更新任务", module="设置界面") + self.downloader = DownloadManager( Config.app_path, "AUTO_MAA", remote_version, download_config ) @@ -526,6 +540,9 @@ class Setting(QWidget): SoundPlayer.play("无新版本") def start_setup(self) -> None: + """启动安装程序""" + + logger.info("启动安装程序", module="设置界面") subprocess.Popen( [ Config.app_path / "AUTO_MAA-Setup.exe", @@ -555,7 +572,10 @@ class Setting(QWidget): if network_result["status_code"] == 200: notice = network_result["response_json"] else: - logger.warning(f"获取最新公告时出错:{network_result['error_message']}") + logger.warning( + f"获取最新公告时出错:{network_result['error_message']}", + module="设置界面", + ) MainInfoBar.push_info_bar( "warning", "获取最新公告时出错", diff --git a/app/utils/ProcessManager.py b/app/utils/ProcessManager.py index c06d21f..a57406d 100644 --- a/app/utils/ProcessManager.py +++ b/app/utils/ProcessManager.py @@ -48,12 +48,7 @@ class ProcessManager(QObject): self.check_timer = QTimer() self.check_timer.timeout.connect(self.check_processes) - def open_process( - self, - path: Path, - args: list = [], - tracking_time: int = 60, - ) -> int: + def open_process(self, path: Path, args: list = [], tracking_time: int = 60) -> int: """ 启动一个新进程并返回其pid,并开始监视该进程 @@ -89,7 +84,7 @@ class ProcessManager(QObject): # 扫描并记录所有相关进程 try: - # 获取主进程及其子进程 + # 获取主进程 main_proc = psutil.Process(self.main_pid) self.tracked_pids.add(self.main_pid) diff --git a/main.py b/main.py index 85e963b..49bc0f7 100644 --- a/main.py +++ b/main.py @@ -44,10 +44,10 @@ def no_print(*args, **kwargs): builtins.print = no_print -from loguru import logger import os import sys import ctypes +import traceback from PySide6.QtWidgets import QApplication from qfluentwidgets import FluentTranslator @@ -60,28 +60,214 @@ def is_admin() -> bool: return False -@logger.catch +def show_system_error(title: str, message: str, detailed_error: str = None): + """使用系统级消息框显示错误""" + try: + # Windows系统消息框 + if sys.platform == "win32": + # 组合完整的错误信息 + full_message = message + if detailed_error: + # 限制详细错误信息长度,避免消息框过大 + if len(detailed_error) > 2000: + detailed_error = ( + detailed_error[:2000] + "\n\n... (错误信息过长已截断)" + ) + full_message += f"\n\n详细错误信息:\n{detailed_error}" + + # 使用ctypes调用Windows API + ctypes.windll.user32.MessageBoxW( + 0, # 父窗口句柄 + full_message, # 消息内容 + title, # 标题 + 0x10 | 0x0, # MB_ICONERROR | MB_OK + ) + + # Linux系统 - 尝试使用zenity或kdialog + elif sys.platform.startswith("linux"): + full_message = message + if detailed_error: + full_message += f"\n\n详细错误:\n{detailed_error[:1000]}" + + try: + # 尝试zenity (GNOME) + os.system( + f'zenity --error --title="{title}" --text="{full_message}" 2>/dev/null' + ) + except: + try: + # 尝试kdialog (KDE) + os.system( + f'kdialog --error "{full_message}" --title "{title}" 2>/dev/null' + ) + except: + # 降级到控制台输出 + print(f"错误: {title}") + print(f"消息: {message}") + if detailed_error: + print(f"详细信息:\n{detailed_error}") + + # macOS系统 + elif sys.platform == "darwin": + full_message = message + if detailed_error: + full_message += f"\n\n详细错误:\n{detailed_error[:1000]}" + + try: + os.system( + f'osascript -e \'display alert "{title}" message "{full_message}" as critical\'' + ) + except: + print(f"错误: {title}") + print(f"消息: {message}") + if detailed_error: + print(f"详细信息:\n{detailed_error}") + + else: + # 其他系统降级到控制台输出 + print(f"错误: {title}") + print(f"消息: {message}") + if detailed_error: + print(f"详细信息:\n{detailed_error}") + + except Exception as e: + # 如果连系统消息框都失败了,输出到控制台 + print(f"无法显示错误对话框: {e}") + print(f"原始错误: {title} - {message}") + if detailed_error: + print(f"详细错误信息:\n{detailed_error}") + + +def save_error_log(error_info: str): + """保存错误日志到文件""" + try: + import datetime + + timestamp = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + log_dir = os.path.join(os.path.dirname(__file__), "debug") + os.makedirs(log_dir, exist_ok=True) + + log_file = os.path.join(log_dir, f"crash_{timestamp}.log") + with open(log_file, "w", encoding="utf-8") as f: + f.write(f"AUTO_MAA 崩溃日志\n") + f.write(f"时间: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n") + f.write(f"Python版本: {sys.version}\n") + f.write(f"平台: {sys.platform}\n") + f.write(f"工作目录: {os.getcwd()}\n") + f.write("=" * 50 + "\n") + f.write(error_info) + + return log_file + except: + return None + + def main(): + """主程序入口""" + application = None - application = QApplication(sys.argv) + try: + # 创建QApplication + application = QApplication(sys.argv) - translator = FluentTranslator() - application.installTranslator(translator) + # 安装翻译器 + translator = FluentTranslator() + application.installTranslator(translator) - from app.ui.main_window import AUTO_MAA + try: + # 导入主窗口模块 + from app.ui.main_window import AUTO_MAA - window = AUTO_MAA() - window.show_ui("显示主窗口", if_start=True) - window.start_up_task() - sys.exit(application.exec()) + # 创建主窗口 + window = AUTO_MAA() + window.show_ui("显示主窗口", if_start=True) + window.start_up_task() + + except ImportError as e: + error_msg = f"模块导入失败: {str(e)}" + detailed_error = traceback.format_exc() + log_file = save_error_log(f"{error_msg}\n\n{detailed_error}") + + if log_file: + error_msg += f"\n\n错误日志已保存到: {log_file}" + + show_system_error("模块导入错误", error_msg, detailed_error) + return + + except Exception as e: + error_msg = f"主窗口创建失败: {str(e)}" + detailed_error = traceback.format_exc() + log_file = save_error_log(f"{error_msg}\n\n{detailed_error}") + + if log_file: + error_msg += f"\n\n错误日志已保存到: {log_file}" + + show_system_error("窗口创建错误", error_msg, detailed_error) + return + + # 启动事件循环 + sys.exit(application.exec()) + + except Exception as e: + error_msg = f"应用程序启动失败: {str(e)}" + detailed_error = traceback.format_exc() + log_file = save_error_log(f"{error_msg}\n\n{detailed_error}") + + if log_file: + error_msg += f"\n\n错误日志已保存到: {log_file}" + + # 尝试显示错误对话框 + show_system_error("应用程序启动错误", error_msg, detailed_error) + + # 如果有应用程序实例,确保正确退出 + if application: + try: + application.quit() + except: + pass + + sys.exit(1) + + +def handle_exception(exc_type, exc_value, exc_traceback): + """全局异常处理器""" + if issubclass(exc_type, KeyboardInterrupt): + sys.__excepthook__(exc_type, exc_value, exc_traceback) + return + + error_msg = f"未处理的异常: {exc_type.__name__}: {str(exc_value)}" + detailed_error = "".join( + traceback.format_exception(exc_type, exc_value, exc_traceback) + ) + log_file = save_error_log(f"{error_msg}\n\n{detailed_error}") + + if log_file: + error_msg += f"\n\n错误日志已保存到: {log_file}" + + show_system_error("程序异常", error_msg, detailed_error) + + +# 设置全局异常处理器 +sys.excepthook = handle_exception if __name__ == "__main__": - if is_admin(): - main() - else: - ctypes.windll.shell32.ShellExecuteW( - None, "runas", sys.executable, os.path.realpath(sys.argv[0]), None, 1 - ) - sys.exit(0) + try: + if is_admin(): + main() + else: + ctypes.windll.shell32.ShellExecuteW( + None, "runas", sys.executable, os.path.realpath(sys.argv[0]), None, 1 + ) + sys.exit(0) + except Exception as e: + error_msg = f"程序启动失败: {str(e)}" + detailed_error = traceback.format_exc() + log_file = save_error_log(f"{error_msg}\n\n{detailed_error}") + + if log_file: + error_msg += f"\n\n错误日志已保存到: {log_file}" + + show_system_error("启动错误", error_msg, detailed_error) + sys.exit(1) diff --git a/resources/version.json b/resources/version.json index b32de3f..56670bf 100644 --- a/resources/version.json +++ b/resources/version.json @@ -1,60 +1,14 @@ { - "main_version": "4.4.0.0", + "main_version": "4.4.1.1", "version_info": { - "4.4.0.0": { - "新增功能": [ - "通用配置模式接入日志系统" - ], + "4.4.1.1": { "修复BUG": [ - "信任系统证书,并添加网络代理地址配置项 #50", - "适配 MAA 任务及基建设施日志翻译" + "修复MAA掉落物统计功能", + "修复模拟器界面被异常关闭且无法重新打开的问题" ], "程序优化": [ - "重构历史记录保存与载入逻辑" - ] - }, - "4.4.0.5": { - "新增功能": [ - "添加导入导出通用配置功能" - ], - "修复BUG": [ - "修复开机自启相关功能" - ] - }, - "4.4.0.4": { - "新增功能": [ - "添加重置管理密钥功能" - ], - "修复BUG": [ - "修复无计划表时数据系统无法正常升级到v1.7的问题" - ] - }, - "4.4.0.3": { - "修复BUG": [ - "适配 MAA 备选关卡字段修改", - "修复无成功日志时的脚本判定逻辑" - ], - "程序优化": [ - "`GameId`字段改为 `Stage`,与 MAA 保持一致" - ] - }, - "4.4.0.2": { - "新增功能": [ - "进一步适配三月七相关配置项" - ], - "修复BUG": [ - "适配 Mirror 酱 平台下载策略调整" - ] - }, - "4.4.0.1": { - "新增功能": [ - "初步完成通用调度模块" - ], - "修复BUG": [ - "修复了程序BUG较少的BUG" - ], - "程序优化": [ - "子线程卡死不再阻塞调度任务" + "重构日志记录,载入更多日志记录项", + "优化日志监看启停逻辑" ] } }