From 1c0a65957dc38d8e1e1ea7f4555e5dbb873dbf4e Mon Sep 17 00:00:00 2001 From: DLmaster361 Date: Thu, 10 Jul 2025 02:29:08 +0800 Subject: [PATCH 01/13] =?UTF-8?q?feat(core):=20=E5=88=9D=E6=AD=A5=E5=AE=8C?= =?UTF-8?q?=E6=88=90=E9=80=9A=E7=94=A8=E8=B0=83=E5=BA=A6=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build-app.yml | 1 + app/core/__init__.py | 12 +- app/core/config.py | 436 ++++----- app/core/task_manager.py | 93 +- app/models/MAA.py | 60 +- app/models/__init__.py | 3 +- app/models/general.py | 905 +++++++++++++++++ app/services/system.py | 96 +- app/ui/Widget.py | 121 ++- app/ui/dispatch_center.py | 48 +- app/ui/main_window.py | 2 +- app/ui/member_manager.py | 1246 +++++++++++++++++++++++- app/ui/queue_manager.py | 18 +- app/utils/ImageUtils.py | 28 + app/utils/ProcessManager.py | 167 ++++ app/utils/__init__.py | 5 +- app/utils/package.py | 2 +- main.py | 20 +- requirements.txt | 24 +- resources/html/general_result.html | 160 +++ resources/html/general_statistics.html | 200 ++++ resources/version.json | 41 +- 22 files changed, 3280 insertions(+), 408 deletions(-) create mode 100644 app/models/general.py create mode 100644 app/utils/ProcessManager.py create mode 100644 resources/html/general_result.html create mode 100644 resources/html/general_statistics.html diff --git a/.github/workflows/build-app.yml b/.github/workflows/build-app.yml index c29f2cb..dfef137 100644 --- a/.github/workflows/build-app.yml +++ b/.github/workflows/build-app.yml @@ -84,6 +84,7 @@ jobs: onefile-tempdir-spec: "{TEMP}/AUTO_MAA" windows-console-mode: attach windows-icon-from-ico: resources/icons/AUTO_MAA.ico + windows-uac-admin: true company-name: AUTO_MAA Team product-name: AUTO_MAA file-version: ${{ steps.get_version.outputs.main_version }} diff --git a/app/core/__init__.py b/app/core/__init__.py index b0e3cc5..c6eab15 100644 --- a/app/core/__init__.py +++ b/app/core/__init__.py @@ -29,7 +29,15 @@ __version__ = "4.2.0" __author__ = "DLmaster361 " __license__ = "GPL-3.0 license" -from .config import QueueConfig, MaaConfig, MaaUserConfig, MaaPlanConfig, Config +from .config import ( + QueueConfig, + MaaConfig, + MaaUserConfig, + MaaPlanConfig, + GeneralConfig, + GeneralSubConfig, + Config, +) from .main_info_bar import MainInfoBar from .network import Network from .sound_player import SoundPlayer @@ -42,6 +50,8 @@ __all__ = [ "MaaConfig", "MaaUserConfig", "MaaPlanConfig", + "GeneralConfig", + "GeneralSubConfig", "MainInfoBar", "Network", "SoundPlayer", diff --git a/app/core/config.py b/app/core/config.py index c2b34ed..62e3056 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -56,6 +56,17 @@ from typing import Union, Dict, List from .network import Network +class FileValidator(ConfigValidator): + """File validator""" + + def validate(self, value): + return Path(value).exists() + + def correct(self, value): + path = Path(value) + return str(path.absolute()).replace("\\", "/") + + class UrlListValidator(ConfigValidator): """Url list validator""" @@ -382,6 +393,9 @@ class MaaConfig(LQConfig): "RunSet", "AutoUpdateMaa", False, BoolValidator() ) + def get_name(self) -> str: + return self.get(self.MaaSet_Name) + class MaaUserConfig(LQConfig): """MAA用户配置""" @@ -574,13 +588,118 @@ class MaaPlanConfig(LQConfig): return self.config_item_dict["ALL"][name] +class GeneralConfig(LQConfig): + """通用配置""" + + def __init__(self) -> None: + super().__init__() + + self.Script_Name = ConfigItem("Script", "Name", "") + self.Script_RootPath = ConfigItem("Script", "RootPath", ".", FolderValidator()) + self.Script_ScriptPath = ConfigItem( + "Script", "ScriptPath", ".", FileValidator() + ) + self.Script_Arguments = ConfigItem("Script", "Arguments", "") + self.Script_ConfigPath = ConfigItem( + "Script", "ConfigPath", ".", FolderValidator() + ) + self.Script_LogPath = ConfigItem("Script", "LogPath", ".", FileValidator()) + self.Script_LogTimeStart = ConfigItem( + "Script", "LogTimeStart", 0, RangeValidator(0, 1024) + ) + self.Script_LogTimeEnd = ConfigItem( + "Script", "LogTimeEnd", 0, RangeValidator(0, 1024) + ) + self.Script_LogTimeFormat = ConfigItem( + "Script", "LogTimeFormat", "%Y-%m-%d %H:%M:%S" + ) + self.Script_SuccessLog = ConfigItem("Script", "SuccessLog", "") + self.Script_ErrorLog = ConfigItem("Script", "ErrorLog", "") + + self.Game_Enabled = ConfigItem("Game", "Enabled", True, BoolValidator()) + self.Game_Style = OptionsConfigItem( + "Game", "Style", "Emulator", OptionsValidator(["Emulator", "Client"]) + ) + self.Game_Path = ConfigItem("Game", "Path", ".", FileValidator()) + self.Game_Arguments = ConfigItem("Game", "Arguments", "") + self.Game_WaitTime = ConfigItem("Game", "WaitTime", 0, RangeValidator(0, 1024)) + self.Game_IfForceClose = ConfigItem( + "Game", "IfForceClose", False, BoolValidator() + ) + + self.Run_ProxyTimesLimit = RangeConfigItem( + "Run", "ProxyTimesLimit", 0, RangeValidator(0, 1024) + ) + self.Run_RunTimesLimit = RangeConfigItem( + "Run", "RunTimesLimit", 3, RangeValidator(1, 1024) + ) + self.Run_RunTimeLimit = RangeConfigItem( + "Run", "RunTimeLimit", 10, RangeValidator(1, 1024) + ) + + def get_name(self) -> str: + return self.get(self.Script_Name) + + +class GeneralSubConfig(LQConfig): + """通用子配置""" + + def __init__(self) -> None: + super().__init__() + + self.Info_Name = ConfigItem("Info", "Name", "新配置") + self.Info_Status = ConfigItem("Info", "Status", True, BoolValidator()) + self.Info_RemainedDay = ConfigItem( + "Info", "RemainedDay", -1, RangeValidator(-1, 1024) + ) + self.Info_IfScriptBeforeTask = ConfigItem( + "Info", "IfScriptBeforeTask", False, BoolValidator() + ) + self.Info_ScriptBeforeTask = ConfigItem( + "Info", "ScriptBeforeTask", "", FileValidator() + ) + self.Info_IfScriptAfterTask = ConfigItem( + "Info", "IfScriptAfterTask", False, BoolValidator() + ) + self.Info_ScriptAfterTask = ConfigItem( + "Info", "ScriptAfterTask", "", FileValidator() + ) + self.Info_Notes = ConfigItem("Info", "Notes", "无") + + self.Data_LastProxyDate = ConfigItem("Data", "LastProxyDate", "2000-01-01") + self.Data_ProxyTimes = ConfigItem( + "Data", "ProxyTimes", 0, RangeValidator(0, 1024) + ) + + self.Notify_Enabled = ConfigItem("Notify", "Enabled", False, BoolValidator()) + self.Notify_IfSendStatistic = ConfigItem( + "Notify", "IfSendStatistic", False, BoolValidator() + ) + self.Notify_IfSendMail = ConfigItem( + "Notify", "IfSendMail", False, BoolValidator() + ) + self.Notify_ToAddress = ConfigItem("Notify", "ToAddress", "") + self.Notify_IfServerChan = ConfigItem( + "Notify", "IfServerChan", False, BoolValidator() + ) + self.Notify_ServerChanKey = ConfigItem("Notify", "ServerChanKey", "") + self.Notify_ServerChanChannel = ConfigItem("Notify", "ServerChanChannel", "") + self.Notify_ServerChanTag = ConfigItem("Notify", "ServerChanTag", "") + self.Notify_IfCompanyWebHookBot = ConfigItem( + "Notify", "IfCompanyWebHookBot", False, BoolValidator() + ) + self.Notify_CompanyWebHookBotUrl = ConfigItem( + "Notify", "CompanyWebHookBotUrl", "" + ) + + class AppConfig(GlobalConfig): - VERSION = "4.3.12.0" + VERSION = "4.4.0.1" gameid_refreshed = Signal() PASSWORD_refreshed = Signal() - user_info_changed = Signal() + sub_info_changed = Signal() power_sign_changed = Signal() def __init__(self) -> None: @@ -777,7 +896,7 @@ class AppConfig(GlobalConfig): db = sqlite3.connect(self.database_path) cur = db.cursor() cur.execute("CREATE TABLE version(v text)") - cur.execute("INSERT INTO version VALUES(?)", ("v1.5",)) + cur.execute("INSERT INTO version VALUES(?)", ("v1.6",)) db.commit() cur.close() db.close() @@ -788,221 +907,9 @@ class AppConfig(GlobalConfig): cur.execute("SELECT * FROM version WHERE True") version = cur.fetchall() - if version[0][0] != "v1.5": + if version[0][0] != "v1.6": logger.info("数据文件版本更新开始") if_streaming = False - # v1.0-->v1.1 - if version[0][0] == "v1.0" or if_streaming: - logger.info("数据文件版本更新:v1.0-->v1.1") - if_streaming = True - cur.execute("SELECT * FROM adminx WHERE True") - data = cur.fetchall() - cur.execute("DROP TABLE IF EXISTS adminx") - cur.execute( - "CREATE TABLE adminx(admin text,id text,server text,day int,status text,last date,game text,game_1 text,game_2 text,routines text,annihilation text,infrastructure text,password byte,notes text,numb int,mode text,uid int)" - ) - for i in range(len(data)): - cur.execute( - "INSERT INTO adminx VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", - ( - data[i][0], # 0 0 0 - data[i][1], # 1 1 - - "Official", # 2 2 - - data[i][2], # 3 3 1 - data[i][3], # 4 4 2 - data[i][4], # 5 5 3 - data[i][5], # 6 6 - - data[i][6], # 7 7 - - data[i][7], # 8 8 - - "y", # 9 - 4 - data[i][8], # 10 9 5 - data[i][9], # 11 10 - - data[i][10], # 12 11 6 - data[i][11], # 13 12 7 - data[i][12], # 14 - - - "simple", # 15 - - - data[i][13], # 16 - - - ), - ) - cur.execute("DELETE FROM version WHERE v = ?", ("v1.0",)) - cur.execute("INSERT INTO version VALUES(?)", ("v1.1",)) - db.commit() - # v1.1-->v1.2 - if version[0][0] == "v1.1" or if_streaming: - logger.info("数据文件版本更新:v1.1-->v1.2") - if_streaming = True - cur.execute("SELECT * FROM adminx WHERE True") - data = cur.fetchall() - for i in range(len(data)): - cur.execute( - "UPDATE adminx SET infrastructure = 'n' WHERE mode = ? AND uid = ?", - ( - data[i][15], - data[i][16], - ), - ) - cur.execute("DELETE FROM version WHERE v = ?", ("v1.1",)) - cur.execute("INSERT INTO version VALUES(?)", ("v1.2",)) - db.commit() - # v1.2-->v1.3 - if version[0][0] == "v1.2" or if_streaming: - logger.info("数据文件版本更新:v1.2-->v1.3") - if_streaming = True - cur.execute("ALTER TABLE adminx RENAME COLUMN routines TO routine") - cur.execute("DELETE FROM version WHERE v = ?", ("v1.2",)) - cur.execute("INSERT INTO version VALUES(?)", ("v1.3",)) - db.commit() - # v1.3-->v1.4 - if version[0][0] == "v1.3" or if_streaming: - logger.info("数据文件版本更新:v1.3-->v1.4") - if_streaming = True - (self.app_path / "config/MaaConfig").mkdir(parents=True, exist_ok=True) - shutil.move( - self.app_path / "data/MaaConfig", - self.app_path / "config/MaaConfig", - ) - (self.app_path / "config/MaaConfig/MaaConfig").rename( - self.app_path / "config/MaaConfig/脚本_1" - ) - shutil.copy( - self.database_path, - self.app_path / "config/MaaConfig/脚本_1/user_data.db", - ) - cur.execute("DROP TABLE IF EXISTS adminx") - cur.execute("DELETE FROM version WHERE v = ?", ("v1.3",)) - cur.execute("INSERT INTO version VALUES(?)", ("v1.4",)) - db.commit() - with (self.app_path / "config/gui.json").open( - "r", encoding="utf-8" - ) as f: - info = json.load(f) - maa_config = { - "MaaSet": { - "Name": "", - "Path": info["Default"]["MaaSet.path"], - }, - "RunSet": { - "AnnihilationTimeLimit": info["Default"][ - "TimeLimit.annihilation" - ], - "RoutineTimeLimit": info["Default"]["TimeLimit.routine"], - "RunTimesLimit": info["Default"]["TimesLimit.run"], - }, - } - with (self.app_path / "config/MaaConfig/脚本_1/config.json").open( - "w", encoding="utf-8" - ) as f: - json.dump(maa_config, f, ensure_ascii=False, indent=4) - config = { - "Function": { - "BossKey": info["Default"]["SelfSet.BossKey"], - "IfAllowSleep": bool( - info["Default"]["SelfSet.IfSleep"] == "True" - ), - "IfSilence": bool( - info["Default"]["SelfSet.IfSilence"] == "True" - ), - }, - "Notify": { - "IfPushPlyer": True, - "IfSendErrorOnly": bool( - info["Default"]["SelfSet.IfSendMail.OnlyError"] == "True" - ), - "IfSendMail": bool( - info["Default"]["SelfSet.IfSendMail"] == "True" - ), - "MailAddress": info["Default"]["SelfSet.MailAddress"], - }, - "Start": { - "IfRunDirectly": bool( - info["Default"]["SelfSet.IfProxyDirectly"] == "True" - ), - "IfSelfStart": bool( - info["Default"]["SelfSet.IfSelfStart"] == "True" - ), - }, - "UI": { - "IfShowTray": bool( - info["Default"]["SelfSet.IfToTray"] == "True" - ), - "IfToTray": bool(info["Default"]["SelfSet.IfToTray"] == "True"), - "location": info["Default"]["SelfSet.UIlocation"], - "maximized": bool( - info["Default"]["SelfSet.UImaximized"] == "True" - ), - "size": info["Default"]["SelfSet.UIsize"], - }, - "Update": {"IfAutoUpdate": False}, - } - with (self.app_path / "config/config.json").open( - "w", encoding="utf-8" - ) as f: - json.dump(config, f, ensure_ascii=False, indent=4) - queue_config = { - "QueueSet": {"Enabled": True, "Name": ""}, - "Queue": { - "Member_1": "脚本_1", - "Member_10": "禁用", - "Member_2": "禁用", - "Member_3": "禁用", - "Member_4": "禁用", - "Member_5": "禁用", - "Member_6": "禁用", - "Member_7": "禁用", - "Member_8": "禁用", - "Member_9": "禁用", - }, - "Time": { - "TimeEnabled_0": bool( - info["Default"]["TimeSet.set1"] == "True" - ), - "TimeEnabled_1": bool( - info["Default"]["TimeSet.set2"] == "True" - ), - "TimeEnabled_2": bool( - info["Default"]["TimeSet.set3"] == "True" - ), - "TimeEnabled_3": bool( - info["Default"]["TimeSet.set4"] == "True" - ), - "TimeEnabled_4": bool( - info["Default"]["TimeSet.set5"] == "True" - ), - "TimeEnabled_5": bool( - info["Default"]["TimeSet.set6"] == "True" - ), - "TimeEnabled_6": bool( - info["Default"]["TimeSet.set7"] == "True" - ), - "TimeEnabled_7": bool( - info["Default"]["TimeSet.set8"] == "True" - ), - "TimeEnabled_8": bool( - info["Default"]["TimeSet.set9"] == "True" - ), - "TimeEnabled_9": bool( - info["Default"]["TimeSet.set10"] == "True" - ), - "TimeSet_0": info["Default"]["TimeSet.run1"], - "TimeSet_1": info["Default"]["TimeSet.run2"], - "TimeSet_2": info["Default"]["TimeSet.run3"], - "TimeSet_3": info["Default"]["TimeSet.run4"], - "TimeSet_4": info["Default"]["TimeSet.run5"], - "TimeSet_5": info["Default"]["TimeSet.run6"], - "TimeSet_6": info["Default"]["TimeSet.run7"], - "TimeSet_7": info["Default"]["TimeSet.run8"], - "TimeSet_8": info["Default"]["TimeSet.run9"], - "TimeSet_9": info["Default"]["TimeSet.run10"], - }, - } - (self.app_path / "config/QueueConfig").mkdir( - parents=True, exist_ok=True - ) - with (self.app_path / "config/QueueConfig/调度队列_1.json").open( - "w", encoding="utf-8" - ) as f: - json.dump(queue_config, f, ensure_ascii=False, indent=4) - (self.app_path / "config/gui.json").unlink() # v1.4-->v1.5 if version[0][0] == "v1.4" or if_streaming: logger.info("数据文件版本更新:v1.4-->v1.5") @@ -1130,7 +1037,38 @@ class AppConfig(GlobalConfig): shutil.rmtree(config["Path"] / f"simple") if (config["Path"] / f"beta").exists(): shutil.rmtree(config["Path"] / f"beta") + # v1.5-->v1.6 + if version[0][0] == "v1.5" or if_streaming: + logger.info("数据文件版本更新:v1.5-->v1.6") + if_streaming = True + cur.execute("DELETE FROM version WHERE v = ?", ("v1.5",)) + cur.execute("INSERT INTO version VALUES(?)", ("v1.6",)) + db.commit() + # 删除旧的注册表项 + import winreg + key = winreg.OpenKey( + winreg.HKEY_CURRENT_USER, + r"Software\Microsoft\Windows\CurrentVersion\Run", + 0, + winreg.KEY_READ, + ) + + try: + value, _ = winreg.QueryValueEx(key, "AUTO_MAA") + winreg.CloseKey(key) + key = winreg.OpenKey( + winreg.HKEY_CURRENT_USER, + r"Software\Microsoft\Windows\CurrentVersion\Run", + winreg.KEY_SET_VALUE, + winreg.KEY_ALL_ACCESS + | winreg.KEY_WRITE + | winreg.KEY_CREATE_SUB_KEY, + ) + winreg.DeleteValue(key, "AUTO_MAA") + winreg.CloseKey(key) + except FileNotFoundError: + pass cur.close() db.close() logger.info("数据文件版本更新完成") @@ -1145,8 +1083,8 @@ class AppConfig(GlobalConfig): Union[ str, Path, - MaaConfig, - Dict[str, Dict[str, Union[Path, MaaUserConfig]]], + Union[MaaConfig, GeneralConfig], + Dict[str, Dict[str, Union[Path, MaaUserConfig, GeneralSubConfig]]], ], ], ] = {} @@ -1164,12 +1102,26 @@ class AppConfig(GlobalConfig): "Config": maa_config, "UserData": None, } + if (self.app_path / "config/GeneralConfig").exists(): + for general_dir in (self.app_path / "config/GeneralConfig").iterdir(): + if general_dir.is_dir(): + + general_config = GeneralConfig() + general_config.load(general_dir / "config.json", general_config) + general_config.save() + + self.member_dict[general_dir.name] = { + "Type": "General", + "Path": general_dir, + "Config": general_config, + "SubData": None, + } self.member_dict = dict( sorted(self.member_dict.items(), key=lambda x: int(x[0][3:])) ) - def search_maa_user(self, name) -> None: + def search_maa_user(self, name: str) -> None: user_dict: Dict[str, Dict[str, Union[Path, MaaUserConfig]]] = {} for user_dir in (Config.member_dict[name]["Path"] / "UserData").iterdir(): @@ -1179,15 +1131,28 @@ class AppConfig(GlobalConfig): user_config.load(user_dir / "config.json", user_config) user_config.save() - user_dict[user_dir.stem] = { - "Path": user_dir, - "Config": user_config, - } + user_dict[user_dir.stem] = {"Path": user_dir, "Config": user_config} self.member_dict[name]["UserData"] = dict( sorted(user_dict.items(), key=lambda x: int(x[0][3:])) ) + def search_general_sub(self, name: str) -> None: + + user_dict: Dict[str, Dict[str, Union[Path, GeneralSubConfig]]] = {} + for sub_dir in (Config.member_dict[name]["Path"] / "SubData").iterdir(): + if sub_dir.is_dir(): + + sub_config = GeneralSubConfig() + sub_config.load(sub_dir / "config.json", sub_config) + sub_config.save() + + user_dict[sub_dir.stem] = {"Path": sub_dir, "Config": sub_config} + + self.member_dict[name]["SubData"] = dict( + sorted(user_dict.items(), key=lambda x: int(x[0][3:])) + ) + def search_plan(self) -> None: """搜索所有计划表""" @@ -1267,7 +1232,7 @@ class AppConfig(GlobalConfig): if user["Config"].get(user["Config"].Info_GameIdMode) == old: user["Config"].set(user["Config"].Info_GameIdMode, new) - def change_user_info( + def change_maa_user_info( self, name: str, user_data: Dict[str, Dict[str, Union[str, Path, dict]]] ) -> None: """代理完成后保存改动的用户信息""" @@ -1301,7 +1266,28 @@ class AppConfig(GlobalConfig): info["Config"]["Data"]["CustomInfrastPlanIndex"], ) - self.user_info_changed.emit() + self.sub_info_changed.emit() + + def change_general_sub_info( + self, name: str, sub_data: Dict[str, Dict[str, Union[str, Path, dict]]] + ) -> None: + """代理完成后保存改动的配置信息""" + + for sub, info in sub_data.items(): + + sub_config = self.member_dict[name]["SubData"][sub]["Config"] + + sub_config.set( + sub_config.Info_RemainedDay, info["Config"]["Info"]["RemainedDay"] + ) + sub_config.set( + sub_config.Data_LastProxyDate, info["Config"]["Data"]["LastProxyDate"] + ) + sub_config.set( + sub_config.Data_ProxyTimes, info["Config"]["Data"]["ProxyTimes"] + ) + + self.sub_info_changed.emit() def set_power_sign(self, sign: str) -> None: """设置当前电源状态""" diff --git a/app/core/task_manager.py b/app/core/task_manager.py index 8d205db..db8166a 100644 --- a/app/core/task_manager.py +++ b/app/core/task_manager.py @@ -36,7 +36,7 @@ from .config import Config from .main_info_bar import MainInfoBar from .network import Network from .sound_player import SoundPlayer -from app.models import MaaManager +from app.models import MaaManager, GeneralManager from app.services import System @@ -48,7 +48,8 @@ class Task(QThread): play_sound = Signal(str) question = Signal(str, str) question_response = Signal(bool) - update_user_info = Signal(str, dict) + update_maa_user_info = Signal(str, dict) + update_general_sub_info = Signal(str, dict) create_task_list = Signal(list) create_user_list = Signal(list) update_task_list = Signal(list) @@ -89,7 +90,31 @@ class Task(QThread): self.task.play_sound.connect(self.play_sound.emit) self.task.accomplish.connect(lambda: self.accomplish.emit([])) - self.task.run() + try: + self.task.run() + except Exception as e: + logger.exception(f"任务异常:{self.name},错误信息:{e}") + self.push_info_bar.emit("error", "任务异常", self.name, -1) + + elif self.mode == "设置通用脚本": + + logger.info(f"任务开始:设置{self.name}") + self.push_info_bar.emit("info", "设置通用脚本", self.name, 3000) + + self.task = GeneralManager( + self.mode, + Config.member_dict[self.name], + self.info["SetSubInfo"]["Path"], + ) + self.task.push_info_bar.connect(self.push_info_bar.emit) + self.task.play_sound.connect(self.play_sound.emit) + self.task.accomplish.connect(lambda: self.accomplish.emit([])) + + try: + self.task.run() + except Exception as e: + logger.exception(f"任务异常:{self.name},错误信息:{e}") + self.push_info_bar.emit("error", "任务异常", self.name, -1) else: @@ -97,11 +122,8 @@ class Task(QThread): [ ( value - if Config.member_dict[value]["Config"].get( - Config.member_dict[value]["Config"].MaaSet_Name - ) - == "" - else f"{value} - {Config.member_dict[value]["Config"].get(Config.member_dict[value]["Config"].MaaSet_Name)}" + if Config.member_dict[value]["Config"].get_name() == "" + else f"{value} - {Config.member_dict[value]["Config"].get_name()}" ), "等待", value, @@ -150,24 +172,60 @@ class Task(QThread): self.task.create_user_list.connect(self.create_user_list.emit) self.task.update_user_list.connect(self.update_user_list.emit) self.task.update_log_text.connect(self.update_log_text.emit) - self.task.update_user_info.connect(self.update_user_info.emit) + self.task.update_user_info.connect(self.update_maa_user_info.emit) self.task.accomplish.connect( lambda log: self.task_accomplish(task[2], log) ) + elif Config.member_dict[task[2]]["Type"] == "General": + + self.task = GeneralManager( + self.mode[0:4], + Config.member_dict[task[2]], + ) + + self.task.question.connect(self.question.emit) + self.question_response.disconnect() + self.question_response.connect(self.task.question_response.emit) + self.task.push_info_bar.connect(self.push_info_bar.emit) + self.task.play_sound.connect(self.play_sound.emit) + self.task.create_user_list.connect(self.create_user_list.emit) + self.task.update_user_list.connect(self.update_user_list.emit) + self.task.update_log_text.connect(self.update_log_text.emit) + self.task.update_sub_info.connect(self.update_general_sub_info.emit) + self.task.accomplish.connect( + lambda log: self.task_accomplish(task[2], log) + ) + + try: self.task.run() - Config.running_list.remove(task[2]) + task[1] = "完成" + self.update_task_list.emit(self.task_list) + logger.info(f"任务完成:{task[0]}") + self.push_info_bar.emit("info", "任务完成", task[0], 3000) - task[1] = "完成" - self.update_task_list.emit(self.task_list) - logger.info(f"任务完成:{task[0]}") - self.push_info_bar.emit("info", "任务完成", task[0], 3000) + except Exception as e: + + self.task_accomplish( + task[2], + { + "Time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + "History": f"任务异常,异常简报:{e}", + }, + ) + + task[1] = "异常" + self.update_task_list.emit(self.task_list) + logger.exception(f"任务异常:{task[0]},错误信息:{e}") + 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): - """保存保存任务结果""" + """保存任务结果""" self.logs.append([name, log]) self.task.deleteLater() @@ -207,7 +265,10 @@ class _TaskManager(QObject): ) self.task_dict[name].push_info_bar.connect(MainInfoBar.push_info_bar) self.task_dict[name].play_sound.connect(SoundPlayer.play) - self.task_dict[name].update_user_info.connect(Config.change_user_info) + self.task_dict[name].update_maa_user_info.connect(Config.change_maa_user_info) + self.task_dict[name].update_general_sub_info.connect( + Config.change_general_sub_info + ) self.task_dict[name].accomplish.connect( lambda logs: self.remove_task(mode, name, logs) ) diff --git a/app/models/MAA.py b/app/models/MAA.py index ded0b51..370620a 100644 --- a/app/models/MAA.py +++ b/app/models/MAA.py @@ -40,7 +40,7 @@ from typing import Union, List, Dict from app.core import Config, MaaConfig, MaaUserConfig from app.services import Notify, Crypto, System, skland_sign_in -from app.utils.ImageUtils import ImageUtils +from app.utils import ProcessManager class MaaManager(QObject): @@ -58,8 +58,6 @@ class MaaManager(QObject): interrupt = Signal() accomplish = Signal(dict) - isInterruptionRequested = False - def __init__( self, mode: str, @@ -81,17 +79,25 @@ class MaaManager(QObject): self.config_path = config["Path"] self.user_config_path = user_config_path + self.emulator_process_manager = ProcessManager() + self.maa_process_manager = ProcessManager() + self.log_monitor = QFileSystemWatcher() self.log_monitor_timer = QTimer() self.log_monitor_timer.timeout.connect(self.refresh_maa_log) self.monitor_loop = QEventLoop() + self.maa_process_manager.processClosed.connect( + lambda: self.log_monitor.fileChanged.emit("进程结束检查") + ) + self.question_loop = QEventLoop() self.question_response.connect(self.__capture_response) self.question_response.connect(self.question_loop.quit) self.wait_loop = QEventLoop() + self.isInterruptionRequested = False self.interrupt.connect(self.quit_monitor) self.maa_version = None @@ -505,9 +511,8 @@ class MaaManager(QObject): logger.info( f"{self.name} | 启动模拟器:{self.emulator_path},参数:{self.emulator_arguments}" ) - self.emulator_process = subprocess.Popen( - [self.emulator_path, *self.emulator_arguments], - creationflags=subprocess.CREATE_NO_WINDOW, + self.emulator_process_manager.open_process( + self.emulator_path, self.emulator_arguments, 0 ) except Exception as e: logger.error(f"{self.name} | 启动模拟器时出现异常:{e}") @@ -526,10 +531,7 @@ class MaaManager(QObject): self.search_ADB_address() # 创建MAA任务 - maa = subprocess.Popen( - [self.maa_exe_path], - creationflags=subprocess.CREATE_NO_WINDOW, - ) + self.maa_process_manager.open_process(self.maa_exe_path, [], 10) # 监测MAA运行状态 self.start_monitor(start_time, mode_book[mode]) @@ -579,11 +581,11 @@ class MaaManager(QObject): f"{self.maa_result}\n正在中止相关程序\n请等待10s" ) # 无命令行中止MAA与其子程序 + self.maa_process_manager.kill(if_force=True) System.kill_process(self.maa_exe_path) # 中止模拟器进程 - self.emulator_process.terminate() - self.emulator_process.wait() + self.emulator_process_manager.kill() self.if_open_emulator = True @@ -644,8 +646,7 @@ class MaaManager(QObject): ) # 任务结束后再次手动中止模拟器进程,防止退出不彻底 if self.if_kill_emulator: - self.emulator_process.terminate() - self.emulator_process.wait() + self.emulator_process_manager.kill() self.if_open_emulator = True # 记录剿灭情况 @@ -777,10 +778,7 @@ class MaaManager(QObject): # 记录当前时间 start_time = datetime.now() # 创建MAA任务 - maa = subprocess.Popen( - [self.maa_exe_path], - creationflags=subprocess.CREATE_NO_WINDOW, - ) + self.maa_process_manager.open_process(self.maa_exe_path, [], 10) # 监测MAA运行状态 self.start_monitor(start_time, "人工排查") @@ -799,6 +797,7 @@ class MaaManager(QObject): f"{self.maa_result}\n正在中止相关程序\n请等待10s" ) # 无命令行中止MAA与其子程序 + self.maa_process_manager.kill(if_force=True) System.kill_process(self.maa_exe_path) self.if_open_emulator = True self.sleep(10) @@ -845,10 +844,7 @@ class MaaManager(QObject): # 配置MAA self.set_maa(self.mode, "") # 创建MAA任务 - maa = subprocess.Popen( - [self.maa_exe_path], - creationflags=subprocess.CREATE_NO_WINDOW, - ) + self.maa_process_manager.open_process(self.maa_exe_path, [], 10) # 记录当前时间 start_time = datetime.now() @@ -870,6 +866,7 @@ class MaaManager(QObject): # 关闭可能未正常退出的MAA进程 if self.isInterruptionRequested: + self.maa_process_manager.kill(if_force=True) System.kill_process(self.maa_exe_path) # 复原MAA配置文件 @@ -1024,6 +1021,7 @@ class MaaManager(QObject): self.ADB_address = ADB_address # 覆写当前ADB地址 + 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: data = json.load(f) @@ -1053,7 +1051,7 @@ class MaaManager(QObject): # 一分钟内未执行日志变化检查,强制检查一次 if datetime.now() - self.last_check_time > timedelta(minutes=1): - self.log_monitor.fileChanged.emit(self.log_monitor.files()[0]) + self.log_monitor.fileChanged.emit("1分钟超时检查") def check_maa_log(self, start_time: datetime, mode: str) -> list: """获取MAA日志并检查以判断MAA程序运行状态""" @@ -1155,7 +1153,10 @@ class MaaManager(QObject): elif "已停止" in log: self.maa_result = "MAA在完成任务前中止" - elif "MaaAssistantArknights GUI exited" in log: + elif ( + "MaaAssistantArknights GUI exited" in log + or not self.maa_process_manager.is_running() + ): self.maa_result = "MAA在完成任务前退出" elif datetime.now() - latest_time > timedelta( @@ -1178,7 +1179,10 @@ class MaaManager(QObject): self.maa_result = "MAA未检测到任何模拟器" elif "已停止" in log: self.maa_result = "MAA在完成任务前中止" - elif "MaaAssistantArknights GUI exited" in log: + elif ( + "MaaAssistantArknights GUI exited" in log + or not self.maa_process_manager.is_running() + ): self.maa_result = "MAA在完成任务前退出" elif self.isInterruptionRequested: self.maa_result = "任务被手动中止" @@ -1186,7 +1190,10 @@ class MaaManager(QObject): self.maa_result = "Wait" elif mode == "设置MAA": - if "MaaAssistantArknights GUI exited" in log: + if ( + "MaaAssistantArknights GUI exited" in log + or not self.maa_process_manager.is_running() + ): self.maa_result = "Success!" else: self.maa_result = "Wait" @@ -1229,6 +1236,7 @@ class MaaManager(QObject): user_data = self.data[index]["Config"] # 配置MAA前关闭可能未正常退出的MAA进程 + self.maa_process_manager.kill(if_force=True) System.kill_process(self.maa_exe_path) # 预导入MAA配置文件 diff --git a/app/models/__init__.py b/app/models/__init__.py index 3a59faa..62aab25 100644 --- a/app/models/__init__.py +++ b/app/models/__init__.py @@ -29,6 +29,7 @@ __version__ = "4.2.0" __author__ = "DLmaster361 " __license__ = "GPL-3.0 license" +from .general import GeneralManager from .MAA import MaaManager -__all__ = ["MaaManager"] +__all__ = ["GeneralManager", "MaaManager"] diff --git a/app/models/general.py b/app/models/general.py new file mode 100644 index 0000000..2dc222d --- /dev/null +++ b/app/models/general.py @@ -0,0 +1,905 @@ +# AUTO_MAA:A MAA Multi Account Management and Automation Tool +# Copyright © 2024-2025 DLmaster361 + +# This file is part of AUTO_MAA. + +# AUTO_MAA is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published +# by the Free Software Foundation, either version 3 of the License, +# or (at your option) any later version. + +# AUTO_MAA is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty +# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See +# the GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with AUTO_MAA. If not, see . + +# Contact: DLmaster_361@163.com + +""" +AUTO_MAA +通用功能组件 +v4.3 +作者:DLmaster_361 +""" + +from loguru import logger +from PySide6.QtCore import QObject, Signal, QEventLoop, QFileSystemWatcher, QTimer +import os +import sys +import shutil +import subprocess +from functools import partial +from datetime import datetime, timedelta +from pathlib import Path +from jinja2 import Environment, FileSystemLoader +from typing import Union, List, Dict + +from app.core import Config, GeneralConfig, GeneralSubConfig +from app.services import Notify, System +from app.utils import ProcessManager + + +class GeneralManager(QObject): + """通用脚本通用控制器""" + + question = Signal(str, str) + question_response = Signal(bool) + update_sub_info = Signal(str, dict) + push_info_bar = Signal(str, str, str, int) + play_sound = Signal(str) + create_user_list = Signal(list) + update_user_list = Signal(list) + update_log_text = Signal(str) + interrupt = Signal() + accomplish = Signal(dict) + + def __init__( + self, + mode: str, + config: Dict[ + str, + Union[ + str, + Path, + GeneralConfig, + Dict[str, Dict[str, Union[Path, GeneralSubConfig]]], + ], + ], + sub_config_path: Path = None, + ): + super(GeneralManager, self).__init__() + + self.sub_list = [] + self.mode = mode + self.config_path = config["Path"] + self.sub_config_path = sub_config_path + + self.game_process_manager = ProcessManager() + self.script_process_manager = ProcessManager() + + self.log_monitor = QFileSystemWatcher() + self.log_monitor_timer = QTimer() + self.log_monitor_timer.timeout.connect(self.refresh_log) + self.monitor_loop = QEventLoop() + + self.script_process_manager.processClosed.connect( + lambda: self.log_monitor.fileChanged.emit("进程结束检查") + ) + + self.question_loop = QEventLoop() + self.question_response.connect(self.__capture_response) + self.question_response.connect(self.question_loop.quit) + + self.wait_loop = QEventLoop() + + self.isInterruptionRequested = False + self.interrupt.connect(self.quit_monitor) + + self.task_dict = {} + self.set = config["Config"].toDict() + + self.data: Dict[str, Dict[str, Union[Path, dict]]] = {} + if self.mode != "设置通用脚本": + for name, info in config["SubData"].items(): + self.data[name] = { + "Path": info["Path"], + "Config": info["Config"].toDict(), + } + + self.data = dict(sorted(self.data.items(), key=lambda x: int(x[0][3:]))) + + def check_config_info(self) -> bool: + """检查配置完整性""" + + if not ( + Path(self.set["Script"]["RootPath"]).exists() + and Path(self.set["Script"]["ScriptPath"]).exists() + and Path(self.set["Script"]["ConfigPath"]).exists() + and Path(self.set["Script"]["LogPath"]).exists() + and self.set["Script"]["LogTimeFormat"] + and self.set["Script"]["ErrorLog"] + ) or ( + self.set["Game"]["Enabled"] and not Path(self.set["Game"]["Path"]).exists() + ): + logger.error("脚本配置缺失") + self.push_info_bar.emit("error", "脚本配置缺失", "请检查脚本配置!", -1) + return False + + return True + + 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"]) + self.script_log_path = Path(self.set["Script"]["LogPath"]) + self.game_path = Path(self.set["Game"]["Path"]) + self.log_time_range = [ + self.set["Script"]["LogTimeStart"], + self.set["Script"]["LogTimeEnd"], + ] + self.success_log = [ + _.strip() for _ in self.set["Script"]["SuccessLog"].split("|") + ] + print(f"Success Log: {self.success_log}") + self.error_log = [_.strip() for _ in self.set["Script"]["ErrorLog"].split("|")] + + def run(self): + """主进程,运行通用脚本代理进程""" + + current_date = datetime.now().strftime("%m-%d") + curdate = Config.server_date().strftime("%Y-%m-%d") + begin_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + + # 检查配置完整性 + if not self.check_config_info(): + + self.accomplish.emit( + {"Time": begin_time, "History": "由于配置不完整,通用代理进程中止"} + ) + return None + + self.configure() + + # 整理用户数据,筛选需代理的用户 + if self.mode != "设置通用脚本": + + self.data = dict(sorted(self.data.items(), key=lambda x: int(x[0][3:]))) + self.sub_list: List[List[str, str, str]] = [ + [_["Config"]["Info"]["Name"], "等待", index] + for index, _ in self.data.items() + if ( + _["Config"]["Info"]["RemainedDay"] != 0 + and _["Config"]["Info"]["Status"] + ) + ] + self.create_user_list.emit(self.sub_list) + + # 自动代理模式 + if self.mode == "自动代理": + + # 执行情况预处理 + for _ in self.sub_list: + if self.data[_[2]]["Config"]["Data"]["LastProxyDate"] != curdate: + self.data[_[2]]["Config"]["Data"]["LastProxyDate"] = curdate + self.data[_[2]]["Config"]["Data"]["ProxyTimes"] = 0 + _[ + 0 + ] += f" - 第{self.data[_[2]]['Config']['Data']['ProxyTimes'] + 1}次代理" + + # 开始代理 + for sub in self.sub_list: + + sub_data = self.data[sub[2]]["Config"] + + if self.isInterruptionRequested: + break + + if ( + self.set["Run"]["ProxyTimesLimit"] == 0 + or sub_data["Data"]["ProxyTimes"] + < self.set["Run"]["ProxyTimesLimit"] + ): + sub[1] = "运行" + self.update_user_list.emit(self.sub_list) + else: + sub[1] = "跳过" + self.update_user_list.emit(self.sub_list) + continue + + logger.info(f"{self.name} | 开始代理配置: {sub[0]}") + + sub_logs_list = [] + sub_start_time = datetime.now() + + run_book = False + + if not (self.data[sub[2]]["Path"] / "ConfigFiles").exists(): + logger.error(f"{self.name} | 配置: {sub[0]} - 未找到配置文件") + self.push_info_bar.emit( + "error", + "启动通用代理进程失败", + f"未找到{sub[0]}的配置文件!", + -1, + ) + run_book = False + continue + + # 尝试次数循环 + for i in range(self.set["Run"]["RunTimesLimit"]): + + if self.isInterruptionRequested or run_book: + break + + logger.info( + f"{self.name} | 用户: {sub[0]} - 尝试次数: {i + 1}/{self.set['Run']['RunTimesLimit']}" + ) + + # 记录当前时间 + start_time = datetime.now() + # 配置脚本 + self.set_sub(sub[2]) + # 执行任务前脚本 + if ( + sub_data["Info"]["IfScriptBeforeTask"] + and Path(sub_data["Info"]["ScriptBeforeTask"]).exists() + ): + self.execute_script_task( + Path(sub_data["Info"]["ScriptBeforeTask"]), "脚本前任务" + ) + + # 启动游戏/模拟器 + if self.set["Game"]["Enabled"]: + + try: + logger.info( + f"{self.name} | 启动游戏/模拟器:{self.game_path},参数:{self.set['Game']['Arguments']}" + ) + self.game_process_manager.open_process( + self.game_path, + str(self.set["Game"]["Arguments"]).split(" "), + 0, + ) + except Exception as e: + logger.error( + f"{self.name} | 启动游戏/模拟器时出现异常:{e}" + ) + self.push_info_bar.emit( + "error", + "启动游戏/模拟器时出现异常", + "请检查游戏/模拟器路径设置", + -1, + ) + self.script_result = "游戏/模拟器启动失败" + break + + # 添加静默进程标记 + if self.set["Game"]["Style"] == "Emulator": + Config.silence_list.append(self.game_path) + + self.update_log_text.emit( + f"正在等待游戏/模拟器完成启动\n请等待{self.set['Game']['WaitTime']}s" + ) + + self.sleep(self.set["Game"]["WaitTime"]) + + # 10s后移除静默进程标记 + if self.set["Game"]["Style"] == "Emulator": + QTimer.singleShot( + 10000, + partial(Config.silence_list.remove, self.game_path), + ) + + # 运行脚本任务 + self.script_process_manager.open_process( + self.script_exe_path, + str(self.set["Script"]["Arguments"]).split(" "), + ) + + # 监测运行状态 + self.start_monitor(start_time) + + if self.script_result == "Success!": + + # 标记任务完成 + run_book = True + + # 中止相关程序 + self.script_process_manager.kill() + System.kill_process(self.script_exe_path) + if self.set["Game"]["Enabled"]: + self.game_process_manager.kill() + if self.set["Game"]["IfForceClose"]: + System.kill_process(self.game_path) + + logger.info( + f"{self.name} | 配置: {sub[0]} - 通用脚本进程完成代理任务" + ) + self.update_log_text.emit( + "检测到通用脚本进程完成代理任务\n正在等待相关程序结束\n请等待10s" + ) + + self.sleep(10) + else: + logger.error( + f"{self.name} | 配置: {sub[0]} - 代理任务异常: {self.script_result}" + ) + # 打印中止信息 + # 此时,log变量内存储的就是出现异常的日志信息,可以保存或发送用于问题排查 + self.update_log_text.emit( + f"{self.script_result}\n正在中止相关程序\n请等待10s" + ) + + # 中止相关程序 + self.script_process_manager.kill() + if self.set["Game"]["Enabled"]: + self.game_process_manager.kill() + if self.set["Game"]["IfForceClose"]: + System.kill_process(self.game_path) + + # 推送异常通知 + Notify.push_plyer( + "用户自动代理出现异常!", + f"用户 {sub[0].replace("_", " 今天的")}出现一次异常", + f"{sub[0].replace("_", " ")}出现异常", + 1, + ) + if i == self.set["Run"]["RunTimesLimit"] - 1: + self.play_sound.emit("子任务失败") + else: + self.play_sound.emit(self.script_result) + self.sleep(10) + + # 执行任务后脚本 + if ( + sub_data["Info"]["IfScriptAfterTask"] + and Path(sub_data["Info"]["ScriptAfterTask"]).exists() + ): + self.execute_script_task( + Path(sub_data["Info"]["ScriptAfterTask"]), "脚本后任务" + ) + + # # 保存运行日志以及统计信息 + # Config.save_maa_log( + # Config.app_path + # / f"history/{curdate}/{sub_data['Info']['Name']}/{start_time.strftime("%H-%M-%S")}.log", + # self.check_script_log(start_time, mode_book[mode]), + # self.maa_result, + # ) + sub_logs_list.append( + Config.app_path + / f"history/{curdate}/{sub_data['Info']['Name']}/{start_time.strftime("%H-%M-%S")}.json", + ) + + # 发送统计信息 + # statistics = Config.merge_maa_logs("指定项", sub_logs_list) + statistics = { + "sub_index": sub[2], + "sub_info": sub[0], + "start_time": sub_start_time.strftime("%Y-%m-%d %H:%M:%S"), + "end_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + "sub_result": "代理成功" if run_book else self.script_result, + } + self.push_notification( + "统计信息", + f"{current_date} | 配置 {sub[0]} 的自动代理统计报告", + statistics, + sub_data, + ) + + if run_book: + # 成功完成代理的用户修改相关参数 + if ( + sub_data["Data"]["ProxyTimes"] == 0 + and sub_data["Info"]["RemainedDay"] != -1 + ): + sub_data["Info"]["RemainedDay"] -= 1 + sub_data["Data"]["ProxyTimes"] += 1 + sub[1] = "完成" + Notify.push_plyer( + "成功完成一个自动代理任务!", + f"已完成配置 {sub[0].replace("_", " 今天的")}任务", + f"已完成 {sub[0].replace("_", " 的")}", + 3, + ) + else: + # 录入代理失败的用户 + sub[1] = "异常" + + self.update_user_list.emit(self.sub_list) + + # 设置通用脚本模式 + elif self.mode == "设置通用脚本": + + # 配置通用脚本 + self.set_sub() + + try: + # 创建通用脚本任务 + logger.info(f"{self.name} | 无参数启动通用脚本:{self.script_exe_path}") + self.script_process_manager.open_process(self.script_exe_path) + + # 记录当前时间 + start_time = datetime.now() + + # 监测通用脚本运行状态 + self.start_monitor(start_time) + + self.sub_config_path.mkdir(parents=True, exist_ok=True) + shutil.copytree( + self.script_config_path, self.sub_config_path, dirs_exist_ok=True + ) + + except Exception as e: + logger.error(f"{self.name} | 启动通用脚本时出现异常:{e}") + self.push_info_bar.emit( + "error", + "启动通用脚本时出现异常", + "请检查相关设置", + -1, + ) + + result_text = "" + + # 导出结果 + if self.mode in ["自动代理"]: + + # 关闭可能未正常退出的通用脚本进程 + if self.isInterruptionRequested: + self.script_process_manager.kill(if_force=True) + System.kill_process(self.script_exe_path) + if self.set["Game"]["Enabled"]: + self.game_process_manager.kill(if_force=True) + if self.set["Game"]["IfForceClose"]: + System.kill_process(self.game_path) + + # 更新用户数据 + updated_info = {_[2]: self.data[_[2]] for _ in self.sub_list} + self.update_sub_info.emit(self.config_path.name, updated_info) + + error_index = [_[2] for _ in self.sub_list if _[1] == "异常"] + over_index = [_[2] for _ in self.sub_list if _[1] == "完成"] + wait_index = [_[2] for _ in self.sub_list if _[1] == "等待"] + + # 保存运行日志 + title = ( + f"{current_date} | {self.name}的{self.mode[:4]}任务报告" + if self.name != "" + else f"{current_date} | {self.mode[:4]}任务报告" + ) + result = { + "title": f"{self.mode[:4]}任务报告", + "script_name": (self.name if self.name != "" else "空白"), + "start_time": begin_time, + "end_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + "completed_count": len(over_index), + "uncompleted_count": len(error_index) + len(wait_index), + "failed_sub": [ + self.data[_]["Config"]["Info"]["Name"] for _ in error_index + ], + "waiting_sub": [ + self.data[_]["Config"]["Info"]["Name"] for _ in wait_index + ], + } + + # 生成结果文本 + result_text = ( + f"任务开始时间:{result['start_time']},结束时间:{result['end_time']}\n" + f"已完成数:{result['completed_count']},未完成数:{result['uncompleted_count']}\n\n" + ) + if len(result["failed_sub"]) > 0: + result_text += f"{self.mode[2:4]}未成功的配置:\n{"\n".join(result['failed_sub'])}\n" + if len(result["waiting_sub"]) > 0: + result_text += f"\n未开始{self.mode[2:4]}的配置:\n{"\n".join(result['waiting_sub'])}\n" + + # 推送代理结果通知 + Notify.push_plyer( + title.replace("报告", "已完成!"), + f"已完成配置数:{len(over_index)},未完成配置数:{len(error_index) + len(wait_index)}", + f"已完成配置数:{len(over_index)},未完成配置数:{len(error_index) + len(wait_index)}", + 10, + ) + self.push_notification("代理结果", title, result) + + self.log_monitor.deleteLater() + self.log_monitor_timer.deleteLater() + self.accomplish.emit({"Time": begin_time, "History": result_text}) + + def requestInterruption(self) -> None: + logger.info(f"{self.name} | 收到任务中止申请") + + if len(self.log_monitor.files()) != 0: + self.interrupt.emit() + + self.script_result = "任务被手动中止" + self.isInterruptionRequested = True + self.wait_loop.quit() + + def push_question(self, title: str, message: str) -> bool: + + self.question.emit(title, message) + self.question_loop.exec() + return self.response + + def __capture_response(self, response: bool) -> None: + self.response = response + + def sleep(self, time: int) -> None: + """非阻塞型等待""" + + QTimer.singleShot(time * 1000, self.wait_loop.quit) + self.wait_loop.exec() + + def refresh_log(self) -> None: + """刷新脚本日志""" + + 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分钟超时检查") + + def strptime( + self, date_string: str, format: str, default_date: datetime + ) -> datetime: + """根据指定格式解析日期字符串""" + + # 时间字段映射表 + time_fields = { + "%Y": "year", + "%m": "month", + "%d": "day", + "%H": "hour", + "%M": "minute", + "%S": "second", + "%f": "microsecond", + } + + date = datetime.strptime(date_string, format) + + # 构建参数字典 + datetime_kwargs = {} + for format_code, field_name in time_fields.items(): + if format_code in format: + datetime_kwargs[field_name] = getattr(date, field_name) + else: + datetime_kwargs[field_name] = getattr(default_date, field_name) + + return datetime(**datetime_kwargs) + + def check_script_log(self, start_time: datetime) -> list: + """获取脚本日志并检查以判断脚本程序运行状态""" + + self.last_check_time = datetime.now() + + # 获取日志 + logs = [] + if_log_start = False + with self.script_log_path.open(mode="r", encoding="utf-8") as f: + for entry in f: + if not if_log_start: + try: + entry_time = self.strptime( + entry[self.log_time_range[0] : self.log_time_range[1]], + self.set["Script"]["LogTimeFormat"], + self.last_check_time, + ) + + if entry_time > start_time: + if_log_start = True + logs.append(entry) + except ValueError: + pass + else: + logs.append(entry) + log = "".join(logs) + + # 更新日志 + if len(logs) > 100: + self.update_log_text.emit("".join(logs[-100:])) + else: + self.update_log_text.emit("".join(logs)) + + if "自动代理" in self.mode: + + # 获取最近一条日志的时间 + latest_time = start_time + for _ in logs[::-1]: + try: + latest_time = self.strptime( + _[self.log_time_range[0] : self.log_time_range[1]], + self.set["Script"]["LogTimeFormat"], + self.last_check_time, + ) + break + except ValueError: + pass + + for success_sign in self.success_log: + if success_sign in log: + self.script_result = "Success!" + break + else: + + if self.isInterruptionRequested: + self.script_result = "任务被手动中止" + elif datetime.now() - latest_time > timedelta( + minutes=self.set["Run"]["RunTimeLimit"] + ): + self.script_result = "脚本进程超时" + else: + for error_sign in self.error_log: + if error_sign in log: + self.script_result = error_sign + break + else: + if self.script_process_manager.is_running(): + self.script_result = "Wait" + elif self.success_log: + self.script_result = "脚本在完成任务前退出" + else: + self.script_result = "Success!" + + elif self.mode == "设置通用脚本": + if self.script_process_manager.is_running(): + self.script_result = "Wait" + else: + self.script_result = "Success!" + + if self.script_result != "Wait": + + self.quit_monitor() + + return logs + + def start_monitor(self, start_time: datetime) -> None: + """开始监视通用脚本日志""" + + logger.info(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() + + def quit_monitor(self) -> None: + """退出通用脚本日志监视进程""" + + if len(self.log_monitor.files()) != 0: + + logger.info(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() + + def set_sub(self, index: str = "") -> dict: + """配置通用脚本运行参数""" + logger.info(f"{self.name} | 配置脚本运行参数: {index}") + + # 配置前关闭可能未正常退出的脚本进程 + System.kill_process(self.script_exe_path) + + # 预导入配置文件 + if self.mode == "设置通用脚本": + if self.sub_config_path.exists(): + shutil.copytree( + self.sub_config_path, self.script_config_path, dirs_exist_ok=True + ) + else: + shutil.copytree( + self.data[index]["Path"] / "ConfigFiles", + self.script_config_path, + dirs_exist_ok=True, + ) + + def execute_script_task(self, script_path: Path, task_name: str) -> bool: + """执行脚本任务并等待结束""" + + try: + logger.info(f"{self.name} | 开始执行{task_name}: {script_path}") + + # 根据文件类型选择执行方式 + if script_path.suffix.lower() == ".py": + cmd = [sys.executable, script_path] + 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}脚本没有指定后缀名,无法执行") + return False + else: + # 使用系统默认程序打开 + os.startfile(str(script_path)) + return True + + # 执行脚本并等待结束 + result = subprocess.run( + cmd, + cwd=script_path.parent, + stdin=subprocess.DEVNULL, + creationflags=( + subprocess.CREATE_NO_WINDOW + if Config.get(Config.function_IfSilence) + else 0 + ), + timeout=600, + capture_output=True, + errors="ignore", + ) + + if result.returncode == 0: + logger.info(f"{self.name} | {task_name}执行成功") + if result.stdout.strip(): + logger.info(f"{self.name} | {task_name}输出: {result.stdout}") + return True + else: + logger.error( + f"{self.name} | {task_name}执行失败,返回码: {result.returncode}" + ) + if result.stderr.strip(): + logger.error(f"{self.name} | {task_name}错误输出: {result.stderr}") + return False + + except subprocess.TimeoutExpired: + logger.error(f"{self.name} | {task_name}执行超时") + return False + except Exception as e: + logger.exception(f"{self.name} | 执行{task_name}时出现异常: {e}") + return False + + def push_notification( + self, + mode: str, + title: str, + message: Union[str, dict], + sub_data: Dict[str, Dict[str, Union[str, int, bool]]] = None, + ) -> None: + """通过所有渠道推送通知""" + + env = Environment( + loader=FileSystemLoader(str(Config.app_path / "resources/html")) + ) + + if mode == "代理结果" and ( + Config.get(Config.notify_SendTaskResultTime) == "任何时刻" + or ( + Config.get(Config.notify_SendTaskResultTime) == "仅失败时" + and message["uncompleted_count"] != 0 + ) + ): + # 生成文本通知内容 + message_text = ( + f"任务开始时间:{message['start_time']},结束时间:{message['end_time']}\n" + f"已完成数:{message['completed_count']},未完成数:{message['uncompleted_count']}\n\n" + ) + + if len(message["failed_sub"]) > 0: + message_text += f"{self.mode[2:4]}未成功的配置:\n{"\n".join(message['failed_sub'])}\n" + if len(message["waiting_sub"]) > 0: + message_text += f"\n未开始{self.mode[2:4]}的配置:\n{"\n".join(message['waiting_sub'])}\n" + + # 生成HTML通知内容 + message["failed_sub"] = "、".join(message["failed_sub"]) + message["waiting_sub"] = "、".join(message["waiting_sub"]) + + template = env.get_template("general_result.html") + message_html = template.render(message) + + # ServerChan的换行是两个换行符。故而将\n替换为\n\n + serverchan_message = message_text.replace("\n", "\n\n") + + # 发送全局通知 + + if Config.get(Config.notify_IfSendMail): + Notify.send_mail( + "网页", title, message_html, Config.get(Config.notify_ToAddress) + ) + + if Config.get(Config.notify_IfServerChan): + Notify.ServerChanPush( + title, + f"{serverchan_message}\n\nAUTO_MAA 敬上", + Config.get(Config.notify_ServerChanKey), + Config.get(Config.notify_ServerChanTag), + Config.get(Config.notify_ServerChanChannel), + ) + + if Config.get(Config.notify_IfCompanyWebHookBot): + Notify.CompanyWebHookBotPush( + title, + f"{message_text}\n\nAUTO_MAA 敬上", + Config.get(Config.notify_CompanyWebHookBotUrl), + ) + + elif mode == "统计信息": + + message_text = ( + f"开始时间: {message['start_time']}\n" + f"结束时间: {message['end_time']}\n" + f"通用脚本执行结果: {message['sub_result']}\n\n" + ) + + # 生成HTML通知内容 + template = env.get_template("general_statistics.html") + message_html = template.render(message) + + # ServerChan的换行是两个换行符。故而将\n替换为\n\n + serverchan_message = message_text.replace("\n", "\n\n") + + # 发送全局通知 + if Config.get(Config.notify_IfSendStatistic): + + if Config.get(Config.notify_IfSendMail): + Notify.send_mail( + "网页", title, message_html, Config.get(Config.notify_ToAddress) + ) + + if Config.get(Config.notify_IfServerChan): + Notify.ServerChanPush( + title, + f"{serverchan_message}\n\nAUTO_MAA 敬上", + Config.get(Config.notify_ServerChanKey), + Config.get(Config.notify_ServerChanTag), + Config.get(Config.notify_ServerChanChannel), + ) + + if Config.get(Config.notify_IfCompanyWebHookBot): + Notify.CompanyWebHookBotPush( + title, + f"{message_text}\n\nAUTO_MAA 敬上", + Config.get(Config.notify_CompanyWebHookBotUrl), + ) + + # 发送用户单独通知 + if sub_data["Notify"]["Enabled"] and sub_data["Notify"]["IfSendStatistic"]: + + # 发送邮件通知 + if sub_data["Notify"]["IfSendMail"]: + if sub_data["Notify"]["ToAddress"]: + Notify.send_mail( + "网页", + title, + message_html, + sub_data["Notify"]["ToAddress"], + ) + else: + logger.error( + f"{self.name} | 用户邮箱地址为空,无法发送用户单独的邮件通知" + ) + + # 发送ServerChan通知 + if sub_data["Notify"]["IfServerChan"]: + if sub_data["Notify"]["ServerChanKey"]: + Notify.ServerChanPush( + title, + f"{serverchan_message}\n\nAUTO_MAA 敬上", + sub_data["Notify"]["ServerChanKey"], + sub_data["Notify"]["ServerChanTag"], + sub_data["Notify"]["ServerChanChannel"], + ) + else: + logger.error( + f"{self.name} |用户ServerChan密钥为空,无法发送用户单独的ServerChan通知" + ) + + # 推送CompanyWebHookBot通知 + if sub_data["Notify"]["IfCompanyWebHookBot"]: + if sub_data["Notify"]["CompanyWebHookBotUrl"]: + Notify.CompanyWebHookBotPush( + title, + f"{message_text}\n\nAUTO_MAA 敬上", + sub_data["Notify"]["CompanyWebHookBotUrl"], + ) + else: + logger.error( + f"{self.name} |用户CompanyWebHookBot密钥为空,无法发送用户单独的CompanyWebHookBot通知" + ) + + return None diff --git a/app/services/system.py b/app/services/system.py index 0f09e96..d0f2e34 100644 --- a/app/services/system.py +++ b/app/services/system.py @@ -61,27 +61,67 @@ class _SystemHandler: # 恢复系统电源状态 ctypes.windll.kernel32.SetThreadExecutionState(self.ES_CONTINUOUS) - def set_SelfStart(self) -> None: + def set_SelfStart(self) -> bool: """同步开机自启""" if Config.get(Config.start_IfSelfStart) and not self.is_startup(): - key = winreg.OpenKey( - winreg.HKEY_CURRENT_USER, - r"Software\Microsoft\Windows\CurrentVersion\Run", - winreg.KEY_SET_VALUE, - winreg.KEY_ALL_ACCESS | winreg.KEY_WRITE | winreg.KEY_CREATE_SUB_KEY, - ) - winreg.SetValueEx(key, "AUTO_MAA", 0, winreg.REG_SZ, Config.app_path_sys) - winreg.CloseKey(key) + + try: + + # 创建任务计划 + result = subprocess.run( + [ + "schtasks", + "/create", + "/tn", + "AUTO_MAA_AutoStart", + "/tr", + Config.app_path_sys, + "/sc", + "onlogon", + "/rl", + "highest", # 以最高权限运行 + "/f", # 强制创建(覆盖现有任务) + ], + creationflags=subprocess.CREATE_NO_WINDOW, + stdin=subprocess.DEVNULL, + capture_output=True, + text=True, + ) + + if result.returncode == 0: + logger.info(f"任务计划程序自启动已创建: {Config.app_path_sys}") + return True + else: + logger.error(f"创建任务计划失败: {result.stderr}") + return False + + except Exception as e: + logger.error(f"设置任务计划程序自启动失败: {e}") + return False + elif not Config.get(Config.start_IfSelfStart) and self.is_startup(): - key = winreg.OpenKey( - winreg.HKEY_CURRENT_USER, - r"Software\Microsoft\Windows\CurrentVersion\Run", - winreg.KEY_SET_VALUE, - winreg.KEY_ALL_ACCESS | winreg.KEY_WRITE | winreg.KEY_CREATE_SUB_KEY, - ) - winreg.DeleteValue(key, "AUTO_MAA") - winreg.CloseKey(key) + + try: + + result = subprocess.run( + ["schtasks", "/delete", "/tn", "AUTO_MAA_AutoStart", "/f"], + creationflags=subprocess.CREATE_NO_WINDOW, + stdin=subprocess.DEVNULL, + capture_output=True, + text=True, + ) + + if result.returncode == 0: + logger.info("任务计划程序自启动已删除") + return True + else: + logger.error(f"删除任务计划失败: {result.stderr}") + return False + + except Exception as e: + logger.error(f"删除任务计划程序自启动失败: {e}") + return False def set_power(self, mode) -> None: @@ -144,19 +184,17 @@ class _SystemHandler: def is_startup(self) -> bool: """判断程序是否已经开机自启""" - key = winreg.OpenKey( - winreg.HKEY_CURRENT_USER, - r"Software\Microsoft\Windows\CurrentVersion\Run", - 0, - winreg.KEY_READ, - ) - try: - value, _ = winreg.QueryValueEx(key, "AUTO_MAA") - winreg.CloseKey(key) - return True - except FileNotFoundError: - winreg.CloseKey(key) + result = subprocess.run( + ["schtasks", "/query", "/tn", "AUTO_MAA_AutoStart"], + creationflags=subprocess.CREATE_NO_WINDOW, + stdin=subprocess.DEVNULL, + capture_output=True, + text=True, + ) + return result.returncode == 0 + except Exception as e: + logger.error(f"检查任务计划程序失败: {e}") return False def get_window_info(self) -> list: diff --git a/app/ui/Widget.py b/app/ui/Widget.py index 3f25e88..5fc7c9f 100644 --- a/app/ui/Widget.py +++ b/app/ui/Widget.py @@ -33,6 +33,8 @@ v4.3 import os import re +import win32com.client +from pathlib import Path from datetime import datetime from functools import partial from typing import Optional, Union, List, Dict @@ -48,6 +50,7 @@ from PySide6.QtWidgets import ( QHBoxLayout, QVBoxLayout, QSizePolicy, + QFileDialog, ) from qfluentwidgets import ( LineEdit, @@ -566,6 +569,73 @@ class PasswordLineEditSettingCard(SettingCard): self.LineEdit.textChanged.connect(self.__textChanged) +class PathSettingCard(PushSettingCard): + + pathChanged = Signal(Path, Path) + + def __init__( + self, + icon: Union[str, QIcon, FluentIconBase], + title: str, + mode: str, + text: str, + qconfig: QConfig, + configItem: ConfigItem, + parent=None, + ): + super().__init__(text, icon, title, "未设置", parent) + + self.title = title + self.mode = mode + self.qconfig = qconfig + self.configItem = configItem + + self.setContent(self.qconfig.get(self.configItem)) + + self.clicked.connect(self.ChoosePath) + self.configItem.valueChanged.connect( + lambda: self.setContent(self.qconfig.get(self.configItem)) + ) + + def ChoosePath(self): + """选择文件或文件夹路径""" + + old_path = Path(self.qconfig.get(self.configItem)) + + if self.mode == "文件夹": + + folder = QFileDialog.getExistingDirectory( + self, "选择文件夹", self.qconfig.get(self.configItem) + ) + if folder: + self.qconfig.set(self.configItem, folder) + self.pathChanged.emit(old_path, Path(folder)) + + else: + + file_path, _ = QFileDialog.getOpenFileName( + self, "打开文件", self.qconfig.get(self.configItem), self.mode + ) + if file_path: + file_path = self.analysis_lnk(file_path) + self.qconfig.set(self.configItem, str(file_path)) + self.pathChanged.emit(old_path, file_path) + + def analysis_lnk(self, path: str) -> Path: + """快捷方式解析""" + + lnk_path = Path(path) + if lnk_path.suffix == ".lnk": + try: + shell = win32com.client.Dispatch("WScript.Shell") + shortcut = shell.CreateShortcut(str(lnk_path)) + return Path(shortcut.TargetPath) + except Exception as e: + return lnk_path + else: + return lnk_path + + class PushAndSwitchButtonSettingCard(SettingCard): """Setting card with push & switch button""" @@ -1163,6 +1233,48 @@ class TimeEditSettingCard(SettingCard): self.TimeEdit.setTime(QTime.fromString(value, "HH:mm")) +class SubLableSettingCard(SettingCard): + """Setting card with Sub's Lable""" + + def __init__( + self, + icon: Union[str, QIcon, FluentIconBase], + title: str, + content: Union[str, None], + qconfig: QConfig, + configItems: Dict[str, ConfigItem], + parent=None, + ): + + super().__init__(icon, title, content, parent) + self.qconfig = qconfig + self.configItems = configItems + self.Lable = SubtitleLabel(self) + + if configItems: + for configItem in configItems.values(): + configItem.valueChanged.connect(self.setValue) + self.setValue() + + self.hBoxLayout.addWidget(self.Lable, 0, Qt.AlignRight) + self.hBoxLayout.addSpacing(16) + + def setValue(self): + + text_list = [] + + if self.configItems: + + text_list.append( + f"今日已代理{self.qconfig.get(self.configItems["ProxyTimes"])}次" + if Config.server_date().strftime("%Y-%m-%d") + == self.qconfig.get(self.configItems["LastProxyDate"]) + else "今日未进行代理" + ) + + self.Lable.setText(" | ".join(text_list)) + + class UserLableSettingCard(SettingCard): """Setting card with User's Lable""" @@ -1341,13 +1453,18 @@ class UserNoticeSettingCard(PushAndSwitchButtonSettingCard): if not ( self.qconfig.get(self.configItems["IfSendStatistic"]) - or self.qconfig.get(self.configItems["IfSendSixStar"]) + or ( + "IfSendSixStar" in self.configItems + and self.qconfig.get(self.configItems["IfSendSixStar"]) + ) ): text_list.append("未启用任何通知项") if self.qconfig.get(self.configItems["IfSendStatistic"]): text_list.append("统计信息已启用") - if self.qconfig.get(self.configItems["IfSendSixStar"]): + if "IfSendSixStar" in self.configItems and self.qconfig.get( + self.configItems["IfSendSixStar"] + ): text_list.append("六星喜报已启用") if self.qconfig.get(self.configItems["IfSendMail"]): diff --git a/app/ui/dispatch_center.py b/app/ui/dispatch_center.py index d97e6eb..5f26d6f 100644 --- a/app/ui/dispatch_center.py +++ b/app/ui/dispatch_center.py @@ -210,8 +210,8 @@ class DispatchCenter(QWidget): self.script_list["主调度台"].top_bar.object.addItem( ( f"实例 - {info['Type']}" - if info["Config"].get(info["Config"].MaaSet_Name) == "" - else f"实例 - {info['Type']} - {info["Config"].get(info["Config"].MaaSet_Name)}" + if info["Config"].get_name() == "" + else f"实例 - {info['Type']} - {info['Config'].get_name()}" ), userData=name, ) @@ -284,8 +284,8 @@ class DispatchCenter(QWidget): continue text_list.append( f"实例 - {info['Type']}" - if info["Config"].get(info["Config"].MaaSet_Name) == "" - else f"实例 - {info['Type']} - {info["Config"].get(info["Config"].MaaSet_Name)}" + if info["Config"].get_name() == "" + else f"实例 - {info['Type']} - {info['Config'].get_name()}" ) data_list.append(name) @@ -317,14 +317,12 @@ class DispatchCenter(QWidget): elif "脚本" in choice.input[0].currentData(): - if Config.member_dict[choice.input[0].currentData()]["Type"] == "Maa": - - logger.info(f"用户添加任务:{choice.input[0].currentData()}") - TaskManager.add_task( - "自动代理_新调度台", - f"自定义队列 - {choice.input[0].currentData()}", - {"Queue": {"Member_1": choice.input[0].currentData()}}, - ) + logger.info(f"用户添加任务:{choice.input[0].currentData()}") + TaskManager.add_task( + "自动代理_新调度台", + f"自定义队列 - {choice.input[0].currentData()}", + {"Queue": {"Member_1": choice.input[0].currentData()}}, + ) class DispatchBox(QWidget): @@ -409,6 +407,18 @@ class DispatchCenter(QWidget): ) return None + if ( + "脚本" in self.object.currentData() + and Config.member_dict[self.object.currentData()]["Type"] + == "General" + and self.mode.currentData() == "人工排查" + ): + logger.warning("通用脚本类型不存在人工排查功能") + MainInfoBar.push_info_bar( + "warning", "不支持的任务", "通用脚本无人工排查功能", 5000 + ) + return None + if "调度队列" in self.object.currentData(): logger.info(f"用户添加任务:{self.object.currentData()}") @@ -420,14 +430,12 @@ class DispatchCenter(QWidget): elif "脚本" in self.object.currentData(): - if Config.member_dict[self.object.currentData()]["Type"] == "Maa": - - logger.info(f"用户添加任务:{self.object.currentData()}") - TaskManager.add_task( - f"{self.mode.currentText()}_主调度台", - "自定义队列", - {"Queue": {"Member_1": self.object.currentData()}}, - ) + logger.info(f"用户添加任务:{self.object.currentData()}") + TaskManager.add_task( + f"{self.mode.currentText()}_主调度台", + "自定义队列", + {"Queue": {"Member_1": self.object.currentData()}}, + ) class DispatchInfoCard(HeaderCardWidget): diff --git a/app/ui/main_window.py b/app/ui/main_window.py index 9069d82..6064b8e 100644 --- a/app/ui/main_window.py +++ b/app/ui/main_window.py @@ -184,7 +184,7 @@ class AUTO_MAA(MSFluentWindow): self.set_min_method() - Config.user_info_changed.connect(self.member_manager.refresh_dashboard) + 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) TaskManager.connect_gui.connect(self.dispatch_center.connect_main_board) diff --git a/app/ui/member_manager.py b/app/ui/member_manager.py index 8942848..66120dc 100644 --- a/app/ui/member_manager.py +++ b/app/ui/member_manager.py @@ -35,9 +35,10 @@ from PySide6.QtWidgets import ( QTableWidgetItem, QHeaderView, ) -from PySide6.QtGui import QIcon +from PySide6.QtGui import QIcon, Qt from qfluentwidgets import ( Action, + ConfigItem, ScrollArea, FluentIcon, MessageBox, @@ -54,7 +55,7 @@ from PySide6.QtCore import Signal from datetime import datetime from functools import partial from pathlib import Path -from typing import List +from typing import List, Union, Type import shutil import json @@ -64,6 +65,8 @@ from app.core import ( TaskManager, MaaConfig, MaaUserConfig, + GeneralConfig, + GeneralSubConfig, Network, SoundPlayer, ) @@ -83,8 +86,10 @@ from .Widget import ( PasswordLineAndSwitchButtonSettingCard, UserLableSettingCard, UserTaskSettingCard, + SubLableSettingCard, ComboBoxSettingCard, SwitchSettingCard, + PathSettingCard, PushAndSwitchButtonSettingCard, PushAndComboBoxSettingCard, StatusSwitchSetting, @@ -158,7 +163,7 @@ class MemberManager(QWidget): self.window(), "选择一个脚本类型以添加相应脚本实例", ["选择脚本类型"], - [["MAA"]], + [["MAA", "通用"]], ) if choice.exec() and choice.input[0].currentIndex() != -1: @@ -183,12 +188,46 @@ class MemberManager(QWidget): "UserData": {}, } - self.member_manager.add_MaaSettingBox(index) + self.member_manager.add_SettingBox( + index, self.MemberSettingBox.MaaSettingBox + ) self.member_manager.switch_SettingBox(index) - logger.success(f"脚本实例 脚本_{index} 添加成功") + logger.success(f"MAA实例 脚本_{index} 添加成功") MainInfoBar.push_info_bar( - "success", "操作成功", f"添加脚本实例 脚本_{index}", 3000 + "success", "操作成功", f"添加 MAA 实例 脚本_{index}", 3000 + ) + SoundPlayer.play("添加脚本实例") + + elif choice.input[0].currentText() == "通用": + + index = len(Config.member_dict) + 1 + + general_config = GeneralConfig() + general_config.load( + Config.app_path / f"config/GeneralConfig/脚本_{index}/config.json", + general_config, + ) + general_config.save() + (Config.app_path / f"config/GeneralConfig/脚本_{index}/SubData").mkdir( + parents=True, exist_ok=True + ) + + Config.member_dict[f"脚本_{index}"] = { + "Type": "General", + "Path": Config.app_path / f"config/GeneralConfig/脚本_{index}", + "Config": general_config, + "SubData": {}, + } + + self.member_manager.add_SettingBox( + index, self.MemberSettingBox.GeneralSettingBox + ) + self.member_manager.switch_SettingBox(index) + + logger.success(f"通用实例 脚本_{index} 添加成功") + MainInfoBar.push_info_bar( + "success", "操作成功", f"添加通用实例 脚本_{index}", 3000 ) SoundPlayer.play("添加脚本实例") @@ -268,11 +307,11 @@ class MemberManager(QWidget): ) Config.change_queue(name, "脚本_0") Config.member_dict[f"脚本_{index-1}"]["Path"].rename( - Config.member_dict[name]["Path"] + Config.member_dict[f"脚本_{index-1}"]["Path"].with_name(name) ) Config.change_queue(f"脚本_{index-1}", name) Config.member_dict[name]["Path"].with_name("脚本_0").rename( - Config.member_dict[f"脚本_{index-1}"]["Path"] + Config.member_dict[name]["Path"].with_name(f"脚本_{index-1}") ) Config.change_queue("脚本_0", f"脚本_{index-1}") @@ -316,11 +355,11 @@ class MemberManager(QWidget): ) Config.change_queue(name, "脚本_0") Config.member_dict[f"脚本_{index+1}"]["Path"].rename( - Config.member_dict[name]["Path"] + Config.member_dict[f"脚本_{index+1}"]["Path"].with_name(name) ) Config.change_queue(f"脚本_{index+1}", name) Config.member_dict[name]["Path"].with_name("脚本_0").rename( - Config.member_dict[f"脚本_{index+1}"]["Path"] + Config.member_dict[name]["Path"].with_name(f"脚本_{index+1}") ) Config.change_queue("脚本_0", f"脚本_{index+1}") @@ -521,12 +560,14 @@ class MemberManager(QWidget): self.refresh_plan_info() def refresh_dashboard(self): - """刷新所有脚本实例的用户仪表盘""" + """刷新所有脚本实例的仪表盘""" for member in self.member_manager.script_list: if isinstance(member, MemberManager.MemberSettingBox.MaaSettingBox): member.user_setting.user_manager.user_dashboard.load_info() + elif isinstance(member, MemberManager.MemberSettingBox.GeneralSettingBox): + member.branch_manager.sub_manager.sub_dashboard.load_info() def refresh_plan_info(self): """刷新所有计划信息""" @@ -554,7 +595,12 @@ class MemberManager(QWidget): self.stackedWidget.setContentsMargins(0, 0, 0, 0) self.stackedWidget.setStyleSheet("background: transparent; border: none;") - self.script_list: List[MemberManager.MemberSettingBox.MaaSettingBox] = [] + self.script_list: List[ + Union[ + MemberManager.MemberSettingBox.MaaSettingBox, + MemberManager.MemberSettingBox.GeneralSettingBox, + ] + ] = [] self.Layout = QVBoxLayout(self) self.Layout.addWidget(self.pivotArea) @@ -576,7 +622,9 @@ class MemberManager(QWidget): for name, info in Config.member_dict.items(): if info["Type"] == "Maa": - self.add_MaaSettingBox(int(name[3:])) + self.add_SettingBox(int(name[3:]), self.MaaSettingBox) + elif info["Type"] == "General": + self.add_SettingBox(int(name[3:]), self.GeneralSettingBox) self.switch_SettingBox(index) @@ -592,9 +640,21 @@ class MemberManager(QWidget): if if_chang_pivot: self.pivot.setCurrentItem(self.script_list[index - 1].objectName()) self.stackedWidget.setCurrentWidget(self.script_list[index - 1]) - self.script_list[index - 1].user_setting.user_manager.switch_SettingBox( - "用户仪表盘" - ) + + if isinstance( + self.script_list[index - 1], + MemberManager.MemberSettingBox.MaaSettingBox, + ): + self.script_list[index - 1].user_setting.user_manager.switch_SettingBox( + "用户仪表盘" + ) + elif isinstance( + self.script_list[index - 1], + MemberManager.MemberSettingBox.GeneralSettingBox, + ): + self.script_list[ + index - 1 + ].branch_manager.sub_manager.switch_SettingBox("配置仪表盘") def clear_SettingBox(self) -> None: """清空所有子界面""" @@ -605,15 +665,18 @@ class MemberManager(QWidget): self.script_list.clear() self.pivot.clear() - def add_MaaSettingBox(self, uid: int) -> None: - """添加一个MAA设置界面""" + def add_SettingBox(self, uid: int, type: Type) -> None: + """添加指定类型设置子界面""" - maa_setting_box = self.MaaSettingBox(uid, self) - - self.script_list.append(maa_setting_box) + if type == self.MaaSettingBox: + setting_box = self.MaaSettingBox(uid, self) + elif type == self.GeneralSettingBox: + setting_box = self.GeneralSettingBox(uid, self) + else: + return None + self.script_list.append(setting_box) self.stackedWidget.addWidget(self.script_list[-1]) - self.pivot.addItem(routeKey=f"脚本_{uid}", text=f"脚本 {uid}") class MaaSettingBox(QWidget): @@ -1171,11 +1234,9 @@ class MemberManager(QWidget): def add_userSettingBox(self, uid: int) -> None: """添加一个用户设置界面""" - maa_setting_box = self.UserMemberSettingBox( - self.name, uid, self - ) + setting_box = self.UserMemberSettingBox(self.name, uid, self) - self.script_list.append(maa_setting_box) + self.script_list.append(setting_box) self.stackedWidget.addWidget(self.script_list[-1]) @@ -2154,3 +2215,1136 @@ class MemberManager(QWidget): self.viewLayout.addLayout(Layout) self.viewLayout.setSpacing(3) self.viewLayout.setContentsMargins(3, 0, 3, 3) + + class GeneralSettingBox(QWidget): + """通用脚本设置界面""" + + def __init__(self, uid: int, parent=None): + super().__init__(parent) + + self.setObjectName(f"脚本_{uid}") + self.config = Config.member_dict[f"脚本_{uid}"]["Config"] + + self.app_setting = self.AppSettingCard(f"脚本_{uid}", self.config, self) + self.branch_manager = self.BranchManager(f"脚本_{uid}", self) + + content_widget = QWidget() + content_layout = QVBoxLayout(content_widget) + content_layout.setContentsMargins(0, 0, 11, 0) + content_layout.addWidget(self.app_setting) + content_layout.addWidget(self.branch_manager) + content_layout.addStretch(1) + + scrollArea = ScrollArea() + scrollArea.setWidgetResizable(True) + scrollArea.setContentsMargins(0, 0, 0, 0) + scrollArea.setStyleSheet("background: transparent; border: none;") + scrollArea.setWidget(content_widget) + + layout = QVBoxLayout(self) + layout.addWidget(scrollArea) + + class AppSettingCard(HeaderCardWidget): + + def __init__(self, name: str, config: GeneralConfig, parent=None): + super().__init__(parent) + + self.setTitle("通用实例") + + self.name = name + self.config = config + + Layout = QVBoxLayout() + + self.card_Name = LineEditSettingCard( + icon=FluentIcon.EDIT, + title="实例名称", + content="用于标识通用实例的名称", + text="请输入实例名称", + qconfig=self.config, + configItem=self.config.Script_Name, + parent=self, + ) + self.card_Script = self.ScriptSettingCard(self.config, self) + self.card_Game = self.GameSettingCard(self.config, self) + self.card_Run = self.RunSettingCard(self.config, self) + + Layout.addWidget(self.card_Name) + Layout.addWidget(self.card_Script) + Layout.addWidget(self.card_Game) + Layout.addWidget(self.card_Run) + self.viewLayout.addLayout(Layout) + + class ScriptSettingCard(ExpandGroupSettingCard): + + def __init__(self, config: GeneralConfig, parent=None): + super().__init__( + FluentIcon.SETTING, "脚本设置", "脚本属性配置选项", parent + ) + self.config = config + + self.card_RootPath = PathSettingCard( + icon=FluentIcon.FOLDER, + title="脚本根目录 - [必填]", + mode="文件夹", + text="选择文件夹", + qconfig=self.config, + configItem=self.config.Script_RootPath, + parent=self, + ) + self.card_ScriptPath = PathSettingCard( + icon=FluentIcon.FOLDER, + title="脚本路径 - [必填]", + mode="可执行文件 (*.exe *.bat)", + text="选择程序", + qconfig=self.config, + configItem=self.config.Script_ScriptPath, + parent=self, + ) + self.card_Arguments = LineEditSettingCard( + icon=FluentIcon.PAGE_RIGHT, + title="脚本启动参数", + content="脚本启动时的附加参数", + text="请输入脚本参数", + qconfig=self.config, + configItem=self.config.Script_Arguments, + parent=self, + ) + self.card_ConfigPath = PathSettingCard( + icon=FluentIcon.FOLDER, + title="脚本配置文件目录 - [必填]", + mode="文件夹", + text="选择文件夹", + qconfig=self.config, + configItem=self.config.Script_ConfigPath, + parent=self, + ) + self.card_LogPath = PathSettingCard( + icon=FluentIcon.FOLDER, + title="脚本日志文件目录 - [必填]", + mode="所有文件 (*)", + text="选择文件", + qconfig=self.config, + configItem=self.config.Script_LogPath, + parent=self, + ) + self.card_LogTimeStart = SpinBoxSettingCard( + icon=FluentIcon.PAGE_RIGHT, + title="脚本日志时间起始位置 - [必填]", + content="脚本日志中时间的起始位置,单位为字符", + range=(0, 1024), + qconfig=self.config, + configItem=self.config.Script_LogTimeStart, + parent=self, + ) + self.card_LogTimeEnd = SpinBoxSettingCard( + icon=FluentIcon.PAGE_RIGHT, + title="脚本日志时间结束位置 - [必填]", + content="脚本日志中时间的结束位置,单位为字符", + range=(0, 1024), + qconfig=self.config, + configItem=self.config.Script_LogTimeEnd, + parent=self, + ) + self.card_LogTimeFormat = LineEditSettingCard( + icon=FluentIcon.PAGE_RIGHT, + title="脚本日志时间格式 - [必填]", + content="脚本日志中时间的格式", + text="请输入脚本日志时间格式", + qconfig=self.config, + configItem=self.config.Script_LogTimeFormat, + parent=self, + ) + self.card_SuccessLog = LineEditSettingCard( + icon=FluentIcon.PAGE_RIGHT, + title="脚本成功日志", + content="任务成功完成时出现的日志,多条请使用“|”隔开", + text="请输入脚本成功日志内容", + qconfig=self.config, + configItem=self.config.Script_SuccessLog, + parent=self, + ) + self.card_ErrorLog = LineEditSettingCard( + icon=FluentIcon.PAGE_RIGHT, + title="脚本异常日志 - [必填]", + content="脚本运行异常时的日志内容,多条请使用“|”隔开", + text="请输入脚本异常日志内容", + qconfig=self.config, + configItem=self.config.Script_ErrorLog, + parent=self, + ) + + self.card_RootPath.pathChanged.connect(self.change_path) + self.card_ScriptPath.pathChanged.connect( + lambda old, new: self.check_path( + self.config.Script_ScriptPath, old, new + ) + ) + self.card_ConfigPath.pathChanged.connect( + lambda old, new: self.check_path( + self.config.Script_ConfigPath, old, new + ) + ) + self.card_LogPath.pathChanged.connect( + lambda old, new: self.check_path( + self.config.Script_LogPath, old, new + ) + ) + + h_layout = QHBoxLayout() + h_layout.addWidget(self.card_LogTimeStart) + h_layout.addWidget(self.card_LogTimeEnd) + + widget = QWidget() + Layout = QVBoxLayout(widget) + Layout.addWidget(self.card_RootPath) + Layout.addWidget(self.card_ScriptPath) + Layout.addWidget(self.card_Arguments) + Layout.addWidget(self.card_ConfigPath) + Layout.addWidget(self.card_LogPath) + Layout.addLayout(h_layout) + Layout.addWidget(self.card_LogTimeFormat) + Layout.addWidget(self.card_SuccessLog) + Layout.addWidget(self.card_ErrorLog) + self.viewLayout.setContentsMargins(0, 0, 0, 0) + self.viewLayout.setSpacing(0) + self.addGroupWidget(widget) + + def change_path(self, old_path: Path, new_path: Path) -> None: + """根据脚本根目录重新计算配置文件路径""" + + path_list = [ + self.config.Script_ScriptPath, + self.config.Script_ConfigPath, + self.config.Script_LogPath, + ] + + for path in path_list: + + if Path(self.config.get(path)).is_relative_to(old_path): + + relative_path = Path(self.config.get(path)).relative_to( + old_path + ) + self.config.set(path, str(new_path / relative_path)) + + def check_path( + self, configItem: ConfigItem, old_path: Path, new_path: Path + ) -> None: + """检查配置路径是否合法""" + + if not new_path.is_relative_to( + Path(self.config.get(self.config.Script_RootPath)) + ): + + self.config.set(configItem, str(old_path)) + logger.warning( + f"配置路径 {new_path} 不在脚本根目录下,已重置为 {old_path}" + ) + MainInfoBar.push_info_bar( + "warning", "路径异常", "所选路径不在脚本根目录下", 5000 + ) + + class GameSettingCard(ExpandGroupSettingCard): + + def __init__(self, config: GeneralConfig, parent=None): + super().__init__( + FluentIcon.SETTING, + "游戏设置", + "游戏/模拟器属性配置选项", + parent, + ) + self.config = config + + self.card_Enabled = SwitchSettingCard( + icon=FluentIcon.PAGE_RIGHT, + title="游戏/模拟器相关功能", + content="是否由AUTO_MAA管理游戏/模拟器相关进程", + qconfig=self.config, + configItem=self.config.Game_Enabled, + parent=self, + ) + self.card_Style = ComboBoxSettingCard( + icon=FluentIcon.PAGE_RIGHT, + title="游戏平台类型", + content="游戏运行在安卓模拟器还是客户端上", + texts=["安卓模拟器", "客户端"], + qconfig=self.config, + configItem=self.config.Game_Style, + parent=self, + ) + self.card_Path = PathSettingCard( + icon=FluentIcon.FOLDER, + title="游戏/模拟器路径", + mode="可执行文件 (*.exe *.bat)", + text="选择文件", + qconfig=self.config, + configItem=self.config.Game_Path, + parent=self, + ) + self.card_Arguments = LineEditSettingCard( + icon=FluentIcon.PAGE_RIGHT, + title="游戏/模拟器启动参数", + content="游戏/模拟器启动时的附加参数", + text="请输入游戏/模拟器参数", + qconfig=self.config, + configItem=self.config.Game_Arguments, + parent=self, + ) + self.card_WaitTime = SpinBoxSettingCard( + icon=FluentIcon.PAGE_RIGHT, + title="等待游戏/模拟器启动时间", + content="启动游戏/模拟器与启动对应脚本的间隔时间,单位为秒", + range=(0, 1024), + qconfig=self.config, + configItem=self.config.Game_WaitTime, + parent=self, + ) + self.card_IfForceClose = SwitchSettingCard( + icon=FluentIcon.PAGE_RIGHT, + title="游戏/模拟器强制关闭", + content="是否强制结束所有同路径进程", + qconfig=self.config, + configItem=self.config.Game_IfForceClose, + parent=self, + ) + + widget = QWidget() + Layout = QVBoxLayout(widget) + Layout.addWidget(self.card_Enabled) + Layout.addWidget(self.card_Style) + Layout.addWidget(self.card_Path) + Layout.addWidget(self.card_Arguments) + Layout.addWidget(self.card_WaitTime) + Layout.addWidget(self.card_IfForceClose) + self.viewLayout.setContentsMargins(0, 0, 0, 0) + self.viewLayout.setSpacing(0) + self.addGroupWidget(widget) + + class RunSettingCard(ExpandGroupSettingCard): + + def __init__(self, config: GeneralConfig, parent=None): + super().__init__( + FluentIcon.SETTING, "运行设置", "运行调控配置选项", parent + ) + self.config = config + + self.card_ProxyTimesLimit = SpinBoxSettingCard( + icon=FluentIcon.PAGE_RIGHT, + title="子配置单日代理次数上限", + content="当子配置本日代理成功次数达到该阈值时跳过代理,阈值为“0”时视为无代理次数上限", + range=(0, 1024), + qconfig=self.config, + configItem=self.config.Run_ProxyTimesLimit, + parent=self, + ) + + self.card_RunTimesLimit = SpinBoxSettingCard( + icon=FluentIcon.PAGE_RIGHT, + title="代理重试次数限制", + content="若超过该次数限制仍未完成代理,视为代理失败", + range=(1, 1024), + qconfig=self.config, + configItem=self.config.Run_RunTimesLimit, + parent=self, + ) + self.card_RunTimeLimit = SpinBoxSettingCard( + icon=FluentIcon.PAGE_RIGHT, + title="自动代理超时限制", + content="脚本日志无变化时间超过该阈值视为超时,单位为分钟", + range=(1, 1024), + qconfig=self.config, + configItem=self.config.Run_RunTimeLimit, + parent=self, + ) + + widget = QWidget() + Layout = QVBoxLayout(widget) + Layout.addWidget(self.card_ProxyTimesLimit) + Layout.addWidget(self.card_RunTimesLimit) + Layout.addWidget(self.card_RunTimeLimit) + self.viewLayout.setContentsMargins(0, 0, 0, 0) + self.viewLayout.setSpacing(0) + self.addGroupWidget(widget) + + class BranchManager(HeaderCardWidget): + """分支管理父页面""" + + def __init__(self, name: str, parent=None): + super().__init__(parent) + + self.setObjectName(f"{name}_分支管理") + self.setTitle("下属配置") + self.name = name + + self.tools = CommandBar() + self.sub_manager = self.SubConfigSettingBox(self.name, self) + + # 逐个添加动作 + self.tools.addActions( + [ + Action( + FluentIcon.ADD_TO, "新建配置", triggered=self.add_sub + ), + Action( + FluentIcon.REMOVE_FROM, + "删除配置", + triggered=self.del_sub, + ), + ] + ) + self.tools.addSeparator() + self.tools.addActions( + [ + Action( + FluentIcon.LEFT_ARROW, + "向前移动", + triggered=self.left_sub, + ), + Action( + FluentIcon.RIGHT_ARROW, + "向后移动", + triggered=self.right_sub, + ), + ] + ) + + layout = QVBoxLayout() + layout.addWidget(self.tools) + layout.addWidget(self.sub_manager) + self.viewLayout.addLayout(layout) + + def add_sub(self): + """添加一个配置""" + + index = len(Config.member_dict[self.name]["SubData"]) + 1 + + sub_config = GeneralSubConfig() + sub_config.load( + Config.member_dict[self.name]["Path"] + / f"SubData/配置_{index}/config.json", + sub_config, + ) + sub_config.save() + + Config.member_dict[self.name]["SubData"][f"配置_{index}"] = { + "Path": Config.member_dict[self.name]["Path"] + / f"SubData/配置_{index}", + "Config": sub_config, + } + + self.sub_manager.add_SettingBox(index) + self.sub_manager.switch_SettingBox(f"配置_{index}") + + logger.success(f"{self.name} 配置_{index} 添加成功") + MainInfoBar.push_info_bar( + "success", "操作成功", f"{self.name} 添加 配置_{index}", 3000 + ) + SoundPlayer.play("添加配置") + + def del_sub(self): + """删除一个配置""" + + name = self.sub_manager.pivot.currentRouteKey() + + if name is None: + logger.warning("未选择配置") + MainInfoBar.push_info_bar( + "warning", "未选择配置", "请先选择一个配置", 5000 + ) + return None + if name == "配置仪表盘": + logger.warning("试图删除配置仪表盘") + MainInfoBar.push_info_bar( + "warning", "未选择配置", "请勿尝试删除配置仪表盘", 5000 + ) + return None + + if self.name in Config.running_list: + logger.warning("所属脚本正在运行") + MainInfoBar.push_info_bar( + "warning", "所属脚本正在运行", "请先停止任务", 5000 + ) + return None + + choice = MessageBox( + "确认", f"确定要删除 {name} 吗?", self.window() + ) + if choice.exec(): + + self.sub_manager.clear_SettingBox() + + shutil.rmtree( + Config.member_dict[self.name]["SubData"][name]["Path"] + ) + for i in range( + int(name[3:]) + 1, + len(Config.member_dict[self.name]["SubData"]) + 1, + ): + if Config.member_dict[self.name]["SubData"][f"配置_{i}"][ + "Path" + ].exists(): + Config.member_dict[self.name]["SubData"][f"配置_{i}"][ + "Path" + ].rename( + Config.member_dict[self.name]["SubData"][ + f"配置_{i}" + ]["Path"].with_name(f"配置_{i-1}") + ) + + self.sub_manager.show_SettingBox( + f"配置_{max(int(name[3:]) - 1, 1)}" + ) + + logger.success(f"{self.name} {name} 删除成功") + MainInfoBar.push_info_bar( + "success", "操作成功", f"{self.name} 删除 {name}", 3000 + ) + SoundPlayer.play("删除配置") + + def left_sub(self): + """向前移动配置""" + + name = self.sub_manager.pivot.currentRouteKey() + + if name is None: + logger.warning("未选择配置") + MainInfoBar.push_info_bar( + "warning", "未选择配置", "请先选择一个配置", 5000 + ) + return None + if name == "配置仪表盘": + logger.warning("试图移动配置仪表盘") + MainInfoBar.push_info_bar( + "warning", "未选择配置", "请勿尝试移动配置仪表盘", 5000 + ) + return None + + index = int(name[3:]) + + if index == 1: + logger.warning("向前移动配置时已到达最左端") + MainInfoBar.push_info_bar( + "warning", "已经是第一个配置", "无法向前移动", 5000 + ) + return None + + if self.name in Config.running_list: + logger.warning("所属脚本正在运行") + MainInfoBar.push_info_bar( + "warning", "所属脚本正在运行", "请先停止任务", 5000 + ) + return None + + self.sub_manager.clear_SettingBox() + + Config.member_dict[self.name]["SubData"][name]["Path"].rename( + Config.member_dict[self.name]["SubData"][name][ + "Path" + ].with_name("配置_0") + ) + Config.member_dict[self.name]["SubData"][f"配置_{index-1}"][ + "Path" + ].rename(Config.member_dict[self.name]["SubData"][name]["Path"]) + Config.member_dict[self.name]["SubData"][name]["Path"].with_name( + "配置_0" + ).rename( + Config.member_dict[self.name]["SubData"][f"配置_{index-1}"][ + "Path" + ] + ) + + self.sub_manager.show_SettingBox(f"配置_{index - 1}") + + logger.success(f"{self.name} {name} 前移成功") + MainInfoBar.push_info_bar( + "success", "操作成功", f"{self.name} 前移 {name}", 3000 + ) + + def right_sub(self): + """向后移动配置""" + + name = self.sub_manager.pivot.currentRouteKey() + + if name is None: + logger.warning("未选择配置") + MainInfoBar.push_info_bar( + "warning", "未选择配置", "请先选择一个配置", 5000 + ) + return None + if name == "配置仪表盘": + logger.warning("试图删除配置仪表盘") + MainInfoBar.push_info_bar( + "warning", "未选择配置", "请勿尝试移动配置仪表盘", 5000 + ) + return None + + index = int(name[3:]) + + if index == len(Config.member_dict[self.name]["SubData"]): + logger.warning("向后移动配置时已到达最右端") + MainInfoBar.push_info_bar( + "warning", "已经是最后一个配置", "无法向后移动", 5000 + ) + return None + + if self.name in Config.running_list: + logger.warning("所属脚本正在运行") + MainInfoBar.push_info_bar( + "warning", "所属脚本正在运行", "请先停止任务", 5000 + ) + return None + + self.sub_manager.clear_SettingBox() + + Config.member_dict[self.name]["SubData"][name]["Path"].rename( + Config.member_dict[self.name]["SubData"][name][ + "Path" + ].with_name("配置_0") + ) + Config.member_dict[self.name]["SubData"][f"配置_{index+1}"][ + "Path" + ].rename(Config.member_dict[self.name]["SubData"][name]["Path"]) + Config.member_dict[self.name]["SubData"][name]["Path"].with_name( + "配置_0" + ).rename( + Config.member_dict[self.name]["SubData"][f"配置_{index+1}"][ + "Path" + ] + ) + + self.sub_manager.show_SettingBox(f"配置_{index + 1}") + + logger.success(f"{self.name} {name} 后移成功") + MainInfoBar.push_info_bar( + "success", "操作成功", f"{self.name} 后移 {name}", 3000 + ) + + class SubConfigSettingBox(QWidget): + """配置管理子页面组""" + + def __init__(self, name: str, parent=None): + super().__init__(parent) + + self.setObjectName("配置管理") + self.name = name + + 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: List[ + MemberManager.MemberSettingBox.GeneralSettingBox.BranchManager.SubConfigSettingBox.SubMemberSettingBox + ] = [] + + self.sub_dashboard = self.SubDashboard(self.name, self) + self.sub_dashboard.switch_to.connect(self.switch_SettingBox) + self.stackedWidget.addWidget(self.sub_dashboard) + self.pivot.addItem(routeKey="配置仪表盘", text="配置仪表盘") + + self.Layout = QVBoxLayout(self) + self.Layout.addWidget(self.pivotArea) + self.Layout.addWidget(self.stackedWidget) + self.Layout.setContentsMargins(0, 0, 0, 0) + + self.pivot.currentItemChanged.connect( + lambda index: self.switch_SettingBox( + index, if_change_pivot=False + ) + ) + + self.show_SettingBox("配置仪表盘") + + def show_SettingBox(self, index: str) -> None: + """加载所有子界面""" + + Config.search_general_sub(self.name) + + for name in Config.member_dict[self.name]["SubData"].keys(): + self.add_SettingBox(name[3:]) + + self.switch_SettingBox(index) + + def switch_SettingBox( + self, index: str, if_change_pivot: bool = True + ) -> None: + """切换到指定的子界面""" + + if len(Config.member_dict[self.name]["SubData"]) == 0: + index = "配置仪表盘" + + if index != "配置仪表盘" and int(index[3:]) > len( + Config.member_dict[self.name]["SubData"] + ): + return None + + if index == "配置仪表盘": + self.sub_dashboard.load_info() + + if if_change_pivot: + self.pivot.setCurrentItem(index) + self.stackedWidget.setCurrentWidget( + self.sub_dashboard + if index == "配置仪表盘" + else self.script_list[int(index[3:]) - 1] + ) + + def clear_SettingBox(self) -> None: + """清空所有子界面""" + + for sub_interface in self.script_list: + self.stackedWidget.removeWidget(sub_interface) + sub_interface.deleteLater() + self.script_list.clear() + self.pivot.clear() + self.sub_dashboard.dashboard.setRowCount(0) + self.stackedWidget.addWidget(self.sub_dashboard) + self.pivot.addItem(routeKey="配置仪表盘", text="配置仪表盘") + + def add_SettingBox(self, uid: int) -> None: + """添加一个配置设置界面""" + + setting_box = self.SubMemberSettingBox(self.name, uid, self) + + self.script_list.append(setting_box) + + self.stackedWidget.addWidget(self.script_list[-1]) + + self.pivot.addItem(routeKey=f"配置_{uid}", text=f"配置 {uid}") + + class SubDashboard(HeaderCardWidget): + """配置仪表盘页面""" + + switch_to = Signal(str) + + def __init__(self, name: str, parent=None): + super().__init__(parent) + self.setObjectName("配置仪表盘") + self.setTitle("配置仪表盘") + self.name = name + + self.dashboard = TableWidget(self) + self.dashboard.setColumnCount(5) + self.dashboard.setHorizontalHeaderLabels( + ["配置名", "状态", "代理情况", "备注", "详"] + ) + self.dashboard.setEditTriggers(TableWidget.NoEditTriggers) + self.dashboard.verticalHeader().setVisible(False) + for col in range(2): + self.dashboard.horizontalHeader().setSectionResizeMode( + col, QHeaderView.ResizeMode.ResizeToContents + ) + for col in range(2, 4): + self.dashboard.horizontalHeader().setSectionResizeMode( + col, QHeaderView.ResizeMode.Stretch + ) + self.dashboard.horizontalHeader().setSectionResizeMode( + 4, QHeaderView.ResizeMode.Fixed + ) + self.dashboard.setColumnWidth(4, 32) + + self.viewLayout.addWidget(self.dashboard) + self.viewLayout.setContentsMargins(3, 0, 3, 3) + + Config.PASSWORD_refreshed.connect(self.load_info) + + def load_info(self): + + self.sub_data = Config.member_dict[self.name]["SubData"] + + self.dashboard.setRowCount(len(self.sub_data)) + + for name, info in self.sub_data.items(): + + config = info["Config"] + + text_list = [] + text_list.append( + f"今日已代理{config.get(config.Data_ProxyTimes)}次" + if Config.server_date().strftime("%Y-%m-%d") + == config.get(config.Data_LastProxyDate) + else "今日未进行代理" + ) + + button = PrimaryToolButton( + FluentIcon.CHEVRON_RIGHT, self + ) + button.setFixedSize(32, 32) + button.clicked.connect( + partial(self.switch_to.emit, name) + ) + + self.dashboard.setItem( + int(name[3:]) - 1, + 0, + QTableWidgetItem(config.get(config.Info_Name)), + ) + self.dashboard.setCellWidget( + int(name[3:]) - 1, + 1, + StatusSwitchSetting( + qconfig=config, + configItem_check=config.Info_Status, + configItem_enable=config.Info_RemainedDay, + parent=self, + ), + ) + self.dashboard.setItem( + int(name[3:]) - 1, + 2, + QTableWidgetItem(" | ".join(text_list)), + ) + self.dashboard.setItem( + int(name[3:]) - 1, + 3, + QTableWidgetItem(config.get(config.Info_Notes)), + ) + self.dashboard.setCellWidget( + int(name[3:]) - 1, 4, button + ) + + class SubMemberSettingBox(HeaderCardWidget): + """配置管理子页面""" + + def __init__(self, name: str, uid: int, parent=None): + super().__init__(parent) + + self.setObjectName(f"配置_{uid}") + self.setTitle(f"配置 {uid}") + self.name = name + self.config = Config.member_dict[self.name]["SubData"][ + f"配置_{uid}" + ]["Config"] + self.sub_path = Config.member_dict[self.name]["SubData"][ + f"配置_{uid}" + ]["Path"] + + self.card_Name = LineEditSettingCard( + icon=FluentIcon.PEOPLE, + title="配置名", + content="用于标识配置", + text="请输入配置名", + qconfig=self.config, + configItem=self.config.Info_Name, + parent=self, + ) + self.card_SetConfig = PushSettingCard( + text="设置具体配置", + icon=FluentIcon.CAFE, + title="具体配置", + content="在脚本原始界面中查看具体配置内容", + parent=self, + ) + self.card_Status = SwitchSettingCard( + icon=FluentIcon.CHECKBOX, + title="配置状态", + content="启用或禁用该配置", + qconfig=self.config, + configItem=self.config.Info_Status, + parent=self, + ) + self.card_RemainedDay = SpinBoxSettingCard( + icon=FluentIcon.CALENDAR, + title="剩余天数", + content="剩余代理天数,-1表示无限代理", + range=(-1, 1024), + qconfig=self.config, + configItem=self.config.Info_RemainedDay, + parent=self, + ) + self.item_IfScriptBeforeTask = StatusSwitchSetting( + qconfig=self.config, + configItem_check=self.config.Info_IfScriptBeforeTask, + configItem_enable=None, + parent=self, + ) + self.card_ScriptBeforeTask = PathSettingCard( + icon=FluentIcon.FOLDER, + title="脚本前置任务", + mode="脚本文件 (*.py *.bat *.exe)", + text="选择脚本文件", + qconfig=self.config, + configItem=self.config.Info_ScriptBeforeTask, + parent=self, + ) + self.item_IfScriptAfterTask = StatusSwitchSetting( + qconfig=self.config, + configItem_check=self.config.Info_IfScriptAfterTask, + configItem_enable=None, + parent=self, + ) + self.card_ScriptAfterTask = PathSettingCard( + icon=FluentIcon.FOLDER, + title="脚本后置任务", + mode="脚本文件 (*.py *.bat *.exe)", + text="选择脚本文件", + qconfig=self.config, + configItem=self.config.Info_ScriptAfterTask, + parent=self, + ) + self.card_Notes = LineEditSettingCard( + icon=FluentIcon.PENCIL_INK, + title="备注", + content="配置备注信息", + text="请输入备注", + qconfig=self.config, + configItem=self.config.Info_Notes, + parent=self, + ) + + self.card_UserLable = SubLableSettingCard( + icon=FluentIcon.INFO, + title="状态信息", + content="配置的代理情况汇总", + qconfig=self.config, + configItems={ + "LastProxyDate": self.config.Data_LastProxyDate, + "ProxyTimes": self.config.Data_ProxyTimes, + }, + parent=self, + ) + + self.card_ScriptBeforeTask.hBoxLayout.insertWidget( + 5, self.item_IfScriptBeforeTask, 0, Qt.AlignRight + ) + self.card_ScriptAfterTask.hBoxLayout.insertWidget( + 5, self.item_IfScriptAfterTask, 0, Qt.AlignRight + ) + + # 单独通知卡片 + self.card_NotifySet = UserNoticeSettingCard( + icon=FluentIcon.MAIL, + title="用户单独通知设置", + content="未启用任何通知项", + text="设置", + qconfig=self.config, + configItem=self.config.Notify_Enabled, + configItems={ + "IfSendStatistic": self.config.Notify_IfSendStatistic, + "IfSendMail": self.config.Notify_IfSendMail, + "ToAddress": self.config.Notify_ToAddress, + "IfServerChan": self.config.Notify_IfServerChan, + "ServerChanKey": self.config.Notify_ServerChanKey, + "IfCompanyWebHookBot": self.config.Notify_IfCompanyWebHookBot, + "CompanyWebHookBotUrl": self.config.Notify_CompanyWebHookBotUrl, + }, + parent=self, + ) + self.card_NotifyContent = self.NotifyContentSettingCard( + self.config, self + ) + self.card_EMail = self.EMailSettingCard(self.config, self) + self.card_ServerChan = self.ServerChanSettingCard( + self.config, self + ) + self.card_CompanyWebhookBot = ( + self.CompanyWechatPushSettingCard(self.config, self) + ) + + self.NotifySetCard = SettingFlyoutView( + self, + "用户通知设置", + [ + self.card_NotifyContent, + self.card_EMail, + self.card_ServerChan, + self.card_CompanyWebhookBot, + ], + ) + + h1_layout = QHBoxLayout() + h1_layout.addWidget(self.card_Name) + h1_layout.addWidget(self.card_SetConfig) + h2_layout = QHBoxLayout() + h2_layout.addWidget(self.card_Status) + h2_layout.addWidget(self.card_RemainedDay) + + Layout = QVBoxLayout() + Layout.addLayout(h1_layout) + Layout.addLayout(h2_layout) + Layout.addWidget(self.card_UserLable) + Layout.addWidget(self.card_ScriptBeforeTask) + Layout.addWidget(self.card_ScriptAfterTask) + Layout.addWidget(self.card_Notes) + Layout.addWidget(self.card_NotifySet) + + self.viewLayout.addLayout(Layout) + self.viewLayout.setContentsMargins(3, 0, 3, 3) + + self.card_SetConfig.clicked.connect(self.set_sub) + self.card_NotifySet.clicked.connect(self.set_notify) + + def set_sub(self) -> None: + """配置子配置""" + + if self.name in Config.running_list: + logger.warning("所属脚本正在运行") + MainInfoBar.push_info_bar( + "warning", "所属脚本正在运行", "请先停止任务", 5000 + ) + return None + + TaskManager.add_task( + "设置通用脚本", + self.name, + {"SetSubInfo": {"Path": self.sub_path / "ConfigFiles"}}, + ) + + def set_notify(self) -> None: + """设置用户通知相关配置""" + + self.NotifySetCard.setVisible(True) + Flyout.make( + self.NotifySetCard, + self.card_NotifySet, + self, + aniType=FlyoutAnimationType.PULL_UP, + isDeleteOnClose=False, + ) + + class NotifyContentSettingCard(HeaderCardWidget): + + def __init__(self, config: MaaUserConfig, parent=None): + super().__init__(parent) + self.setTitle("用户通知内容选项") + + self.config = config + + self.card_IfSendStatistic = SwitchSettingCard( + icon=FluentIcon.PAGE_RIGHT, + title="推送统计信息", + content="推送自动代理统计信息的通知", + qconfig=self.config, + configItem=self.config.Notify_IfSendStatistic, + parent=self, + ) + + Layout = QVBoxLayout() + Layout.addWidget(self.card_IfSendStatistic) + self.viewLayout.addLayout(Layout) + self.viewLayout.setSpacing(3) + self.viewLayout.setContentsMargins(3, 0, 3, 3) + + class EMailSettingCard(HeaderCardWidget): + + def __init__(self, config: MaaUserConfig, parent=None): + super().__init__(parent) + self.setTitle("用户邮箱通知") + + self.config = config + + self.card_IfSendMail = SwitchSettingCard( + icon=FluentIcon.PAGE_RIGHT, + title="推送用户邮件通知", + content="是否启用用户邮件通知功能", + qconfig=self.config, + configItem=self.config.Notify_IfSendMail, + parent=self, + ) + self.card_ToAddress = LineEditSettingCard( + icon=FluentIcon.PAGE_RIGHT, + title="用户收信邮箱地址", + content="接收用户通知的邮箱地址", + text="请输入用户收信邮箱地址", + qconfig=self.config, + configItem=self.config.Notify_ToAddress, + parent=self, + ) + + Layout = QVBoxLayout() + Layout.addWidget(self.card_IfSendMail) + Layout.addWidget(self.card_ToAddress) + self.viewLayout.addLayout(Layout) + self.viewLayout.setSpacing(3) + self.viewLayout.setContentsMargins(3, 0, 3, 3) + + class ServerChanSettingCard(HeaderCardWidget): + + def __init__(self, config: MaaUserConfig, parent=None): + super().__init__(parent) + self.setTitle("用户ServerChan通知") + + self.config = config + + self.card_IfServerChan = SwitchSettingCard( + icon=FluentIcon.PAGE_RIGHT, + title="推送用户Server酱通知", + content="是否启用用户Server酱通知功能", + qconfig=self.config, + configItem=self.config.Notify_IfServerChan, + parent=self, + ) + self.card_ServerChanKey = LineEditSettingCard( + icon=FluentIcon.PAGE_RIGHT, + title="用户SendKey", + content="SC3与SCT均须填写", + text="请输入用户SendKey", + qconfig=self.config, + configItem=self.config.Notify_ServerChanKey, + parent=self, + ) + self.card_ServerChanChannel = LineEditSettingCard( + icon=FluentIcon.PAGE_RIGHT, + title="用户ServerChanChannel代码", + content="留空则默认,多个请使用“|”隔开", + text="请输入Channel代码,仅SCT生效", + qconfig=self.config, + configItem=self.config.Notify_ServerChanChannel, + parent=self, + ) + self.card_ServerChanTag = LineEditSettingCard( + icon=FluentIcon.PAGE_RIGHT, + title="用户Tag内容", + content="留空则默认,多个请使用“|”隔开", + text="请输入加入推送的Tag,仅SC3生效", + qconfig=self.config, + configItem=self.config.Notify_ServerChanTag, + parent=self, + ) + + Layout = QVBoxLayout() + Layout.addWidget(self.card_IfServerChan) + Layout.addWidget(self.card_ServerChanKey) + Layout.addWidget(self.card_ServerChanChannel) + Layout.addWidget(self.card_ServerChanTag) + self.viewLayout.addLayout(Layout) + self.viewLayout.setSpacing(3) + self.viewLayout.setContentsMargins(3, 0, 3, 3) + + class CompanyWechatPushSettingCard(HeaderCardWidget): + + def __init__(self, config: MaaUserConfig, parent=None): + super().__init__(parent) + self.setTitle("用户企业微信推送") + + self.config = config + + self.card_IfCompanyWebHookBot = SwitchSettingCard( + icon=FluentIcon.PAGE_RIGHT, + title="推送用户企业微信机器人通知", + content="是否启用用户企微机器人通知功能", + qconfig=self.config, + configItem=self.config.Notify_IfCompanyWebHookBot, + parent=self, + ) + self.card_CompanyWebHookBotUrl = LineEditSettingCard( + icon=FluentIcon.PAGE_RIGHT, + title="WebhookUrl", + content="用户企微群机器人Webhook地址", + text="请输入用户Webhook的Url", + qconfig=self.config, + configItem=self.config.Notify_CompanyWebHookBotUrl, + parent=self, + ) + + Layout = QVBoxLayout() + Layout.addWidget(self.card_IfCompanyWebHookBot) + Layout.addWidget(self.card_CompanyWebHookBotUrl) + self.viewLayout.addLayout(Layout) + self.viewLayout.setSpacing(3) + self.viewLayout.setContentsMargins(3, 0, 3, 3) diff --git a/app/ui/queue_manager.py b/app/ui/queue_manager.py index 266ef5d..f2490d4 100644 --- a/app/ui/queue_manager.py +++ b/app/ui/queue_manager.py @@ -111,7 +111,7 @@ class QueueManager(QWidget): "Config": queue_config, } - self.queue_manager.add_QueueSettingBox(index) + self.queue_manager.add_SettingBox(index) self.queue_manager.switch_SettingBox(index) logger.success(f"调度队列_{index} 添加成功") @@ -256,8 +256,8 @@ class QueueManager(QWidget): + [ ( k - if v["Config"].get(v["Config"].MaaSet_Name) == "" - else f"{k} - {v["Config"].get(v["Config"].MaaSet_Name)}" + if v["Config"].get_name() == "" + else f"{k} - {v["Config"].get_name()}" ) for k, v in Config.member_dict.items() ], @@ -332,7 +332,7 @@ class QueueManager(QWidget): Config.search_queue() for name in Config.queue_dict.keys(): - self.add_QueueSettingBox(int(name[5:])) + self.add_SettingBox(int(name[5:])) self.switch_SettingBox(index) @@ -358,12 +358,12 @@ class QueueManager(QWidget): self.script_list.clear() self.pivot.clear() - def add_QueueSettingBox(self, uid: int) -> None: + def add_SettingBox(self, uid: int) -> None: """添加一个调度队列设置界面""" - maa_setting_box = self.QueueMemberSettingBox(uid, self) + setting_box = self.QueueMemberSettingBox(uid, self) - self.script_list.append(maa_setting_box) + self.script_list.append(setting_box) self.stackedWidget.addWidget(self.script_list[-1]) @@ -586,8 +586,8 @@ class QueueManager(QWidget): + [ ( k - if v["Config"].get(v["Config"].MaaSet_Name) == "" - else f"{k} - {v["Config"].get(v["Config"].MaaSet_Name)}" + if v["Config"].get_name() == "" + else f"{k} - {v["Config"].get_name()}" ) for k, v in Config.member_dict.items() ], diff --git a/app/utils/ImageUtils.py b/app/utils/ImageUtils.py index 64b2447..aa02969 100644 --- a/app/utils/ImageUtils.py +++ b/app/utils/ImageUtils.py @@ -1,9 +1,37 @@ +# AUTO_MAA:A MAA Multi Account Management and Automation Tool +# Copyright © 2025 ClozyA + +# This file is part of AUTO_MAA. + +# AUTO_MAA is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published +# by the Free Software Foundation, either version 3 of the License, +# or (at your option) any later version. + +# AUTO_MAA is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty +# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See +# the GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with AUTO_MAA. If not, see . + +# Contact: DLmaster_361@163.com + +""" +AUTO_MAA +AUTO_MAA图像组件 +v4.3 +作者:ClozyA +""" + import base64 import hashlib from pathlib import Path from PIL import Image + class ImageUtils: @staticmethod def get_base64_from_file(image_path): diff --git a/app/utils/ProcessManager.py b/app/utils/ProcessManager.py new file mode 100644 index 0000000..09110f8 --- /dev/null +++ b/app/utils/ProcessManager.py @@ -0,0 +1,167 @@ +# AUTO_MAA:A MAA Multi Account Management and Automation Tool +# Copyright © 2024-2025 DLmaster361 + +# This file is part of AUTO_MAA. + +# AUTO_MAA is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published +# by the Free Software Foundation, either version 3 of the License, +# or (at your option) any later version. + +# AUTO_MAA is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty +# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See +# the GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with AUTO_MAA. If not, see . + +# Contact: DLmaster_361@163.com + +""" +AUTO_MAA +AUTO_MAA进程管理组件 +v4.3 +作者:DLmaster_361 +""" + + +import psutil +import subprocess +from pathlib import Path +from datetime import datetime + +from PySide6.QtCore import QTimer, QObject, Signal + + +class ProcessManager(QObject): + """进程监视器类,用于跟踪主进程及其所有子进程的状态""" + + processClosed = Signal() + + def __init__(self): + super().__init__() + + self.main_pid = None + self.tracked_pids = set() + + 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: + """ + 启动一个新进程并返回其pid,并开始监视该进程 + + :param path: 可执行文件的路径 + :param args: 启动参数列表 + :param tracking_time: 子进程追踪持续时间(秒) + :return: 新进程的PID + """ + + process = subprocess.Popen( + [path, *args], + creationflags=subprocess.CREATE_NO_WINDOW, + stdin=subprocess.DEVNULL, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + + self.start_monitoring(process.pid, tracking_time) + + def start_monitoring(self, pid: int, tracking_time: int = 60) -> None: + """ + 启动进程监视器,跟踪指定的主进程及其子进程 + + :param pid: 被监视进程的PID + :param tracking_time: 子进程追踪持续时间(秒) + """ + + self.clear() + + self.main_pid = pid + self.tracking_time = tracking_time + + # 扫描并记录所有相关进程 + try: + # 获取主进程及其子进程 + main_proc = psutil.Process(self.main_pid) + self.tracked_pids.add(self.main_pid) + + # 递归获取所有子进程 + for child in main_proc.children(recursive=True): + self.tracked_pids.add(child.pid) + + except psutil.NoSuchProcess: + pass + + # 启动持续追踪机制 + self.start_time = datetime.now() + self.check_timer.start(100) + + def check_processes(self) -> None: + """检查跟踪的进程是否仍在运行,并更新子进程列表""" + + # 仅在时限内持续更新跟踪的进程列表,发现新的子进程 + if (datetime.now() - self.start_time).total_seconds() < self.tracking_time: + + current_pids = set(self.tracked_pids) + for pid in current_pids: + try: + proc = psutil.Process(pid) + for child in proc.children(): + if child.pid not in self.tracked_pids: + # 新发现的子进程 + self.tracked_pids.add(child.pid) + except psutil.NoSuchProcess: + continue + + if not self.is_running(): + self.clear() + self.processClosed.emit() + + def is_running(self) -> bool: + """检查所有跟踪的进程是否还在运行""" + + for pid in self.tracked_pids: + try: + proc = psutil.Process(pid) + if proc.is_running(): + return True + except psutil.NoSuchProcess: + continue + + return False + + def kill(self, if_force: bool = False) -> None: + """停止监视器并中止所有跟踪的进程""" + + self.check_timer.stop() + + for pid in self.tracked_pids: + try: + proc = psutil.Process(pid) + if if_force: + kill_process = subprocess.Popen( + ["taskkill", "/F", "/T", "/PID", str(pid)], + creationflags=subprocess.CREATE_NO_WINDOW, + ) + kill_process.wait() + proc.terminate() + except psutil.NoSuchProcess: + continue + + if self.main_pid: + self.processClosed.emit() + self.clear() + + def clear(self) -> None: + """清空跟踪的进程列表""" + + self.main_pid = None + self.check_timer.stop() + self.tracked_pids.clear() diff --git a/app/utils/__init__.py b/app/utils/__init__.py index 917751c..0487e1d 100644 --- a/app/utils/__init__.py +++ b/app/utils/__init__.py @@ -29,4 +29,7 @@ __version__ = "4.2.0" __author__ = "DLmaster361 " __license__ = "GPL-3.0 license" -__all__ = [] +from .ImageUtils import ImageUtils +from .ProcessManager import ProcessManager + +__all__ = ["ImageUtils", "ProcessManager"] diff --git a/app/utils/package.py b/app/utils/package.py index bb9a7d4..7e075ba 100644 --- a/app/utils/package.py +++ b/app/utils/package.py @@ -70,7 +70,7 @@ if __name__ == "__main__": print("Packaging AUTO_MAA main program ...") os.system( - "powershell -Command python -m nuitka --standalone --onefile --mingw64" + "powershell -Command python -m nuitka --standalone --onefile --mingw64 --windows-uac-admin" " --enable-plugins=pyside6 --windows-console-mode=attach" " --onefile-tempdir-spec='{TEMP}\\AUTO_MAA'" " --windows-icon-from-ico=resources\\icons\\AUTO_MAA.ico" diff --git a/main.py b/main.py index 641122e..0e9d7d3 100644 --- a/main.py +++ b/main.py @@ -45,9 +45,19 @@ builtins.print = no_print from loguru import logger +import os +import sys +import ctypes from PySide6.QtWidgets import QApplication from qfluentwidgets import FluentTranslator -import sys + + +def is_admin() -> bool: + """检查当前程序是否以管理员身份运行""" + try: + return ctypes.windll.shell32.IsUserAnAdmin() + except: + return False @logger.catch @@ -68,4 +78,10 @@ def main(): if __name__ == "__main__": - 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) diff --git a/requirements.txt b/requirements.txt index 28ce2e5..f2d6ee6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,14 +1,14 @@ -loguru -plyer -PySide6 +loguru==0.7.3 +plyer==2.1.0 +PySide6==6.9.1 PySide6-Fluent-Widgets[full] -psutil -pywin32 -keyboard -pycryptodome +psutil==7.0.0 +pywin32==310 +keyboard==0.13.5 +pycryptodome==3.23.0 certifi==2025.4.26 -requests -markdown -Jinja2 -nuitka -pillow \ No newline at end of file +requests==2.32.4 +markdown==3.8.2 +Jinja2==3.1.6 +nuitka==2.7.11 +pillow==11.3.0 \ No newline at end of file diff --git a/resources/html/general_result.html b/resources/html/general_result.html new file mode 100644 index 0000000..1a1c842 --- /dev/null +++ b/resources/html/general_result.html @@ -0,0 +1,160 @@ + + + + + + + + +
+
+

{{ title }}

+ + +
+ +
+

脚本实例名称:{{ script_name }}

+

任务开始时间:{{ start_time }}

+

任务结束时间:{{ end_time }}

+

已完成数:{{ completed_count }}

+ {% if uncompleted_count %} +

未完成数:{{ uncompleted_count }}

+ {% endif %} + {% if failed_sub %} +

代理未成功的配置: {{ failed_sub }}

+ {% endif %} + {% if waiting_sub %} +

未开始代理的配置: {{ waiting_sub }}

+ {% endif %} +
+ +

AUTO_MAA 敬上

+ + + +
+ + + \ No newline at end of file diff --git a/resources/html/general_statistics.html b/resources/html/general_statistics.html new file mode 100644 index 0000000..9e577e0 --- /dev/null +++ b/resources/html/general_statistics.html @@ -0,0 +1,200 @@ + + + + + + + + +
+
+

自动代理统计报告

+ + +
+ +
+

用户代理信息:{{ sub_info }}

+

任务开始时间:{{ start_time }}

+

任务结束时间:{{ end_time }}

+

脚本执行结果: + {% if sub_result == '代理成功' %} + {{ sub_result }} + {% elif sub_result == '代理失败' %} + {{ sub_result }} + {% else %} + {{ sub_result }} + {% endif %} +

+ +
+ +

AUTO_MAA 敬上

+ + + +
+ + + \ No newline at end of file diff --git a/resources/version.json b/resources/version.json index 6f462af..316cd10 100644 --- a/resources/version.json +++ b/resources/version.json @@ -1,46 +1,15 @@ { - "main_version": "4.3.12.0", + "main_version": "4.4.0.1", "version_info": { - "4.3.12.0": { - "修复BUG": [ - "固定certifi版本号" - ] - }, - "4.3.11.0": { - "修复BUG": [ - "修复删除计划表引发的错误" - ] - }, - "4.3.10.0": { + "4.4.0.1": { "新增功能": [ - "更换全新默认主页图", - "适配 MAA 无`Default`配置情况 #52" - ], - "程序优化": [ - "静默模式控制时段延长至模拟器完成启动的10s后" - ] - }, - "4.3.10.3": { - "程序优化": [ - "使用 keyboard 模块替代 pyautogui 模块" - ] - }, - "4.3.10.2": { - "新增功能": [ - "公招喜报模板优化", - "支持使用命令行调用" + "初步完成通用调度模块" ], "修复BUG": [ - "修复更新动作重复执行问题" + "修复了程序BUG较少的BUG" ], "程序优化": [ - "Mirror 酱链接添加`source`字段,用于标识来源", - "优化下载器测速中止条件" - ] - }, - "4.3.10.1": { - "新增功能": [ - "森空岛签到功能上线" + "子线程卡死不再阻塞调度任务" ] } } From d9043aab0a5fd54e654d66a5a3c84688a105483c Mon Sep 17 00:00:00 2001 From: DLmaster361 Date: Thu, 10 Jul 2025 13:29:17 +0800 Subject: [PATCH 02/13] =?UTF-8?q?fix(ui):=20=E9=80=82=E9=85=8D=20Mirror=20?= =?UTF-8?q?=E9=85=B1=20=E5=B9=B3=E5=8F=B0=E4=B8=8B=E8=BD=BD=E7=AD=96?= =?UTF-8?q?=E7=95=A5=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + app/core/config.py | 2 +- app/ui/downloader.py | 77 ++++++++++++++++++++++++++---------------- resources/version.json | 7 +++- 4 files changed, 56 insertions(+), 31 deletions(-) diff --git a/.gitignore b/.gitignore index 2f712af..c3f664a 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ config/ data/ debug/ history/ +script/ resources/notice.json resources/theme_image.json resources/images/Home/BannerTheme.jpg \ No newline at end of file diff --git a/app/core/config.py b/app/core/config.py index 62e3056..6f54cfd 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -695,7 +695,7 @@ class GeneralSubConfig(LQConfig): class AppConfig(GlobalConfig): - VERSION = "4.4.0.1" + VERSION = "4.4.0.2" gameid_refreshed = Signal() PASSWORD_refreshed = Signal() diff --git a/app/ui/downloader.py b/app/ui/downloader.py index 9d4d12e..a2f17a0 100644 --- a/app/ui/downloader.py +++ b/app/ui/downloader.py @@ -47,6 +47,8 @@ from PySide6.QtCore import QThread, Signal, QTimer, QEventLoop from typing import List, Dict, Union +from app.services import System + def version_text(version_numb: list) -> str: """将版本号列表转为可读的文本信息""" @@ -94,7 +96,15 @@ class DownloadProcess(QThread): if self.download_path.exists(): self.download_path.unlink() - headers = {"Range": f"bytes={self.start_byte}-{self.end_byte}"} + logger.info( + f"开始下载:{self.url},范围:{self.start_byte}-{self.end_byte},存储地址:{self.download_path}" + ) + + headers = ( + {"Range": f"bytes={self.start_byte}-{self.end_byte}"} + if not (self.start_byte == -1 or self.end_byte == -1) + else None + ) while not self.isInterruptionRequested() and self.check_times != 0: @@ -106,14 +116,20 @@ class DownloadProcess(QThread): self.url, headers=headers, timeout=10, stream=True ) - if response.status_code != 206: + if response.status_code not in [200, 206]: if self.check_times != -1: self.check_times -= 1 + logger.error( + f"连接失败:{self.url},状态码:{response.status_code},剩余重试次数:{self.check_times}" + ) + time.sleep(1) continue + logger.info(f"连接成功:{self.url},状态码:{response.status_code}") + downloaded_size = 0 with self.download_path.open(mode="wb") as f: @@ -132,10 +148,14 @@ class DownloadProcess(QThread): if self.download_path.exists(): self.download_path.unlink() self.accomplish.emit(0) + logger.info(f"下载中止:{self.url}") else: self.accomplish.emit(time.time() - start_time) + logger.success( + f"下载完成:{self.url},实际下载大小:{downloaded_size} 字节,耗时:{time.time() - start_time:.2f} 秒" + ) break @@ -143,6 +163,10 @@ class DownloadProcess(QThread): if self.check_times != -1: self.check_times -= 1 + + logger.exception( + f"下载出错:{self.url},错误信息:{e},剩余重试次数:{self.check_times}" + ) time.sleep(1) else: @@ -150,6 +174,7 @@ class DownloadProcess(QThread): if self.download_path.exists(): self.download_path.unlink() self.accomplish.emit(0) + logger.error(f"下载失败:{self.url}") class ZipExtractProcess(QThread): @@ -172,6 +197,8 @@ class ZipExtractProcess(QThread): try: + logger.info(f"开始解压:{self.download_path} 到 {self.app_path}") + while True: if self.isInterruptionRequested(): @@ -181,13 +208,15 @@ 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}") break except PermissionError: if self.name == "AUTO_MAA": self.info.emit(f"解压出错:AUTO_MAA正在运行,正在尝试将其关闭") - self.kill_process(self.app_path / "AUTO_MAA.exe") + System.kill_process(self.app_path / "AUTO_MAA.exe") else: self.info.emit(f"解压出错:{self.name}正在运行,正在等待其关闭") + logger.warning(f"解压出错:{self.name}正在运行,正在等待其关闭") time.sleep(1) except Exception as e: @@ -195,32 +224,9 @@ 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}") return None - def kill_process(self, path: Path) -> None: - """根据路径中止进程""" - - for pid in self.search_pids(path): - killprocess = subprocess.Popen( - f"taskkill /F /PID {pid}", - shell=True, - creationflags=subprocess.CREATE_NO_WINDOW, - ) - killprocess.wait() - - def search_pids(self, path: Path) -> list: - """根据路径查找进程PID""" - - pids = [] - for proc in psutil.process_iter(["pid", "exe"]): - try: - if proc.info["exe"] and proc.info["exe"].lower() == str(path).lower(): - pids.append(proc.info["pid"]) - except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess): - # 进程可能在此期间已结束或无法访问,忽略这些异常 - pass - return pids - class DownloadManager(QDialog): """下载管理器""" @@ -346,6 +352,8 @@ class DownloadManager(QDialog): url_dict = self.get_download_url("测速") self.test_speed_result: Dict[str, float] = {} + logger.info(f"测速链接:{url_dict}") + for name, url in url_dict.items(): if self.isInterruptionRequested: @@ -427,6 +435,7 @@ class DownloadManager(QDialog): # 保存测速结果 self.config["speed_result"] = self.test_speed_result + logger.info(f"测速结果:{self.test_speed_result}") self.update_info("测速完成!") self.speed_test_accomplish.emit() @@ -465,8 +474,8 @@ class DownloadManager(QDialog): # 创建下载子线程 self.download_process_dict[f"part{i}"] = DownloadProcess( url, - start_byte, - end_byte, + -1 if self.config["mode"] == "MirrorChyan" else start_byte, + -1 if self.config["mode"] == "MirrorChyan" else end_byte, self.download_path.with_suffix(f".part{i}"), 1 if self.config["mode"] == "MirrorChyan" else -1, ) @@ -525,6 +534,9 @@ class DownloadManager(QDialog): return None # 合并下载的分段文件 + logger.info( + f"所有分段下载完成:{self.name},开始合并分段文件到 {self.download_path}" + ) with self.download_path.open(mode="wb") as outfile: for i in range(self.config["thread_numb"]): with self.download_path.with_suffix(f".part{i}").open( @@ -533,6 +545,10 @@ class DownloadManager(QDialog): outfile.write(infile.read()) self.download_path.with_suffix(f".part{i}").unlink() + logger.success( + f"合并完成:{self.name},下载文件大小:{self.download_path.stat().st_size} 字节" + ) + self.update_info("正在解压更新文件") self.update_progress(0, 0, 0) @@ -583,6 +599,9 @@ class DownloadManager(QDialog): self.progress_2.setValue(current) def requestInterruption(self) -> None: + """请求中断下载任务""" + + logger.info("收到下载任务中止请求") self.isInterruptionRequested = True diff --git a/resources/version.json b/resources/version.json index 316cd10..60c0fe7 100644 --- a/resources/version.json +++ b/resources/version.json @@ -1,6 +1,11 @@ { - "main_version": "4.4.0.1", + "main_version": "4.4.0.2", "version_info": { + "4.4.0.2": { + "修复BUG": [ + "适配 Mirror 酱 平台下载策略调整" + ] + }, "4.4.0.1": { "新增功能": [ "初步完成通用调度模块" From 5b0d7f0012e9b348c34ce99b2e1da056d6a2e6b4 Mon Sep 17 00:00:00 2001 From: DLmaster361 Date: Thu, 10 Jul 2025 17:34:41 +0800 Subject: [PATCH 03/13] =?UTF-8?q?feat(ui):=20=E4=BF=AE=E6=94=B9=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E5=8F=B7=E5=88=B0v4.4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 进一步适配三月七相关配置项 --- app/__init__.py | 2 +- app/core/__init__.py | 2 +- app/core/config.py | 17 +++++++--- app/core/main_info_bar.py | 2 +- app/core/network.py | 2 +- app/core/sound_player.py | 2 +- app/core/task_manager.py | 2 +- app/core/timer.py | 2 +- app/models/MAA.py | 2 +- app/models/__init__.py | 2 +- app/models/general.py | 61 ++++++++++++++++++++++++++---------- app/services/__init__.py | 2 +- app/services/notification.py | 2 +- app/services/security.py | 2 +- app/services/skland.py | 2 +- app/services/system.py | 2 +- app/ui/Widget.py | 35 ++++++++++++++++++--- app/ui/__init__.py | 2 +- app/ui/dispatch_center.py | 2 +- app/ui/downloader.py | 2 +- app/ui/history.py | 2 +- app/ui/home.py | 2 +- app/ui/main_window.py | 2 +- app/ui/member_manager.py | 28 +++++++++++------ app/ui/plan_manager.py | 2 +- app/ui/queue_manager.py | 2 +- app/ui/setting.py | 2 +- app/utils/ImageUtils.py | 2 +- app/utils/ProcessManager.py | 2 +- app/utils/__init__.py | 2 +- app/utils/package.py | 2 +- main.py | 2 +- resources/version.json | 3 ++ 33 files changed, 138 insertions(+), 62 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index 403828c..b2f64f6 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -21,7 +21,7 @@ """ AUTO_MAA AUTO_MAA主程序包 -v4.3 +v4.4 作者:DLmaster_361 """ diff --git a/app/core/__init__.py b/app/core/__init__.py index c6eab15..8a9058d 100644 --- a/app/core/__init__.py +++ b/app/core/__init__.py @@ -21,7 +21,7 @@ """ AUTO_MAA AUTO_MAA核心组件包 -v4.3 +v4.4 作者:DLmaster_361 """ diff --git a/app/core/config.py b/app/core/config.py index 6f54cfd..9338e60 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -21,7 +21,7 @@ """ AUTO_MAA AUTO_MAA配置管理 -v4.3 +v4.4 作者:DLmaster_361 """ @@ -601,14 +601,21 @@ class GeneralConfig(LQConfig): ) self.Script_Arguments = ConfigItem("Script", "Arguments", "") self.Script_ConfigPath = ConfigItem( - "Script", "ConfigPath", ".", FolderValidator() + "Script", "ConfigPath", ".", FileValidator() + ) + self.Script_ConfigPathMode = OptionsConfigItem( + "Script", + "ConfigPathMode", + "所有文件 (*)", + OptionsValidator(["所有文件 (*)", "文件夹"]), ) self.Script_LogPath = ConfigItem("Script", "LogPath", ".", FileValidator()) + self.Script_LogPathFormat = ConfigItem("Script", "LogPathFormat", "%Y-%m-%d") self.Script_LogTimeStart = ConfigItem( - "Script", "LogTimeStart", 0, RangeValidator(0, 1024) + "Script", "LogTimeStart", 1, RangeValidator(1, 1024) ) self.Script_LogTimeEnd = ConfigItem( - "Script", "LogTimeEnd", 0, RangeValidator(0, 1024) + "Script", "LogTimeEnd", 1, RangeValidator(1, 1024) ) self.Script_LogTimeFormat = ConfigItem( "Script", "LogTimeFormat", "%Y-%m-%d %H:%M:%S" @@ -616,7 +623,7 @@ class GeneralConfig(LQConfig): self.Script_SuccessLog = ConfigItem("Script", "SuccessLog", "") self.Script_ErrorLog = ConfigItem("Script", "ErrorLog", "") - self.Game_Enabled = ConfigItem("Game", "Enabled", True, BoolValidator()) + self.Game_Enabled = ConfigItem("Game", "Enabled", False, BoolValidator()) self.Game_Style = OptionsConfigItem( "Game", "Style", "Emulator", OptionsValidator(["Emulator", "Client"]) ) diff --git a/app/core/main_info_bar.py b/app/core/main_info_bar.py index d409809..715ff25 100644 --- a/app/core/main_info_bar.py +++ b/app/core/main_info_bar.py @@ -21,7 +21,7 @@ """ AUTO_MAA AUTO_MAA信息通知栏 -v4.3 +v4.4 作者:DLmaster_361 """ diff --git a/app/core/network.py b/app/core/network.py index d040cdb..15e3a26 100644 --- a/app/core/network.py +++ b/app/core/network.py @@ -21,7 +21,7 @@ """ AUTO_MAA AUTO_MAA网络请求线程 -v4.3 +v4.4 作者:DLmaster_361 """ diff --git a/app/core/sound_player.py b/app/core/sound_player.py index 2627cd1..422ae0a 100644 --- a/app/core/sound_player.py +++ b/app/core/sound_player.py @@ -21,7 +21,7 @@ """ AUTO_MAA AUTO_MAA音效播放器 -v4.3 +v4.4 作者:DLmaster_361 """ diff --git a/app/core/task_manager.py b/app/core/task_manager.py index db8166a..67365b4 100644 --- a/app/core/task_manager.py +++ b/app/core/task_manager.py @@ -21,7 +21,7 @@ """ AUTO_MAA AUTO_MAA业务调度器 -v4.3 +v4.4 作者:DLmaster_361 """ diff --git a/app/core/timer.py b/app/core/timer.py index 9aed618..d91047b 100644 --- a/app/core/timer.py +++ b/app/core/timer.py @@ -21,7 +21,7 @@ """ AUTO_MAA AUTO_MAA主业务定时器 -v4.3 +v4.4 作者:DLmaster_361 """ diff --git a/app/models/MAA.py b/app/models/MAA.py index 370620a..8d09a06 100644 --- a/app/models/MAA.py +++ b/app/models/MAA.py @@ -21,7 +21,7 @@ """ AUTO_MAA MAA功能组件 -v4.3 +v4.4 作者:DLmaster_361 """ diff --git a/app/models/__init__.py b/app/models/__init__.py index 62aab25..fdb7653 100644 --- a/app/models/__init__.py +++ b/app/models/__init__.py @@ -21,7 +21,7 @@ """ AUTO_MAA AUTO_MAA模组包 -v4.3 +v4.4 作者:DLmaster_361 """ diff --git a/app/models/general.py b/app/models/general.py index 2dc222d..e9904e2 100644 --- a/app/models/general.py +++ b/app/models/general.py @@ -21,7 +21,7 @@ """ AUTO_MAA 通用功能组件 -v4.3 +v4.4 作者:DLmaster_361 """ @@ -118,7 +118,7 @@ class GeneralManager(QObject): Path(self.set["Script"]["RootPath"]).exists() and Path(self.set["Script"]["ScriptPath"]).exists() and Path(self.set["Script"]["ConfigPath"]).exists() - and Path(self.set["Script"]["LogPath"]).exists() + and Path(self.set["Script"]["LogPath"]).parent.exists() and self.set["Script"]["LogTimeFormat"] and self.set["Script"]["ErrorLog"] ) or ( @@ -137,16 +137,24 @@ class GeneralManager(QObject): 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"]) - self.script_log_path = Path(self.set["Script"]["LogPath"]) + self.script_log_path = ( + Path(self.set["Script"]["LogPath"]).with_stem( + datetime.now().strftime(self.set["Script"]["LogPathFormat"]) + ) + if self.set["Script"]["LogPathFormat"] + else Path(self.set["Script"]["LogPath"]) + ) + if not self.script_log_path.exists(): + self.script_log_path.parent.mkdir(parents=True, exist_ok=True) + self.script_log_path.touch(exist_ok=True) self.game_path = Path(self.set["Game"]["Path"]) self.log_time_range = [ - self.set["Script"]["LogTimeStart"], + self.set["Script"]["LogTimeStart"] - 1, self.set["Script"]["LogTimeEnd"], ] self.success_log = [ _.strip() for _ in self.set["Script"]["SuccessLog"].split("|") ] - print(f"Success Log: {self.success_log}") self.error_log = [_.strip() for _ in self.set["Script"]["ErrorLog"].split("|")] def run(self): @@ -431,9 +439,14 @@ class GeneralManager(QObject): self.start_monitor(start_time) self.sub_config_path.mkdir(parents=True, exist_ok=True) - shutil.copytree( - self.script_config_path, self.sub_config_path, dirs_exist_ok=True - ) + if self.set["Script"]["ConfigPathMode"] == "文件夹": + shutil.copytree( + self.script_config_path, + self.sub_config_path, + dirs_exist_ok=True, + ) + else: + shutil.copy(self.script_config_path, self.sub_config_path) except Exception as e: logger.error(f"{self.name} | 启动通用脚本时出现异常:{e}") @@ -690,15 +703,31 @@ class GeneralManager(QObject): # 预导入配置文件 if self.mode == "设置通用脚本": if self.sub_config_path.exists(): - shutil.copytree( - self.sub_config_path, self.script_config_path, dirs_exist_ok=True - ) + if self.set["Script"]["ConfigPathMode"] == "文件夹": + shutil.copytree( + self.sub_config_path, + self.script_config_path, + dirs_exist_ok=True, + ) + elif (self.sub_config_path / self.script_config_path.name).exists(): + shutil.copy( + self.sub_config_path / self.script_config_path.name, + self.script_config_path, + ) else: - shutil.copytree( - self.data[index]["Path"] / "ConfigFiles", - self.script_config_path, - dirs_exist_ok=True, - ) + if self.set["Script"]["ConfigPathMode"] == "文件夹": + shutil.copytree( + self.data[index]["Path"] / "ConfigFiles", + self.script_config_path, + dirs_exist_ok=True, + ) + else: + shutil.copy( + self.data[index]["Path"] + / "ConfigFiles" + / self.script_config_path.name, + self.script_config_path, + ) def execute_script_task(self, script_path: Path, task_name: str) -> bool: """执行脚本任务并等待结束""" diff --git a/app/services/__init__.py b/app/services/__init__.py index 9c018a1..0bdbea6 100644 --- a/app/services/__init__.py +++ b/app/services/__init__.py @@ -21,7 +21,7 @@ """ AUTO_MAA AUTO_MAA服务包 -v4.3 +v4.4 作者:DLmaster_361 """ diff --git a/app/services/notification.py b/app/services/notification.py index f433c0b..6ea440d 100644 --- a/app/services/notification.py +++ b/app/services/notification.py @@ -21,7 +21,7 @@ """ AUTO_MAA AUTO_MAA通知服务 -v4.3 +v4.4 作者:DLmaster_361 """ diff --git a/app/services/security.py b/app/services/security.py index 6ac0671..dda8dc1 100644 --- a/app/services/security.py +++ b/app/services/security.py @@ -21,7 +21,7 @@ """ AUTO_MAA AUTO_MAA安全服务 -v4.3 +v4.4 作者:DLmaster_361 """ diff --git a/app/services/skland.py b/app/services/skland.py index 192f589..c49724e 100644 --- a/app/services/skland.py +++ b/app/services/skland.py @@ -28,7 +28,7 @@ """ AUTO_MAA AUTO_MAA森空岛服务 -v4.3 +v4.4 作者:DLmaster_361、ClozyA """ diff --git a/app/services/system.py b/app/services/system.py index d0f2e34..4600c59 100644 --- a/app/services/system.py +++ b/app/services/system.py @@ -21,7 +21,7 @@ """ AUTO_MAA AUTO_MAA系统服务 -v4.3 +v4.4 作者:DLmaster_361 """ diff --git a/app/ui/Widget.py b/app/ui/Widget.py index 5fc7c9f..a6c91e2 100644 --- a/app/ui/Widget.py +++ b/app/ui/Widget.py @@ -27,7 +27,7 @@ """ AUTO_MAA AUTO_MAA组件 -v4.3 +v4.4 作者:DLmaster_361 """ @@ -577,7 +577,7 @@ class PathSettingCard(PushSettingCard): self, icon: Union[str, QIcon, FluentIconBase], title: str, - mode: str, + mode: Union[str, OptionsConfigItem], text: str, qconfig: QConfig, configItem: ConfigItem, @@ -590,6 +590,18 @@ class PathSettingCard(PushSettingCard): self.qconfig = qconfig self.configItem = configItem + if isinstance(mode, OptionsConfigItem): + + self.ComboBox = ComboBox(self) + self.hBoxLayout.insertWidget(5, self.ComboBox, 0, Qt.AlignRight) + + for option in mode.options: + self.ComboBox.addItem(option, userData=option) + + self.ComboBox.setCurrentText(self.qconfig.get(mode)) + self.ComboBox.currentIndexChanged.connect(self._onCurrentIndexChanged) + mode.valueChanged.connect(self.setValue) + self.setContent(self.qconfig.get(self.configItem)) self.clicked.connect(self.ChoosePath) @@ -602,7 +614,7 @@ class PathSettingCard(PushSettingCard): old_path = Path(self.qconfig.get(self.configItem)) - if self.mode == "文件夹": + if self.get_mode() == "文件夹": folder = QFileDialog.getExistingDirectory( self, "选择文件夹", self.qconfig.get(self.configItem) @@ -614,7 +626,7 @@ class PathSettingCard(PushSettingCard): else: file_path, _ = QFileDialog.getOpenFileName( - self, "打开文件", self.qconfig.get(self.configItem), self.mode + self, "打开文件", self.qconfig.get(self.configItem), self.get_mode() ) if file_path: file_path = self.analysis_lnk(file_path) @@ -635,6 +647,21 @@ class PathSettingCard(PushSettingCard): else: return lnk_path + def get_mode(self) -> str: + """获取当前模式""" + if isinstance(self.mode, OptionsConfigItem): + return self.qconfig.get(self.mode) + return self.mode + + def _onCurrentIndexChanged(self, index: int): + + self.qconfig.set(self.mode, self.ComboBox.itemData(index)) + + def setValue(self, value): + + self.ComboBox.setCurrentText(value) + self.qconfig.set(self.mode, value) + class PushAndSwitchButtonSettingCard(SettingCard): """Setting card with push & switch button""" diff --git a/app/ui/__init__.py b/app/ui/__init__.py index 4d6e574..ea7d35a 100644 --- a/app/ui/__init__.py +++ b/app/ui/__init__.py @@ -21,7 +21,7 @@ """ AUTO_MAA AUTO_MAA图形化界面包 -v4.3 +v4.4 作者:DLmaster_361 """ diff --git a/app/ui/dispatch_center.py b/app/ui/dispatch_center.py index 5f26d6f..d216411 100644 --- a/app/ui/dispatch_center.py +++ b/app/ui/dispatch_center.py @@ -21,7 +21,7 @@ """ AUTO_MAA AUTO_MAA调度中枢界面 -v4.3 +v4.4 作者:DLmaster_361 """ diff --git a/app/ui/downloader.py b/app/ui/downloader.py index a2f17a0..bebc307 100644 --- a/app/ui/downloader.py +++ b/app/ui/downloader.py @@ -21,7 +21,7 @@ """ AUTO_MAA AUTO_MAA更新器 -v4.3 +v4.4 作者:DLmaster_361 """ diff --git a/app/ui/history.py b/app/ui/history.py index cc6f0c0..2f0d4c6 100644 --- a/app/ui/history.py +++ b/app/ui/history.py @@ -21,7 +21,7 @@ """ AUTO_MAA AUTO_MAA历史记录界面 -v4.3 +v4.4 作者:DLmaster_361 """ diff --git a/app/ui/home.py b/app/ui/home.py index 00624a6..fa3dba5 100644 --- a/app/ui/home.py +++ b/app/ui/home.py @@ -21,7 +21,7 @@ """ AUTO_MAA AUTO_MAA主界面 -v4.3 +v4.4 作者:DLmaster_361 """ diff --git a/app/ui/main_window.py b/app/ui/main_window.py index 6064b8e..f95fce0 100644 --- a/app/ui/main_window.py +++ b/app/ui/main_window.py @@ -21,7 +21,7 @@ """ AUTO_MAA AUTO_MAA主界面 -v4.3 +v4.4 作者:DLmaster_361 """ diff --git a/app/ui/member_manager.py b/app/ui/member_manager.py index 66120dc..30c9b5e 100644 --- a/app/ui/member_manager.py +++ b/app/ui/member_manager.py @@ -21,7 +21,7 @@ """ AUTO_MAA AUTO_MAA脚本管理界面 -v4.3 +v4.4 作者:DLmaster_361 """ @@ -2312,27 +2312,36 @@ class MemberManager(QWidget): ) self.card_ConfigPath = PathSettingCard( icon=FluentIcon.FOLDER, - title="脚本配置文件目录 - [必填]", - mode="文件夹", - text="选择文件夹", + title="脚本配置文件路径 - [必填]", + mode=self.config.Script_ConfigPathMode, + text="选择路径", qconfig=self.config, configItem=self.config.Script_ConfigPath, parent=self, ) self.card_LogPath = PathSettingCard( icon=FluentIcon.FOLDER, - title="脚本日志文件目录 - [必填]", + title="脚本日志文件路径 - [必填]", mode="所有文件 (*)", text="选择文件", qconfig=self.config, configItem=self.config.Script_LogPath, parent=self, ) + self.card_LogPathFormat = LineEditSettingCard( + icon=FluentIcon.PAGE_RIGHT, + title="脚本日志文件名格式", + content="若脚本日志文件名中随时间变化,请填入时间格式,留空则不启用", + text="请输入脚本日志文件名格式", + qconfig=self.config, + configItem=self.config.Script_LogPathFormat, + parent=self, + ) self.card_LogTimeStart = SpinBoxSettingCard( icon=FluentIcon.PAGE_RIGHT, title="脚本日志时间起始位置 - [必填]", content="脚本日志中时间的起始位置,单位为字符", - range=(0, 1024), + range=(1, 1024), qconfig=self.config, configItem=self.config.Script_LogTimeStart, parent=self, @@ -2341,7 +2350,7 @@ class MemberManager(QWidget): icon=FluentIcon.PAGE_RIGHT, title="脚本日志时间结束位置 - [必填]", content="脚本日志中时间的结束位置,单位为字符", - range=(0, 1024), + range=(1, 1024), qconfig=self.config, configItem=self.config.Script_LogTimeEnd, parent=self, @@ -2402,6 +2411,7 @@ class MemberManager(QWidget): Layout.addWidget(self.card_Arguments) Layout.addWidget(self.card_ConfigPath) Layout.addWidget(self.card_LogPath) + Layout.addWidget(self.card_LogPathFormat) Layout.addLayout(h_layout) Layout.addWidget(self.card_LogTimeFormat) Layout.addWidget(self.card_SuccessLog) @@ -3066,7 +3076,7 @@ class MemberManager(QWidget): self.card_ScriptBeforeTask = PathSettingCard( icon=FluentIcon.FOLDER, title="脚本前置任务", - mode="脚本文件 (*.py *.bat *.exe)", + mode="脚本文件 (*.py *.bat *.cmd *.exe)", text="选择脚本文件", qconfig=self.config, configItem=self.config.Info_ScriptBeforeTask, @@ -3081,7 +3091,7 @@ class MemberManager(QWidget): self.card_ScriptAfterTask = PathSettingCard( icon=FluentIcon.FOLDER, title="脚本后置任务", - mode="脚本文件 (*.py *.bat *.exe)", + mode="脚本文件 (*.py *.bat *.cmd *.exe)", text="选择脚本文件", qconfig=self.config, configItem=self.config.Info_ScriptAfterTask, diff --git a/app/ui/plan_manager.py b/app/ui/plan_manager.py index 1a44ce9..7ebd9bf 100644 --- a/app/ui/plan_manager.py +++ b/app/ui/plan_manager.py @@ -21,7 +21,7 @@ """ AUTO_MAA AUTO_MAA计划管理界面 -v4.3 +v4.4 作者:DLmaster_361 """ diff --git a/app/ui/queue_manager.py b/app/ui/queue_manager.py index f2490d4..303edda 100644 --- a/app/ui/queue_manager.py +++ b/app/ui/queue_manager.py @@ -21,7 +21,7 @@ """ AUTO_MAA AUTO_MAA调度队列界面 -v4.3 +v4.4 作者:DLmaster_361 """ diff --git a/app/ui/setting.py b/app/ui/setting.py index bec3b26..b1b0808 100644 --- a/app/ui/setting.py +++ b/app/ui/setting.py @@ -21,7 +21,7 @@ """ AUTO_MAA AUTO_MAA设置界面 -v4.3 +v4.4 作者:DLmaster_361 """ diff --git a/app/utils/ImageUtils.py b/app/utils/ImageUtils.py index aa02969..2631dc9 100644 --- a/app/utils/ImageUtils.py +++ b/app/utils/ImageUtils.py @@ -21,7 +21,7 @@ """ AUTO_MAA AUTO_MAA图像组件 -v4.3 +v4.4 作者:ClozyA """ diff --git a/app/utils/ProcessManager.py b/app/utils/ProcessManager.py index 09110f8..0d428b5 100644 --- a/app/utils/ProcessManager.py +++ b/app/utils/ProcessManager.py @@ -21,7 +21,7 @@ """ AUTO_MAA AUTO_MAA进程管理组件 -v4.3 +v4.4 作者:DLmaster_361 """ diff --git a/app/utils/__init__.py b/app/utils/__init__.py index 0487e1d..ead5c44 100644 --- a/app/utils/__init__.py +++ b/app/utils/__init__.py @@ -21,7 +21,7 @@ """ AUTO_MAA AUTO_MAA工具包 -v4.3 +v4.4 作者:DLmaster_361 """ diff --git a/app/utils/package.py b/app/utils/package.py index 7e075ba..59d2d20 100644 --- a/app/utils/package.py +++ b/app/utils/package.py @@ -21,7 +21,7 @@ """ AUTO_MAA AUTO_MAA打包程序 -v4.3 +v4.4 作者:DLmaster_361 """ diff --git a/main.py b/main.py index 0e9d7d3..85e963b 100644 --- a/main.py +++ b/main.py @@ -21,7 +21,7 @@ """ AUTO_MAA AUTO_MAA主程序 -v4.3 +v4.4 作者:DLmaster_361 """ diff --git a/resources/version.json b/resources/version.json index 60c0fe7..7d6cd9b 100644 --- a/resources/version.json +++ b/resources/version.json @@ -2,6 +2,9 @@ "main_version": "4.4.0.2", "version_info": { "4.4.0.2": { + "新增功能": [ + "进一步适配三月七相关配置项" + ], "修复BUG": [ "适配 Mirror 酱 平台下载策略调整" ] From abeb9f054d63c3b27a7f8589bdc15e691f352c53 Mon Sep 17 00:00:00 2001 From: DLmaster361 Date: Fri, 11 Jul 2025 14:48:23 +0800 Subject: [PATCH 04/13] =?UTF-8?q?fix(maa):=20=E9=80=82=E9=85=8D=20MAA=20?= =?UTF-8?q?=E5=A4=87=E9=80=89=E5=85=B3=E5=8D=A1=E5=AD=97=E6=AE=B5=E4=BF=AE?= =?UTF-8?q?=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/core/config.py | 188 ++++++++++++++-------- app/core/timer.py | 2 +- app/models/MAA.py | 46 +++--- app/ui/member_manager.py | 241 ++++++++++++++++------------- app/ui/plan_manager.py | 35 +++-- resources/docs/MAA_config_info.txt | 1 + resources/version.json | 10 +- 7 files changed, 315 insertions(+), 208 deletions(-) diff --git a/app/core/config.py b/app/core/config.py index 9338e60..3a341fc 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -408,7 +408,7 @@ class MaaUserConfig(LQConfig): self.Info_Mode = OptionsConfigItem( "Info", "Mode", "简洁", OptionsValidator(["简洁", "详细"]) ) - self.Info_GameIdMode = ConfigItem("Info", "GameIdMode", "固定") + self.Info_StageMode = ConfigItem("Info", "StageMode", "固定") self.Info_Server = OptionsConfigItem( "Info", "Server", "Official", OptionsValidator(["Official", "Bilibili"]) ) @@ -437,10 +437,11 @@ class MaaUserConfig(LQConfig): "0", OptionsValidator(["0", "6", "5", "4", "3", "2", "1", "-1"]), ) - self.Info_GameId = ConfigItem("Info", "GameId", "-") - self.Info_GameId_1 = ConfigItem("Info", "GameId_1", "-") - self.Info_GameId_2 = ConfigItem("Info", "GameId_2", "-") - self.Info_GameId_Remain = ConfigItem("Info", "GameId_Remain", "-") + self.Info_Stage = ConfigItem("Info", "Stage", "-") + self.Info_Stage_1 = ConfigItem("Info", "Stage_1", "-") + self.Info_Stage_2 = ConfigItem("Info", "Stage_2", "-") + self.Info_Stage_3 = ConfigItem("Info", "Stage_3", "-") + self.Info_Stage_Remain = ConfigItem("Info", "Stage_Remain", "-") self.Info_IfSkland = ConfigItem("Info", "IfSkland", False, BoolValidator()) self.Info_SklandToken = ConfigItem("Info", "SklandToken", "") @@ -499,24 +500,26 @@ class MaaUserConfig(LQConfig): def get_plan_info(self) -> Dict[str, Union[str, int]]: """获取当前的计划下信息""" - if self.get(self.Info_GameIdMode) == "固定": + if self.get(self.Info_StageMode) == "固定": return { "MedicineNumb": self.get(self.Info_MedicineNumb), "SeriesNumb": self.get(self.Info_SeriesNumb), - "GameId": self.get(self.Info_GameId), - "GameId_1": self.get(self.Info_GameId_1), - "GameId_2": self.get(self.Info_GameId_2), - "GameId_Remain": self.get(self.Info_GameId_Remain), + "Stage": self.get(self.Info_Stage), + "Stage_1": self.get(self.Info_Stage_1), + "Stage_2": self.get(self.Info_Stage_2), + "Stage_3": self.get(self.Info_Stage_3), + "Stage_Remain": self.get(self.Info_Stage_Remain), } - elif "计划" in self.get(self.Info_GameIdMode): - plan = Config.plan_dict[self.get(self.Info_GameIdMode)]["Config"] + elif "计划" in self.get(self.Info_StageMode): + plan = Config.plan_dict[self.get(self.Info_StageMode)]["Config"] return { "MedicineNumb": plan.get(plan.get_current_info("MedicineNumb")), "SeriesNumb": plan.get(plan.get_current_info("SeriesNumb")), - "GameId": plan.get(plan.get_current_info("GameId")), - "GameId_1": plan.get(plan.get_current_info("GameId_1")), - "GameId_2": plan.get(plan.get_current_info("GameId_2")), - "GameId_Remain": plan.get(plan.get_current_info("GameId_Remain")), + "Stage": plan.get(plan.get_current_info("Stage")), + "Stage_1": plan.get(plan.get_current_info("Stage_1")), + "Stage_2": plan.get(plan.get_current_info("Stage_2")), + "Stage_3": plan.get(plan.get_current_info("Stage_3")), + "Stage_Remain": plan.get(plan.get_current_info("Stage_Remain")), } @@ -554,24 +557,22 @@ class MaaPlanConfig(LQConfig): "0", OptionsValidator(["0", "6", "5", "4", "3", "2", "1", "-1"]), ) - self.config_item_dict[group]["GameId"] = ConfigItem(group, "GameId", "-") - self.config_item_dict[group]["GameId_1"] = ConfigItem( - group, "GameId_1", "-" - ) - self.config_item_dict[group]["GameId_2"] = ConfigItem( - group, "GameId_2", "-" - ) - self.config_item_dict[group]["GameId_Remain"] = ConfigItem( - group, "GameId_Remain", "-" + self.config_item_dict[group]["Stage"] = ConfigItem(group, "Stage", "-") + self.config_item_dict[group]["Stage_1"] = ConfigItem(group, "Stage_1", "-") + self.config_item_dict[group]["Stage_2"] = ConfigItem(group, "Stage_2", "-") + self.config_item_dict[group]["Stage_3"] = ConfigItem(group, "Stage_3", "-") + self.config_item_dict[group]["Stage_Remain"] = ConfigItem( + group, "Stage_Remain", "-" ) for name in [ "MedicineNumb", "SeriesNumb", - "GameId", - "GameId_1", - "GameId_2", - "GameId_Remain", + "Stage", + "Stage_1", + "Stage_2", + "Stage_3", + "Stage_Remain", ]: setattr(self, f"{group}_{name}", self.config_item_dict[group][name]) @@ -702,9 +703,9 @@ class GeneralSubConfig(LQConfig): class AppConfig(GlobalConfig): - VERSION = "4.4.0.2" + VERSION = "4.4.0.3" - gameid_refreshed = Signal() + stage_refreshed = Signal() PASSWORD_refreshed = Signal() sub_info_changed = Signal() power_sign_changed = Signal() @@ -719,15 +720,13 @@ class AppConfig(GlobalConfig): self.database_path = self.app_path / "data/data.db" self.config_path = self.app_path / "config/config.json" self.key_path = self.app_path / "data/key" - self.gameid_path = self.app_path / "data/gameid.txt" - self.version_path = self.app_path / "resources/version.json" self.main_window = None self.PASSWORD = "" self.running_list = [] self.silence_list = [] self.info_bar_list = [] - self.gameid_dict = { + self.stage_dict = { "ALL": {"value": [], "text": []}, "Monday": {"value": [], "text": []}, "Tuesday": {"value": [], "text": []}, @@ -778,15 +777,12 @@ class AppConfig(GlobalConfig): self.init_logger() self.check_data() - self.get_gameid() + self.get_stage() logger.info("程序初始化完成") def init_logger(self) -> None: """初始化日志记录器""" - if self.args.mode != "cli": - logger.remove(0) - logger.add( sink=self.log_path, level="DEBUG", @@ -810,7 +806,7 @@ class AppConfig(GlobalConfig): logger.info("日志记录器初始化完成") - def get_gameid(self) -> None: + def get_stage(self) -> None: # 从MAA服务器获取活动关卡信息 network = Network.add_task( @@ -820,33 +816,33 @@ class AppConfig(GlobalConfig): network.loop.exec() network_result = Network.get_result(network) if network_result["status_code"] == 200: - gameid_infos: List[Dict[str, Union[str, Dict[str, Union[str, int]]]]] = ( + 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']}" ) - gameid_infos = [] + stage_infos = [] - ss_gameid_dict = {"value": [], "text": []} + ss_stage_dict = {"value": [], "text": []} - for gameid_info in gameid_infos: + for stage_info in stage_infos: if ( datetime.strptime( - gameid_info["Activity"]["UtcStartTime"], "%Y/%m/%d %H:%M:%S" + stage_info["Activity"]["UtcStartTime"], "%Y/%m/%d %H:%M:%S" ) < datetime.now() < datetime.strptime( - gameid_info["Activity"]["UtcExpireTime"], "%Y/%m/%d %H:%M:%S" + stage_info["Activity"]["UtcExpireTime"], "%Y/%m/%d %H:%M:%S" ) ): - ss_gameid_dict["value"].append(gameid_info["Value"]) - ss_gameid_dict["text"].append(gameid_info["Value"]) + ss_stage_dict["value"].append(stage_info["Value"]) + ss_stage_dict["text"].append(stage_info["Value"]) # 生成每日关卡信息 - gameid_daily_info = [ + 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]}, @@ -872,20 +868,20 @@ class AppConfig(GlobalConfig): for day in range(0, 8): - today_gameid_dict = {"value": [], "text": []} + today_stage_dict = {"value": [], "text": []} - for gameid_info in gameid_daily_info: + for stage_info in stage_daily_info: - if day in gameid_info["days"] or day == 0: - today_gameid_dict["value"].append(gameid_info["value"]) - today_gameid_dict["text"].append(gameid_info["text"]) + 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.gameid_dict[calendar.day_name[day - 1] if day > 0 else "ALL"] = { - "value": today_gameid_dict["value"] + ss_gameid_dict["value"], - "text": today_gameid_dict["text"] + ss_gameid_dict["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.gameid_refreshed.emit() + self.stage_refreshed.emit() def server_date(self) -> date: """获取当前的服务器日期""" @@ -903,7 +899,7 @@ class AppConfig(GlobalConfig): db = sqlite3.connect(self.database_path) cur = db.cursor() cur.execute("CREATE TABLE version(v text)") - cur.execute("INSERT INTO version VALUES(?)", ("v1.6",)) + cur.execute("INSERT INTO version VALUES(?)", ("v1.7",)) db.commit() cur.close() db.close() @@ -914,7 +910,7 @@ class AppConfig(GlobalConfig): cur.execute("SELECT * FROM version WHERE True") version = cur.fetchall() - if version[0][0] != "v1.6": + if version[0][0] != "v1.7": logger.info("数据文件版本更新开始") if_streaming = False # v1.4-->v1.5 @@ -922,10 +918,6 @@ class AppConfig(GlobalConfig): logger.info("数据文件版本更新:v1.4-->v1.5") if_streaming = True - cur.execute("DELETE FROM version WHERE v = ?", ("v1.4",)) - cur.execute("INSERT INTO version VALUES(?)", ("v1.5",)) - db.commit() - member_dict: Dict[str, Dict[str, Union[str, Path]]] = {} if (self.app_path / "config/MaaConfig").exists(): for maa_dir in (self.app_path / "config/MaaConfig").iterdir(): @@ -1044,6 +1036,11 @@ class AppConfig(GlobalConfig): shutil.rmtree(config["Path"] / f"simple") if (config["Path"] / f"beta").exists(): shutil.rmtree(config["Path"] / f"beta") + + cur.execute("DELETE FROM version WHERE v = ?", ("v1.4",)) + cur.execute("INSERT INTO version VALUES(?)", ("v1.5",)) + db.commit() + # v1.5-->v1.6 if version[0][0] == "v1.5" or if_streaming: logger.info("数据文件版本更新:v1.5-->v1.6") @@ -1076,6 +1073,67 @@ class AppConfig(GlobalConfig): winreg.CloseKey(key) except FileNotFoundError: pass + # v1.6-->v1.7 + if version[0][0] == "v1.6" or if_streaming: + logger.info("数据文件版本更新:v1.6-->v1.7") + if_streaming = True + + for MaaConfig in (self.app_path / "config/MaaConfig").iterdir(): + if MaaConfig.is_dir(): + for user in (MaaConfig / "UserData").iterdir(): + if user.is_dir(): + if (user / "config.json").exists(): + with (user / "config.json").open( + encoding="utf-8" + ) as f: + user_config = json.load(f) + user_config["Info"]["Stage"] = user_config["Info"][ + "GameId" + ] + user_config["Info"]["StageMode"] = user_config[ + "Info" + ]["GameIdMode"] + user_config["Info"]["Stage_1"] = user_config[ + "Info" + ]["GameId_1"] + user_config["Info"]["Stage_2"] = user_config[ + "Info" + ]["GameId_2"] + user_config["Info"]["Stage_Remain"] = user_config[ + "Info" + ]["GameId_Remain"] + with (user / "config.json").open( + "w", encoding="utf-8" + ) as f: + json.dump( + user_config, f, ensure_ascii=False, indent=4 + ) + for MaaPlanConfig in (self.app_path / "config/MaaPlanConfig").iterdir(): + if ( + MaaPlanConfig.is_dir() + and (MaaPlanConfig / "config.json").exists() + ): + with (MaaPlanConfig / "config.json").open( + encoding="utf-8" + ) as f: + plan_config = json.load(f) + + for k in self.stage_dict.keys(): + plan_config[k]["Stage"] = plan_config[k]["GameId"] + plan_config[k]["Stage_1"] = plan_config[k]["GameId_1"] + plan_config[k]["Stage_2"] = plan_config[k]["GameId_2"] + plan_config[k]["Stage_Remain"] = plan_config[k][ + "GameId_Remain" + ] + with (MaaPlanConfig / "config.json").open( + "w", encoding="utf-8" + ) as f: + json.dump(plan_config, f, ensure_ascii=False, indent=4) + + cur.execute("DELETE FROM version WHERE v = ?", ("v1.6",)) + cur.execute("INSERT INTO version VALUES(?)", ("v1.7",)) + db.commit() + cur.close() db.close() logger.info("数据文件版本更新完成") @@ -1236,8 +1294,8 @@ class AppConfig(GlobalConfig): for user in member["UserData"].values(): - if user["Config"].get(user["Config"].Info_GameIdMode) == old: - user["Config"].set(user["Config"].Info_GameIdMode, new) + if user["Config"].get(user["Config"].Info_StageMode) == old: + user["Config"].set(user["Config"].Info_StageMode, new) def change_maa_user_info( self, name: str, user_data: Dict[str, Dict[str, Union[str, Path, dict]]] diff --git a/app/core/timer.py b/app/core/timer.py index d91047b..4566d4d 100644 --- a/app/core/timer.py +++ b/app/core/timer.py @@ -53,7 +53,7 @@ class _MainTimer(QObject): def long_timed_task(self): """长时间定期检定任务""" - Config.get_gameid() + Config.get_stage() Config.main_window.setting.show_notice() if Config.get(Config.update_IfAutoUpdate): Config.main_window.setting.check_update() diff --git a/app/models/MAA.py b/app/models/MAA.py index 8d09a06..ac28227 100644 --- a/app/models/MAA.py +++ b/app/models/MAA.py @@ -1450,23 +1450,28 @@ class MaaManager(QObject): user_data["Info"]["MedicineNumb"] ) # 吃理智药数量 data["Configurations"]["Default"]["MainFunction.Stage1"] = ( - user_data["Info"]["GameId"] - if user_data["Info"]["GameId"] != "-" + user_data["Info"]["Stage"] + if user_data["Info"]["Stage"] != "-" else "" ) # 主关卡 data["Configurations"]["Default"]["MainFunction.Stage2"] = ( - user_data["Info"]["GameId_1"] - if user_data["Info"]["GameId_1"] != "-" + user_data["Info"]["Stage_1"] + if user_data["Info"]["Stage_1"] != "-" else "" ) # 备选关卡1 data["Configurations"]["Default"]["MainFunction.Stage3"] = ( - user_data["Info"]["GameId_2"] - if user_data["Info"]["GameId_2"] != "-" + user_data["Info"]["Stage_2"] + if user_data["Info"]["Stage_2"] != "-" else "" ) # 备选关卡2 + data["Configurations"]["Default"]["MainFunction.Stage4"] = ( + user_data["Info"]["Stage_3"] + if user_data["Info"]["Stage_3"] != "-" + else "" + ) # 备选关卡3 data["Configurations"]["Default"]["Fight.RemainingSanityStage"] = ( - user_data["Info"]["GameId_Remain"] - if user_data["Info"]["GameId_Remain"] != "-" + user_data["Info"]["Stage_Remain"] + if user_data["Info"]["Stage_Remain"] != "-" else "" ) # 剩余理智关卡 data["Configurations"]["Default"][ @@ -1486,7 +1491,7 @@ class MaaManager(QObject): data["Configurations"]["Default"][ "Fight.UseRemainingSanityStage" ] = ( - "True" if user_data["Info"]["GameId_Remain"] != "-" else "False" + "True" if user_data["Info"]["Stage_Remain"] != "-" else "False" ) # 使用剩余理智 data["Configurations"]["Default"][ "Fight.UseExpiringMedicine" @@ -1556,23 +1561,28 @@ class MaaManager(QObject): user_data["Info"]["MedicineNumb"] ) # 吃理智药数量 data["Configurations"]["Default"]["MainFunction.Stage1"] = ( - user_data["Info"]["GameId"] - if user_data["Info"]["GameId"] != "-" + user_data["Info"]["Stage"] + if user_data["Info"]["Stage"] != "-" else "" ) # 主关卡 data["Configurations"]["Default"]["MainFunction.Stage2"] = ( - user_data["Info"]["GameId_1"] - if user_data["Info"]["GameId_1"] != "-" + user_data["Info"]["Stage_1"] + if user_data["Info"]["Stage_1"] != "-" else "" ) # 备选关卡1 data["Configurations"]["Default"]["MainFunction.Stage3"] = ( - user_data["Info"]["GameId_2"] - if user_data["Info"]["GameId_2"] != "-" + user_data["Info"]["Stage_2"] + if user_data["Info"]["Stage_2"] != "-" else "" ) # 备选关卡2 + data["Configurations"]["Default"]["MainFunction.Stage4"] = ( + user_data["Info"]["Stage_3"] + if user_data["Info"]["Stage_3"] != "-" + else "" + ) # 备选关卡3 data["Configurations"]["Default"]["Fight.RemainingSanityStage"] = ( - user_data["Info"]["GameId_Remain"] - if user_data["Info"]["GameId_Remain"] != "-" + user_data["Info"]["Stage_Remain"] + if user_data["Info"]["Stage_Remain"] != "-" else "" ) # 剩余理智关卡 data["Configurations"]["Default"][ @@ -1586,7 +1596,7 @@ class MaaManager(QObject): data["Configurations"]["Default"][ "Fight.UseRemainingSanityStage" ] = ( - "True" if user_data["Info"]["GameId_Remain"] != "-" else "False" + "True" if user_data["Info"]["Stage_Remain"] != "-" else "False" ) # 使用剩余理智 # 基建模式 diff --git a/app/ui/member_manager.py b/app/ui/member_manager.py index 30c9b5e..427c255 100644 --- a/app/ui/member_manager.py +++ b/app/ui/member_manager.py @@ -547,14 +547,14 @@ class MemberManager(QWidget): for user_setting in member.user_setting.user_manager.script_list: - user_setting.card_GameIdMode.comboBox.currentIndexChanged.disconnect( - user_setting.switch_gameid_mode + user_setting.card_StageMode.comboBox.currentIndexChanged.disconnect( + user_setting.switch_stage_mode ) - user_setting.card_GameIdMode.reLoadOptions( + user_setting.card_StageMode.reLoadOptions( plan_list[0], plan_list[1] ) - user_setting.card_GameIdMode.comboBox.currentIndexChanged.connect( - user_setting.switch_gameid_mode + user_setting.card_StageMode.comboBox.currentIndexChanged.connect( + user_setting.switch_stage_mode ) self.refresh_plan_info() @@ -578,7 +578,7 @@ class MemberManager(QWidget): member.user_setting.user_manager.user_dashboard.load_info() for user_setting in member.user_setting.user_manager.script_list: - user_setting.switch_gameid_mode() + user_setting.switch_stage_mode() class MemberSettingBox(QWidget): """脚本管理子页面组""" @@ -1217,8 +1217,8 @@ class MemberManager(QWidget): """清空所有子界面""" for sub_interface in self.script_list: - Config.gameid_refreshed.disconnect( - sub_interface.refresh_gameid + Config.stage_refreshed.disconnect( + sub_interface.refresh_stage ) Config.PASSWORD_refreshed.disconnect( sub_interface.refresh_password @@ -1254,7 +1254,7 @@ class MemberManager(QWidget): self.name = name self.dashboard = TableWidget(self) - self.dashboard.setColumnCount(11) + self.dashboard.setColumnCount(12) self.dashboard.setHorizontalHeaderLabels( [ "用户名", @@ -1266,6 +1266,7 @@ class MemberManager(QWidget): "关卡选择", "备选 - 1", "备选 - 2", + "备选 - 3", "剩余理智", "详", ] @@ -1276,14 +1277,14 @@ class MemberManager(QWidget): self.dashboard.horizontalHeader().setSectionResizeMode( col, QHeaderView.ResizeMode.ResizeToContents ) - for col in range(6, 10): + for col in range(6, 11): self.dashboard.horizontalHeader().setSectionResizeMode( col, QHeaderView.ResizeMode.Stretch ) self.dashboard.horizontalHeader().setSectionResizeMode( - 10, QHeaderView.ResizeMode.Fixed + 11, QHeaderView.ResizeMode.Fixed ) - self.dashboard.setColumnWidth(10, 32) + self.dashboard.setColumnWidth(11, 32) self.viewLayout.addWidget(self.dashboard) self.viewLayout.setContentsMargins(3, 0, 3, 3) @@ -1319,7 +1320,7 @@ class MemberManager(QWidget): else "本周剿灭未完成" ) - gameid_info = config.get_plan_info() + stage_info = config.get_plan_info() button = PrimaryToolButton( FluentIcon.CHEVRON_RIGHT, self @@ -1369,74 +1370,86 @@ class MemberManager(QWidget): self.dashboard.setItem( int(name[3:]) - 1, 5, - QTableWidgetItem(str(gameid_info["MedicineNumb"])), + QTableWidgetItem(str(stage_info["MedicineNumb"])), ) self.dashboard.setItem( int(name[3:]) - 1, 6, QTableWidgetItem( - Config.gameid_dict["ALL"]["text"][ - Config.gameid_dict["ALL"]["value"].index( - gameid_info["GameId"] + Config.stage_dict["ALL"]["text"][ + Config.stage_dict["ALL"]["value"].index( + stage_info["Stage"] ) ] - if gameid_info["GameId"] - in Config.gameid_dict["ALL"]["value"] - else gameid_info["GameId"] + if stage_info["Stage"] + in Config.stage_dict["ALL"]["value"] + else stage_info["Stage"] ), ) self.dashboard.setItem( int(name[3:]) - 1, 7, QTableWidgetItem( - Config.gameid_dict["ALL"]["text"][ - Config.gameid_dict["ALL"]["value"].index( - gameid_info["GameId_1"] + Config.stage_dict["ALL"]["text"][ + Config.stage_dict["ALL"]["value"].index( + stage_info["Stage"] ) ] - if gameid_info["GameId_1"] - in Config.gameid_dict["ALL"]["value"] - else gameid_info["GameId_1"] + if stage_info["Stage"] + in Config.stage_dict["ALL"]["value"] + else stage_info["Stage"] ), ) self.dashboard.setItem( int(name[3:]) - 1, 8, QTableWidgetItem( - Config.gameid_dict["ALL"]["text"][ - Config.gameid_dict["ALL"]["value"].index( - gameid_info["GameId_2"] + Config.stage_dict["ALL"]["text"][ + Config.stage_dict["ALL"]["value"].index( + stage_info["Stage_2"] ) ] - if gameid_info["GameId_2"] - in Config.gameid_dict["ALL"]["value"] - else gameid_info["GameId_2"] + if stage_info["Stage_2"] + in Config.stage_dict["ALL"]["value"] + else stage_info["Stage_2"] ), ) self.dashboard.setItem( int(name[3:]) - 1, 9, + QTableWidgetItem( + Config.stage_dict["ALL"]["text"][ + Config.stage_dict["ALL"]["value"].index( + stage_info["Stage_3"] + ) + ] + if stage_info["Stage_3"] + in Config.stage_dict["ALL"]["value"] + else stage_info["Stage_3"] + ), + ) + self.dashboard.setItem( + int(name[3:]) - 1, + 10, QTableWidgetItem( "不使用" - if gameid_info["GameId_Remain"] == "-" + if stage_info["Stage_Remain"] == "-" else ( ( - Config.gameid_dict["ALL"]["text"][ - Config.gameid_dict["ALL"][ + Config.stage_dict["ALL"]["text"][ + Config.stage_dict["ALL"][ "value" - ].index( - gameid_info["GameId_Remain"] - ) + ].index(stage_info["Stage_Remain"]) ] ) - if gameid_info["GameId_Remain"] - in Config.gameid_dict["ALL"]["value"] - else gameid_info["GameId_Remain"] + if stage_info["Stage_Remain"] + in Config.stage_dict["ALL"]["value"] + else stage_info["Stage_Remain"] ) ), ) self.dashboard.setCellWidget( - int(name[3:]) - 1, 10, button + int(name[3:]) - 1, 11, button ) class UserMemberSettingBox(HeaderCardWidget): @@ -1495,17 +1508,6 @@ class MemberManager(QWidget): configItem=self.config.Info_Mode, parent=self, ) - self.card_GameIdMode = NoOptionComboBoxSettingCard( - icon=FluentIcon.DICTIONARY, - title="关卡配置模式", - content="刷理智关卡号的配置模式", - value=plan_list[0], - texts=plan_list[1], - qconfig=self.config, - configItem=self.config.Info_GameIdMode, - parent=self, - ) - self.card_GameIdMode.comboBox.setMinimumWidth(0) self.card_Server = ComboBoxSettingCard( icon=FluentIcon.PROJECTOR, title="服务器", @@ -1602,48 +1604,69 @@ class MemberManager(QWidget): parent=self, ) self.card_SeriesNumb.comboBox.setMinimumWidth(150) - self.card_GameId = EditableComboBoxWithPlanSettingCard( + self.card_StageMode = NoOptionComboBoxSettingCard( + icon=FluentIcon.DICTIONARY, + title="关卡配置模式", + content="刷理智关卡号的配置模式", + value=plan_list[0], + texts=plan_list[1], + qconfig=self.config, + configItem=self.config.Info_StageMode, + parent=self, + ) + self.card_StageMode.comboBox.setMinimumWidth(150) + self.card_Stage = EditableComboBoxWithPlanSettingCard( icon=FluentIcon.GAME, title="关卡选择", content="按下回车以添加自定义关卡号", - value=Config.gameid_dict["ALL"]["value"], - texts=Config.gameid_dict["ALL"]["text"], + value=Config.stage_dict["ALL"]["value"], + texts=Config.stage_dict["ALL"]["text"], qconfig=self.config, - configItem=self.config.Info_GameId, + configItem=self.config.Info_Stage, parent=self, ) - self.card_GameId_1 = EditableComboBoxWithPlanSettingCard( + self.card_Stage_1 = EditableComboBoxWithPlanSettingCard( icon=FluentIcon.GAME, title="备选关卡 - 1", content="按下回车以添加自定义关卡号", - value=Config.gameid_dict["ALL"]["value"], - texts=Config.gameid_dict["ALL"]["text"], + value=Config.stage_dict["ALL"]["value"], + texts=Config.stage_dict["ALL"]["text"], qconfig=self.config, - configItem=self.config.Info_GameId_1, + configItem=self.config.Info_Stage_1, parent=self, ) - self.card_GameId_2 = EditableComboBoxWithPlanSettingCard( + self.card_Stage_2 = EditableComboBoxWithPlanSettingCard( icon=FluentIcon.GAME, title="备选关卡 - 2", content="按下回车以添加自定义关卡号", - value=Config.gameid_dict["ALL"]["value"], - texts=Config.gameid_dict["ALL"]["text"], + value=Config.stage_dict["ALL"]["value"], + texts=Config.stage_dict["ALL"]["text"], qconfig=self.config, - configItem=self.config.Info_GameId_2, + configItem=self.config.Info_Stage_2, parent=self, ) - self.card_GameId_Remain = ( + self.card_Stage_3 = EditableComboBoxWithPlanSettingCard( + icon=FluentIcon.GAME, + title="备选关卡 - 3", + content="按下回车以添加自定义关卡号", + value=Config.stage_dict["ALL"]["value"], + texts=Config.stage_dict["ALL"]["text"], + qconfig=self.config, + configItem=self.config.Info_Stage_3, + parent=self, + ) + self.card_Stage_Remain = ( EditableComboBoxWithPlanSettingCard( icon=FluentIcon.GAME, title="剩余理智关卡", content="按下回车以添加自定义关卡号", - value=Config.gameid_dict["ALL"]["value"], + value=Config.stage_dict["ALL"]["value"], texts=[ "不使用" if _ == "当前/上次" else _ - for _ in Config.gameid_dict["ALL"]["text"] + for _ in Config.stage_dict["ALL"]["text"] ], qconfig=self.config, - configItem=self.config.Info_GameId_Remain, + configItem=self.config.Info_Stage_Remain, parent=self, ) ) @@ -1822,7 +1845,6 @@ class MemberManager(QWidget): h1_layout.addWidget(self.card_Id) h2_layout = QHBoxLayout() h2_layout.addWidget(self.card_Mode) - h2_layout.addWidget(self.card_GameIdMode) h2_layout.addWidget(self.card_Server) h3_layout = QHBoxLayout() h3_layout.addWidget(self.card_Status) @@ -1838,11 +1860,14 @@ class MemberManager(QWidget): h6_layout.addWidget(self.card_MedicineNumb) h6_layout.addWidget(self.card_SeriesNumb) h7_layout = QHBoxLayout() - h7_layout.addWidget(self.card_GameId) - h7_layout.addWidget(self.card_GameId_1) + h7_layout.addWidget(self.card_StageMode) + h7_layout.addWidget(self.card_Stage) h8_layout = QHBoxLayout() - h8_layout.addWidget(self.card_GameId_2) - h8_layout.addWidget(self.card_GameId_Remain) + h8_layout.addWidget(self.card_Stage_1) + h8_layout.addWidget(self.card_Stage_2) + h9_layout = QHBoxLayout() + h9_layout.addWidget(self.card_Stage_3) + h9_layout.addWidget(self.card_Stage_Remain) Layout = QVBoxLayout() Layout.addLayout(h1_layout) @@ -1854,6 +1879,7 @@ class MemberManager(QWidget): Layout.addLayout(h6_layout) Layout.addLayout(h7_layout) Layout.addLayout(h8_layout) + Layout.addLayout(h9_layout) Layout.addWidget(self.card_Skland) Layout.addWidget(self.card_TaskSet) Layout.addWidget(self.card_NotifySet) @@ -1878,14 +1904,14 @@ class MemberManager(QWidget): ) self.card_TaskSet.clicked.connect(self.set_task) self.card_NotifySet.clicked.connect(self.set_notify) - self.card_GameIdMode.comboBox.currentIndexChanged.connect( - self.switch_gameid_mode + self.card_StageMode.comboBox.currentIndexChanged.connect( + self.switch_stage_mode ) - Config.gameid_refreshed.connect(self.refresh_gameid) + Config.stage_refreshed.connect(self.refresh_stage) Config.PASSWORD_refreshed.connect(self.refresh_password) self.switch_mode() - self.switch_gameid_mode() + self.switch_stage_mode() self.switch_infrastructure() def switch_mode(self) -> None: @@ -1904,37 +1930,39 @@ class MemberManager(QWidget): self.card_Annihilation.button.setVisible(True) self.card_Routine.setVisible(True) - def switch_gameid_mode(self) -> None: + def switch_stage_mode(self) -> None: for card, name in zip( [ self.card_MedicineNumb, self.card_SeriesNumb, - self.card_GameId, - self.card_GameId_1, - self.card_GameId_2, - self.card_GameId_Remain, + self.card_Stage, + self.card_Stage_1, + self.card_Stage_2, + self.card_Stage_3, + self.card_Stage_Remain, ], [ "MedicineNumb", "SeriesNumb", - "GameId", - "GameId_1", - "GameId_2", - "GameId_Remain", + "Stage", + "Stage_1", + "Stage_2", + "Stage_3", + "Stage_Remain", ], ): card.switch_mode( - self.config.get(self.config.Info_GameIdMode)[:2] + self.config.get(self.config.Info_StageMode)[:2] ) if ( - self.config.get(self.config.Info_GameIdMode) + self.config.get(self.config.Info_StageMode) != "固定" ): card.change_plan( Config.plan_dict[ - self.config.get(self.config.Info_GameIdMode) + self.config.get(self.config.Info_StageMode) ]["Config"].get_current_info(name) ) @@ -1959,26 +1987,27 @@ class MemberManager(QWidget): "自定义基建配置文件未生效" ) - def refresh_gameid(self): + def refresh_stage(self): - self.card_GameId.reLoadOptions( - Config.gameid_dict["ALL"]["value"], - Config.gameid_dict["ALL"]["text"], + self.card_Stage.reLoadOptions( + Config.stage_dict["ALL"]["value"], + Config.stage_dict["ALL"]["text"], ) - self.card_GameId_1.reLoadOptions( - Config.gameid_dict["ALL"]["value"], - Config.gameid_dict["ALL"]["text"], + self.card_Stage_1.reLoadOptions( + Config.stage_dict["ALL"]["value"], + Config.stage_dict["ALL"]["text"], ) - self.card_GameId_2.reLoadOptions( - Config.gameid_dict["ALL"]["value"], - Config.gameid_dict["ALL"]["text"], + self.card_Stage_2.reLoadOptions( + Config.stage_dict["ALL"]["value"], + Config.stage_dict["ALL"]["text"], ) - self.card_GameId_Remain.reLoadOptions( - Config.gameid_dict["ALL"]["value"], - [ - "不使用" if _ == "当前/上次" else _ - for _ in Config.gameid_dict["ALL"]["text"] - ], + self.card_Stage_3.reLoadOptions( + Config.stage_dict["ALL"]["value"], + Config.stage_dict["ALL"]["text"], + ) + self.card_Stage_Remain.reLoadOptions( + Config.stage_dict["ALL"]["value"], + Config.stage_dict["ALL"]["text"], ) def refresh_password(self): diff --git a/app/ui/plan_manager.py b/app/ui/plan_manager.py index 7ebd9bf..5c6c84d 100644 --- a/app/ui/plan_manager.py +++ b/app/ui/plan_manager.py @@ -324,7 +324,7 @@ class PlanManager(QWidget): """清空所有子界面""" for sub_interface in self.script_list: - Config.gameid_refreshed.disconnect(sub_interface.refresh_gameid) + Config.stage_refreshed.disconnect(sub_interface.refresh_stage) self.stackedWidget.removeWidget(sub_interface) sub_interface.deleteLater() self.script_list.clear() @@ -372,7 +372,7 @@ class PlanManager(QWidget): self.table = TableWidget(self) self.table.setColumnCount(8) - self.table.setRowCount(6) + self.table.setRowCount(7) self.table.setHorizontalHeaderLabels( ["全局", "周一", "周二", "周三", "周四", "周五", "周六", "周日"] ) @@ -383,6 +383,7 @@ class PlanManager(QWidget): "关卡选择", "备选 - 1", "备选 - 2", + "备选 - 3", "剩余理智", ] ) @@ -392,7 +393,7 @@ class PlanManager(QWidget): self.table.horizontalHeader().setSectionResizeMode( col, QHeaderView.ResizeMode.Stretch ) - for row in range(6): + for row in range(7): self.table.verticalHeader().setSectionResizeMode( row, QHeaderView.ResizeMode.ResizeToContents ) @@ -427,21 +428,21 @@ class PlanManager(QWidget): configItem=configItem, parent=self, ) - elif name == "GameId_Remain": + elif name == "Stage_Remain": self.item_dict[group][name] = EditableComboBoxSetting( - value=Config.gameid_dict[group]["value"], + value=Config.stage_dict[group]["value"], texts=[ "不使用" if _ == "当前/上次" else _ - for _ in Config.gameid_dict[group]["text"] + for _ in Config.stage_dict[group]["text"] ], qconfig=self.config, configItem=configItem, parent=self, ) - elif "GameId" in name: + elif "Stage" in name: self.item_dict[group][name] = EditableComboBoxSetting( - value=Config.gameid_dict[group]["value"], - texts=Config.gameid_dict[group]["text"], + value=Config.stage_dict[group]["value"], + texts=Config.stage_dict[group]["text"], qconfig=self.config, configItem=configItem, parent=self, @@ -459,7 +460,7 @@ class PlanManager(QWidget): self.viewLayout.setContentsMargins(3, 0, 3, 3) self.card_Mode.comboBox.currentIndexChanged.connect(self.switch_mode) - Config.gameid_refreshed.connect(self.refresh_gameid) + Config.stage_refreshed.connect(self.refresh_stage) self.switch_mode() @@ -473,25 +474,25 @@ class PlanManager(QWidget): == (self.config.get(self.config.Info_Mode) == "ALL") ) - def refresh_gameid(self): + def refresh_stage(self): for group, name_dict in self.item_dict.items(): for name, setting_item in name_dict.items(): - if name == "GameId_Remain": + if name == "Stage_Remain": setting_item.reLoadOptions( - Config.gameid_dict[group]["value"], + Config.stage_dict[group]["value"], [ "不使用" if _ == "当前/上次" else _ - for _ in Config.gameid_dict[group]["text"] + for _ in Config.stage_dict[group]["text"] ], ) - elif "GameId" in name: + elif "Stage" in name: setting_item.reLoadOptions( - Config.gameid_dict[group]["value"], - Config.gameid_dict[group]["text"], + Config.stage_dict[group]["value"], + Config.stage_dict[group]["text"], ) diff --git a/resources/docs/MAA_config_info.txt b/resources/docs/MAA_config_info.txt index c7e0d9d..8bce40e 100644 --- a/resources/docs/MAA_config_info.txt +++ b/resources/docs/MAA_config_info.txt @@ -24,6 +24,7 @@ "MainFunction.Stage1": "" #主关卡 "MainFunction.Stage2": "" #备选关卡1 "MainFunction.Stage3": "" #备选关卡2 +"MainFunction.Stage4": "" #备选关卡3 "Fight.RemainingSanityStage": "Annihilation" #剩余理智关卡 "MainFunction.Series.Quantity": "1" #连战次数 "Penguin.IsDrGrandet": "True" #博朗台模式 diff --git a/resources/version.json b/resources/version.json index 7d6cd9b..87943f3 100644 --- a/resources/version.json +++ b/resources/version.json @@ -1,6 +1,14 @@ { - "main_version": "4.4.0.2", + "main_version": "4.4.0.3", "version_info": { + "4.4.0.3": { + "修复BUG": [ + "适配 MAA 备选关卡字段修改" + ], + "程序优化": [ + "`GameId`字段改为 `Stage`,与 MAA 保持一致" + ] + }, "4.4.0.2": { "新增功能": [ "进一步适配三月七相关配置项" From 54917fbe6de3ca5ac4a07bc88cdf0178a23f88dc Mon Sep 17 00:00:00 2001 From: DLmaster361 Date: Fri, 11 Jul 2025 18:38:58 +0800 Subject: [PATCH 05/13] =?UTF-8?q?fix(general):=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E6=97=A0=E6=88=90=E5=8A=9F=E6=97=A5=E5=BF=97=E6=97=B6=E7=9A=84?= =?UTF-8?q?=E8=84=9A=E6=9C=AC=E5=88=A4=E5=AE=9A=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/core/config.py | 3 +++ app/models/general.py | 17 +++++++++++++---- app/ui/member_manager.py | 9 +++++++++ app/utils/ProcessManager.py | 6 ++++-- resources/version.json | 3 ++- 5 files changed, 31 insertions(+), 7 deletions(-) diff --git a/app/core/config.py b/app/core/config.py index 3a341fc..42749e6 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -601,6 +601,9 @@ class GeneralConfig(LQConfig): "Script", "ScriptPath", ".", FileValidator() ) self.Script_Arguments = ConfigItem("Script", "Arguments", "") + self.Script_IfTrackProcess = ConfigItem( + "Script", "IfTrackProcess", False, BoolValidator() + ) self.Script_ConfigPath = ConfigItem( "Script", "ConfigPath", ".", FileValidator() ) diff --git a/app/models/general.py b/app/models/general.py index e9904e2..99fa78d 100644 --- a/app/models/general.py +++ b/app/models/general.py @@ -152,9 +152,11 @@ class GeneralManager(QObject): self.set["Script"]["LogTimeStart"] - 1, self.set["Script"]["LogTimeEnd"], ] - self.success_log = [ - _.strip() for _ in self.set["Script"]["SuccessLog"].split("|") - ] + self.success_log = ( + [_.strip() for _ in self.set["Script"]["SuccessLog"].split("|")] + if self.set["Script"]["SuccessLog"] + else [] + ) self.error_log = [_.strip() for _ in self.set["Script"]["ErrorLog"].split("|")] def run(self): @@ -304,9 +306,13 @@ class GeneralManager(QObject): ) # 运行脚本任务 + logger.info( + f"{self.name} | 运行脚本任务:{self.script_exe_path},参数:{self.set['Script']['Arguments']}" + ) self.script_process_manager.open_process( self.script_exe_path, str(self.set["Script"]["Arguments"]).split(" "), + tracking_time=60 if self.set["Script"]["IfTrackProcess"] else 0, ) # 监测运行状态 @@ -430,7 +436,10 @@ class GeneralManager(QObject): try: # 创建通用脚本任务 logger.info(f"{self.name} | 无参数启动通用脚本:{self.script_exe_path}") - self.script_process_manager.open_process(self.script_exe_path) + self.script_process_manager.open_process( + self.script_exe_path, + tracking_time=60 if self.set["Script"]["IfTrackProcess"] else 0, + ) # 记录当前时间 start_time = datetime.now() diff --git a/app/ui/member_manager.py b/app/ui/member_manager.py index 427c255..62a6e61 100644 --- a/app/ui/member_manager.py +++ b/app/ui/member_manager.py @@ -2339,6 +2339,14 @@ class MemberManager(QWidget): configItem=self.config.Script_Arguments, parent=self, ) + self.card_IfTrackProcess = SwitchSettingCard( + icon=FluentIcon.PAGE_RIGHT, + title="追踪脚本子进程", + content="启用后将在脚本启动后 60s 内追踪其子进程,并仅在所有子进程结束后判定脚本中止", + qconfig=self.config, + configItem=self.config.Script_IfTrackProcess, + parent=self, + ) self.card_ConfigPath = PathSettingCard( icon=FluentIcon.FOLDER, title="脚本配置文件路径 - [必填]", @@ -2438,6 +2446,7 @@ class MemberManager(QWidget): Layout.addWidget(self.card_RootPath) Layout.addWidget(self.card_ScriptPath) Layout.addWidget(self.card_Arguments) + Layout.addWidget(self.card_IfTrackProcess) Layout.addWidget(self.card_ConfigPath) Layout.addWidget(self.card_LogPath) Layout.addWidget(self.card_LogPathFormat) diff --git a/app/utils/ProcessManager.py b/app/utils/ProcessManager.py index 0d428b5..c06d21f 100644 --- a/app/utils/ProcessManager.py +++ b/app/utils/ProcessManager.py @@ -65,6 +65,7 @@ class ProcessManager(QObject): process = subprocess.Popen( [path, *args], + cwd=path.parent, creationflags=subprocess.CREATE_NO_WINDOW, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, @@ -93,8 +94,9 @@ class ProcessManager(QObject): self.tracked_pids.add(self.main_pid) # 递归获取所有子进程 - for child in main_proc.children(recursive=True): - self.tracked_pids.add(child.pid) + if tracking_time: + for child in main_proc.children(recursive=True): + self.tracked_pids.add(child.pid) except psutil.NoSuchProcess: pass diff --git a/resources/version.json b/resources/version.json index 87943f3..4c7f7b3 100644 --- a/resources/version.json +++ b/resources/version.json @@ -3,7 +3,8 @@ "version_info": { "4.4.0.3": { "修复BUG": [ - "适配 MAA 备选关卡字段修改" + "适配 MAA 备选关卡字段修改", + "修复无成功日志时的脚本判定逻辑" ], "程序优化": [ "`GameId`字段改为 `Stage`,与 MAA 保持一致" From 6d3fda50d35ac7e63d560b61edc9200874bdb2ec Mon Sep 17 00:00:00 2001 From: DLmaster361 Date: Sat, 12 Jul 2025 01:27:27 +0800 Subject: [PATCH 06/13] =?UTF-8?q?feat(security):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E9=87=8D=E7=BD=AE=E7=AE=A1=E7=90=86=E5=AF=86=E9=92=A5=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/core/config.py | 111 +++++++++++++++++++++------------------ app/services/security.py | 34 ++++++++---- app/ui/main_window.py | 2 +- app/ui/member_manager.py | 18 +++---- app/ui/setting.py | 74 ++++++++++++++++++++++++-- requirements.txt | 2 +- resources/version.json | 10 +++- 7 files changed, 173 insertions(+), 78 deletions(-) diff --git a/app/core/config.py b/app/core/config.py index 42749e6..1fb5cfa 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -706,7 +706,7 @@ class GeneralSubConfig(LQConfig): class AppConfig(GlobalConfig): - VERSION = "4.4.0.3" + VERSION = "4.4.0.4" stage_refreshed = Signal() PASSWORD_refreshed = Signal() @@ -1081,57 +1081,66 @@ class AppConfig(GlobalConfig): logger.info("数据文件版本更新:v1.6-->v1.7") if_streaming = True - for MaaConfig in (self.app_path / "config/MaaConfig").iterdir(): - if MaaConfig.is_dir(): - for user in (MaaConfig / "UserData").iterdir(): - if user.is_dir(): - if (user / "config.json").exists(): - with (user / "config.json").open( - encoding="utf-8" - ) as f: - user_config = json.load(f) - user_config["Info"]["Stage"] = user_config["Info"][ - "GameId" - ] - user_config["Info"]["StageMode"] = user_config[ - "Info" - ]["GameIdMode"] - user_config["Info"]["Stage_1"] = user_config[ - "Info" - ]["GameId_1"] - user_config["Info"]["Stage_2"] = user_config[ - "Info" - ]["GameId_2"] - user_config["Info"]["Stage_Remain"] = user_config[ - "Info" - ]["GameId_Remain"] - with (user / "config.json").open( - "w", encoding="utf-8" - ) as f: - json.dump( - user_config, f, ensure_ascii=False, indent=4 - ) - for MaaPlanConfig in (self.app_path / "config/MaaPlanConfig").iterdir(): - if ( - MaaPlanConfig.is_dir() - and (MaaPlanConfig / "config.json").exists() - ): - with (MaaPlanConfig / "config.json").open( - encoding="utf-8" - ) as f: - plan_config = json.load(f) + if (self.app_path / "config/MaaConfig").exists(): - for k in self.stage_dict.keys(): - plan_config[k]["Stage"] = plan_config[k]["GameId"] - plan_config[k]["Stage_1"] = plan_config[k]["GameId_1"] - plan_config[k]["Stage_2"] = plan_config[k]["GameId_2"] - plan_config[k]["Stage_Remain"] = plan_config[k][ - "GameId_Remain" - ] - with (MaaPlanConfig / "config.json").open( - "w", encoding="utf-8" - ) as f: - json.dump(plan_config, f, ensure_ascii=False, indent=4) + for MaaConfig in (self.app_path / "config/MaaConfig").iterdir(): + if MaaConfig.is_dir(): + for user in (MaaConfig / "UserData").iterdir(): + if user.is_dir(): + if (user / "config.json").exists(): + with (user / "config.json").open( + encoding="utf-8" + ) as f: + user_config = json.load(f) + user_config["Info"]["Stage"] = user_config[ + "Info" + ]["GameId"] + user_config["Info"]["StageMode"] = user_config[ + "Info" + ]["GameIdMode"] + user_config["Info"]["Stage_1"] = user_config[ + "Info" + ]["GameId_1"] + user_config["Info"]["Stage_2"] = user_config[ + "Info" + ]["GameId_2"] + user_config["Info"]["Stage_Remain"] = ( + user_config["Info"]["GameId_Remain"] + ) + with (user / "config.json").open( + "w", encoding="utf-8" + ) as f: + json.dump( + user_config, + f, + ensure_ascii=False, + indent=4, + ) + + if (self.app_path / "config/MaaPlanConfig").exists(): + for MaaPlanConfig in ( + self.app_path / "config/MaaPlanConfig" + ).iterdir(): + if ( + MaaPlanConfig.is_dir() + and (MaaPlanConfig / "config.json").exists() + ): + with (MaaPlanConfig / "config.json").open( + encoding="utf-8" + ) as f: + plan_config = json.load(f) + + for k in self.stage_dict.keys(): + plan_config[k]["Stage"] = plan_config[k]["GameId"] + plan_config[k]["Stage_1"] = plan_config[k]["GameId_1"] + plan_config[k]["Stage_2"] = plan_config[k]["GameId_2"] + plan_config[k]["Stage_Remain"] = plan_config[k][ + "GameId_Remain" + ] + with (MaaPlanConfig / "config.json").open( + "w", encoding="utf-8" + ) as f: + json.dump(plan_config, f, ensure_ascii=False, indent=4) cur.execute("DELETE FROM version WHERE v = ?", ("v1.6",)) cur.execute("INSERT INTO version VALUES(?)", ("v1.7",)) diff --git a/app/services/security.py b/app/services/security.py index dda8dc1..b7bcc1a 100644 --- a/app/services/security.py +++ b/app/services/security.py @@ -147,22 +147,36 @@ class CryptoHandler: for member in Config.member_dict.values(): # 使用旧管理密钥解密 - for user in member["UserData"].values(): - user["Password"] = self.AUTO_decryptor( - user["Config"].get(user["Config"].Info_Password), PASSWORD_old - ) + if member["Type"] == "Maa": + for user in member["UserData"].values(): + user["Password"] = self.AUTO_decryptor( + user["Config"].get(user["Config"].Info_Password), PASSWORD_old + ) self.get_PASSWORD(PASSWORD_new) for member in Config.member_dict.values(): # 使用新管理密钥重新加密 - for user in member["UserData"].values(): - user["Config"].set( - user["Config"].Info_Password, self.AUTO_encryptor(user["Password"]) - ) - user["Password"] = None - del user["Password"] + if member["Type"] == "Maa": + for user in member["UserData"].values(): + user["Config"].set( + user["Config"].Info_Password, + self.AUTO_encryptor(user["Password"]), + ) + user["Password"] = None + del user["Password"] + + def reset_PASSWORD(self, PASSWORD_new: str) -> None: + """重置管理密钥""" + + self.get_PASSWORD(PASSWORD_new) + + for member in Config.member_dict.values(): + + if member["Type"] == "Maa": + for user in member["UserData"].values(): + user["Config"].set(user["Config"].Info_Password, "") def win_encryptor( self, note: str, description: str = None, entropy: bytes = None diff --git a/app/ui/main_window.py b/app/ui/main_window.py index f95fce0..595c751 100644 --- a/app/ui/main_window.py +++ b/app/ui/main_window.py @@ -469,7 +469,7 @@ class AUTO_MAA(MSFluentWindow): logger.warning("启动主任务失败:未找到有效的主任务配置文件") MainInfoBar.push_info_bar( - "warning", "启动主任务失败", "“调度队列_1”与“脚本_1”均不存在", -1 + "warning", "启动主任务失败", "「调度队列_1」与「脚本_1」均不存在", -1 ) def __currentChanged(self, index: int) -> None: diff --git a/app/ui/member_manager.py b/app/ui/member_manager.py index 62a6e61..406c883 100644 --- a/app/ui/member_manager.py +++ b/app/ui/member_manager.py @@ -804,7 +804,7 @@ class MemberManager(QWidget): self.card_TaskTransitionMethod = ComboBoxSettingCard( icon=FluentIcon.PAGE_RIGHT, title="任务切换方式", - content="相邻两个任务间的切换方式,使用“详细”配置的用户固定为“重启模拟器”", + content="相邻两个任务间的切换方式,使用「详细」配置的用户固定为「重启模拟器」", texts=["直接切换账号", "重启明日方舟", "重启模拟器"], qconfig=self.config, configItem=self.config.RunSet_TaskTransitionMethod, @@ -813,7 +813,7 @@ class MemberManager(QWidget): self.card_ProxyTimesLimit = SpinBoxSettingCard( icon=FluentIcon.PAGE_RIGHT, title="用户单日代理次数上限", - content="当用户本日代理成功次数达到该阈值时跳过代理,阈值为“0”时视为无代理次数上限", + content="当用户本日代理成功次数达到该阈值时跳过代理,阈值为「0」时视为无代理次数上限", range=(0, 1024), qconfig=self.config, configItem=self.config.RunSet_ProxyTimesLimit, @@ -2187,7 +2187,7 @@ class MemberManager(QWidget): self.card_ServerChanChannel = LineEditSettingCard( icon=FluentIcon.PAGE_RIGHT, title="用户ServerChanChannel代码", - content="留空则默认,多个请使用“|”隔开", + content="留空则默认,多个请使用「|」隔开", text="请输入Channel代码,仅SCT生效", qconfig=self.config, configItem=self.config.Notify_ServerChanChannel, @@ -2196,7 +2196,7 @@ class MemberManager(QWidget): self.card_ServerChanTag = LineEditSettingCard( icon=FluentIcon.PAGE_RIGHT, title="用户Tag内容", - content="留空则默认,多个请使用“|”隔开", + content="留空则默认,多个请使用「|」隔开", text="请输入加入推送的Tag,仅SC3生效", qconfig=self.config, configItem=self.config.Notify_ServerChanTag, @@ -2404,7 +2404,7 @@ class MemberManager(QWidget): self.card_SuccessLog = LineEditSettingCard( icon=FluentIcon.PAGE_RIGHT, title="脚本成功日志", - content="任务成功完成时出现的日志,多条请使用“|”隔开", + content="任务成功完成时出现的日志,多条请使用「|」隔开", text="请输入脚本成功日志内容", qconfig=self.config, configItem=self.config.Script_SuccessLog, @@ -2413,7 +2413,7 @@ class MemberManager(QWidget): self.card_ErrorLog = LineEditSettingCard( icon=FluentIcon.PAGE_RIGHT, title="脚本异常日志 - [必填]", - content="脚本运行异常时的日志内容,多条请使用“|”隔开", + content="脚本运行异常时的日志内容,多条请使用「|」隔开", text="请输入脚本异常日志内容", qconfig=self.config, configItem=self.config.Script_ErrorLog, @@ -2580,7 +2580,7 @@ class MemberManager(QWidget): self.card_ProxyTimesLimit = SpinBoxSettingCard( icon=FluentIcon.PAGE_RIGHT, title="子配置单日代理次数上限", - content="当子配置本日代理成功次数达到该阈值时跳过代理,阈值为“0”时视为无代理次数上限", + content="当子配置本日代理成功次数达到该阈值时跳过代理,阈值为「0」时视为无代理次数上限", range=(0, 1024), qconfig=self.config, configItem=self.config.Run_ProxyTimesLimit, @@ -3339,7 +3339,7 @@ class MemberManager(QWidget): self.card_ServerChanChannel = LineEditSettingCard( icon=FluentIcon.PAGE_RIGHT, title="用户ServerChanChannel代码", - content="留空则默认,多个请使用“|”隔开", + content="留空则默认,多个请使用「|」隔开", text="请输入Channel代码,仅SCT生效", qconfig=self.config, configItem=self.config.Notify_ServerChanChannel, @@ -3348,7 +3348,7 @@ class MemberManager(QWidget): self.card_ServerChanTag = LineEditSettingCard( icon=FluentIcon.PAGE_RIGHT, title="用户Tag内容", - content="留空则默认,多个请使用“|”隔开", + content="留空则默认,多个请使用「|」隔开", text="请输入加入推送的Tag,仅SC3生效", qconfig=self.config, configItem=self.config.Notify_ServerChanTag, diff --git a/app/ui/setting.py b/app/ui/setting.py index b1b0808..68d049e 100644 --- a/app/ui/setting.py +++ b/app/ui/setting.py @@ -86,6 +86,7 @@ class Setting(QWidget): ) self.start.card_IfSelfStart.checkedChanged.connect(System.set_SelfStart) self.security.card_changePASSWORD.clicked.connect(self.change_PASSWORD) + self.security.card_resetPASSWORD.clicked.connect(self.reset_PASSWORD) self.updater.card_CheckUpdate.clicked.connect( lambda: self.check_update(if_show=True) ) @@ -119,7 +120,7 @@ class Setting(QWidget): choice = MessageBox( "授权声明", - "开启“托管bilibili游戏隐私政策”功能,即代表您已完整阅读并同意《哔哩哔哩弹幕网用户使用协议》、《哔哩哔哩隐私政策》和《哔哩哔哩游戏中心用户协议》,并授权AUTO_MAA在其认定需要时以其认定合适的方法替您处理相关弹窗\n\n是否同意授权?", + "开启「托管bilibili游戏隐私政策」功能,即代表您已完整阅读并同意《哔哩哔哩弹幕网用户使用协议》、《哔哩哔哩隐私政策》和《哔哩哔哩游戏中心用户协议》,并授权AUTO_MAA在其认定需要时以其认定合适的方法替您处理相关弹窗\n\n是否同意授权?", self.window(), ) if choice.exec(): @@ -147,7 +148,7 @@ class Setting(QWidget): choice = MessageBox( "风险声明", - "开启“跳过MuMu启动广告”功能,即代表您已安装MuMu模拟器-12且允许AUTO_MAA以其认定合适的方法屏蔽MuMu启动广告,并接受此操作带来的风险\n\n此功能即时生效,是否仍要开启此功能?", + "开启「跳过MuMu启动广告」功能,即代表您已安装MuMu模拟器-12且允许AUTO_MAA以其认定合适的方法屏蔽MuMu启动广告,并接受此操作带来的风险\n\n此功能即时生效,是否仍要开启此功能?", self.window(), ) if choice.exec(): @@ -259,6 +260,61 @@ class Setting(QWidget): if choice.exec(): break + def reset_PASSWORD(self) -> None: + """重置管理密钥""" + + choice = MessageBox( + "确认", + "重置管理密钥将清空所有使用管理密钥加密的数据,您确认要重置管理密钥吗?", + self.window(), + ) + if choice.exec(): + choice = LineEditMessageBox( + self.window(), + "请输入文本提示框内的验证信息", + "AUTO_MAA绝赞DeBug中!", + "明文", + ) + + if choice.exec() and choice.input.text() in [ + "AUTO_MAA绝赞DeBug中!", + "AUTO_MAA绝赞DeBug中!", + ]: + + # 获取新的管理密钥 + while True: + + choice = LineEditMessageBox( + self.window(), "请输入新的管理密钥", "新管理密钥", "密码" + ) + if choice.exec() and choice.input.text() != "": + + # 重置管理密钥 + Crypto.reset_PASSWORD(choice.input.text()) + MainInfoBar.push_info_bar( + "success", "操作成功", "管理密钥重置成功", 3000 + ) + break + + else: + + choice = MessageBox( + "确认", + "您没有输入新的管理密钥,是否取消修改管理密钥?", + self.window(), + ) + if choice.exec(): + break + + else: + + MainInfoBar.push_info_bar( + "info", + "验证未通过", + "请输入「AUTO_MAA绝赞DeBug中!」后单击确认键", + 3000, + ) + def check_update(self, if_show: bool = False, if_first: bool = False) -> None: """检查版本更新,调起文件下载进程""" @@ -670,7 +726,7 @@ class FunctionSettingCard(HeaderCardWidget): self.card_BossKey = LineEditSettingCard( icon=FluentIcon.PAGE_RIGHT, title="模拟器老板键", - content="请输入对应的模拟器老板键,请直接输入文字,多个键位之间请用“+”隔开。如:“Alt+Q”", + content="请输入对应的模拟器老板键,请直接输入文字,多个键位之间请用「+」隔开。如:「Alt+Q」", text="请以文字形式输入模拟器老板快捷键", qconfig=Config, configItem=Config.function_BossKey, @@ -981,7 +1037,7 @@ class NotifySettingCard(HeaderCardWidget): self.card_ServerChanChannel = LineEditSettingCard( icon=FluentIcon.PAGE_RIGHT, title="ServerChanChannel代码", - content="可以留空,留空则默认。可以多个,请使用“|”隔开", + content="可以留空,留空则默认。可以多个,请使用「|」隔开", text="请输入需要推送的Channel代码(SCT生效)", qconfig=Config, configItem=Config.notify_ServerChanChannel, @@ -990,7 +1046,7 @@ class NotifySettingCard(HeaderCardWidget): self.card_ServerChanTag = LineEditSettingCard( icon=FluentIcon.PAGE_RIGHT, title="Tag内容", - content="可以留空,留空则默认。可以多个,请使用“|”隔开", + content="可以留空,留空则默认。可以多个,请使用「|」隔开", text="请输入加入推送的Tag(SC3生效)", qconfig=Config, configItem=Config.notify_ServerChanTag, @@ -1056,9 +1112,17 @@ class SecuritySettingCard(HeaderCardWidget): content="修改用于解密用户密码的管理密钥", parent=self, ) + self.card_resetPASSWORD = PushSettingCard( + text="重置", + icon=FluentIcon.VPN, + title="重置管理密钥", + content="重置用于解密用户密码的管理密钥", + parent=self, + ) Layout = QVBoxLayout() Layout.addWidget(self.card_changePASSWORD) + Layout.addWidget(self.card_resetPASSWORD) self.viewLayout.addLayout(Layout) diff --git a/requirements.txt b/requirements.txt index f2d6ee6..9448234 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,5 +10,5 @@ certifi==2025.4.26 requests==2.32.4 markdown==3.8.2 Jinja2==3.1.6 -nuitka==2.7.11 +nuitka==2.7.12 pillow==11.3.0 \ No newline at end of file diff --git a/resources/version.json b/resources/version.json index 4c7f7b3..211e42d 100644 --- a/resources/version.json +++ b/resources/version.json @@ -1,6 +1,14 @@ { - "main_version": "4.4.0.3", + "main_version": "4.4.0.4", "version_info": { + "4.4.0.4": { + "新增功能": [ + "添加重置管理密钥功能" + ], + "修复BUG": [ + "修复无计划表时数据系统无法正常升级到v1.7的问题" + ] + }, "4.4.0.3": { "修复BUG": [ "适配 MAA 备选关卡字段修改", From 4efbafc174504bd2fbd5b22075e3b5429406691b Mon Sep 17 00:00:00 2001 From: DLmaster361 Date: Sat, 12 Jul 2025 16:08:07 +0800 Subject: [PATCH 07/13] =?UTF-8?q?fix(system):=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E5=BC=80=E6=9C=BA=E8=87=AA=E5=90=AF=E7=9B=B8=E5=85=B3=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/core/config.py | 4 +- app/services/system.py | 126 ++++++++++++++++++++++++++++++----------- resources/version.json | 7 ++- 3 files changed, 100 insertions(+), 37 deletions(-) diff --git a/app/core/config.py b/app/core/config.py index 1fb5cfa..3642ff6 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -706,7 +706,7 @@ class GeneralSubConfig(LQConfig): class AppConfig(GlobalConfig): - VERSION = "4.4.0.4" + VERSION = "4.4.0.5" stage_refreshed = Signal() PASSWORD_refreshed = Signal() @@ -717,7 +717,7 @@ class AppConfig(GlobalConfig): super().__init__() self.app_path = Path(sys.argv[0]).resolve().parent # 获取软件根目录 - self.app_path_sys = str(Path(sys.argv[0]).resolve()) # 获取软件自身的路径 + 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" diff --git a/app/services/system.py b/app/services/system.py index 4600c59..e856061 100644 --- a/app/services/system.py +++ b/app/services/system.py @@ -31,9 +31,11 @@ import sys import ctypes import win32gui import win32process -import winreg import psutil import subprocess +import tempfile +import getpass +from datetime import datetime from pathlib import Path from app.core import Config @@ -61,44 +63,103 @@ class _SystemHandler: # 恢复系统电源状态 ctypes.windll.kernel32.SetThreadExecutionState(self.ES_CONTINUOUS) - def set_SelfStart(self) -> bool: + def set_SelfStart(self) -> None: """同步开机自启""" if Config.get(Config.start_IfSelfStart) and not self.is_startup(): + # 创建任务计划 try: - # 创建任务计划 - result = subprocess.run( - [ - "schtasks", - "/create", - "/tn", - "AUTO_MAA_AutoStart", - "/tr", - Config.app_path_sys, - "/sc", - "onlogon", - "/rl", - "highest", # 以最高权限运行 - "/f", # 强制创建(覆盖现有任务) - ], - creationflags=subprocess.CREATE_NO_WINDOW, - stdin=subprocess.DEVNULL, - capture_output=True, - text=True, - ) + # 获取当前用户和时间 + current_user = getpass.getuser() + current_time = datetime.now().strftime("%Y-%m-%dT%H:%M:%S") - if result.returncode == 0: - logger.info(f"任务计划程序自启动已创建: {Config.app_path_sys}") - return True - else: - logger.error(f"创建任务计划失败: {result.stderr}") - return False + # XML 模板 + xml_content = f""" + + + {current_time} + {current_user} + AUTO_MAA自启动服务 + \\AUTO_MAA_AutoStart + + + + {current_time} + true + + + + + InteractiveToken + HighestAvailable + + + + IgnoreNew + false + false + false + true + false + + false + false + + true + true + false + false + false + PT0S + 7 + + + + "{Config.app_path_sys}" + + + """ + + # 创建临时 XML 文件并执行 + with tempfile.NamedTemporaryFile( + mode="w", suffix=".xml", delete=False, encoding="utf-16" + ) as f: + f.write(xml_content) + xml_file = f.name + + try: + result = subprocess.run( + [ + "schtasks", + "/create", + "/tn", + "AUTO_MAA_AutoStart", + "/xml", + xml_file, + "/f", + ], + creationflags=subprocess.CREATE_NO_WINDOW, + stdin=subprocess.DEVNULL, + capture_output=True, + text=True, + ) + + if result.returncode == 0: + logger.info(f"任务计划程序自启动已创建: {Config.app_path_sys}") + else: + logger.error(f"创建任务计划失败: {result.stderr}") + + finally: + # 删除临时文件 + try: + Path(xml_file).unlink() + except: + pass except Exception as e: - logger.error(f"设置任务计划程序自启动失败: {e}") - return False + logger.exception(f"设置任务计划程序自启动失败: {e}") elif not Config.get(Config.start_IfSelfStart) and self.is_startup(): @@ -114,14 +175,11 @@ class _SystemHandler: if result.returncode == 0: logger.info("任务计划程序自启动已删除") - return True else: logger.error(f"删除任务计划失败: {result.stderr}") - return False except Exception as e: - logger.error(f"删除任务计划程序自启动失败: {e}") - return False + logger.exception(f"删除任务计划程序自启动失败: {e}") def set_power(self, mode) -> None: diff --git a/resources/version.json b/resources/version.json index 211e42d..3d58ed5 100644 --- a/resources/version.json +++ b/resources/version.json @@ -1,6 +1,11 @@ { - "main_version": "4.4.0.4", + "main_version": "4.4.0.5", "version_info": { + "4.4.0.5": { + "修复BUG": [ + "修复开机自启相关功能" + ] + }, "4.4.0.4": { "新增功能": [ "添加重置管理密钥功能" From c52820550fef62ecc8780f6563c1e5971f49efc3 Mon Sep 17 00:00:00 2001 From: DLmaster361 Date: Sat, 12 Jul 2025 21:54:40 +0800 Subject: [PATCH 08/13] =?UTF-8?q?feat(ui):=20=E6=B7=BB=E5=8A=A0=E5=AF=BC?= =?UTF-8?q?=E5=85=A5=E5=AF=BC=E5=87=BA=E9=80=9A=E7=94=A8=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/ui/member_manager.py | 71 ++++++++++++++++++++++++++++++++++++++++ resources/version.json | 3 ++ 2 files changed, 74 insertions(+) diff --git a/app/ui/member_manager.py b/app/ui/member_manager.py index 406c883..956bc78 100644 --- a/app/ui/member_manager.py +++ b/app/ui/member_manager.py @@ -2297,11 +2297,15 @@ class MemberManager(QWidget): self.card_Script = self.ScriptSettingCard(self.config, self) self.card_Game = self.GameSettingCard(self.config, self) self.card_Run = self.RunSettingCard(self.config, self) + self.card_Config = self.ConfigSettingCard( + self.name, self.config, self + ) Layout.addWidget(self.card_Name) Layout.addWidget(self.card_Script) Layout.addWidget(self.card_Game) Layout.addWidget(self.card_Run) + Layout.addWidget(self.card_Config) self.viewLayout.addLayout(Layout) class ScriptSettingCard(ExpandGroupSettingCard): @@ -2615,6 +2619,73 @@ class MemberManager(QWidget): self.viewLayout.setSpacing(0) self.addGroupWidget(widget) + class ConfigSettingCard(ExpandGroupSettingCard): + + def __init__(self, name: str, config: GeneralConfig, parent=None): + super().__init__( + FluentIcon.SETTING, + "配置管理", + "使用配置模板文件快速设置脚本", + parent, + ) + self.name = name + self.config = config + + self.card_ImportFromFile = PushSettingCard( + text="从文件导入", + icon=FluentIcon.PAGE_RIGHT, + title="从文件导入通用配置", + content="选择一个配置文件,导入其中的配置信息", + parent=self, + ) + self.card_ExportToFile = PushSettingCard( + text="导出到文件", + icon=FluentIcon.PAGE_RIGHT, + title="导出通用配置到文件", + content="选择一个保存路径,将当前配置信息导出到文件", + parent=self, + ) + + self.card_ImportFromFile.clicked.connect(self.import_from_file) + self.card_ExportToFile.clicked.connect(self.export_to_file) + + widget = QWidget() + Layout = QVBoxLayout(widget) + Layout.addWidget(self.card_ImportFromFile) + Layout.addWidget(self.card_ExportToFile) + self.viewLayout.setContentsMargins(0, 0, 0, 0) + self.viewLayout.setSpacing(0) + self.addGroupWidget(widget) + + def import_from_file(self): + """从文件导入配置""" + + file_path, _ = QFileDialog.getOpenFileName( + self, "选择配置文件", "", "JSON Files (*.json)" + ) + if file_path: + + shutil.copy( + file_path, + Config.member_dict[self.name]["Path"] / "config.json", + ) + self.config.load( + Config.member_dict[self.name]["Path"] / "config.json" + ) + + def export_to_file(self): + """导出配置到文件""" + + file_path, _ = QFileDialog.getSaveFileName( + self, "选择保存路径", "", "JSON Files (*.json)" + ) + if file_path: + + temp = self.config.toDict() + temp["Script"]["Name"] = Path(file_path).stem + with open(file_path, "w", encoding="utf-8") as file: + json.dump(temp, file, ensure_ascii=False, indent=4) + class BranchManager(HeaderCardWidget): """分支管理父页面""" diff --git a/resources/version.json b/resources/version.json index 3d58ed5..3226b06 100644 --- a/resources/version.json +++ b/resources/version.json @@ -2,6 +2,9 @@ "main_version": "4.4.0.5", "version_info": { "4.4.0.5": { + "新增功能": [ + "添加导入导出通用配置功能" + ], "修复BUG": [ "修复开机自启相关功能" ] From d539c0f808c308b23aa1ef3b6c5a5994a945f1d7 Mon Sep 17 00:00:00 2001 From: DLmaster361 Date: Sun, 13 Jul 2025 22:50:04 +0800 Subject: [PATCH 09/13] =?UTF-8?q?fix(core):=20=E4=BF=A1=E4=BB=BB=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F=E8=AF=81=E4=B9=A6=EF=BC=8C=E5=B9=B6=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E7=BD=91=E7=BB=9C=E4=BB=A3=E7=90=86=E5=9C=B0=E5=9D=80=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E9=A1=B9=20#50?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/core/config.py | 6 +++--- app/core/network.py | 14 ++++++++++++-- app/services/notification.py | 21 +++++++++++++++++++-- app/services/skland.py | 22 +++++++++++++++++++++- app/ui/downloader.py | 33 +++++++++++++++++++++++++++++---- app/ui/main_window.py | 3 +++ app/ui/setting.py | 10 ++++++++++ requirements.txt | 1 + resources/version.json | 7 ++++++- 9 files changed, 104 insertions(+), 13 deletions(-) diff --git a/app/core/config.py b/app/core/config.py index 3642ff6..dce5b78 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -262,6 +262,7 @@ class GlobalConfig(LQConfig): self.update_ThreadNumb = RangeConfigItem( "Update", "ThreadNumb", 8, RangeValidator(1, 32) ) + self.update_ProxyAddress = ConfigItem("Update", "ProxyAddress", "") self.update_ProxyUrlList = ConfigItem( "Update", "ProxyUrlList", [], UrlListValidator() ) @@ -706,7 +707,7 @@ class GeneralSubConfig(LQConfig): class AppConfig(GlobalConfig): - VERSION = "4.4.0.5" + VERSION = "4.4.0.6" stage_refreshed = Signal() PASSWORD_refreshed = Signal() @@ -780,7 +781,6 @@ class AppConfig(GlobalConfig): self.init_logger() self.check_data() - self.get_stage() logger.info("程序初始化完成") def init_logger(self) -> None: @@ -810,8 +810,8 @@ class AppConfig(GlobalConfig): logger.info("日志记录器初始化完成") def get_stage(self) -> None: + """从MAA服务器获取活动关卡信息""" - # 从MAA服务器获取活动关卡信息 network = Network.add_task( mode="get", url="https://api.maa.plus/MaaAssistantArknights/api/gui/StageActivity.json", diff --git a/app/core/network.py b/app/core/network.py index 15e3a26..003145a 100644 --- a/app/core/network.py +++ b/app/core/network.py @@ -30,6 +30,7 @@ from PySide6.QtCore import QObject, QThread, QEventLoop import re import time import requests +import truststore from pathlib import Path @@ -51,12 +52,21 @@ class NetworkThread(QThread): self.url = url self.path = path + from .config import Config + + self.proxies = { + "http": Config.get(Config.update_ProxyAddress), + "https": Config.get(Config.update_ProxyAddress), + } + self.status_code = None self.response_json = None self.error_message = None self.loop = QEventLoop() + truststore.inject_into_ssl() + @logger.catch def run(self) -> None: """运行网络请求线程""" @@ -73,7 +83,7 @@ class NetworkThread(QThread): for _ in range(self.max_retries): try: - response = requests.get(url, timeout=self.timeout) + response = requests.get(url, timeout=self.timeout, proxies=self.proxies) self.status_code = response.status_code self.response_json = response.json() self.error_message = None @@ -92,7 +102,7 @@ class NetworkThread(QThread): response = None try: - response = requests.get(url, timeout=10) + response = requests.get(url, timeout=10, proxies=self.proxies) if response.status_code == 200: with open(path, "wb") as file: file.write(response.content) diff --git a/app/services/notification.py b/app/services/notification.py index 6ea440d..56ddf04 100644 --- a/app/services/notification.py +++ b/app/services/notification.py @@ -199,7 +199,16 @@ class Notification(QObject): params = {"title": title, "desp": content, **options} headers = {"Content-Type": "application/json;charset=utf-8"} - response = requests.post(url, json=params, headers=headers, timeout=10) + response = requests.post( + url, + json=params, + headers=headers, + timeout=10, + proxies={ + "http": Config.get(Config.update_ProxyAddress), + "https": Config.get(Config.update_ProxyAddress), + }, + ) result = response.json() if result.get("code") == 0: @@ -244,6 +253,10 @@ class Notification(QObject): url=webhook_url, json=data, timeout=10, + proxies={ + "http": Config.get(Config.update_ProxyAddress), + "https": Config.get(Config.update_ProxyAddress), + }, ) info = response.json() break @@ -307,7 +320,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.error(f"图片编码或MD5计算失败:{e}") + logger.exception(f"图片编码或MD5计算失败:{e}") self.push_info_bar.emit( "error", "企业微信群机器人通知推送异常", @@ -327,6 +340,10 @@ class Notification(QObject): url=webhook_url, json=data, timeout=10, + proxies={ + "http": Config.get(Config.update_ProxyAddress), + "https": Config.get(Config.update_ProxyAddress), + }, ) info = response.json() break diff --git a/app/services/skland.py b/app/services/skland.py index c49724e..d752662 100644 --- a/app/services/skland.py +++ b/app/services/skland.py @@ -40,6 +40,8 @@ import hashlib import requests from urllib import parse +from app.core import Config + def skland_sign_in(token) -> dict: """森空岛签到""" @@ -137,7 +139,13 @@ def skland_sign_in(token) -> dict: # 通过grant code换cred和sign_token def get_cred(grant): rsp = requests.post( - cred_code_url, json={"code": grant, "kind": 1}, headers=header_login + cred_code_url, + json={"code": grant, "kind": 1}, + headers=header_login, + proxies={ + "http": Config.get(Config.update_ProxyAddress), + "https": Config.get(Config.update_ProxyAddress), + }, ).json() if rsp["code"] != 0: raise Exception(f'获得cred失败:{rsp.get("messgae")}') @@ -151,6 +159,10 @@ def skland_sign_in(token) -> dict: grant_code_url, json={"appCode": app_code, "token": token, "type": 0}, headers=header_login, + proxies={ + "http": Config.get(Config.update_ProxyAddress), + "https": Config.get(Config.update_ProxyAddress), + }, ).json() if rsp["status"] != 0: raise Exception( @@ -172,6 +184,10 @@ def skland_sign_in(token) -> dict: headers=get_sign_header( binding_url, "get", None, copy_header(cred), sign_token ), + proxies={ + "http": Config.get(Config.update_ProxyAddress), + "https": Config.get(Config.update_ProxyAddress), + }, ).json() if rsp["code"] != 0: logger.error(f"森空岛服务 | 请求角色列表出现问题:{rsp['message']}") @@ -209,6 +225,10 @@ def skland_sign_in(token) -> dict: sign_url, "post", body, copy_header(cred), sign_token ), json=body, + proxies={ + "http": Config.get(Config.update_ProxyAddress), + "https": Config.get(Config.update_ProxyAddress), + }, ).json() if rsp["code"] != 0: diff --git a/app/ui/downloader.py b/app/ui/downloader.py index bebc307..ae3aac1 100644 --- a/app/ui/downloader.py +++ b/app/ui/downloader.py @@ -30,7 +30,6 @@ import zipfile import requests import subprocess import time -import psutil from functools import partial from pathlib import Path @@ -47,6 +46,7 @@ from PySide6.QtCore import QThread, Signal, QTimer, QEventLoop from typing import List, Dict, Union +from app.core import Config from app.services import System @@ -113,7 +113,14 @@ class DownloadProcess(QThread): start_time = time.time() response = requests.get( - self.url, headers=headers, timeout=10, stream=True + self.url, + headers=headers, + timeout=10, + stream=True, + proxies={ + "http": Config.get(Config.update_ProxyAddress), + "https": Config.get(Config.update_ProxyAddress), + }, ) if response.status_code not in [200, 206]: @@ -332,6 +339,10 @@ class DownloadManager(QDialog): allow_redirects=True, timeout=10, stream=True, + proxies={ + "http": Config.get(Config.update_ProxyAddress), + "https": Config.get(Config.update_ProxyAddress), + }, ) as response: if response.status_code == 200: return response.url @@ -339,7 +350,14 @@ class DownloadManager(QDialog): elif self.config["mode"] == "MirrorChyan": with requests.get( - self.config["url"], allow_redirects=True, timeout=10, stream=True + self.config["url"], + allow_redirects=True, + timeout=10, + stream=True, + proxies={ + "http": Config.get(Config.update_ProxyAddress), + "https": Config.get(Config.update_ProxyAddress), + }, ) as response: if response.status_code == 200: return response.url @@ -448,7 +466,14 @@ class DownloadManager(QDialog): url = self.get_download_url("下载") self.downloaded_size_list: List[List[int, bool]] = [] - response = requests.head(url, timeout=10) + response = requests.head( + url, + timeout=10, + proxies={ + "http": Config.get(Config.update_ProxyAddress), + "https": Config.get(Config.update_ProxyAddress), + }, + ) self.file_size = int(response.headers.get("content-length", 0)) part_size = self.file_size // self.config["thread_numb"] diff --git a/app/ui/main_window.py b/app/ui/main_window.py index 595c751..3a97d12 100644 --- a/app/ui/main_window.py +++ b/app/ui/main_window.py @@ -361,6 +361,9 @@ class AUTO_MAA(MSFluentWindow): # 检查密码 self.setting.check_PASSWORD() + # 获取关卡号信息 + Config.get_stage() + # 获取主题图像 if Config.get(Config.function_HomeImageMode) == "主题图像": self.home.get_home_image() diff --git a/app/ui/setting.py b/app/ui/setting.py index 68d049e..71c35c7 100644 --- a/app/ui/setting.py +++ b/app/ui/setting.py @@ -1164,6 +1164,15 @@ class UpdaterSettingCard(HeaderCardWidget): configItem=Config.update_ThreadNumb, parent=self, ) + self.card_ProxyAddress = LineEditSettingCard( + icon=FluentIcon.PAGE_RIGHT, + title="网络代理地址", + content="使用网络代理软件时,若出现网络连接问题,请尝试设置代理地址,此设置全局生效", + text="请输入代理地址", + qconfig=Config, + configItem=Config.update_ProxyAddress, + parent=self, + ) self.card_ProxyUrlList = UrlListSettingCard( icon=FluentIcon.SETTING, title="代理地址列表", @@ -1196,6 +1205,7 @@ class UpdaterSettingCard(HeaderCardWidget): Layout.addWidget(self.card_IfAutoUpdate) Layout.addWidget(self.card_UpdateType) Layout.addWidget(self.card_ThreadNumb) + Layout.addWidget(self.card_ProxyAddress) Layout.addWidget(self.card_ProxyUrlList) Layout.addWidget(self.card_MirrorChyanCDK) self.viewLayout.addLayout(Layout) diff --git a/requirements.txt b/requirements.txt index 9448234..ab2ff4b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,6 +7,7 @@ pywin32==310 keyboard==0.13.5 pycryptodome==3.23.0 certifi==2025.4.26 +truststore==0.10.1 requests==2.32.4 markdown==3.8.2 Jinja2==3.1.6 diff --git a/resources/version.json b/resources/version.json index 3d58ed5..5dcf661 100644 --- a/resources/version.json +++ b/resources/version.json @@ -1,6 +1,11 @@ { - "main_version": "4.4.0.5", + "main_version": "4.4.0.6", "version_info": { + "4.4.0.6": { + "修复BUG": [ + "信任系统证书,并添加网络代理地址配置项 #50" + ] + }, "4.4.0.5": { "修复BUG": [ "修复开机自启相关功能" From 12cf10f97a8f63f53ec51d7fdb5d55800146963c Mon Sep 17 00:00:00 2001 From: DLmaster361 Date: Mon, 14 Jul 2025 19:12:59 +0800 Subject: [PATCH 10/13] =?UTF-8?q?fix((ui):=20=E4=BF=AE=E5=A4=8DMAA?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E4=BB=AA=E8=A1=A8=E7=9B=98=E5=A4=87=E9=80=89?= =?UTF-8?q?1=E5=AD=97=E6=AE=B5=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/ui/member_manager.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/ui/member_manager.py b/app/ui/member_manager.py index 956bc78..13d5f07 100644 --- a/app/ui/member_manager.py +++ b/app/ui/member_manager.py @@ -1392,12 +1392,12 @@ class MemberManager(QWidget): QTableWidgetItem( Config.stage_dict["ALL"]["text"][ Config.stage_dict["ALL"]["value"].index( - stage_info["Stage"] + stage_info["Stage_1"] ) ] - if stage_info["Stage"] + if stage_info["Stage_1"] in Config.stage_dict["ALL"]["value"] - else stage_info["Stage"] + else stage_info["Stage_1"] ), ) self.dashboard.setItem( From 403f69df8b415b76f05e382f14c797644dd5a212 Mon Sep 17 00:00:00 2001 From: DLmaster361 Date: Tue, 15 Jul 2025 16:08:11 +0800 Subject: [PATCH 11/13] =?UTF-8?q?refactor(ui):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E5=8E=86=E5=8F=B2=E8=AE=B0=E5=BD=95=E4=BF=9D=E5=AD=98=E4=B8=8E?= =?UTF-8?q?=E8=BD=BD=E5=85=A5=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/core/config.py | 185 +++++++++++++++-------------------------- app/models/MAA.py | 2 +- app/ui/history.py | 160 ++++++++++++++--------------------- resources/version.json | 7 +- 4 files changed, 135 insertions(+), 219 deletions(-) diff --git a/app/core/config.py b/app/core/config.py index dce5b78..5e882fa 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -707,7 +707,7 @@ class GeneralSubConfig(LQConfig): class AppConfig(GlobalConfig): - VERSION = "4.4.0.6" + VERSION = "4.4.0.0" stage_refreshed = Signal() PASSWORD_refreshed = Signal() @@ -1386,7 +1386,7 @@ class AppConfig(GlobalConfig): logger.warning(f"保存历史记录时未找到调度队列: {key}") def save_maa_log(self, log_path: Path, logs: list, maa_result: str) -> bool: - """保存MAA日志并生成初步统计数据""" + """保存MAA日志并生成对应统计数据""" data: Dict[str, Union[str, Dict[str, Union[int, dict]]]] = { "recruit_statistics": defaultdict(int), @@ -1507,117 +1507,77 @@ class AppConfig(GlobalConfig): logger.info(f"处理完成:{log_path}") - self.merge_maa_logs("所有项", log_path.parent) - return if_six_star - def merge_maa_logs(self, mode: str, logs_path: Union[Path, List[Path]]) -> dict: + def merge_statistic_info(self, statistic_path_list: List[Path]) -> dict: """合并指定数据统计信息文件""" - data = { - "recruit_statistics": defaultdict(int), - "drop_statistics": defaultdict(dict), - "maa_result": defaultdict(str), - } + data = {"index": {}} - if mode == "所有项": - logs_path_list = list(logs_path.glob("*.json")) - elif mode == "指定项": - logs_path_list = logs_path - - for json_file in logs_path_list: + for json_file in statistic_path_list: with json_file.open("r", encoding="utf-8") as f: single_data: Dict[str, Union[str, Dict[str, Union[int, dict]]]] = ( json.load(f) ) - # 合并公招统计 - for star_level, count in single_data["recruit_statistics"].items(): - data["recruit_statistics"][star_level] += count + for key in single_data.keys(): - # 合并掉落统计 - for stage, drops in single_data["drop_statistics"].items(): - if stage not in data["drop_statistics"]: - data["drop_statistics"][stage] = {} # 初始化关卡 + if key not in data: + data[key] = {} - for item, count in drops.items(): + # 合并公招统计 + if key == "recruit_statistics": - if item in data["drop_statistics"][stage]: - data["drop_statistics"][stage][item] += count - else: - data["drop_statistics"][stage][item] = count + for star_level, count in single_data[key].items(): + if star_level not in data[key]: + data[key][star_level] = 0 + data[key][star_level] += count - # 合并MAA结果 - data["maa_result"][json_file.stem.replace("-", ":")] = single_data[ - "maa_result" - ] + # 合并掉落统计 + if key == "drop_statistics": - # 生成汇总 JSON 文件 - if mode == "所有项": + for stage, drops in single_data[key].items(): + if stage not in data[key]: + data[key][stage] = {} # 初始化关卡 - with logs_path.with_suffix(".json").open("w", encoding="utf-8") as f: - json.dump(data, f, ensure_ascii=False, indent=4) + for item, count in drops.items(): - logger.info(f"统计完成:{logs_path.with_suffix('.json')}") + if item not in data[key][stage]: + data[key][stage][item] = 0 + data[key][stage][item] += count - return data + # 录入MAA结果 + if key == "maa_result": - def load_maa_logs( - self, mode: str, json_path: Path - ) -> Dict[str, Union[str, list, Dict[str, list]]]: - """加载MAA日志统计信息""" + actual_date = datetime.strptime( + f"{json_file.parent.parent.name} {json_file.stem}", + "%Y-%m-%d %H-%M-%S", + ) + timedelta( + days=( + 1 + if datetime.strptime(json_file.stem, "%H-%M-%S").time() + < datetime.min.time().replace(hour=4) + else 0 + ) + ) - if mode == "总览": + if single_data[key] != "Success!": + if "error_info" not in data: + data["error_info"] = {} + data["error_info"][actual_date.strftime("%d日 %H:%M:%S")] = ( + single_data[key] + ) - with json_path.open("r", encoding="utf-8") as f: - info: Dict[str, Dict[str, Union[int, dict]]] = json.load(f) + data["index"][actual_date] = [ + actual_date.strftime("%d日 %H:%M:%S"), + ("完成" if single_data["maa_result"] == "Success!" else "异常"), + json_file, + ] - data = {} - # 4点前的记录放在当日最后 - sorted_maa_result = sorted( - info["maa_result"].items(), - key=lambda x: ( - ( - 1 - if datetime.strptime(x[0], "%H:%M:%S").time() - < datetime.min.time().replace(hour=4) - else 0 - ), - datetime.strptime(x[0], "%H:%M:%S"), - ), - ) - data["条目索引"] = [ - [k, "完成" if v == "Success!" else "异常"] for k, v in sorted_maa_result - ] - data["条目索引"].insert(0, ["数据总览", "运行"]) - data["统计数据"] = {"公招统计": list(info["recruit_statistics"].items())} + data["index"] = [data["index"][_] for _ in sorted(data["index"])] - for game_id, drops in info["drop_statistics"].items(): - data["统计数据"][f"掉落统计:{game_id}"] = list(drops.items()) - - data["统计数据"]["报错汇总"] = [ - [k, v] for k, v in info["maa_result"].items() if v != "Success!" - ] - - elif mode == "单项": - - with json_path.open("r", encoding="utf-8") as f: - info: Dict[str, Union[str, Dict[str, Union[int, dict]]]] = json.load(f) - - data = {} - - data["统计数据"] = {"公招统计": list(info["recruit_statistics"].items())} - - for game_id, drops in info["drop_statistics"].items(): - data["统计数据"][f"掉落统计:{game_id}"] = list(drops.items()) - - with json_path.with_suffix(".log").open("r", encoding="utf-8") as f: - log = f.read() - - data["日志信息"] = log - - return data + return {k: v for k, v in data.items() if v} def search_history( self, mode: str, start_date: datetime, end_date: datetime @@ -1638,43 +1598,28 @@ class AppConfig(GlobalConfig): continue # 只统计在范围内的日期 if mode == "按日合并": - - history_dict[date.strftime("%Y年 %m月 %d日")] = list( - date_folder.glob("*.json") - ) - + date_name = date.strftime("%Y年 %m月 %d日") elif mode == "按周合并": - year, week, _ = date.isocalendar() - if f"{year}年 第{week}周" not in history_dict: - history_dict[f"{year}年 第{week}周"] = {} - - for user in date_folder.glob("*.json"): - - if user.stem not in history_dict[f"{year}年 第{week}周"]: - history_dict[f"{year}年 第{week}周"][user.stem] = list( - user.with_suffix("").glob("*.json") - ) - else: - history_dict[f"{year}年 第{week}周"][user.stem] += list( - user.with_suffix("").glob("*.json") - ) - + date_name = f"{year}年 第{week}周" elif mode == "按月合并": + date_name = date.strftime("%Y年 %m月") - if date.strftime("%Y年 %m月") not in history_dict: - history_dict[date.strftime("%Y年 %m月")] = {} + if date_name not in history_dict: + history_dict[date_name] = {} - for user in date_folder.glob("*.json"): + for user_folder in date_folder.iterdir(): + if not user_folder.is_dir(): + continue # 只处理用户文件夹 - if user.stem not in history_dict[date.strftime("%Y年 %m月")]: - history_dict[date.strftime("%Y年 %m月")][user.stem] = list( - user.with_suffix("").glob("*.json") - ) - else: - history_dict[date.strftime("%Y年 %m月")][user.stem] += list( - user.with_suffix("").glob("*.json") - ) + if user_folder.stem not in history_dict[date_name]: + history_dict[date_name][user_folder.stem] = list( + user_folder.with_suffix("").glob("*.json") + ) + else: + history_dict[date_name][user_folder.stem] += list( + user_folder.with_suffix("").glob("*.json") + ) except ValueError: logger.warning(f"非日期格式的目录: {date_folder}") diff --git a/app/models/MAA.py b/app/models/MAA.py index ac28227..490197d 100644 --- a/app/models/MAA.py +++ b/app/models/MAA.py @@ -701,7 +701,7 @@ class MaaManager(QObject): logger.info(f"{self.name} | 更新动作结束") # 发送统计信息 - statistics = Config.merge_maa_logs("指定项", user_logs_list) + statistics = Config.merge_statistic_info(user_logs_list) statistics["user_index"] = user[2] statistics["user_info"] = user[0] statistics["start_time"] = user_start_time.strftime("%Y-%m-%d %H:%M:%S") diff --git a/app/ui/history.py b/app/ui/history.py index 2f0d4c6..b253f1d 100644 --- a/app/ui/history.py +++ b/app/ui/history.py @@ -48,7 +48,7 @@ import subprocess from datetime import datetime, timedelta from functools import partial from pathlib import Path -from typing import Union, List, Dict +from typing import List, Dict from app.core import Config, SoundPlayer @@ -100,9 +100,9 @@ class History(QWidget): datetime(end_date.year(), end_date.month(), end_date.day()), ) - for date, user in history_dict.items(): + for date, user_dict in history_dict.items(): - self.history_card_list.append(self.HistoryCard(mode, date, user, self)) + self.history_card_list.append(self.HistoryCard(date, user_dict, self)) self.content_layout.addWidget(self.history_card_list[-1]) self.content_layout.addStretch(1) @@ -172,14 +172,9 @@ class History(QWidget): self.search.clicked.emit() class HistoryCard(QuickExpandGroupCard): + """历史记录卡片""" - def __init__( - self, - mode: str, - date: str, - user: Union[List[Path], Dict[str, List[Path]]], - parent=None, - ): + def __init__(self, date: str, user_dict: Dict[str, List[Path]], parent=None): super().__init__( FluentIcon.HISTORY, date, f"{date}的历史运行记录与统计信息", parent ) @@ -192,64 +187,28 @@ class History(QWidget): self.user_history_card_list = [] - if mode == "按日合并": - - for user_path in user: - self.user_history_card_list.append( - self.UserHistoryCard(mode, user_path.stem, user_path, self) - ) - Layout.addWidget(self.user_history_card_list[-1]) - - elif mode in ["按周合并", "按月合并"]: - - for user, info in user.items(): - self.user_history_card_list.append( - self.UserHistoryCard(mode, user, info, self) - ) - Layout.addWidget(self.user_history_card_list[-1]) + for user, info in user_dict.items(): + self.user_history_card_list.append( + self.UserHistoryCard(user, info, self) + ) + Layout.addWidget(self.user_history_card_list[-1]) class UserHistoryCard(HeaderCardWidget): """用户历史记录卡片""" - def __init__( - self, - mode: str, - name: str, - user_history: Union[Path, List[Path]], - parent=None, - ): + def __init__(self, name: str, user_history: List[Path], parent=None): super().__init__(parent) - self.setTitle(name) - if mode == "按日合并": + self.user_history = user_history - self.user_history_path = user_history - self.main_history = Config.load_maa_logs("总览", user_history) - - self.index_card = self.IndexCard( - self.main_history["条目索引"], self - ) - self.index_card.index_changed.connect(self.update_info) - self.viewLayout.addWidget(self.index_card) - - elif mode in ["按周合并", "按月合并"]: - - history = Config.merge_maa_logs("指定项", user_history) - - self.main_history = {} - self.main_history["统计数据"] = { - "公招统计": list(history["recruit_statistics"].items()) - } - - for game_id, drops in history["drop_statistics"].items(): - self.main_history["统计数据"][f"掉落统计:{game_id}"] = list( - drops.items() - ) + self.index_card = self.IndexCard(self.user_history, self) + self.index_card.index_changed.connect(self.update_info) self.statistics_card = QHBoxLayout() self.log_card = self.LogCard(self) + self.viewLayout.addWidget(self.index_card) self.viewLayout.addLayout(self.statistics_card) self.viewLayout.addWidget(self.log_card) self.viewLayout.setContentsMargins(0, 0, 0, 0) @@ -259,19 +218,45 @@ class History(QWidget): self.update_info("数据总览") + def get_statistics(self, mode: str) -> dict: + """生成GUI相应结构化统计数据""" + + history_info = Config.merge_statistic_info( + self.user_history if mode == "数据总览" else [Path(mode)] + ) + + statistics_info = {} + + if "recruit_statistics" in history_info: + statistics_info["公招统计"] = list( + history_info["recruit_statistics"].items() + ) + + if "drop_statistics" in history_info: + for game_id, drops in history_info["drop_statistics"].items(): + statistics_info[f"掉落统计:{game_id}"] = list(drops.items()) + + if mode == "数据总览" and "error_info" in history_info: + statistics_info["报错汇总"] = list( + history_info["error_info"].items() + ) + + return statistics_info + def update_info(self, index: str) -> None: """更新信息""" + # 移除已有统计信息UI组件 + while self.statistics_card.count() > 0: + item = self.statistics_card.takeAt(0) + if item.spacerItem(): + self.statistics_card.removeItem(item.spacerItem()) + elif item.widget(): + item.widget().deleteLater() + if index == "数据总览": - while self.statistics_card.count() > 0: - item = self.statistics_card.takeAt(0) - if item.spacerItem(): - self.statistics_card.removeItem(item.spacerItem()) - elif item.widget(): - item.widget().deleteLater() - - for name, item_list in self.main_history["统计数据"].items(): + for name, item_list in self.get_statistics("数据总览").items(): statistics_card = self.StatisticsCard(name, item_list, self) self.statistics_card.addWidget(statistics_card) @@ -280,44 +265,24 @@ class History(QWidget): else: - single_history = Config.load_maa_logs( - "单项", - self.user_history_path.with_suffix("") - / f"{index.replace(":","-")}.json", - ) - - while self.statistics_card.count() > 0: - item = self.statistics_card.takeAt(0) - if item.spacerItem(): - self.statistics_card.removeItem(item.spacerItem()) - elif item.widget(): - item.widget().deleteLater() - - for name, item_list in single_history["统计数据"].items(): + 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) - self.log_card.text.setText(single_history["日志信息"]) + with log_path.open("r", encoding="utf-8") as f: + log = f.read() + + self.log_card.text.setText(log) self.log_card.open_file.clicked.disconnect() self.log_card.open_file.clicked.connect( - lambda: os.startfile( - self.user_history_path.with_suffix("") - / f"{index.replace(":","-")}.log" - ) + lambda: os.startfile(log_path) ) self.log_card.open_dir.clicked.disconnect() self.log_card.open_dir.clicked.connect( - lambda: subprocess.Popen( - [ - "explorer", - "/select,", - str( - self.user_history_path.with_suffix("") - / f"{index.replace(":","-")}.log" - ), - ] - ) + lambda: subprocess.Popen(["explorer", "/select,", log_path]) ) self.log_card.show() @@ -329,7 +294,7 @@ class History(QWidget): index_changed = Signal(str) - def __init__(self, index_list: list, parent=None): + def __init__(self, history_list: List[Path], parent=None): super().__init__(parent) self.setTitle("记录条目") @@ -339,11 +304,14 @@ 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)) + self.index_cards.append(StatefulItemCard(index[:2])) self.index_cards[-1].clicked.connect( - partial(self.index_changed.emit, index[0]) + partial(self.index_changed.emit, str(index[2])) ) self.Layout.addWidget(self.index_cards[-1]) diff --git a/resources/version.json b/resources/version.json index e363cd9..19593e4 100644 --- a/resources/version.json +++ b/resources/version.json @@ -1,9 +1,12 @@ { - "main_version": "4.4.0.6", + "main_version": "4.4.0.0", "version_info": { - "4.4.0.6": { + "4.4.0.0": { "修复BUG": [ "信任系统证书,并添加网络代理地址配置项 #50" + ], + "程序优化": [ + "重构历史记录保存与载入逻辑" ] }, "4.4.0.5": { From a2f4adb647ad0ad7c488cf5717717d28a70fe46d Mon Sep 17 00:00:00 2001 From: DLmaster361 Date: Tue, 15 Jul 2025 16:20:09 +0800 Subject: [PATCH 12/13] =?UTF-8?q?fix:=20=E9=80=82=E9=85=8D=20MAA=20?= =?UTF-8?q?=E4=BB=BB=E5=8A=A1=E5=8F=8A=E5=9F=BA=E5=BB=BA=E8=AE=BE=E6=96=BD?= =?UTF-8?q?=E6=97=A5=E5=BF=97=E7=BF=BB=E8=AF=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/MAA.py | 22 +++++++++++++--------- resources/version.json | 3 ++- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/app/models/MAA.py b/app/models/MAA.py index 490197d..fda0410 100644 --- a/app/models/MAA.py +++ b/app/models/MAA.py @@ -1122,21 +1122,25 @@ class MaaManager(QObject): elif "任务已全部完成!" in log: - if "完成任务: StartUp" in log: + if "完成任务: StartUp" in log or "完成任务: 开始唤醒" in log: self.task_dict["WakeUp"] = "False" - if "完成任务: Recruit" in log: + if "完成任务: Recruit" in log or "完成任务: 自动公招" in log: self.task_dict["Recruiting"] = "False" - if "完成任务: Infrast" in log: + if "完成任务: Infrast" in log or "完成任务: 基建换班" in log: self.task_dict["Base"] = "False" - if "完成任务: Fight" in log or "剿灭任务失败" in log: + if ( + "完成任务: Fight" in log + or "完成任务: 刷理智" in log + or "剿灭任务失败" in log + ): self.task_dict["Combat"] = "False" - if "完成任务: Mall" in log: + if "完成任务: Mall" in log or "完成任务: 获取信用及购物" in log: self.task_dict["Mall"] = "False" - if "完成任务: Award" in log: + if "完成任务: Award" in log or "完成任务: 领取奖励" in log: self.task_dict["Mission"] = "False" - if "完成任务: Roguelike" in log: + if "完成任务: Roguelike" in log or "完成任务: 自动肉鸽" in log: self.task_dict["AutoRoguelike"] = "False" - if "完成任务: Reclamation" in log: + if "完成任务: Reclamation" in log or "完成任务: 生息演算" in log: self.task_dict["Reclamation"] = "False" if all(v == "False" for v in self.task_dict.values()): @@ -1171,7 +1175,7 @@ class MaaManager(QObject): self.maa_result = "Wait" elif mode == "人工排查": - if "完成任务: StartUp" in log: + if "完成任务: StartUp" in log or "完成任务: 开始唤醒" in log: self.maa_result = "Success!" elif "请 「检查连接设置」 → 「尝试重启模拟器与 ADB」 → 「重启电脑」" in log: self.maa_result = "MAA的ADB连接异常" diff --git a/resources/version.json b/resources/version.json index 19593e4..6c0a5e8 100644 --- a/resources/version.json +++ b/resources/version.json @@ -3,7 +3,8 @@ "version_info": { "4.4.0.0": { "修复BUG": [ - "信任系统证书,并添加网络代理地址配置项 #50" + "信任系统证书,并添加网络代理地址配置项 #50", + "适配 MAA 任务及基建设施日志翻译" ], "程序优化": [ "重构历史记录保存与载入逻辑" From 75b06ca770ebcee68bf05ab95f069c6e8fd0cae8 Mon Sep 17 00:00:00 2001 From: DLmaster361 Date: Tue, 15 Jul 2025 18:15:26 +0800 Subject: [PATCH 13/13] =?UTF-8?q?feat(general):=20=E9=80=9A=E7=94=A8?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E6=A8=A1=E5=BC=8F=E6=8E=A5=E5=85=A5=E6=97=A5?= =?UTF-8?q?=E5=BF=97=E7=B3=BB=E7=BB=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/core/config.py | 22 ++++++++++++++++++---- app/models/MAA.py | 14 ++++++++------ app/models/general.py | 18 ++++++------------ resources/version.json | 3 +++ 4 files changed, 35 insertions(+), 22 deletions(-) diff --git a/app/core/config.py b/app/core/config.py index 5e882fa..fe19472 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -1509,6 +1509,20 @@ class AppConfig(GlobalConfig): return if_six_star + def save_general_log(self, log_path: Path, logs: list, general_result: str) -> None: + """保存通用日志并生成对应统计数据""" + + data: Dict[str, str] = {"general_result": general_result} + + # 保存日志 + log_path.parent.mkdir(parents=True, exist_ok=True) + with log_path.with_suffix(".log").open("w", encoding="utf-8") as f: + f.writelines(logs) + 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}") + def merge_statistic_info(self, statistic_path_list: List[Path]) -> dict: """合并指定数据统计信息文件""" @@ -1535,7 +1549,7 @@ class AppConfig(GlobalConfig): data[key][star_level] += count # 合并掉落统计 - if key == "drop_statistics": + elif key == "drop_statistics": for stage, drops in single_data[key].items(): if stage not in data[key]: @@ -1547,8 +1561,8 @@ class AppConfig(GlobalConfig): data[key][stage][item] = 0 data[key][stage][item] += count - # 录入MAA结果 - if key == "maa_result": + # 录入运行结果 + elif key in ["maa_result", "general_result"]: actual_date = datetime.strptime( f"{json_file.parent.parent.name} {json_file.stem}", @@ -1571,7 +1585,7 @@ class AppConfig(GlobalConfig): data["index"][actual_date] = [ actual_date.strftime("%d日 %H:%M:%S"), - ("完成" if single_data["maa_result"] == "Success!" else "异常"), + ("完成" if single_data[key] == "Success!" else "异常"), json_file, ] diff --git a/app/models/MAA.py b/app/models/MAA.py index fda0410..974ba65 100644 --- a/app/models/MAA.py +++ b/app/models/MAA.py @@ -1903,15 +1903,17 @@ class MaaManager(QObject): # 生成文本通知内容 formatted = [] - for stage, items in message["drop_statistics"].items(): - formatted.append(f"掉落统计({stage}):") - for item, quantity in items.items(): - formatted.append(f" {item}: {quantity}") + if "drop_statistics" in message: + for stage, items in message["drop_statistics"].items(): + formatted.append(f"掉落统计({stage}):") + for item, quantity in items.items(): + formatted.append(f" {item}: {quantity}") drop_text = "\n".join(formatted) formatted = ["招募统计:"] - for star, count in message["recruit_statistics"].items(): - formatted.append(f" {star}: {count}") + if "recruit_statistics" in message: + for star, count in message["recruit_statistics"].items(): + formatted.append(f" {star}: {count}") recruit_text = "\n".join(formatted) message_text = ( diff --git a/app/models/general.py b/app/models/general.py index 99fa78d..5966bb2 100644 --- a/app/models/general.py +++ b/app/models/general.py @@ -224,7 +224,6 @@ class GeneralManager(QObject): logger.info(f"{self.name} | 开始代理配置: {sub[0]}") - sub_logs_list = [] sub_start_time = datetime.now() run_book = False @@ -378,20 +377,15 @@ class GeneralManager(QObject): Path(sub_data["Info"]["ScriptAfterTask"]), "脚本后任务" ) - # # 保存运行日志以及统计信息 - # Config.save_maa_log( - # Config.app_path - # / f"history/{curdate}/{sub_data['Info']['Name']}/{start_time.strftime("%H-%M-%S")}.log", - # self.check_script_log(start_time, mode_book[mode]), - # self.maa_result, - # ) - sub_logs_list.append( + # 保存运行日志以及统计信息 + Config.save_general_log( Config.app_path - / f"history/{curdate}/{sub_data['Info']['Name']}/{start_time.strftime("%H-%M-%S")}.json", + / f"history/{curdate}/{sub_data['Info']['Name']}/{start_time.strftime("%H-%M-%S")}.log", + self.check_script_log(start_time), + self.script_result, ) # 发送统计信息 - # statistics = Config.merge_maa_logs("指定项", sub_logs_list) statistics = { "sub_index": sub[2], "sub_info": sub[0], @@ -658,7 +652,7 @@ class GeneralManager(QObject): else: for error_sign in self.error_log: if error_sign in log: - self.script_result = error_sign + self.script_result = f"异常日志:{error_sign}" break else: if self.script_process_manager.is_running(): diff --git a/resources/version.json b/resources/version.json index 6c0a5e8..b32de3f 100644 --- a/resources/version.json +++ b/resources/version.json @@ -2,6 +2,9 @@ "main_version": "4.4.0.0", "version_info": { "4.4.0.0": { + "新增功能": [ + "通用配置模式接入日志系统" + ], "修复BUG": [ "信任系统证书,并添加网络代理地址配置项 #50", "适配 MAA 任务及基建设施日志翻译"