diff --git a/app/__init__.py b/app/__init__.py index 50f2c66..1e72259 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -29,16 +29,16 @@ __version__ = "4.2.0" __author__ = "DLmaster361 " __license__ = "GPL-3.0 license" -from .core import AppConfig, QueueConfig, MaaConfig, Task, TaskManager, MainTimer +from .core import QueueConfig, MaaConfig, MaaUserConfig, Task, TaskManager, MainTimer from .models import MaaManager from .services import Notify, Crypto, System from .ui import AUTO_MAA from .utils import DownloadManager __all__ = [ - "AppConfig", "QueueConfig", "MaaConfig", + "MaaUserConfig", "Task", "TaskManager", "MainTimer", diff --git a/app/core/__init__.py b/app/core/__init__.py index 3b8b5a3..436cb39 100644 --- a/app/core/__init__.py +++ b/app/core/__init__.py @@ -29,16 +29,16 @@ __version__ = "4.2.0" __author__ = "DLmaster361 " __license__ = "GPL-3.0 license" -from .config import AppConfig, QueueConfig, MaaConfig, Config +from .config import QueueConfig, MaaConfig, MaaUserConfig, Config from .main_info_bar import MainInfoBar from .task_manager import Task, TaskManager from .timer import MainTimer __all__ = [ - "AppConfig", "Config", "QueueConfig", "MaaConfig", + "MaaUserConfig", "MainInfoBar", "Task", "TaskManager", diff --git a/app/core/config.py b/app/core/config.py index 6a4feec..5248107 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -26,12 +26,16 @@ v4.2 """ from loguru import logger +from PySide6.QtCore import Signal import sqlite3 import json import sys import shutil import re -from datetime import datetime +import requests +import time +import base64 +from datetime import datetime, timedelta from collections import defaultdict from pathlib import Path from qfluentwidgets import ( @@ -39,18 +43,568 @@ from qfluentwidgets import ( ConfigItem, OptionsConfigItem, RangeConfigItem, + ConfigValidator, FolderValidator, BoolValidator, RangeValidator, OptionsValidator, - qconfig, + exceptionHandler, ) -from typing import Union, Dict, List, Tuple +from urllib.parse import urlparse +from typing import Union, Dict, List -class AppConfig: +class UrlListValidator(ConfigValidator): + """Url list validator""" + + def validate(self, value): + + try: + result = urlparse(value) + return all([result.scheme, result.netloc]) + except ValueError: + return False + + def correct(self, value: List[str]): + + urls = [] + + for url in [_ for _ in value if _ != ""]: + if url[-1] != "/": + urls.append(f"{url}/") + else: + urls.append(url) + + return list(set([_ for _ in urls if self.validate(_)])) + + +class GlobalConfig(QConfig): + """全局配置""" def __init__(self) -> None: + super().__init__() + + self.function_HomeImageMode = OptionsConfigItem( + "Function", + "HomeImageMode", + "默认", + OptionsValidator(["默认", "自定义", "主题图像"]), + ) + self.function_HistoryRetentionTime = OptionsConfigItem( + "Function", "HistoryRetentionTime", 0, OptionsValidator([7, 15, 30, 60, 0]) + ) + self.function_IfAllowSleep = ConfigItem( + "Function", "IfAllowSleep", False, BoolValidator() + ) + self.function_IfSilence = ConfigItem( + "Function", "IfSilence", False, BoolValidator() + ) + self.function_BossKey = ConfigItem("Function", "BossKey", "") + self.function_IfAgreeBilibili = ConfigItem( + "Function", "IfAgreeBilibili", False, BoolValidator() + ) + self.function_IfSkipMumuSplashAds = ConfigItem( + "Function", "IfSkipMumuSplashAds", False, BoolValidator() + ) + + self.start_IfSelfStart = ConfigItem( + "Start", "IfSelfStart", False, BoolValidator() + ) + self.start_IfRunDirectly = ConfigItem( + "Start", "IfRunDirectly", False, BoolValidator() + ) + self.start_IfMinimizeDirectly = ConfigItem( + "Start", "IfMinimizeDirectly", False, BoolValidator() + ) + + self.ui_IfShowTray = ConfigItem("UI", "IfShowTray", False, BoolValidator()) + self.ui_IfToTray = ConfigItem("UI", "IfToTray", False, BoolValidator()) + self.ui_size = ConfigItem("UI", "size", "1200x700") + self.ui_location = ConfigItem("UI", "location", "100x100") + self.ui_maximized = ConfigItem("UI", "maximized", False, BoolValidator()) + + self.notify_SendTaskResultTime = OptionsConfigItem( + "Notify", + "SendTaskResultTime", + "不推送", + OptionsValidator(["不推送", "任何时刻", "仅失败时"]), + ) + self.notify_IfSendStatistic = ConfigItem( + "Notify", "IfSendStatistic", False, BoolValidator() + ) + self.notify_IfSendSixStar = ConfigItem( + "Notify", "IfSendSixStar", False, BoolValidator() + ) + self.notify_IfPushPlyer = ConfigItem( + "Notify", "IfPushPlyer", False, BoolValidator() + ) + self.notify_IfSendMail = ConfigItem( + "Notify", "IfSendMail", False, BoolValidator() + ) + self.notify_SMTPServerAddress = ConfigItem("Notify", "SMTPServerAddress", "") + self.notify_AuthorizationCode = ConfigItem("Notify", "AuthorizationCode", "") + self.notify_FromAddress = ConfigItem("Notify", "FromAddress", "") + 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", "" + ) + self.notify_IfPushDeer = ConfigItem( + "Notify", "IfPushDeer", False, BoolValidator() + ) + self.notify_IfPushDeerKey = ConfigItem("Notify", "PushDeerKey", "") + + self.update_IfAutoUpdate = ConfigItem( + "Update", "IfAutoUpdate", False, BoolValidator() + ) + self.update_UpdateType = OptionsConfigItem( + "Update", "UpdateType", "stable", OptionsValidator(["stable", "beta"]) + ) + self.update_ThreadNumb = RangeConfigItem( + "Update", "ThreadNumb", 8, RangeValidator(1, 32) + ) + self.update_ProxyUrlList = ConfigItem( + "Update", "ProxyUrlList", [], UrlListValidator() + ) + self.update_MirrorChyanCDK = ConfigItem("Update", "MirrorChyanCDK", "") + + def toDict(self, serialize=True): + """convert config items to `dict`""" + items = {} + for name in dir(self._cfg): + item = getattr(self._cfg, name) + if not isinstance(item, ConfigItem): + continue + + value = item.serialize() if serialize else item.value + if not items.get(item.group): + if not item.name: + items[item.group] = value + else: + items[item.group] = {} + + if item.name: + items[item.group][item.name] = value + + return items + + @exceptionHandler() + def load(self, file=None, config=None): + """load config + + Parameters + ---------- + file: str or Path + the path of json config file + + config: Config + config object to be initialized + """ + if isinstance(config, QConfig): + self._cfg = config + self._cfg.themeChanged.connect(self.themeChanged) + + if isinstance(file, (str, Path)): + self._cfg.file = Path(file) + + try: + with open(self._cfg.file, encoding="utf-8") as f: + cfg = json.load(f) + except: + cfg = {} + + # map config items'key to item + items = {} + for name in dir(self._cfg): + item = getattr(self._cfg, name) + if isinstance(item, ConfigItem): + items[item.key] = item + + # update the value of config item + for k, v in cfg.items(): + if not isinstance(v, dict) and items.get(k) is not None: + items[k].deserializeFrom(v) + elif isinstance(v, dict): + for key, value in v.items(): + key = k + "." + key + if items.get(key) is not None: + items[key].deserializeFrom(value) + + self.theme = self.get(self._cfg.themeMode) + + +class QueueConfig(QConfig): + """队列配置""" + + def __init__(self) -> None: + super().__init__() + + self.queueSet_Name = ConfigItem("QueueSet", "Name", "") + self.queueSet_Enabled = ConfigItem( + "QueueSet", "Enabled", False, BoolValidator() + ) + self.queueSet_AfterAccomplish = OptionsConfigItem( + "QueueSet", + "AfterAccomplish", + "None", + OptionsValidator(["None", "KillSelf", "Sleep", "Hibernate", "Shutdown"]), + ) + + self.time_TimeEnabled_0 = ConfigItem( + "Time", "TimeEnabled_0", False, BoolValidator() + ) + self.time_TimeSet_0 = ConfigItem("Time", "TimeSet_0", "00:00") + + self.time_TimeEnabled_1 = ConfigItem( + "Time", "TimeEnabled_1", False, BoolValidator() + ) + self.time_TimeSet_1 = ConfigItem("Time", "TimeSet_1", "00:00") + + self.time_TimeEnabled_2 = ConfigItem( + "Time", "TimeEnabled_2", False, BoolValidator() + ) + self.time_TimeSet_2 = ConfigItem("Time", "TimeSet_2", "00:00") + + self.time_TimeEnabled_3 = ConfigItem( + "Time", "TimeEnabled_3", False, BoolValidator() + ) + self.time_TimeSet_3 = ConfigItem("Time", "TimeSet_3", "00:00") + + self.time_TimeEnabled_4 = ConfigItem( + "Time", "TimeEnabled_4", False, BoolValidator() + ) + self.time_TimeSet_4 = ConfigItem("Time", "TimeSet_4", "00:00") + + self.time_TimeEnabled_5 = ConfigItem( + "Time", "TimeEnabled_5", False, BoolValidator() + ) + self.time_TimeSet_5 = ConfigItem("Time", "TimeSet_5", "00:00") + + self.time_TimeEnabled_6 = ConfigItem( + "Time", "TimeEnabled_6", False, BoolValidator() + ) + self.time_TimeSet_6 = ConfigItem("Time", "TimeSet_6", "00:00") + + self.time_TimeEnabled_7 = ConfigItem( + "Time", "TimeEnabled_7", False, BoolValidator() + ) + self.time_TimeSet_7 = ConfigItem("Time", "TimeSet_7", "00:00") + + self.time_TimeEnabled_8 = ConfigItem( + "Time", "TimeEnabled_8", False, BoolValidator() + ) + self.time_TimeSet_8 = ConfigItem("Time", "TimeSet_8", "00:00") + + self.time_TimeEnabled_9 = ConfigItem( + "Time", "TimeEnabled_9", False, BoolValidator() + ) + self.time_TimeSet_9 = ConfigItem("Time", "TimeSet_9", "00:00") + + self.queue_Member_1 = OptionsConfigItem("Queue", "Member_1", "禁用") + self.queue_Member_2 = OptionsConfigItem("Queue", "Member_2", "禁用") + self.queue_Member_3 = OptionsConfigItem("Queue", "Member_3", "禁用") + self.queue_Member_4 = OptionsConfigItem("Queue", "Member_4", "禁用") + self.queue_Member_5 = OptionsConfigItem("Queue", "Member_5", "禁用") + self.queue_Member_6 = OptionsConfigItem("Queue", "Member_6", "禁用") + self.queue_Member_7 = OptionsConfigItem("Queue", "Member_7", "禁用") + self.queue_Member_8 = OptionsConfigItem("Queue", "Member_8", "禁用") + self.queue_Member_9 = OptionsConfigItem("Queue", "Member_9", "禁用") + self.queue_Member_10 = OptionsConfigItem("Queue", "Member_10", "禁用") + + def toDict(self, serialize=True): + """convert config items to `dict`""" + items = {} + for name in dir(self._cfg): + item = getattr(self._cfg, name) + if not isinstance(item, ConfigItem): + continue + + value = item.serialize() if serialize else item.value + if not items.get(item.group): + if not item.name: + items[item.group] = value + else: + items[item.group] = {} + + if item.name: + items[item.group][item.name] = value + + return items + + @exceptionHandler() + def load(self, file=None, config=None): + """load config + + Parameters + ---------- + file: str or Path + the path of json config file + + config: Config + config object to be initialized + """ + if isinstance(config, QConfig): + self._cfg = config + self._cfg.themeChanged.connect(self.themeChanged) + + if isinstance(file, (str, Path)): + self._cfg.file = Path(file) + + try: + with open(self._cfg.file, encoding="utf-8") as f: + cfg = json.load(f) + except: + cfg = {} + + # map config items'key to item + items = {} + for name in dir(self._cfg): + item = getattr(self._cfg, name) + if isinstance(item, ConfigItem): + items[item.key] = item + + # update the value of config item + for k, v in cfg.items(): + if not isinstance(v, dict) and items.get(k) is not None: + items[k].deserializeFrom(v) + elif isinstance(v, dict): + for key, value in v.items(): + key = k + "." + key + if items.get(key) is not None: + items[key].deserializeFrom(value) + + self.theme = self.get(self._cfg.themeMode) + + +class MaaConfig(QConfig): + """MAA配置""" + + def __init__(self) -> None: + super().__init__() + + self.MaaSet_Name = ConfigItem("MaaSet", "Name", "") + self.MaaSet_Path = ConfigItem("MaaSet", "Path", ".", FolderValidator()) + + self.RunSet_TaskTransitionMethod = OptionsConfigItem( + "RunSet", + "TaskTransitionMethod", + "ExitEmulator", + OptionsValidator(["NoAction", "ExitGame", "ExitEmulator"]), + ) + self.RunSet_EnhanceTask = OptionsConfigItem( + "RunSet", + "EnhanceTask", + "None", + OptionsValidator(["None", "KillADB", "KillEmulator", "KillADB&Emulator"]), + ) + self.RunSet_ProxyTimesLimit = RangeConfigItem( + "RunSet", "ProxyTimesLimit", 0, RangeValidator(0, 1024) + ) + self.RunSet_AnnihilationTimeLimit = RangeConfigItem( + "RunSet", "AnnihilationTimeLimit", 40, RangeValidator(1, 1024) + ) + self.RunSet_RoutineTimeLimit = RangeConfigItem( + "RunSet", "RoutineTimeLimit", 10, RangeValidator(1, 1024) + ) + self.RunSet_RunTimesLimit = RangeConfigItem( + "RunSet", "RunTimesLimit", 3, RangeValidator(1, 1024) + ) + self.RunSet_AnnihilationWeeklyLimit = ConfigItem( + "RunSet", "AnnihilationWeeklyLimit", False, BoolValidator() + ) + + def toDict(self, serialize=True): + """convert config items to `dict`""" + items = {} + for name in dir(self._cfg): + item = getattr(self._cfg, name) + if not isinstance(item, ConfigItem): + continue + + value = item.serialize() if serialize else item.value + if not items.get(item.group): + if not item.name: + items[item.group] = value + else: + items[item.group] = {} + + if item.name: + items[item.group][item.name] = value + + return items + + @exceptionHandler() + def load(self, file=None, config=None): + """load config + + Parameters + ---------- + file: str or Path + the path of json config file + + config: Config + config object to be initialized + """ + if isinstance(config, QConfig): + self._cfg = config + self._cfg.themeChanged.connect(self.themeChanged) + + if isinstance(file, (str, Path)): + self._cfg.file = Path(file) + + try: + with open(self._cfg.file, encoding="utf-8") as f: + cfg = json.load(f) + except: + cfg = {} + + # map config items'key to item + items = {} + for name in dir(self._cfg): + item = getattr(self._cfg, name) + if isinstance(item, ConfigItem): + items[item.key] = item + + # update the value of config item + for k, v in cfg.items(): + if not isinstance(v, dict) and items.get(k) is not None: + items[k].deserializeFrom(v) + elif isinstance(v, dict): + for key, value in v.items(): + key = k + "." + key + if items.get(key) is not None: + items[key].deserializeFrom(value) + + self.theme = self.get(self._cfg.themeMode) + + +class MaaUserConfig(QConfig): + """MAA用户配置""" + + def __init__(self) -> None: + super().__init__() + + self.Info_Name = ConfigItem("Info", "Name", "新用户") + self.Info_Id = ConfigItem("Info", "Id", "") + self.Info_Mode = OptionsConfigItem( + "Info", "Mode", "简洁", OptionsValidator(["简洁", "详细"]) + ) + self.Info_GameIdMode = OptionsConfigItem( + "Info", "GameIdMode", "固定", OptionsValidator(["固定"]) + ) + self.Info_Server = OptionsConfigItem( + "Info", "Server", "Official", OptionsValidator(["Official", "Bilibili"]) + ) + self.Info_Status = ConfigItem("Info", "Status", True, BoolValidator()) + self.Info_RemainedDay = ConfigItem( + "Info", "RemainedDay", -1, RangeValidator(-1, 1024) + ) + self.Info_Annihilation = ConfigItem( + "Info", "Annihilation", False, BoolValidator() + ) + self.Info_Routine = ConfigItem("Info", "Routine", False, BoolValidator()) + self.Info_Infrastructure = ConfigItem( + "Info", "Infrastructure", False, BoolValidator() + ) + self.Info_Password = ConfigItem("Info", "Password", "") + self.Info_Notes = ConfigItem("Info", "Notes", "无") + self.Info_MedicineNumb = ConfigItem( + "Info", "MedicineNumb", 0, RangeValidator(0, 1024) + ) + self.Info_GameId = ConfigItem("Info", "GameId", "-") + self.Info_GameId_1 = ConfigItem("Info", "GameId_1", "-") + self.Info_GameId_2 = ConfigItem("Info", "GameId_2", "-") + + self.Data_LastProxyDate = ConfigItem("Data", "LastProxyDate", "2000-01-01") + self.Data_LastAnnihilationDate = ConfigItem( + "Data", "LastAnnihilationDate", "2000-01-01" + ) + self.Data_ProxyTimes = ConfigItem( + "Data", "ProxyTimes", 0, RangeValidator(0, 1024) + ) + self.Data_IfPassCheck = ConfigItem("Data", "IfPassCheck", True, BoolValidator()) + + def toDict(self, serialize=True): + """convert config items to `dict`""" + items = {} + for name in dir(self._cfg): + item = getattr(self._cfg, name) + if not isinstance(item, ConfigItem): + continue + + value = item.serialize() if serialize else item.value + if not items.get(item.group): + if not item.name: + items[item.group] = value + else: + items[item.group] = {} + + if item.name: + items[item.group][item.name] = value + + return items + + @exceptionHandler() + def load(self, file=None, config=None): + """load config + + Parameters + ---------- + file: str or Path + the path of json config file + + config: Config + config object to be initialized + """ + if isinstance(config, QConfig): + self._cfg = config + self._cfg.themeChanged.connect(self.themeChanged) + + if isinstance(file, (str, Path)): + self._cfg.file = Path(file) + + try: + with open(self._cfg.file, encoding="utf-8") as f: + cfg = json.load(f) + except: + cfg = {} + + # map config items'key to item + items = {} + for name in dir(self._cfg): + item = getattr(self._cfg, name) + if isinstance(item, ConfigItem): + items[item.key] = item + + # update the value of config item + for k, v in cfg.items(): + if not isinstance(v, dict) and items.get(k) is not None: + items[k].deserializeFrom(v) + elif isinstance(v, dict): + for key, value in v.items(): + key = k + "." + key + if items.get(key) is not None: + items[key].deserializeFrom(value) + + self.theme = self.get(self._cfg.themeMode) + + +class AppConfig(GlobalConfig): + + VERSION = "4.2.5.9" + + gameid_refreshed = Signal() + + def __init__(self) -> None: + super().__init__() self.app_path = Path(sys.argv[0]).resolve().parent # 获取软件根目录 self.app_path_sys = str(Path(sys.argv[0]).resolve()) # 获取软件自身的路径 @@ -66,13 +620,14 @@ class AppConfig: self.PASSWORD = "" self.running_list = [] self.silence_list = [] + self.gameid_dict = {"value": [], "text": []} + self.if_ignore_silence = False self.if_database_opened = False - # 检查文件完整性 self.initialize() def initialize(self) -> None: - """初始化程序的配置文件""" + """初始化程序配置管理模块""" # 检查目录 (self.app_path / "config").mkdir(parents=True, exist_ok=True) @@ -89,17 +644,13 @@ class AppConfig: with self.version_path.open(mode="w", encoding="utf-8") as f: json.dump(version, f, ensure_ascii=False, indent=4) - # 生成预设gameid替换方案文件 - if not self.gameid_path.exists(): - self.gameid_path.write_text( - "龙门币:CE-6\n技能:CA-5\n红票:AP-5\n经验:LS-6\n剿灭模式:Annihilation", - encoding="utf-8", - ) + self.load(self.config_path, self) + self.save() self.init_logger() - self.init_config() self.check_data() - logger.info("程序配置管理模块初始化完成") + self.get_gameid("ALL") + logger.info("程序初始化完成") def init_logger(self) -> None: """初始化日志记录器""" @@ -125,38 +676,125 @@ class AppConfig: logger.info("日志记录器初始化完成") - def init_config(self) -> None: - """初始化配置类""" + def get_gameid(self, mode: str) -> list: - self.global_config = GlobalConfig() - self.queue_config = QueueConfig() - self.maa_config = MaaConfig() + # 从MAA服务器获取活动关卡信息 + for _ in range(3): + try: + response = requests.get( + "https://ota.maa.plus/MaaAssistantArknights/api/gui/StageActivity.json" + ) + gameid_infos: List[ + Dict[str, Union[str, Dict[str, Union[str, int]]]] + ] = response.json()["Official"]["sideStoryStage"] + break + except Exception as e: + err = e + time.sleep(0.1) + else: + logger.warning(f"无法从MAA服务器获取活动关卡信息:{err}") + gameid_infos = [] - qconfig.load(self.config_path, self.global_config) + gameid_dict = {"value": [], "text": []} - config_list = self.search_config() - for config in config_list: - if config[0] == "Maa": - qconfig.load(config[1], self.maa_config) - self.maa_config.save() - elif config[0] == "Queue": - qconfig.load(config[1], self.queue_config) - self.queue_config.save() + for gameid_info in gameid_infos: - logger.info("配置类初始化完成") + if ( + datetime.strptime( + gameid_info["Activity"]["UtcStartTime"], "%Y/%m/%d %H:%M:%S" + ) + < datetime.now() + < datetime.strptime( + gameid_info["Activity"]["UtcExpireTime"], "%Y/%m/%d %H:%M:%S" + ) + ): + gameid_dict["value"].append(gameid_info["Value"]) + gameid_dict["text"].append(gameid_info["Value"]) - def init_database(self, mode: str) -> None: - """初始化用户数据库""" + if mode == "ALL": + self.gameid_dict["value"] = gameid_dict["value"] + [ + "-", + "1-7", + "R8-11", + "12-17-HARD", + "CE-6", + "AP-5", + "CA-5", + "LS-6", + "SK-5", + "PR-A-1", + "PR-A-2", + "PR-B-1", + "PR-B-2", + "PR-C-1", + "PR-C-2", + "PR-D-1", + "PR-D-2", + ] + self.gameid_dict["text"] = gameid_dict["text"] + [ + "当前/上次", + "1-7", + "R8-11", + "12-17-HARD", + "龙门币-6/5", + "红票-5", + "技能-5", + "经验-6/5", + "碳-5", + "奶/盾芯片", + "奶/盾芯片组", + "术/狙芯片", + "术/狙芯片组", + "先/辅芯片", + "先/辅芯片组", + "近/特芯片", + "近/特芯片组", + ] - if mode == "Maa": - self.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,routine text,annihilation text,infrastructure text,password byte,notes text,numb int,mode text,uid int)" - ) - self.cur.execute("CREATE TABLE version(v text)") - self.cur.execute("INSERT INTO version VALUES(?)", ("v1.4",)) - self.db.commit() + self.gameid_refreshed.emit() - logger.info("用户数据库初始化完成") + elif mode == "Week": + + days = datetime.strptime(self.server_date(), "%Y-%m-%d").isoweekday() + + gameid_list = [ + {"value": "-", "text": "当前/上次", "days": [1, 2, 3, 4, 5, 6, 7]}, + {"value": "1-7", "text": "1-7", "days": [1, 2, 3, 4, 5, 6, 7]}, + {"value": "R8-11", "text": "R8-11", "days": [1, 2, 3, 4, 5, 6, 7]}, + { + "value": "12-17-HARD", + "text": "12-17-HARD", + "days": [1, 2, 3, 4, 5, 6, 7], + }, + {"value": "CE-6", "text": "龙门币-6/5", "days": [2, 4, 6, 7]}, + {"value": "AP-5", "text": "红票-5", "days": [1, 4, 6, 7]}, + {"value": "CA-5", "text": "技能-5", "days": [2, 3, 5, 7]}, + {"value": "LS-6", "text": "经验-6/5", "days": [1, 2, 3, 4, 5, 6, 7]}, + {"value": "SK-5", "text": "碳-5", "days": [1, 3, 5, 6]}, + {"value": "PR-A-1", "text": "奶/盾芯片", "days": [1, 4, 5, 7]}, + {"value": "PR-A-2", "text": "奶/盾芯片组", "days": [1, 4, 5, 7]}, + {"value": "PR-B-1", "text": "术/狙芯片", "days": [1, 2, 5, 6]}, + {"value": "PR-B-2", "text": "术/狙芯片组", "days": [1, 2, 5, 6]}, + {"value": "PR-C-1", "text": "先/辅芯片", "days": [3, 4, 6, 7]}, + {"value": "PR-C-2", "text": "先/辅芯片组", "days": [3, 4, 6, 7]}, + {"value": "PR-D-1", "text": "近/特芯片", "days": [2, 3, 6, 7]}, + {"value": "PR-D-2", "text": "近/特芯片组", "days": [2, 3, 6, 7]}, + ] + + for gameid_info in gameid_list: + if days in gameid_info["days"]: + gameid_dict["value"].append(gameid_info["value"]) + gameid_dict["text"].append(gameid_info["text"]) + + return gameid_dict + + def server_date(self) -> str: + """获取当前的服务器日期""" + + dt = datetime.now() + if dt.time() < datetime.min.time().replace(hour=4): + dt = dt - timedelta(days=1) + return dt.strftime("%Y-%m-%d") def check_data(self) -> None: """检查用户数据文件并处理数据文件版本更新""" @@ -166,7 +804,7 @@ class AppConfig: db = sqlite3.connect(self.database_path) cur = db.cursor() cur.execute("CREATE TABLE version(v text)") - cur.execute("INSERT INTO version VALUES(?)", ("v1.4",)) + cur.execute("INSERT INTO version VALUES(?)", ("v1.5",)) db.commit() cur.close() db.close() @@ -177,7 +815,7 @@ class AppConfig: cur.execute("SELECT * FROM version WHERE True") version = cur.fetchall() - if version[0][0] != "v1.4": + if version[0][0] != "v1.5": logger.info("数据文件版本更新开始") if_streaming = False # v1.0-->v1.1 @@ -392,79 +1030,276 @@ class AppConfig: ) 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") + 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(): + if maa_dir.is_dir(): + member_dict[maa_dir.name] = { + "Type": "Maa", + "Path": maa_dir, + } + + member_dict = dict( + sorted(member_dict.items(), key=lambda x: int(x[0][3:])) + ) + + for name, config in member_dict.items(): + if config["Type"] == "Maa": + + _db = sqlite3.connect(config["Path"] / "user_data.db") + _cur = _db.cursor() + _cur.execute("SELECT * FROM adminx WHERE True") + data = _cur.fetchall() + data = [list(row) for row in data] + data = sorted(data, key=lambda x: (-len(x[15]), x[16])) + _cur.close() + _db.close() + + (config["Path"] / "user_data.db").unlink() + + (config["Path"] / f"UserData").mkdir( + parents=True, exist_ok=True + ) + + for i in range(len(data)): + + info = { + "Data": { + "IfPassCheck": True, + "LastAnnihilationDate": "2000-01-01", + "LastProxyDate": data[i][5], + "ProxyTimes": data[i][14], + }, + "Info": { + "Annihilation": bool(data[i][10] == "y"), + "GameId": data[i][6], + "GameIdMode": "固定", + "GameId_1": data[i][7], + "GameId_2": data[i][8], + "Id": data[i][1], + "Infrastructure": bool(data[i][11] == "y"), + "MedicineNumb": 0, + "Mode": ( + "简洁" if data[i][15] == "simple" else "详细" + ), + "Name": data[i][0], + "Notes": data[i][13], + "Password": base64.b64encode(data[i][12]).decode( + "utf-8" + ), + "RemainedDay": data[i][3], + "Routine": bool(data[i][9] == "y"), + "Server": data[i][2], + "Status": bool(data[i][4] == "y"), + }, + } + + (config["Path"] / f"UserData/用户_{i + 1}").mkdir( + parents=True, exist_ok=True + ) + with ( + config["Path"] / f"UserData/用户_{i + 1}/config.json" + ).open(mode="w", encoding="utf-8") as f: + json.dump(info, f, ensure_ascii=False, indent=4) + + if ( + self.app_path + / f"config/MaaConfig/{name}/{data[i][15]}/{data[i][16]}/annihilation/gui.json" + ).exists(): + ( + config["Path"] + / f"UserData/用户_{i + 1}/Annihilation" + ).mkdir(parents=True, exist_ok=True) + shutil.move( + self.app_path + / f"config/MaaConfig/{name}/{data[i][15]}/{data[i][16]}/annihilation/gui.json", + config["Path"] + / f"UserData/用户_{i + 1}/Annihilation/gui.json", + ) + if ( + self.app_path + / f"config/MaaConfig/{name}/{data[i][15]}/{data[i][16]}/routine/gui.json" + ).exists(): + ( + config["Path"] / f"UserData/用户_{i + 1}/Routine" + ).mkdir(parents=True, exist_ok=True) + shutil.move( + self.app_path + / f"config/MaaConfig/{name}/{data[i][15]}/{data[i][16]}/routine/gui.json", + config["Path"] + / f"UserData/用户_{i + 1}/Routine/gui.json", + ) + if ( + self.app_path + / f"config/MaaConfig/{name}/{data[i][15]}/{data[i][16]}/infrastructure/infrastructure.json" + ).exists(): + ( + config["Path"] + / f"UserData/用户_{i + 1}/Infrastructure" + ).mkdir(parents=True, exist_ok=True) + shutil.move( + self.app_path + / f"config/MaaConfig/{name}/{data[i][15]}/{data[i][16]}/infrastructure/infrastructure.json", + config["Path"] + / f"UserData/用户_{i + 1}/Infrastructure/infrastructure.json", + ) + + if (config["Path"] / f"simple").exists(): + shutil.rmtree(config["Path"] / f"simple") + if (config["Path"] / f"beta").exists(): + shutil.rmtree(config["Path"] / f"beta") + cur.close() db.close() logger.info("数据文件版本更新完成") - def search_config(self) -> list: - """搜索所有子配置文件""" - - config_list = [] + def search_member(self) -> None: + """搜索所有脚本实例""" + self.member_dict: Dict[ + str, + Dict[ + str, + Union[ + str, + Path, + MaaConfig, + Dict[str, Dict[str, Union[Path, MaaUserConfig]]], + ], + ], + ] = {} if (self.app_path / "config/MaaConfig").exists(): - for subdir in (self.app_path / "config/MaaConfig").iterdir(): - if subdir.is_dir(): - config_list.append(["Maa", subdir / "config.json"]) + for maa_dir in (self.app_path / "config/MaaConfig").iterdir(): + if maa_dir.is_dir(): + + maa_config = MaaConfig() + maa_config.load(maa_dir / "config.json", maa_config) + maa_config.save() + + # user_dict: Dict[str, Dict[str, Union[Path, MaaUserConfig]]] = {} + # for user_dir in (maa_dir / "UserData").iterdir(): + # if user_dir.is_dir(): + + # # user_config = MaaUserConfig() + # # user_config.load(user_dir / "config.json", user_config) + # # user_config.save() + + # user_dict[user_dir.stem] = { + # "Path": user_dir, + # "Config": None, + # } + + self.member_dict[maa_dir.name] = { + "Type": "Maa", + "Path": maa_dir, + "Config": maa_config, + "UserData": None, + } + + self.member_dict = dict( + sorted(self.member_dict.items(), key=lambda x: int(x[0][3:])) + ) + + def search_maa_user(self, name) -> None: + + user_dict: Dict[str, Dict[str, Union[Path, MaaUserConfig]]] = {} + for user_dir in (Config.member_dict[name]["Path"] / "UserData").iterdir(): + if user_dir.is_dir(): + + user_config = MaaUserConfig() + user_config.load(user_dir / "config.json", user_config) + user_config.save() + + 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_queue(self): + """搜索所有调度队列实例""" + + self.queue_dict: Dict[str, Dict[str, Union[Path, QueueConfig]]] = {} if (self.app_path / "config/QueueConfig").exists(): for json_file in (self.app_path / "config/QueueConfig").glob("*.json"): - config_list.append(["Queue", json_file]) - return config_list + queue_config = QueueConfig() + queue_config.load(json_file, queue_config) + queue_config.save() - def open_database(self, mode: str, index: str = None) -> None: - """打开数据库""" + self.queue_dict[json_file.stem] = { + "Path": json_file, + "Config": queue_config, + } - self.close_database() - self.db = sqlite3.connect( - self.app_path / f"config/{mode}Config/{index}/user_data.db" + self.queue_dict = dict( + sorted(self.queue_dict.items(), key=lambda x: int(x[0][5:])) ) - self.cur = self.db.cursor() - self.if_database_opened = True - def close_database(self) -> None: - """关闭数据库""" + def change_queue(self, old: str, new: str) -> None: + """修改调度队列配置文件的队列参数""" - if self.if_database_opened: - self.cur.close() - self.db.close() - self.if_database_opened = False + for queue in self.queue_dict.values(): + + if queue["Config"].get(queue["Config"].queue_Member_1) == old: + queue["Config"].set(queue["Config"].queue_Member_1, new) + if queue["Config"].get(queue["Config"].queue_Member_2) == old: + queue["Config"].set(queue["Config"].queue_Member_2, new) + if queue["Config"].get(queue["Config"].queue_Member_3) == old: + queue["Config"].set(queue["Config"].queue_Member_3, new) + if queue["Config"].get(queue["Config"].queue_Member_4) == old: + queue["Config"].set(queue["Config"].queue_Member_4, new) + if queue["Config"].get(queue["Config"].queue_Member_5) == old: + queue["Config"].set(queue["Config"].queue_Member_5, new) + if queue["Config"].get(queue["Config"].queue_Member_6) == old: + queue["Config"].set(queue["Config"].queue_Member_6, new) + if queue["Config"].get(queue["Config"].queue_Member_7) == old: + queue["Config"].set(queue["Config"].queue_Member_7, new) + if queue["Config"].get(queue["Config"].queue_Member_8) == old: + queue["Config"].set(queue["Config"].queue_Member_8, new) + if queue["Config"].get(queue["Config"].queue_Member_9) == old: + queue["Config"].set(queue["Config"].queue_Member_9, new) + if queue["Config"].get(queue["Config"].queue_Member_10) == old: + queue["Config"].set(queue["Config"].queue_Member_10, new) def change_user_info( - self, - data_path: Path, - modes: list, - uids: list, - days: list, - lasts: list, - notes: list, - numbs: list, + self, name: str, user_data: Dict[str, Dict[str, Union[str, Path, dict]]] ) -> None: - """将代理完成后发生改动的用户信息同步至本地数据库""" + """代理完成后保存改动的用户信息""" - db = sqlite3.connect(data_path / "user_data.db") - cur = db.cursor() + for user, info in user_data.items(): - for index in range(len(uids)): - cur.execute( - "UPDATE adminx SET day = ? WHERE mode = ? AND uid = ?", - (days[index], modes[index], uids[index]), + user_config = self.member_dict[name]["UserData"][user]["Config"] + + user_config.set( + user_config.Info_RemainedDay, info["Config"]["Info"]["RemainedDay"] ) - cur.execute( - "UPDATE adminx SET last = ? WHERE mode = ? AND uid = ?", - (lasts[index], modes[index], uids[index]), + user_config.set( + user_config.Data_LastProxyDate, info["Config"]["Data"]["LastProxyDate"] ) - cur.execute( - "UPDATE adminx SET notes = ? WHERE mode = ? AND uid = ?", - (notes[index], modes[index], uids[index]), + user_config.set( + user_config.Data_LastAnnihilationDate, + info["Config"]["Data"]["LastAnnihilationDate"], ) - cur.execute( - "UPDATE adminx SET numb = ? WHERE mode = ? AND uid = ?", - (numbs[index], modes[index], uids[index]), + user_config.set( + user_config.Data_ProxyTimes, info["Config"]["Data"]["ProxyTimes"] + ) + user_config.set( + user_config.Data_IfPassCheck, info["Config"]["Data"]["IfPassCheck"] ) - db.commit() - cur.close() - db.close() def save_maa_log(self, log_path: Path, logs: list, maa_result: str) -> bool: """保存MAA日志""" @@ -508,34 +1343,34 @@ class AppConfig: # 掉落统计 # 存储所有关卡的掉落统计 all_stage_drops = {} - + # 查找所有Fight任务的开始和结束位置 fight_tasks = [] for i, line in enumerate(logs): if "开始任务: Fight" in line: # 查找对应的任务结束位置 end_index = -1 - for j in range(i+1, len(logs)): + for j in range(i + 1, len(logs)): if "完成任务: Fight" in logs[j]: end_index = j break # 如果遇到新的Fight任务开始,则当前任务没有正常结束 if j < len(logs) and "开始任务: Fight" in logs[j]: break - + # 如果找到了结束位置,记录这个任务的范围 if end_index != -1: fight_tasks.append((i, end_index)) - + # 处理每个Fight任务 for start_idx, end_idx in fight_tasks: # 提取当前任务的日志 - task_logs = logs[start_idx:end_idx+1] - + task_logs = logs[start_idx : end_idx + 1] + # 查找任务中的最后一次掉落统计 last_drop_stats = {} current_stage = None - + for line in task_logs: # 匹配掉落统计行,如"1-7 掉落统计:" drop_match = re.search(r"([A-Za-z0-9\-]+) 掉落统计:", line) @@ -544,7 +1379,7 @@ class AppConfig: current_stage = drop_match.group(1) last_drop_stats = {} continue - + # 如果已经找到了关卡,处理掉落物 if current_stage: item_match: List[str] = re.findall( @@ -565,17 +1400,17 @@ class AppConfig: "剩余时间", ]: last_drop_stats[item] = total - + # 如果任务中有掉落统计,更新总统计 if current_stage and last_drop_stats: if current_stage not in all_stage_drops: all_stage_drops[current_stage] = {} - + # 累加掉落数据 for item, count in last_drop_stats.items(): all_stage_drops[current_stage].setdefault(item, 0) all_stage_drops[current_stage][item] += count - + # 将累加后的掉落数据保存到结果中 data["drop_statistics"] = all_stage_drops @@ -739,206 +1574,5 @@ class AppConfig: key, {"Time": "0000-00-00 00:00", "History": "暂无历史运行记录"} ) - def clear_maa_config(self) -> None: - """清空MAA配置""" - - self.maa_config.set(self.maa_config.MaaSet_Name, "") - self.maa_config.set(self.maa_config.MaaSet_Path, ".") - self.maa_config.set(self.maa_config.RunSet_TaskTransitionMethod, "ExitEmulator") - self.maa_config.set(self.maa_config.RunSet_ProxyTimesLimit, 0) - self.maa_config.set(self.maa_config.RunSet_AnnihilationTimeLimit, 40) - self.maa_config.set(self.maa_config.RunSet_RoutineTimeLimit, 10) - self.maa_config.set(self.maa_config.RunSet_RunTimesLimit, 3) - self.maa_config.set(self.maa_config.MaaSet_Name, "") - self.maa_config.set(self.maa_config.MaaSet_Name, "") - self.maa_config.set(self.maa_config.MaaSet_Name, "") - - def clear_queue_config(self) -> None: - """清空队列配置""" - - self.queue_config.set(self.queue_config.queueSet_Name, "") - self.queue_config.set(self.queue_config.queueSet_Enabled, False) - self.queue_config.set(self.queue_config.queueSet_AfterAccomplish, "None") - - self.queue_config.set(self.queue_config.time_TimeEnabled_0, False) - self.queue_config.set(self.queue_config.time_TimeSet_0, "00:00") - self.queue_config.set(self.queue_config.time_TimeEnabled_1, False) - self.queue_config.set(self.queue_config.time_TimeSet_1, "00:00") - self.queue_config.set(self.queue_config.time_TimeEnabled_2, False) - self.queue_config.set(self.queue_config.time_TimeSet_2, "00:00") - self.queue_config.set(self.queue_config.time_TimeEnabled_3, False) - self.queue_config.set(self.queue_config.time_TimeSet_3, "00:00") - self.queue_config.set(self.queue_config.time_TimeEnabled_4, False) - self.queue_config.set(self.queue_config.time_TimeSet_4, "00:00") - self.queue_config.set(self.queue_config.time_TimeEnabled_5, False) - self.queue_config.set(self.queue_config.time_TimeSet_5, "00:00") - self.queue_config.set(self.queue_config.time_TimeEnabled_6, False) - self.queue_config.set(self.queue_config.time_TimeSet_6, "00:00") - self.queue_config.set(self.queue_config.time_TimeEnabled_7, False) - self.queue_config.set(self.queue_config.time_TimeSet_7, "00:00") - self.queue_config.set(self.queue_config.time_TimeEnabled_8, False) - self.queue_config.set(self.queue_config.time_TimeSet_8, "00:00") - self.queue_config.set(self.queue_config.time_TimeEnabled_9, False) - self.queue_config.set(self.queue_config.time_TimeSet_9, "00:00") - - self.queue_config.set(self.queue_config.queue_Member_1, "禁用") - self.queue_config.set(self.queue_config.queue_Member_2, "禁用") - self.queue_config.set(self.queue_config.queue_Member_3, "禁用") - self.queue_config.set(self.queue_config.queue_Member_4, "禁用") - self.queue_config.set(self.queue_config.queue_Member_5, "禁用") - self.queue_config.set(self.queue_config.queue_Member_6, "禁用") - self.queue_config.set(self.queue_config.queue_Member_7, "禁用") - self.queue_config.set(self.queue_config.queue_Member_8, "禁用") - self.queue_config.set(self.queue_config.queue_Member_9, "禁用") - self.queue_config.set(self.queue_config.queue_Member_10, "禁用") - - -class GlobalConfig(QConfig): - """全局配置""" - - function_HomeImageMode = OptionsConfigItem( - "Function", - "HomeImageMode", - "默认", - OptionsValidator(["默认", "自定义", "主题图像"]), - ) - function_HistoryRetentionTime = OptionsConfigItem( - "Function", "HistoryRetentionTime", 0, OptionsValidator([7, 15, 30, 60, 0]) - ) - function_IfAllowSleep = ConfigItem( - "Function", "IfAllowSleep", False, BoolValidator() - ) - function_IfSilence = ConfigItem("Function", "IfSilence", False, BoolValidator()) - function_BossKey = ConfigItem("Function", "BossKey", "") - function_IfAgreeBilibili = ConfigItem( - "Function", "IfAgreeBilibili", False, BoolValidator() - ) - - start_IfSelfStart = ConfigItem("Start", "IfSelfStart", False, BoolValidator()) - start_IfRunDirectly = ConfigItem("Start", "IfRunDirectly", False, BoolValidator()) - start_IfMinimizeDirectly = ConfigItem( - "Start", "IfMinimizeDirectly", False, BoolValidator() - ) - - ui_IfShowTray = ConfigItem("UI", "IfShowTray", False, BoolValidator()) - ui_IfToTray = ConfigItem("UI", "IfToTray", False, BoolValidator()) - ui_size = ConfigItem("UI", "size", "1200x700") - ui_location = ConfigItem("UI", "location", "100x100") - ui_maximized = ConfigItem("UI", "maximized", False, BoolValidator()) - - notify_SendTaskResultTime = OptionsConfigItem( - "Notify", - "SendTaskResultTime", - "不推送", - OptionsValidator(["不推送", "任何时刻", "仅失败时"]), - ) - notify_IfSendStatistic = ConfigItem( - "Notify", "IfSendStatistic", False, BoolValidator() - ) - notify_IfSendSixStar = ConfigItem("Notify", "IfSendSixStar", False, BoolValidator()) - notify_IfPushPlyer = ConfigItem("Notify", "IfPushPlyer", False, BoolValidator()) - notify_IfSendMail = ConfigItem("Notify", "IfSendMail", False, BoolValidator()) - notify_SMTPServerAddress = ConfigItem("Notify", "SMTPServerAddress", "") - notify_AuthorizationCode = ConfigItem("Notify", "AuthorizationCode", "") - notify_FromAddress = ConfigItem("Notify", "FromAddress", "") - notify_ToAddress = ConfigItem("Notify", "ToAddress", "") - notify_IfServerChan = ConfigItem("Notify", "IfServerChan", False, BoolValidator()) - notify_ServerChanKey = ConfigItem("Notify", "ServerChanKey", "") - notify_ServerChanChannel = ConfigItem("Notify", "ServerChanChannel", "") - notify_ServerChanTag = ConfigItem("Notify", "ServerChanTag", "") - notify_IfCompanyWebHookBot = ConfigItem( - "Notify", "IfCompanyWebHookBot", False, BoolValidator() - ) - notify_CompanyWebHookBotUrl = ConfigItem("Notify", "CompanyWebHookBotUrl", "") - notify_IfPushDeer = ConfigItem("Notify", "IfPushDeer", False, BoolValidator()) - notify_IfPushDeerKey = ConfigItem("Notify", "PushDeerKey", "") - - update_IfAutoUpdate = ConfigItem("Update", "IfAutoUpdate", False, BoolValidator()) - update_UpdateType = OptionsConfigItem( - "Update", "UpdateType", "main", OptionsValidator(["main", "dev"]) - ) - - -class QueueConfig(QConfig): - """队列配置""" - - queueSet_Name = ConfigItem("QueueSet", "Name", "") - queueSet_Enabled = ConfigItem("QueueSet", "Enabled", False, BoolValidator()) - queueSet_AfterAccomplish = OptionsConfigItem( - "QueueSet", - "AfterAccomplish", - "None", - OptionsValidator(["None", "KillSelf", "Sleep", "Hibernate", "Shutdown"]), - ) - - time_TimeEnabled_0 = ConfigItem("Time", "TimeEnabled_0", False, BoolValidator()) - time_TimeSet_0 = ConfigItem("Time", "TimeSet_0", "00:00") - - time_TimeEnabled_1 = ConfigItem("Time", "TimeEnabled_1", False, BoolValidator()) - time_TimeSet_1 = ConfigItem("Time", "TimeSet_1", "00:00") - - time_TimeEnabled_2 = ConfigItem("Time", "TimeEnabled_2", False, BoolValidator()) - time_TimeSet_2 = ConfigItem("Time", "TimeSet_2", "00:00") - - time_TimeEnabled_3 = ConfigItem("Time", "TimeEnabled_3", False, BoolValidator()) - time_TimeSet_3 = ConfigItem("Time", "TimeSet_3", "00:00") - - time_TimeEnabled_4 = ConfigItem("Time", "TimeEnabled_4", False, BoolValidator()) - time_TimeSet_4 = ConfigItem("Time", "TimeSet_4", "00:00") - - time_TimeEnabled_5 = ConfigItem("Time", "TimeEnabled_5", False, BoolValidator()) - time_TimeSet_5 = ConfigItem("Time", "TimeSet_5", "00:00") - - time_TimeEnabled_6 = ConfigItem("Time", "TimeEnabled_6", False, BoolValidator()) - time_TimeSet_6 = ConfigItem("Time", "TimeSet_6", "00:00") - - time_TimeEnabled_7 = ConfigItem("Time", "TimeEnabled_7", False, BoolValidator()) - time_TimeSet_7 = ConfigItem("Time", "TimeSet_7", "00:00") - - time_TimeEnabled_8 = ConfigItem("Time", "TimeEnabled_8", False, BoolValidator()) - time_TimeSet_8 = ConfigItem("Time", "TimeSet_8", "00:00") - - time_TimeEnabled_9 = ConfigItem("Time", "TimeEnabled_9", False, BoolValidator()) - time_TimeSet_9 = ConfigItem("Time", "TimeSet_9", "00:00") - - queue_Member_1 = OptionsConfigItem("Queue", "Member_1", "禁用") - queue_Member_2 = OptionsConfigItem("Queue", "Member_2", "禁用") - queue_Member_3 = OptionsConfigItem("Queue", "Member_3", "禁用") - queue_Member_4 = OptionsConfigItem("Queue", "Member_4", "禁用") - queue_Member_5 = OptionsConfigItem("Queue", "Member_5", "禁用") - queue_Member_6 = OptionsConfigItem("Queue", "Member_6", "禁用") - queue_Member_7 = OptionsConfigItem("Queue", "Member_7", "禁用") - queue_Member_8 = OptionsConfigItem("Queue", "Member_8", "禁用") - queue_Member_9 = OptionsConfigItem("Queue", "Member_9", "禁用") - queue_Member_10 = OptionsConfigItem("Queue", "Member_10", "禁用") - - -class MaaConfig(QConfig): - """MAA配置""" - - MaaSet_Name = ConfigItem("MaaSet", "Name", "") - MaaSet_Path = ConfigItem("MaaSet", "Path", ".", FolderValidator()) - - RunSet_TaskTransitionMethod = OptionsConfigItem( - "RunSet", - "TaskTransitionMethod", - "ExitEmulator", - OptionsValidator(["NoAction", "ExitGame", "ExitEmulator"]), - ) - RunSet_ProxyTimesLimit = RangeConfigItem( - "RunSet", "ProxyTimesLimit", 0, RangeValidator(0, 1024) - ) - RunSet_AnnihilationTimeLimit = RangeConfigItem( - "RunSet", "AnnihilationTimeLimit", 40, RangeValidator(1, 1024) - ) - RunSet_RoutineTimeLimit = RangeConfigItem( - "RunSet", "RoutineTimeLimit", 10, RangeValidator(1, 1024) - ) - RunSet_RunTimesLimit = RangeConfigItem( - "RunSet", "RunTimesLimit", 3, RangeValidator(1, 1024) - ) - Config = AppConfig() - - diff --git a/app/core/task_manager.py b/app/core/task_manager.py index 41acd51..7f6b830 100644 --- a/app/core/task_manager.py +++ b/app/core/task_manager.py @@ -28,8 +28,6 @@ v4.2 from loguru import logger from PySide6.QtCore import QThread, QObject, Signal from qfluentwidgets import MessageBox -import json -from pathlib import Path from datetime import datetime from typing import Dict, Union @@ -45,7 +43,7 @@ class Task(QThread): push_info_bar = Signal(str, str, str, int) question = Signal(str, str) question_response = Signal(bool) - update_user_info = Signal(Path, list, list, list, list, list, list) + update_user_info = Signal(str, dict) create_task_list = Signal(list) create_user_list = Signal(list) update_task_list = Signal(list) @@ -76,13 +74,8 @@ class Task(QThread): self.task = MaaManager( self.mode, - Config.app_path / f"config/MaaConfig/{self.name}", - ( - None - if "全局" in self.mode - else Config.app_path - / f"config/MaaConfig/{self.name}/beta/{self.info["SetMaaInfo"]["UserId"]}/{self.info["SetMaaInfo"]["SetType"]}" - ), + Config.member_dict[self.name], + (None if "全局" in self.mode else self.info["SetMaaInfo"]["Path"]), ) self.task.push_info_bar.connect(self.push_info_bar.emit) self.task.accomplish.connect(lambda: self.accomplish.emit([])) @@ -91,42 +84,52 @@ class Task(QThread): else: - self.member_dict = self.search_member() - self.task_dict = [ - [value, "等待"] - for _, value in self.info["Queue"].items() + self.task_list = [ + [ + ( + 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)}" + ), + "等待", + value, + ] + for _, value in sorted( + self.info["Queue"].items(), key=lambda x: int(x[0][7:]) + ) if value != "禁用" ] - self.create_task_list.emit(self.task_dict) + self.create_task_list.emit(self.task_list) - for i in range(len(self.task_dict)): + for task in self.task_list: if self.isInterruptionRequested(): break - self.task_dict[i][1] = "运行" - self.update_task_list.emit(self.task_dict) + task[1] = "运行" + self.update_task_list.emit(self.task_list) - if self.task_dict[i][0] in Config.running_list: + if task[2] in Config.running_list: - self.task_dict[i][1] = "跳过" - self.update_task_list.emit(self.task_dict) - logger.info(f"跳过任务:{self.task_dict[i][0]}") - self.push_info_bar.emit( - "info", "跳过任务", self.task_dict[i][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) continue - Config.running_list.append(self.task_dict[i][0]) - logger.info(f"任务开始:{self.task_dict[i][0]}") - self.push_info_bar.emit("info", "任务开始", self.task_dict[i][0], 3000) + Config.running_list.append(task[2]) + logger.info(f"任务开始:{task[0]}") + self.push_info_bar.emit("info", "任务开始", task[0], 3000) - if self.member_dict[self.task_dict[i][0]][0] == "Maa": + if Config.member_dict[task[2]]["Type"] == "Maa": self.task = MaaManager( self.mode[0:4], - self.member_dict[self.task_dict[i][0]][1], + Config.member_dict[task[2]], ) self.task.question.connect(self.question.emit) @@ -136,44 +139,21 @@ 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( - lambda modes, uids, days, lasts, notes, numbs: self.update_user_info.emit( - self.member_dict[self.task_dict[i][0]][1], - modes, - uids, - days, - lasts, - notes, - numbs, - ) - ) + self.task.update_user_info.connect(self.update_user_info.emit) self.task.accomplish.connect( - lambda log: self.task_accomplish(self.task_dict[i][0], log) + lambda log: self.task_accomplish(task[2], log) ) self.task.run() - Config.running_list.remove(self.task_dict[i][0]) + Config.running_list.remove(task[2]) - self.task_dict[i][1] = "完成" - logger.info(f"任务完成:{self.task_dict[i][0]}") - self.push_info_bar.emit("info", "任务完成", self.task_dict[i][0], 3000) + task[1] = "完成" + logger.info(f"任务完成:{task[0]}") + self.push_info_bar.emit("info", "任务完成", task[0], 3000) self.accomplish.emit(self.logs) - def search_member(self) -> dict: - """搜索所有脚本实例及其路径""" - - member_dict = {} - - if (Config.app_path / "config/MaaConfig").exists(): - for subdir in (Config.app_path / "config/MaaConfig").iterdir(): - if subdir.is_dir(): - - member_dict[subdir.name] = ["Maa", subdir] - - return member_dict - def task_accomplish(self, name: str, log: dict): """保存保存任务结果""" @@ -279,12 +259,13 @@ class _TaskManager(QObject): Config.running_list.remove(name) if "调度队列" in name and "人工排查" not in mode: - with (Config.app_path / f"config/QueueConfig/{name}.json").open( - "r", encoding="utf-8" - ) as f: - info = json.load(f) - if info["QueueSet"]["AfterAccomplish"] != "None": + if ( + Config.queue_dict[name]["Config"].get( + Config.queue_dict[name]["Config"].queueSet_AfterAccomplish + ) + != "None" + ): from app.ui import ProgressRingMessageBox @@ -297,10 +278,14 @@ class _TaskManager(QObject): choice = ProgressRingMessageBox( self.main_window, - f"{mode_book[info['QueueSet']['AfterAccomplish']]}倒计时", + f"{mode_book[Config.queue_dict[name]["Config"].get(Config.queue_dict[name]["Config"].queueSet_AfterAccomplish)]}倒计时", ) if choice.exec(): - System.set_power(info["QueueSet"]["AfterAccomplish"]) + System.set_power( + Config.queue_dict[name]["Config"].get( + Config.queue_dict[name]["Config"].queueSet_AfterAccomplish + ) + ) def push_dialog(self, name: str, title: str, content: str): """推送对话框""" diff --git a/app/core/timer.py b/app/core/timer.py index 99de8f9..721477c 100644 --- a/app/core/timer.py +++ b/app/core/timer.py @@ -28,7 +28,6 @@ v4.2 from loguru import logger from PySide6.QtWidgets import QWidget from PySide6.QtCore import QTimer -import json from datetime import datetime import pyautogui @@ -48,26 +47,31 @@ class _MainTimer(QWidget): self.Timer.timeout.connect(self.timed_start) self.Timer.timeout.connect(self.set_silence) self.Timer.start(1000) + self.LongTimer = QTimer() + self.LongTimer.timeout.connect(self.long_timed_task) + self.LongTimer.start(3600000) + + def long_timed_task(self): + """长时间定期检定任务""" + + Config.get_gameid("ALL") def timed_start(self): """定时启动代理任务""" - # 获取定时列表 - queue_list = self.search_queue() + for name, info in Config.queue_dict.items(): - for i in queue_list: - - name, info = i - - if not info["QueueSet"]["Enabled"]: + if not info["Config"].get(info["Config"].queueSet_Enabled): continue history = Config.get_history(name) + data = info["Config"].toDict() + time_set = [ - info["Time"][f"TimeSet_{_}"] + data["Time"][f"TimeSet_{_}"] for _ in range(10) - if info["Time"][f"TimeEnabled_{_}"] + if data["Time"][f"TimeEnabled_{_}"] ] # 按时间调起代理任务 curtime = datetime.now().strftime("%Y-%m-%d %H:%M") @@ -78,15 +82,15 @@ class _MainTimer(QWidget): ): logger.info(f"定时任务:{name}") - TaskManager.add_task("自动代理_新调度台", name, info) + TaskManager.add_task("自动代理_新调度台", name, data) def set_silence(self): """设置静默模式""" if ( not Config.if_ignore_silence - and Config.global_config.get(Config.global_config.function_IfSilence) - and Config.global_config.get(Config.global_config.function_BossKey) != "" + and Config.get(Config.function_IfSilence) + and Config.get(Config.function_BossKey) != "" ): windows = System.get_window_info() @@ -99,9 +103,7 @@ class _MainTimer(QWidget): pyautogui.hotkey( *[ _.strip().lower() - for _ in Config.global_config.get( - Config.global_config.function_BossKey - ).split("+") + for _ in Config.get(Config.function_BossKey).split("+") ] ) except pyautogui.FailSafeException as e: @@ -109,18 +111,5 @@ class _MainTimer(QWidget): logger.warning(f"FailSafeException: {e}") self.if_FailSafeException = True - def search_queue(self) -> list: - """搜索所有调度队列实例""" - - queue_list = [] - - if (Config.app_path / "config/QueueConfig").exists(): - for json_file in (Config.app_path / "config/QueueConfig").glob("*.json"): - with json_file.open("r", encoding="utf-8") as f: - info = json.load(f) - queue_list.append([json_file.stem, info]) - - return queue_list - MainTimer = _MainTimer() diff --git a/app/models/MAA.py b/app/models/MAA.py index 2d1fc31..c5a01e9 100644 --- a/app/models/MAA.py +++ b/app/models/MAA.py @@ -28,16 +28,15 @@ v4.2 from loguru import logger from PySide6.QtCore import QObject, Signal, QEventLoop, QFileSystemWatcher, QTimer import json -import sqlite3 from datetime import datetime, timedelta import subprocess import shutil import time from pathlib import Path from jinja2 import Environment, FileSystemLoader -from typing import Union, List +from typing import Union, List, Dict -from app.core import Config +from app.core import Config, MaaConfig, MaaUserConfig from app.services import Notify, System @@ -46,7 +45,7 @@ class MaaManager(QObject): question = Signal(str, str) question_response = Signal(bool) - update_user_info = Signal(list, list, list, list, list, list) + update_user_info = Signal(str, dict) push_info_bar = Signal(str, str, str, int) create_user_list = Signal(list) update_user_list = Signal(list) @@ -59,16 +58,22 @@ class MaaManager(QObject): def __init__( self, mode: str, - config_path: Path, + config: Dict[ + str, + Union[ + str, + Path, + MaaConfig, + Dict[str, Dict[str, Union[Path, MaaUserConfig]]], + ], + ], user_config_path: Path = None, ): super(MaaManager, self).__init__() - self.current_user = "" - self.weekly_annihilation_limit_reached = False self.user_list = "" self.mode = mode - self.config_path = config_path + self.config_path = config["Path"] self.user_config_path = user_config_path self.log_monitor = QFileSystemWatcher() @@ -82,21 +87,17 @@ class MaaManager(QObject): self.interrupt.connect(self.quit_monitor) - with (self.config_path / "config.json").open("r", encoding="utf-8") as f: - self.set = json.load(f) + self.set = config["Config"].toDict() + self.data = {} if "设置MAA" not in self.mode: + for name, info in config["UserData"].items(): + self.data[name] = { + "Path": info["Path"], + "Config": info["Config"].toDict(), + } - db = sqlite3.connect(self.config_path / "user_data.db") - cur = db.cursor() - cur.execute("SELECT * FROM adminx WHERE True") - self.data = cur.fetchall() - self.data = [list(row) for row in self.data] - cur.close() - db.close() - - else: - self.data = [] + self.data = dict(sorted(self.data.items(), key=lambda x: int(x[0][3:]))) def configure(self): """提取配置信息""" @@ -110,10 +111,8 @@ class MaaManager(QObject): def run(self): """主进程,运行MAA代理进程""" - # 初始化本周剿灭上限标志 - self.weekly_annihilation_limit_reached = False - curdate = self.server_date() + curdate = Config.server_date() begin_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") self.configure() @@ -135,11 +134,19 @@ class MaaManager(QObject): # 整理用户数据,筛选需代理的用户 if "设置MAA" not in self.mode: - self.data = sorted(self.data, key=lambda x: (-len(x[15]), x[16])) - self.user_list: List[List[str, str, int]] = [ - [_[0], "等待", index] - for index, _ in enumerate(self.data) - if (_[3] != 0 and _[4] == "y") + self.data = dict( + sorted( + self.data.items(), + key=lambda x: (x[1]["Config"]["Info"]["Mode"], int(x[0][3:])), + ) + ) + self.user_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.user_list) @@ -150,26 +157,25 @@ class MaaManager(QObject): self.if_open_emulator = True # 执行情况预处理 for _ in self.user_list: - if self.data[_[2]][5] != curdate: - self.data[_[2]][5] = curdate - self.data[_[2]][14] = 0 - _[0] += f" - 第{self.data[_[2]][14] + 1}次代理" + 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 user in self.user_list: - self.current_user = user[0].split(" - ")[0] - if self.load_weekly_annihilation_status(self.current_user): - self.weekly_annihilation_limit_reached = True - logger.info(f"用户 {self.current_user} 本周已达上限") - else: - self.weekly_annihilation_limit_reached = False + + user_data = self.data[user[2]]["Config"] if self.isInterruptionRequested: break if ( self.set["RunSet"]["ProxyTimesLimit"] == 0 - or self.data[user[2]][14] < self.set["RunSet"]["ProxyTimesLimit"] + or user_data["Data"]["ProxyTimes"] + < self.set["RunSet"]["ProxyTimesLimit"] ): user[1] = "运行" self.update_user_list.emit(self.user_list) @@ -181,17 +187,20 @@ class MaaManager(QObject): logger.info(f"{self.name} | 开始代理用户: {user[0]}") # 初始化代理情况记录和模式替换记录 - run_book = [False for _ in range(2)] - mode_book = ["自动代理_剿灭", "自动代理_日常"] + run_book = {"Annihilation": False, "Routine": False} + mode_book = { + "Annihilation": "自动代理_剿灭", + "Routine": "自动代理_日常", + } # 简洁模式用户默认开启日常选项 - if self.data[user[2]][15] == "simple": - self.data[user[2]][9] = "y" - elif self.data[user[2]][15] == "beta": - check_book = [ - [True, "annihilation", "剿灭"], - [True, "routine", "日常"], - ] + if user_data["Info"]["Mode"] == "简洁": + user_data["Info"]["Routine"] = True + elif user_data["Info"]["Mode"] == "详细": + check_book = { + "Annihilation": True, + "Routine": True, + } user_logs_list = [] user_start_time = datetime.now() @@ -207,62 +216,76 @@ class MaaManager(QObject): ) # 剿灭-日常模式循环 - for j in range(2): + for mode in ["Annihilation", "Routine"]: if self.isInterruptionRequested: break - # j == 0 剿灭模式;满足条件跳过剿灭 + # 剿灭模式;满足条件跳过剿灭 if ( - j == 0 - and self.set["RunSet"].get( - "RunSet_AnnihilationWeeklyLimit", True - ) - and self.weekly_annihilation_limit_reached + mode == "Annihilation" + and self.set["RunSet"]["AnnihilationWeeklyLimit"] + and datetime.strptime( + user_data["Data"]["LastAnnihilationDate"], "%Y-%m-%d" + ).isocalendar()[:2] + == datetime.strptime(curdate, "%Y-%m-%d").isocalendar()[:2] ): logger.info( - f"{self.name} | 用户: {self.current_user} - 本周剿灭模式已达上限,跳过执行剿灭任务" + f"{self.name} | 用户: {user_data["Info"]["Name"]} - 本周剿灭模式已达上限,跳过执行剿灭任务" ) - run_book[j] = True + run_book[mode] = True continue + else: + self.weekly_annihilation_limit_reached = False - if self.data[user[2]][10 - j] == "n": - run_book[j] = True + if not user_data["Info"][mode]: + run_book[mode] = True continue - if run_book[j]: + if run_book[mode]: continue logger.info( - f"{self.name} | 用户: {user[0]} - 模式: {mode_book[j]}" + f"{self.name} | 用户: {user[0]} - 模式: {mode_book[mode]}" ) - if self.data[user[2]][15] == "beta": + if user_data["Info"]["Mode"] == "详细": self.if_open_emulator = True if ( - check_book[j][0] + check_book[mode] and not ( - self.config_path - / f"beta/{self.data[user[2]][16]}/{check_book[j][1]}/gui.json" + self.data[user[2]]["Path"] / f"{mode}/gui.json" ).exists() ): logger.error( - f"{self.name} | 用户: {user[0]} - 未找到{check_book[j][2]}配置文件" + f"{self.name} | 用户: {user[0]} - 未找到{mode_book[mode][5:7]}配置文件" ) self.push_info_bar.emit( "error", "启动MAA代理进程失败", - f"未找到{user[0]}的{check_book[j][2]}配置文件!", + f"未找到{user[0]}的{mode_book[mode][5:7]}配置文件!", -1, ) - check_book[j][0] = False + check_book[mode] = False continue - elif not check_book[j][0]: + elif not check_book[mode]: continue + # 更新当前模式到界面 + self.update_user_list.emit( + [ + ( + [f"{_[0]} - {mode_book[mode][5:7]}", _[1], _[2]] + if _[2] == user[2] + else _ + ) + for _ in self.user_list + ] + ) + # 配置MAA - set = self.set_maa(mode_book[j], user[2]) + set = self.set_maa(mode_book[mode], user[2]) # 记录当前时间 start_time = datetime.now() @@ -291,13 +314,13 @@ class MaaManager(QObject): creationflags=subprocess.CREATE_NO_WINDOW, ) # 监测MAA运行状态 - self.start_monitor(start_time, mode_book[j]) + self.start_monitor(start_time, mode_book[mode]) if self.maa_result == "Success!": logger.info( f"{self.name} | 用户: {user[0]} - MAA进程完成代理任务" ) - run_book[j] = True + run_book[mode] = True self.update_log_text.emit( "检测到MAA进程完成代理任务\n正在等待相关程序结束\n请等待10s" ) @@ -322,8 +345,8 @@ class MaaManager(QObject): # 推送异常通知 Notify.push_plyer( "用户自动代理出现异常!", - f"用户 {user[0].replace("_", " 今天的")}的{mode_book[j][5:7]}部分出现一次异常", - f"{user[0].replace("_", " ")}的{mode_book[j][5:7]}出现异常", + f"用户 {user[0].replace("_", " 今天的")}的{mode_book[mode][5:7]}部分出现一次异常", + f"{user[0].replace("_", " ")}的{mode_book[mode][5:7]}出现异常", 1, ) for _ in range(10): @@ -343,36 +366,40 @@ class MaaManager(QObject): ): System.kill_process(self.emulator_path) + # 记录剿灭情况 + if ( + mode == "Annihilation" + and self.weekly_annihilation_limit_reached + ): + user_data["Data"]["LastAnnihilationDate"] = curdate # 保存运行日志以及统计信息 if_six_star = Config.save_maa_log( Config.app_path - / f"history/{curdate}/{self.data[user[2]][0]}/{start_time.strftime("%H-%M-%S")}.log", - self.check_maa_log(start_time, mode_book[j]), + / f"history/{curdate}/{user_data["Info"]["Name"]}/{start_time.strftime("%H-%M-%S")}.log", + self.check_maa_log(start_time, mode_book[mode]), self.maa_result, ) user_logs_list.append( Config.app_path - / f"history/{curdate}/{self.data[user[2]][0]}/{start_time.strftime("%H-%M-%S")}.json", + / f"history/{curdate}/{user_data["Info"]["Name"]}/{start_time.strftime("%H-%M-%S")}.json", ) - if ( - Config.global_config.get( - Config.global_config.notify_IfSendSixStar - ) - and if_six_star - ): + if Config.get(Config.notify_IfSendSixStar) and if_six_star: self.push_notification( "公招六星", f"喜报:用户 {user[0]} 公招出六星啦!", - {"user_name": self.data[user[2]][0]}, + {"user_name": user_data["Info"]["Name"]}, ) # 成功完成代理的用户修改相关参数 - if run_book[0] and run_book[1]: - if self.data[user[2]][14] == 0 and self.data[user[2]][3] != -1: - self.data[user[2]][3] -= 1 - self.data[user[2]][14] += 1 + if run_book["Annihilation"] and run_book["Routine"]: + if ( + user_data["Data"]["ProxyTimes"] == 0 + and user_data["Info"]["RemainedDay"] != -1 + ): + user_data["Info"]["RemainedDay"] -= 1 + user_data["Data"]["ProxyTimes"] += 1 user[1] = "完成" Notify.push_plyer( "成功完成一个自动代理任务!", @@ -382,9 +409,7 @@ class MaaManager(QObject): ) break - if Config.global_config.get( - Config.global_config.notify_IfSendStatistic - ): + if Config.get(Config.notify_IfSendStatistic): statistics = Config.merge_maa_logs("指定项", user_logs_list) statistics["user_info"] = user[0] @@ -396,7 +421,7 @@ class MaaManager(QObject): ) statistics["maa_result"] = ( "代理任务全部完成" - if (run_book[0] and run_book[1]) + if (run_book["Annihilation"] and run_book["Routine"]) else "代理任务未全部完成" ) self.push_notification( @@ -404,7 +429,7 @@ class MaaManager(QObject): ) # 录入代理失败的用户 - if not (run_book[0] and run_book[1]): + if not (run_book["Annihilation"] and run_book["Routine"]): user[1] = "异常" self.update_user_list.emit(self.user_list) @@ -424,6 +449,8 @@ class MaaManager(QObject): # 开始排查 for user in self.user_list: + user_data = self.data[user[2]]["Config"] + if self.isInterruptionRequested: break @@ -432,7 +459,7 @@ class MaaManager(QObject): user[1] = "运行" self.update_user_list.emit(self.user_list) - if self.data[user[2]][15] == "beta": + if user_data["Info"]["Mode"] == "详细": self.if_open_emulator = True run_book = [False for _ in range(2)] @@ -495,20 +522,14 @@ class MaaManager(QObject): ): run_book[1] = True - # 结果录入用户备注栏 + # 结果录入 if run_book[0] and run_book[1]: logger.info(f"{self.name} | 用户 {user[0]} 通过人工排查") - if "未通过人工排查" in self.data[user[2]][13]: - self.data[user[2]][13] = self.data[user[2]][13].replace( - "未通过人工排查|", "" - ) + user_data["Data"]["IfPassCheck"] = True user[1] = "完成" else: logger.info(f"{self.name} | 用户 {user[0]} 未通过人工排查") - if not "未通过人工排查" in self.data[user[2]][13]: - self.data[user[2]][ - 13 - ] = f"未通过人工排查|{self.data[user[2]][13]}" + user_data["Data"]["IfPassCheck"] = False user[1] = "异常" self.update_user_list.emit(self.user_list) @@ -551,13 +572,8 @@ class MaaManager(QObject): System.kill_process(self.maa_exe_path) # 更新用户数据 - modes = [self.data[_[2]][15] for _ in self.user_list] - uids = [self.data[_[2]][16] for _ in self.user_list] - days = [self.data[_[2]][3] for _ in self.user_list] - lasts = [self.data[_[2]][5] for _ in self.user_list] - notes = [self.data[_[2]][13] for _ in self.user_list] - numbs = [self.data[_[2]][14] for _ in self.user_list] - self.update_user_info.emit(modes, uids, days, lasts, notes, numbs) + updated_info = {_[2]: self.data[_[2]] for _ in self.user_list} + self.update_user_info.emit(self.config_path.name, updated_info) error_index = [_[2] for _ in self.user_list if _[1] == "异常"] over_index = [_[2] for _ in self.user_list if _[1] == "完成"] @@ -580,8 +596,12 @@ class MaaManager(QObject): "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_user": [self.data[_][0] for _ in error_index], - "waiting_user": [self.data[_][0] for _ in wait_index], + "failed_user": [ + self.data[_]["Config"]["Info"]["Name"] for _ in error_index + ], + "waiting_user": [ + self.data[_]["Config"]["Info"]["Name"] for _ in wait_index + ], } # 推送代理结果通知 Notify.push_plyer( @@ -590,11 +610,8 @@ class MaaManager(QObject): f"已完成用户数:{len(over_index)},未完成用户数:{len(error_index) + len(wait_index)}", 10, ) - if Config.global_config.get( - Config.global_config.notify_SendTaskResultTime - ) == "任何时刻" or ( - Config.global_config.get(Config.global_config.notify_SendTaskResultTime) - == "仅失败时" + if Config.get(Config.notify_SendTaskResultTime) == "任何时刻" or ( + Config.get(Config.notify_SendTaskResultTime) == "仅失败时" and len(error_index) + len(wait_index) != 0 ): result_text = self.push_notification("代理结果", title, result) @@ -658,12 +675,6 @@ class MaaManager(QObject): logs.append(entry) log = "".join(logs) - # 如果日志中包含提示信息,则设置上限标志,并记录持久化信息 - if "剿灭任务失败" in log: - logger.info(f"用户:{self.current_user} | 剿灭任务失败,设置上限标志为True") - self.weekly_annihilation_limit_reached = True - self.record_weekly_annihilation_limit(self.current_user) - # 更新MAA日志 if len(logs) > 100: self.update_log_text.emit("".join(logs[-100:])) @@ -686,6 +697,11 @@ class MaaManager(QObject): "自动代理_日常": "RoutineTimeLimit", } + if mode == "自动代理_剿灭" and "剿灭任务失败" in log: + self.weekly_annihilation_limit_reached = True + else: + self.weekly_annihilation_limit_reached = False + if mode == "自动代理_日常" and "任务出错: Fight" in log: self.maa_result = "检测到MAA未能实际执行任务" elif "任务出错: StartUp" in log: @@ -763,6 +779,9 @@ class MaaManager(QObject): """配置MAA运行参数""" logger.info(f"{self.name} | 配置MAA运行参数: {mode}/{index}") + if "设置MAA" not in self.mode: + user_data = self.data[index]["Config"] + # 配置MAA前关闭可能未正常退出的MAA进程 System.kill_process(self.maa_exe_path) @@ -777,36 +796,38 @@ class MaaManager(QObject): ) elif (mode == "设置MAA_全局") or ( ("自动代理" in mode or "人工排查" in mode) - and self.data[index][15] == "simple" + and user_data["Info"]["Mode"] == "简洁" ): shutil.copy( self.config_path / "Default/gui.json", self.maa_set_path, ) - elif "自动代理" in mode and self.data[index][15] == "beta": + elif "自动代理" in mode and user_data["Info"]["Mode"] == "详细": if mode == "自动代理_剿灭": shutil.copy( - self.config_path - / f"beta/{self.data[index][16]}/annihilation/gui.json", + self.data[index]["Path"] / "Annihilation/gui.json", self.maa_set_path, ) elif mode == "自动代理_日常": shutil.copy( - self.config_path / f"beta/{self.data[index][16]}/routine/gui.json", + self.data[index]["Path"] / "Routine/gui.json", self.maa_set_path, ) - elif "人工排查" in mode and self.data[index][15] == "beta": + elif "人工排查" in mode and user_data["Info"]["Mode"] == "详细": shutil.copy( - self.config_path / f"beta/{self.data[index][16]}/routine/gui.json", + self.data[index]["Path"] / "Routine/gui.json", self.maa_set_path, ) with self.maa_set_path.open(mode="r", encoding="utf-8") as f: data = json.load(f) if "设置MAA" not in mode and ( - (self.data[index][15] == "simple" and self.data[index][2] == "Bilibili") + ( + user_data["Info"]["Mode"] == "简洁" + and user_data["Info"]["Server"] == "Bilibili" + ) or ( - self.data[index][15] == "beta" + user_data["Info"]["Mode"] == "详细" and data["Configurations"]["Default"]["Start.ClientType"] == "Bilibili" ) ): @@ -822,16 +843,19 @@ class MaaManager(QObject): data["Global"][f"Timer.Timer{i}"] = "False" # 时间设置 if ( - [i for i, _ in enumerate(self.user_list) if _[2] == index][0] + next((i for i, _ in enumerate(self.user_list) if _[2] == index), None) == len(self.user_list) - 1 ) or ( self.data[ self.user_list[ - [i for i, _ in enumerate(self.user_list) if _[2] == index][0] + next( + (i for i, _ in enumerate(self.user_list) if _[2] == index), + None, + ) + 1 ][2] - ][15] - == "beta" + ]["Config"]["Info"]["Mode"] + == "详细" ): data["Configurations"]["Default"][ "MainFunction.PostActions" @@ -849,12 +873,24 @@ class MaaManager(QObject): "True" if self.if_open_emulator else "False" ) # 启动MAA后自动开启模拟器 - if Config.global_config.get(Config.global_config.function_IfSilence): + if Config.get(Config.function_IfSilence): data["Global"]["Start.MinimizeDirectly"] = "True" # 启动MAA后直接最小化 data["Global"]["GUI.UseTray"] = "True" # 显示托盘图标 data["Global"]["GUI.MinimizeToTray"] = "True" # 最小化时隐藏至托盘 - if self.data[index][15] == "simple": + # 账号切换 + if user_data["Info"]["Server"] == "Official": + data["Configurations"]["Default"]["Start.AccountName"] = ( + f"{user_data["Info"]["Id"][:3]}****{user_data["Info"]["Id"][7:]}" + if len(user_data["Info"]["Id"]) == 11 + else user_data["Info"]["Id"] + ) + elif user_data["Info"]["Server"] == "Bilibili": + data["Configurations"]["Default"]["Start.AccountName"] = user_data[ + "Info" + ]["Id"] + + if user_data["Info"]["Mode"] == "简洁": data["Global"][ "VersionUpdate.ScheduledUpdateCheck" @@ -865,22 +901,11 @@ class MaaManager(QObject): data["Global"][ "VersionUpdate.AutoInstallUpdatePackage" ] = "False" # 自动安装更新包 - data["Configurations"]["Default"]["Start.ClientType"] = self.data[ - index + data["Configurations"]["Default"]["Start.ClientType"] = user_data[ + "Info" ][ - 2 + "Server" ] # 客户端类型 - # 账号切换 - if self.data[index][2] == "Official": - data["Configurations"]["Default"]["Start.AccountName"] = ( - f"{self.data[index][1][:3]}****{self.data[index][1][7:]}" - if len(self.data[index][1]) == 11 - else self.data[index][1] - ) - elif self.data[index][2] == "Bilibili": - data["Configurations"]["Default"]["Start.AccountName"] = self.data[ - index - ][1] if "剿灭" in mode: @@ -968,32 +993,35 @@ class MaaManager(QObject): data["Configurations"]["Default"][ "TaskQueue.Reclamation.IsChecked" ] = "False" # 生息演算 - # 主关卡 - if self.data[index][6] == "-": - data["Configurations"]["Default"]["MainFunction.Stage1"] = "" - else: - data["Configurations"]["Default"]["MainFunction.Stage1"] = ( - self.data[index][6] - ) - # 备选关卡1 - if self.data[index][7] == "-": - data["Configurations"]["Default"]["MainFunction.Stage2"] = "" - else: - data["Configurations"]["Default"]["MainFunction.Stage2"] = ( - self.data[index][7] - ) - # 备选关卡2 - if self.data[index][8] == "-": - data["Configurations"]["Default"]["MainFunction.Stage3"] = "" - else: - data["Configurations"]["Default"]["MainFunction.Stage3"] = ( - self.data[index][8] - ) + + data["Configurations"]["Default"]["MainFunction.UseMedicine"] = ( + "False" if user_data["Info"]["MedicineNumb"] == 0 else "True" + ) # 吃理智药 + data["Configurations"]["Default"][ + "MainFunction.UseMedicine.Quantity" + ] = str( + user_data["Info"]["MedicineNumb"] + ) # 吃理智药数量 + data["Configurations"]["Default"]["MainFunction.Stage1"] = ( + user_data["Info"]["GameId"] + if user_data["Info"]["GameId"] != "-" + else "" + ) # 主关卡 + data["Configurations"]["Default"]["MainFunction.Stage2"] = ( + user_data["Info"]["GameId_1"] + if user_data["Info"]["GameId_1"] != "-" + else "" + ) # 备选关卡1 + data["Configurations"]["Default"]["MainFunction.Stage3"] = ( + user_data["Info"]["GameId_2"] + if user_data["Info"]["GameId_2"] != "-" + else "" + ) # 备选关卡2 data["Configurations"]["Default"][ "Fight.RemainingSanityStage" ] = "" # 剩余理智关卡 # 连战次数 - if self.data[index][6] == "1-7": + if user_data["Info"]["GameId"] == "1-7": data["Configurations"]["Default"][ "MainFunction.Series.Quantity" ] = "6" @@ -1008,7 +1036,10 @@ class MaaManager(QObject): "GUI.CustomStageCode" ] = "True" # 手动输入关卡名 # 备选关卡 - if self.data[index][7] == "-" and self.data[index][8] == "-": + if ( + user_data["Info"]["GameId_1"] == "-" + and user_data["Info"]["GameId_2"] == "-" + ): data["Configurations"]["Default"][ "GUI.UseAlternateStage" ] = "False" @@ -1023,29 +1054,92 @@ class MaaManager(QObject): "Fight.UseExpiringMedicine" ] = "True" # 无限吃48小时内过期的理智药 # 自定义基建配置 - if self.data[index][11] == "n": - data["Configurations"]["Default"][ - "Infrast.CustomInfrastEnabled" - ] = "False" # 禁用自定义基建配置 + if user_data["Info"]["Infrastructure"]: + + if ( + self.data[index]["Path"] + / "Infrastructure/infrastructure.json" + ).exists(): + data["Configurations"]["Default"][ + "Infrast.CustomInfrastEnabled" + ] = "True" # 启用自定义基建配置 + data["Configurations"]["Default"][ + "Infrast.CustomInfrastPlanIndex" + ] = "1" # 自定义基建配置索引 + data["Configurations"]["Default"][ + "Infrast.DefaultInfrast" + ] = "user_defined" # 内置配置 + data["Configurations"]["Default"][ + "Infrast.IsCustomInfrastFileReadOnly" + ] = "False" # 自定义基建配置文件只读 + data["Configurations"]["Default"][ + "Infrast.CustomInfrastFile" + ] = str( + self.data[index]["Path"] + / "Infrastructure/infrastructure.json" + ) # 自定义基建配置文件地址 + else: + logger.warning( + f"未选择用户 {user_data["Info"]["Name"]} 的自定义基建配置文件" + ) + self.push_info_bar.emit( + "warning", + "启用自定义基建失败", + f"未选择用户 {user_data["Info"]["Name"]} 的自定义基建配置文件", + -1, + ) + data["Configurations"]["Default"][ + "Infrast.CustomInfrastEnabled" + ] = "False" # 禁用自定义基建配置 else: data["Configurations"]["Default"][ "Infrast.CustomInfrastEnabled" - ] = "True" # 启用自定义基建配置 + ] = "False" # 禁用自定义基建配置 + + elif user_data["Info"]["Mode"] == "详细": + + if "剿灭" in mode: + + pass + + elif "日常" in mode: + + data["Configurations"]["Default"]["MainFunction.UseMedicine"] = ( + "False" if user_data["Info"]["MedicineNumb"] == 0 else "True" + ) # 吃理智药 + data["Configurations"]["Default"][ + "MainFunction.UseMedicine.Quantity" + ] = str( + user_data["Info"]["MedicineNumb"] + ) # 吃理智药数量 + data["Configurations"]["Default"]["MainFunction.Stage1"] = ( + user_data["Info"]["GameId"] + if user_data["Info"]["GameId"] != "-" + else "" + ) # 主关卡 + data["Configurations"]["Default"]["MainFunction.Stage2"] = ( + user_data["Info"]["GameId_1"] + if user_data["Info"]["GameId_1"] != "-" + else "" + ) # 备选关卡1 + data["Configurations"]["Default"]["MainFunction.Stage3"] = ( + user_data["Info"]["GameId_2"] + if user_data["Info"]["GameId_2"] != "-" + else "" + ) # 备选关卡2 + + # 备选关卡 + if ( + user_data["Info"]["GameId_1"] == "-" + and user_data["Info"]["GameId_2"] == "-" + ): data["Configurations"]["Default"][ - "Infrast.CustomInfrastPlanIndex" - ] = "1" # 自定义基建配置索引 + "GUI.UseAlternateStage" + ] = "False" + else: data["Configurations"]["Default"][ - "Infrast.DefaultInfrast" - ] = "user_defined" # 内置配置 - data["Configurations"]["Default"][ - "Infrast.IsCustomInfrastFileReadOnly" - ] = "False" # 自定义基建配置文件只读 - data["Configurations"]["Default"][ - "Infrast.CustomInfrastFile" - ] = str( - self.config_path - / f"simple/{self.data[index][16]}/infrastructure/infrastructure.json" - ) # 自定义基建配置文件地址 + "GUI.UseAlternateStage" + ] = "True" # 人工排查配置 elif "人工排查" in mode: @@ -1060,10 +1154,6 @@ class MaaManager(QObject): "Start.RunDirectly" ] = "True" # 启动MAA后直接运行 data["Global"]["Start.MinimizeDirectly"] = "True" # 启动MAA后直接最小化 - # 启动MAA后直接运行 - data["Configurations"]["Default"]["Start.OpenEmulatorAfterLaunch"] = "True" - # 启动MAA后自动开启模拟器 - data["Configurations"]["Default"]["Start.RunDirectly"] = "True" data["Global"]["GUI.UseTray"] = "True" # 显示托盘图标 data["Global"]["GUI.MinimizeToTray"] = "True" # 最小化时隐藏至托盘 @@ -1071,7 +1161,19 @@ class MaaManager(QObject): "True" if self.if_open_emulator else "False" ) # 启动MAA后自动开启模拟器 - if self.data[index][15] == "simple": + # 账号切换 + if user_data["Info"]["Server"] == "Official": + data["Configurations"]["Default"]["Start.AccountName"] = ( + f"{user_data["Info"]["Id"][:3]}****{user_data["Info"]["Id"][7:]}" + if len(user_data["Info"]["Id"]) == 11 + else user_data["Info"]["Id"] + ) + elif user_data["Info"]["Server"] == "Bilibili": + data["Configurations"]["Default"]["Start.AccountName"] = user_data[ + "Info" + ]["Id"] + + if user_data["Info"]["Mode"] == "简洁": data["Global"][ "VersionUpdate.ScheduledUpdateCheck" @@ -1082,22 +1184,11 @@ class MaaManager(QObject): data["Global"][ "VersionUpdate.AutoInstallUpdatePackage" ] = "False" # 自动安装更新包 - data["Configurations"]["Default"]["Start.ClientType"] = self.data[ - index + data["Configurations"]["Default"]["Start.ClientType"] = user_data[ + "Info" ][ - 2 + "Server" ] # 客户端类型 - # 账号切换 - if self.data[index][2] == "Official": - data["Configurations"]["Default"]["Start.AccountName"] = ( - f"{self.data[index][1][:3]}****{self.data[index][1][7:]}" - if len(self.data[index][1]) == 11 - else self.data[index][1] - ) - elif self.data[index][2] == "Bilibili": - data["Configurations"]["Default"]["Start.AccountName"] = self.data[ - index - ][1] data["Configurations"]["Default"][ "TaskQueue.WakeUp.IsChecked" @@ -1140,7 +1231,7 @@ class MaaManager(QObject): "Start.OpenEmulatorAfterLaunch" ] = "False" # 启动MAA后自动开启模拟器 - if Config.global_config.get(Config.global_config.function_IfSilence): + if Config.get(Config.function_IfSilence): data["Global"][ "Start.MinimizeDirectly" ] = "False" # 启动MAA后直接最小化 @@ -1204,9 +1295,7 @@ class MaaManager(QObject): with self.maa_tasks_path.open(mode="r", encoding="utf-8") as f: data = json.load(f) - if if_agree and Config.global_config.get( - Config.global_config.function_IfAgreeBilibili - ): + if if_agree and Config.get(Config.function_IfAgreeBilibili): data["BilibiliAgreement_AUTO"] = { "algorithm": "OcrDetect", "action": "ClickSelf", @@ -1226,14 +1315,6 @@ class MaaManager(QObject): with self.maa_tasks_path.open(mode="w", encoding="utf-8") as f: json.dump(data, f, ensure_ascii=False, indent=4) - def server_date(self): - """获取当前的服务器日期""" - - dt = datetime.now() - if dt.time() < datetime.min.time().replace(hour=4): - dt = dt - timedelta(days=1) - return dt.strftime("%Y-%m-%d") - def push_notification( self, mode: str, @@ -1318,60 +1399,3 @@ class MaaManager(QObject): Notify.send_mail("网页", title, message_html) Notify.ServerChanPush(title, "好羡慕~\n\nAUTO_MAA 敬上") Notify.CompanyWebHookBotPush(title, "好羡慕~\n\nAUTO_MAA 敬上") - - def record_weekly_annihilation_limit(self, username: str) -> None: - """ - 持久化记录当前用户在本周剿灭模式达到上限的信息 - 计算方式:以周一凌晨4点为一周起点,故将当前时间减4小时再计算 ISO 周 - """ - self.weekly_annihilation_limit_reached = True - logger.info(f"已记录用户 {username} 达到本周剿灭模式达到上限") - now = datetime.now() - shifted_time = now - timedelta(hours=4) - year, week, _ = shifted_time.isocalendar() - - # 构造/history/annihilation.json路径 - record_path = Config.app_path / "history" / "annihilation.json" - record_path.parent.mkdir(parents=True, exist_ok=True) - - # 如果文件存在,则读取原有数据,否则初始化为空字典 - if record_path.exists(): - with record_path.open("r", encoding="utf-8") as f: - data = json.load(f) - else: - data = {} - - # 如果该用户本周已经记录则不更新,否则写入当前记录 - if username in data: - existing = data[username] - if existing.get("year") == year and existing.get("week") == week: - return - data[username] = {"year": year, "week": week} - - with record_path.open("w", encoding="utf-8") as f: - json.dump(data, f, ensure_ascii=False, indent=4) - - @staticmethod - def load_weekly_annihilation_status(username: str) -> bool: - """ - 读取 /history/annihilation.json 判断当前用户是否在本周已经达到剿灭上限 - 计算方式:当前时间减4小时再计算 ISO 周 - """ - record_path = Config.app_path / "history" / "annihilation.json" - if not record_path.exists(): - logger.info("未找到记录文件,将不进行剿灭上限判断") - return False - - with record_path.open("r", encoding="utf-8") as f: - data = json.load(f) - - now = datetime.now() - shifted_time = now - timedelta(hours=4) - year, week, _ = shifted_time.isocalendar() - - if username in data: - existing = data[username] - if existing.get("year") == year and existing.get("week") == week: - return True - - return False diff --git a/app/services/notification.py b/app/services/notification.py index 0b0a315..1458c39 100644 --- a/app/services/notification.py +++ b/app/services/notification.py @@ -53,7 +53,7 @@ class Notification(QWidget): def push_plyer(self, title, message, ticker, t): """推送系统通知""" - if Config.global_config.get(Config.global_config.notify_IfPushPlyer): + if Config.get(Config.notify_IfPushPlyer): notification.notify( title=title, @@ -70,27 +70,21 @@ class Notification(QWidget): def send_mail(self, mode, title, content) -> None: """推送邮件通知""" - if Config.global_config.get(Config.global_config.notify_IfSendMail): + if Config.get(Config.notify_IfSendMail): if ( - Config.global_config.get(Config.global_config.notify_SMTPServerAddress) - == "" - or Config.global_config.get( - Config.global_config.notify_AuthorizationCode - ) - == "" + Config.get(Config.notify_SMTPServerAddress) == "" + or Config.get(Config.notify_AuthorizationCode) == "" or not bool( re.match( r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$", - Config.global_config.get( - Config.global_config.notify_FromAddress - ), + Config.get(Config.notify_FromAddress), ) ) or not bool( re.match( r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$", - Config.global_config.get(Config.global_config.notify_ToAddress), + Config.get(Config.notify_ToAddress), ) ) ): @@ -114,15 +108,13 @@ class Notification(QWidget): message["From"] = formataddr( ( Header("AUTO_MAA通知服务", "utf-8").encode(), - Config.global_config.get( - Config.global_config.notify_FromAddress - ), + Config.get(Config.notify_FromAddress), ) ) # 发件人显示的名字 message["To"] = formataddr( ( Header("AUTO_MAA用户", "utf-8").encode(), - Config.global_config.get(Config.global_config.notify_ToAddress), + Config.get(Config.notify_ToAddress), ) ) # 收件人显示的名字 message["Subject"] = Header(title, "utf-8") @@ -131,22 +123,16 @@ class Notification(QWidget): message.attach(MIMEText(content, "html", "utf-8")) smtpObj = smtplib.SMTP_SSL( - Config.global_config.get( - Config.global_config.notify_SMTPServerAddress - ), + Config.get(Config.notify_SMTPServerAddress), 465, ) smtpObj.login( - Config.global_config.get(Config.global_config.notify_FromAddress), - Crypto.win_decryptor( - Config.global_config.get( - Config.global_config.notify_AuthorizationCode - ) - ), + Config.get(Config.notify_FromAddress), + Crypto.win_decryptor(Config.get(Config.notify_AuthorizationCode)), ) smtpObj.sendmail( - Config.global_config.get(Config.global_config.notify_FromAddress), - Config.global_config.get(Config.global_config.notify_ToAddress), + Config.get(Config.notify_FromAddress), + Config.get(Config.notify_ToAddress), message.as_string(), ) smtpObj.quit() @@ -158,12 +144,9 @@ class Notification(QWidget): def ServerChanPush(self, title, content): """使用Server酱推送通知""" - if Config.global_config.get(Config.global_config.notify_IfServerChan): + if Config.get(Config.notify_IfServerChan): - if ( - Config.global_config.get(Config.global_config.notify_ServerChanKey) - == "" - ): + if Config.get(Config.notify_ServerChanKey) == "": logger.error("请正确设置Server酱的SendKey") self.push_info_bar.emit( "error", @@ -173,9 +156,7 @@ class Notification(QWidget): ) return None else: - send_key = Config.global_config.get( - Config.global_config.notify_ServerChanKey - ) + send_key = Config.get(Config.notify_ServerChanKey) option = {} is_valid = lambda s: s == "" or ( @@ -186,16 +167,11 @@ class Notification(QWidget): 允许空的Tag和Channel即不启用,但不允许例如a||b,|a|b,a|b|,|||| """ send_tag = "|".join( - _.strip() - for _ in Config.global_config.get( - Config.global_config.notify_ServerChanTag - ).split("|") + _.strip() for _ in Config.get(Config.notify_ServerChanTag).split("|") ) send_channel = "|".join( _.strip() - for _ in Config.global_config.get( - Config.global_config.notify_ServerChanChannel - ).split("|") + for _ in Config.get(Config.notify_ServerChanChannel).split("|") ) if is_valid(send_tag): @@ -239,14 +215,9 @@ class Notification(QWidget): def CompanyWebHookBotPush(self, title, content): """使用企业微信群机器人推送通知""" - if Config.global_config.get(Config.global_config.notify_IfCompanyWebHookBot): + if Config.get(Config.notify_IfCompanyWebHookBot): - if ( - Config.global_config.get( - Config.global_config.notify_CompanyWebHookBotUrl - ) - == "" - ): + if Config.get(Config.notify_CompanyWebHookBotUrl) == "": logger.error("请正确设置企业微信群机器人的WebHook地址") self.push_info_bar.emit( "error", @@ -259,9 +230,7 @@ class Notification(QWidget): content = f"{title}\n{content}" data = {"msgtype": "text", "text": {"content": content}} response = requests.post( - url=Config.global_config.get( - Config.global_config.notify_CompanyWebHookBotUrl - ), + url=Config.get(Config.notify_CompanyWebHookBotUrl), json=data, ) if response.json()["errcode"] == 0: @@ -291,7 +260,7 @@ class Notification(QWidget): ) # 发送邮件通知 - if Config.global_config.get(Config.global_config.notify_IfSendMail): + if Config.get(Config.notify_IfSendMail): self.send_mail( "文本", "AUTO_MAA测试通知", @@ -299,14 +268,14 @@ class Notification(QWidget): ) # 发送Server酱通知 - if Config.global_config.get(Config.global_config.notify_IfServerChan): + if Config.get(Config.notify_IfServerChan): self.ServerChanPush( "AUTO_MAA测试通知", "这是 AUTO_MAA 外部通知测试信息。如果你看到了这段内容,说明 AUTO_MAA 的通知功能已经正确配置且可以正常工作!", ) # 发送企业微信机器人通知 - if Config.global_config.get(Config.global_config.notify_IfCompanyWebHookBot): + if Config.get(Config.notify_IfCompanyWebHookBot): self.CompanyWebHookBotPush( "AUTO_MAA测试通知", "这是 AUTO_MAA 外部通知测试信息。如果你看到了这段内容,说明 AUTO_MAA 的通知功能已经正确配置且可以正常工作!", diff --git a/app/services/security.py b/app/services/security.py index ed7c50e..162d58c 100644 --- a/app/services/security.py +++ b/app/services/security.py @@ -26,7 +26,6 @@ v4.2 """ from loguru import logger -import sqlite3 import hashlib import random import secrets @@ -85,9 +84,12 @@ class CryptoHandler: private_key_local = AES_key.encrypt(pad(private_key.exportKey(), 32)) (Config.app_path / "data/key/private_key.bin").write_bytes(private_key_local) - def AUTO_encryptor(self, note: str) -> bytes: + def AUTO_encryptor(self, note: str) -> str: """使用AUTO_MAA的算法加密数据""" + if note == "": + return "" + # 读取RSA公钥 public_key_local = RSA.import_key( (Config.app_path / "data/key/public_key.pem").read_bytes() @@ -95,11 +97,14 @@ class CryptoHandler: # 使用RSA公钥对数据进行加密 cipher = PKCS1_OAEP.new(public_key_local) encrypted = cipher.encrypt(note.encode("utf-8")) - return encrypted + return base64.b64encode(encrypted).decode("utf-8") - def AUTO_decryptor(self, note: bytes, PASSWORD: str) -> str: + def AUTO_decryptor(self, note: str, PASSWORD: str) -> str: """使用AUTO_MAA的算法解密数据""" + if note == "": + return "" + # 读入RSA私钥密文、盐与校验哈希值 private_key_local = ( (Config.app_path / "data/key/private_key.bin").read_bytes().strip() @@ -133,63 +138,40 @@ class CryptoHandler: private_key = RSA.import_key(private_key_pem) # 使用RSA私钥解密数据 decrypter = PKCS1_OAEP.new(private_key) - note = decrypter.decrypt(note) - return note.decode("utf-8") + note = decrypter.decrypt(base64.b64decode(note)).decode("utf-8") + return note def change_PASSWORD(self, PASSWORD_old: str, PASSWORD_new: str) -> None: """修改管理密钥""" - member_list = self.search_member() - - for user_data in member_list: - - # 读取用户数据 - db = sqlite3.connect(user_data["Path"]) - cur = db.cursor() - cur.execute("SELECT * FROM adminx WHERE True") - data = cur.fetchall() + for member in Config.member_dict.values(): # 使用旧管理密钥解密 - user_data["Password"] = [] - for i in range(len(data)): - user_data["Password"].append( - self.AUTO_decryptor(data[i][12], PASSWORD_old) + for user in member["UserData"].values(): + user["Password"] = self.AUTO_decryptor( + user["Config"].get(user["Config"].Info_Password), PASSWORD_old ) - cur.close() - db.close() self.get_PASSWORD(PASSWORD_new) - for user_data in member_list: - - # 读取用户数据 - db = sqlite3.connect(user_data["Path"]) - cur = db.cursor() - cur.execute("SELECT * FROM adminx WHERE True") - data = cur.fetchall() + for member in Config.member_dict.values(): # 使用新管理密钥重新加密 - for i in range(len(data)): - cur.execute( - "UPDATE adminx SET password = ? WHERE mode = ? AND uid = ?", - ( - self.AUTO_encryptor(user_data["Password"][i]), - data[i][15], - data[i][16], - ), + for user in member["UserData"].values(): + user["Config"].set( + user["Config"].Info_Password, self.AUTO_encryptor(user["Password"]) ) - db.commit() - user_data["Password"][i] = None - del user_data["Password"] - - cur.close() - db.close() + user["Password"] = None + del user["Password"] def win_encryptor( self, note: str, description: str = None, entropy: bytes = None ) -> str: """使用Windows DPAPI加密数据""" + if note == "": + return "" + encrypted = win32crypt.CryptProtectData( note.encode("utf-8"), description, entropy, None, None, 0 ) @@ -223,7 +205,7 @@ class CryptoHandler: """验证管理密钥""" return bool( - self.AUTO_decryptor(self.AUTO_encryptor(""), PASSWORD) != "管理密钥错误" + self.AUTO_decryptor(self.AUTO_encryptor("-"), PASSWORD) != "管理密钥错误" ) diff --git a/app/services/system.py b/app/services/system.py index 88cfecf..1e2f793 100644 --- a/app/services/system.py +++ b/app/services/system.py @@ -54,7 +54,7 @@ class _SystemHandler: def set_Sleep(self) -> None: """同步系统休眠状态""" - if Config.global_config.get(Config.global_config.function_IfAllowSleep): + if Config.get(Config.function_IfAllowSleep): # 设置系统电源状态 ctypes.windll.kernel32.SetThreadExecutionState( self.ES_CONTINUOUS | self.ES_SYSTEM_REQUIRED @@ -66,10 +66,7 @@ class _SystemHandler: def set_SelfStart(self) -> None: """同步开机自启""" - if ( - Config.global_config.get(Config.global_config.start_IfSelfStart) - and not self.is_startup() - ): + if Config.get(Config.start_IfSelfStart) and not self.is_startup(): key = winreg.OpenKey( winreg.HKEY_CURRENT_USER, r"Software\Microsoft\Windows\CurrentVersion\Run", @@ -78,10 +75,7 @@ class _SystemHandler: ) winreg.SetValueEx(key, "AUTO_MAA", 0, winreg.REG_SZ, Config.app_path_sys) winreg.CloseKey(key) - elif ( - not Config.global_config.get(Config.global_config.start_IfSelfStart) - and self.is_startup() - ): + elif not Config.get(Config.start_IfSelfStart) and self.is_startup(): key = winreg.OpenKey( winreg.HKEY_CURRENT_USER, r"Software\Microsoft\Windows\CurrentVersion\Run", diff --git a/app/ui/Widget.py b/app/ui/Widget.py index 4d6cb2e..0eacbc6 100644 --- a/app/ui/Widget.py +++ b/app/ui/Widget.py @@ -46,12 +46,13 @@ from qfluentwidgets import ( FluentIconBase, Signal, ComboBox, + EditableComboBox, CheckBox, IconWidget, FluentIcon, CardWidget, BodyLabel, - qconfig, + QConfig, ConfigItem, TimeEdit, OptionsConfigItem, @@ -65,22 +66,27 @@ from qfluentwidgets import ( ProgressRing, TextBrowser, HeaderCardWidget, + SwitchButton, + IndicatorPosition, + Slider, ) from qfluentwidgets.common.overload import singledispatchmethod import os import re import markdown +from datetime import datetime from urllib.parse import urlparse from functools import partial from typing import Optional, Union, List, Dict +from app.core import Config from app.services import Crypto class LineEditMessageBox(MessageBoxBase): """输入对话框""" - def __init__(self, parent, title: str, content: str, mode: str): + def __init__(self, parent, title: str, content: Union[str, None], mode: str): super().__init__(parent) self.title = SubtitleLabel(title) @@ -90,6 +96,7 @@ class LineEditMessageBox(MessageBoxBase): elif mode == "密码": self.input = PasswordLineEdit() + self.input.returnPressed.connect(self.yesButton.click) self.input.setPlaceholderText(content) # 将组件添加到布局中 @@ -248,6 +255,143 @@ class NoticeMessageBox(MessageBoxBase): self.Layout.addStretch(1) +class SwitchSettingCard(SettingCard): + """Setting card with switch button""" + + checkedChanged = Signal(bool) + + def __init__( + self, + icon: Union[str, QIcon, FluentIconBase], + title: str, + content: Union[str, None], + qconfig: QConfig, + configItem: ConfigItem, + parent=None, + ): + super().__init__(icon, title, content, parent) + self.qconfig = qconfig + self.configItem = configItem + self.switchButton = SwitchButton(self.tr("Off"), self, IndicatorPosition.RIGHT) + + if configItem: + self.setValue(self.qconfig.get(configItem)) + configItem.valueChanged.connect(self.setValue) + + # add switch button to layout + self.hBoxLayout.addWidget(self.switchButton, 0, Qt.AlignRight) + self.hBoxLayout.addSpacing(16) + + self.switchButton.checkedChanged.connect(self.__onCheckedChanged) + + def __onCheckedChanged(self, isChecked: bool): + """switch button checked state changed slot""" + self.setValue(isChecked) + self.checkedChanged.emit(isChecked) + + def setValue(self, isChecked: bool): + if self.configItem: + self.qconfig.set(self.configItem, isChecked) + + self.switchButton.setChecked(isChecked) + self.switchButton.setText(self.tr("On") if isChecked else self.tr("Off")) + + def setChecked(self, isChecked: bool): + self.setValue(isChecked) + + def isChecked(self): + return self.switchButton.isChecked() + + +class RangeSettingCard(SettingCard): + """Setting card with a slider""" + + valueChanged = Signal(int) + + def __init__( + self, + icon: Union[str, QIcon, FluentIconBase], + title: str, + content: Union[str, None], + qconfig: QConfig, + configItem: ConfigItem, + parent=None, + ): + super().__init__(icon, title, content, parent) + self.qconfig = qconfig + self.configItem = configItem + self.slider = Slider(Qt.Horizontal, self) + self.valueLabel = QLabel(self) + self.slider.setMinimumWidth(268) + + self.slider.setSingleStep(1) + self.slider.setRange(*configItem.range) + self.slider.setValue(configItem.value) + self.valueLabel.setNum(configItem.value) + + self.hBoxLayout.addStretch(1) + self.hBoxLayout.addWidget(self.valueLabel, 0, Qt.AlignRight) + self.hBoxLayout.addSpacing(6) + self.hBoxLayout.addWidget(self.slider, 0, Qt.AlignRight) + self.hBoxLayout.addSpacing(16) + + self.valueLabel.setObjectName("valueLabel") + configItem.valueChanged.connect(self.setValue) + self.slider.valueChanged.connect(self.__onValueChanged) + + def __onValueChanged(self, value: int): + """slider value changed slot""" + self.setValue(value) + self.valueChanged.emit(value) + + def setValue(self, value): + self.qconfig.set(self.configItem, value) + self.valueLabel.setNum(value) + self.valueLabel.adjustSize() + self.slider.setValue(value) + + +class ComboBoxSettingCard(SettingCard): + """Setting card with a combo box""" + + def __init__( + self, + icon: Union[str, QIcon, FluentIconBase], + title: str, + content: Union[str, None], + texts: List[str], + qconfig: QConfig, + configItem: OptionsConfigItem, + parent=None, + ): + + super().__init__(icon, title, content, parent) + self.qconfig = qconfig + self.configItem = configItem + self.comboBox = ComboBox(self) + self.hBoxLayout.addWidget(self.comboBox, 0, Qt.AlignRight) + self.hBoxLayout.addSpacing(16) + + self.optionToText = {o: t for o, t in zip(configItem.options, texts)} + for text, option in zip(texts, configItem.options): + self.comboBox.addItem(text, userData=option) + + self.comboBox.setCurrentText(self.optionToText[self.qconfig.get(configItem)]) + self.comboBox.currentIndexChanged.connect(self._onCurrentIndexChanged) + configItem.valueChanged.connect(self.setValue) + + def _onCurrentIndexChanged(self, index: int): + + self.qconfig.set(self.configItem, self.comboBox.itemData(index)) + + def setValue(self, value): + if value not in self.optionToText: + return + + self.comboBox.setCurrentText(self.optionToText[value]) + self.qconfig.set(self.configItem, value) + + class LineEditSettingCard(SettingCard): """Setting card with LineEdit""" @@ -255,22 +399,24 @@ class LineEditSettingCard(SettingCard): def __init__( self, - text, icon: Union[str, QIcon, FluentIconBase], - title, - content=None, - configItem: ConfigItem = None, + title: str, + content: Union[str, None], + text: str, + qconfig: QConfig, + configItem: ConfigItem, parent=None, ): super().__init__(icon, title, content, parent) + self.qconfig = qconfig self.configItem = configItem self.LineEdit = LineEdit(self) self.LineEdit.setMinimumWidth(250) self.LineEdit.setPlaceholderText(text) if configItem: - self.setValue(qconfig.get(configItem)) + self.setValue(self.qconfig.get(configItem)) configItem.valueChanged.connect(self.setValue) self.hBoxLayout.addWidget(self.LineEdit, 0, Qt.AlignRight) @@ -279,39 +425,46 @@ class LineEditSettingCard(SettingCard): self.LineEdit.textChanged.connect(self.__textChanged) def __textChanged(self, content: str): - self.setValue(content) - self.textChanged.emit(content) + self.setValue(content.strip()) + self.textChanged.emit(content.strip()) def setValue(self, content: str): if self.configItem: - qconfig.set(self.configItem, content) + self.qconfig.set(self.configItem, content.strip()) - self.LineEdit.setText(content) + self.LineEdit.setText(content.strip()) class PasswordLineEditSettingCard(SettingCard): """Setting card with PasswordLineEdit""" - textChanged = Signal(str) + textChanged = Signal() def __init__( self, - text, icon: Union[str, QIcon, FluentIconBase], - title, - content=None, - configItem: ConfigItem = None, + title: str, + content: Union[str, None], + text: str, + algorithm: str, + qconfig: QConfig, + configItem: ConfigItem, parent=None, ): super().__init__(icon, title, content, parent) + self.algorithm = algorithm + self.qconfig = qconfig self.configItem = configItem self.LineEdit = PasswordLineEdit(self) - self.LineEdit.setMinimumWidth(250) + self.LineEdit.setMinimumWidth(200) self.LineEdit.setPlaceholderText(text) + if algorithm == "AUTO": + self.LineEdit.setViewPasswordButtonVisible(False) + self.if_setValue = False if configItem: - self.setValue(qconfig.get(configItem)) + self.setValue(self.qconfig.get(configItem)) configItem.valueChanged.connect(self.setValue) self.hBoxLayout.addWidget(self.LineEdit, 0, Qt.AlignRight) @@ -320,14 +473,141 @@ class PasswordLineEditSettingCard(SettingCard): self.LineEdit.textChanged.connect(self.__textChanged) def __textChanged(self, content: str): - self.setValue(Crypto.win_encryptor(content)) - self.textChanged.emit(content) + + if self.if_setValue: + return None + + if self.algorithm == "DPAPI": + self.setValue(Crypto.win_encryptor(content)) + elif self.algorithm == "AUTO": + self.setValue(Crypto.AUTO_encryptor(content)) + self.textChanged.emit() def setValue(self, content: str): - if self.configItem: - qconfig.set(self.configItem, content) - self.LineEdit.setText(Crypto.win_decryptor(content)) + self.if_setValue = True + + if self.configItem: + self.qconfig.set(self.configItem, content) + + if self.algorithm == "DPAPI": + self.LineEdit.setText(Crypto.win_decryptor(content)) + elif self.algorithm == "AUTO": + if Crypto.check_PASSWORD(Config.PASSWORD): + self.LineEdit.setText(Crypto.AUTO_decryptor(content, Config.PASSWORD)) + self.LineEdit.setPasswordVisible(True) + self.LineEdit.setReadOnly(False) + elif Config.PASSWORD: + self.LineEdit.setText("管理密钥错误") + self.LineEdit.setPasswordVisible(True) + self.LineEdit.setReadOnly(True) + else: + self.LineEdit.setText("************") + self.LineEdit.setPasswordVisible(False) + self.LineEdit.setReadOnly(True) + + self.if_setValue = False + + +class UserLableSettingCard(SettingCard): + """Setting card with User's Lable""" + + textChanged = Signal(str) + + 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): + if self.configItems: + + text_list = [] + if not self.qconfig.get(self.configItems["IfPassCheck"]): + text_list.append("未通过人工排查") + text_list.append( + f"今日已代理{self.qconfig.get(self.configItems["ProxyTimes"])}次" + if Config.server_date() + == self.qconfig.get(self.configItems["LastProxyDate"]) + else "今日未进行代理" + ) + text_list.append( + "本周剿灭已完成" + if datetime.strptime( + self.qconfig.get(self.configItems["LastAnnihilationDate"]), + "%Y-%m-%d", + ).isocalendar()[:2] + == datetime.strptime(Config.server_date(), "%Y-%m-%d").isocalendar()[:2] + else "本周剿灭未完成" + ) + + self.Lable.setText(" | ".join(text_list)) + + +class PushAndSwitchButtonSettingCard(SettingCard): + """Setting card with push & switch button""" + + checkedChanged = Signal(bool) + clicked = Signal() + + def __init__( + self, + icon: Union[str, QIcon, FluentIconBase], + title: str, + content: Union[str, None], + text: str, + qconfig: QConfig, + configItem: ConfigItem, + parent=None, + ): + super().__init__(icon, title, content, parent) + self.qconfig = qconfig + self.configItem = configItem + self.switchButton = SwitchButton("关", self, IndicatorPosition.RIGHT) + self.button = PushButton(text, self) + self.hBoxLayout.addWidget(self.button, 0, Qt.AlignRight) + self.hBoxLayout.addSpacing(16) + self.button.clicked.connect(self.clicked) + + if configItem: + self.setValue(self.qconfig.get(configItem)) + configItem.valueChanged.connect(self.setValue) + + # add switch button to layout + self.hBoxLayout.addWidget(self.switchButton, 0, Qt.AlignRight) + self.hBoxLayout.addSpacing(16) + + self.switchButton.checkedChanged.connect(self.__onCheckedChanged) + + def __onCheckedChanged(self, isChecked: bool): + """switch button checked state changed slot""" + self.setValue(isChecked) + self.checkedChanged.emit(isChecked) + + def setValue(self, isChecked: bool): + if self.configItem: + self.qconfig.set(self.configItem, isChecked) + + self.switchButton.setChecked(isChecked) + self.switchButton.setText("开" if isChecked else "关") class SpinBoxSettingCard(SettingCard): @@ -337,15 +617,17 @@ class SpinBoxSettingCard(SettingCard): def __init__( self, - range: tuple[int, int], icon: Union[str, QIcon, FluentIconBase], - title, - content=None, - configItem: ConfigItem = None, + title: str, + content: Union[str, None], + range: tuple[int, int], + qconfig: QConfig, + configItem: ConfigItem, parent=None, ): super().__init__(icon, title, content, parent) + self.qconfig = qconfig self.configItem = configItem self.SpinBox = SpinBox(self) self.SpinBox.setRange(range[0], range[1]) @@ -366,7 +648,7 @@ class SpinBoxSettingCard(SettingCard): def setValue(self, value: int): if self.configItem: - qconfig.set(self.configItem, value) + self.qconfig.set(self.configItem, value) self.SpinBox.setValue(value) @@ -375,16 +657,18 @@ class NoOptionComboBoxSettingCard(SettingCard): def __init__( self, - configItem: OptionsConfigItem, icon: Union[str, QIcon, FluentIconBase], - title, - content=None, - value=None, - texts=None, + title: str, + content: Union[str, None], + value: List[str], + texts: List[str], + qconfig: QConfig, + configItem: OptionsConfigItem, parent=None, ): super().__init__(icon, title, content, parent) + self.qconfig = qconfig self.configItem = configItem self.comboBox = ComboBox(self) self.comboBox.setMinimumWidth(250) @@ -395,20 +679,131 @@ class NoOptionComboBoxSettingCard(SettingCard): for text, option in zip(texts, value): self.comboBox.addItem(text, userData=option) - self.comboBox.setCurrentText(self.optionToText[qconfig.get(configItem)]) + self.comboBox.setCurrentText(self.optionToText[self.qconfig.get(configItem)]) self.comboBox.currentIndexChanged.connect(self._onCurrentIndexChanged) configItem.valueChanged.connect(self.setValue) def _onCurrentIndexChanged(self, index: int): - qconfig.set(self.configItem, self.comboBox.itemData(index)) + self.qconfig.set(self.configItem, self.comboBox.itemData(index)) def setValue(self, value): if value not in self.optionToText: return self.comboBox.setCurrentText(self.optionToText[value]) - qconfig.set(self.configItem, value) + self.qconfig.set(self.configItem, value) + + def reLoadOptions(self, value: List[str], texts: List[str]): + + self.comboBox.currentIndexChanged.disconnect() + self.comboBox.clear() + self.optionToText = {o: t for o, t in zip(value, texts)} + for text, option in zip(texts, value): + self.comboBox.addItem(text, userData=option) + self.comboBox.setCurrentText( + self.optionToText[self.qconfig.get(self.configItem)] + ) + self.comboBox.currentIndexChanged.connect(self._onCurrentIndexChanged) + + +class EditableComboBoxSettingCard(SettingCard): + """Setting card with EditableComboBox""" + + def __init__( + self, + icon: Union[str, QIcon, FluentIconBase], + title: str, + content: Union[str, None], + value: List[str], + texts: List[str], + qconfig: QConfig, + configItem: OptionsConfigItem, + parent=None, + ): + + super().__init__(icon, title, content, parent) + self.qconfig = qconfig + self.configItem = configItem + self.comboBox = self._EditableComboBox(self) + self.comboBox.setMinimumWidth(100) + self.hBoxLayout.addWidget(self.comboBox, 0, Qt.AlignRight) + self.hBoxLayout.addSpacing(16) + + self.optionToText = {o: t for o, t in zip(value, texts)} + for text, option in zip(texts, value): + self.comboBox.addItem(text, userData=option) + + if qconfig.get(configItem) not in self.optionToText: + self.optionToText[qconfig.get(configItem)] = qconfig.get(configItem) + self.comboBox.addItem( + qconfig.get(configItem), userData=qconfig.get(configItem) + ) + + self.comboBox.setCurrentText(self.optionToText[qconfig.get(configItem)]) + self.comboBox.currentIndexChanged.connect(self._onCurrentIndexChanged) + configItem.valueChanged.connect(self.setValue) + + def _onCurrentIndexChanged(self, index: int): + + self.qconfig.set( + self.configItem, + ( + self.comboBox.itemData(index) + if self.comboBox.itemData(index) + else self.comboBox.itemText(index) + ), + ) + + def setValue(self, value): + if value not in self.optionToText: + self.optionToText[value] = value + if self.comboBox.findText(value) == -1: + self.comboBox.addItem(value, userData=value) + else: + self.comboBox.setItemData(self.comboBox.findText(value), value) + + self.comboBox.setCurrentText(self.optionToText[value]) + self.qconfig.set(self.configItem, value) + + def reLoadOptions(self, value: List[str], texts: List[str]): + + self.comboBox.currentIndexChanged.disconnect() + self.comboBox.clear() + self.optionToText = {o: t for o, t in zip(value, texts)} + for text, option in zip(texts, value): + self.comboBox.addItem(text, userData=option) + if self.qconfig.get(self.configItem) not in self.optionToText: + self.optionToText[self.qconfig.get(self.configItem)] = self.qconfig.get( + self.configItem + ) + self.comboBox.addItem( + self.qconfig.get(self.configItem), + userData=self.qconfig.get(self.configItem), + ) + self.comboBox.setCurrentText( + self.optionToText[self.qconfig.get(self.configItem)] + ) + self.comboBox.currentIndexChanged.connect(self._onCurrentIndexChanged) + + class _EditableComboBox(EditableComboBox): + """EditableComboBox""" + + def __init__(self, parent=None): + super().__init__(parent) + + def _onReturnPressed(self): + if not self.text(): + return + + index = self.findText(self.text()) + if index >= 0 and index != self.currentIndex(): + self._currentIndex = index + self.currentIndexChanged.emit(index) + elif index == -1: + self.addItem(self.text()) + self.setCurrentIndex(self.count() - 1) + self.currentIndexChanged.emit(self.count() - 1) class TimeEditSettingCard(SettingCard): @@ -419,14 +814,16 @@ class TimeEditSettingCard(SettingCard): def __init__( self, icon: Union[str, QIcon, FluentIconBase], - title, - content=None, - configItem_bool: ConfigItem = None, - configItem_time: ConfigItem = None, + title: str, + content: Union[str, None], + qconfig: QConfig, + configItem_bool: ConfigItem, + configItem_time: ConfigItem, parent=None, ): super().__init__(icon, title, content, parent) + self.qconfig = qconfig self.configItem_bool = configItem_bool self.configItem_time = configItem_time self.CheckBox = CheckBox(self) @@ -464,13 +861,13 @@ class TimeEditSettingCard(SettingCard): def setValue_bool(self, value: bool): if self.configItem_bool: - qconfig.set(self.configItem_bool, value) + self.qconfig.set(self.configItem_bool, value) self.CheckBox.setChecked(value) def setValue_time(self, value: str): if self.configItem_time: - qconfig.set(self.configItem_time, value) + self.qconfig.set(self.configItem_time, value) self.TimeEdit.setTime(QTime.fromString(value, "HH:mm")) @@ -510,31 +907,18 @@ class UrlListSettingCard(ExpandSettingCard): def __init__( self, icon: Union[str, QIcon, FluentIconBase], - configItem: ConfigItem, title: str, - content: str = None, + content: Union[str, None], + qconfig: QConfig, + configItem: ConfigItem, parent=None, ): - """ - Parameters - ---------- - configItem: RangeConfigItem - configuration item operated by the card - - title: str - the title of card - - content: str - the content of card - - parent: QWidget - parent widget - """ super().__init__(icon, title, content, parent) + self.qconfig = qconfig self.configItem = configItem self.addUrlButton = PushButton("添加代理网址", self) - self.urls: List[str] = qconfig.get(configItem).copy() + self.urls: List[str] = self.qconfig.get(configItem).copy() self.__initWidget() def __initWidget(self): @@ -567,7 +951,7 @@ class UrlListSettingCard(ExpandSettingCard): self.__addUrlItem(url) self.urls.append(url) - qconfig.set(self.configItem, self.urls) + self.qconfig.set(self.configItem, self.urls) self.urlChanged.emit(self.urls) def __addUrlItem(self, url: str): @@ -600,7 +984,7 @@ class UrlListSettingCard(ExpandSettingCard): self._adjustViewSize() self.urlChanged.emit(self.urls) - qconfig.set(self.configItem, self.urls) + self.qconfig.set(self.configItem, self.urls) def __validate(self, value): @@ -681,7 +1065,7 @@ class IconButton(TransparentToolButton): icon: Union[str, QIcon, FluentIconBase], isTooltip: bool, tip_title: str, - tip_content: str, + tip_content: Union[str, None], parent: QWidget = None, ): self.__init__(parent) diff --git a/app/ui/dispatch_center.py b/app/ui/dispatch_center.py index 8810568..a4928b6 100644 --- a/app/ui/dispatch_center.py +++ b/app/ui/dispatch_center.py @@ -47,7 +47,6 @@ from qfluentwidgets import ( from PySide6.QtCore import Qt from PySide6.QtGui import QTextCursor from typing import List, Dict -import json from app.core import Config, TaskManager, Task, MainInfoBar @@ -164,31 +163,39 @@ class DispatchCenter(QWidget): def update_top_bar(self): """更新顶栏""" - list = [] - queue_numb, member_numb = 0, 0 - - if (Config.app_path / "config/QueueConfig").exists(): - for json_file in (Config.app_path / "config/QueueConfig").glob("*.json"): - list.append(f"队列 - {json_file.stem}") - queue_numb += 1 - - if (Config.app_path / "config/MaaConfig").exists(): - for subdir in (Config.app_path / "config/MaaConfig").iterdir(): - if subdir.is_dir(): - list.append(f"实例 - Maa - {subdir.name}") - member_numb += 1 - self.script_list["主调度台"].top_bar.object.clear() - self.script_list["主调度台"].top_bar.object.addItems(list) - self.script_list["主调度台"].top_bar.mode.clear() - self.script_list["主调度台"].top_bar.mode.addItems(["自动代理", "人工排查"]) - if queue_numb == 1: + for name, info in Config.queue_dict.items(): + self.script_list["主调度台"].top_bar.object.addItem( + ( + "队列" + if info["Config"].get(info["Config"].queueSet_Name) == "" + else f"队列 - {info["Config"].get(info["Config"].queueSet_Name)}" + ), + userData=name, + ) + + for name, info in Config.member_dict.items(): + 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)}" + ), + userData=name, + ) + + if len(Config.queue_dict) == 1: self.script_list["主调度台"].top_bar.object.setCurrentIndex(0) - elif member_numb == 1: - self.script_list["主调度台"].top_bar.object.setCurrentIndex(queue_numb) + elif len(Config.member_dict) == 1: + self.script_list["主调度台"].top_bar.object.setCurrentIndex( + len(Config.queue_dict) + ) else: self.script_list["主调度台"].top_bar.object.setCurrentIndex(-1) + + self.script_list["主调度台"].top_bar.mode.clear() + self.script_list["主调度台"].top_bar.mode.addItems(["自动代理", "人工排查"]) self.script_list["主调度台"].top_bar.mode.setCurrentIndex(0) @@ -270,32 +277,31 @@ class DispatchBox(QWidget): ) return None - name = self.object.currentText().split(" - ")[-1] - - if name in Config.running_list: - logger.warning(f"任务已存在:{name}") - MainInfoBar.push_info_bar("warning", "任务已存在", name, 5000) + if self.object.currentData() in Config.running_list: + logger.warning(f"任务已存在:{self.object.currentData()}") + MainInfoBar.push_info_bar( + "warning", "任务已存在", self.object.currentData(), 5000 + ) return None - if self.object.currentText().split(" - ")[0] == "队列": + if "调度队列" in self.object.currentData(): - with (Config.app_path / f"config/QueueConfig/{name}.json").open( - mode="r", encoding="utf-8" - ) as f: - info = json.load(f) + logger.info(f"用户添加任务:{self.object.currentData()}") + TaskManager.add_task( + f"{self.mode.currentText()}_主调度台", + self.object.currentData(), + Config.queue_dict[self.object.currentData()]["Config"].toDict(), + ) - logger.info(f"用户添加任务:{name}") - TaskManager.add_task(f"{self.mode.currentText()}_主调度台", name, info) + elif "脚本" in self.object.currentData(): - elif self.object.currentText().split(" - ")[0] == "实例": + if Config.member_dict[self.object.currentData()]["Type"] == "Maa": - if self.object.currentText().split(" - ")[1] == "Maa": - - info = {"Queue": {"Member_1": name}} - - logger.info(f"用户添加任务:{name}") + logger.info(f"用户添加任务:{self.object.currentData()}") TaskManager.add_task( - f"{self.mode.currentText()}_主调度台", "自定义队列", info + f"{self.mode.currentText()}_主调度台", + "自定义队列", + {"Queue": {"Member_1": self.object.currentData()}}, ) class DispatchInfoCard(HeaderCardWidget): diff --git a/app/ui/home.py b/app/ui/home.py index 0de3e35..a979e55 100644 --- a/app/ui/home.py +++ b/app/ui/home.py @@ -160,15 +160,9 @@ class Home(QWidget): def get_home_image(self) -> None: """获取主页图片""" - if ( - Config.global_config.get(Config.global_config.function_HomeImageMode) - == "默认" - ): + if Config.get(Config.function_HomeImageMode) == "默认": pass - elif ( - Config.global_config.get(Config.global_config.function_HomeImageMode) - == "自定义" - ): + elif Config.get(Config.function_HomeImageMode) == "自定义": file_path, _ = QFileDialog.getOpenFileName( self, "打开自定义主页图片", "", "图片文件 (*.png *.jpg *.bmp)" @@ -202,10 +196,7 @@ class Home(QWidget): "未选择图片文件!", 5000, ) - elif ( - Config.global_config.get(Config.global_config.function_HomeImageMode) - == "主题图像" - ): + elif Config.get(Config.function_HomeImageMode) == "主题图像": # 从远程服务器获取最新主题图像 for _ in range(3): @@ -244,7 +235,6 @@ class Home(QWidget): ).exists() or ( datetime.now() > datetime.strptime(theme_image["time"], "%Y-%m-%d %H:%M") - and datetime.strptime(theme_image["time"], "%Y-%m-%d %H:%M") > time_local ): @@ -293,28 +283,19 @@ class Home(QWidget): def set_banner(self): """设置主页图像""" - if ( - Config.global_config.get(Config.global_config.function_HomeImageMode) - == "默认" - ): + if Config.get(Config.function_HomeImageMode) == "默认": self.banner.set_banner_image( str(Config.app_path / "resources/images/Home/BannerDefault.png") ) self.imageButton.hide() self.banner_text.setVisible(False) - elif ( - Config.global_config.get(Config.global_config.function_HomeImageMode) - == "自定义" - ): + elif Config.get(Config.function_HomeImageMode) == "自定义": for file in Config.app_path.glob("resources/images/Home/BannerCustomize.*"): self.banner.set_banner_image(str(file)) break self.imageButton.show() self.banner_text.setVisible(False) - elif ( - Config.global_config.get(Config.global_config.function_HomeImageMode) - == "主题图像" - ): + elif Config.get(Config.function_HomeImageMode) == "主题图像": self.banner.set_banner_image( str(Config.app_path / "resources/images/Home/BannerTheme.jpg") ) diff --git a/app/ui/main_window.py b/app/ui/main_window.py index 053fdd9..53629e5 100644 --- a/app/ui/main_window.py +++ b/app/ui/main_window.py @@ -28,28 +28,23 @@ v4.2 from loguru import logger from PySide6.QtWidgets import QSystemTrayIcon from qfluentwidgets import ( + qconfig, Action, - PushButton, SystemTrayMenu, SplashScreen, FluentIcon, - InfoBar, - InfoBarPosition, setTheme, isDarkTheme, SystemThemeListener, Theme, MSFluentWindow, NavigationItemPosition, - qconfig, - FluentBackgroundTheme, ) from PySide6.QtGui import QIcon, QCloseEvent -from PySide6.QtCore import Qt, QTimer -import json +from PySide6.QtCore import QTimer from datetime import datetime, timedelta import shutil -import sys +import darkdetect from app.core import Config, TaskManager, MainTimer, MainInfoBar from app.services import Notify, Crypto, System @@ -69,7 +64,6 @@ class AUTO_MAA(MSFluentWindow): self.setWindowIcon(QIcon(str(Config.app_path / "resources/icons/AUTO_MAA.ico"))) self.setWindowTitle("AUTO_MAA") - setTheme(Theme.AUTO, lazy=True) self.switch_theme() self.splashScreen = SplashScreen(self.windowIcon(), self) @@ -130,10 +124,9 @@ class AUTO_MAA(MSFluentWindow): NavigationItemPosition.BOTTOM, ) self.stackedWidget.currentChanged.connect( - lambda index: (self.member_manager.refresh() if index == 1 else None) - ) - self.stackedWidget.currentChanged.connect( - lambda index: self.queue_manager.refresh() if index == 2 else None + lambda index: ( + self.queue_manager.reload_member_name() if index == 2 else None + ) ) self.stackedWidget.currentChanged.connect( lambda index: ( @@ -190,6 +183,7 @@ class AUTO_MAA(MSFluentWindow): self.tray.setContextMenu(self.tray_menu) self.tray.activated.connect(self.on_tray_activated) + Config.gameid_refreshed.connect(self.member_manager.refresh_gameid) TaskManager.create_gui.connect(self.dispatch_center.add_board) TaskManager.connect_gui.connect(self.dispatch_center.connect_main_board) Notify.push_info_bar.connect(MainInfoBar.push_info_bar) @@ -211,7 +205,10 @@ class AUTO_MAA(MSFluentWindow): def switch_theme(self) -> None: """切换主题""" - setTheme(Theme.AUTO, lazy=True) + + setTheme( + Theme(darkdetect.theme()) if darkdetect.theme() else Theme.LIGHT, lazy=True + ) QTimer.singleShot(300, lambda: setTheme(Theme.AUTO, lazy=True)) # 云母特效启用时需要增加重试机制 @@ -238,25 +235,22 @@ class AUTO_MAA(MSFluentWindow): def start_up_task(self) -> None: """启动时任务""" - # 加载配置 - qconfig.load(Config.config_path, Config.global_config) - Config.global_config.save() - # 清理旧日志 self.clean_old_logs() + # 清理临时更新器 + if (Config.app_path / "AUTO_Updater.active.exe").exists(): + (Config.app_path / "AUTO_Updater.active.exe").unlink() + # 检查密码 self.setting.check_PASSWORD() # 获取主题图像 - if ( - Config.global_config.get(Config.global_config.function_HomeImageMode) - == "主题图像" - ): + if Config.get(Config.function_HomeImageMode) == "主题图像": self.home.get_home_image() # 直接运行主任务 - if Config.global_config.get(Config.global_config.start_IfRunDirectly): + if Config.get(Config.start_IfRunDirectly): self.start_main_task() @@ -264,18 +258,18 @@ class AUTO_MAA(MSFluentWindow): self.setting.show_notice(if_show=False) # 检查更新 - if Config.global_config.get(Config.global_config.update_IfAutoUpdate): + if Config.get(Config.update_IfAutoUpdate): self.setting.check_update() # 直接最小化 - if Config.global_config.get(Config.global_config.start_IfMinimizeDirectly): + if Config.get(Config.start_IfMinimizeDirectly): self.titleBar.minBtn.click() def set_min_method(self) -> None: """设置最小化方法""" - if Config.global_config.get(Config.global_config.ui_IfToTray): + if Config.get(Config.ui_IfToTray): self.titleBar.minBtn.clicked.disconnect() self.titleBar.minBtn.clicked.connect(lambda: self.show_ui("隐藏到托盘")) @@ -295,10 +289,7 @@ class AUTO_MAA(MSFluentWindow): 删除超过用户设定天数的日志文件(基于目录日期) """ - if ( - Config.global_config.get(Config.global_config.function_HistoryRetentionTime) - == 0 - ): + if Config.get(Config.function_HistoryRetentionTime) == 0: logger.info("由于用户设置日志永久保留,跳过日志清理") return @@ -312,9 +303,7 @@ class AUTO_MAA(MSFluentWindow): # 只检查 `YYYY-MM-DD` 格式的文件夹 folder_date = datetime.strptime(date_folder.name, "%Y-%m-%d") if datetime.now() - folder_date > timedelta( - days=Config.global_config.get( - Config.global_config.function_HistoryRetentionTime - ) + days=Config.get(Config.function_HistoryRetentionTime) ): shutil.rmtree(date_folder, ignore_errors=True) deleted_count += 1 @@ -327,26 +316,25 @@ class AUTO_MAA(MSFluentWindow): def start_main_task(self) -> None: """启动主任务""" - if (Config.app_path / "config/QueueConfig/调度队列_1.json").exists(): - - with (Config.app_path / "config/QueueConfig/调度队列_1.json").open( - mode="r", encoding="utf-8" - ) as f: - info = json.load(f) + if "调度队列_1" in Config.queue_dict: logger.info("自动添加任务:调度队列_1") - TaskManager.add_task("自动代理_主调度台", "主任务队列", info) + TaskManager.add_task( + "自动代理_主调度台", + "主任务队列", + Config.queue_dict["调度队列_1"]["Config"].toDict(), + ) - elif (Config.app_path / "config/MaaConfig/脚本_1").exists(): - - info = {"Queue": {"Member_1": "脚本_1"}} + elif "脚本_1" in Config.member_dict: logger.info("自动添加任务:脚本_1") - TaskManager.add_task("自动代理_主调度台", "主任务队列", info) + TaskManager.add_task( + "自动代理_主调度台", "主任务队列", {"Queue": {"Member_1": "脚本_1"}} + ) else: - logger.worning("启动主任务失败:未找到有效的主任务配置文件") + logger.warning("启动主任务失败:未找到有效的主任务配置文件") MainInfoBar.push_info_bar( "warning", "启动主任务失败", "“调度队列_1”与“脚本_1”均不存在", -1 ) @@ -354,21 +342,21 @@ class AUTO_MAA(MSFluentWindow): def show_ui(self, mode: str, if_quick: bool = False) -> None: """配置窗口状态""" + self.switch_theme() + if mode == "显示主窗口": # 配置主窗口 size = list( map( int, - Config.global_config.get(Config.global_config.ui_size).split("x"), + Config.get(Config.ui_size).split("x"), ) ) location = list( map( int, - Config.global_config.get(Config.global_config.ui_location).split( - "x" - ), + Config.get(Config.ui_location).split("x"), ) ) self.window().setGeometry(location[0], location[1], size[0], size[1]) @@ -376,14 +364,14 @@ class AUTO_MAA(MSFluentWindow): self.window().raise_() self.window().activateWindow() if not if_quick: - if Config.global_config.get(Config.global_config.ui_maximized): + if Config.get(Config.ui_maximized): self.window().showMaximized() self.set_min_method() self.show_ui("配置托盘") elif mode == "配置托盘": - if Config.global_config.get(Config.global_config.ui_IfShowTray): + if Config.get(Config.ui_IfShowTray): self.tray.show() else: self.tray.hide() @@ -393,18 +381,16 @@ class AUTO_MAA(MSFluentWindow): # 保存窗口相关属性 if not self.window().isMaximized(): - Config.global_config.set( - Config.global_config.ui_size, + Config.set( + Config.ui_size, f"{self.geometry().width()}x{self.geometry().height()}", ) - Config.global_config.set( - Config.global_config.ui_location, + Config.set( + Config.ui_location, f"{self.geometry().x()}x{self.geometry().y()}", ) - Config.global_config.set( - Config.global_config.ui_maximized, self.window().isMaximized() - ) - Config.global_config.save() + Config.set(Config.ui_maximized, self.window().isMaximized()) + Config.save() # 隐藏主窗口 if not if_quick: @@ -420,11 +406,10 @@ class AUTO_MAA(MSFluentWindow): # 清理各功能线程 MainTimer.Timer.stop() MainTimer.Timer.deleteLater() + MainTimer.LongTimer.stop() + MainTimer.LongTimer.deleteLater() TaskManager.stop_task("ALL") - # 关闭数据库连接 - Config.close_database() - # 关闭主题监听 self.themeListener.terminate() self.themeListener.deleteLater() diff --git a/app/ui/member_manager.py b/app/ui/member_manager.py index b658ad6..a605ac9 100644 --- a/app/ui/member_manager.py +++ b/app/ui/member_manager.py @@ -29,49 +29,46 @@ from loguru import logger from PySide6.QtWidgets import ( QWidget, QFileDialog, - QTableWidgetItem, - QHeaderView, + QHBoxLayout, QVBoxLayout, QStackedWidget, ) from qfluentwidgets import ( Action, - qconfig, - TableWidget, Pivot, - ComboBox, ScrollArea, FluentIcon, MessageBox, HeaderCardWidget, CommandBar, ExpandGroupSettingCard, - ComboBoxSettingCard, PushSettingCard, - SwitchSettingCard, ) from PySide6.QtCore import Qt import requests import time -from functools import partial from pathlib import Path from typing import List -from datetime import datetime, timedelta -import json import shutil -from app.core import Config, MainInfoBar, TaskManager -from app.services import Crypto +from app.core import Config, MainInfoBar, TaskManager, MaaConfig, MaaUserConfig from app.utils import DownloadManager from .Widget import ( LineEditMessageBox, LineEditSettingCard, SpinBoxSettingCard, ComboBoxMessageBox, + EditableComboBoxSettingCard, + PasswordLineEditSettingCard, + UserLableSettingCard, + ComboBoxSettingCard, + SwitchSettingCard, + PushAndSwitchButtonSettingCard, ) class MemberManager(QWidget): + """脚本管理父界面""" def __init__(self, parent=None): super().__init__(parent) @@ -82,7 +79,7 @@ class MemberManager(QWidget): self.tools = CommandBar() - self.member_manager = MemberSettingBox(self) + self.member_manager = self.MemberSettingBox(self) # 逐个添加动作 self.tools.addActions( @@ -141,20 +138,33 @@ class MemberManager(QWidget): if choice.input[0].currentText() == "MAA": - index = len(self.member_manager.search_member()) + 1 + index = len(Config.member_dict) + 1 - qconfig.load( + maa_config = MaaConfig() + maa_config.load( Config.app_path / f"config/MaaConfig/脚本_{index}/config.json", - Config.maa_config, + maa_config, + ) + maa_config.save() + (Config.app_path / f"config/MaaConfig/脚本_{index}/UserData").mkdir( + parents=True, exist_ok=True ) - Config.clear_maa_config() - Config.maa_config.save() - Config.open_database("Maa", f"脚本_{index}") - Config.init_database("Maa") + Config.member_dict[f"脚本_{index}"] = { + "Type": "Maa", + "Path": Config.app_path / f"config/MaaConfig/脚本_{index}", + "Config": maa_config, + "UserData": {}, + } + self.member_manager.add_MaaSettingBox(index) self.member_manager.switch_SettingBox(index) + logger.success(f"脚本实例 脚本_{index} 添加成功") + MainInfoBar.push_info_bar( + "success", "操作成功", f"添加脚本实例 脚本_{index}", 3000 + ) + def del_setting_box(self): """删除一个脚本实例""" @@ -181,25 +191,23 @@ class MemberManager(QWidget): ) if choice.exec(): - member_list = self.member_manager.search_member() - move_list = [_ for _ in member_list if int(_[0][3:]) > int(name[3:])] - - type = [_[1] for _ in member_list if _[0] == name] - index = max(int(name[3:]) - 1, 1) - self.member_manager.clear_SettingBox() - shutil.rmtree(Config.app_path / f"config/{type[0]}Config/{name}") - self.change_queue(name, "禁用") - for member in move_list: - if (Config.app_path / f"config/{member[1]}Config/{member[0]}").exists(): - (Config.app_path / f"config/{member[1]}Config/{member[0]}").rename( - Config.app_path - / f"config/{member[1]}Config/脚本_{int(member[0][3:])-1}", + shutil.rmtree(Config.member_dict[name]["Path"]) + Config.change_queue(name, "禁用") + for i in range(int(name[3:]) + 1, len(Config.member_dict) + 1): + if Config.member_dict[f"脚本_{i}"]["Path"].exists(): + Config.member_dict[f"脚本_{i}"]["Path"].rename( + Config.member_dict[f"脚本_{i}"]["Path"].with_name(f"脚本_{i-1}") ) - self.change_queue(member[0], f"脚本_{int(member[0][3:])-1}") + Config.change_queue(f"脚本_{i}", f"脚本_{i-1}") - self.member_manager.show_SettingBox(index) + self.member_manager.show_SettingBox(max(int(name[3:]) - 1, 1)) + + logger.success(f"脚本实例 {name} 删除成功") + MainInfoBar.push_info_bar( + "success", "操作成功", f"删除脚本实例 {name}", 3000 + ) def left_setting_box(self): """向左移动脚本实例""" @@ -213,7 +221,6 @@ class MemberManager(QWidget): ) return None - member_list = self.member_manager.search_member() index = int(name[3:]) if index == 1: @@ -230,26 +237,26 @@ class MemberManager(QWidget): ) return None - type_right = [_[1] for _ in member_list if _[0] == name] - type_left = [_[1] for _ in member_list if _[0] == f"脚本_{index-1}"] - self.member_manager.clear_SettingBox() - (Config.app_path / f"config/{type_right[0]}Config/脚本_{index}").rename( - Config.app_path / f"config/{type_right[0]}Config/脚本_0" + Config.member_dict[name]["Path"].rename( + Config.member_dict[name]["Path"].with_name("脚本_0") ) - self.change_queue(f"脚本_{index}", "脚本_0") - (Config.app_path / f"config/{type_left[0]}Config/脚本_{index-1}").rename( - Config.app_path / f"config/{type_left[0]}Config/脚本_{index}" + Config.change_queue(name, "脚本_0") + Config.member_dict[f"脚本_{index-1}"]["Path"].rename( + Config.member_dict[name]["Path"] ) - self.change_queue(f"脚本_{index-1}", f"脚本_{index}") - (Config.app_path / f"config/{type_right[0]}Config/脚本_0").rename( - Config.app_path / f"config/{type_right[0]}Config/脚本_{index-1}" + Config.change_queue(f"脚本_{index-1}", name) + Config.member_dict[name]["Path"].with_name("脚本_0").rename( + Config.member_dict[f"脚本_{index-1}"]["Path"] ) - self.change_queue("脚本_0", f"脚本_{index-1}") + Config.change_queue("脚本_0", f"脚本_{index-1}") self.member_manager.show_SettingBox(index - 1) + logger.success(f"脚本实例 {name} 左移成功") + MainInfoBar.push_info_bar("success", "操作成功", f"左移脚本实例 {name}", 3000) + def right_setting_box(self): """向右移动脚本实例""" @@ -262,10 +269,9 @@ class MemberManager(QWidget): ) return None - member_list = self.member_manager.search_member() index = int(name[3:]) - if index == len(member_list): + if index == len(Config.member_dict): logger.warning("向右移动脚本实例时已到达最右端") MainInfoBar.push_info_bar( "warning", "已经是最后一个脚本实例", "无法向右移动", 5000 @@ -279,26 +285,26 @@ class MemberManager(QWidget): ) return None - type_left = [_[1] for _ in member_list if _[0] == name] - type_right = [_[1] for _ in member_list if _[0] == f"脚本_{index+1}"] - self.member_manager.clear_SettingBox() - (Config.app_path / f"config/{type_left[0]}Config/脚本_{index}").rename( - Config.app_path / f"config/{type_left[0]}Config/脚本_0", + Config.member_dict[name]["Path"].rename( + Config.member_dict[name]["Path"].with_name("脚本_0") ) - self.change_queue(f"脚本_{index}", "脚本_0") - (Config.app_path / f"config/{type_right[0]}Config/脚本_{index+1}").rename( - Config.app_path / f"config/{type_right[0]}Config/脚本_{index}", + Config.change_queue(name, "脚本_0") + Config.member_dict[f"脚本_{index+1}"]["Path"].rename( + Config.member_dict[name]["Path"] ) - self.change_queue(f"脚本_{index+1}", f"脚本_{index}") - (Config.app_path / f"config/{type_left[0]}Config/脚本_0").rename( - Config.app_path / f"config/{type_left[0]}Config/脚本_{index+1}", + Config.change_queue(f"脚本_{index+1}", name) + Config.member_dict[name]["Path"].with_name("脚本_0").rename( + Config.member_dict[f"脚本_{index+1}"]["Path"] ) - self.change_queue("脚本_0", f"脚本_{index+1}") + Config.change_queue("脚本_0", f"脚本_{index+1}") self.member_manager.show_SettingBox(index + 1) + logger.success(f"脚本实例 {name} 右移成功") + MainInfoBar.push_info_bar("success", "操作成功", f"右移脚本实例 {name}", 3000) + def member_downloader(self): """脚本下载器""" @@ -327,7 +333,7 @@ class MemberManager(QWidget): for _ in range(3): try: response = requests.get( - "https://mirrorc.top/api/resources/MAA/latest?user_agent=MaaWpfGui&os=win&arch=x64&channel=stable" + "https://mirrorchyan.com/api/resources/MAA/latest?user_agent=MaaWpfGui&os=win&arch=x64&channel=stable" ) maa_info = response.json() break @@ -359,12 +365,7 @@ class MemberManager(QWidget): Path(folder), "MAA", maa_version, - [], - { - "thread_numb": Config.global_config.get( - Config.global_config.update_ThreadNumb - ) - }, + {"thread_numb": Config.get(Config.update_ThreadNumb)}, ) self.downloader.show() self.downloader.run() @@ -380,1349 +381,937 @@ class MemberManager(QWidget): ) if choice.exec() and choice.input.text() != "": Config.PASSWORD = choice.input.text() - self.member_manager.script_list[ - int(self.member_manager.pivot.currentRouteKey()[3:]) - 1 - ].user_setting.user_list.update_user_info("normal") + for script in self.member_manager.script_list: + script.user_setting.refresh_password() self.key.setIcon(FluentIcon.VIEW) self.key.setChecked(True) else: Config.PASSWORD = "" - self.member_manager.script_list[ - int(self.member_manager.pivot.currentRouteKey()[3:]) - 1 - ].user_setting.user_list.update_user_info("normal") + for script in self.member_manager.script_list: + script.user_setting.refresh_password() self.key.setIcon(FluentIcon.HIDE) self.key.setChecked(False) else: Config.PASSWORD = "" - self.member_manager.script_list[ - int(self.member_manager.pivot.currentRouteKey()[3:]) - 1 - ].user_setting.user_list.update_user_info("normal") + for script in self.member_manager.script_list: + script.user_setting.refresh_password() self.key.setIcon(FluentIcon.HIDE) self.key.setChecked(False) - def change_queue(self, old: str, new: str) -> None: - """修改调度队列配置文件的队列参数""" + def refresh_gameid(self): + """刷新所有脚本实例的游戏ID列表""" - if (Config.app_path / "config/QueueConfig").exists(): - for json_file in (Config.app_path / "config/QueueConfig").glob("*.json"): - with json_file.open("r", encoding="utf-8") as f: - data = json.load(f) + for script in self.member_manager.script_list: + script.user_setting.refresh_gameid() - for i in range(10): - if data["Queue"][f"Member_{i+1}"] == old: - data["Queue"][f"Member_{i+1}"] = new + class MemberSettingBox(QWidget): + """脚本管理子页面组""" - with json_file.open("w", encoding="utf-8") as f: - json.dump(data, f, ensure_ascii=False, indent=4) - - def refresh(self): - """刷新脚本实例界面""" - - if len(self.member_manager.search_member()) == 0: - index = 0 - else: - index = int(self.member_manager.pivot.currentRouteKey()[3:]) - self.member_manager.switch_SettingBox(index) - - -class MemberSettingBox(QWidget): - - def __init__(self, parent=None): - super().__init__(parent) - - self.setObjectName("脚本管理") - - self.pivot = Pivot(self) - self.stackedWidget = QStackedWidget(self) - self.Layout = QVBoxLayout(self) - - self.script_list: List[MaaSettingBox] = [] - - self.Layout.addWidget(self.pivot, 0, Qt.AlignHCenter) - self.Layout.addWidget(self.stackedWidget) - self.Layout.setContentsMargins(0, 0, 0, 0) - - self.pivot.currentItemChanged.connect( - lambda index: self.switch_SettingBox(int(index[3:]), if_chang_pivot=False) - ) - - self.show_SettingBox(1) - - def show_SettingBox(self, index) -> None: - """加载所有子界面""" - - member_list = self.search_member() - - qconfig.load( - Config.app_path / "config/临时.json", - Config.maa_config, - ) - Config.clear_maa_config() - for member in member_list: - if member[1] == "Maa": - Config.open_database(member[1], member[0]) - self.add_MaaSettingBox(int(member[0][3:])) - if (Config.app_path / "config/临时.json").exists(): - (Config.app_path / "config/临时.json").unlink() - - self.switch_SettingBox(index) - - def switch_SettingBox(self, index: int, if_chang_pivot: bool = True) -> None: - """切换到指定的子界面""" - - member_list = self.search_member() - - if len(member_list) == 0: - return None - - if index > len(member_list): - return None - - type = [_[1] for _ in member_list if _[0] == f"脚本_{index}"] - - qconfig.load( - Config.app_path - / f"config/{type[0]}Config/{self.script_list[index-1].objectName()}/config.json", - Config.maa_config, - ) - Config.open_database(type[0], self.script_list[index - 1].objectName()) - self.script_list[index - 1].user_setting.user_list.update_user_info("normal") - - if if_chang_pivot: - self.pivot.setCurrentItem(self.script_list[index - 1].objectName()) - self.stackedWidget.setCurrentWidget(self.script_list[index - 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() - qconfig.load( - Config.app_path / "config/临时.json", - Config.maa_config, - ) - Config.clear_maa_config() - if (Config.app_path / "config/临时.json").exists(): - (Config.app_path / "config/临时.json").unlink() - Config.close_database() - - def add_MaaSettingBox(self, uid: int) -> None: - """添加一个MAA设置界面""" - - maa_setting_box = MaaSettingBox(uid, self) - - self.script_list.append(maa_setting_box) - - self.stackedWidget.addWidget(self.script_list[-1]) - - self.pivot.addItem(routeKey=f"脚本_{uid}", text=f"脚本 {uid}") - - def search_member(self) -> list: - """搜索所有脚本实例""" - - member_list = [] - - if (Config.app_path / "config/MaaConfig").exists(): - for subdir in (Config.app_path / "config/MaaConfig").iterdir(): - if subdir.is_dir(): - member_list.append([subdir.name, "Maa"]) - - return member_list - - -class MaaSettingBox(QWidget): - - def __init__(self, uid: int, parent=None): - super().__init__(parent) - - self.setObjectName(f"脚本_{uid}") - - layout = QVBoxLayout() - - scrollArea = ScrollArea() - scrollArea.setWidgetResizable(True) - - content_widget = QWidget() - content_layout = QVBoxLayout(content_widget) - - self.app_setting = self.AppSettingCard(self, self.objectName()) - self.user_setting = self.UserSettingCard(self, self.objectName()) - - content_layout.addWidget(self.app_setting) - content_layout.addWidget(self.user_setting) - content_layout.addStretch(1) - - scrollArea.setWidget(content_widget) - - layout.addWidget(scrollArea) - - self.setLayout(layout) - - class AppSettingCard(HeaderCardWidget): - - def __init__(self, parent=None, name: str = None): + def __init__(self, parent=None): super().__init__(parent) - self.setTitle("MAA实例") + self.setObjectName("脚本管理页面组") - self.name = name + self.pivot = Pivot(self) + self.stackedWidget = QStackedWidget(self) + self.Layout = QVBoxLayout(self) - Layout = QVBoxLayout() + self.script_list: List[MemberManager.MemberSettingBox.MaaSettingBox] = [] - self.card_Name = LineEditSettingCard( - "请输入实例名称", - FluentIcon.EDIT, - "实例名称", - "用于标识MAA实例的名称", - Config.maa_config.MaaSet_Name, - ) - self.card_Path = PushSettingCard( - "选择文件夹", - FluentIcon.FOLDER, - "MAA目录", - Config.maa_config.get(Config.maa_config.MaaSet_Path), - ) - self.card_Set = PushSettingCard( - "设置", - FluentIcon.HOME, - "MAA全局配置", - "简洁模式下MAA将继承全局配置", - ) - self.RunSet = self.RunSetSettingCard(self) + self.Layout.addWidget(self.pivot, 0, Qt.AlignHCenter) + self.Layout.addWidget(self.stackedWidget) + self.Layout.setContentsMargins(0, 0, 0, 0) - self.card_Path.clicked.connect(self.PathClicked) - Config.maa_config.MaaSet_Path.valueChanged.connect( - lambda: self.card_Path.setContent( - Config.maa_config.get(Config.maa_config.MaaSet_Path) + self.pivot.currentItemChanged.connect( + lambda index: self.switch_SettingBox( + int(index[3:]), if_chang_pivot=False ) ) - self.card_Set.clicked.connect( - lambda: TaskManager.add_task("设置MAA_全局", self.name, None) - ) - Layout.addWidget(self.card_Name) - Layout.addWidget(self.card_Path) - Layout.addWidget(self.card_Set) - Layout.addWidget(self.RunSet) + self.show_SettingBox(1) - self.viewLayout.addLayout(Layout) + def show_SettingBox(self, index) -> None: + """加载所有子界面""" - def PathClicked(self): + Config.search_member() - folder = QFileDialog.getExistingDirectory( - self, - "选择MAA目录", - Config.maa_config.get(Config.maa_config.MaaSet_Path), - ) - if ( - not folder - or Config.maa_config.get(Config.maa_config.MaaSet_Path) == folder - ): - logger.warning("选择MAA目录时未选择文件夹或未更改文件夹") - MainInfoBar.push_info_bar( - "warning", "警告", "未选择文件夹或未更改文件夹", 5000 - ) - return None - elif ( - not (Path(folder) / "config/gui.json").exists() - or not (Path(folder) / "MAA.exe").exists() - ): - logger.warning("选择MAA目录时未找到MAA程序或配置文件") - MainInfoBar.push_info_bar( - "warning", "警告", "未找到MAA程序或配置文件", 5000 - ) + for name, info in Config.member_dict.items(): + if info["Type"] == "Maa": + self.add_MaaSettingBox(int(name[3:])) + + self.switch_SettingBox(index) + + def switch_SettingBox(self, index: int, if_chang_pivot: bool = True) -> None: + """切换到指定的子界面""" + + if len(Config.member_dict) == 0: return None - (Config.app_path / f"config/MaaConfig/{self.name}/Default").mkdir( - parents=True, exist_ok=True - ) - shutil.copy( - Path(folder) / "config/gui.json", - Config.app_path / f"config/MaaConfig/{self.name}/Default/gui.json", - ) - Config.maa_config.set(Config.maa_config.MaaSet_Path, folder) - self.card_Path.setContent(folder) - - class RunSetSettingCard(ExpandGroupSettingCard): - - def __init__(self, parent=None): - super().__init__(FluentIcon.SETTING, "运行", "MAA运行调控选项", parent) - - self.card_TaskTransitionMethod = ComboBoxSettingCard( - configItem=Config.maa_config.RunSet_TaskTransitionMethod, - icon=FluentIcon.PAGE_RIGHT, - title="任务切换方式", - content="简洁用户列表下相邻两个任务间的切换方式", - texts=["直接切换账号", "重启明日方舟", "重启模拟器"], - ) - self.card_EnhanceTask = ComboBoxSettingCard( - configItem=Config.maa_config.RunSet_EnhanceTask, - icon=FluentIcon.PAGE_RIGHT, - title="自动代理增效任务", - content="自动代理时的额外操作,此操作无法区分多开,可能会干扰其他任务,也可能关闭您正在使用的模拟器", - texts=[ - "禁用", - "强制关闭ADB", - "强制关闭所有模拟器", - "强制关闭ADB和所有模拟器", - ], - ) - self.ProxyTimesLimit = SpinBoxSettingCard( - (0, 1024), - FluentIcon.PAGE_RIGHT, - "用户单日代理次数上限", - "当用户本日代理成功次数超过该阈值时跳过代理,阈值为“0”时视为无代理次数上限", - Config.maa_config.RunSet_ProxyTimesLimit, - ) - self.AnnihilationTimeLimit = SpinBoxSettingCard( - (1, 1024), - FluentIcon.PAGE_RIGHT, - "剿灭代理超时限制", - "MAA日志无变化时间超过该阈值视为超时,单位为分钟", - Config.maa_config.RunSet_AnnihilationTimeLimit, - ) - self.RoutineTimeLimit = SpinBoxSettingCard( - (1, 1024), - FluentIcon.PAGE_RIGHT, - "自动代理超时限制", - "MAA日志无变化时间超过该阈值视为超时,单位为分钟", - Config.maa_config.RunSet_RoutineTimeLimit, - ) - self.RunTimesLimit = SpinBoxSettingCard( - (1, 1024), - FluentIcon.PAGE_RIGHT, - "代理重试次数限制", - "若超过该次数限制仍未完成代理,视为代理失败", - Config.maa_config.RunSet_RunTimesLimit, - ) - self.AnnihilationWeeklyLimit = SwitchSettingCard( - configItem=Config.maa_config.RunSet_AnnihilationWeeklyLimit, - icon=FluentIcon.PAGE_RIGHT, - title="每周剿灭仅执行到上限", - content="每周剿灭模式执行到上限,本周剩下时间不再执行剿灭任务", - ) - - widget = QWidget() - Layout = QVBoxLayout(widget) - Layout.addWidget(self.card_TaskTransitionMethod) - Layout.addWidget(self.card_EnhanceTask) - Layout.addWidget(self.ProxyTimesLimit) - Layout.addWidget(self.AnnihilationTimeLimit) - Layout.addWidget(self.RoutineTimeLimit) - Layout.addWidget(self.RunTimesLimit) - Layout.addWidget(self.AnnihilationWeeklyLimit) - self.viewLayout.setContentsMargins(0, 0, 0, 0) - self.viewLayout.setSpacing(0) - self.addGroupWidget(widget) - - class UserSettingCard(HeaderCardWidget): - - def __init__(self, parent=None, name: str = None): - super().__init__(parent) - - self.setTitle("用户列表") - - self.name = name - - Layout = QVBoxLayout() - - self.user_list = self.UserListBox(self.name, self) - - self.tools = CommandBar() - self.tools.addActions( - [ - Action( - FluentIcon.ADD, "新建用户", triggered=self.user_list.add_user - ), - Action( - FluentIcon.REMOVE, "删除用户", triggered=self.user_list.del_user - ), - ] - ) - self.tools.addSeparator() - self.tools.addActions( - [ - Action(FluentIcon.UP, "向上移动", triggered=self.user_list.up_user), - Action( - FluentIcon.DOWN, "向下移动", triggered=self.user_list.down_user - ), - ] - ) - self.tools.addSeparator() - self.tools.addAction( - Action( - FluentIcon.SCROLL, "模式转换", triggered=self.user_list.switch_user - ) - ) - self.tools.addSeparator() - self.tools.addAction( - Action( - FluentIcon.DEVELOPER_TOOLS, "用户选项配置", triggered=self.set_more - ) - ) - - Layout.addWidget(self.tools) - Layout.addWidget(self.user_list) - - self.viewLayout.addLayout(Layout) - - def set_more(self): - """用户选项配置""" - - if len(Config.running_list) > 0: - logger.warning("配置用户选项时调度队列未停止运行") - MainInfoBar.push_info_bar( - "warning", "调度中心正在执行任务", "请等待或手动中止任务", 5000 - ) + if index > len(Config.member_dict): return None - Config.cur.execute("SELECT * FROM adminx WHERE True") - data = Config.cur.fetchall() - data = sorted(data, key=lambda x: (-len(x[15]), x[16])) + if if_chang_pivot: + self.pivot.setCurrentItem(self.script_list[index - 1].objectName()) + self.stackedWidget.setCurrentWidget(self.script_list[index - 1]) - if self.user_list.pivot.currentRouteKey() == f"{self.name}_简洁用户列表": + def clear_SettingBox(self) -> None: + """清空所有子界面""" - user_list = [_[0] for _ in data if _[15] == "simple"] - set_list = ["自定义基建"] + for sub_interface in self.script_list: + self.stackedWidget.removeWidget(sub_interface) + sub_interface.deleteLater() + self.script_list.clear() + self.pivot.clear() - choice = ComboBoxMessageBox( - self.window(), - "用户选项配置", - ["选择要配置的用户", "选择要配置的选项"], - [user_list, set_list], - ) - if ( - choice.exec() - and choice.input[0].currentIndex() != -1 - and choice.input[1].currentIndex() != -1 - ): + def add_MaaSettingBox(self, uid: int) -> None: + """添加一个MAA设置界面""" - if choice.input[1].currentIndex() == 0: - file_path, _ = QFileDialog.getOpenFileName( - self, - "选择自定义基建文件", - ".", - "JSON 文件 (*.json)", - ) - if file_path != "": - ( - Config.app_path - / f"config/MaaConfig/{self.name}/simple/{choice.input[0].currentIndex()}/infrastructure" - ).mkdir(parents=True, exist_ok=True) - shutil.copy( - file_path, - Config.app_path - / f"config/MaaConfig/{self.name}/simple/{choice.input[0].currentIndex()}/infrastructure/infrastructure.json", - ) - else: - logger.warning("未选择自定义基建文件") - MainInfoBar.push_info_bar( - "warning", "警告", "未选择自定义基建文件", 5000 - ) + maa_setting_box = self.MaaSettingBox(uid, self) - elif self.user_list.pivot.currentRouteKey() == f"{self.name}_高级用户列表": + self.script_list.append(maa_setting_box) - user_list = [_[0] for _ in data if _[15] == "beta"] - set_list = ["MAA日常配置", "MAA剿灭配置"] + self.stackedWidget.addWidget(self.script_list[-1]) - choice = ComboBoxMessageBox( - self.window(), - "用户选项配置", - ["选择要配置的用户", "选择要配置的选项"], - [user_list, set_list], - ) - if ( - choice.exec() - and choice.input[0].currentIndex() != -1 - and choice.input[1].currentIndex() != -1 - ): + self.pivot.addItem(routeKey=f"脚本_{uid}", text=f"脚本 {uid}") - set_book = ["routine", "annihilation"] - TaskManager.add_task( - "设置MAA_用户", - self.name, - { - "SetMaaInfo": { - "UserId": choice.input[0].currentIndex(), - "SetType": set_book[choice.input[1].currentIndex()], - } - }, - ) + class MaaSettingBox(QWidget): + """MAA类脚本设置界面""" - class UserListBox(QWidget): - - def __init__(self, name: str, parent=None): + def __init__(self, uid: int, parent=None): super().__init__(parent) - self.setObjectName(f"{name}_用户列表") - self.name = name + self.setObjectName(f"脚本_{uid}") + self.config = Config.member_dict[f"脚本_{uid}"]["Config"] - self.if_user_list_editable = True - self.if_update_database = True - self.if_update_config = True + layout = QVBoxLayout() - self.user_mode_list = ["simple", "beta"] - self.user_column = [ - "admin", - "id", - "server", - "day", - "status", - "last", - "game", - "game_1", - "game_2", - "routine", - "annihilation", - "infrastructure", - "password", - "notes", - "numb", - "mode", - "uid", - ] - self.userlist_simple_index = [ - 0, - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - "-", - 9, - 10, - 11, - 12, - "-", - "-", - "-", - ] - self.userlist_beta_index = [ - 0, - "-", - "-", - 1, - 2, - 3, - "-", - "-", - "-", - 4, - 5, - "-", - 6, - 7, - "-", - "-", - "-", - ] + scrollArea = ScrollArea() + scrollArea.setWidgetResizable(True) - self.pivot = Pivot(self) - self.stackedWidget = QStackedWidget(self) - self.Layout = QVBoxLayout(self) + content_widget = QWidget() + content_layout = QVBoxLayout(content_widget) - self.user_list_simple = TableWidget() - self.user_list_simple.setObjectName(f"{self.name}_简洁用户列表") - self.user_list_simple.setColumnCount(13) - self.user_list_simple.setBorderVisible(True) - self.user_list_simple.setBorderRadius(10) - self.user_list_simple.setWordWrap(False) - self.user_list_simple.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) - self.user_list_simple.setHorizontalHeaderLabels( - [ - "用户名", - "账号ID", - "服务器", - "代理天数", - "状态", - "执行情况", - "关卡", - "备选关卡-1", - "备选关卡-2", - "剿灭", - "自定义基建", - "密码", - "备注", - ] - ) + self.app_setting = self.AppSettingCard(f"脚本_{uid}", self.config, self) + self.user_setting = self.UserManager(f"脚本_{uid}", self) - self.user_list_beta = TableWidget() - self.user_list_beta.setObjectName(f"{name}_高级用户列表") - self.user_list_beta.setColumnCount(8) - self.user_list_beta.setBorderVisible(True) - self.user_list_beta.setBorderRadius(10) - self.user_list_beta.setWordWrap(False) - self.user_list_beta.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) - self.user_list_beta.setHorizontalHeaderLabels( - [ - "用户名", - "代理天数", - "状态", - "执行情况", - "日常", - "剿灭", - "密码", - "备注", - ] - ) + content_layout.addWidget(self.app_setting) + content_layout.addWidget(self.user_setting) + content_layout.addStretch(1) - self.user_list_simple.itemChanged.connect( - lambda item: self.change_user_Item(item, "simple") - ) - self.user_list_beta.itemChanged.connect( - lambda item: self.change_user_Item(item, "beta") - ) + scrollArea.setWidget(content_widget) - self.stackedWidget.addWidget(self.user_list_simple) - self.pivot.addItem( - routeKey=f"{name}_简洁用户列表", text=f"简洁用户列表" - ) - self.stackedWidget.addWidget(self.user_list_beta) - self.pivot.addItem( - routeKey=f"{name}_高级用户列表", text=f"高级用户列表" - ) + layout.addWidget(scrollArea) - self.Layout.addWidget(self.pivot, 0, Qt.AlignHCenter) - self.Layout.addWidget(self.stackedWidget) - self.Layout.setContentsMargins(0, 0, 0, 0) + self.setLayout(layout) - self.update_user_info("normal") - self.switch_SettingBox(f"{name}_简洁用户列表") - self.pivot.currentItemChanged.connect( - lambda index: self.switch_SettingBox(index) - ) + class AppSettingCard(HeaderCardWidget): - def switch_SettingBox(self, index: str) -> None: - """切换到指定的子界面""" + def __init__(self, name: str, config: MaaConfig, parent=None): + super().__init__(parent) - self.pivot.setCurrentItem(index) - if "简洁用户列表" in index: - self.stackedWidget.setCurrentWidget(self.user_list_simple) - elif "高级用户列表" in index: - self.stackedWidget.setCurrentWidget(self.user_list_beta) + self.setTitle("MAA实例") - def update_user_info(self, operation: str) -> None: - """将本地数据库中的用户配置同步至GUI的用户管理界面""" + self.name = name + self.config = config - # 读入本地数据库 - Config.cur.execute("SELECT * FROM adminx WHERE True") - data = Config.cur.fetchall() + Layout = QVBoxLayout() - # 处理部分模式调整 - if operation == "read_only": - self.if_user_list_editable = False - elif operation == "editable": - self.if_user_list_editable = True + self.card_Name = LineEditSettingCard( + icon=FluentIcon.EDIT, + title="实例名称", + content="用于标识MAA实例的名称", + text="请输入实例名称", + qconfig=self.config, + configItem=self.config.MaaSet_Name, + parent=self, + ) + self.card_Path = PushSettingCard( + text="选择文件夹", + icon=FluentIcon.FOLDER, + title="MAA目录", + content=self.config.get(self.config.MaaSet_Path), + parent=self, + ) + self.card_Set = PushSettingCard( + text="设置", + icon=FluentIcon.HOME, + title="MAA全局配置", + content="简洁模式下MAA将继承全局配置", + parent=self, + ) + self.RunSet = self.RunSetSettingCard(self.config, self) - # 阻止GUI用户数据被立即写入数据库形成死循环 - self.if_update_database = False - - # user_switch_list = ["转为高级", "转为简洁"] - # self.user_switch.setText(user_switch_list[index]) - - # 同步简洁用户配置列表 - data_simple = [_ for _ in data if _[15] == "simple"] - self.user_list_simple.setRowCount(len(data_simple)) - height = self.user_list_simple.horizontalHeader().height() - - for i, row in enumerate(data_simple): - - height += self.user_list_simple.rowHeight(i) - - for j, value in enumerate(row): - - if self.userlist_simple_index[j] == "-": - continue - - # 生成表格组件 - if j == 2: - item = ComboBox() - item.addItems(["官服", "B服"]) - if value == "Official": - item.setCurrentIndex(0) - elif value == "Bilibili": - item.setCurrentIndex(1) - item.currentIndexChanged.connect( - partial( - self.change_user_CellWidget, - data_simple[i][16], - self.user_column[j], - ) - ) - elif j in [4, 10, 11]: - item = ComboBox() - item.addItems(["启用", "禁用"]) - if value == "y": - item.setCurrentIndex(0) - elif value == "n": - item.setCurrentIndex(1) - item.currentIndexChanged.connect( - partial( - self.change_user_CellWidget, - data_simple[i][16], - self.user_column[j], - ) - ) - elif j == 3 and value == -1: - item = QTableWidgetItem("无限") - elif j == 5: - curdate = server_date() - if curdate != value: - item = QTableWidgetItem("未代理") - else: - item = QTableWidgetItem(f"已代理{data_simple[i][14]}次") - item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) - elif j == 12: - if Config.PASSWORD == "": - item = QTableWidgetItem("******") - item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) - else: - result = Crypto.AUTO_decryptor(value, Config.PASSWORD) - item = QTableWidgetItem(result) - if result == "管理密钥错误": - item.setFlags( - Qt.ItemIsSelectable | Qt.ItemIsEnabled - ) - else: - item = QTableWidgetItem(str(value)) - - # 组件录入表格 - if j in [2, 4, 10, 11]: - if not self.if_user_list_editable: - item.setEnabled(False) - self.user_list_simple.setCellWidget( - data_simple[i][16], self.userlist_simple_index[j], item - ) - else: - self.user_list_simple.setItem( - data_simple[i][16], self.userlist_simple_index[j], item - ) - self.user_list_simple.setFixedHeight( - height + self.user_list_simple.frameWidth() * 2 + 10 - ) - - # 同步高级用户配置列表 - data_beta = [_ for _ in data if _[15] == "beta"] - self.user_list_beta.setRowCount(len(data_beta)) - height = self.user_list_beta.horizontalHeader().height() - - for i, row in enumerate(data_beta): - - height += self.user_list_beta.rowHeight(i) - - for j, value in enumerate(row): - - if self.userlist_beta_index[j] == "-": - continue - - # 生成表格组件 - if j in [4, 9, 10]: - item = ComboBox() - item.addItems(["启用", "禁用"]) - if value == "y": - item.setCurrentIndex(0) - elif value == "n": - item.setCurrentIndex(1) - item.currentIndexChanged.connect( - partial( - self.change_user_CellWidget, - data_beta[i][16], - self.user_column[j], - ) - ) - elif j == 3 and value == -1: - item = QTableWidgetItem("无限") - elif j == 5: - curdate = server_date() - if curdate != value: - item = QTableWidgetItem("未代理") - else: - item = QTableWidgetItem(f"已代理{data_beta[i][14]}次") - item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) - elif j == 12: - if Config.PASSWORD == "": - item = QTableWidgetItem("******") - item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) - else: - result = Crypto.AUTO_decryptor(value, Config.PASSWORD) - item = QTableWidgetItem(result) - if result == "管理密钥错误": - item.setFlags( - Qt.ItemIsSelectable | Qt.ItemIsEnabled - ) - else: - item = QTableWidgetItem(str(value)) - - # 组件录入表格 - if j in [4, 9, 10]: - if not self.if_user_list_editable: - item.setEnabled(False) - self.user_list_beta.setCellWidget( - data_beta[i][16], self.userlist_beta_index[j], item - ) - else: - self.user_list_beta.setItem( - data_beta[i][16], self.userlist_beta_index[j], item - ) - self.user_list_beta.setFixedHeight( - height + self.user_list_beta.frameWidth() * 2 + 10 - ) - - # 设置列表可编辑状态 - if self.if_user_list_editable: - self.user_list_simple.setEditTriggers(TableWidget.AllEditTriggers) - self.user_list_beta.setEditTriggers(TableWidget.AllEditTriggers) - else: - self.user_list_simple.setEditTriggers(TableWidget.NoEditTriggers) - self.user_list_beta.setEditTriggers(TableWidget.NoEditTriggers) - - # 允许GUI改变被同步到本地数据库 - self.if_update_database = True - - # 设置用户配置列表的标题栏宽度 - self.user_list_simple.horizontalHeader().setSectionResizeMode( - QHeaderView.Stretch - ) - self.user_list_beta.horizontalHeader().setSectionResizeMode( - QHeaderView.Stretch - ) - - def change_user_Item(self, item: QTableWidgetItem, mode): - """将GUI中发生修改的用户配置表中的一般信息同步至本地数据库""" - - # 验证能否写入本地数据库 - if not self.if_update_database: - return None - - text = item.text() - # 简洁用户配置列表 - if mode == "simple": - # 待写入信息预处理 - if item.column() == 3: # 代理天数 - try: - text = max(int(text), -1) - except ValueError: - self.update_user_info("normal") - return None - if item.column() in [6, 7, 8]: # 关卡号 - # 导入与应用特殊关卡规则 - games = {} - with Config.gameid_path.open(mode="r", encoding="utf-8") as f: - gameids = f.readlines() - for line in gameids: - if ":" in line: - game_in, game_out = line.split(":", 1) - games[game_in.strip()] = game_out.strip() - text = games.get(text, text) - if item.column() == 11: # 密码 - text = Crypto.AUTO_encryptor(text) - - # 保存至本地数据库 - if text != "": - Config.cur.execute( - f"UPDATE adminx SET {self.user_column[self.userlist_simple_index.index(item.column())]} = ? WHERE mode = 'simple' AND uid = ?", - (text, item.row()), + self.card_Path.clicked.connect(self.PathClicked) + self.config.MaaSet_Path.valueChanged.connect( + lambda: self.card_Path.setContent( + self.config.get(self.config.MaaSet_Path) ) - # 高级用户配置列表 - elif mode == "beta": - # 待写入信息预处理 - if item.column() == 1: # 代理天数 - try: - text = max(int(text), -1) - except ValueError: - self.update_user_info("normal") - return None - if item.column() == 6: # 密码 - text = Crypto.AUTO_encryptor(text) + ) + self.card_Set.clicked.connect( + lambda: TaskManager.add_task("设置MAA_全局", self.name, None) + ) - # 保存至本地数据库 - if text != "": - Config.cur.execute( - f"UPDATE adminx SET {self.user_column[self.userlist_beta_index.index(item.column())]} = ? WHERE mode = 'beta' AND uid = ?", - (text, item.row()), + Layout.addWidget(self.card_Name) + Layout.addWidget(self.card_Path) + Layout.addWidget(self.card_Set) + Layout.addWidget(self.RunSet) + + self.viewLayout.addLayout(Layout) + + def PathClicked(self): + + folder = QFileDialog.getExistingDirectory( + self, + "选择MAA目录", + self.config.get(self.config.MaaSet_Path), + ) + if not folder or self.config.get(self.config.MaaSet_Path) == folder: + logger.warning("选择MAA目录时未选择文件夹或未更改文件夹") + MainInfoBar.push_info_bar( + "warning", "警告", "未选择文件夹或未更改文件夹", 5000 ) - Config.db.commit() + return None + elif ( + not (Path(folder) / "config/gui.json").exists() + or not (Path(folder) / "MAA.exe").exists() + ): + logger.warning("选择MAA目录时未找到MAA程序或配置文件") + MainInfoBar.push_info_bar( + "warning", "警告", "未找到MAA程序或配置文件", 5000 + ) + return None - # 同步一般用户信息更改到GUI - self.update_user_info("normal") - - def change_user_CellWidget(self, row, column, index): - """将GUI中发生修改的用户配置表中的CellWidget类信息同步至本地数据库""" - - # 验证能否写入本地数据库 - if not self.if_update_database: - return None - - if "简洁用户列表" in self.pivot.currentRouteKey(): - mode = 0 - elif "高级用户列表" in self.pivot.currentRouteKey(): - mode = 1 - - # 初次开启自定义MAA配置或选择修改MAA配置时调起MAA配置任务 - # if ( - # mode == 1 - # and column in ["routine", "annihilation"] - # and ( - # index == 2 - # or ( - # index == 0 - # and not ( - # Config.app_path - # / f"data/MAAconfig/{self.user_mode_list[index]}/{row}/{column}/gui.json" - # ).exists() - # ) - # ) - # ): - # pass - # self.MaaManager.get_json_path = [ - # index, - # row, - # column, - # ] - # self.maa_starter("设置MAA_用户") - - # 服务器 - if mode == 0 and column == "server": - server_list = ["Official", "Bilibili"] - Config.cur.execute( - f"UPDATE adminx SET server = ? WHERE mode = 'simple' AND uid = ?", - (server_list[index], row), + (Config.member_dict[self.name]["Path"] / "Default").mkdir( + parents=True, exist_ok=True ) - # 其它(启用/禁用) - elif index in [0, 1]: - index_list = ["y", "n"] - Config.cur.execute( - f"UPDATE adminx SET {column} = ? WHERE mode = ? AND uid = ?", - ( - index_list[index], - self.user_mode_list[mode], - row, - ), + shutil.copy( + Path(folder) / "config/gui.json", + Config.member_dict[self.name]["Path"] / "Default/gui.json", ) - Config.db.commit() + self.config.set(self.config.MaaSet_Path, folder) - # 同步用户组件信息修改到GUI - self.update_user_info("normal") + class RunSetSettingCard(ExpandGroupSettingCard): - def add_user(self): - """添加一位新用户""" + def __init__(self, config: MaaConfig, parent=None): + super().__init__( + FluentIcon.SETTING, "运行", "MAA运行调控选项", parent + ) + self.config = config - # 插入预设用户数据 - if "简洁用户列表" in self.pivot.currentRouteKey(): - set_book = ["simple", self.user_list_simple.rowCount()] - elif "高级用户列表" in self.pivot.currentRouteKey(): - set_book = ["beta", self.user_list_beta.rowCount()] - Config.cur.execute( - "INSERT INTO adminx VALUES('新用户','手机号码(官服)/B站ID(B服)','Official',-1,'y','2000-01-01','1-7','-','-','n','n','n',?,'无',0,?,?)", - ( - Crypto.AUTO_encryptor("未设置"), - set_book[0], - set_book[1], - ), - ) - Config.db.commit(), + self.card_TaskTransitionMethod = ComboBoxSettingCard( + icon=FluentIcon.PAGE_RIGHT, + title="任务切换方式", + content="简洁用户列表下相邻两个任务间的切换方式", + texts=["直接切换账号", "重启明日方舟", "重启模拟器"], + qconfig=self.config, + configItem=self.config.RunSet_TaskTransitionMethod, + parent=self, + ) + self.card_EnhanceTask = ComboBoxSettingCard( + icon=FluentIcon.PAGE_RIGHT, + title="自动代理增效任务", + content="自动代理时的额外操作,此操作无法区分多开,可能会干扰其他任务,也可能关闭您正在使用的模拟器", + texts=[ + "禁用", + "强制关闭ADB", + "强制关闭所有模拟器", + "强制关闭ADB和所有模拟器", + ], + qconfig=self.config, + configItem=self.config.RunSet_EnhanceTask, + parent=self, + ) + self.ProxyTimesLimit = SpinBoxSettingCard( + icon=FluentIcon.PAGE_RIGHT, + title="用户单日代理次数上限", + content="当用户本日代理成功次数超过该阈值时跳过代理,阈值为“0”时视为无代理次数上限", + range=(0, 1024), + qconfig=self.config, + configItem=self.config.RunSet_ProxyTimesLimit, + parent=self, + ) + self.AnnihilationTimeLimit = SpinBoxSettingCard( + icon=FluentIcon.PAGE_RIGHT, + title="剿灭代理超时限制", + content="MAA日志无变化时间超过该阈值视为超时,单位为分钟", + range=(1, 1024), + qconfig=self.config, + configItem=self.config.RunSet_AnnihilationTimeLimit, + parent=self, + ) + self.RoutineTimeLimit = SpinBoxSettingCard( + icon=FluentIcon.PAGE_RIGHT, + title="自动代理超时限制", + content="MAA日志无变化时间超过该阈值视为超时,单位为分钟", + range=(1, 1024), + qconfig=self.config, + configItem=self.config.RunSet_RoutineTimeLimit, + parent=self, + ) + self.RunTimesLimit = SpinBoxSettingCard( + icon=FluentIcon.PAGE_RIGHT, + title="代理重试次数限制", + content="若超过该次数限制仍未完成代理,视为代理失败", + range=(1, 1024), + qconfig=self.config, + configItem=self.config.RunSet_RunTimesLimit, + parent=self, + ) + self.AnnihilationWeeklyLimit = SwitchSettingCard( + icon=FluentIcon.PAGE_RIGHT, + title="每周剿灭仅执行到上限", + content="每周剿灭模式执行到上限,本周剩下时间不再执行剿灭任务", + qconfig=self.config, + configItem=self.config.RunSet_AnnihilationWeeklyLimit, + parent=self, + ) - # 同步新用户至GUI - self.update_user_info("normal") + widget = QWidget() + Layout = QVBoxLayout(widget) + Layout.addWidget(self.card_TaskTransitionMethod) + Layout.addWidget(self.card_EnhanceTask) + Layout.addWidget(self.ProxyTimesLimit) + Layout.addWidget(self.AnnihilationTimeLimit) + Layout.addWidget(self.RoutineTimeLimit) + Layout.addWidget(self.RunTimesLimit) + Layout.addWidget(self.AnnihilationWeeklyLimit) + self.viewLayout.setContentsMargins(0, 0, 0, 0) + self.viewLayout.setSpacing(0) + self.addGroupWidget(widget) - def del_user(self) -> None: - """删除选中的首位用户""" + class UserManager(HeaderCardWidget): + """用户管理父页面""" - if len(Config.running_list) > 0: - logger.warning("删除用户时调度队列未停止运行") + def __init__(self, name: str, parent=None): + super().__init__(parent) + + self.setObjectName(f"{name}_用户管理") + self.setTitle("下属用户") + self.name = name + + self.tools = CommandBar() + self.user_manager = self.UserSettingBox(self.name, self) + + # 逐个添加动作 + self.tools.addActions( + [ + Action( + FluentIcon.ADD_TO, "新建用户", triggered=self.add_user + ), + Action( + FluentIcon.REMOVE_FROM, + "删除用户", + triggered=self.del_user, + ), + ] + ) + self.tools.addSeparator() + self.tools.addActions( + [ + Action( + FluentIcon.LEFT_ARROW, + "向前移动", + triggered=self.left_user, + ), + Action( + FluentIcon.RIGHT_ARROW, + "向后移动", + triggered=self.right_user, + ), + ] + ) + + layout = QVBoxLayout() + layout.addWidget(self.tools) + layout.addWidget(self.user_manager) + self.viewLayout.addLayout(layout) + + def add_user(self): + """添加一个用户""" + + index = len(Config.member_dict[self.name]["UserData"]) + 1 + + user_config = MaaUserConfig() + user_config.load( + Config.member_dict[self.name]["Path"] + / f"UserData/用户_{index}/config.json", + user_config, + ) + user_config.save() + + Config.member_dict[self.name]["UserData"][f"用户_{index}"] = { + "Path": Config.member_dict[self.name]["Path"] + / f"UserData/用户_{index}", + "Config": user_config, + } + + self.user_manager.add_userSettingBox(index) + self.user_manager.switch_SettingBox(index) + + logger.success(f"{self.name} 用户_{index} 添加成功") MainInfoBar.push_info_bar( - "warning", "调度中心正在执行任务", "请等待或手动中止任务", 5000 + "success", "操作成功", f"{self.name} 添加 用户_{index}", 3000 ) - return None - # 获取对应的行索引 - if "简洁用户列表" in self.pivot.currentRouteKey(): - row = self.user_list_simple.currentRow() - current_numb = self.user_list_simple.rowCount() - mode = 0 - elif "高级用户列表" in self.pivot.currentRouteKey(): - row = self.user_list_beta.currentRow() - current_numb = self.user_list_beta.rowCount() - mode = 1 + def del_user(self): + """删除一个用户""" - # 判断选择合理性 - if row == -1: - logger.warning("删除用户时未选中用户") - MainInfoBar.push_info_bar( - "warning", "未选择用户", "请先选择一个用户", 5000 + name = self.user_manager.pivot.currentRouteKey() + + if name == None: + 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(), ) - return None + if choice.exec(): - # 确认待删除用户信息 - Config.cur.execute( - "SELECT * FROM adminx WHERE mode = ? AND uid = ?", - ( - self.user_mode_list[mode], - row, - ), - ) - data = Config.cur.fetchall() - choice = MessageBox( - "确认", - f"确定要删除用户 {data[0][0]} 吗?", - self.window(), - ) + self.user_manager.clear_SettingBox() - # 删除用户 - if choice.exec(): - # 删除所选用户 - Config.cur.execute( - "DELETE FROM adminx WHERE mode = ? AND uid = ?", - ( - self.user_mode_list[mode], - row, - ), - ) - Config.db.commit() - - if ( - Config.app_path - / f"config/MaaConfig/{self.name}/{self.user_mode_list[mode]}/{row}" - ).exists(): shutil.rmtree( - Config.app_path - / f"config/MaaConfig/{self.name}/{self.user_mode_list[mode]}/{row}" + Config.member_dict[self.name]["UserData"][name]["Path"] ) - # 后续用户补位 - for i in range(row + 1, current_numb): - Config.cur.execute( - "UPDATE adminx SET uid = ? WHERE mode = ? AND uid = ?", - ( - i - 1, - self.user_mode_list[mode], - i, - ), + for i in range( + int(name[3:]) + 1, + len(Config.member_dict[self.name]["UserData"]) + 1, + ): + if Config.member_dict[self.name]["UserData"][f"用户_{i}"][ + "Path" + ].exists(): + Config.member_dict[self.name]["UserData"][f"用户_{i}"][ + "Path" + ].rename( + Config.member_dict[self.name]["UserData"][ + f"用户_{i}" + ]["Path"].with_name(f"用户_{i-1}") + ) + + self.user_manager.show_SettingBox(max(int(name[3:]) - 1, 1)) + + logger.success(f"{self.name} {name} 删除成功") + MainInfoBar.push_info_bar( + "success", "操作成功", f"{self.name} 删除 {name}", 3000 ) - Config.db.commit() - if ( - Config.app_path - / f"config/MaaConfig/{self.name}/{self.user_mode_list[mode]}/{i}" - ).exists(): - ( - Config.app_path - / f"config/MaaConfig/{self.name}/{self.user_mode_list[mode]}/{i}" - ).rename( - Config.app_path - / f"config/MaaConfig/{self.name}/{self.user_mode_list[mode]}/{i}" + + def left_user(self): + """向前移动用户""" + + name = self.user_manager.pivot.currentRouteKey() + + if name == None: + 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.user_manager.clear_SettingBox() + + Config.member_dict[self.name]["UserData"][name]["Path"].rename( + Config.member_dict[self.name]["UserData"][name][ + "Path" + ].with_name("用户_0") + ) + Config.member_dict[self.name]["UserData"][f"用户_{index-1}"][ + "Path" + ].rename(Config.member_dict[self.name]["UserData"][name]["Path"]) + Config.member_dict[self.name]["UserData"][name]["Path"].with_name( + "用户_0" + ).rename( + Config.member_dict[self.name]["UserData"][f"用户_{index-1}"][ + "Path" + ] + ) + + self.user_manager.show_SettingBox(index - 1) + + logger.success(f"{self.name} {name} 前移成功") + MainInfoBar.push_info_bar( + "success", "操作成功", f"{self.name} 前移 {name}", 3000 + ) + + def right_user(self): + """向后移动用户""" + + name = self.user_manager.pivot.currentRouteKey() + + if name == None: + logger.warning("未选择用户") + MainInfoBar.push_info_bar( + "warning", "未选择用户", "请先选择一个用户", 5000 + ) + return None + + index = int(name[3:]) + + if index == len(Config.member_dict[self.name]["UserData"]): + 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.user_manager.clear_SettingBox() + + Config.member_dict[self.name]["UserData"][name]["Path"].rename( + Config.member_dict[self.name]["UserData"][name][ + "Path" + ].with_name("用户_0") + ) + Config.member_dict[self.name]["UserData"][f"用户_{index+1}"][ + "Path" + ].rename(Config.member_dict[self.name]["UserData"][name]["Path"]) + Config.member_dict[self.name]["UserData"][name]["Path"].with_name( + "用户_0" + ).rename( + Config.member_dict[self.name]["UserData"][f"用户_{index+1}"][ + "Path" + ] + ) + + self.user_manager.show_SettingBox(index + 1) + + logger.success(f"{self.name} {name} 后移成功") + MainInfoBar.push_info_bar( + "success", "操作成功", f"{self.name} 后移 {name}", 3000 + ) + + def refresh_password(self): + """刷新用户密码栏""" + + for script in self.user_manager.script_list: + + script.card_Password.setValue( + script.card_Password.qconfig.get( + script.card_Password.configItem + ) + ) + + def refresh_gameid(self): + """刷新用户密码栏""" + + for script in self.user_manager.script_list: + + script.card_GameId.reLoadOptions( + Config.gameid_dict["value"], Config.gameid_dict["text"] + ) + script.card_GameId_1.reLoadOptions( + Config.gameid_dict["value"], Config.gameid_dict["text"] + ) + script.card_GameId_2.reLoadOptions( + Config.gameid_dict["value"], Config.gameid_dict["text"] + ) + + class UserSettingBox(QWidget): + """用户管理子页面组""" + + def __init__( + self, + name: str, + parent=None, + ): + super().__init__(parent) + + self.setObjectName("用户管理") + self.name = name + + self.pivot = Pivot(self) + self.stackedWidget = QStackedWidget(self) + self.Layout = QVBoxLayout(self) + + self.script_list: List[ + MemberManager.MemberSettingBox.MaaSettingBox.UserManager.UserSettingBox.UserMemberSettingBox + ] = [] + + self.Layout.addWidget(self.pivot, 0, Qt.AlignHCenter) + self.Layout.addWidget(self.stackedWidget) + self.Layout.setContentsMargins(0, 0, 0, 0) + + self.pivot.currentItemChanged.connect( + lambda index: self.switch_SettingBox( + int(index[3:]), if_change_pivot=False + ) + ) + + self.show_SettingBox(1) + + def show_SettingBox(self, index) -> None: + """加载所有子界面""" + + Config.search_maa_user(self.name) + + for name in Config.member_dict[self.name]["UserData"].keys(): + self.add_userSettingBox(int(name[3:])) + + self.switch_SettingBox(index) + + def switch_SettingBox( + self, index: int, if_change_pivot: bool = True + ) -> None: + """切换到指定的子界面""" + + if len(Config.member_dict[self.name]["UserData"]) == 0: + return None + + if index > len(Config.member_dict[self.name]["UserData"]): + return None + + if if_change_pivot: + self.pivot.setCurrentItem( + self.script_list[index - 1].objectName() + ) + self.stackedWidget.setCurrentWidget(self.script_list[index - 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() + + def add_userSettingBox(self, uid: int) -> None: + """添加一个用户设置界面""" + + maa_setting_box = self.UserMemberSettingBox( + self.name, uid, self + ) + + self.script_list.append(maa_setting_box) + + self.stackedWidget.addWidget(self.script_list[-1]) + + self.pivot.addItem(routeKey=f"用户_{uid}", text=f"用户 {uid}") + + class UserMemberSettingBox(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]["UserData"][ + f"用户_{uid}" + ]["Config"] + self.user_path = Config.member_dict[self.name]["UserData"][ + 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_Id = LineEditSettingCard( + icon=FluentIcon.PEOPLE, + title="账号ID", + content="官服输入手机号,B服输入B站ID", + text="请输入账号ID", + qconfig=self.config, + configItem=self.config.Info_Id, + parent=self, + ) + self.card_Mode = ComboBoxSettingCard( + icon=FluentIcon.DICTIONARY, + title="用户配置模式", + content="用户信息配置模式", + texts=["简洁", "详细"], + qconfig=self.config, + configItem=self.config.Info_Mode, + parent=self, + ) + self.card_GameIdMode = ComboBoxSettingCard( + icon=FluentIcon.DICTIONARY, + title="关卡配置模式", + content="刷理智关卡号的配置模式", + texts=["固定"], + qconfig=self.config, + configItem=self.config.Info_GameIdMode, + parent=self, + ) + self.card_Server = ComboBoxSettingCard( + icon=FluentIcon.PROJECTOR, + title="服务器", + content="选择服务器类型", + texts=["官服", "B服"], + qconfig=self.config, + configItem=self.config.Info_Server, + 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.card_Annihilation = PushAndSwitchButtonSettingCard( + icon=FluentIcon.CAFE, + title="剿灭代理", + content="剿灭代理子任务相关设置", + text="设置具体配置", + qconfig=self.config, + configItem=self.config.Info_Annihilation, + parent=self, + ) + self.card_Routine = PushAndSwitchButtonSettingCard( + icon=FluentIcon.CAFE, + title="日常代理", + content="日常代理子任务相关设置", + text="设置具体配置", + qconfig=self.config, + configItem=self.config.Info_Routine, + parent=self, + ) + self.card_Infrastructure = PushAndSwitchButtonSettingCard( + icon=FluentIcon.CAFE, + title="自定义基建", + content="自定义基建相关设置项", + text="选择配置文件", + qconfig=self.config, + configItem=self.config.Info_Infrastructure, + parent=self, + ) + self.card_Password = PasswordLineEditSettingCard( + icon=FluentIcon.VPN, + title="密码", + content="仅用于用户密码记录", + text="请输入用户密码", + algorithm="AUTO", + qconfig=self.config, + configItem=self.config.Info_Password, + 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_MedicineNumb = SpinBoxSettingCard( + icon=FluentIcon.GAME, + title="吃理智药", + content="吃理智药次数,输入0以关闭", + range=(0, 1024), + qconfig=self.config, + configItem=self.config.Info_MedicineNumb, + parent=self, + ) + self.card_GameId = EditableComboBoxSettingCard( + icon=FluentIcon.GAME, + title="关卡选择", + content="按下回车以添加自定义关卡号", + value=Config.gameid_dict["value"], + texts=Config.gameid_dict["text"], + qconfig=self.config, + configItem=self.config.Info_GameId, + parent=self, + ) + self.card_GameId_1 = EditableComboBoxSettingCard( + icon=FluentIcon.GAME, + title="备选关卡-1", + content="按下回车以添加自定义关卡号", + value=Config.gameid_dict["value"], + texts=Config.gameid_dict["text"], + qconfig=self.config, + configItem=self.config.Info_GameId_1, + parent=self, + ) + self.card_GameId_2 = EditableComboBoxSettingCard( + icon=FluentIcon.GAME, + title="备选关卡-2", + content="按下回车以添加自定义关卡号", + value=Config.gameid_dict["value"], + texts=Config.gameid_dict["text"], + qconfig=self.config, + configItem=self.config.Info_GameId_2, + parent=self, ) - # 同步最终结果至GUI - self.update_user_info("normal") - - def up_user(self): - """向上移动用户""" - - if len(Config.running_list) > 0: - logger.warning("向上移动用户时调度队列未停止运行") - MainInfoBar.push_info_bar( - "warning", "调度中心正在执行任务", "请等待或手动中止任务", 5000 - ) - return None - - # 获取对应的行索引 - if "简洁用户列表" in self.pivot.currentRouteKey(): - row = self.user_list_simple.currentRow() - mode = 0 - elif "高级用户列表" in self.pivot.currentRouteKey(): - row = self.user_list_beta.currentRow() - mode = 1 - - # 判断选择合理性 - if row == -1: - logger.warning("向上移动用户时未选中用户") - MainInfoBar.push_info_bar( - "warning", "未选中用户", "请先选择一个用户", 5000 - ) - return None - - if row == 0: - logger.warning("向上移动用户时已到达最上端") - MainInfoBar.push_info_bar( - "warning", "已经是第一个用户", "无法向上移动", 5000 - ) - return None - - Config.cur.execute( - "UPDATE adminx SET uid = ? WHERE mode = ? AND uid = ?", - ( - -1, - self.user_mode_list[mode], - row, - ), - ) - Config.cur.execute( - "UPDATE adminx SET uid = ? WHERE mode = ? AND uid = ?", - ( - row, - self.user_mode_list[mode], - row - 1, - ), - ) - Config.cur.execute( - "UPDATE adminx SET uid = ? WHERE mode = ? AND uid = ?", - ( - row - 1, - self.user_mode_list[mode], - -1, - ), - ) - Config.db.commit() - - if ( - Config.app_path - / f"config/MaaConfig/{self.name}/{self.user_mode_list[mode]}/{row}" - ).exists(): - ( - Config.app_path - / f"config/MaaConfig/{self.name}/{self.user_mode_list[mode]}/{row}" - ).rename( - Config.app_path - / f"config/MaaConfig/{self.name}/{self.user_mode_list[mode]}/{-1}" - ) - if ( - Config.app_path - / f"config/MaaConfig/{self.name}/{self.user_mode_list[mode]}/{row - 1}" - ).exists(): - ( - Config.app_path - / f"config/MaaConfig/{self.name}/{self.user_mode_list[mode]}/{row - 1}" - ).rename( - Config.app_path - / f"config/MaaConfig/{self.name}/{self.user_mode_list[mode]}/{row}" - ) - if ( - Config.app_path - / f"config/MaaConfig/{self.name}/{self.user_mode_list[mode]}/{-1}" - ).exists(): - ( - Config.app_path - / f"config/MaaConfig/{self.name}/{self.user_mode_list[mode]}/{-1}" - ).rename( - Config.app_path - / f"config/MaaConfig/{self.name}/{self.user_mode_list[mode]}/{row - 1}" - ) - - self.update_user_info("normal") - if "简洁用户列表" in self.pivot.currentRouteKey(): - self.user_list_simple.selectRow(row - 1) - elif "高级用户列表" in self.pivot.currentRouteKey(): - self.user_list_beta.selectRow(row - 1) - - def down_user(self): - """向下移动用户""" - - if len(Config.running_list) > 0: - logger.warning("向下移动用户时调度队列未停止运行") - MainInfoBar.push_info_bar( - "warning", "调度中心正在执行任务", "请等待或手动中止任务", 5000 - ) - return None - - # 获取对应的行索引 - if "简洁用户列表" in self.pivot.currentRouteKey(): - row = self.user_list_simple.currentRow() - current_numb = self.user_list_simple.rowCount() - mode = 0 - elif "高级用户列表" in self.pivot.currentRouteKey(): - row = self.user_list_beta.currentRow() - current_numb = self.user_list_beta.rowCount() - mode = 1 - - # 判断选择合理性 - if row == -1: - logger.warning("向下移动用户时未选中用户") - MainInfoBar.push_info_bar( - "warning", "未选中用户", "请先选择一个用户", 5000 - ) - return None - - if row == current_numb - 1: - logger.warning("向下移动用户时已到达最下端") - MainInfoBar.push_info_bar( - "warning", "已经是最后一个用户", "无法向下移动", 5000 - ) - return None - - Config.cur.execute( - "UPDATE adminx SET uid = ? WHERE mode = ? AND uid = ?", - ( - -1, - self.user_mode_list[mode], - row, - ), - ) - Config.cur.execute( - "UPDATE adminx SET uid = ? WHERE mode = ? AND uid = ?", - ( - row, - self.user_mode_list[mode], - row + 1, - ), - ) - Config.cur.execute( - "UPDATE adminx SET uid = ? WHERE mode = ? AND uid = ?", - ( - row + 1, - self.user_mode_list[mode], - -1, - ), - ) - Config.db.commit() - - if ( - Config.app_path - / f"config/MaaConfig/{self.name}/{self.user_mode_list[mode]}/{row}" - ).exists(): - ( - Config.app_path - / f"config/MaaConfig/{self.name}/{self.user_mode_list[mode]}/{row}" - ).rename( - Config.app_path - / f"config/MaaConfig/{self.name}/{self.user_mode_list[mode]}/{-1}" - ) - if ( - Config.app_path - / f"config/MaaConfig/{self.name}/{self.user_mode_list[mode]}/{row + 1}" - ).exists(): - ( - Config.app_path - / f"config/MaaConfig/{self.name}/{self.user_mode_list[mode]}/{row + 1}" - ).rename( - Config.app_path - / f"config/MaaConfig/{self.name}/{self.user_mode_list[mode]}/{row}" - ) - if ( - Config.app_path - / f"config/MaaConfig/{self.name}/{self.user_mode_list[mode]}/{-1}" - ).exists(): - ( - Config.app_path - / f"config/MaaConfig/{self.name}/{self.user_mode_list[mode]}/{-1}" - ).rename( - Config.app_path - / f"config/MaaConfig/{self.name}/{self.user_mode_list[mode]}/{row + 1}" - ) - - self.update_user_info("normal") - if "简洁用户列表" in self.pivot.currentRouteKey(): - self.user_list_simple.selectRow(row + 1) - elif "高级用户列表" in self.pivot.currentRouteKey(): - self.user_list_beta.selectRow(row + 1) - - def switch_user(self) -> None: - """切换用户配置模式""" - - if len(Config.running_list) > 0: - logger.warning("切换用户配置模式时调度队列未停止运行") - MainInfoBar.push_info_bar( - "warning", "调度中心正在执行任务", "请等待或手动中止任务", 5000 - ) - return None - - # 获取当前用户配置模式信息 - if "简洁用户列表" in self.pivot.currentRouteKey(): - row = self.user_list_simple.currentRow() - mode = 0 - elif "高级用户列表" in self.pivot.currentRouteKey(): - row = self.user_list_beta.currentRow() - mode = 1 - - # 判断选择合理性 - if row == -1: - logger.warning("切换用户配置模式时未选中用户") - MainInfoBar.push_info_bar( - "warning", "未选中用户", "请先选择一个用户", 5000 - ) - return None - - # 确认待切换用户信息 - Config.cur.execute( - "SELECT * FROM adminx WHERE mode = ? AND uid = ?", - ( - self.user_mode_list[mode], - row, - ), - ) - data = Config.cur.fetchall() - - mode_list = ["简洁", "高级"] - choice = MessageBox( - "确认", - f"确定要将用户 {data[0][0]} 转为{mode_list[1 - mode]}配置模式吗?", - self.window(), - ) - - # 切换用户 - if choice.exec(): - Config.cur.execute("SELECT * FROM adminx WHERE True") - data = Config.cur.fetchall() - if mode == 0: - current_numb = self.user_list_simple.rowCount() - elif mode == 1: - current_numb = self.user_list_beta.rowCount() - # 切换所选用户 - other_numb = len(data) - current_numb - Config.cur.execute( - "UPDATE adminx SET mode = ?, uid = ? WHERE mode = ? AND uid = ?", - ( - self.user_mode_list[1 - mode], - other_numb, - self.user_mode_list[mode], - row, - ), - ) - Config.db.commit() - if ( - Config.app_path - / f"config/MaaConfig/{self.name}/{self.user_mode_list[mode]}/{row}" - ).exists(): - shutil.move( - Config.app_path - / f"config/MaaConfig/{self.name}/{self.user_mode_list[mode]}/{row}", - Config.app_path - / f"config/MaaConfig/{self.name}/{self.user_mode_list[1 - mode]}/{other_numb}", - ) - # 后续用户补位 - for i in range(row + 1, current_numb): - Config.cur.execute( - "UPDATE adminx SET uid = ? WHERE mode = ? AND uid = ?", - ( - i - 1, - self.user_mode_list[mode], - i, - ), - ) - Config.db.commit(), - if ( - Config.app_path - / f"config/MaaConfig/{self.name}/{self.user_mode_list[mode]}/{i}" - ).exists(): - ( - Config.app_path - / f"config/MaaConfig/{self.name}/{self.user_mode_list[mode]}/{i}" - ).rename( - Config.app_path - / f"config/MaaConfig/{self.name}/{self.user_mode_list[mode]}/{i - 1}" + self.card_UserLable = UserLableSettingCard( + icon=FluentIcon.INFO, + title="状态信息", + content="用户的代理情况汇总", + qconfig=self.config, + configItems={ + "LastProxyDate": self.config.Data_LastProxyDate, + "LastAnnihilationDate": self.config.Data_LastAnnihilationDate, + "ProxyTimes": self.config.Data_ProxyTimes, + "IfPassCheck": self.config.Data_IfPassCheck, + }, + parent=self, ) - self.update_user_info("normal") + h1_layout = QHBoxLayout() + h1_layout.addWidget(self.card_Name) + 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) + h3_layout.addWidget(self.card_RemainedDay) + h4_layout = QHBoxLayout() + h4_layout.addWidget(self.card_Annihilation) + h4_layout.addWidget(self.card_Routine) + h4_layout.addWidget(self.card_Infrastructure) + h5_layout = QHBoxLayout() + h5_layout.addWidget(self.card_Password) + h5_layout.addWidget(self.card_Notes) + h6_layout = QHBoxLayout() + h6_layout.addWidget(self.card_MedicineNumb) + h6_layout.addWidget(self.card_GameId) + h7_layout = QHBoxLayout() + h7_layout.addWidget(self.card_GameId_1) + h7_layout.addWidget(self.card_GameId_2) + Layout = QVBoxLayout() + Layout.addLayout(h1_layout) + Layout.addLayout(h2_layout) + Layout.addLayout(h3_layout) + Layout.addWidget(self.card_UserLable) + Layout.addLayout(h4_layout) + Layout.addLayout(h5_layout) + Layout.addLayout(h6_layout) + Layout.addLayout(h7_layout) -def server_date() -> str: - """获取当前的服务器日期""" + self.viewLayout.addLayout(Layout) - dt = datetime.now() - if dt.time() < datetime.min.time().replace(hour=4): - dt = dt - timedelta(days=1) - return dt.strftime("%Y-%m-%d") + self.card_Mode.comboBox.currentIndexChanged.connect( + self.switch_mode + ) + self.card_Annihilation.clicked.connect( + lambda: self.set_maa("Annihilation") + ) + self.card_Routine.clicked.connect( + lambda: self.set_maa("Routine") + ) + self.card_Infrastructure.clicked.connect( + self.set_infrastructure + ) + + self.switch_mode() + + def switch_mode(self) -> None: + + if self.config.get(self.config.Info_Mode) == "简洁": + + self.card_Routine.setVisible(False) + self.card_Server.setVisible(True) + self.card_Annihilation.button.setVisible(False) + self.card_Infrastructure.setVisible(True) + + elif self.config.get(self.config.Info_Mode) == "详细": + + self.card_Server.setVisible(False) + self.card_Infrastructure.setVisible(False) + self.card_Annihilation.button.setVisible(True) + self.card_Routine.setVisible(True) + + def set_infrastructure(self) -> None: + """配置自定义基建""" + + if self.name in Config.running_list: + logger.warning("所属脚本正在运行") + MainInfoBar.push_info_bar( + "warning", "所属脚本正在运行", "请先停止任务", 5000 + ) + return None + + file_path, _ = QFileDialog.getOpenFileName( + self, + "选择自定义基建文件", + ".", + "JSON 文件 (*.json)", + ) + if file_path != "": + (self.user_path / "Infrastructure").mkdir( + parents=True, exist_ok=True + ) + shutil.copy( + file_path, + self.user_path + / "Infrastructure/infrastructure.json", + ) + else: + logger.warning("未选择自定义基建文件") + MainInfoBar.push_info_bar( + "warning", "警告", "未选择自定义基建文件", 5000 + ) + + def set_maa(self, mode: str) -> None: + """配置MAA子配置""" + + if self.name in Config.running_list: + logger.warning("所属脚本正在运行") + MainInfoBar.push_info_bar( + "warning", "所属脚本正在运行", "请先停止任务", 5000 + ) + return None + + TaskManager.add_task( + "设置MAA_用户", + self.name, + { + "SetMaaInfo": { + "Path": self.user_path / mode, + } + }, + ) diff --git a/app/ui/queue_manager.py b/app/ui/queue_manager.py index 4e82a93..ed24bbd 100644 --- a/app/ui/queue_manager.py +++ b/app/ui/queue_manager.py @@ -34,7 +34,6 @@ from PySide6.QtWidgets import ( ) from qfluentwidgets import ( Action, - qconfig, Pivot, ScrollArea, FluentIcon, @@ -42,16 +41,14 @@ from qfluentwidgets import ( HeaderCardWidget, TextBrowser, CommandBar, - SwitchSettingCard, - ComboBoxSettingCard, ) from PySide6.QtCore import Qt from typing import List -import json -import shutil -from app.core import Config, MainInfoBar +from app.core import QueueConfig, Config, MainInfoBar from .Widget import ( + SwitchSettingCard, + ComboBoxSettingCard, LineEditSettingCard, TimeEditSettingCard, NoOptionComboBoxSettingCard, @@ -69,7 +66,7 @@ class QueueManager(QWidget): self.tools = CommandBar() - self.queue_manager = QueueSettingBox(self) + self.queue_manager = self.QueueSettingBox(self) # 逐个添加动作 self.tools.addActions( @@ -102,18 +99,25 @@ class QueueManager(QWidget): def add_setting_box(self): """添加一个调度队列""" - index = len(self.queue_manager.search_queue()) + 1 + index = len(Config.queue_dict) + 1 - qconfig.load( - Config.app_path / f"config/QueueConfig/调度队列_{index}.json", - Config.queue_config, + queue_config = QueueConfig() + queue_config.load( + Config.app_path / f"config/QueueConfig/调度队列_{index}.json", queue_config ) - Config.clear_queue_config() - Config.queue_config.save() + queue_config.save() + + Config.queue_dict[f"调度队列_{index}"] = { + "Path": Config.app_path / f"config/QueueConfig/调度队列_{index}.json", + "Config": queue_config, + } self.queue_manager.add_QueueSettingBox(index) self.queue_manager.switch_SettingBox(index) + logger.success(f"调度队列_{index} 添加成功") + MainInfoBar.push_info_bar("success", "操作成功", f"添加 调度队列_{index}", 3000) + def del_setting_box(self): """删除一个调度队列实例""" @@ -140,22 +144,21 @@ class QueueManager(QWidget): ) if choice.exec(): - queue_list = self.queue_manager.search_queue() - move_list = [_ for _ in queue_list if int(_[0][5:]) > int(name[5:])] - - index = max(int(name[5:]) - 1, 1) - self.queue_manager.clear_SettingBox() - (Config.app_path / f"config/QueueConfig/{name}.json").unlink() - for queue in move_list: - if (Config.app_path / f"config/QueueConfig/{queue[0]}.json").exists(): - (Config.app_path / f"config/QueueConfig/{queue[0]}.json").rename( - Config.app_path - / f"config/QueueConfig/调度队列_{int(queue[0][5:])-1}.json", + Config.queue_dict[name]["Path"].unlink() + for i in range(int(name[5:]) + 1, len(Config.queue_dict) + 1): + if Config.queue_dict[f"调度队列_{i}"]["Path"].exists(): + Config.queue_dict[f"调度队列_{i}"]["Path"].rename( + Config.queue_dict[f"调度队列_{i}"]["Path"].with_name( + f"调度队列_{i-1}.json" + ) ) - self.queue_manager.show_SettingBox(index) + self.queue_manager.show_SettingBox(max(int(name[5:]) - 1, 1)) + + logger.success(f"{name} 删除成功") + MainInfoBar.push_info_bar("success", "操作成功", f"删除 {name}", 3000) def left_setting_box(self): """向左移动调度队列实例""" @@ -187,19 +190,21 @@ class QueueManager(QWidget): self.queue_manager.clear_SettingBox() - (Config.app_path / f"config/QueueConfig/调度队列_{index}.json").rename( - Config.app_path / f"config/QueueConfig/调度队列_0.json", + Config.queue_dict[name]["Path"].rename( + Config.queue_dict[name]["Path"].with_name("调度队列_0.json") ) - shutil.move( - str(Config.app_path / f"config/QueueConfig/调度队列_{index-1}.json"), - str(Config.app_path / f"config/QueueConfig/调度队列_{index}.json"), + Config.queue_dict[f"调度队列_{index-1}"]["Path"].rename( + Config.queue_dict[name]["Path"] ) - (Config.app_path / f"config/QueueConfig/调度队列_0.json").rename( - Config.app_path / f"config/QueueConfig/调度队列_{index-1}.json", + Config.queue_dict[name]["Path"].with_name("调度队列_0.json").rename( + Config.queue_dict[f"调度队列_{index-1}"]["Path"] ) self.queue_manager.show_SettingBox(index - 1) + logger.success(f"{name} 左移成功") + MainInfoBar.push_info_bar("success", "操作成功", f"左移 {name}", 3000) + def right_setting_box(self): """向右移动调度队列实例""" @@ -212,10 +217,9 @@ class QueueManager(QWidget): ) return None - queue_list = self.queue_manager.search_queue() index = int(name[5:]) - if index == len(queue_list): + if index == len(Config.queue_dict): logger.warning("向右移动调度队列时已到达最右端") MainInfoBar.push_info_bar( "warning", "已经是最后一个调度队列", "无法向右移动", 5000 @@ -231,439 +235,485 @@ class QueueManager(QWidget): self.queue_manager.clear_SettingBox() - (Config.app_path / f"config/QueueConfig/调度队列_{index}.json").rename( - Config.app_path / f"config/QueueConfig/调度队列_0.json", + Config.queue_dict[name]["Path"].rename( + Config.queue_dict[name]["Path"].with_name("调度队列_0.json") ) - (Config.app_path / f"config/QueueConfig/调度队列_{index+1}.json").rename( - Config.app_path / f"config/QueueConfig/调度队列_{index}.json", + Config.queue_dict[f"调度队列_{index+1}"]["Path"].rename( + Config.queue_dict[name]["Path"] ) - (Config.app_path / f"config/QueueConfig/调度队列_0.json").rename( - Config.app_path / f"config/QueueConfig/调度队列_{index+1}.json", + Config.queue_dict[name]["Path"].with_name("调度队列_0.json").rename( + Config.queue_dict[f"调度队列_{index+1}"]["Path"] ) self.queue_manager.show_SettingBox(index + 1) - def refresh(self): - """刷新调度队列界面""" + logger.success(f"{name} 右移成功") + MainInfoBar.push_info_bar("success", "操作成功", f"右移 {name}", 3000) - if len(self.queue_manager.search_queue()) == 0: - index = 0 - else: - index = int(self.queue_manager.pivot.currentRouteKey()[5:]) - self.queue_manager.clear_SettingBox() - self.queue_manager.show_SettingBox(index) + def reload_member_name(self): + """刷新调度队列成员""" + member_list = [ + ["禁用"] + [_ for _ in Config.member_dict.keys()], + ["未启用"] + + [ + ( + k + if v["Config"].get(v["Config"].MaaSet_Name) == "" + else f"{k} - {v["Config"].get(v["Config"].MaaSet_Name)}" + ) + for k, v in Config.member_dict.items() + ], + ] + for script in self.queue_manager.script_list: -class QueueSettingBox(QWidget): + script.task.card_Member_1.reLoadOptions( + value=member_list[0], texts=member_list[1] + ) + script.task.card_Member_2.reLoadOptions( + value=member_list[0], texts=member_list[1] + ) + script.task.card_Member_3.reLoadOptions( + value=member_list[0], texts=member_list[1] + ) + script.task.card_Member_4.reLoadOptions( + value=member_list[0], texts=member_list[1] + ) + script.task.card_Member_5.reLoadOptions( + value=member_list[0], texts=member_list[1] + ) + script.task.card_Member_6.reLoadOptions( + value=member_list[0], texts=member_list[1] + ) + script.task.card_Member_7.reLoadOptions( + value=member_list[0], texts=member_list[1] + ) + script.task.card_Member_8.reLoadOptions( + value=member_list[0], texts=member_list[1] + ) + script.task.card_Member_9.reLoadOptions( + value=member_list[0], texts=member_list[1] + ) + script.task.card_Member_10.reLoadOptions( + value=member_list[0], texts=member_list[1] + ) - def __init__(self, parent=None): - super().__init__(parent) - - self.setObjectName("调度队列管理") - - self.pivot = Pivot(self) - self.stackedWidget = QStackedWidget(self) - self.Layout = QVBoxLayout(self) - - self.script_list: List[QueueMemberSettingBox] = [] - - self.Layout.addWidget(self.pivot, 0, Qt.AlignHCenter) - self.Layout.addWidget(self.stackedWidget) - self.Layout.setContentsMargins(0, 0, 0, 0) - - self.pivot.currentItemChanged.connect( - lambda index: self.switch_SettingBox(int(index[5:]), if_change_pivot=False) - ) - - self.show_SettingBox(1) - - def show_SettingBox(self, index) -> None: - """加载所有子界面""" - - queue_list = self.search_queue() - - qconfig.load( - Config.app_path / "config/临时.json", - Config.queue_config, - ) - Config.clear_queue_config() - for queue in queue_list: - self.add_QueueSettingBox(int(queue[0][5:])) - if (Config.app_path / "config/临时.json").exists(): - (Config.app_path / "config/临时.json").unlink() - - self.switch_SettingBox(index) - - def switch_SettingBox(self, index: int, if_change_pivot: bool = True) -> None: - """切换到指定的子界面""" - - queue_list = self.search_queue() - - if len(queue_list) == 0: - return None - - if index > len(queue_list): - return None - - qconfig.load( - Config.app_path - / f"config/QueueConfig/{self.script_list[index-1].objectName()}.json", - Config.queue_config, - ) - - if if_change_pivot: - self.pivot.setCurrentItem(self.script_list[index - 1].objectName()) - self.stackedWidget.setCurrentWidget(self.script_list[index - 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() - qconfig.load( - Config.app_path / "config/临时.json", - Config.queue_config, - ) - Config.clear_queue_config() - if (Config.app_path / "config/临时.json").exists(): - (Config.app_path / "config/临时.json").unlink() - - def add_QueueSettingBox(self, uid: int) -> None: - """添加一个调度队列设置界面""" - - maa_setting_box = QueueMemberSettingBox(uid, self) - - self.script_list.append(maa_setting_box) - - self.stackedWidget.addWidget(self.script_list[-1]) - - self.pivot.addItem(routeKey=f"调度队列_{uid}", text=f"调度队列 {uid}") - - def search_queue(self) -> list: - """搜索所有调度队列实例""" - - queue_list = [] - - if (Config.app_path / "config/QueueConfig").exists(): - for json_file in (Config.app_path / "config/QueueConfig").glob("*.json"): - with json_file.open("r", encoding="utf-8") as f: - info = json.load(f) - queue_list.append([json_file.stem, info["QueueSet"]["Name"]]) - - return queue_list - - -class QueueMemberSettingBox(QWidget): - - def __init__(self, uid: int, parent=None): - super().__init__(parent) - - self.setObjectName(f"调度队列_{uid}") - - layout = QVBoxLayout() - - scrollArea = ScrollArea() - scrollArea.setWidgetResizable(True) - - content_widget = QWidget() - content_layout = QVBoxLayout(content_widget) - - self.queue_set = self.QueueSetSettingCard(self) - self.time = self.TimeSettingCard(self) - self.task = self.TaskSettingCard(self) - self.history = self.HistoryCard(self, f"调度队列_{uid}") - - content_layout.addWidget(self.queue_set) - content_layout.addWidget(self.time) - content_layout.addWidget(self.task) - content_layout.addWidget(self.history) - content_layout.addStretch(1) - - scrollArea.setWidget(content_widget) - - layout.addWidget(scrollArea) - - self.setLayout(layout) - - class QueueSetSettingCard(HeaderCardWidget): + class QueueSettingBox(QWidget): def __init__(self, parent=None): super().__init__(parent) - self.setTitle("队列设置") + self.setObjectName("调度队列管理") - Layout = QVBoxLayout() + self.pivot = Pivot(self) + self.stackedWidget = QStackedWidget(self) + self.Layout = QVBoxLayout(self) - self.card_Name = LineEditSettingCard( - "请输入调度队列名称", - FluentIcon.EDIT, - "调度队列名称", - "用于标识调度队列的名称", - Config.queue_config.queueSet_Name, - ) - self.card_Enable = SwitchSettingCard( - FluentIcon.HOME, - "状态", - "调度队列状态", - Config.queue_config.queueSet_Enabled, - ) - self.card_AfterAccomplish = ComboBoxSettingCard( - configItem=Config.queue_config.queueSet_AfterAccomplish, - icon=FluentIcon.POWER_BUTTON, - title="调度队列结束后", - content="选择调度队列结束后的操作", - texts=[ - "无动作", - "退出AUTO_MAA", - "睡眠(win系统需禁用休眠)", - "休眠", - "关机", - ], + self.script_list: List[ + QueueManager.QueueSettingBox.QueueMemberSettingBox + ] = [] + + self.Layout.addWidget(self.pivot, 0, Qt.AlignHCenter) + self.Layout.addWidget(self.stackedWidget) + self.Layout.setContentsMargins(0, 0, 0, 0) + + self.pivot.currentItemChanged.connect( + lambda index: self.switch_SettingBox( + int(index[5:]), if_change_pivot=False + ) ) - Layout.addWidget(self.card_Name) - Layout.addWidget(self.card_Enable) - Layout.addWidget(self.card_AfterAccomplish) + self.show_SettingBox(1) - self.viewLayout.addLayout(Layout) + def show_SettingBox(self, index) -> None: + """加载所有子界面""" - class TimeSettingCard(HeaderCardWidget): + Config.search_queue() - def __init__(self, parent=None): - super().__init__(parent) + for name in Config.queue_dict.keys(): + self.add_QueueSettingBox(int(name[5:])) - self.setTitle("定时设置") + self.switch_SettingBox(index) - widget_1 = QWidget() - Layout_1 = QVBoxLayout(widget_1) - widget_2 = QWidget() - Layout_2 = QVBoxLayout(widget_2) - Layout = QHBoxLayout() + def switch_SettingBox(self, index: int, if_change_pivot: bool = True) -> None: + """切换到指定的子界面""" - self.card_Time_0 = TimeEditSettingCard( - FluentIcon.STOP_WATCH, - "定时 1", - "", - Config.queue_config.time_TimeEnabled_0, - Config.queue_config.time_TimeSet_0, - ) - self.card_Time_1 = TimeEditSettingCard( - FluentIcon.STOP_WATCH, - "定时 2", - "", - Config.queue_config.time_TimeEnabled_1, - Config.queue_config.time_TimeSet_1, - ) - self.card_Time_2 = TimeEditSettingCard( - FluentIcon.STOP_WATCH, - "定时 3", - "", - Config.queue_config.time_TimeEnabled_2, - Config.queue_config.time_TimeSet_2, - ) - self.card_Time_3 = TimeEditSettingCard( - FluentIcon.STOP_WATCH, - "定时 4", - "", - Config.queue_config.time_TimeEnabled_3, - Config.queue_config.time_TimeSet_3, - ) - self.card_Time_4 = TimeEditSettingCard( - FluentIcon.STOP_WATCH, - "定时 5", - "", - Config.queue_config.time_TimeEnabled_4, - Config.queue_config.time_TimeSet_4, - ) - self.card_Time_5 = TimeEditSettingCard( - FluentIcon.STOP_WATCH, - "定时 6", - "", - Config.queue_config.time_TimeEnabled_5, - Config.queue_config.time_TimeSet_5, - ) - self.card_Time_6 = TimeEditSettingCard( - FluentIcon.STOP_WATCH, - "定时 7", - "", - Config.queue_config.time_TimeEnabled_6, - Config.queue_config.time_TimeSet_6, - ) - self.card_Time_7 = TimeEditSettingCard( - FluentIcon.STOP_WATCH, - "定时 8", - "", - Config.queue_config.time_TimeEnabled_7, - Config.queue_config.time_TimeSet_7, - ) - self.card_Time_8 = TimeEditSettingCard( - FluentIcon.STOP_WATCH, - "定时 9", - "", - Config.queue_config.time_TimeEnabled_8, - Config.queue_config.time_TimeSet_8, - ) - self.card_Time_9 = TimeEditSettingCard( - FluentIcon.STOP_WATCH, - "定时 10", - "", - Config.queue_config.time_TimeEnabled_9, - Config.queue_config.time_TimeSet_9, - ) + if len(Config.queue_dict) == 0: + return None - Layout_1.addWidget(self.card_Time_0) - Layout_1.addWidget(self.card_Time_1) - Layout_1.addWidget(self.card_Time_2) - Layout_1.addWidget(self.card_Time_3) - Layout_1.addWidget(self.card_Time_4) - Layout_2.addWidget(self.card_Time_5) - Layout_2.addWidget(self.card_Time_6) - Layout_2.addWidget(self.card_Time_7) - Layout_2.addWidget(self.card_Time_8) - Layout_2.addWidget(self.card_Time_9) - Layout.addWidget(widget_1) - Layout.addWidget(widget_2) + if index > len(Config.queue_dict): + return None - self.viewLayout.addLayout(Layout) + if if_change_pivot: + self.pivot.setCurrentItem(self.script_list[index - 1].objectName()) + self.stackedWidget.setCurrentWidget(self.script_list[index - 1]) - class TaskSettingCard(HeaderCardWidget): + def clear_SettingBox(self) -> None: + """清空所有子界面""" - def __init__(self, parent=None): - super().__init__(parent) + for sub_interface in self.script_list: + self.stackedWidget.removeWidget(sub_interface) + sub_interface.deleteLater() + self.script_list.clear() + self.pivot.clear() - self.setTitle("任务队列") + def add_QueueSettingBox(self, uid: int) -> None: + """添加一个调度队列设置界面""" - Layout = QVBoxLayout() + maa_setting_box = self.QueueMemberSettingBox(uid, self) - member_list = self.search_member() + self.script_list.append(maa_setting_box) - self.card_Member_1 = NoOptionComboBoxSettingCard( - Config.queue_config.queue_Member_1, - FluentIcon.APPLICATION, - "任务实例 1", - "第一个调起的脚本任务实例", - member_list[0], - member_list[1], - ) - self.card_Member_2 = NoOptionComboBoxSettingCard( - Config.queue_config.queue_Member_2, - FluentIcon.APPLICATION, - "任务实例 2", - "第二个调起的脚本任务实例", - member_list[0], - member_list[1], - ) - self.card_Member_3 = NoOptionComboBoxSettingCard( - Config.queue_config.queue_Member_3, - FluentIcon.APPLICATION, - "任务实例 3", - "第三个调起的脚本任务实例", - member_list[0], - member_list[1], - ) - self.card_Member_4 = NoOptionComboBoxSettingCard( - Config.queue_config.queue_Member_4, - FluentIcon.APPLICATION, - "任务实例 4", - "第四个调起的脚本任务实例", - member_list[0], - member_list[1], - ) - self.card_Member_5 = NoOptionComboBoxSettingCard( - Config.queue_config.queue_Member_5, - FluentIcon.APPLICATION, - "任务实例 5", - "第五个调起的脚本任务实例", - member_list[0], - member_list[1], - ) - self.card_Member_6 = NoOptionComboBoxSettingCard( - Config.queue_config.queue_Member_6, - FluentIcon.APPLICATION, - "任务实例 6", - "第六个调起的脚本任务实例", - member_list[0], - member_list[1], - ) - self.card_Member_7 = NoOptionComboBoxSettingCard( - Config.queue_config.queue_Member_7, - FluentIcon.APPLICATION, - "任务实例 7", - "第七个调起的脚本任务实例", - member_list[0], - member_list[1], - ) - self.card_Member_8 = NoOptionComboBoxSettingCard( - Config.queue_config.queue_Member_8, - FluentIcon.APPLICATION, - "任务实例 8", - "第八个调起的脚本任务实例", - member_list[0], - member_list[1], - ) - self.card_Member_9 = NoOptionComboBoxSettingCard( - Config.queue_config.queue_Member_9, - FluentIcon.APPLICATION, - "任务实例 9", - "第九个调起的脚本任务实例", - member_list[0], - member_list[1], - ) - self.card_Member_10 = NoOptionComboBoxSettingCard( - Config.queue_config.queue_Member_10, - FluentIcon.APPLICATION, - "任务实例 10", - "第十个调起的脚本任务实例", - member_list[0], - member_list[1], - ) + self.stackedWidget.addWidget(self.script_list[-1]) - Layout.addWidget(self.card_Member_1) - Layout.addWidget(self.card_Member_2) - Layout.addWidget(self.card_Member_3) - Layout.addWidget(self.card_Member_4) - Layout.addWidget(self.card_Member_5) - Layout.addWidget(self.card_Member_6) - Layout.addWidget(self.card_Member_7) - Layout.addWidget(self.card_Member_8) - Layout.addWidget(self.card_Member_9) - Layout.addWidget(self.card_Member_10) + self.pivot.addItem(routeKey=f"调度队列_{uid}", text=f"调度队列 {uid}") - self.viewLayout.addLayout(Layout) + class QueueMemberSettingBox(QWidget): - def search_member(self) -> list: - """搜索所有脚本实例""" + def __init__(self, uid: int, parent=None): + super().__init__(parent) - member_list_name = ["禁用"] - member_list_text = ["未启用"] + self.setObjectName(f"调度队列_{uid}") + self.config = Config.queue_dict[f"调度队列_{uid}"]["Config"] - if (Config.app_path / "config/MaaConfig").exists(): - for subdir in (Config.app_path / "config/MaaConfig").iterdir(): - if subdir.is_dir(): - member_list_name.append(subdir.name) - with (subdir / "config.json").open("r", encoding="utf-8") as f: - info = json.load(f) - if info["MaaSet"]["Name"] != "": - member_list_text.append( - f"{subdir.name} - {info["MaaSet"]["Name"]}" + layout = QVBoxLayout() + + scrollArea = ScrollArea() + scrollArea.setWidgetResizable(True) + + content_widget = QWidget() + content_layout = QVBoxLayout(content_widget) + + self.queue_set = self.QueueSetSettingCard(self.config, self) + self.time = self.TimeSettingCard(self.config, self) + self.task = self.TaskSettingCard(self.config, self) + self.history = self.HistoryCard(f"调度队列_{uid}", self) + + content_layout.addWidget(self.queue_set) + content_layout.addWidget(self.time) + content_layout.addWidget(self.task) + content_layout.addWidget(self.history) + content_layout.addStretch(1) + + scrollArea.setWidget(content_widget) + + layout.addWidget(scrollArea) + + self.setLayout(layout) + + class QueueSetSettingCard(HeaderCardWidget): + + def __init__(self, config: QueueConfig, parent=None): + super().__init__(parent) + + self.setTitle("队列设置") + self.config = config + + self.card_Name = LineEditSettingCard( + icon=FluentIcon.EDIT, + title="调度队列名称", + content="用于标识调度队列的名称", + text="请输入调度队列名称", + qconfig=self.config, + configItem=self.config.queueSet_Name, + parent=self, + ) + self.card_Enable = SwitchSettingCard( + icon=FluentIcon.HOME, + title="状态", + content="调度队列状态", + qconfig=self.config, + configItem=self.config.queueSet_Enabled, + parent=self, + ) + self.card_AfterAccomplish = ComboBoxSettingCard( + icon=FluentIcon.POWER_BUTTON, + title="调度队列结束后", + content="选择调度队列结束后的操作", + texts=[ + "无动作", + "退出AUTO_MAA", + "睡眠(win系统需禁用休眠)", + "休眠", + "关机", + ], + qconfig=self.config, + configItem=self.config.queueSet_AfterAccomplish, + parent=self, + ) + + Layout = QVBoxLayout() + Layout.addWidget(self.card_Name) + Layout.addWidget(self.card_Enable) + Layout.addWidget(self.card_AfterAccomplish) + + self.viewLayout.addLayout(Layout) + + class TimeSettingCard(HeaderCardWidget): + + def __init__(self, config: QueueConfig, parent=None): + super().__init__(parent) + + self.setTitle("定时设置") + self.config = config + + widget_1 = QWidget() + Layout_1 = QVBoxLayout(widget_1) + widget_2 = QWidget() + Layout_2 = QVBoxLayout(widget_2) + Layout = QHBoxLayout() + + self.card_Time_0 = TimeEditSettingCard( + icon=FluentIcon.STOP_WATCH, + title="定时 1", + content=None, + qconfig=self.config, + configItem_bool=self.config.time_TimeEnabled_0, + configItem_time=self.config.time_TimeSet_0, + parent=self, + ) + self.card_Time_1 = TimeEditSettingCard( + icon=FluentIcon.STOP_WATCH, + title="定时 2", + content=None, + qconfig=self.config, + configItem_bool=self.config.time_TimeEnabled_1, + configItem_time=self.config.time_TimeSet_1, + parent=self, + ) + self.card_Time_2 = TimeEditSettingCard( + icon=FluentIcon.STOP_WATCH, + title="定时 3", + content=None, + qconfig=self.config, + configItem_bool=self.config.time_TimeEnabled_2, + configItem_time=self.config.time_TimeSet_2, + parent=self, + ) + self.card_Time_3 = TimeEditSettingCard( + icon=FluentIcon.STOP_WATCH, + title="定时 4", + content=None, + qconfig=self.config, + configItem_bool=self.config.time_TimeEnabled_3, + configItem_time=self.config.time_TimeSet_3, + parent=self, + ) + self.card_Time_4 = TimeEditSettingCard( + icon=FluentIcon.STOP_WATCH, + title="定时 5", + content=None, + qconfig=self.config, + configItem_bool=self.config.time_TimeEnabled_4, + configItem_time=self.config.time_TimeSet_4, + parent=self, + ) + self.card_Time_5 = TimeEditSettingCard( + icon=FluentIcon.STOP_WATCH, + title="定时 6", + content=None, + qconfig=self.config, + configItem_bool=self.config.time_TimeEnabled_5, + configItem_time=self.config.time_TimeSet_5, + parent=self, + ) + self.card_Time_6 = TimeEditSettingCard( + icon=FluentIcon.STOP_WATCH, + title="定时 7", + content=None, + qconfig=self.config, + configItem_bool=self.config.time_TimeEnabled_6, + configItem_time=self.config.time_TimeSet_6, + parent=self, + ) + self.card_Time_7 = TimeEditSettingCard( + icon=FluentIcon.STOP_WATCH, + title="定时 8", + content=None, + qconfig=self.config, + configItem_bool=self.config.time_TimeEnabled_7, + configItem_time=self.config.time_TimeSet_7, + parent=self, + ) + self.card_Time_8 = TimeEditSettingCard( + icon=FluentIcon.STOP_WATCH, + title="定时 9", + content=None, + qconfig=self.config, + configItem_bool=self.config.time_TimeEnabled_8, + configItem_time=self.config.time_TimeSet_8, + parent=self, + ) + self.card_Time_9 = TimeEditSettingCard( + icon=FluentIcon.STOP_WATCH, + title="定时 10", + content=None, + qconfig=self.config, + configItem_bool=self.config.time_TimeEnabled_9, + configItem_time=self.config.time_TimeSet_9, + parent=self, + ) + + Layout_1.addWidget(self.card_Time_0) + Layout_1.addWidget(self.card_Time_1) + Layout_1.addWidget(self.card_Time_2) + Layout_1.addWidget(self.card_Time_3) + Layout_1.addWidget(self.card_Time_4) + Layout_2.addWidget(self.card_Time_5) + Layout_2.addWidget(self.card_Time_6) + Layout_2.addWidget(self.card_Time_7) + Layout_2.addWidget(self.card_Time_8) + Layout_2.addWidget(self.card_Time_9) + Layout.addWidget(widget_1) + Layout.addWidget(widget_2) + + self.viewLayout.addLayout(Layout) + + class TaskSettingCard(HeaderCardWidget): + + def __init__(self, config: QueueConfig, parent=None): + super().__init__(parent) + + self.setTitle("任务队列") + self.config = config + + member_list = [ + ["禁用"] + [_ for _ in Config.member_dict.keys()], + ["未启用"] + + [ + ( + k + if v["Config"].get(v["Config"].MaaSet_Name) == "" + else f"{k} - {v["Config"].get(v["Config"].MaaSet_Name)}" ) - else: - member_list_text.append(subdir.name) + for k, v in Config.member_dict.items() + ], + ] - return [member_list_name, member_list_text] + self.card_Member_1 = NoOptionComboBoxSettingCard( + icon=FluentIcon.APPLICATION, + title="任务实例 1", + content="第一个调起的脚本任务实例", + value=member_list[0], + texts=member_list[1], + qconfig=self.config, + configItem=self.config.queue_Member_1, + parent=self, + ) + self.card_Member_2 = NoOptionComboBoxSettingCard( + icon=FluentIcon.APPLICATION, + title="任务实例 2", + content="第二个调起的脚本任务实例", + value=member_list[0], + texts=member_list[1], + qconfig=self.config, + configItem=self.config.queue_Member_2, + parent=self, + ) + self.card_Member_3 = NoOptionComboBoxSettingCard( + icon=FluentIcon.APPLICATION, + title="任务实例 3", + content="第三个调起的脚本任务实例", + value=member_list[0], + texts=member_list[1], + qconfig=self.config, + configItem=self.config.queue_Member_3, + parent=self, + ) + self.card_Member_4 = NoOptionComboBoxSettingCard( + icon=FluentIcon.APPLICATION, + title="任务实例 4", + content="第四个调起的脚本任务实例", + value=member_list[0], + texts=member_list[1], + qconfig=self.config, + configItem=self.config.queue_Member_4, + parent=self, + ) + self.card_Member_5 = NoOptionComboBoxSettingCard( + icon=FluentIcon.APPLICATION, + title="任务实例 5", + content="第五个调起的脚本任务实例", + value=member_list[0], + texts=member_list[1], + qconfig=self.config, + configItem=self.config.queue_Member_5, + parent=self, + ) + self.card_Member_6 = NoOptionComboBoxSettingCard( + icon=FluentIcon.APPLICATION, + title="任务实例 6", + content="第六个调起的脚本任务实例", + value=member_list[0], + texts=member_list[1], + qconfig=self.config, + configItem=self.config.queue_Member_6, + parent=self, + ) + self.card_Member_7 = NoOptionComboBoxSettingCard( + icon=FluentIcon.APPLICATION, + title="任务实例 7", + content="第七个调起的脚本任务实例", + value=member_list[0], + texts=member_list[1], + qconfig=self.config, + configItem=self.config.queue_Member_7, + parent=self, + ) + self.card_Member_8 = NoOptionComboBoxSettingCard( + icon=FluentIcon.APPLICATION, + title="任务实例 8", + content="第八个调起的脚本任务实例", + value=member_list[0], + texts=member_list[1], + qconfig=self.config, + configItem=self.config.queue_Member_8, + parent=self, + ) + self.card_Member_9 = NoOptionComboBoxSettingCard( + icon=FluentIcon.APPLICATION, + title="任务实例 9", + content="第九个调起的脚本任务实例", + value=member_list[0], + texts=member_list[1], + qconfig=self.config, + configItem=self.config.queue_Member_9, + parent=self, + ) + self.card_Member_10 = NoOptionComboBoxSettingCard( + icon=FluentIcon.APPLICATION, + title="任务实例 10", + content="第十个调起的脚本任务实例", + value=member_list[0], + texts=member_list[1], + qconfig=self.config, + configItem=self.config.queue_Member_10, + parent=self, + ) - class HistoryCard(HeaderCardWidget): + Layout = QVBoxLayout() + Layout.addWidget(self.card_Member_1) + Layout.addWidget(self.card_Member_2) + Layout.addWidget(self.card_Member_3) + Layout.addWidget(self.card_Member_4) + Layout.addWidget(self.card_Member_5) + Layout.addWidget(self.card_Member_6) + Layout.addWidget(self.card_Member_7) + Layout.addWidget(self.card_Member_8) + Layout.addWidget(self.card_Member_9) + Layout.addWidget(self.card_Member_10) - def __init__(self, parent=None, name: str = None): - super().__init__(parent) - self.setTitle("历史运行记录") + self.viewLayout.addLayout(Layout) - self.text = TextBrowser() - self.text.setMinimumHeight(300) - history = Config.get_history(name) - self.text.setPlainText(history["History"]) + class HistoryCard(HeaderCardWidget): - self.viewLayout.addWidget(self.text) + def __init__(self, name: str, parent=None): + super().__init__(parent) + self.setTitle("历史运行记录") + + self.text = TextBrowser() + self.text.setMinimumHeight(300) + history = Config.get_history(name) + self.text.setPlainText(history["History"]) + + self.viewLayout.addWidget(self.text) diff --git a/app/ui/setting.py b/app/ui/setting.py index b74f1f8..6e3a00b 100644 --- a/app/ui/setting.py +++ b/app/ui/setting.py @@ -31,6 +31,7 @@ from PySide6.QtWidgets import ( QApplication, QVBoxLayout, ) +from PySide6.QtCore import Qt from qfluentwidgets import ( ScrollArea, FluentIcon, @@ -38,13 +39,12 @@ from qfluentwidgets import ( Dialog, HyperlinkCard, HeaderCardWidget, - SwitchSettingCard, - RangeSettingCard, ExpandGroupSettingCard, PushSettingCard, - ComboBoxSettingCard, + HyperlinkButton, ) import os +import re import json import time import shutil @@ -56,8 +56,10 @@ from typing import Dict, List, Union from app.core import Config, MainInfoBar from app.services import Crypto, System, Notify -from app.utils import DownloadManager from .Widget import ( + SwitchSettingCard, + RangeSettingCard, + ComboBoxSettingCard, LineEditMessageBox, LineEditSettingCard, PasswordLineEditSettingCard, @@ -111,7 +113,7 @@ class Setting(QWidget): def agree_bilibili(self) -> None: """授权bilibili游戏隐私政策""" - if Config.global_config.get(Config.global_config.function_IfAgreeBilibili): + if Config.get(Config.function_IfAgreeBilibili): choice = MessageBox( "授权声明", @@ -124,9 +126,7 @@ class Setting(QWidget): "success", "操作成功", "已确认授权bilibili游戏隐私政策", 3000 ) else: - Config.global_config.set( - Config.global_config.function_IfAgreeBilibili, False - ) + Config.set(Config.function_IfAgreeBilibili, False) else: logger.info("取消授权bilibili游戏隐私政策") @@ -141,7 +141,7 @@ class Setting(QWidget): Path(os.getenv("APPDATA")) / "Netease/MuMuPlayer-12.0/data/startupImage" ) - if Config.global_config.get(Config.global_config.function_IfSkipMumuSplashAds): + if Config.get(Config.function_IfSkipMumuSplashAds): choice = MessageBox( "风险声明", @@ -160,9 +160,7 @@ class Setting(QWidget): "success", "操作成功", "已开启跳过MuMu启动广告功能", 3000 ) else: - Config.global_config.set( - Config.global_config.function_IfSkipMumuSplashAds, False - ) + Config.set(Config.function_IfSkipMumuSplashAds, False) else: @@ -267,28 +265,17 @@ class Setting(QWidget): def check_update(self) -> None: """检查版本更新,调起文件下载进程""" - # 从本地版本信息文件获取当前版本信息 - with Config.version_path.open(mode="r", encoding="utf-8") as f: - version_current: Dict[ - str, Union[str, Dict[str, Union[str, Dict[str, List[str]]]]] - ] = json.load(f) - main_version_current = list(map(int, Config.VERSION.split("."))) - updater_version_current = list( - map(int, version_current["updater_version"].split(".")) - ) - # 检查更新器是否存在 - if not (Config.app_path / "Updater.exe").exists(): - updater_version_current = [0, 0, 0, 0] + current_version = list(map(int, Config.VERSION.split("."))) # 从远程服务器获取最新版本信息 for _ in range(3): try: response = requests.get( - f"https://gitee.com/DLmaster_361/AUTO_MAA/raw/{Config.global_config.get(Config.global_config.update_UpdateType)}/resources/version.json" + f"https://mirrorchyan.com/api/resources/AUTO_MAA/latest?current_version={version_text(current_version)}&cdk={Crypto.win_decryptor(Config.get(Config.update_MirrorChyanCDK))}&channel={Config.get(Config.update_UpdateType)}" + ) + version_info: Dict[str, Union[int, str, Dict[str, str]]] = ( + response.json() ) - version_remote: Dict[ - str, Union[str, Dict[str, Union[str, Dict[str, List[str]]]]] - ] = response.json() break except Exception as e: err = e @@ -304,52 +291,77 @@ class Setting(QWidget): if choice.exec(): return None - main_version_remote = list(map(int, version_remote["main_version"].split("."))) - updater_version_remote = list( - map(int, version_remote["updater_version"].split(".")) - ) - remote_proxy_list = version_remote["proxy_list"] - Config.global_config.set( - Config.global_config.update_ProxyUrlList, - list( - set( - Config.global_config.get(Config.global_config.update_ProxyUrlList) - + remote_proxy_list + if version_info["code"] != 0: + + logger.error(f"获取版本信息时出错:{version_info["msg"]}") + + error_remark_dict = { + 1001: "获取版本信息的URL参数不正确", + 7001: "填入的 CDK 已过期", + 7002: "填入的 CDK 错误", + 7003: "填入的 CDK 今日下载次数已达上限", + 7004: "填入的 CDK 类型和待下载的资源不匹配", + 7005: "填入的 CDK 不合法", + 8001: "对应架构和系统下的资源不存在", + 8002: "错误的系统参数", + 8003: "错误的架构参数", + 8004: "错误的更新通道参数", + 1: version_info["msg"], + } + + if version_info["code"] in error_remark_dict: + MainInfoBar.push_info_bar( + "error", + "获取版本信息时出错", + error_remark_dict[version_info["code"]], + -1, ) - ), + else: + MainInfoBar.push_info_bar( + "error", + "获取版本信息时出错", + "意料之外的错误,请及时联系项目组以获取来自 Mirror 酱的技术支持", + -1, + ) + + return None + + remote_version = list( + map( + int, + version_info["data"]["version_name"][1:] + .replace("-beta", "") + .split("."), + ) + ) + + version_info_json: Dict[str, Dict[str, str]] = json.loads( + re.sub( + r"^$", + r"\1", + version_info["data"]["release_note"].splitlines()[0], + ) ) # 有版本更新 - if (main_version_remote > main_version_current) or ( - updater_version_remote > updater_version_current - ): + if remote_version > current_version: # 生成版本更新信息 - if main_version_remote > main_version_current: - main_version_info = f"## 主程序:{version_text(main_version_current)} --> {version_text(main_version_remote)}\n\n" - else: - main_version_info = ( - f"## 主程序:{version_text(main_version_current)}\n\n" - ) - if updater_version_remote > updater_version_current: - updater_version_info = f"## 更新器:{version_text(updater_version_current)} --> {version_text(updater_version_remote)}\n\n" - else: - updater_version_info = ( - f"## 更新器:{version_text(updater_version_current)}\n\n" - ) + main_version_info = f"## 主程序:{version_text(current_version)} --> {version_text(remote_version)}" + update_version_info = {} all_version_info = {} for v_i in [ info - for version, info in version_remote["version_info"].items() - if list(map(int, version.split("."))) > main_version_current + for version, info in version_info_json.items() + if list(map(int, version.split("."))) > current_version ]: for key, value in v_i.items(): if key in update_version_info: update_version_info[key] += value.copy() else: update_version_info[key] = value.copy() - for v_i in version_remote["version_info"].values(): + for v_i in update_version_info.values(): for key, value in v_i.items(): if key in all_version_info: all_version_info[key] += value.copy() @@ -357,69 +369,48 @@ class Setting(QWidget): all_version_info[key] = value.copy() version_info = { - "更新总览": f"{main_version_info}{updater_version_info}{version_info_markdown(update_version_info)}", + "更新总览": f"{main_version_info}\n\n{version_info_markdown(update_version_info)}", "ALL~版本信息": version_info_markdown(all_version_info), **{ version_text(list(map(int, k.split(".")))): version_info_markdown(v) - for k, v in version_remote["version_info"].items() + for k, v in update_version_info.items() }, } # 询问是否开始版本更新 choice = NoticeMessageBox(self.window(), "版本更新", version_info) - if not choice.exec(): - return None + if choice.exec(): - # 更新更新器 - if updater_version_remote > updater_version_current: - # 创建更新进程 - self.updater = DownloadManager( - Config.app_path, - "AUTO_MAA更新器", - main_version_remote, - updater_version_remote, - { - "proxy_list": Config.global_config.get( - Config.global_config.update_ProxyUrlList - ), - "download_dict": version_remote["download_dict"], - "thread_numb": Config.global_config.get( - Config.global_config.update_ThreadNumb - ), - }, + with Config.version_path.open(mode="r", encoding="utf-8") as f: + version_info = json.load(f) + version_info["main_version"] = Config.VERSION + with Config.version_path.open(mode="w", encoding="utf-8") as f: + json.dump(version_info, f, ensure_ascii=False, indent=4) + + if (Config.app_path / "AUTO_Updater.exe").exists(): + shutil.copy( + Config.app_path / "AUTO_Updater.exe", + Config.app_path / "AUTO_Updater.active.exe", + ) + else: + logger.error("更新器文件不存在") + MainInfoBar.push_info_bar( + "error", "更新器不存在", "请手动前往 GitHub 获取最新版本", -1 + ) + return None + + subprocess.Popen( + str(Config.app_path / "AUTO_Updater.active.exe"), + shell=True, + creationflags=subprocess.CREATE_NO_WINDOW, ) - # 完成更新器的更新后更新主程序 - if main_version_remote > main_version_current: - self.updater.download_accomplish.connect(self.update_main) - # 显示更新页面 - self.updater.show() - self.updater.run() - - # 更新主程序 - elif main_version_remote > main_version_current: - self.update_main() + self.close() + QApplication.quit() # 无版本更新 else: MainInfoBar.push_info_bar("success", "更新检查", "已是最新版本~", 3000) - def update_main(self) -> None: - """更新主程序""" - - with Config.version_path.open(mode="r", encoding="utf-8") as f: - version_info = json.load(f) - version_info["main_version"] = Config.VERSION - with Config.version_path.open(mode="w", encoding="utf-8") as f: - json.dump(version_info, f, ensure_ascii=False, indent=4) - - subprocess.Popen( - str(Config.app_path / "Updater.exe"), - shell=True, - creationflags=subprocess.CREATE_NO_WINDOW, - ) - self.close() - QApplication.quit() - def show_notice(self, if_show: bool = True): """显示公告""" @@ -464,8 +455,9 @@ class Setting(QWidget): } if if_show or ( - datetime.now() > datetime.strptime(notice["time"], "%Y-%m-%d %H:%M") - and datetime.strptime(notice["time"], "%Y-%m-%d %H:%M") > time_local + datetime.now() + > datetime.strptime(notice["time"], "%Y-%m-%d %H:%M") + > time_local ): choice = NoticeMessageBox(self.window(), "公告", notice["notice_dict"]) @@ -485,37 +477,47 @@ class FunctionSettingCard(HeaderCardWidget): self.setTitle("功能") self.card_HomeImageMode = ComboBoxSettingCard( - configItem=Config.global_config.function_HomeImageMode, icon=FluentIcon.PAGE_RIGHT, title="主页背景图模式", content="选择主页背景图的来源", texts=["默认", "自定义", "主题图像"], + qconfig=Config, + configItem=Config.function_HomeImageMode, + parent=self, ) self.card_HistoryRetentionTime = ComboBoxSettingCard( - configItem=Config.global_config.function_HistoryRetentionTime, icon=FluentIcon.PAGE_RIGHT, title="历史记录保留时间", content="选择历史记录的保留时间,超期自动清理", texts=["7 天", "15 天", "30 天", "60 天", "永久"], + qconfig=Config, + configItem=Config.function_HistoryRetentionTime, + parent=self, ) self.card_IfAllowSleep = SwitchSettingCard( icon=FluentIcon.PAGE_RIGHT, title="启动时阻止系统休眠", content="仅阻止电脑自动休眠,不会影响屏幕是否熄灭", - configItem=Config.global_config.function_IfAllowSleep, + qconfig=Config, + configItem=Config.function_IfAllowSleep, + parent=self, ) self.card_IfSilence = self.SilenceSettingCard(self) self.card_IfAgreeBilibili = SwitchSettingCard( icon=FluentIcon.PAGE_RIGHT, title="托管bilibili游戏隐私政策", content="授权AUTO_MAA同意bilibili游戏隐私政策", - configItem=Config.global_config.function_IfAgreeBilibili, + qconfig=Config, + configItem=Config.function_IfAgreeBilibili, + parent=self, ) self.card_IfSkipMumuSplashAds = SwitchSettingCard( icon=FluentIcon.PAGE_RIGHT, title="跳过MuMu启动广告", content="启动MuMu模拟器时屏蔽启动广告", - configItem=Config.global_config.function_IfSkipMumuSplashAds, + qconfig=Config, + configItem=Config.function_IfSkipMumuSplashAds, + parent=self, ) Layout = QVBoxLayout() @@ -541,14 +543,18 @@ class FunctionSettingCard(HeaderCardWidget): icon=FluentIcon.PAGE_RIGHT, title="静默模式", content="是否启用静默模式", - configItem=Config.global_config.function_IfSilence, + qconfig=Config, + configItem=Config.function_IfSilence, + parent=self, ) self.card_BossKey = LineEditSettingCard( - text="请输入安卓模拟器老板键", icon=FluentIcon.PAGE_RIGHT, title="模拟器老板键", content="输入模拟器老板快捷键,以“+”分隔", - configItem=Config.global_config.function_BossKey, + text="请输入安卓模拟器老板键", + qconfig=Config, + configItem=Config.function_BossKey, + parent=self, ) widget = QWidget() @@ -570,19 +576,25 @@ class StartSettingCard(HeaderCardWidget): icon=FluentIcon.PAGE_RIGHT, title="开机时自动启动", content="将AUTO_MAA添加到开机启动项", - configItem=Config.global_config.start_IfSelfStart, + qconfig=Config, + configItem=Config.start_IfSelfStart, + parent=self, ) self.card_IfRunDirectly = SwitchSettingCard( icon=FluentIcon.PAGE_RIGHT, title="启动后直接运行主任务", content="启动AUTO_MAA后自动运行自动代理任务,优先级:调度队列 1 > 脚本 1", - configItem=Config.global_config.start_IfRunDirectly, + qconfig=Config, + configItem=Config.start_IfRunDirectly, + parent=self, ) self.card_IfMinimizeDirectly = SwitchSettingCard( icon=FluentIcon.PAGE_RIGHT, title="启动后直接最小化", content="启动AUTO_MAA后直接最小化", - configItem=Config.global_config.start_IfMinimizeDirectly, + qconfig=Config, + configItem=Config.start_IfMinimizeDirectly, + parent=self, ) Layout = QVBoxLayout() @@ -602,13 +614,17 @@ class UiSettingCard(HeaderCardWidget): icon=FluentIcon.PAGE_RIGHT, title="显示托盘图标", content="常态显示托盘图标", - configItem=Config.global_config.ui_IfShowTray, + qconfig=Config, + configItem=Config.ui_IfShowTray, + parent=self, ) self.card_IfToTray = SwitchSettingCard( icon=FluentIcon.PAGE_RIGHT, title="最小化到托盘", content="最小化时隐藏到托盘", - configItem=Config.global_config.ui_IfToTray, + qconfig=Config, + configItem=Config.ui_IfToTray, + parent=self, ) Layout = QVBoxLayout() @@ -634,6 +650,7 @@ class NotifySettingCard(HeaderCardWidget): icon=FluentIcon.SEND, title="测试通知", content="发送测试通知到所有已启用的通知渠道", + parent=self, ) self.card_TestNotification.clicked.connect(self.send_test_notification) @@ -652,8 +669,8 @@ class NotifySettingCard(HeaderCardWidget): MainInfoBar.push_info_bar( "success", "测试通知已发送", - "请检查已配置的通知渠道是否可以收到消息", - 3000 + "请检查已配置的通知渠道是否正常收到消息", + 3000, ) class NotifyContentSettingCard(ExpandGroupSettingCard): @@ -664,23 +681,29 @@ class NotifySettingCard(HeaderCardWidget): ) self.card_SendTaskResultTime = ComboBoxSettingCard( - configItem=Config.global_config.notify_SendTaskResultTime, icon=FluentIcon.PAGE_RIGHT, title="推送任务结果选项", content="选择推送自动代理与人工排查任务结果的时机", texts=["不推送", "任何时刻", "仅失败时"], + qconfig=Config, + configItem=Config.notify_SendTaskResultTime, + parent=self, ) self.card_IfSendStatistic = SwitchSettingCard( icon=FluentIcon.PAGE_RIGHT, title="推送统计信息", content="推送自动代理统计信息的通知", - configItem=Config.global_config.notify_IfSendStatistic, + qconfig=Config, + configItem=Config.notify_IfSendStatistic, + parent=self, ) self.card_IfSendSixStar = SwitchSettingCard( icon=FluentIcon.PAGE_RIGHT, title="推送公招高资喜报", content="公招出现六星词条时推送喜报", - configItem=Config.global_config.notify_IfSendSixStar, + qconfig=Config, + configItem=Config.notify_IfSendSixStar, + parent=self, ) widget = QWidget() @@ -703,7 +726,9 @@ class NotifySettingCard(HeaderCardWidget): icon=FluentIcon.PAGE_RIGHT, title="推送系统通知", content="使用Plyer推送系统级通知,不会在通知中心停留", - configItem=Config.global_config.notify_IfPushPlyer, + qconfig=Config, + configItem=Config.notify_IfPushPlyer, + parent=self, ) widget = QWidget() @@ -724,35 +749,46 @@ class NotifySettingCard(HeaderCardWidget): icon=FluentIcon.PAGE_RIGHT, title="推送邮件通知", content="是否启用邮件通知功能", - configItem=Config.global_config.notify_IfSendMail, + qconfig=Config, + configItem=Config.notify_IfSendMail, + parent=self, ) self.card_SMTPServerAddress = LineEditSettingCard( - text="请输入SMTP服务器地址", icon=FluentIcon.PAGE_RIGHT, title="SMTP服务器地址", content="发信邮箱的SMTP服务器地址", - configItem=Config.global_config.notify_SMTPServerAddress, + text="请输入SMTP服务器地址", + qconfig=Config, + configItem=Config.notify_SMTPServerAddress, + parent=self, ) self.card_FromAddress = LineEditSettingCard( - text="请输入发信邮箱地址", icon=FluentIcon.PAGE_RIGHT, title="发信邮箱地址", content="发送通知的邮箱地址", - configItem=Config.global_config.notify_FromAddress, + text="请输入发信邮箱地址", + qconfig=Config, + configItem=Config.notify_FromAddress, + parent=self, ) self.card_AuthorizationCode = PasswordLineEditSettingCard( - text="请输入发信邮箱授权码", icon=FluentIcon.PAGE_RIGHT, title="发信邮箱授权码", content="发送通知的邮箱授权码", - configItem=Config.global_config.notify_AuthorizationCode, + text="请输入发信邮箱授权码", + algorithm="DPAPI", + qconfig=Config, + configItem=Config.notify_AuthorizationCode, + parent=self, ) self.card_ToAddress = LineEditSettingCard( - text="请输入收信邮箱地址", icon=FluentIcon.PAGE_RIGHT, title="收信邮箱地址", content="接收通知的邮箱地址", - configItem=Config.global_config.notify_ToAddress, + text="请输入收信邮箱地址", + qconfig=Config, + configItem=Config.notify_ToAddress, + parent=self, ) widget = QWidget() @@ -779,28 +815,36 @@ class NotifySettingCard(HeaderCardWidget): icon=FluentIcon.PAGE_RIGHT, title="推送SeverChan通知", content="是否启用SeverChan通知功能", - configItem=Config.global_config.notify_IfServerChan, + qconfig=Config, + configItem=Config.notify_IfServerChan, + parent=self, ) self.card_ServerChanKey = LineEditSettingCard( - text="请输入SendKey", icon=FluentIcon.PAGE_RIGHT, title="SendKey", content="Server酱的SendKey(SC3与SCT都可以)", - configItem=Config.global_config.notify_ServerChanKey, + text="请输入SendKey", + qconfig=Config, + configItem=Config.notify_ServerChanKey, + parent=self, ) self.card_ServerChanChannel = LineEditSettingCard( - text="请输入需要推送的Channel代码(SCT生效)", icon=FluentIcon.PAGE_RIGHT, title="ServerChanChannel代码", content="可以留空,留空则默认。可以多个,请使用“|”隔开", - configItem=Config.global_config.notify_ServerChanChannel, + text="请输入需要推送的Channel代码(SCT生效)", + qconfig=Config, + configItem=Config.notify_ServerChanChannel, + parent=self, ) self.card_ServerChanTag = LineEditSettingCard( - text="请输入加入推送的Tag(SC3生效)", icon=FluentIcon.PAGE_RIGHT, title="Tag内容", content="可以留空,留空则默认。可以多个,请使用“|”隔开", - configItem=Config.global_config.notify_ServerChanTag, + text="请输入加入推送的Tag(SC3生效)", + qconfig=Config, + configItem=Config.notify_ServerChanTag, + parent=self, ) widget = QWidget() @@ -826,14 +870,18 @@ class NotifySettingCard(HeaderCardWidget): icon=FluentIcon.PAGE_RIGHT, title="推送企业微信机器人通知", content="是否启用企业微信机器人通知功能", - configItem=Config.global_config.notify_IfCompanyWebHookBot, + qconfig=Config, + configItem=Config.notify_IfCompanyWebHookBot, + parent=self, ) self.card_CompanyWebHookBotUrl = LineEditSettingCard( - text="请输入Webhook的Url", icon=FluentIcon.PAGE_RIGHT, title="WebhookUrl", content="企业微信群机器人的Webhook地址", - configItem=Config.global_config.notify_CompanyWebHookBotUrl, + text="请输入Webhook的Url", + qconfig=Config, + configItem=Config.notify_CompanyWebHookBotUrl, + parent=self, ) widget = QWidget() @@ -856,6 +904,7 @@ class SecuritySettingCard(HeaderCardWidget): icon=FluentIcon.VPN, title="修改管理密钥", content="修改用于解密用户密码的管理密钥", + parent=self, ) Layout = QVBoxLayout() @@ -869,45 +918,70 @@ class UpdaterSettingCard(HeaderCardWidget): super().__init__(parent) self.setTitle("更新") - self.card_IfAutoUpdate = SwitchSettingCard( - icon=FluentIcon.PAGE_RIGHT, - title="自动检查更新", - content="将在启动时自动检查AUTO_MAA是否有新版本", - configItem=Config.global_config.update_IfAutoUpdate, - ) - self.card_UpdateType = ComboBoxSettingCard( - configItem=Config.global_config.update_UpdateType, - icon=FluentIcon.PAGE_RIGHT, - title="版本更新类别", - content="选择AUTO_MAA的更新类别", - texts=["稳定版", "公测版"], - ) - self.card_ThreadNumb = RangeSettingCard( - configItem=Config.global_config.update_ThreadNumb, - icon=FluentIcon.PAGE_RIGHT, - title="下载器线程数", - content="更新器的下载线程数,建议仅在下载速度较慢时适量拉高", - ) - self.card_ProxyUrlList = UrlListSettingCard( - icon=FluentIcon.SETTING, - configItem=Config.global_config.update_ProxyUrlList, - title="代理地址列表", - content="更新器代理地址列表", - parent=self, - ) self.card_CheckUpdate = PushSettingCard( text="检查更新", icon=FluentIcon.UPDATE, title="获取最新版本", content="检查AUTO_MAA是否有新版本", + parent=self, + ) + self.card_IfAutoUpdate = SwitchSettingCard( + icon=FluentIcon.PAGE_RIGHT, + title="自动检查更新", + content="将在启动时自动检查AUTO_MAA是否有新版本", + qconfig=Config, + configItem=Config.update_IfAutoUpdate, + parent=self, + ) + self.card_UpdateType = ComboBoxSettingCard( + icon=FluentIcon.PAGE_RIGHT, + title="版本更新类别", + content="选择AUTO_MAA的更新类别", + texts=["稳定版", "公测版"], + qconfig=Config, + configItem=Config.update_UpdateType, + parent=self, + ) + self.card_ThreadNumb = RangeSettingCard( + icon=FluentIcon.PAGE_RIGHT, + title="下载器线程数", + content="更新器的下载线程数,建议仅在下载速度较慢时适量拉高", + qconfig=Config, + configItem=Config.update_ThreadNumb, + parent=self, + ) + self.card_ProxyUrlList = UrlListSettingCard( + icon=FluentIcon.SETTING, + title="代理地址列表", + content="更新器代理地址列表", + qconfig=Config, + configItem=Config.update_ProxyUrlList, + parent=self, + ) + self.card_MirrorChyanCDK = PasswordLineEditSettingCard( + icon=FluentIcon.PAGE_RIGHT, + title="Mirror酱CDK", + content="填写后改为使用由Mirror酱提供的下载服务", + text="请输入Mirror酱CDK", + algorithm="DPAPI", + qconfig=Config, + configItem=Config.update_MirrorChyanCDK, + parent=self, + ) + mirrorchyan_url = HyperlinkButton( + "https://mirrorchyan.com/", "购买Mirror酱CDK", self + ) + self.card_MirrorChyanCDK.hBoxLayout.insertWidget( + 5, mirrorchyan_url, 0, Qt.AlignRight ) Layout = QVBoxLayout() + Layout.addWidget(self.card_CheckUpdate) Layout.addWidget(self.card_IfAutoUpdate) Layout.addWidget(self.card_UpdateType) Layout.addWidget(self.card_ThreadNumb) Layout.addWidget(self.card_ProxyUrlList) - Layout.addWidget(self.card_CheckUpdate) + Layout.addWidget(self.card_MirrorChyanCDK) self.viewLayout.addLayout(Layout) @@ -922,6 +996,7 @@ class OtherSettingCard(HeaderCardWidget): icon=FluentIcon.PAGE_RIGHT, title="公告", content="查看AUTO_MAA的最新公告", + parent=self, ) self.card_UserDocs = HyperlinkCard( url="https://clozya.github.io/AUTOMAA_docs", @@ -929,8 +1004,9 @@ class OtherSettingCard(HeaderCardWidget): icon=FluentIcon.PAGE_RIGHT, title="AUTO_MAA官方文档站", content="访问AUTO_MAA的官方文档站,获取使用指南和项目相关信息", + parent=self, ) - self.card_Association = self.AssociationSettingCard() + self.card_Association = self.AssociationSettingCard(self) Layout = QVBoxLayout() Layout.addWidget(self.card_Notice) @@ -954,6 +1030,7 @@ class OtherSettingCard(HeaderCardWidget): icon=FluentIcon.GITHUB, title="GitHub", content="查看AUTO_MAA的源代码,提交问题和建议,欢迎参与开发", + parent=self, ) self.card_QQGroup = HyperlinkCard( url="https://qm.qq.com/q/bd9fISNoME", @@ -961,6 +1038,7 @@ class OtherSettingCard(HeaderCardWidget): icon=FluentIcon.CHAT, title="QQ群", content="与AUTO_MAA开发者和用户交流", + parent=self, ) widget = QWidget() diff --git a/app/utils/downloader.py b/app/utils/downloader.py index a9febdf..d02e6bc 100644 --- a/app/utils/downloader.py +++ b/app/utils/downloader.py @@ -31,6 +31,8 @@ import zipfile import requests import subprocess import time +import win32crypt +import base64 from functools import partial from pathlib import Path @@ -199,7 +201,6 @@ class DownloadManager(QDialog): app_path: Path, name: str, main_version: list, - updater_version: list, config: dict, ) -> None: super().__init__() @@ -207,7 +208,6 @@ class DownloadManager(QDialog): self.app_path = app_path self.name = name self.main_version = main_version - self.updater_version = updater_version self.config = config self.download_path = app_path / "DOWNLOAD_TEMP.zip" # 临时下载文件的路径 self.version_path = app_path / "resources/version.json" @@ -241,9 +241,12 @@ class DownloadManager(QDialog): if self.name == "MAA": self.download_task1() - else: - self.test_speed_task1() - self.speed_test_accomplish.connect(self.download_task1) + elif self.name == "AUTO_MAA": + if self.config["mode"] == "Proxy": + self.test_speed_task1() + self.speed_test_accomplish.connect(self.download_task1) + elif self.config["mode"] == "MirrorChyan": + self.download_task1() def get_download_url(self, mode: str) -> Union[str, Dict[str, str]]: """获取下载链接""" @@ -273,37 +276,33 @@ class DownloadManager(QDialog): if self.name == "MAA": return f"https://jp-download.fearr.xyz/MAA/MAA-{version_text(self.main_version)}-win-x64.zip" - if "selected" in self.config: - selected_url = self.config["selected"] - elif "speed_result" in self.config: - selected_url = max( - self.config["speed_result"], key=self.config["speed_result"].get - ) + if self.name == "AUTO_MAA": - if self.name == "AUTO_MAA主程序": + if self.config["mode"] == "Proxy": - if selected_url == "GitHub站": - return f"https://github.com/DLmaster361/AUTO_MAA/releases/download/{version_text(self.main_version)}/AUTO_MAA_{version_text(self.main_version)}.zip" - elif selected_url == "官方镜像站": - return f"https://gitee.com/DLmaster_361/AUTO_MAA/releases/download/{version_text(self.main_version)}/AUTO_MAA_{version_text(self.main_version)}.zip" - elif selected_url in self.config["download_dict"].keys(): - return f"{self.config["download_dict"][selected_url]}AUTO_MAA_{version_text(self.main_version)}.zip" - else: - return f"{selected_url}https://github.com/DLmaster361/AUTO_MAA/releases/download/{version_text(self.main_version)}/AUTO_MAA_{version_text(self.main_version)}.zip" + if "selected" in self.config: + selected_url = self.config["selected"] + elif "speed_result" in self.config: + selected_url = max( + self.config["speed_result"], + key=self.config["speed_result"].get, + ) - elif self.name == "AUTO_MAA更新器": + if selected_url == "GitHub站": + return f"https://github.com/DLmaster361/AUTO_MAA/releases/download/{version_text(self.main_version)}/AUTO_MAA_{version_text(self.main_version)}.zip" + elif selected_url == "官方镜像站": + return f"https://gitee.com/DLmaster_361/AUTO_MAA/releases/download/{version_text(self.main_version)}/AUTO_MAA_{version_text(self.main_version)}.zip" + elif selected_url in self.config["download_dict"].keys(): + return f"{self.config["download_dict"][selected_url]}AUTO_MAA_{version_text(self.main_version)}.zip" + else: + return f"{selected_url}https://github.com/DLmaster361/AUTO_MAA/releases/download/{version_text(self.main_version)}/AUTO_MAA_{version_text(self.main_version)}.zip" - if selected_url == "GitHub站": - return f"https://github.com/DLmaster361/AUTO_MAA/releases/download/{version_text(self.main_version)}/Updater_{version_text(self.updater_version)}.zip" - elif selected_url == "官方镜像站": - return f"https://gitee.com/DLmaster_361/AUTO_MAA/releases/download/{version_text(self.main_version)}/Updater_{version_text(self.updater_version)}.zip" - elif selected_url in self.config["download_dict"].keys(): - print( - f"{self.config["download_dict"][selected_url]}Updater_{version_text(self.updater_version)}.zip" - ) - return f"{self.config["download_dict"][selected_url]}Updater_{version_text(self.updater_version)}.zip" - else: - return f"{selected_url}https://github.com/DLmaster361/AUTO_MAA/releases/download/{version_text(self.main_version)}/Updater_{version_text(self.updater_version)}.zip" + elif self.config["mode"] == "MirrorChyan": + with requests.get( + self.config["url"], allow_redirects=True, stream=True + ) as response: + if response.status_code == 200: + return response.url def test_speed_task1(self) -> None: @@ -422,7 +421,11 @@ class DownloadManager(QDialog): # 创建下载子线程 self.download_process_dict[f"part{i}"] = DownloadProcess( - url, start_byte, end_byte, self.download_path.with_suffix(f".part{i}") + url, + start_byte, + end_byte, + self.download_path.with_suffix(f".part{i}"), + 1 if self.config["mode"] == "MirrorChyan" else -1, ) self.downloaded_size_list.append([0, False]) self.download_process_dict[f"part{i}"].progress.connect( @@ -500,29 +503,25 @@ class DownloadManager(QDialog): self.zip_extract.start() self.zip_loop.exec() + self.update_info("正在删除已弃用的文件") + if (app_path / "changes.json").exists(): + + with (app_path / "changes.json").open(mode="r", encoding="utf-8") as f: + info: Dict[str, List[str]] = json.load(f) + + if "deleted" in info: + for file_path in info: + (self.app_path / file_path).unlink() + + (app_path / "changes.json").unlink() + self.update_info("正在删除临时文件") self.update_progress(0, 0, 0) if self.download_path.exists(): self.download_path.unlink() - # 更新version文件 - if not self.isInterruptionRequested and self.name in [ - "AUTO_MAA主程序", - "AUTO_MAA更新器", - ]: - with open(self.version_path, "r", encoding="utf-8") as f: - version_info = json.load(f) - if self.name == "AUTO_MAA主程序": - version_info["main_version"] = ".".join(map(str, self.main_version)) - elif self.name == "AUTO_MAA更新器": - version_info["updater_version"] = ".".join( - map(str, self.updater_version) - ) - with open(self.version_path, "w", encoding="utf-8") as f: - json.dump(version_info, f, ensure_ascii=False, indent=4) - # 主程序更新完成后打开对应程序 - if not self.isInterruptionRequested and self.name == "AUTO_MAA主程序": + if not self.isInterruptionRequested and self.name == "AUTO_MAA": subprocess.Popen( str(self.app_path / "AUTO_MAA.exe"), shell=True, @@ -585,14 +584,11 @@ class AUTO_MAA_Downloader(QApplication): app_path: Path, name: str, main_version: list, - updater_version: list, config: dict, ) -> None: super().__init__() - self.main = DownloadManager( - app_path, name, main_version, updater_version, config - ) + self.main = DownloadManager(app_path, name, main_version, config) self.main.show() self.main.run() @@ -607,45 +603,64 @@ if __name__ == "__main__": with (app_path / "resources/version.json").open( mode="r", encoding="utf-8" ) as f: - version_current = json.load(f) - main_version_current = list( - map(int, version_current["main_version"].split(".")) + current_version_info = json.load(f) + current_version = list( + map(int, current_version_info["main_version"].split(".")) ) else: - main_version_current = [0, 0, 0, 0] + current_version = [0, 0, 0, 0] # 从本地配置文件获取更新信息 if (app_path / "config/config.json").exists(): with (app_path / "config/config.json").open(mode="r", encoding="utf-8") as f: config = json.load(f) - if "Update" in config and "UpdateType" in config["Update"]: - update_type = config["Update"]["UpdateType"] - else: - update_type = "main" - if "Update" in config and "ProxyUrlList" in config["Update"]: - proxy_list = config["Update"]["ProxyUrlList"] + if "Update" in config: + + if "UpdateType" in config["Update"]: + update_type = config["Update"]["UpdateType"] + else: + update_type = "stable" + if "ProxyUrlList" in config["Update"]: + proxy_list = config["Update"]["ProxyUrlList"] + else: + proxy_list = [] + if "ThreadNumb" in config["Update"]: + thread_numb = config["Update"]["ThreadNumb"] + else: + thread_numb = 8 + if "MirrorChyanCDK" in config["Update"]: + mirrorchyan_CDK = ( + win32crypt.CryptUnprotectData( + base64.b64decode(config["Update"]["MirrorChyanCDK"]), + None, + None, + None, + 0, + )[1].decode("utf-8") + if config["Update"]["MirrorChyanCDK"] + else "" + ) + else: + mirrorchyan_CDK = "" + else: + update_type = "stable" proxy_list = [] - if "Update" in config and "ThreadNumb" in config["Update"]: - thread_numb = config["Update"]["ThreadNumb"] - else: thread_numb = 8 + mirrorchyan_CDK = "" else: - update_type = "main" + update_type = "stable" proxy_list = [] thread_numb = 8 + mirrorchyan_CDK = "" # 从远程服务器获取最新版本信息 for _ in range(3): try: response = requests.get( - f"https://gitee.com/DLmaster_361/AUTO_MAA/raw/{update_type}/resources/version.json" + f"https://mirrorchyan.com/api/resources/AUTO_MAA/latest?current_version={version_text(current_version)}&cdk={mirrorchyan_CDK}&channel={update_type}" ) - version_remote = response.json() - main_version_remote = list( - map(int, version_remote["main_version"].split(".")) - ) - remote_proxy_list = version_remote["proxy_list"] + version_info: Dict[str, Union[int, str, Dict[str, str]]] = response.json() break except Exception as e: err = e @@ -653,20 +668,55 @@ if __name__ == "__main__": else: sys.exit(f"获取版本信息时出错:\n{err}") - # 合并代理列表 - download_config = { - "proxy_list": list(set(proxy_list + remote_proxy_list)), - "download_dict": version_remote["download_dict"], - "thread_numb": thread_numb, - } + if version_info["code"] == 0: + + if "url" in version_info["data"]: + download_config = { + "mode": "MirrorChyan", + "thread_numb": 1, + "url": version_info["data"]["url"], + } + else: + + download_config = {"mode": "Proxy", "thread_numb": thread_numb} + else: + sys.exit(f"获取版本信息时出错:{version_info["msg"]}") + + remote_version = list( + map( + int, + version_info["data"]["version_name"][1:].replace("-beta", "").split("."), + ) + ) + + if download_config["mode"] == "Proxy": + for _ in range(3): + try: + response = requests.get( + "https://gitee.com/DLmaster_361/AUTO_MAA/raw/server/download_info.json" + ) + download_info = response.json() + + download_config["proxy_list"] = list( + set(proxy_list + download_info["proxy_list"]) + ) + download_config["download_dict"] = download_info["download_dict"] + break + except Exception as e: + err = e + time.sleep(0.1) + else: + sys.exit(f"获取代理信息时出错:{err}") + + if (app_path / "changes.json").exists(): + (app_path / "changes.json").unlink() # 启动更新线程 - if main_version_remote > main_version_current: + if remote_version > current_version: app = AUTO_MAA_Downloader( app_path, - "AUTO_MAA主程序", - main_version_remote, - [], + "AUTO_MAA", + remote_version, download_config, ) sys.exit(app.exec()) diff --git a/app/utils/package.py b/app/utils/package.py index 0fa3492..fac3e04 100644 --- a/app/utils/package.py +++ b/app/utils/package.py @@ -68,9 +68,8 @@ 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 --mingw64" " --enable-plugins=pyside6 --windows-console-mode=disable" - " --onefile-tempdir-spec='{TEMP}\\AUTO_MAA'" " --windows-icon-from-ico=resources\\icons\\AUTO_MAA.ico" " --company-name='AUTO_MAA Team' --product-name=AUTO_MAA" f" --file-version={version["main_version"]}" @@ -83,27 +82,69 @@ if __name__ == "__main__": print("AUTO_MAA main program packaging completed !") - shutil.copy(root_path / "app/utils/downloader.py", root_path) - print("Packaging AUTO_MAA update program ...") + shutil.copy(root_path / "app/utils/downloader.py", root_path) os.system( - "powershell -Command python -m nuitka --standalone --onefile --mingw64" + "powershell -Command python -m nuitka --standalone --mingw64" " --enable-plugins=pyside6 --windows-console-mode=disable" - " --onefile-tempdir-spec='{TEMP}\\AUTO_MAA_Updater'" " --windows-icon-from-ico=resources\\icons\\AUTO_MAA_Updater.ico" " --company-name='AUTO_MAA Team' --product-name=AUTO_MAA" f" --file-version={version["updater_version"]}" f" --product-version={version["main_version"]}" " --file-description='AUTO_MAA Component'" " --copyright='Copyright © 2024 DLmaster361'" - " --assume-yes-for-downloads --output-filename=Updater" + " --assume-yes-for-downloads --output-filename=AUTO_Updater" " --remove-output downloader.py" ) + (root_path / "downloader.py").unlink() print("AUTO_MAA update program packaging completed !") - (root_path / "downloader.py").unlink() + (root_path / "AUTO_MAA").mkdir(parents=True, exist_ok=True) + + print("Start to copy AUTO_MAA main program ...") + + for item in (root_path / "main.dist").iterdir(): + if item.is_dir(): + shutil.copytree( + item, root_path / "AUTO_MAA" / item.name, dirs_exist_ok=True + ) + else: + shutil.copy(item, root_path / "AUTO_MAA" / item.name) + shutil.rmtree(root_path / "main.dist") + + print("Start to copy AUTO_MAA update program ...") + + for item in (root_path / "downloader.dist").iterdir(): + if item.is_dir(): + shutil.copytree( + item, root_path / "AUTO_MAA" / item.name, dirs_exist_ok=True + ) + else: + shutil.copy(item, root_path / "AUTO_MAA" / item.name) + shutil.rmtree(root_path / "downloader.dist") + + print("Start to copy rescourses ...") + + shutil.copytree(root_path / "app", root_path / "AUTO_MAA/app") + shutil.copytree(root_path / "resources", root_path / "AUTO_MAA/resources") + shutil.copy(root_path / "main.py", root_path / "AUTO_MAA/main.py") + shutil.copy(root_path / "requirements.txt", root_path / "AUTO_MAA/requirements.txt") + shutil.copy(root_path / "README.md", root_path / "AUTO_MAA/README.md") + shutil.copy(root_path / "LICENSE", root_path / "AUTO_MAA/LICENSE") + + print("Start to compress ...") + + shutil.make_archive( + base_name=root_path / f"AUTO_MAA_{version_text(main_version_numb)}", + format="zip", + root_dir=root_path / "AUTO_MAA", + base_dir=".", + ) + shutil.rmtree(root_path / "AUTO_MAA") + + print("compress completed !") all_version_info = {} for v_i in version["version_info"].values(): @@ -114,6 +155,6 @@ if __name__ == "__main__": all_version_info[key] = value.copy() (root_path / "version_info.txt").write_text( - f"{version_text(main_version_numb)}\n{version_text(updater_version_numb)}\n{version_info_markdown(all_version_info)}", + f"{version_text(main_version_numb)}\n{version_text(updater_version_numb)}\n\n{version_info_markdown(all_version_info)}", encoding="utf-8", ) diff --git a/res/version.json b/res/version.json deleted file mode 100644 index 78560a8..0000000 --- a/res/version.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "main_version": "4.2.0.0", - "updater_version": "1.1.0.0", - "announcement": "\n# 这是一个中转版本,此版本后更换程序架构方式。\n# 由于更新方法无法通用,您需要在完成本次更新后再次检查更新以获取最新版本。\n", - "proxy_list":[ - "", - "https://gitproxy.click/", - "https://cdn.moran233.xyz/", - "https://gh.llkk.cc/", - "https://github.akams.cn/", - "https://www.ghproxy.cn/", - "https://ghfast.top/" - ] -} \ No newline at end of file diff --git a/resources/docs/MAA_config_info.txt b/resources/docs/MAA_config_info.txt index 12b1668..1510c73 100644 --- a/resources/docs/MAA_config_info.txt +++ b/resources/docs/MAA_config_info.txt @@ -11,6 +11,8 @@ "TaskQueue.AutoRoguelike.IsChecked": "False" #自动肉鸽 "TaskQueue.Reclamation.IsChecked": "False" #生息演算 #刷理智 +"MainFunction.UseMedicine": "True" #吃理智药 +"MainFunction.UseMedicine.Quantity": "999" #吃理智药数量 "MainFunction.Stage1": "" #主关卡 "MainFunction.Stage2": "" #备选关卡1 "MainFunction.Stage3": "" #备选关卡2 diff --git a/resources/version.json b/resources/version.json index f044d69..aa8a30c 100644 --- a/resources/version.json +++ b/resources/version.json @@ -1,8 +1,20 @@ { - "main_version": "4.2.5.8", + "main_version": "4.2.5.9", "updater_version": "1.2.0.2", "announcement": "\n## 新增功能\n- 屏蔽MuMu模拟器开屏广告功能上线\n- 更新器支持多线程下载\n- 添加强制关闭ADB与模拟器等增强任务项\n## 修复BUG\n- 修复统计信息HTML模板公招匹配错误\n- 修复密码显示按钮动画异常\n- 修复`检测到MAA未能实际执行任务`报错被异常屏蔽\n- 修复MAA超时判定异常失效\n## 程序优化\n- 关机等电源操作添加100s倒计时\n- 人工排查弹窗方法优化\n- 人工排查时自动屏蔽静默操作\n- 公告样式优化", "version_info": { + "4.2.5.9":{ + "新增功能": [ + "添加理智药设置选项 #34", + "预接入`mirrorc`" + ], + "程序优化": [ + "输入对话框添加回车键确认能力 #35", + "用户列表UI改版升级", + "配置类取消单例限制", + "配置读取方式与界面渲染方法优化" + ] + }, "4.2.5.8":{ "修复BUG": [ "对win10主题进一步适配"