diff --git a/.github/workflows/build-app.yml b/.github/workflows/build-app.yml index a5ad763..5ea3f85 100644 --- a/.github/workflows/build-app.yml +++ b/.github/workflows/build-app.yml @@ -77,18 +77,12 @@ jobs: "AUTO_MAA_version=$MAIN_VERSION" | Out-File -FilePath $env:GITHUB_ENV -Append $UPDATER_VERSION=(Get-Content -Path "version_info.txt" -TotalCount 2 | Select-Object -Index 1).Trim() "updater_version=$UPDATER_VERSION" | Out-File -FilePath $env:GITHUB_ENV -Append - - name: Create Zip - id: create_zip - run: | - Compress-Archive -Path app,resources,main.py,AUTO_MAA.exe,requirements.txt,README.md,LICENSE -DestinationPath AUTO_MAA_${{ env.AUTO_MAA_version }}.zip - Compress-Archive -Path Updater.exe -DestinationPath Updater_${{ env.updater_version }}.zip - name: Upload Artifact uses: actions/upload-artifact@v4 with: name: AUTO_MAA_${{ env.AUTO_MAA_version }} path: | AUTO_MAA_${{ env.AUTO_MAA_version }}.zip - Updater_${{ env.updater_version }}.zip - name: Upload Version_Info Artifact uses: actions/upload-artifact@v4 with: @@ -152,14 +146,12 @@ jobs: gh release create "$TAGNAME" --target "main" --title "$NAME" --notes "$NOTES" artifacts/* env: GITHUB_TOKEN: ${{ secrets.WORKFLOW_TOKEN }} - - name: Trigger MirrorChyanUploading run: | gh workflow run --repo $GITHUB_REPOSITORY mirrorchyan gh workflow run --repo $GITHUB_REPOSITORY mirrorchyan_release_note env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Setup SSH Key run: | mkdir -p ~/.ssh diff --git a/.github/workflows/build-pre.yml b/.github/workflows/build-pre.yml index 6f6760a..25ea104 100644 --- a/.github/workflows/build-pre.yml +++ b/.github/workflows/build-pre.yml @@ -77,18 +77,12 @@ jobs: "AUTO_MAA_version=$MAIN_VERSION" | Out-File -FilePath $env:GITHUB_ENV -Append $UPDATER_VERSION=(Get-Content -Path "version_info.txt" -TotalCount 2 | Select-Object -Index 1).Trim() "updater_version=$UPDATER_VERSION" | Out-File -FilePath $env:GITHUB_ENV -Append - - name: Create Zip - id: create_zip - run: | - Compress-Archive -Path app,resources,main.py,AUTO_MAA.exe,requirements.txt,README.md,LICENSE -DestinationPath AUTO_MAA_${{ env.AUTO_MAA_version }}.zip - Compress-Archive -Path Updater.exe -DestinationPath Updater_${{ env.updater_version }}.zip - name: Upload Artifact uses: actions/upload-artifact@v4 with: name: AUTO_MAA_${{ env.AUTO_MAA_version }} path: | AUTO_MAA_${{ env.AUTO_MAA_version }}.zip - Updater_${{ env.updater_version }}.zip - name: Upload Version_Info Artifact uses: actions/upload-artifact@v4 with: @@ -152,14 +146,12 @@ jobs: gh release create "$TAGNAME" --target "main" --title "$NAME" --notes "$NOTES" --prerelease artifacts/* env: GITHUB_TOKEN: ${{ secrets.WORKFLOW_TOKEN }} - - name: Trigger MirrorChyanUploading run: | gh workflow run --repo $GITHUB_REPOSITORY mirrorchyan gh workflow run --repo $GITHUB_REPOSITORY mirrorchyan_release_note env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Setup SSH Key run: | mkdir -p ~/.ssh diff --git a/app/__init__.py b/app/__init__.py index 1bf7e18..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 Updater +from .utils import DownloadManager __all__ = [ - "AppConfig", "QueueConfig", "MaaConfig", + "MaaUserConfig", "Task", "TaskManager", "MainTimer", @@ -47,5 +47,5 @@ __all__ = [ "Crypto", "System", "AUTO_MAA", - "Updater", + "DownloadManager", ] 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..f35621b 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,570 @@ 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.10" + + gameid_refreshed = Signal() + PASSWORD_refreshed = Signal() + user_info_changed = 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 +622,17 @@ class AppConfig: self.PASSWORD = "" self.running_list = [] self.silence_list = [] + self.gameid_dict = { + "ALL": {"value": [], "text": []}, + "Today": {"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) @@ -80,26 +640,13 @@ class AppConfig: (self.app_path / "debug").mkdir(parents=True, exist_ok=True) (self.app_path / "history").mkdir(parents=True, exist_ok=True) - # 生成版本信息文件 - if not self.version_path.exists(): - version = { - "main_version": "0.0.0.0", - "updater_version": "0.0.0.0", - } - 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() + logger.info("程序初始化完成") def init_logger(self) -> None: """初始化日志记录器""" @@ -119,44 +666,130 @@ class AppConfig: ) logger.info("===================================") logger.info("AUTO_MAA 主程序") - logger.info("版本号: v4.2.1.1") + logger.info(f"版本号: v{self.VERSION}") logger.info(f"根目录: {self.app_path}") logger.info("===================================") logger.info("日志记录器初始化完成") - def init_config(self) -> None: - """初始化配置类""" + def get_gameid(self) -> None: - 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: - """初始化用户数据库""" + # 生成全部关卡信息 + self.gameid_dict["ALL"]["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["ALL"]["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() + # 生成本日关卡信息 + days = datetime.strptime(self.server_date(), "%Y-%m-%d").isoweekday() - logger.info("用户数据库初始化完成") + 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"]) + + self.gameid_dict["Today"] = gameid_dict + + self.gameid_refreshed.emit() + + 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 +799,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 +810,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 +1025,265 @@ 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() + + 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"] ) - db.commit() - cur.close() - db.close() + user_config.set( + user_config.Data_IfPassCheck, info["Config"]["Data"]["IfPassCheck"] + ) + + self.user_info_changed.emit() def save_maa_log(self, log_path: Path, logs: list, maa_result: str) -> bool: """保存MAA日志""" @@ -508,34 +1327,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 +1363,7 @@ class AppConfig: current_stage = drop_match.group(1) last_drop_stats = {} continue - + # 如果已经找到了关卡,处理掉落物 if current_stage: item_match: List[str] = re.findall( @@ -565,17 +1384,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 +1558,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/main_info_bar.py b/app/core/main_info_bar.py index c83f1ca..aed883b 100644 --- a/app/core/main_info_bar.py +++ b/app/core/main_info_bar.py @@ -42,51 +42,32 @@ class _MainInfoBar: def push_info_bar(self, mode: str, title: str, content: str, time: int): """推送到信息通知栏""" - if self.main_window is None: logger.error("信息通知栏未设置父窗口") return None - - if mode == "success": - InfoBar.success( + + # 定义模式到 InfoBar 方法的映射 + mode_mapping = { + "success": InfoBar.success, + "warning": InfoBar.warning, + "error": InfoBar.error, + "info": InfoBar.info + } + + # 根据 mode 获取对应的 InfoBar 方法 + info_bar_method = mode_mapping.get(mode) + if info_bar_method: + info_bar_method( title=title, content=content, orient=Qt.Horizontal, isClosable=True, position=InfoBarPosition.TOP_RIGHT, duration=time, - parent=self.main_window, - ) - elif mode == "warning": - InfoBar.warning( - title=title, - content=content, - orient=Qt.Horizontal, - isClosable=True, - position=InfoBarPosition.TOP_RIGHT, - duration=time, - parent=self.main_window, - ) - elif mode == "error": - InfoBar.error( - title=title, - content=content, - orient=Qt.Horizontal, - isClosable=True, - position=InfoBarPosition.TOP_RIGHT, - duration=time, - parent=self.main_window, - ) - elif mode == "info": - InfoBar.info( - title=title, - content=content, - orient=Qt.Horizontal, - isClosable=True, - position=InfoBarPosition.TOP_RIGHT, - duration=time, - parent=self.main_window, + parent=self.main_window ) + else: + logger.error(f"未知的通知栏模式: {mode}") MainInfoBar = _MainInfoBar() diff --git a/app/core/task_manager.py b/app/core/task_manager.py index afe43fa..7f6b830 100644 --- a/app/core/task_manager.py +++ b/app/core/task_manager.py @@ -27,9 +27,7 @@ v4.2 from loguru import logger from PySide6.QtCore import QThread, QObject, Signal -from qfluentwidgets import Dialog -import json -from pathlib import Path +from qfluentwidgets import MessageBox 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) @@ -66,6 +64,7 @@ class Task(QThread): self.question_response.connect(lambda: print("response")) + @logger.catch def run(self): if "设置MAA" in self.mode: @@ -75,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([])) @@ -90,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) @@ -135,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): """保存保存任务结果""" @@ -187,9 +168,10 @@ class _TaskManager(QObject): connect_gui = Signal(Task) push_info_bar = Signal(str, str, str, int) - def __init__(self): + def __init__(self, main_window=None): super(_TaskManager, self).__init__() + self.main_window = main_window self.task_dict: Dict[str, Task] = {} def add_task( @@ -277,20 +259,42 @@ 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) - System.set_power(info["QueueSet"]["AfterAccomplish"]) + + if ( + Config.queue_dict[name]["Config"].get( + Config.queue_dict[name]["Config"].queueSet_AfterAccomplish + ) + != "None" + ): + + from app.ui import ProgressRingMessageBox + + mode_book = { + "Shutdown": "关机", + "Hibernate": "休眠", + "Sleep": "睡眠", + "KillSelf": "关闭AUTO_MAA", + } + + choice = ProgressRingMessageBox( + self.main_window, + f"{mode_book[Config.queue_dict[name]["Config"].get(Config.queue_dict[name]["Config"].queueSet_AfterAccomplish)]}倒计时", + ) + if choice.exec(): + 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): """推送对话框""" - choice = Dialog(title, content, None) + choice = MessageBox(title, content, self.main_window) choice.yesButton.setText("是") choice.cancelButton.setText("否") - self.task_dict[name].question_response.emit(bool(choice.exec_())) + self.task_dict[name].question_response.emit(bool(choice.exec())) TaskManager = _TaskManager() diff --git a/app/core/timer.py b/app/core/timer.py index 449ceba..66cc434 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() 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,14 +82,15 @@ class _MainTimer(QWidget): ): logger.info(f"定时任务:{name}") - TaskManager.add_task("自动代理_新调度台", name, info) + TaskManager.add_task("自动代理_新调度台", name, data) def set_silence(self): """设置静默模式""" if ( - Config.global_config.get(Config.global_config.function_IfSilence) - and Config.global_config.get(Config.global_config.function_BossKey) != "" + not Config.if_ignore_silence + and Config.get(Config.function_IfSilence) + and Config.get(Config.function_BossKey) != "" ): windows = System.get_window_info() @@ -98,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: @@ -108,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 28938d7..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,36 +58,46 @@ 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.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() self.log_monitor_timer = QTimer() self.log_monitor_timer.timeout.connect(self.refresh_maa_log) self.monitor_loop = QEventLoop() + self.question_loop = QEventLoop() + self.question_response.connect(self.__capture_response) + self.question_response.connect(self.question_loop.quit) + 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): """提取配置信息""" @@ -103,7 +112,7 @@ class MaaManager(QObject): def run(self): """主进程,运行MAA代理进程""" - curdate = self.server_date() + curdate = Config.server_date() begin_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") self.configure() @@ -125,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) @@ -140,20 +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: + 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) @@ -165,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() @@ -191,72 +216,111 @@ class MaaManager(QObject): ) # 剿灭-日常模式循环 - for j in range(2): + for mode in ["Annihilation", "Routine"]: if self.isInterruptionRequested: break - if self.data[user[2]][10 - j] == "n": - run_book[j] = True + # 剿灭模式;满足条件跳过剿灭 + if ( + 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} | 用户: {user_data["Info"]["Name"]} - 本周剿灭模式已达上限,跳过执行剿灭任务" + ) + run_book[mode] = True continue - if run_book[j]: + else: + self.weekly_annihilation_limit_reached = False + + if not user_data["Info"][mode]: + run_book[mode] = True + continue + 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 - self.set_maa(mode_book[j], user[2]) + set = self.set_maa(mode_book[mode], user[2]) # 记录当前时间 start_time = datetime.now() + + # 记录模拟器与ADB路径 + self.emulator_path = Path( + set["Configurations"]["Default"]["Start.EmulatorPath"] + ) + self.ADB_path = Path( + set["Configurations"]["Default"]["Connect.AdbPath"] + ) + self.if_kill_emulator = bool( + set["Configurations"]["Default"]["MainFunction.PostActions"] + == "12" + ) + # 添加静默进程标记 + Config.silence_list.append(self.emulator_path) + + # 增强任务:任务开始前强杀ADB + if "ADB" in self.set["RunSet"]["EnhanceTask"]: + System.kill_process(self.ADB_path) + # 创建MAA任务 maa = subprocess.Popen( [self.maa_exe_path], shell=True, creationflags=subprocess.CREATE_NO_WINDOW, ) - # 添加静默进程标记 - with self.maa_set_path.open(mode="r", encoding="utf-8") as f: - set = json.load(f) - self.emulator_path = Path( - set["Configurations"]["Default"]["Start.EmulatorPath"] - ) - Config.silence_list.append(self.emulator_path) - # 监测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" ) @@ -275,12 +339,14 @@ class MaaManager(QObject): ) # 无命令行中止MAA与其子程序 System.kill_process(self.maa_exe_path) + if "Emulator" in self.set["RunSet"]["EnhanceTask"]: + System.kill_process(self.emulator_path) self.if_open_emulator = True # 推送异常通知 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): @@ -291,36 +357,49 @@ class MaaManager(QObject): # 移除静默进程标记 Config.silence_list.remove(self.emulator_path) + # 增强任务:任务结束后强杀ADB和模拟器 + if "ADB" in self.set["RunSet"]["EnhanceTask"]: + System.kill_process(self.ADB_path) + if ( + self.if_kill_emulator + and "Emulator" in self.set["RunSet"]["EnhanceTask"] + ): + 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( "成功完成一个自动代理任务!", @@ -330,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] @@ -344,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( @@ -352,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) @@ -360,6 +437,9 @@ class MaaManager(QObject): # 人工排查模式 elif self.mode == "人工排查": + # 人工排查时,屏蔽静默操作 + Config.if_ignore_silence = True + # 标记是否需要启动模拟器 self.if_open_emulator = True # 标识排查模式 @@ -369,6 +449,8 @@ class MaaManager(QObject): # 开始排查 for user in self.user_list: + user_data = self.data[user[2]]["Config"] + if self.isInterruptionRequested: break @@ -377,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)] @@ -440,24 +522,21 @@ 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) + # 解除静默操作屏蔽 + Config.if_ignore_silence = False + # 设置MAA模式 elif "设置MAA" in self.mode: @@ -493,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] == "完成"] @@ -522,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( @@ -532,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) @@ -562,13 +637,10 @@ class MaaManager(QObject): def push_question(self, title: str, message: str) -> bool: self.question.emit(title, message) - loop = QEventLoop() - self.question_response.connect(self._capture_response) - self.question_response.connect(loop.quit) - loop.exec() + self.question_loop.exec() return self.response - def _capture_response(self, response: bool) -> None: + def __capture_response(self, response: bool) -> None: self.response = response def refresh_maa_log(self) -> None: @@ -577,9 +649,15 @@ class MaaManager(QObject): with self.maa_log_path.open(mode="r", encoding="utf-8") as f: pass + # 一分钟内未执行日志变化检查,强制检查一次 + if datetime.now() - self.last_check_time > timedelta(minutes=1): + self.log_monitor.fileChanged.emit(self.log_monitor.files()[0]) + def check_maa_log(self, start_time: datetime, mode: str) -> list: """获取MAA日志并检查以判断MAA程序运行状态""" + self.last_check_time = datetime.now() + # 获取日志 logs = [] if_log_start = False @@ -619,9 +697,14 @@ 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未能实际执行任务" - if "任务出错: StartUp" in log: + elif "任务出错: StartUp" in log: self.maa_result = "检测到MAA未能正确登录PRTS" elif "任务已全部完成!" in log: self.maa_result = "Success!" @@ -677,6 +760,7 @@ class MaaManager(QObject): lambda: self.check_maa_log(start_time, mode) ) self.log_monitor_timer.start(1000) + self.last_check_time = datetime.now() self.monitor_loop.exec() def quit_monitor(self) -> None: @@ -688,12 +772,16 @@ class MaaManager(QObject): self.log_monitor.removePath(str(self.maa_log_path)) self.log_monitor.fileChanged.disconnect() self.log_monitor_timer.stop() + self.last_check_time = None self.monitor_loop.quit() - def set_maa(self, mode, index): + def set_maa(self, mode, index) -> dict: """配置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) @@ -708,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" ) ): @@ -753,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" @@ -780,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" @@ -796,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: @@ -899,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" @@ -939,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" @@ -954,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: @@ -991,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" # 最小化时隐藏至托盘 @@ -1002,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" @@ -1013,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" @@ -1071,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后直接最小化 @@ -1124,7 +1284,7 @@ class MaaManager(QObject): with self.maa_set_path.open(mode="w", encoding="utf-8") as f: json.dump(data, f, ensure_ascii=False, indent=4) - return True + return data def agree_bilibili(self, if_agree): """向MAA写入Bilibili协议相关任务""" @@ -1135,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", @@ -1157,23 +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 get_emulator_path(self): - """获取模拟器路径""" - - # 读取配置文件 - with self.maa_set_path.open(mode="r", encoding="utf-8") as f: - set = json.load(f) - # 获取模拟器路径 - return Path(set["Configurations"]["Default"]["Start.EmulatorPath"]) - - 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, @@ -1244,7 +1385,9 @@ class MaaManager(QObject): message_html = template.render(message) Notify.send_mail("网页", title, message_html) - Notify.ServerChanPush(title, f"{message_text}\n\nAUTO_MAA 敬上") + # ServerChan的换行是两个换行符。故而将\n替换为\n\n + serverchan_message = message_text.replace("\n", "\n\n") + Notify.ServerChanPush(title, f"{serverchan_message}\n\nAUTO_MAA 敬上") Notify.CompanyWebHookBotPush(title, f"{message_text}\n\nAUTO_MAA 敬上") elif mode == "公招六星": diff --git a/app/services/notification.py b/app/services/notification.py index a8eeda1..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,10 +144,20 @@ class Notification(QWidget): def ServerChanPush(self, title, content): """使用Server酱推送通知""" - if Config.global_config.get(Config.global_config.notify_IfServerChan): - send_key = Config.global_config.get( - Config.global_config.notify_ServerChanKey - ) + if Config.get(Config.notify_IfServerChan): + + if Config.get(Config.notify_ServerChanKey) == "": + logger.error("请正确设置Server酱的SendKey") + self.push_info_bar.emit( + "error", + "Server酱通知推送异常", + "请正确设置Server酱的SendKey", + -1, + ) + return None + else: + send_key = Config.get(Config.notify_ServerChanKey) + option = {} is_valid = lambda s: s == "" or ( s == "|".join(s.split("|")) and (s.count("|") == 0 or all(s.split("|"))) @@ -170,11 +166,12 @@ class Notification(QWidget): is_valid => True, 如果启用的话需要正确设置Tag和Channel。 允许空的Tag和Channel即不启用,但不允许例如a||b,|a|b,a|b|,|||| """ - send_tag = Config.global_config.get( - Config.global_config.notify_ServerChanTag + send_tag = "|".join( + _.strip() for _ in Config.get(Config.notify_ServerChanTag).split("|") ) - send_channel = Config.global_config.get( - Config.global_config.notify_ServerChanChannel + send_channel = "|".join( + _.strip() + for _ in Config.get(Config.notify_ServerChanChannel).split("|") ) if is_valid(send_tag): @@ -218,13 +215,22 @@ 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.get(Config.notify_CompanyWebHookBotUrl) == "": + logger.error("请正确设置企业微信群机器人的WebHook地址") + self.push_info_bar.emit( + "error", + "企业微信群机器人通知推送异常", + "请正确设置企业微信群机器人的WebHook地址", + -1, + ) + return None + 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: @@ -243,5 +249,39 @@ class Notification(QWidget): f'使用企业微信群机器人推送通知时出错:\n{response.json()["errmsg"]}' ) + def send_test_notification(self): + """发送测试通知到所有已启用的通知渠道""" + # 发送系统通知 + self.push_plyer( + "测试通知", + "这是 AUTO_MAA 外部通知测试信息。如果你看到了这段内容,说明 AUTO_MAA 的通知功能已经正确配置且可以正常工作!", + "测试通知", + 3, + ) + + # 发送邮件通知 + if Config.get(Config.notify_IfSendMail): + self.send_mail( + "文本", + "AUTO_MAA测试通知", + "这是 AUTO_MAA 外部通知测试信息。如果你看到了这段内容,说明 AUTO_MAA 的通知功能已经正确配置且可以正常工作!", + ) + + # 发送Server酱通知 + if Config.get(Config.notify_IfServerChan): + self.ServerChanPush( + "AUTO_MAA测试通知", + "这是 AUTO_MAA 外部通知测试信息。如果你看到了这段内容,说明 AUTO_MAA 的通知功能已经正确配置且可以正常工作!", + ) + + # 发送企业微信机器人通知 + if Config.get(Config.notify_IfCompanyWebHookBot): + self.CompanyWebHookBotPush( + "AUTO_MAA测试通知", + "这是 AUTO_MAA 外部通知测试信息。如果你看到了这段内容,说明 AUTO_MAA 的通知功能已经正确配置且可以正常工作!", + ) + + return True + Notify = Notification() 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 1fe9610..e0cda7b 100644 --- a/app/ui/Widget.py +++ b/app/ui/Widget.py @@ -25,43 +25,68 @@ v4.2 作者:DLmaster_361 """ -from PySide6.QtWidgets import QWidget, QHBoxLayout -from PySide6.QtCore import Qt, QTime, QEvent +from PySide6.QtWidgets import ( + QWidget, + QWidget, + QLabel, + QHBoxLayout, + QVBoxLayout, + QSizePolicy, +) +from PySide6.QtCore import Qt, QTime, QTimer, QEvent, QSize from PySide6.QtGui import QIcon, QPixmap, QPainter, QPainterPath from qfluentwidgets import ( LineEdit, PasswordLineEdit, MessageBoxBase, + MessageBox, SubtitleLabel, SettingCard, SpinBox, FluentIconBase, Signal, ComboBox, + EditableComboBox, CheckBox, IconWidget, FluentIcon, CardWidget, BodyLabel, - qconfig, + QConfig, ConfigItem, TimeEdit, OptionsConfigItem, TeachingTip, TransparentToolButton, TeachingTipTailPosition, + ExpandSettingCard, + ToolButton, + PushButton, + PrimaryPushButton, + ProgressRing, + TextBrowser, + HeaderCardWidget, + SwitchButton, + IndicatorPosition, + Slider, ) from qfluentwidgets.common.overload import singledispatchmethod import os -from typing import Optional, Union, List +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) @@ -71,6 +96,7 @@ class LineEditMessageBox(MessageBoxBase): elif mode == "密码": self.input = PasswordLineEdit() + self.input.returnPressed.connect(self.yesButton.click) self.input.setPlaceholderText(content) # 将组件添加到布局中 @@ -103,6 +129,269 @@ class ComboBoxMessageBox(MessageBoxBase): self.viewLayout.addWidget(Widget) +class ProgressRingMessageBox(MessageBoxBase): + """进度环倒计时对话框""" + + def __init__(self, parent, title: str): + super().__init__(parent) + self.title = SubtitleLabel(title) + + self.time = 100 + Widget = QWidget() + Layout = QHBoxLayout(Widget) + self.ring = ProgressRing() + self.ring.setRange(0, 100) + self.ring.setValue(100) + self.ring.setTextVisible(True) + self.ring.setFormat("%p 秒") + self.ring.setFixedSize(100, 100) + self.ring.setStrokeWidth(4) + Layout.addWidget(self.ring) + + self.yesButton.hide() + self.cancelButton.clicked.connect(self.__quit_timer) + self.buttonLayout.insertStretch(1) + + # 将组件添加到布局中 + self.viewLayout.addWidget(self.title) + self.viewLayout.addWidget(Widget) + + self.timer = QTimer(self) + self.timer.timeout.connect(self.__update_time) + self.timer.start(1000) + + def __update_time(self): + + self.time -= 1 + self.ring.setValue(self.time) + + if self.time == 0: + self.timer.stop() + self.timer.deleteLater() + self.yesButton.click() + + def __quit_timer(self): + self.timer.stop() + self.timer.deleteLater() + + +class NoticeMessageBox(MessageBoxBase): + """公告对话框""" + + def __init__(self, parent, title: str, content: Dict[str, str]): + super().__init__(parent) + + self.index = self.NoticeIndexCard(title, content, self) + self.text = TextBrowser(self) + self.text.setOpenExternalLinks(True) + self.button_yes = PrimaryPushButton("确认", self) + self.button_cancel = PrimaryPushButton("取消", self) + + self.buttonGroup.hide() + + self.v_layout = QVBoxLayout() + self.v_layout.addWidget(self.text) + self.button_layout = QHBoxLayout() + self.button_layout.addWidget(self.button_yes) + self.button_layout.addWidget(self.button_cancel) + self.v_layout.addLayout(self.button_layout) + + self.h_layout = QHBoxLayout() + self.h_layout.addWidget(self.index) + self.h_layout.addLayout(self.v_layout) + self.h_layout.setStretch(0, 1) + self.h_layout.setStretch(1, 3) + + # 将组件添加到布局中 + self.viewLayout.addLayout(self.h_layout) + self.widget.setFixedSize(800, 600) + + self.index.index_changed.connect(self.__update_text) + self.button_yes.clicked.connect(self.yesButton.click) + self.button_cancel.clicked.connect(self.cancelButton.click) + self.index.index_cards[0].clicked.emit() + + def __update_text(self, text: str): + + html = markdown.markdown(text).replace("\n", "") + html = re.sub( + r"(.*?)", + r"\1", + html, + ) + html = re.sub( + r'(]*href="[^"]+"[^>]*)>', r'\1 style="color: #009faa;">', html + ) + html = re.sub(r"
  • (.*?)

  • ", r"

    \1

    ", html) + html = re.sub(r"
      (.*?)
    ", r"\1", html) + + self.text.setHtml(f"{html}") + + class NoticeIndexCard(HeaderCardWidget): + + index_changed = Signal(str) + + def __init__(self, title: str, content: Dict[str, str], parent=None): + super().__init__(parent) + self.setTitle(title) + + self.Layout = QVBoxLayout() + self.viewLayout.addLayout(self.Layout) + self.viewLayout.setContentsMargins(3, 0, 3, 3) + + self.index_cards: List[QuantifiedItemCard] = [] + + for index, text in content.items(): + + self.index_cards.append(QuantifiedItemCard([index, ""])) + self.index_cards[-1].clicked.connect( + partial(self.index_changed.emit, text) + ) + self.Layout.addWidget(self.index_cards[-1]) + + if not content: + self.Layout.addWidget(QuantifiedItemCard(["暂无信息", ""])) + + 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""" @@ -110,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) @@ -134,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) @@ -175,14 +473,139 @@ 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""" + + 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): @@ -192,15 +615,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]) @@ -221,7 +646,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) @@ -230,16 +655,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) @@ -250,20 +677,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): @@ -274,14 +812,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) @@ -319,17 +859,140 @@ 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")) +class UrlItem(QWidget): + """Url item""" + + removed = Signal(QWidget) + + def __init__(self, url: str, parent=None): + super().__init__(parent=parent) + self.url = url + self.hBoxLayout = QHBoxLayout(self) + self.folderLabel = QLabel(url, self) + self.removeButton = ToolButton(FluentIcon.CLOSE, self) + + self.removeButton.setFixedSize(39, 29) + self.removeButton.setIconSize(QSize(12, 12)) + + self.setFixedHeight(53) + self.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Fixed) + self.hBoxLayout.setContentsMargins(48, 0, 60, 0) + self.hBoxLayout.addWidget(self.folderLabel, 0, Qt.AlignLeft) + self.hBoxLayout.addSpacing(16) + self.hBoxLayout.addStretch(1) + self.hBoxLayout.addWidget(self.removeButton, 0, Qt.AlignRight) + self.hBoxLayout.setAlignment(Qt.AlignVCenter) + + self.removeButton.clicked.connect(lambda: self.removed.emit(self)) + + +class UrlListSettingCard(ExpandSettingCard): + """Url list setting card""" + + urlChanged = Signal(list) + + 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.addUrlButton = PushButton("添加代理网址", self) + + self.urls: List[str] = self.qconfig.get(configItem).copy() + self.__initWidget() + + def __initWidget(self): + self.addWidget(self.addUrlButton) + + # initialize layout + self.viewLayout.setSpacing(0) + self.viewLayout.setAlignment(Qt.AlignTop) + self.viewLayout.setContentsMargins(0, 0, 0, 0) + for url in self.urls: + self.__addUrlItem(url) + + self.addUrlButton.clicked.connect(self.__showUrlDialog) + + def __showUrlDialog(self): + """show url dialog""" + + choice = LineEditMessageBox( + self.window(), "添加代理网址", "请输入代理网址", "明文" + ) + if choice.exec() and self.__validate(choice.input.text()): + + if choice.input.text()[-1] == "/": + url = choice.input.text() + else: + url = f"{choice.input.text()}/" + + if url in self.urls: + return + + self.__addUrlItem(url) + self.urls.append(url) + self.qconfig.set(self.configItem, self.urls) + self.urlChanged.emit(self.urls) + + def __addUrlItem(self, url: str): + """add url item""" + item = UrlItem(url, self.view) + item.removed.connect(self.__showConfirmDialog) + self.viewLayout.addWidget(item) + item.show() + self._adjustViewSize() + + def __showConfirmDialog(self, item: UrlItem): + """show confirm dialog""" + + choice = MessageBox( + "确认", + f"确定要删除 {item.url} 代理网址吗?", + self.window(), + ) + if choice.exec(): + self.__removeUrl(item) + + def __removeUrl(self, item: UrlItem): + """remove folder""" + if item.url not in self.urls: + return + + self.urls.remove(item.url) + self.viewLayout.removeWidget(item) + item.deleteLater() + self._adjustViewSize() + + self.urlChanged.emit(self.urls) + self.qconfig.set(self.configItem, self.urls) + + def __validate(self, value): + + try: + result = urlparse(value) + return all([result.scheme, result.netloc]) + except ValueError: + return False + + class StatefulItemCard(CardWidget): def __init__(self, item: list, parent=None): @@ -400,7 +1063,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/__init__.py b/app/ui/__init__.py index 3764f04..419aac6 100644 --- a/app/ui/__init__.py +++ b/app/ui/__init__.py @@ -30,5 +30,6 @@ __author__ = "DLmaster361 " __license__ = "GPL-3.0 license" from .main_window import AUTO_MAA +from .Widget import ProgressRingMessageBox -__all__ = ["AUTO_MAA"] +__all__ = ["AUTO_MAA", "ProgressRingMessageBox"] 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 e9ae41a..3586740 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): @@ -223,7 +214,7 @@ class Home(QWidget): MainInfoBar.push_info_bar( "error", "主题图像获取失败", - f"获取最新主题图像信息时出错:\n{err}", + f"获取最新主题图像信息时出错!", -1, ) return None @@ -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") ) @@ -388,11 +369,11 @@ class ButtonGroup(SimpleCardWidget): doc_button.clicked.connect(self.open_chat) layout.addWidget(doc_button) - # 创建 官方店铺 按钮 (当然没有) + # 创建 MirrorChyan 按钮 doc_button = IconButton( FluentIcon.SHOPPING_CART.icon(color=QColor("#fff")), - tip_title="官方店铺", - tip_content="暂时没有官方店铺,但是可以加入官方群聊哦~", + tip_title="非官方店铺", + tip_content="获取 MirrorChyan CDK,更新快人一步", isTooltip=True, ) doc_button.setIconSize(QSize(32, 32)) @@ -419,5 +400,5 @@ class ButtonGroup(SimpleCardWidget): QDesktopServices.openUrl(QUrl("https://clozya.github.io/AUTOMAA_docs")) def open_sales(self): - """其实还是打开 Q群 链接""" - QDesktopServices.openUrl(QUrl("https://qm.qq.com/q/bd9fISNoME")) + """打开 MirrorChyan 链接""" + QDesktopServices.openUrl(QUrl("https://mirrorchyan.com/")) diff --git a/app/ui/main_window.py b/app/ui/main_window.py index d709762..309a0b0 100644 --- a/app/ui/main_window.py +++ b/app/ui/main_window.py @@ -28,26 +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, ) 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 darkdetect from app.core import Config, TaskManager, MainTimer, MainInfoBar from app.services import Notify, Crypto, System @@ -67,11 +64,12 @@ 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) self.show_ui("显示主窗口", if_quick=True) + TaskManager.main_window = self.window() MainInfoBar.main_window = self.window() System.main_window = self.window() @@ -126,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: ( @@ -186,6 +183,7 @@ class AUTO_MAA(MSFluentWindow): self.tray.setContextMenu(self.tray_menu) self.tray.activated.connect(self.on_tray_activated) + Config.user_info_changed.connect(self.member_manager.refresh_dashboard) 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) @@ -205,26 +203,38 @@ class AUTO_MAA(MSFluentWindow): self.themeListener.systemThemeChanged.connect(self.switch_theme) self.themeListener.start() - def switch_theme(self): + def switch_theme(self) -> None: """切换主题""" - setTheme(Theme.AUTO, lazy=True) - QTimer.singleShot(100, lambda: 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)) # 云母特效启用时需要增加重试机制 + # 云母特效不兼容Win10,如果True则通过云母进行主题转换,False则根据当前主题设置背景颜色 if self.isMicaEffectEnabled(): QTimer.singleShot( - 100, + 300, lambda: self.windowEffect.setMicaEffect(self.winId(), isDarkTheme()), ) + else: + # 根据当前主题设置背景颜色 + if isDarkTheme(): + self.setStyleSheet( + """ + CardWidget {background-color: #313131;} + HeaderCardWidget {background-color: #313131;} + background-color: #313131; + """ + ) + else: + self.setStyleSheet("background-color: #ffffff;") + def start_up_task(self) -> None: """启动时任务""" - # 加载配置 - qconfig.load(Config.config_path, Config.global_config) - Config.global_config.save() - # 清理旧日志 self.clean_old_logs() @@ -232,50 +242,30 @@ class AUTO_MAA(MSFluentWindow): 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.get(Config.start_IfRunDirectly): + + self.start_main_task() + # 获取公告 self.setting.show_notice(if_show=False) # 检查更新 - if Config.global_config.get(Config.global_config.update_IfAutoUpdate): - result = self.setting.get_update_info() - if result == "已是最新版本~": - MainInfoBar.push_info_bar("success", "更新检查", result, 3000) - else: - info = InfoBar.info( - title="更新检查", - content=result, - orient=Qt.Horizontal, - isClosable=True, - position=InfoBarPosition.BOTTOM_LEFT, - duration=-1, - parent=self, - ) - Up = PushButton("更新") - Up.clicked.connect(lambda: self.setting.get_update(if_question=False)) - Up.clicked.connect(info.close) - info.addWidget(Up) - info.show() - - # 直接运行主任务 - if Config.global_config.get(Config.global_config.start_IfRunDirectly): - - self.start_main_task() + 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 +285,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 +299,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 +312,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 +338,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 +360,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 +377,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: @@ -417,14 +399,18 @@ class AUTO_MAA(MSFluentWindow): self.show_ui("隐藏到托盘", if_quick=True) + # 清理临时更新器 + if (Config.app_path / "AUTO_Updater.active.exe").exists(): + System.kill_process(Config.app_path / "AUTO_Updater.active.exe") + (Config.app_path / "AUTO_Updater.active.exe").unlink() + # 清理各功能线程 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 940b7c7..d28815a 100644 --- a/app/ui/member_manager.py +++ b/app/ui/member_manager.py @@ -29,48 +29,53 @@ from loguru import logger from PySide6.QtWidgets import ( QWidget, QFileDialog, - QTableWidgetItem, - QHeaderView, + QHBoxLayout, QVBoxLayout, QStackedWidget, + QTableWidgetItem, + QHeaderView, ) from qfluentwidgets import ( Action, - qconfig, - TableWidget, Pivot, - ComboBox, ScrollArea, FluentIcon, MessageBox, HeaderCardWidget, CommandBar, ExpandGroupSettingCard, - ComboBoxSettingCard, PushSettingCard, + TableWidget, + PrimaryToolButton, ) -from PySide6.QtCore import Qt +from PySide6.QtCore import Qt, Signal import requests import time +from datetime import datetime 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.core import Config, MainInfoBar, TaskManager, MaaConfig, MaaUserConfig from app.services import Crypto -from app.utils import Updater +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) @@ -81,7 +86,7 @@ class MemberManager(QWidget): self.tools = CommandBar() - self.member_manager = MemberSettingBox(self) + self.member_manager = self.MemberSettingBox(self) # 逐个添加动作 self.tools.addActions( @@ -116,14 +121,13 @@ class MemberManager(QWidget): ) ) self.tools.addSeparator() - self.tools.addAction( - Action( - FluentIcon.HIDE, - "显示/隐藏密码", - checkable=True, - triggered=self.show_password, - ) + self.key = Action( + FluentIcon.HIDE, + "显示/隐藏密码", + checkable=True, + triggered=self.show_password, ) + self.tools.addAction(self.key) layout.addWidget(self.tools) layout.addWidget(self.member_manager) @@ -141,20 +145,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 +198,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 +228,6 @@ class MemberManager(QWidget): ) return None - member_list = self.member_manager.search_member() index = int(name[3:]) if index == 1: @@ -230,26 +244,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 +276,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 +292,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 +340,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 @@ -355,8 +368,14 @@ class MemberManager(QWidget): while len(maa_version) < 4: maa_version.append(0) - self.downloader = Updater(Path(folder), "MAA", maa_version, []) + self.downloader = DownloadManager( + Path(folder), + "MAA", + maa_version, + {"thread_numb": Config.get(Config.update_ThreadNumb)}, + ) self.downloader.show() + self.downloader.run() def show_password(self): @@ -369,1329 +388,1150 @@ 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") + Config.PASSWORD_refreshed.emit() 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") + Config.PASSWORD_refreshed.emit() 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") + Config.PASSWORD_refreshed.emit() self.key.setIcon(FluentIcon.HIDE) self.key.setChecked(False) - def change_queue(self, old: str, new: str) -> None: - """修改调度队列配置文件的队列参数""" + def refresh_dashboard(self): + """刷新所有脚本实例的用户仪表盘""" - 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.user_manager.user_dashboard.load_info() - 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.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, - ) - - widget = QWidget() - Layout = QVBoxLayout(widget) - Layout.addWidget(self.card_TaskTransitionMethod) - Layout.addWidget(self.ProxyTimesLimit) - Layout.addWidget(self.AnnihilationTimeLimit) - Layout.addWidget(self.RoutineTimeLimit) - Layout.addWidget(self.RunTimesLimit) - 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]) + self.script_list[index - 1].user_setting.user_manager.switch_SettingBox( + "用户仪表盘" + ) - 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(f"用户_{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 name == "用户仪表盘": + logger.warning("试图删除用户仪表盘") + MainInfoBar.push_info_bar( + "warning", "未选择用户", "请勿尝试删除用户仪表盘", 5000 + ) + return None + + if self.name in Config.running_list: + logger.warning("所属脚本正在运行") + MainInfoBar.push_info_bar( + "warning", "所属脚本正在运行", "请先停止任务", 5000 + ) + return None + + choice = MessageBox( + "确认", + f"确定要删除 {name} 吗?", + self.window(), ) - 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( + f"用户_{max(int(name[3:]) - 1, 1)}" ) - 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}" + + logger.success(f"{self.name} {name} 删除成功") + MainInfoBar.push_info_bar( + "success", "操作成功", f"{self.name} 删除 {name}", 3000 + ) + + def left_user(self): + """向前移动用户""" + + name = self.user_manager.pivot.currentRouteKey() + + if name == None: + logger.warning("未选择用户") + MainInfoBar.push_info_bar( + "warning", "未选择用户", "请先选择一个用户", 5000 + ) + return None + if name == "用户仪表盘": + logger.warning("试图移动用户仪表盘") + MainInfoBar.push_info_bar( + "warning", "未选择用户", "请勿尝试移动用户仪表盘", 5000 + ) + return None + + index = int(name[3:]) + + if index == 1: + logger.warning("向前移动用户时已到达最左端") + MainInfoBar.push_info_bar( + "warning", "已经是第一个用户", "无法向前移动", 5000 + ) + return None + + if self.name in Config.running_list: + logger.warning("所属脚本正在运行") + MainInfoBar.push_info_bar( + "warning", "所属脚本正在运行", "请先停止任务", 5000 + ) + return None + + self.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(f"用户_{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 + if name == "用户仪表盘": + 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(f"用户_{index + 1}") + + logger.success(f"{self.name} {name} 后移成功") + MainInfoBar.push_info_bar( + "success", "操作成功", f"{self.name} 后移 {name}", 3000 + ) + + 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.user_dashboard = self.UserDashboard(self.name, self) + self.user_dashboard.switch_to.connect(self.switch_SettingBox) + self.stackedWidget.addWidget(self.user_dashboard) + self.pivot.addItem(routeKey="用户仪表盘", text="用户仪表盘") + + 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( + index, if_change_pivot=False + ) + ) + + self.show_SettingBox("用户仪表盘") + + def show_SettingBox(self, index: str) -> None: + """加载所有子界面""" + + Config.search_maa_user(self.name) + + for name in Config.member_dict[self.name]["UserData"].keys(): + self.add_userSettingBox(name[3:]) + + self.switch_SettingBox(index) + + def switch_SettingBox( + self, index: str, if_change_pivot: bool = True + ) -> None: + """切换到指定的子界面""" + + if len(Config.member_dict[self.name]["UserData"]) == 0: + index = "用户仪表盘" + + if index != "用户仪表盘" and int(index[3:]) > len( + Config.member_dict[self.name]["UserData"] + ): + return None + + if index == "用户仪表盘": + self.user_dashboard.load_info() + + if if_change_pivot: + self.pivot.setCurrentItem(index) + self.stackedWidget.setCurrentWidget( + self.user_dashboard + if index == "用户仪表盘" + else self.script_list[int(index[3:]) - 1] + ) + + def clear_SettingBox(self) -> None: + """清空所有子界面""" + + for sub_interface in self.script_list: + Config.gameid_refreshed.disconnect( + sub_interface.refresh_gameid + ) + Config.PASSWORD_refreshed.disconnect( + sub_interface.refresh_password + ) + self.stackedWidget.removeWidget(sub_interface) + sub_interface.deleteLater() + self.script_list.clear() + self.pivot.clear() + self.user_dashboard.dashboard.setRowCount(0) + self.stackedWidget.addWidget(self.user_dashboard) + self.pivot.addItem(routeKey="用户仪表盘", text="用户仪表盘") + + 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 UserDashboard(HeaderCardWidget): + """用户仪表盘页面""" + + switch_to = Signal(str) + + def __init__(self, name: str, parent=None): + super().__init__(parent) + self.setObjectName("用户仪表盘") + self.setTitle("用户仪表盘") + self.name = name + + self.dashboard = TableWidget(self) + self.dashboard.setColumnCount(10) + self.dashboard.setHorizontalHeaderLabels( + [ + "用户名", + "账号ID", + "密码", + "状态", + "代理情况", + "给药量", + "关卡选择", + "备选关卡-1", + "备选关卡-2", + "详", + ] + ) + self.dashboard.setEditTriggers(TableWidget.NoEditTriggers) + self.dashboard.verticalHeader().setVisible(False) + for col in range(6): + self.dashboard.horizontalHeader().setSectionResizeMode( + col, QHeaderView.ResizeMode.ResizeToContents + ) + for col in range(6, 9): + self.dashboard.horizontalHeader().setSectionResizeMode( + col, QHeaderView.ResizeMode.Stretch + ) + self.dashboard.horizontalHeader().setSectionResizeMode( + 9, QHeaderView.ResizeMode.Fixed + ) + self.dashboard.setColumnWidth(9, 32) + + self.viewLayout.addWidget(self.dashboard) + self.viewLayout.setContentsMargins(3, 0, 3, 3) + + Config.PASSWORD_refreshed.connect(self.load_info) + + def load_info(self): + + self.user_data = Config.member_dict[self.name]["UserData"] + + self.dashboard.setRowCount(len(self.user_data)) + + for name, info in self.user_data.items(): + + config = info["Config"] + + text_list = [] + if not config.get(config.Data_IfPassCheck): + text_list.append("未通过人工排查") + text_list.append( + f"今日已代理{config.get(config.Data_ProxyTimes)}次" + if Config.server_date() + == config.get(config.Data_LastProxyDate) + else "今日未进行代理" + ) + text_list.append( + "本周剿灭已完成" + if datetime.strptime( + config.get(config.Data_LastAnnihilationDate), + "%Y-%m-%d", + ).isocalendar()[:2] + == datetime.strptime( + Config.server_date(), "%Y-%m-%d" + ).isocalendar()[:2] + else "本周剿灭未完成" + ) + + button = PrimaryToolButton( + FluentIcon.CHEVRON_RIGHT, self + ) + button.setFixedSize(32, 32) + button.clicked.connect( + partial(self.switch_to.emit, name) + ) + + self.dashboard.setItem( + int(name[3:]) - 1, + 0, + QTableWidgetItem(config.get(config.Info_Name)), + ) + self.dashboard.setItem( + int(name[3:]) - 1, + 1, + QTableWidgetItem(config.get(config.Info_Id)), + ) + self.dashboard.setItem( + int(name[3:]) - 1, + 2, + QTableWidgetItem( + Crypto.AUTO_decryptor( + config.get(config.Info_Password), + Config.PASSWORD, + ) + if Config.PASSWORD + else "******" + ), + ) + self.dashboard.setItem( + int(name[3:]) - 1, + 3, + QTableWidgetItem( + "启用" + if config.get(config.Info_Status) + and config.get(config.Info_RemainedDay) != 0 + else "禁用" + ), + ) + self.dashboard.setItem( + int(name[3:]) - 1, + 4, + QTableWidgetItem(" | ".join(text_list)), + ) + self.dashboard.setItem( + int(name[3:]) - 1, + 5, + QTableWidgetItem( + str(config.get(config.Info_MedicineNumb)) + ), + ) + self.dashboard.setItem( + int(name[3:]) - 1, + 6, + QTableWidgetItem( + Config.gameid_dict["ALL"]["text"][ + Config.gameid_dict["ALL"]["value"].index( + config.get(config.Info_GameId) + ) + ] + if config.get(config.Info_GameId) + in Config.gameid_dict["ALL"]["value"] + else config.get(config.Info_GameId) + ), + ) + self.dashboard.setItem( + int(name[3:]) - 1, + 7, + QTableWidgetItem( + Config.gameid_dict["ALL"]["text"][ + Config.gameid_dict["ALL"]["value"].index( + config.get(config.Info_GameId_1) + ) + ] + if config.get(config.Info_GameId_1) + in Config.gameid_dict["ALL"]["value"] + else config.get(config.Info_GameId_1) + ), + ) + self.dashboard.setItem( + int(name[3:]) - 1, + 8, + QTableWidgetItem( + Config.gameid_dict["ALL"]["text"][ + Config.gameid_dict["ALL"]["value"].index( + config.get(config.Info_GameId_2) + ) + ] + if config.get(config.Info_GameId_2) + in Config.gameid_dict["ALL"]["value"] + else config.get(config.Info_GameId_2) + ), + ) + self.dashboard.setCellWidget( + int(name[3:]) - 1, 9, button + ) + + 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["ALL"]["value"], + texts=Config.gameid_dict["ALL"]["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["ALL"]["value"], + texts=Config.gameid_dict["ALL"]["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["ALL"]["value"], + texts=Config.gameid_dict["ALL"]["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) + self.viewLayout.setContentsMargins(3, 0, 3, 3) - 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 + ) + Config.gameid_refreshed.connect(self.refresh_gameid) + Config.PASSWORD_refreshed.connect(self.refresh_password) + + 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 refresh_gameid(self): + + self.card_GameId.reLoadOptions( + Config.gameid_dict["ALL"]["value"], + Config.gameid_dict["ALL"]["text"], + ) + self.card_GameId_1.reLoadOptions( + Config.gameid_dict["ALL"]["value"], + Config.gameid_dict["ALL"]["text"], + ) + self.card_GameId_2.reLoadOptions( + Config.gameid_dict["ALL"]["value"], + Config.gameid_dict["ALL"]["text"], + ) + + def refresh_password(self): + + self.card_Password.setValue( + self.card_Password.qconfig.get( + self.card_Password.configItem + ) + ) + + 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 5aa6e92..abe5100 100644 --- a/app/ui/setting.py +++ b/app/ui/setting.py @@ -30,8 +30,8 @@ from PySide6.QtWidgets import ( QWidget, QApplication, QVBoxLayout, - QVBoxLayout, ) +from PySide6.QtCore import Qt from qfluentwidgets import ( ScrollArea, FluentIcon, @@ -39,21 +39,33 @@ from qfluentwidgets import ( Dialog, HyperlinkCard, HeaderCardWidget, - SwitchSettingCard, ExpandGroupSettingCard, PushSettingCard, - ComboBoxSettingCard, + HyperlinkButton, ) -from datetime import datetime +import os +import re import json -import subprocess import time +import shutil import requests +import subprocess +from datetime import datetime +from pathlib import Path +from typing import Dict, List, Union from app.core import Config, MainInfoBar -from app.services import Crypto, System -from app.utils import Updater -from .Widget import LineEditMessageBox, LineEditSettingCard, PasswordLineEditSettingCard +from app.services import Crypto, System, Notify +from .Widget import ( + SwitchSettingCard, + RangeSettingCard, + ComboBoxSettingCard, + LineEditMessageBox, + LineEditSettingCard, + PasswordLineEditSettingCard, + UrlListSettingCard, + NoticeMessageBox, +) class Setting(QWidget): @@ -75,9 +87,12 @@ class Setting(QWidget): self.function.card_IfAllowSleep.checkedChanged.connect(System.set_Sleep) self.function.card_IfAgreeBilibili.checkedChanged.connect(self.agree_bilibili) + self.function.card_IfSkipMumuSplashAds.checkedChanged.connect( + self.skip_MuMu_splash_ads + ) self.start.card_IfSelfStart.checkedChanged.connect(System.set_SelfStart) self.security.card_changePASSWORD.clicked.connect(self.change_PASSWORD) - self.updater.card_CheckUpdate.clicked.connect(self.get_update) + self.updater.card_CheckUpdate.clicked.connect(self.check_update) self.other.card_Notice.clicked.connect(self.show_notice) content_layout.addWidget(self.function) @@ -98,26 +113,63 @@ class Setting(QWidget): def agree_bilibili(self) -> None: """授权bilibili游戏隐私政策""" - if not Config.global_config.get(Config.global_config.function_IfAgreeBilibili): + if Config.get(Config.function_IfAgreeBilibili): + + choice = MessageBox( + "授权声明", + "开启“托管bilibili游戏隐私政策”功能,即代表您已完整阅读并同意《哔哩哔哩弹幕网用户使用协议》、《哔哩哔哩隐私政策》和《哔哩哔哩游戏中心用户协议》,并授权AUTO_MAA在其认定需要时以其认定合适的方法替您处理相关弹窗\n\n是否同意授权?", + self.window(), + ) + if choice.exec(): + logger.success("确认授权bilibili游戏隐私政策") + MainInfoBar.push_info_bar( + "success", "操作成功", "已确认授权bilibili游戏隐私政策", 3000 + ) + else: + Config.set(Config.function_IfAgreeBilibili, False) + else: + logger.info("取消授权bilibili游戏隐私政策") MainInfoBar.push_info_bar( "info", "操作成功", "已取消授权bilibili游戏隐私政策", 3000 ) - return None - choice = MessageBox( - "授权声明", - "开启“托管bilibili游戏隐私政策”功能,即代表您已完整阅读并同意《哔哩哔哩弹幕网用户使用协议》、《哔哩哔哩隐私政策》和《哔哩哔哩游戏中心用户协议》,并授权AUTO_MAA在其认定需要时以其认定合适的方法替您处理相关弹窗\n\n是否同意授权?", - self.window(), + def skip_MuMu_splash_ads(self) -> None: + """跳过MuMu启动广告""" + + MuMu_splash_ads_path = ( + Path(os.getenv("APPDATA")) / "Netease/MuMuPlayer-12.0/data/startupImage" ) - if choice.exec(): - logger.success("确认授权bilibili游戏隐私政策") - MainInfoBar.push_info_bar( - "success", "操作成功", "已确认授权bilibili游戏隐私政策", 3000 + + if Config.get(Config.function_IfSkipMumuSplashAds): + + choice = MessageBox( + "风险声明", + "开启“跳过MuMu启动广告”功能,即代表您已安装MuMu模拟器-12且允许AUTO_MAA以其认定合适的方法屏蔽MuMu启动广告,并接受此操作带来的风险\n\n此功能即时生效,是否仍要开启此功能?", + self.window(), ) + if choice.exec(): + + if MuMu_splash_ads_path.exists() and MuMu_splash_ads_path.is_dir(): + shutil.rmtree(MuMu_splash_ads_path) + + MuMu_splash_ads_path.touch() + + logger.success("开启跳过MuMu启动广告功能") + MainInfoBar.push_info_bar( + "success", "操作成功", "已开启跳过MuMu启动广告功能", 3000 + ) + else: + Config.set(Config.function_IfSkipMumuSplashAds, False) + else: - Config.global_config.set( - Config.global_config.function_IfAgreeBilibili, False + + if MuMu_splash_ads_path.exists() and MuMu_splash_ads_path.is_file(): + MuMu_splash_ads_path.unlink() + + logger.info("关闭跳过MuMu启动广告功能") + MainInfoBar.push_info_bar( + "info", "操作成功", "已关闭跳过MuMu启动广告功能", 3000 ) def check_PASSWORD(self) -> None: @@ -210,65 +262,20 @@ class Setting(QWidget): if choice.exec(): break - def get_update_info(self) -> str: - """检查主程序版本更新,返回更新信息""" - - # 从本地版本信息文件获取当前版本信息 - with Config.version_path.open(mode="r", encoding="utf-8") as f: - version_current = json.load(f) - main_version_current = list( - map(int, version_current["main_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" - ) - version_remote = response.json() - break - except Exception as e: - err = e - time.sleep(0.1) - else: - return f"获取版本信息时出错:\n{err}" - - main_version_remote = list(map(int, version_remote["main_version"].split("."))) - - # 有版本更新 - if main_version_remote > main_version_current: - - main_version_info = f" 主程序:{version_text(main_version_current)} --> {version_text(main_version_remote)}\n" - - return f"发现新版本:\n{main_version_info} 更新说明:\n{version_remote['announcement'].replace("\n# ","\n !").replace("\n## ","\n - ").replace("\n- ","\n · ")}\n\n是否开始更新?\n\n 注意:主程序更新时AUTO_MAA将自动关闭" - - else: - return "已是最新版本~" - - def get_update(self, if_question: bool = True) -> None: + def check_update(self) -> None: """检查版本更新,调起文件下载进程""" - # 从本地版本信息文件获取当前版本信息 - with Config.version_path.open(mode="r", encoding="utf-8") as f: - version_current = json.load(f) - main_version_current = list( - map(int, version_current["main_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 = response.json() break except Exception as e: err = e @@ -284,74 +291,126 @@ 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(".")) + 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("."), + ) ) # 有版本更新 - if (main_version_remote > main_version_current) or ( - updater_version_remote > updater_version_current - ): + if remote_version > current_version: + + 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: - main_version_info = f" 主程序:{version_text(main_version_current)} --> {version_text(main_version_remote)}\n" - else: - main_version_info = ( - f" 主程序:{version_text(main_version_current)}\n" - ) - if updater_version_remote > updater_version_current: - updater_version_info = f" 更新器:{version_text(updater_version_current)} --> {version_text(updater_version_remote)}\n" - else: - updater_version_info = ( - f" 更新器:{version_text(updater_version_current)}\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_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_info_json.values(): + for key, value in v_i.items(): + if key in all_version_info: + all_version_info[key] += value.copy() + else: + all_version_info[key] = value.copy() + + 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_info_json.items() + }, + } # 询问是否开始版本更新 - if if_question: - choice = MessageBox( - "版本更新", - f"发现新版本:\n{main_version_info}{updater_version_info} 更新说明:\n{version_remote['announcement'].replace("\n# ","\n !").replace("\n## ","\n - ").replace("\n- ","\n · ")}\n\n是否开始更新?\n\n 注意:主程序更新时AUTO_MAA将自动关闭", - self.window(), - ) - if not choice.exec(): + choice = NoticeMessageBox(self.window(), "版本更新", version_info) + if choice.exec(): + + 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 - # 更新更新器 - if updater_version_remote > updater_version_current: - # 创建更新进程 - self.updater = Updater( - Config.app_path, - "AUTO_MAA更新器", - main_version_remote, - updater_version_remote, + 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.update_process.accomplish.connect(self.update_main) - # 显示更新页面 - self.updater.show() - - # 更新主程序 - 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: - """更新主程序""" - - 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): """显示公告""" @@ -388,14 +447,22 @@ class Setting(QWidget): else: time_local = datetime.strptime("2000-01-01 00:00", "%Y-%m-%d %H:%M") + notice["notice_dict"] = { + "ALL~公告": "\n---\n".join( + [str(_) for _ in notice["notice_dict"].values() if isinstance(_, str)] + ), + **notice["notice_dict"], + } + 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 = Dialog("公告", notice["content"], self) - choice.cancelButton.hide() - choice.buttonLayout.insertStretch(1) + choice = NoticeMessageBox(self.window(), "公告", notice["notice_dict"]) + choice.button_cancel.hide() + choice.button_layout.insertStretch(0, 1) if choice.exec(): with (Config.app_path / "resources/notice.json").open( mode="w", encoding="utf-8" @@ -410,31 +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模拟器时屏蔽启动广告", + qconfig=Config, + configItem=Config.function_IfSkipMumuSplashAds, + parent=self, ) Layout = QVBoxLayout() @@ -443,6 +526,7 @@ class FunctionSettingCard(HeaderCardWidget): Layout.addWidget(self.card_IfAllowSleep) Layout.addWidget(self.card_IfSilence) Layout.addWidget(self.card_IfAgreeBilibili) + Layout.addWidget(self.card_IfSkipMumuSplashAds) self.viewLayout.addLayout(Layout) class SilenceSettingCard(ExpandGroupSettingCard): @@ -459,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() @@ -488,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() @@ -520,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() @@ -547,6 +645,14 @@ class NotifySettingCard(HeaderCardWidget): self.card_EMail = self.EMailSettingCard(self) self.card_ServerChan = self.ServerChanSettingCard(self) self.card_CompanyWebhookBot = self.CompanyWechatPushSettingCard(self) + self.card_TestNotification = PushSettingCard( + text="发送测试通知", + icon=FluentIcon.SEND, + title="测试通知", + content="发送测试通知到所有已启用的通知渠道", + parent=self, + ) + self.card_TestNotification.clicked.connect(self.send_test_notification) Layout = QVBoxLayout() Layout.addWidget(self.card_NotifyContent) @@ -554,8 +660,19 @@ class NotifySettingCard(HeaderCardWidget): Layout.addWidget(self.card_EMail) Layout.addWidget(self.card_ServerChan) Layout.addWidget(self.card_CompanyWebhookBot) + Layout.addWidget(self.card_TestNotification) self.viewLayout.addLayout(Layout) + def send_test_notification(self): + """发送测试通知到所有已启用的通知渠道""" + if Notify.send_test_notification(): + MainInfoBar.push_info_bar( + "success", + "测试通知已发送", + "请检查已配置的通知渠道是否正常收到消息", + 3000, + ) + class NotifyContentSettingCard(ExpandGroupSettingCard): def __init__(self, parent=None): @@ -564,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() @@ -603,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() @@ -624,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() @@ -679,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() @@ -726,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() @@ -756,6 +904,7 @@ class SecuritySettingCard(HeaderCardWidget): icon=FluentIcon.VPN, title="修改管理密钥", content="修改用于解密用户密码的管理密钥", + parent=self, ) Layout = QVBoxLayout() @@ -769,30 +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_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_CheckUpdate) + Layout.addWidget(self.card_ThreadNumb) + Layout.addWidget(self.card_ProxyUrlList) + Layout.addWidget(self.card_MirrorChyanCDK) self.viewLayout.addLayout(Layout) @@ -807,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", @@ -814,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) @@ -839,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", @@ -846,6 +1038,7 @@ class OtherSettingCard(HeaderCardWidget): icon=FluentIcon.CHAT, title="QQ群", content="与AUTO_MAA开发者和用户交流", + parent=self, ) widget = QWidget() @@ -867,3 +1060,14 @@ def version_text(version_numb: list) -> str: f"v{'.'.join(str(_) for _ in version_numb[0:3])}-beta.{version_numb[3]}" ) return version + + +def version_info_markdown(info: dict) -> str: + """将版本信息字典转为markdown信息""" + + version_info = "" + for key, value in info.items(): + version_info += f"### {key}\n\n" + for v in value: + version_info += f"- {v}\n\n" + return version_info diff --git a/app/utils/Updater.py b/app/utils/Updater.py deleted file mode 100644 index 859de70..0000000 --- a/app/utils/Updater.py +++ /dev/null @@ -1,490 +0,0 @@ -# -# Copyright © <2024> - -# This file is part of AUTO_MAA. - -# AUTO_MAA is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published -# by the Free Software Foundation, either version 3 of the License, -# or (at your option) any later version. - -# AUTO_MAA is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty -# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See -# the GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with AUTO_MAA. If not, see . - -# DLmaster_361@163.com - -""" -AUTO_MAA -AUTO_MAA更新器 -v1.1 -作者:DLmaster_361 -""" - -import sys -import json -import zipfile -import requests -import subprocess -import time -from pathlib import Path - -from PySide6.QtWidgets import QApplication, QDialog, QVBoxLayout, QHBoxLayout -from qfluentwidgets import ( - ProgressBar, - IndeterminateProgressBar, - BodyLabel, - PushButton, - EditableComboBox, -) -from PySide6.QtGui import QIcon, QCloseEvent -from PySide6.QtCore import QThread, Signal, QEventLoop - - -def version_text(version_numb: list) -> str: - """将版本号列表转为可读的文本信息""" - - if version_numb[3] == 0: - version = f"v{'.'.join(str(_) for _ in version_numb[0:3])}" - else: - version = ( - f"v{'.'.join(str(_) for _ in version_numb[0:3])}-beta.{version_numb[3]}" - ) - return version - - -class UpdateProcess(QThread): - - info = Signal(str) - progress = Signal(int, int, int) - question = Signal(dict) - question_response = Signal(str) - accomplish = Signal() - - def __init__( - self, app_path: Path, name: str, main_version: list, updater_version: list - ) -> None: - super(UpdateProcess, self).__init__() - - self.app_path = app_path - self.name = name - self.main_version = main_version - self.updater_version = updater_version - self.download_path = app_path / "DOWNLOAD_TEMP.zip" # 临时下载文件的路径 - self.version_path = app_path / "resources/version.json" - self.response = None - - self.question_response.connect(self._capture_response) - - def run(self) -> None: - - # 清理可能存在的临时文件 - if self.download_path.exists(): - self.download_path.unlink() - - self.info.emit("正在获取下载链接") - url_list = self.get_download_url() - url_dict = {} - - # 验证下载地址 - for i, url in enumerate(url_list): - - if self.isInterruptionRequested(): - return None - - self.progress.emit(0, len(url_list), i) - - try: - self.info.emit(f"正在验证下载地址:{url}") - response = requests.get(url, stream=True) - if response.status_code != 200: - self.info.emit(f"连接失败,错误代码 {response.status_code}") - time.sleep(1) - continue - url_dict[url] = response.elapsed.total_seconds() - except requests.RequestException: - self.info.emit(f"请求超时") - time.sleep(1) - - download_url = self.push_question(url_dict) - - # 获取文件大小 - try: - self.info.emit(f"正在连接下载地址:{download_url}") - self.progress.emit(0, 0, 0) - response = requests.get(download_url, stream=True) - if response.status_code != 200: - self.info.emit(f"连接失败,错误代码 {response.status_code}") - return None - file_size = response.headers.get("Content-Length") - except requests.RequestException: - self.info.emit(f"请求超时") - return None - - if file_size is None: - file_size = 1 - else: - file_size = int(file_size) - - try: - # 下载文件 - with open(self.download_path, "wb") as f: - - downloaded_size = 0 - last_download_size = 0 - speed = 0 - last_time = time.time() - - for chunk in response.iter_content(chunk_size=8192): - - if self.isInterruptionRequested(): - break - - # 写入已下载数据 - f.write(chunk) - downloaded_size += len(chunk) - - # 计算下载速度 - if time.time() - last_time >= 1.0: - speed = ( - (downloaded_size - last_download_size) - / (time.time() - last_time) - / 1024 - ) - last_download_size = downloaded_size - last_time = time.time() - - # 更新下载进度 - if speed >= 1024: - self.info.emit( - f"正在下载:{self.name} 已下载:{downloaded_size / 1048576:.2f}/{file_size / 1048576:.2f} MB ({downloaded_size / file_size * 100:.2f}%) 下载速度:{speed / 1024:.2f} MB/s", - ) - else: - self.info.emit( - f"正在下载:{self.name} 已下载:{downloaded_size / 1048576:.2f}/{file_size / 1048576:.2f} MB ({downloaded_size / file_size * 100:.2f}%) 下载速度:{speed:.2f} KB/s", - ) - self.progress.emit(0, 100, int(downloaded_size / file_size * 100)) - - if self.isInterruptionRequested() and self.download_path.exists(): - self.download_path.unlink() - return None - - except Exception as e: - e = str(e) - e = "\n".join([e[_ : _ + 75] for _ in range(0, len(e), 75)]) - self.info.emit(f"下载{self.name}时出错:\n{e}") - return None - - # 解压 - try: - - while True: - if self.isInterruptionRequested(): - self.download_path.unlink() - return None - try: - self.info.emit("正在解压更新文件") - self.progress.emit(0, 0, 0) - with zipfile.ZipFile(self.download_path, "r") as zip_ref: - zip_ref.extractall(self.app_path) - break - except PermissionError: - self.info.emit(f"解压出错:{self.name}正在运行,正在等待其关闭") - time.sleep(1) - - self.info.emit("正在删除临时文件") - self.progress.emit(0, 0, 0) - self.download_path.unlink() - - self.info.emit(f"{self.name}更新成功!") - self.progress.emit(0, 100, 100) - - except Exception as e: - - e = str(e) - e = "\n".join([e[_ : _ + 75] for _ in range(0, len(e), 75)]) - self.info.emit(f"解压更新时出错:\n{e}") - return None - - # 更新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) - - # 主程序更新完成后打开AUTO_MAA - if not self.isInterruptionRequested and self.name == "AUTO_MAA主程序": - subprocess.Popen( - str(self.app_path / "AUTO_MAA.exe"), - shell=True, - creationflags=subprocess.CREATE_NO_WINDOW, - ) - elif not self.isInterruptionRequested and self.name == "MAA": - subprocess.Popen( - str(self.app_path / "MAA.exe"), - shell=True, - creationflags=subprocess.CREATE_NO_WINDOW, - ) - - self.accomplish.emit() - - def get_download_url(self) -> list: - """获取下载链接""" - - try_num = 3 - for i in range(try_num): - try: - response = requests.get( - "https://gitee.com/DLmaster_361/AUTO_MAA/raw/main/resources/version.json" - ) - if response.status_code != 200: - self.info.emit( - f"连接失败,错误代码 {response.status_code} ,正在重试({i+1}/{try_num})" - ) - time.sleep(0.1) - continue - version_remote = response.json() - PROXY_list = version_remote["proxy_list"] - break - except requests.RequestException: - self.info.emit(f"请求超时,正在重试({i+1}/{try_num})") - time.sleep(0.1) - except KeyError: - self.info.emit(f"未找到远端代理网址项,正在重试({i+1}/{try_num})") - time.sleep(0.1) - else: - self.info.emit("获取远端代理信息失败,将使用默认代理地址") - PROXY_list = [ - "", - "https://gitproxy.click/", - "https://cdn.moran233.xyz/", - "https://gh.llkk.cc/", - "https://github.akams.cn/", - "https://www.ghproxy.cn/", - "https://ghfast.top/", - ] - time.sleep(1) - - url_list = [] - if self.name == "AUTO_MAA主程序": - url_list.append( - f"https://gitee.com/DLmaster_361/AUTO_MAA/releases/download/{version_text(self.main_version)}/AUTO_MAA_{version_text(self.main_version)}.zip" - ) - url_list.append( - f"https://jp-download.fearr.xyz/AUTO_MAA/AUTO_MAA_{version_text(self.main_version)}.zip" - ) - for i in range(len(PROXY_list)): - url_list.append( - f"{PROXY_list[i]}https://github.com/DLmaster361/AUTO_MAA/releases/download/{version_text(self.main_version)}/AUTO_MAA_{version_text(self.main_version)}.zip" - ) - elif self.name == "AUTO_MAA更新器": - url_list.append( - f"https://gitee.com/DLmaster_361/AUTO_MAA/releases/download/{version_text(self.main_version)}/Updater_{version_text(self.updater_version)}.zip" - ) - url_list.append( - f"https://jp-download.fearr.xyz/AUTO_MAA/Updater_{version_text(self.updater_version)}.zip" - ) - for i in range(len(PROXY_list)): - url_list.append( - f"{PROXY_list[i]}https://github.com/DLmaster361/AUTO_MAA/releases/download/{version_text(self.main_version)}/Updater_{version_text(self.updater_version)}.zip" - ) - elif self.name == "MAA": - url_list.append( - f"https://jp-download.fearr.xyz/MAA/MAA-{version_text(self.main_version)}-win-x64.zip" - ) - for i in range(len(PROXY_list)): - url_list.append( - f"{PROXY_list[i]}https://github.com/MaaAssistantArknights/MaaAssistantArknights/releases/download/{version_text(self.main_version)}/MAA-{version_text(self.main_version)}-win-x64.zip" - ) - return url_list - - def push_question(self, url_dict: dict) -> str: - self.question.emit(url_dict) - loop = QEventLoop() - self.question_response.connect(loop.quit) - loop.exec() - return self.response - - def _capture_response(self, response: str) -> None: - self.response = response - - -class Updater(QDialog): - - def __init__( - self, app_path: Path, name: str, main_version: list, updater_version: list - ) -> None: - super().__init__() - - self.setWindowTitle("AUTO_MAA更新器") - self.setWindowIcon( - QIcon( - str( - Path(sys.argv[0]).resolve().parent - / "resources/icons/AUTO_MAA_Updater.ico" - ) - ) - ) - - # 创建垂直布局 - self.Layout = QVBoxLayout(self) - - self.info = BodyLabel("正在初始化", self) - self.progress_1 = IndeterminateProgressBar(self) - self.progress_2 = ProgressBar(self) - self.combo_box = EditableComboBox(self) - - self.button = PushButton("继续", self) - self.h_layout = QHBoxLayout() - self.h_layout.addStretch(1) - self.h_layout.addWidget(self.button) - - self.update_progress(0, 0, 0) - - self.Layout.addWidget(self.info) - self.Layout.addStretch(1) - self.Layout.addWidget(self.progress_1) - self.Layout.addWidget(self.progress_2) - self.Layout.addWidget(self.combo_box) - self.Layout.addLayout(self.h_layout) - self.Layout.addStretch(1) - - self.update_process = UpdateProcess( - app_path, name, main_version, updater_version - ) - - self.update_process.info.connect(self.update_info) - self.update_process.progress.connect(self.update_progress) - self.update_process.question.connect(self.question) - - self.update_process.start() - - def update_info(self, text: str) -> None: - self.info.setText(text) - - def update_progress( - self, begin: int, end: int, current: int, if_show_combo_box: bool = False - ) -> None: - - self.combo_box.setVisible(if_show_combo_box) - self.button.setVisible(if_show_combo_box) - - if if_show_combo_box: - self.progress_1.setVisible(False) - self.progress_2.setVisible(False) - self.resize(1000, 90) - elif begin == 0 and end == 0: - self.progress_2.setVisible(False) - self.progress_1.setVisible(True) - self.resize(700, 70) - else: - self.progress_1.setVisible(False) - self.progress_2.setVisible(True) - self.progress_2.setRange(begin, end) - self.progress_2.setValue(current) - self.resize(700, 70) - - def question(self, url_dict: dict) -> None: - - self.update_info("测速完成,请选择或自行输入一个合适下载地址:") - self.update_progress(0, 0, 0, True) - - url_dict = dict(sorted(url_dict.items(), key=lambda item: item[1])) - - for url, time in url_dict.items(): - self.combo_box.addItem(f"{url} | 响应时间:{time:.3f}秒") - - self.button.clicked.connect( - lambda: self.update_process.question_response.emit( - self.combo_box.currentText().split(" | ")[0] - ) - ) - - def closeEvent(self, event: QCloseEvent): - """清理残余进程""" - - self.update_process.requestInterruption() - self.update_process.quit() - self.update_process.wait() - - event.accept() - - -class AUTO_MAA_Updater(QApplication): - def __init__( - self, app_path: Path, name: str, main_version: list, updater_version: list - ) -> None: - super().__init__() - - self.main = Updater(app_path, name, main_version, updater_version) - self.main.show() - - -if __name__ == "__main__": - - # 获取软件自身的路径 - app_path = Path(sys.argv[0]).resolve().parent - - # 从本地版本信息文件获取当前版本信息 - if (app_path / "resources/version.json").exists(): - 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(".")) - ) - else: - main_version_current = [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" - else: - update_type = "main" - - # 从远程服务器获取最新版本信息 - for _ in range(3): - try: - response = requests.get( - f"https://gitee.com/DLmaster_361/AUTO_MAA/raw/{update_type}/resources/version.json" - ) - version_remote = response.json() - main_version_remote = list( - map(int, version_remote["main_version"].split(".")) - ) - break - except Exception as e: - err = e - time.sleep(0.1) - else: - sys.exit(f"获取版本信息时出错:\n{err}") - - # 启动更新线程 - if main_version_remote > main_version_current: - app = AUTO_MAA_Updater( - app_path, - "AUTO_MAA主程序", - main_version_remote, - [], - ) - sys.exit(app.exec()) diff --git a/app/utils/__init__.py b/app/utils/__init__.py index 3cbc288..5050f6e 100644 --- a/app/utils/__init__.py +++ b/app/utils/__init__.py @@ -29,6 +29,6 @@ __version__ = "4.2.0" __author__ = "DLmaster361 " __license__ = "GPL-3.0 license" -from .Updater import Updater +from .downloader import DownloadManager -__all__ = ["Updater"] +__all__ = ["DownloadManager"] diff --git a/app/utils/downloader.py b/app/utils/downloader.py new file mode 100644 index 0000000..d02e6bc --- /dev/null +++ b/app/utils/downloader.py @@ -0,0 +1,722 @@ +# +# Copyright © <2024> + +# This file is part of AUTO_MAA. + +# AUTO_MAA is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published +# by the Free Software Foundation, either version 3 of the License, +# or (at your option) any later version. + +# AUTO_MAA is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty +# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See +# the GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with AUTO_MAA. If not, see . + +# DLmaster_361@163.com + +""" +AUTO_MAA +AUTO_MAA更新器 +v1.2 +作者:DLmaster_361 +""" + +import sys +import json +import zipfile +import requests +import subprocess +import time +import win32crypt +import base64 +from functools import partial +from pathlib import Path + +from PySide6.QtWidgets import QApplication, QDialog, QVBoxLayout +from qfluentwidgets import ( + ProgressBar, + IndeterminateProgressBar, + BodyLabel, + setTheme, + Theme, +) +from PySide6.QtGui import QIcon, QCloseEvent +from PySide6.QtCore import QThread, Signal, QTimer, QEventLoop + +from typing import List, Dict, Union + + +def version_text(version_numb: list) -> str: + """将版本号列表转为可读的文本信息""" + + if version_numb[3] == 0: + version = f"v{'.'.join(str(_) for _ in version_numb[0:3])}" + else: + version = ( + f"v{'.'.join(str(_) for _ in version_numb[0:3])}-beta.{version_numb[3]}" + ) + return version + + +class DownloadProcess(QThread): + """分段下载子线程""" + + progress = Signal(int) + accomplish = Signal(float) + + def __init__( + self, + url: str, + start_byte: int, + end_byte: int, + download_path: Path, + check_times: int = -1, + ) -> None: + super(DownloadProcess, self).__init__() + + self.url = url + self.start_byte = start_byte + self.end_byte = end_byte + self.download_path = download_path + self.check_times = check_times + + def run(self) -> None: + + # 清理可能存在的临时文件 + if self.download_path.exists(): + self.download_path.unlink() + + headers = {"Range": f"bytes={self.start_byte}-{self.end_byte}"} + + while not self.isInterruptionRequested() and self.check_times != 0: + + try: + + start_time = time.time() + + response = requests.get( + self.url, headers=headers, timeout=10, stream=True + ) + + if response.status_code != 206: + + if self.check_times != -1: + self.check_times -= 1 + + time.sleep(1) + continue + + downloaded_size = 0 + with self.download_path.open(mode="wb") as f: + + for chunk in response.iter_content(chunk_size=8192): + + if self.isInterruptionRequested(): + break + + f.write(chunk) + downloaded_size += len(chunk) + + self.progress.emit(downloaded_size) + + if self.isInterruptionRequested(): + + if self.download_path.exists(): + self.download_path.unlink() + self.accomplish.emit(0) + + else: + + self.accomplish.emit(time.time() - start_time) + + break + + except Exception as e: + + if self.check_times != -1: + self.check_times -= 1 + time.sleep(1) + + else: + + if self.download_path.exists(): + self.download_path.unlink() + self.accomplish.emit(0) + + +class ZipExtractProcess(QThread): + """解压子线程""" + + info = Signal(str) + accomplish = Signal() + + def __init__(self, name: str, app_path: Path, download_path: Path) -> None: + super(ZipExtractProcess, self).__init__() + + self.name = name + self.app_path = app_path + self.download_path = download_path + + def run(self) -> None: + + try: + + while True: + + if self.isInterruptionRequested(): + self.download_path.unlink() + return None + try: + with zipfile.ZipFile(self.download_path, "r") as zip_ref: + zip_ref.extractall(self.app_path) + self.accomplish.emit() + break + except PermissionError: + self.info.emit(f"解压出错:{self.name}正在运行,正在等待其关闭") + time.sleep(1) + + except Exception as e: + + e = str(e) + e = "\n".join([e[_ : _ + 75] for _ in range(0, len(e), 75)]) + self.info.emit(f"解压更新时出错:\n{e}") + return None + + +class DownloadManager(QDialog): + """下载管理器""" + + speed_test_accomplish = Signal() + download_accomplish = Signal() + download_process_clear = Signal() + + isInterruptionRequested = False + + def __init__( + self, + app_path: Path, + name: str, + main_version: list, + config: dict, + ) -> None: + super().__init__() + + self.app_path = app_path + self.name = name + self.main_version = main_version + self.config = config + self.download_path = app_path / "DOWNLOAD_TEMP.zip" # 临时下载文件的路径 + self.version_path = app_path / "resources/version.json" + self.download_process_dict: Dict[str, DownloadProcess] = {} + self.timer_dict: Dict[str, QTimer] = {} + + self.setWindowTitle("AUTO_MAA更新器") + self.setWindowIcon( + QIcon(str(app_path / "resources/icons/AUTO_MAA_Updater.ico")) + ) + self.resize(700, 70) + + setTheme(Theme.AUTO, lazy=True) + + # 创建垂直布局 + self.Layout = QVBoxLayout(self) + + self.info = BodyLabel("正在初始化", self) + self.progress_1 = IndeterminateProgressBar(self) + self.progress_2 = ProgressBar(self) + + self.update_progress(0, 0, 0) + + self.Layout.addWidget(self.info) + self.Layout.addStretch(1) + self.Layout.addWidget(self.progress_1) + self.Layout.addWidget(self.progress_2) + self.Layout.addStretch(1) + + def run(self) -> None: + + if self.name == "MAA": + 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]]: + """获取下载链接""" + + url_dict = {} + + if mode == "测速": + + url_dict["GitHub站"] = ( + f"https://github.com/DLmaster361/AUTO_MAA/releases/download/{version_text(self.main_version)}/AUTO_MAA_{version_text(self.main_version)}.zip" + ) + url_dict["官方镜像站"] = ( + f"https://gitee.com/DLmaster_361/AUTO_MAA/releases/download/{version_text(self.main_version)}/AUTO_MAA_{version_text(self.main_version)}.zip" + ) + for name, download_url_head in self.config["download_dict"].items(): + url_dict[name] = ( + f"{download_url_head}AUTO_MAA_{version_text(self.main_version)}.zip" + ) + for proxy_url in self.config["proxy_list"]: + url_dict[proxy_url] = ( + f"{proxy_url}https://github.com/DLmaster361/AUTO_MAA/releases/download/{version_text(self.main_version)}/AUTO_MAA_{version_text(self.main_version)}.zip" + ) + return url_dict + + elif mode == "下载": + + if self.name == "MAA": + return f"https://jp-download.fearr.xyz/MAA/MAA-{version_text(self.main_version)}-win-x64.zip" + + if self.name == "AUTO_MAA": + + if self.config["mode"] == "Proxy": + + 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 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" + + 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: + + if self.isInterruptionRequested: + return None + + url_dict = self.get_download_url("测速") + self.test_speed_result: Dict[str, float] = {} + + for name, url in url_dict.items(): + + if self.isInterruptionRequested: + break + + # 创建测速线程,下载4MB文件以测试下载速度 + self.download_process_dict[name] = DownloadProcess( + url, + 0, + 4194304, + self.app_path / f"{name.replace('/','').replace(':','')}.zip", + 10, + ) + self.test_speed_result[name] = -1 + self.download_process_dict[name].accomplish.connect( + partial(self.test_speed_task2, name) + ) + + self.download_process_dict[name].start() + timer = QTimer(self) + timer.setSingleShot(True) + timer.timeout.connect(partial(self.kill_speed_test, name)) + timer.start(30000) + self.timer_dict[name] = timer + + self.update_info("正在测速,预计用时30秒") + self.update_progress(0, 1, 0) + + def kill_speed_test(self, name: str) -> None: + + if name in self.download_process_dict: + self.download_process_dict[name].requestInterruption() + + def test_speed_task2(self, name: str, t: float) -> None: + + # 计算下载速度 + if self.isInterruptionRequested: + self.update_info(f"已中止测速进程:{name}") + self.test_speed_result[name] = 0 + elif t != 0: + self.update_info(f"{name}:{ 4 / t:.2f} MB/s") + self.test_speed_result[name] = 4 / t + else: + self.update_info(f"{name}:{ 0:.2f} MB/s") + self.test_speed_result[name] = 0 + self.update_progress( + 0, + len(self.test_speed_result), + sum(1 for speed in self.test_speed_result.values() if speed != -1), + ) + + # 删除临时文件 + if (self.app_path / f"{name.replace('/','').replace(':','')}.zip").exists(): + (self.app_path / f"{name.replace('/','').replace(':','')}.zip").unlink() + + # 清理下载线程 + self.timer_dict[name].stop() + self.timer_dict[name].deleteLater() + self.timer_dict.pop(name) + self.download_process_dict[name].requestInterruption() + self.download_process_dict[name].quit() + self.download_process_dict[name].wait() + self.download_process_dict[name].deleteLater() + self.download_process_dict.pop(name) + if not self.download_process_dict: + self.download_process_clear.emit() + + if any(speed == -1 for _, speed in self.test_speed_result.items()): + return None + + # 保存测速结果 + self.config["speed_result"] = self.test_speed_result + + self.update_info("测速完成!") + self.speed_test_accomplish.emit() + + def download_task1(self) -> None: + + if self.isInterruptionRequested: + return None + + url = self.get_download_url("下载") + self.downloaded_size_list: List[List[int, bool]] = [] + + response = requests.head(url) + + self.file_size = int(response.headers.get("content-length", 0)) + part_size = self.file_size // self.config["thread_numb"] + self.downloaded_size = 0 + self.last_download_size = 0 + self.last_time = time.time() + self.speed = 0 + + # 拆分下载任务,启用多线程下载 + for i in range(self.config["thread_numb"]): + + if self.isInterruptionRequested: + break + + # 计算单任务下载范围 + start_byte = i * part_size + end_byte = ( + (i + 1) * part_size - 1 + if (i != self.config["thread_numb"] - 1) + else self.file_size - 1 + ) + + # 创建下载子线程 + self.download_process_dict[f"part{i}"] = DownloadProcess( + 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( + partial(self.download_task2, i) + ) + self.download_process_dict[f"part{i}"].accomplish.connect( + partial(self.download_task3, i) + ) + self.download_process_dict[f"part{i}"].start() + + def download_task2(self, index: str, current: int) -> None: + """更新下载进度""" + + self.downloaded_size_list[index][0] = current + self.downloaded_size = sum([_[0] for _ in self.downloaded_size_list]) + self.update_progress(0, self.file_size, self.downloaded_size) + + if time.time() - self.last_time >= 1.0: + self.speed = ( + (self.downloaded_size - self.last_download_size) + / (time.time() - self.last_time) + / 1024 + ) + self.last_download_size = self.downloaded_size + self.last_time = time.time() + + if self.speed >= 1024: + self.update_info( + f"正在下载:{self.name} 已下载:{self.downloaded_size / 1048576:.2f}/{self.file_size / 1048576:.2f} MB ({self.downloaded_size / self.file_size * 100:.2f}%) 下载速度:{self.speed / 1024:.2f} MB/s", + ) + else: + self.update_info( + f"正在下载:{self.name} 已下载:{self.downloaded_size / 1048576:.2f}/{self.file_size / 1048576:.2f} MB ({self.downloaded_size / self.file_size * 100:.2f}%) 下载速度:{self.speed:.2f} KB/s", + ) + + def download_task3(self, index: str, t: float) -> None: + + # 标记下载线程完成 + self.downloaded_size_list[index][1] = True + + # 清理下载线程 + self.download_process_dict[f"part{index}"].requestInterruption() + self.download_process_dict[f"part{index}"].quit() + self.download_process_dict[f"part{index}"].wait() + self.download_process_dict[f"part{index}"].deleteLater() + self.download_process_dict.pop(f"part{index}") + if not self.download_process_dict: + self.download_process_clear.emit() + + if ( + any([not _[1] for _ in self.downloaded_size_list]) + or self.isInterruptionRequested + ): + return None + + # 合并下载的分段文件 + with self.download_path.open(mode="wb") as outfile: + for i in range(self.config["thread_numb"]): + with self.download_path.with_suffix(f".part{i}").open( + mode="rb" + ) as infile: + outfile.write(infile.read()) + self.download_path.with_suffix(f".part{i}").unlink() + + self.update_info("正在解压更新文件") + self.update_progress(0, 0, 0) + + # 创建解压线程 + self.zip_extract = ZipExtractProcess( + self.name, self.app_path, self.download_path + ) + self.zip_loop = QEventLoop() + self.zip_extract.info.connect(self.update_info) + self.zip_extract.accomplish.connect(self.zip_loop.quit) + 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() + + # 主程序更新完成后打开对应程序 + if not self.isInterruptionRequested and self.name == "AUTO_MAA": + subprocess.Popen( + str(self.app_path / "AUTO_MAA.exe"), + shell=True, + creationflags=subprocess.CREATE_NO_WINDOW, + ) + elif not self.isInterruptionRequested and self.name == "MAA": + subprocess.Popen( + str(self.app_path / "MAA.exe"), + shell=True, + creationflags=subprocess.CREATE_NO_WINDOW, + ) + + self.update_info(f"{self.name}更新成功!") + self.update_progress(0, 100, 100) + self.download_accomplish.emit() + + def update_info(self, text: str) -> None: + self.info.setText(text) + + def update_progress(self, begin: int, end: int, current: int) -> None: + + if begin == 0 and end == 0: + self.progress_2.setVisible(False) + self.progress_1.setVisible(True) + else: + self.progress_1.setVisible(False) + self.progress_2.setVisible(True) + self.progress_2.setRange(begin, end) + self.progress_2.setValue(current) + + def requestInterruption(self) -> None: + + self.isInterruptionRequested = True + + if hasattr(self, "zip_extract") and self.zip_extract: + self.zip_extract.requestInterruption() + + if hasattr(self, "zip_loop") and self.zip_loop: + self.zip_loop.quit() + + for process in self.download_process_dict.values(): + process.requestInterruption() + + if self.download_process_dict: + loop = QEventLoop() + self.download_process_clear.connect(loop.quit) + loop.exec() + + def closeEvent(self, event: QCloseEvent): + """清理残余进程""" + + self.requestInterruption() + + event.accept() + + +class AUTO_MAA_Downloader(QApplication): + def __init__( + self, + app_path: Path, + name: str, + main_version: list, + config: dict, + ) -> None: + super().__init__() + + self.main = DownloadManager(app_path, name, main_version, config) + self.main.show() + self.main.run() + + +if __name__ == "__main__": + + # 获取软件自身的路径 + app_path = Path(sys.argv[0]).resolve().parent + + # 从本地版本信息文件获取当前版本信息 + if (app_path / "resources/version.json").exists(): + with (app_path / "resources/version.json").open( + mode="r", encoding="utf-8" + ) as f: + current_version_info = json.load(f) + current_version = list( + map(int, current_version_info["main_version"].split(".")) + ) + else: + 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: + + 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 = [] + thread_numb = 8 + mirrorchyan_CDK = "" + else: + update_type = "stable" + proxy_list = [] + thread_numb = 8 + mirrorchyan_CDK = "" + + # 从远程服务器获取最新版本信息 + for _ in range(3): + try: + response = requests.get( + f"https://mirrorchyan.com/api/resources/AUTO_MAA/latest?current_version={version_text(current_version)}&cdk={mirrorchyan_CDK}&channel={update_type}" + ) + version_info: Dict[str, Union[int, str, Dict[str, str]]] = response.json() + break + except Exception as e: + err = e + time.sleep(0.1) + else: + sys.exit(f"获取版本信息时出错:\n{err}") + + 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 remote_version > current_version: + app = AUTO_MAA_Downloader( + app_path, + "AUTO_MAA", + remote_version, + download_config, + ) + sys.exit(app.exec()) diff --git a/app/utils/package.py b/app/utils/package.py index 7c17754..6c9c706 100644 --- a/app/utils/package.py +++ b/app/utils/package.py @@ -44,6 +44,17 @@ def version_text(version_numb: list) -> str: return version +def version_info_markdown(info: dict) -> str: + """将版本信息字典转为markdown信息""" + + version_info = "" + for key, value in info.items(): + version_info += f"## {key}\n" + for v in value: + version_info += f"- {v}\n" + return version_info + + if __name__ == "__main__": root_path = Path(sys.argv[0]).resolve().parent @@ -57,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"]}" @@ -72,19 +82,9 @@ if __name__ == "__main__": print("AUTO_MAA main program packaging completed !") - shutil.copy(root_path / "app/utils/Updater.py", root_path) - - file_content = (root_path / "Updater.py").read_text(encoding="utf-8") - - (root_path / "Updater.py").write_text( - file_content.replace( - "from .version import version_text", "from app import version_text" - ), - encoding="utf-8", - ) - 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" " --enable-plugins=pyside6 --windows-console-mode=disable" @@ -95,15 +95,60 @@ if __name__ == "__main__": f" --product-version={version["main_version"]}" " --file-description='AUTO_MAA Component'" " --copyright='Copyright © 2024 DLmaster361'" - " --assume-yes-for-downloads --output-filename=Updater" - " --remove-output Updater.py" + " --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 / "Updater.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/") + shutil.rmtree(root_path / "main.dist") + + print("Start to copy AUTO_MAA update program ...") + + shutil.move(root_path / "AUTO_Updater.exe", root_path / "AUTO_MAA/") + + 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/") + shutil.copy(root_path / "requirements.txt", root_path / "AUTO_MAA/") + shutil.copy(root_path / "README.md", root_path / "AUTO_MAA/") + shutil.copy(root_path / "LICENSE", root_path / "AUTO_MAA/") + + 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(): + for key, value in v_i.items(): + if key in all_version_info: + all_version_info[key] += value.copy() + else: + 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)}{version["announcement"]}", + 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/requirements.txt b/requirements.txt index a8597de..1759840 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,6 +8,7 @@ pywin32 pyautogui pycryptodome requests +markdown Jinja2 serverchan_sdk -nuitka==2.6 \ No newline at end of file +nuitka \ No newline at end of file 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 d5e5a1c..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 @@ -44,4 +46,5 @@ G"Start.MinimizeDirectly": "True" #启动MAA后直接最小化 "Start.OpenEmulatorAfterLaunch": "True" #启动MAA后自动开启模拟器 G"GUI.UseTray": "True" #显示托盘图标 G"GUI.MinimizeToTray": "False" #最小化时隐藏至托盘 -"Start.EmulatorPath" #模拟器路径 \ No newline at end of file +"Start.EmulatorPath" #模拟器路径 +"Connect.AdbPath" #ADB路径 \ No newline at end of file diff --git a/resources/images/Home.png b/resources/images/Home.png deleted file mode 100644 index 0795101..0000000 Binary files a/resources/images/Home.png and /dev/null differ diff --git a/resources/version.json b/resources/version.json index f3c6d11..d74a850 100644 --- a/resources/version.json +++ b/resources/version.json @@ -1,14 +1,112 @@ { - "main_version": "4.2.5.1", - "updater_version": "1.1.2.1", - "announcement": "\n## 新增功能\n- 暂无\n## 修复BUG\n- 修复统计信息HTML模板公招匹配错误\n## 程序优化\n- 暂无", + "main_version": "4.3.0.0", + "updater_version": "1.0.0.0", + "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.3.0.0":{ + "新增功能": [ + "正式接入`mirrorc`" + ], + "修复BUG": [ + "添加了更多未知BUG(无误)" + ] + }, + "4.2.5.10":{ + "新增功能": [ + "添加用户仪表盘子界面" + ], + "修复BUG": [ + "更新逻辑修复" + ], + "程序优化": [ + "获取关卡号,用户密码解密逻辑优化" + ] + }, + "4.2.5.9":{ + "新增功能": [ + "添加理智药设置选项 #34" + ], + "修复BUG": [ + "对win10主题进一步适配" + ], + "程序优化": [ + "输入对话框添加回车键确认能力 #35", + "用户列表UI改版升级", + "配置类取消单例限制", + "配置读取方式与界面渲染方法优化" + ] + }, + "4.2.5.8":{ + "程序优化": [ + "loguru开始捕获子线程异常", + "通知服务添加校验项" + ] + }, + "4.2.5.7":{ + "新增功能": [ + "添加每周剿灭模式上限功能" + ], + "修复BUG": [ + "修复更新通知阻碍调度开始问题 #32", + "修复更新器解压失败问题" + ], + "程序优化": [ + "打包流程删除无用过程", + "主程序版本号完全写死在代码内部" + ] + }, + "4.2.5.6":{ + "程序优化": [ + "更新信息样式优化", + "更新器支持动态获取下载站" + ] + }, + "4.2.5.5": { + "程序优化": [ + "公告样式优化" + ] + }, + "4.2.5.4": { + "新增功能": [ + "添加强制关闭ADB与模拟器等增强任务项" + ], + "修复BUG": [ + "修复`检测到MAA未能实际执行任务`报错被异常屏蔽", + "修复MAA超时判定异常失效" + ] + }, + "4.2.5.3": { + "程序优化": [ + "关机等电源操作添加100s倒计时", + "人工排查弹窗方法优化", + "人工排查时自动屏蔽静默操作" + ] + }, + "4.2.5.2": { + "新增功能": [ + "屏蔽MuMu模拟器开屏广告功能上线", + "更新器支持多线程下载" + ], + "修复BUG": [ + "修复密码显示按钮动画异常" + ] + }, + "4.2.5.1": { + "修复BUG": [ + "修复统计信息HTML模板公招匹配错误" + ] + } + }, "proxy_list": [ - "", "https://gitproxy.click/", "https://cdn.moran233.xyz/", "https://gh.llkk.cc/", "https://github.akams.cn/", "https://www.ghproxy.cn/", "https://ghfast.top/" - ] + ], + "download_dict": { + "官方下载站-jp": "https://jp-download.fearr.xyz/AUTO_MAA/", + "官方下载站-hw": "http://hwobs.fearr.xyz/releases/artifacts/" + } } \ No newline at end of file