feat(core): 重构日志记录,载入更多日志记录项

This commit is contained in:
DLmaster361
2025-07-18 18:12:47 +08:00
parent 8427bd9f6b
commit 9b492b5e0d
26 changed files with 2217 additions and 800 deletions

View File

@@ -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",

View File

@@ -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="<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | <level>{level: <8}</level> | <level>{message}</level>",
format="<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | <level>{level: <8}</level> | <cyan>{extra[module]}</cyan> | <level>{message}</level>",
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="<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | <level>{level: <8}</level> | <cyan>{extra[module]}</cyan> | <level>{message}</level>",
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()

34
app/core/logger.py Normal file
View File

@@ -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 <https://www.gnu.org/licenses/>.
# 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)

View File

@@ -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":

View File

@@ -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

View File

@@ -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)

View File

@@ -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("")

View File

@@ -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")

View File

@@ -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"]:

View File

@@ -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"]:

View File

@@ -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

View File

@@ -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) != "管理密钥错误"

View File

@@ -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}

View File

@@ -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"]):

View File

@@ -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)):

View File

@@ -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

View File

@@ -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)

View File

@@ -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))

View File

@@ -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()

View File

@@ -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
)

View File

@@ -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():

View File

@@ -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

View File

@@ -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",
"获取最新公告时出错",

View File

@@ -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)

220
main.py
View File

@@ -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)

View File

@@ -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"
],
"程序优化": [
"子线程卡死不再阻塞调度任务"
"重构日志记录,载入更多日志记录项",
"优化日志监看启停逻辑"
]
}
}