diff --git a/README.md b/README.md index 4a1a8ea..2f2c5e1 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,11 @@ 本软件是明日方舟第三方软件`MAA`的第三方工具,即第33方软件。旨在优化MAA多账号功能体验,并通过一些方法解决MAA项目未能解决的部分问题,提高代理的稳定性。 +- **集中管理**:一站式管理多个MAA脚本与多个用户配置,和凌乱的散装脚本窗口说再见! +- **无人值守**:自动处理MAA相关报错,再也不用为代理任务卡死时自己不在电脑旁烦恼啦! +- **配置灵活**:通过调度队列与脚本的组合,自由实现您能想到的所有调度需求! +- **信息统计**:自动统计用户的公招与关卡掉落物,看看这个月您的收益是多少! + ### 原理 本软件可以存储多个明日方舟账号数据,并通过以下流程实现代理功能: @@ -32,11 +37,9 @@ ### 优势 -- **节省运行开销:** 只需要一份MAA软件与一个模拟器,无需多开就能完成多账号代理,羸弱的电脑也能代理日常。 -- **自定义空间大:** 依靠高级用户配置模式,支持MAA几乎所有设置选项自定义,支持模拟器多开。 -- **调度方法自由:** 通过调度队列功能,自由实现MAA多开等多种调度方式。 -- **一键代理无忧:** 无须中途手动修改MAA配置,将繁琐交给AUTO_MAA,把游戏留给自己。 -- **代理结果复核:** 通过人工排查功能核实各用户代理情况,堵住自动代理的最后一丝风险。 +- **高效稳定**:通过日志监测、异常处理等机制,保障代理任务顺利完成。 +- **简洁易用**:无需手动修改配置文件,实现自动化调度与多开管理。 +- **兼容扩展**:支持 MAA 几乎所有的配置选项,满足不同用户需求。 ## 重要声明 diff --git a/app/core/__init__.py b/app/core/__init__.py index baedc38..b56b538 100644 --- a/app/core/__init__.py +++ b/app/core/__init__.py @@ -29,7 +29,7 @@ __version__ = "4.2.0" __author__ = "DLmaster361 " __license__ = "GPL-3.0 license" -from .config import QueueConfig, MaaConfig, MaaUserConfig, Config +from .config import QueueConfig, MaaConfig, MaaUserConfig, MaaPlanConfig, Config from .main_info_bar import MainInfoBar from .network import Network from .task_manager import Task, TaskManager @@ -40,6 +40,7 @@ __all__ = [ "QueueConfig", "MaaConfig", "MaaUserConfig", + "MaaPlanConfig", "MainInfoBar", "Network", "Task", diff --git a/app/core/config.py b/app/core/config.py index c7a92af..b99afff 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -33,6 +33,7 @@ import sys import shutil import re import base64 +import calendar from datetime import datetime, timedelta, date from collections import defaultdict from pathlib import Path @@ -78,7 +79,78 @@ class UrlListValidator(ConfigValidator): return list(set([_ for _ in urls if self.validate(_)])) -class GlobalConfig(QConfig): +class LQConfig(QConfig): + """局域配置类""" + + def __init__(self) -> None: + super().__init__() + + 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 GlobalConfig(LQConfig): """全局配置""" def __init__(self) -> None: @@ -163,10 +235,6 @@ class GlobalConfig(QConfig): 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() @@ -182,72 +250,8 @@ class GlobalConfig(QConfig): ) 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): +class QueueConfig(LQConfig): """队列配置""" def __init__(self) -> None: @@ -334,72 +338,8 @@ class QueueConfig(QConfig): "Data", "LastProxyHistory", "暂无历史运行记录" ) - 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): +class MaaConfig(LQConfig): """MAA配置""" def __init__(self) -> None: @@ -417,6 +357,9 @@ class MaaConfig(QConfig): self.RunSet_ProxyTimesLimit = RangeConfigItem( "RunSet", "ProxyTimesLimit", 0, RangeValidator(0, 1024) ) + self.RunSet_ADBSearchRange = RangeConfigItem( + "RunSet", "ADBSearchRange", 0, RangeValidator(0, 3) + ) self.RunSet_RunTimesLimit = RangeConfigItem( "RunSet", "RunTimesLimit", 3, RangeValidator(1, 1024) ) @@ -433,72 +376,8 @@ class MaaConfig(QConfig): "RunSet", "AutoUpdateMaa", 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): +class MaaUserConfig(LQConfig): """MAA用户配置""" def __init__(self) -> None: @@ -509,9 +388,7 @@ class MaaUserConfig(QConfig): self.Info_Mode = OptionsConfigItem( "Info", "Mode", "简洁", OptionsValidator(["简洁", "详细"]) ) - self.Info_GameIdMode = OptionsConfigItem( - "Info", "GameIdMode", "固定", OptionsValidator(["固定"]) - ) + self.Info_GameIdMode = ConfigItem("Info", "GameIdMode", "固定") self.Info_Server = OptionsConfigItem( "Info", "Server", "Official", OptionsValidator(["Official", "Bilibili"]) ) @@ -537,8 +414,8 @@ class MaaUserConfig(QConfig): self.Info_SeriesNumb = OptionsConfigItem( "Info", "SeriesNumb", - "1", - OptionsValidator(["1000", "6", "5", "4", "3", "2", "1", "-1"]), + "0", + OptionsValidator(["0", "6", "5", "4", "3", "2", "1", "-1"]), ) self.Info_GameId = ConfigItem("Info", "GameId", "-") self.Info_GameId_1 = ConfigItem("Info", "GameId_1", "-") @@ -557,74 +434,140 @@ class MaaUserConfig(QConfig): "Data", "CustomInfrastPlanIndex", "0" ) - 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 + self.Task_IfWakeUp = ConfigItem("Task", "IfWakeUp", True, BoolValidator()) + self.Task_IfRecruiting = ConfigItem( + "Task", "IfRecruiting", True, BoolValidator() + ) + self.Task_IfBase = ConfigItem("Task", "IfBase", True, BoolValidator()) + self.Task_IfCombat = ConfigItem("Task", "IfCombat", True, BoolValidator()) + self.Task_IfMall = ConfigItem("Task", "IfMall", True, BoolValidator()) + self.Task_IfMission = ConfigItem("Task", "IfMission", True, BoolValidator()) + self.Task_IfAutoRoguelike = ConfigItem( + "Task", "IfAutoRoguelike", False, BoolValidator() + ) + self.Task_IfReclamation = ConfigItem( + "Task", "IfReclamation", False, BoolValidator() + ) - 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] = {} + self.Notify_Enabled = ConfigItem("Notify", "Enabled", False, BoolValidator()) + self.Notify_IfSendStatistic = ConfigItem( + "Notify", "IfSendStatistic", False, BoolValidator() + ) + self.Notify_IfSendSixStar = ConfigItem( + "Notify", "IfSendSixStar", False, BoolValidator() + ) + self.Notify_IfSendMail = ConfigItem( + "Notify", "IfSendMail", False, BoolValidator() + ) + self.Notify_ToAddress = ConfigItem("Notify", "ToAddress", "") + self.Notify_IfServerChan = ConfigItem( + "Notify", "IfServerChan", False, BoolValidator() + ) + self.Notify_ServerChanKey = ConfigItem("Notify", "ServerChanKey", "") + self.Notify_ServerChanChannel = ConfigItem("Notify", "ServerChanChannel", "") + self.Notify_ServerChanTag = ConfigItem("Notify", "ServerChanTag", "") + self.Notify_IfCompanyWebHookBot = ConfigItem( + "Notify", "IfCompanyWebHookBot", False, BoolValidator() + ) + self.Notify_CompanyWebHookBotUrl = ConfigItem( + "Notify", "CompanyWebHookBotUrl", "" + ) - if item.name: - items[item.group][item.name] = value + def get_plan_info(self) -> Dict[str, Union[str, int]]: + """获取当前的计划下信息""" - return items + if self.get(self.Info_GameIdMode) == "固定": + return { + "MedicineNumb": self.get(self.Info_MedicineNumb), + "SeriesNumb": self.get(self.Info_SeriesNumb), + "GameId": self.get(self.Info_GameId), + "GameId_1": self.get(self.Info_GameId_1), + "GameId_2": self.get(self.Info_GameId_2), + "GameId_Remain": self.get(self.Info_GameId_Remain), + } + elif "计划" in self.get(self.Info_GameIdMode): + plan = Config.plan_dict[self.get(self.Info_GameIdMode)]["Config"] + return { + "MedicineNumb": plan.get(plan.get_current_info("MedicineNumb")), + "SeriesNumb": plan.get(plan.get_current_info("SeriesNumb")), + "GameId": plan.get(plan.get_current_info("GameId")), + "GameId_1": plan.get(plan.get_current_info("GameId_1")), + "GameId_2": plan.get(plan.get_current_info("GameId_2")), + "GameId_Remain": plan.get(plan.get_current_info("GameId_Remain")), + } - @exceptionHandler() - def load(self, file=None, config=None): - """load config - Parameters - ---------- - file: str or Path - the path of json config file +class MaaPlanConfig(LQConfig): + """MAA计划表配置""" - config: Config - config object to be initialized - """ - if isinstance(config, QConfig): - self._cfg = config - self._cfg.themeChanged.connect(self.themeChanged) + def __init__(self) -> None: + super().__init__() - if isinstance(file, (str, Path)): - self._cfg.file = Path(file) + self.Info_Name = ConfigItem("Info", "Name", "") + self.Info_Mode = OptionsConfigItem( + "Info", "Mode", "ALL", OptionsValidator(["ALL", "Weekly"]) + ) - try: - with open(self._cfg.file, encoding="utf-8") as f: - cfg = json.load(f) - except: - cfg = {} + self.config_item_dict: dict[str, Dict[str, ConfigItem]] = {} - # 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 + for group in [ + "ALL", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", + "Sunday", + ]: + self.config_item_dict[group] = {} - # 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.config_item_dict[group]["MedicineNumb"] = ConfigItem( + group, "MedicineNumb", 0, RangeValidator(0, 1024) + ) + self.config_item_dict[group]["SeriesNumb"] = OptionsConfigItem( + group, + "SeriesNumb", + "0", + OptionsValidator(["0", "6", "5", "4", "3", "2", "1", "-1"]), + ) + self.config_item_dict[group]["GameId"] = ConfigItem(group, "GameId", "-") + self.config_item_dict[group]["GameId_1"] = ConfigItem( + group, "GameId_1", "-" + ) + self.config_item_dict[group]["GameId_2"] = ConfigItem( + group, "GameId_2", "-" + ) + self.config_item_dict[group]["GameId_Remain"] = ConfigItem( + group, "GameId_Remain", "-" + ) - self.theme = self.get(self._cfg.themeMode) + for name in [ + "MedicineNumb", + "SeriesNumb", + "GameId", + "GameId_1", + "GameId_2", + "GameId_Remain", + ]: + setattr(self, f"{group}_{name}", self.config_item_dict[group][name]) + + def get_current_info(self, name: str) -> ConfigItem: + """获取当前的计划表配置项""" + + if self.get(self.Info_Mode) == "ALL": + return self.config_item_dict["ALL"][name] + elif self.get(self.Info_Mode) == "Weekly": + today = datetime.now().strftime("%A") + if today in self.config_item_dict: + return self.config_item_dict[today][name] + else: + return self.config_item_dict["ALL"][name] class AppConfig(GlobalConfig): - VERSION = "4.3.8.2" + VERSION = "4.3.8.0" gameid_refreshed = Signal() PASSWORD_refreshed = Signal() @@ -648,9 +591,16 @@ class AppConfig(GlobalConfig): self.PASSWORD = "" self.running_list = [] self.silence_list = [] + self.info_bar_list = [] self.gameid_dict = { "ALL": {"value": [], "text": []}, - "Today": {"value": [], "text": []}, + "Monday": {"value": [], "text": []}, + "Tuesday": {"value": [], "text": []}, + "Wednesday": {"value": [], "text": []}, + "Thursday": {"value": [], "text": []}, + "Friday": {"value": [], "text": []}, + "Saturday": {"value": [], "text": []}, + "Sunday": {"value": [], "text": []}, } self.power_sign = "NoAction" self.if_ignore_silence = False @@ -704,7 +654,7 @@ class AppConfig(GlobalConfig): # 从MAA服务器获取活动关卡信息 Network.set_info( mode="get", - url="https://ota.maa.plus/MaaAssistantArknights/api/gui/StageActivity.json", + url="https://api.maa.plus/MaaAssistantArknights/api/gui/StageActivity.json", ) Network.start() Network.loop.exec() @@ -716,7 +666,7 @@ class AppConfig(GlobalConfig): logger.warning(f"无法从MAA服务器获取活动关卡信息:{Network.error_message}") gameid_infos = [] - gameid_dict = {"value": [], "text": []} + ss_gameid_dict = {"value": [], "text": []} for gameid_info in gameid_infos: @@ -729,53 +679,11 @@ class AppConfig(GlobalConfig): gameid_info["Activity"]["UtcExpireTime"], "%Y/%m/%d %H:%M:%S" ) ): - gameid_dict["value"].append(gameid_info["Value"]) - gameid_dict["text"].append(gameid_info["Value"]) + ss_gameid_dict["value"].append(gameid_info["Value"]) + ss_gameid_dict["text"].append(gameid_info["Value"]) - # 生成全部关卡信息 - 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", - "奶/盾芯片", - "奶/盾芯片组", - "术/狙芯片", - "术/狙芯片组", - "先/辅芯片", - "先/辅芯片组", - "近/特芯片", - "近/特芯片组", - ] - - # 生成本日关卡信息 - days = self.server_date().isoweekday() - - gameid_list = [ + # 生成每日关卡信息 + gameid_daily_info = [ {"value": "-", "text": "当前/上次", "days": [1, 2, 3, 4, 5, 6, 7]}, {"value": "1-7", "text": "1-7", "days": [1, 2, 3, 4, 5, 6, 7]}, {"value": "R8-11", "text": "R8-11", "days": [1, 2, 3, 4, 5, 6, 7]}, @@ -799,12 +707,20 @@ class AppConfig(GlobalConfig): {"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"]) + for day in range(0, 8): - self.gameid_dict["Today"] = gameid_dict + today_gameid_dict = {"value": [], "text": []} + + for gameid_info in gameid_daily_info: + + if day in gameid_info["days"] or day == 0: + today_gameid_dict["value"].append(gameid_info["value"]) + today_gameid_dict["text"].append(gameid_info["text"]) + + self.gameid_dict[calendar.day_name[day - 1] if day > 0 else "ALL"] = { + "value": today_gameid_dict["value"] + ss_gameid_dict["value"], + "text": today_gameid_dict["text"] + ss_gameid_dict["text"], + } self.gameid_refreshed.emit() @@ -1235,6 +1151,28 @@ class AppConfig(GlobalConfig): sorted(user_dict.items(), key=lambda x: int(x[0][3:])) ) + def search_plan(self) -> None: + """搜索所有计划表""" + + self.plan_dict: Dict[str, Dict[str, Union[str, Path, MaaPlanConfig]]] = {} + if (self.app_path / "config/MaaPlanConfig").exists(): + for maa_plan_dir in (self.app_path / "config/MaaPlanConfig").iterdir(): + if maa_plan_dir.is_dir(): + + maa_plan_config = MaaPlanConfig() + maa_plan_config.load(maa_plan_dir / "config.json", maa_plan_config) + maa_plan_config.save() + + self.plan_dict[maa_plan_dir.name] = { + "Type": "Maa", + "Path": maa_plan_dir, + "Config": maa_plan_config, + } + + self.plan_dict = dict( + sorted(self.plan_dict.items(), key=lambda x: int(x[0][3:])) + ) + def search_queue(self): """搜索所有调度队列实例""" @@ -1282,6 +1220,16 @@ class AppConfig(GlobalConfig): if queue["Config"].get(queue["Config"].queue_Member_10) == old: queue["Config"].set(queue["Config"].queue_Member_10, new) + def change_plan(self, old: str, new: str) -> None: + """修改脚本管理所有下属用户的计划表配置参数""" + + for member in self.member_dict.values(): + + for user in member["UserData"].values(): + + if user["Config"].get(user["Config"].Info_GameIdMode) == old: + user["Config"].set(user["Config"].Info_GameIdMode, new) + def change_user_info( self, name: str, user_data: Dict[str, Dict[str, Union[str, Path, dict]]] ) -> None: diff --git a/app/core/main_info_bar.py b/app/core/main_info_bar.py index 791f8c4..399774d 100644 --- a/app/core/main_info_bar.py +++ b/app/core/main_info_bar.py @@ -35,23 +35,30 @@ from .config import Config class _MainInfoBar: """信息通知栏""" - def push_info_bar(self, mode: str, title: str, content: str, time: int): + # 模式到 InfoBar 方法的映射 + mode_mapping = { + "success": InfoBar.success, + "warning": InfoBar.warning, + "error": InfoBar.error, + "info": InfoBar.info, + } + + def push_info_bar( + self, mode: str, title: str, content: str, time: int, if_force: bool = False + ): """推送到信息通知栏""" if Config.main_window is None: logger.error("信息通知栏未设置父窗口") return None - # 定义模式到 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 = self.mode_mapping.get(mode) + + if not info_bar_method: + logger.error(f"未知的通知栏模式: {mode}") + return None + + if Config.main_window.isVisible(): info_bar_method( title=title, content=content, @@ -61,8 +68,16 @@ class _MainInfoBar: duration=time, parent=Config.main_window, ) - else: - logger.error(f"未知的通知栏模式: {mode}") + elif if_force: + # 如果主窗口不可见且强制推送,则录入消息队列等待窗口显示后推送 + info_bar_item = { + "mode": mode, + "title": title, + "content": content, + "time": time, + } + if info_bar_item not in Config.info_bar_list: + Config.info_bar_list.append(info_bar_item) MainInfoBar = _MainInfoBar() diff --git a/app/core/timer.py b/app/core/timer.py index 09e3d81..c09bd7f 100644 --- a/app/core/timer.py +++ b/app/core/timer.py @@ -29,6 +29,7 @@ from loguru import logger from PySide6.QtWidgets import QWidget from PySide6.QtCore import QTimer from datetime import datetime +from pathlib import Path import pyautogui from .config import Config @@ -97,6 +98,16 @@ class _MainTimer(QWidget): ): windows = System.get_window_info() + + # 排除雷电名为新通知的窗口 + windows = [ + window + for window in windows + if not ( + window[0] == "新通知" and Path(window[1]) in Config.silence_list + ) + ] + if any( str(emulator_path) in window for window in windows diff --git a/app/models/MAA.py b/app/models/MAA.py index 03727da..d43de4e 100644 --- a/app/models/MAA.py +++ b/app/models/MAA.py @@ -102,6 +102,9 @@ class MaaManager(QObject): "Path": info["Path"], "Config": info["Config"].toDict(), } + planed_info = info["Config"].get_plan_info() + for key, value in planed_info.items(): + self.data[name]["Config"]["Info"][key] = value self.data = dict(sorted(self.data.items(), key=lambda x: int(x[0][3:]))) @@ -114,10 +117,15 @@ class MaaManager(QObject): self.maa_log_path = self.maa_root_path / "debug/gui.log" self.maa_exe_path = self.maa_root_path / "MAA.exe" self.maa_tasks_path = self.maa_root_path / "resource/tasks/tasks.json" + self.port_range = [0] + [ + (i // 2 + 1) * (-1 if i % 2 else 1) + for i in range(0, 2 * self.set["RunSet"]["ADBSearchRange"]) + ] def run(self): """主进程,运行MAA代理进程""" + current_date = datetime.now().strftime("%m-%d") curdate = Config.server_date().strftime("%Y-%m-%d") begin_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") @@ -266,9 +274,23 @@ class MaaManager(QObject): ) # 解析任务构成 - if user_data["Info"]["Mode"] == "简洁": + if mode == "Routine": + + self.task_dict = { + "WakeUp": str(user_data["Task"]["IfWakeUp"]), + "Recruiting": str(user_data["Task"]["IfRecruiting"]), + "Base": str(user_data["Task"]["IfBase"]), + "Combat": str(user_data["Task"]["IfCombat"]), + "Mission": str(user_data["Task"]["IfMission"]), + "Mall": str(user_data["Task"]["IfMall"]), + "AutoRoguelike": str(user_data["Task"]["IfAutoRoguelike"]), + "Reclamation": str(user_data["Task"]["IfReclamation"]), + } + + elif mode == "Annihilation": + + if user_data["Info"]["Mode"] == "简洁": - if mode == "Annihilation": self.task_dict = { "WakeUp": "True", "Recruiting": "False", @@ -280,52 +302,40 @@ class MaaManager(QObject): "Reclamation": "False", } - elif mode == "Routine": + elif user_data["Info"]["Mode"] == "详细": + + with (self.data[user[2]]["Path"] / f"{mode}/gui.json").open( + mode="r", encoding="utf-8" + ) as f: + data = json.load(f) + self.task_dict = { - "WakeUp": "True", - "Recruiting": "True", - "Base": "True", - "Combat": "True", - "Mission": "True", - "Mall": "True", - "AutoRoguelike": "False", - "Reclamation": "False", + "WakeUp": data["Configurations"]["Default"][ + "TaskQueue.WakeUp.IsChecked" + ], + "Recruiting": data["Configurations"]["Default"][ + "TaskQueue.Recruiting.IsChecked" + ], + "Base": data["Configurations"]["Default"][ + "TaskQueue.Base.IsChecked" + ], + "Combat": data["Configurations"]["Default"][ + "TaskQueue.Combat.IsChecked" + ], + "Mission": data["Configurations"]["Default"][ + "TaskQueue.Mission.IsChecked" + ], + "Mall": data["Configurations"]["Default"][ + "TaskQueue.Mall.IsChecked" + ], + "AutoRoguelike": data["Configurations"]["Default"][ + "TaskQueue.AutoRoguelike.IsChecked" + ], + "Reclamation": data["Configurations"]["Default"][ + "TaskQueue.Reclamation.IsChecked" + ], } - elif user_data["Info"]["Mode"] == "详细": - - with (self.data[user[2]]["Path"] / f"{mode}/gui.json").open( - mode="r", encoding="utf-8" - ) as f: - data = json.load(f) - - self.task_dict = { - "WakeUp": data["Configurations"]["Default"][ - "TaskQueue.WakeUp.IsChecked" - ], - "Recruiting": data["Configurations"]["Default"][ - "TaskQueue.Recruiting.IsChecked" - ], - "Base": data["Configurations"]["Default"][ - "TaskQueue.Base.IsChecked" - ], - "Combat": data["Configurations"]["Default"][ - "TaskQueue.Combat.IsChecked" - ], - "Mission": data["Configurations"]["Default"][ - "TaskQueue.Mission.IsChecked" - ], - "Mall": data["Configurations"]["Default"][ - "TaskQueue.Mall.IsChecked" - ], - "AutoRoguelike": data["Configurations"]["Default"][ - "TaskQueue.AutoRoguelike.IsChecked" - ], - "Reclamation": data["Configurations"]["Default"][ - "TaskQueue.Reclamation.IsChecked" - ], - } - # 尝试次数循环 for i in range(self.set["RunSet"]["RunTimesLimit"]): @@ -386,6 +396,12 @@ class MaaManager(QObject): self.if_open_emulator = True break + self.wait_time = int( + set["Configurations"]["Default"][ + "Start.EmulatorWaitSeconds" + ] + ) + self.ADB_path = Path( set["Configurations"]["Default"]["Connect.AdbPath"] ) @@ -414,6 +430,7 @@ class MaaManager(QObject): [self.ADB_path, "disconnect", self.ADB_address], creationflags=subprocess.CREATE_NO_WINDOW, ) + logger.info(f"{self.name} | 释放ADB:{self.ADB_address}") except subprocess.CalledProcessError as e: # 忽略错误,因为可能本来就没有连接 logger.warning(f"{self.name} | 释放ADB时出现异常:{e}") @@ -432,6 +449,9 @@ class MaaManager(QObject): [self.emulator_path, *self.emulator_arguments], creationflags=subprocess.CREATE_NO_WINDOW, ) + logger.info( + f"{self.name} | 启动模拟器:{self.emulator_path},参数:{self.emulator_arguments}" + ) except Exception as e: logger.error(f"{self.name} | 启动模拟器时出现异常:{e}") self.push_info_bar.emit( @@ -446,6 +466,8 @@ class MaaManager(QObject): # 添加静默进程标记 Config.silence_list.append(self.emulator_path) + self.search_ADB_address() + # 创建MAA任务 maa = subprocess.Popen( [self.maa_exe_path], @@ -517,6 +539,12 @@ class MaaManager(QObject): ) as f: data = json.load(f) + # 记录自定义基建索引 + if self.task_dict["Base"] == "False": + user_data["Data"]["CustomInfrastPlanIndex"] = data[ + "Configurations" + ]["Default"]["Infrast.CustomInfrastPlanIndex"] + # 记录更新包路径 if ( data["Global"]["VersionUpdate.package"] @@ -541,15 +569,13 @@ class MaaManager(QObject): break time.sleep(1) - # 移除静默进程标记 - Config.silence_list.remove(self.emulator_path) - # 任务结束后释放ADB try: subprocess.run( [self.ADB_path, "disconnect", self.ADB_address], creationflags=subprocess.CREATE_NO_WINDOW, ) + logger.info(f"{self.name} | 释放ADB:{self.ADB_address}") except subprocess.CalledProcessError as e: # 忽略错误,因为可能本来就没有连接 logger.warning(f"{self.name} | 释放ADB时出现异常:{e}") @@ -567,6 +593,33 @@ class MaaManager(QObject): self.emulator_process.wait() self.if_open_emulator = True + # 记录剿灭情况 + 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}/{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}/{user_data["Info"]["Name"]}/{start_time.strftime("%H-%M-%S")}.json", + ) + if if_six_star: + self.push_notification( + "公招六星", + f"喜报:用户 {user[0]} 公招出六星啦!", + { + "user_name": user_data["Info"]["Name"], + }, + user_data, + ) + # 执行MAA解压更新动作 if self.maa_update_package: @@ -590,50 +643,23 @@ class MaaManager(QObject): logger.info(f"{self.name} | 更新动作结束") - # 记录剿灭情况 - 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}/{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}/{user_data["Info"]["Name"]}/{start_time.strftime("%H-%M-%S")}.json", - ) - - if Config.get(Config.notify_IfSendSixStar) and if_six_star: - - self.push_notification( - "公招六星", - f"喜报:用户 {user[0]} 公招出六星啦!", - {"user_name": user_data["Info"]["Name"]}, - ) - - if Config.get(Config.notify_IfSendStatistic): - - statistics = Config.merge_maa_logs("指定项", user_logs_list) - statistics["user_info"] = user[0] - statistics["start_time"] = user_start_time.strftime( - "%Y-%m-%d %H:%M:%S" - ) - statistics["end_time"] = datetime.now().strftime( - "%Y-%m-%d %H:%M:%S" - ) - statistics["maa_result"] = ( - "代理任务全部完成" - if (run_book["Annihilation"] and run_book["Routine"]) - else "代理任务未全部完成" - ) - self.push_notification( - "统计信息", f"用户 {user[0]} 的自动代理统计报告", statistics - ) + # 发送统计信息 + statistics = Config.merge_maa_logs("指定项", user_logs_list) + statistics["user_index"] = user[2] + statistics["user_info"] = user[0] + statistics["start_time"] = user_start_time.strftime("%Y-%m-%d %H:%M:%S") + statistics["end_time"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + statistics["maa_result"] = ( + "代理任务全部完成" + if (run_book["Annihilation"] and run_book["Routine"]) + else "代理任务未全部完成" + ) + self.push_notification( + "统计信息", + f"{current_date} | 用户 {user[0]} 的自动代理统计报告", + statistics, + user_data, + ) if run_book["Annihilation"] and run_book["Routine"]: # 成功完成代理的用户修改相关参数 @@ -804,9 +830,9 @@ class MaaManager(QObject): # 保存运行日志 title = ( - f"{self.set["MaaSet"]["Name"]}的{self.mode[:4]}任务报告" + f"{current_date} | {self.set["MaaSet"]["Name"]}的{self.mode[:4]}任务报告" if self.set["MaaSet"]["Name"] != "" - else f"{self.mode[:4]}任务报告" + else f"{current_date} | {self.mode[:4]}任务报告" ) result = { "title": f"{self.mode[:4]}任务报告", @@ -826,6 +852,17 @@ class MaaManager(QObject): self.data[_]["Config"]["Info"]["Name"] for _ in wait_index ], } + + # 生成结果文本 + result_text = ( + f"任务开始时间:{result["start_time"]},结束时间:{result["end_time"]}\n" + f"已完成数:{result["completed_count"]},未完成数:{result["uncompleted_count"]}\n\n" + ) + if len(result["failed_user"]) > 0: + result_text += f"{self.mode[2:4]}未成功的用户:\n{"\n".join(result["failed_user"])}\n" + if len(result["waiting_user"]) > 0: + result_text += f"\n未开始{self.mode[2:4]}的用户:\n{"\n".join(result["waiting_user"])}\n" + # 推送代理结果通知 Notify.push_plyer( title.replace("报告", "已完成!"), @@ -833,15 +870,7 @@ class MaaManager(QObject): f"已完成用户数:{len(over_index)},未完成用户数:{len(error_index) + len(wait_index)}", 10, ) - 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) - else: - result_text = self.push_notification( - "代理结果", title, result, if_get_text_only=True - ) + self.push_notification("代理结果", title, result) self.agree_bilibili(False) self.log_monitor.deleteLater() @@ -866,6 +895,86 @@ class MaaManager(QObject): def __capture_response(self, response: bool) -> None: self.response = response + def search_ADB_address(self) -> None: + """搜索ADB实际地址""" + + self.update_log_text.emit( + f"即将搜索ADB实际地址\n正在等待模拟器完成启动\n请等待{self.wait_time}s" + ) + + for _ in range(self.wait_time): + if self.isInterruptionRequested: + break + time.sleep(1) + + # 移除静默进程标记 + Config.silence_list.remove(self.emulator_path) + + if "-" in self.ADB_address: + ADB_ip = f"{self.ADB_address.split("-")[0]}-" + ADB_port = int(self.ADB_address.split("-")[1]) + + elif ":" in self.ADB_address: + ADB_ip = f"{self.ADB_address.split(':')[0]}:" + ADB_port = int(self.ADB_address.split(":")[1]) + + logger.info( + f"{self.name} | 正在搜索ADB实际地址,ADB前缀:{ADB_ip},初始端口:{ADB_port},搜索范围:{self.port_range}" + ) + + for port in self.port_range: + + ADB_address = f"{ADB_ip}{ADB_port + port}" + + # 尝试通过ADB连接到指定地址 + connect_result = subprocess.run( + [self.ADB_path, "connect", ADB_address], + creationflags=subprocess.CREATE_NO_WINDOW, + capture_output=True, + text=True, + encoding="utf-8", + ) + + if "connected" in connect_result.stdout: + + # 检查连接状态 + devices_result = subprocess.run( + [self.ADB_path, "devices"], + creationflags=subprocess.CREATE_NO_WINDOW, + capture_output=True, + text=True, + encoding="utf-8", + ) + if ADB_address in devices_result.stdout: + + logger.info(f"{self.name} | ADB实际地址:{ADB_address}") + + # 断开连接 + subprocess.run( + [self.ADB_path, "disconnect", ADB_address], + creationflags=subprocess.CREATE_NO_WINDOW, + ) + + self.ADB_address = ADB_address + + # 覆写当前ADB地址 + System.kill_process(self.maa_exe_path) + with self.maa_set_path.open(mode="r", encoding="utf-8") as f: + data = json.load(f) + data["Configurations"]["Default"][ + "Connect.Address" + ] = self.ADB_address + data["Configurations"]["Default"]["Start.EmulatorWaitSeconds"] = "0" + with self.maa_set_path.open(mode="w", encoding="utf-8") as f: + json.dump(data, f, ensure_ascii=False, indent=4) + + return None + + else: + logger.info(f"{self.name} | 无法连接到ADB地址:{ADB_address}") + else: + logger.info(f"{self.name} | 无法连接到ADB地址:{ADB_address}") + def refresh_maa_log(self) -> None: """刷新MAA日志""" @@ -1168,6 +1277,9 @@ class MaaManager(QObject): ]["Id"] # 按预设设定任务 + data["Configurations"]["Default"][ + "TaskQueue.WakeUp.IsChecked" + ] = "True" # 开始唤醒 data["Configurations"]["Default"]["TaskQueue.Recruiting.IsChecked"] = ( self.task_dict["Recruiting"] ) # 自动公招 @@ -1192,10 +1304,6 @@ class MaaManager(QObject): if user_data["Info"]["Mode"] == "简洁": - data["Configurations"]["Default"][ - "TaskQueue.WakeUp.IsChecked" - ] = "True" # 开始唤醒 - data["Configurations"]["Default"]["Start.ClientType"] = user_data[ "Info" ][ @@ -1294,7 +1402,9 @@ class MaaManager(QObject): ] = "True" # 备选关卡 data["Configurations"]["Default"][ "Fight.UseRemainingSanityStage" - ] = "True" # 使用剩余理智 + ] = ( + "True" if user_data["Info"]["GameId_Remain"] != "-" else "False" + ) # 使用剩余理智 data["Configurations"]["Default"][ "Fight.UseExpiringMedicine" ] = "True" # 无限吃48小时内过期的理智药 @@ -1392,7 +1502,9 @@ class MaaManager(QObject): ] = "True" # 备选关卡 data["Configurations"]["Default"][ "Fight.UseRemainingSanityStage" - ] = "True" # 使用剩余理智 + ] = ( + "True" if user_data["Info"]["GameId_Remain"] != "-" else "False" + ) # 使用剩余理智 # 基建模式 if ( @@ -1418,12 +1530,20 @@ class MaaManager(QObject): "Start.RunDirectly" ] = "True" # 启动MAA后直接运行 data["Global"]["Start.MinimizeDirectly"] = "True" # 启动MAA后直接最小化 - data["Global"]["GUI.UseTray"] = "True" # 显示托盘图标 data["Global"]["GUI.MinimizeToTray"] = "True" # 最小化时隐藏至托盘 data["Configurations"]["Default"]["Start.OpenEmulatorAfterLaunch"] = str( self.if_open_emulator ) # 启动MAA后自动开启模拟器 + data["Global"][ + "VersionUpdate.ScheduledUpdateCheck" + ] = "False" # 定时检查更新 + data["Global"][ + "VersionUpdate.AutoDownloadUpdatePackage" + ] = "False" # 自动下载更新包 + data["Global"][ + "VersionUpdate.AutoInstallUpdatePackage" + ] = "False" # 自动安装更新包 # 账号切换 if user_data["Info"]["Server"] == "Official": @@ -1439,15 +1559,6 @@ class MaaManager(QObject): if user_data["Info"]["Mode"] == "简洁": - data["Global"][ - "VersionUpdate.ScheduledUpdateCheck" - ] = "False" # 定时检查更新 - data["Global"][ - "VersionUpdate.AutoDownloadUpdatePackage" - ] = "False" # 自动下载更新包 - data["Global"][ - "VersionUpdate.AutoInstallUpdatePackage" - ] = "False" # 自动安装更新包 data["Configurations"]["Default"]["Start.ClientType"] = user_data[ "Info" ][ @@ -1494,6 +1605,15 @@ class MaaManager(QObject): data["Configurations"]["Default"][ "Start.OpenEmulatorAfterLaunch" ] = "False" # 启动MAA后自动开启模拟器 + data["Global"][ + "VersionUpdate.ScheduledUpdateCheck" + ] = "False" # 定时检查更新 + data["Global"][ + "VersionUpdate.AutoDownloadUpdatePackage" + ] = "False" # 自动下载更新包 + data["Global"][ + "VersionUpdate.AutoInstallUpdatePackage" + ] = "False" # 自动安装更新包 if Config.get(Config.function_IfSilence): data["Global"][ @@ -1502,15 +1622,6 @@ class MaaManager(QObject): if "全局" in mode: - data["Global"][ - "VersionUpdate.ScheduledUpdateCheck" - ] = "False" # 定时检查更新 - data["Global"][ - "VersionUpdate.AutoDownloadUpdatePackage" - ] = "False" # 自动下载更新包 - data["Global"][ - "VersionUpdate.AutoInstallUpdatePackage" - ] = "False" # 自动安装更新包 data["Configurations"]["Default"][ "TaskQueue.WakeUp.IsChecked" ] = "False" # 开始唤醒 @@ -1635,16 +1746,21 @@ class MaaManager(QObject): mode: str, title: str, message: Union[str, dict], - if_get_text_only: bool = False, - ) -> str: + user_data: Dict[str, Dict[str, Union[str, int, bool]]] = None, + ) -> None: """通过所有渠道推送通知""" env = Environment( loader=FileSystemLoader(str(Config.app_path / "resources/html")) ) - if mode == "代理结果": - + if mode == "代理结果" and ( + Config.get(Config.notify_SendTaskResultTime) == "任何时刻" + or ( + Config.get(Config.notify_SendTaskResultTime) == "仅失败时" + and message["uncompleted_count"] != 0 + ) + ): # 生成文本通知内容 message_text = ( f"任务开始时间:{message["start_time"]},结束时间:{message["end_time"]}\n" @@ -1656,9 +1772,6 @@ class MaaManager(QObject): if len(message["waiting_user"]) > 0: message_text += f"\n未开始{self.mode[2:4]}的用户:\n{"\n".join(message["waiting_user"])}\n" - if if_get_text_only: - return message_text - # 生成HTML通知内容 message["failed_user"] = "、".join(message["failed_user"]) message["waiting_user"] = "、".join(message["waiting_user"]) @@ -1666,11 +1779,31 @@ class MaaManager(QObject): template = env.get_template("MAA_result.html") message_html = template.render(message) - Notify.send_mail("网页", title, message_html) - Notify.ServerChanPush(title, f"{message_text}\n\nAUTO_MAA 敬上") - Notify.CompanyWebHookBotPush(title, f"{message_text}\n\nAUTO_MAA 敬上") + # ServerChan的换行是两个换行符。故而将\n替换为\n\n + serverchan_message = message_text.replace("\n", "\n\n") - return message_text + # 发送全局通知 + + if Config.get(Config.notify_IfSendMail): + Notify.send_mail( + "网页", title, message_html, Config.get(Config.notify_ToAddress) + ) + + if Config.get(Config.notify_IfServerChan): + Notify.ServerChanPush( + title, + f"{serverchan_message}\n\nAUTO_MAA 敬上", + Config.get(Config.notify_ServerChanKey), + Config.get(Config.notify_ServerChanTag), + Config.get(Config.notify_ServerChanChannel), + ) + + if Config.get(Config.notify_IfCompanyWebHookBot): + Notify.CompanyWebHookBotPush( + title, + f"{message_text}\n\nAUTO_MAA 敬上", + Config.get(Config.notify_CompanyWebHookBotUrl), + ) elif mode == "统计信息": @@ -1699,18 +1832,155 @@ class MaaManager(QObject): template = env.get_template("MAA_statistics.html") message_html = template.render(message) - Notify.send_mail("网页", title, message_html) # 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 敬上") + + # 发送全局通知 + if Config.get(Config.notify_IfSendStatistic): + + if Config.get(Config.notify_IfSendMail): + Notify.send_mail( + "网页", title, message_html, Config.get(Config.notify_ToAddress) + ) + + if Config.get(Config.notify_IfServerChan): + Notify.ServerChanPush( + title, + f"{serverchan_message}\n\nAUTO_MAA 敬上", + Config.get(Config.notify_ServerChanKey), + Config.get(Config.notify_ServerChanTag), + Config.get(Config.notify_ServerChanChannel), + ) + + if Config.get(Config.notify_IfCompanyWebHookBot): + Notify.CompanyWebHookBotPush( + title, + f"{message_text}\n\nAUTO_MAA 敬上", + Config.get(Config.notify_CompanyWebHookBotUrl), + ) + + # 发送用户单独通知 + if ( + user_data["Notify"]["Enabled"] + and user_data["Notify"]["IfSendStatistic"] + ): + + # 发送邮件通知 + if user_data["Notify"]["IfSendMail"]: + if user_data["Notify"]["ToAddress"]: + Notify.send_mail( + "网页", + title, + message_html, + user_data["Notify"]["ToAddress"], + ) + else: + logger.error( + f"{self.name} | 用户邮箱地址为空,无法发送用户单独的邮件通知" + ) + + # 发送ServerChan通知 + if user_data["Notify"]["IfServerChan"]: + if user_data["Notify"]["ServerChanKey"]: + Notify.ServerChanPush( + title, + f"{serverchan_message}\n\nAUTO_MAA 敬上", + user_data["Notify"]["ServerChanKey"], + user_data["Notify"]["ServerChanTag"], + user_data["Notify"]["ServerChanChannel"], + ) + else: + logger.error( + f"{self.name} |用户ServerChan密钥为空,无法发送用户单独的ServerChan通知" + ) + + # 推送CompanyWebHookBot通知 + if user_data["Notify"]["IfCompanyWebHookBot"]: + if user_data["Notify"]["CompanyWebHookBotUrl"]: + Notify.CompanyWebHookBotPush( + title, + f"{message_text}\n\nAUTO_MAA 敬上", + user_data["Notify"]["CompanyWebHookBotUrl"], + ) + else: + logger.error( + f"{self.name} |用户CompanyWebHookBot密钥为空,无法发送用户单独的CompanyWebHookBot通知" + ) elif mode == "公招六星": # 生成HTML通知内容 template = env.get_template("MAA_six_star.html") + message_html = template.render(message) - Notify.send_mail("网页", title, message_html) - Notify.ServerChanPush(title, "好羡慕~\n\nAUTO_MAA 敬上") - Notify.CompanyWebHookBotPush(title, "好羡慕~\n\nAUTO_MAA 敬上") + # 发送全局通知 + if Config.get(Config.notify_IfSendSixStar): + + if Config.get(Config.notify_IfSendMail): + Notify.send_mail( + "网页", title, message_html, Config.get(Config.notify_ToAddress) + ) + + if Config.get(Config.notify_IfServerChan): + Notify.ServerChanPush( + title, + "好羡慕~\n\nAUTO_MAA 敬上", + Config.get(Config.notify_ServerChanKey), + Config.get(Config.notify_ServerChanTag), + Config.get(Config.notify_ServerChanChannel), + ) + + if Config.get(Config.notify_IfCompanyWebHookBot): + Notify.CompanyWebHookBotPush( + title, + "好羡慕~\n\nAUTO_MAA 敬上", + Config.get(Config.notify_CompanyWebHookBotUrl), + ) + + # 发送用户单独通知 + if user_data["Notify"]["Enabled"] and user_data["Notify"]["IfSendSixStar"]: + + # 发送邮件通知 + if user_data["Notify"]["IfSendMail"]: + if user_data["Notify"]["ToAddress"]: + Notify.send_mail( + "网页", + title, + message_html, + user_data["Notify"]["ToAddress"], + ) + else: + logger.error( + f"{self.name} | 用户邮箱地址为空,无法发送用户单独的邮件通知" + ) + + # 发送ServerChan通知 + if user_data["Notify"]["IfServerChan"]: + + if user_data["Notify"]["ServerChanKey"]: + Notify.ServerChanPush( + title, + "好羡慕~\n\nAUTO_MAA 敬上", + user_data["Notify"]["ServerChanKey"], + user_data["Notify"]["ServerChanTag"], + user_data["Notify"]["ServerChanChannel"], + ) + else: + logger.error( + f"{self.name} |用户ServerChan密钥为空,无法发送用户单独的ServerChan通知" + ) + + # 推送CompanyWebHookBot通知 + if user_data["Notify"]["IfCompanyWebHookBot"]: + if user_data["Notify"]["CompanyWebHookBotUrl"]: + Notify.CompanyWebHookBotPush( + title, + "好羡慕~\n\nAUTO_MAA 敬上", + user_data["Notify"]["CompanyWebHookBotUrl"], + ) + else: + logger.error( + f"{self.name} |用户CompanyWebHookBot密钥为空,无法发送用户单独的CompanyWebHookBot通知" + ) + return None diff --git a/app/services/notification.py b/app/services/notification.py index 0f63acf..7d17545 100644 --- a/app/services/notification.py +++ b/app/services/notification.py @@ -25,18 +25,20 @@ v4.3 作者:DLmaster_361 """ -from PySide6.QtWidgets import QWidget -from PySide6.QtCore import Signal -import requests -import time -from loguru import logger -from plyer import notification import re import smtplib -from email.mime.text import MIMEText -from email.mime.multipart import MIMEMultipart +import time from email.header import Header +from email.mime.multipart import MIMEMultipart +from email.mime.text import MIMEText from email.utils import formataddr + +import requests +from PySide6.QtCore import Signal +from PySide6.QtWidgets import QWidget +from loguru import logger +from plyer import notification + from app.core import Config from app.services.security import Crypto @@ -65,218 +67,210 @@ class Notification(QWidget): return True - def send_mail(self, mode, title, content) -> None: + def send_mail(self, mode, title, content, to_address) -> None: """推送邮件通知""" - - if Config.get(Config.notify_IfSendMail): - - if ( - 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.get(Config.notify_FromAddress), - ) - ) - or not bool( - re.match( - r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$", - Config.get(Config.notify_ToAddress), - ) - ) - ): - logger.error( - "请正确设置邮件通知的SMTP服务器地址、授权码、发件人地址和收件人地址" - ) - self.push_info_bar.emit( - "error", - "邮件通知推送异常", - "请正确设置邮件通知的SMTP服务器地址、授权码、发件人地址和收件人地址", - -1, - ) - return None - - try: - # 定义邮件正文 - if mode == "文本": - message = MIMEText(content, "plain", "utf-8") - elif mode == "网页": - message = MIMEMultipart("alternative") - message["From"] = formataddr( - ( - Header("AUTO_MAA通知服务", "utf-8").encode(), - Config.get(Config.notify_FromAddress), - ) - ) # 发件人显示的名字 - message["To"] = formataddr( - ( - Header("AUTO_MAA用户", "utf-8").encode(), - Config.get(Config.notify_ToAddress), - ) - ) # 收件人显示的名字 - message["Subject"] = Header(title, "utf-8") - - if mode == "网页": - message.attach(MIMEText(content, "html", "utf-8")) - - smtpObj = smtplib.SMTP_SSL( - Config.get(Config.notify_SMTPServerAddress), - 465, - ) - smtpObj.login( + if ( + 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.get(Config.notify_FromAddress), - Crypto.win_decryptor(Config.get(Config.notify_AuthorizationCode)), ) - smtpObj.sendmail( + ) + or not bool( + re.match( + r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$", + to_address, + ) + ) + ): + logger.error( + "请正确设置邮件通知的SMTP服务器地址、授权码、发件人地址和收件人地址" + ) + self.push_info_bar.emit( + "error", + "邮件通知推送异常", + "请正确设置邮件通知的SMTP服务器地址、授权码、发件人地址和收件人地址", + -1, + ) + return None + + try: + # 定义邮件正文 + if mode == "文本": + message = MIMEText(content, "plain", "utf-8") + elif mode == "网页": + message = MIMEMultipart("alternative") + message["From"] = formataddr( + ( + Header("AUTO_MAA通知服务", "utf-8").encode(), Config.get(Config.notify_FromAddress), - Config.get(Config.notify_ToAddress), - message.as_string(), ) - smtpObj.quit() - logger.success("邮件发送成功") - except Exception as e: - logger.error(f"发送邮件时出错:\n{e}") - self.push_info_bar.emit("error", "发送邮件时出错", f"{e}", -1) - - def ServerChanPush(self, title, content): - """使用Server酱推送通知(支持 tag 和 channel,避免使用SDK)""" - if Config.get(Config.notify_IfServerChan): - send_key = Config.get(Config.notify_ServerChanKey) - - if not send_key: - logger.error("请正确设置Server酱的SendKey") - self.push_info_bar.emit( - "error", "Server酱通知推送异常", "请正确设置Server酱的SendKey", -1 + ) # 发件人显示的名字 + message["To"] = formataddr( + ( + Header("AUTO_MAA用户", "utf-8").encode(), + to_address, ) - return None + ) # 收件人显示的名字 + message["Subject"] = Header(title, "utf-8") - try: - # 构造 URL - if send_key.startswith("sctp"): - match = re.match(r"^sctp(\d+)t", send_key) - if match: - url = f"https://{match.group(1)}.push.ft07.com/send/{send_key}.send" - else: - raise ValueError("SendKey 格式错误(sctp)") + if mode == "网页": + message.attach(MIMEText(content, "html", "utf-8")) + + smtpObj = smtplib.SMTP_SSL( + Config.get(Config.notify_SMTPServerAddress), + 465, + ) + smtpObj.login( + Config.get(Config.notify_FromAddress), + Crypto.win_decryptor(Config.get(Config.notify_AuthorizationCode)), + ) + smtpObj.sendmail( + Config.get(Config.notify_FromAddress), + to_address, + message.as_string(), + ) + smtpObj.quit() + logger.success("邮件发送成功") + return None + except Exception as e: + logger.error(f"发送邮件时出错:\n{e}") + self.push_info_bar.emit("error", "发送邮件时出错", f"{e}", -1) + return None + return None + + def ServerChanPush(self, title, content, send_key, tag, channel): + """使用Server酱推送通知""" + if not send_key: + logger.error("请正确设置Server酱的SendKey") + self.push_info_bar.emit( + "error", "Server酱通知推送异常", "请正确设置Server酱的SendKey", -1 + ) + return None + + try: + # 构造 URL + if send_key.startswith("sctp"): + match = re.match(r"^sctp(\d+)t", send_key) + if match: + url = f"https://{match.group(1)}.push.ft07.com/send/{send_key}.send" else: - url = f"https://sctapi.ftqq.com/{send_key}.send" - - # 构建 tags 和 channel - def is_valid(s): - return s == "" or ( - s == "|".join(s.split("|")) - and (s.count("|") == 0 or all(s.split("|"))) - ) - - tags = "|".join( - _.strip() - for _ in Config.get(Config.notify_ServerChanTag).split("|") - ) - channels = "|".join( - _.strip() - for _ in Config.get(Config.notify_ServerChanChannel).split("|") - ) - - options = {} - if is_valid(tags): - options["tags"] = tags - else: - logger.warning("Server酱 Tag 配置不正确,将被忽略") - self.push_info_bar.emit( - "warning", - "Server酱通知推送异常", - "请正确设置 ServerChan 的 Tag", - -1, - ) - - if is_valid(channels): - options["channel"] = channels - else: - logger.warning("Server酱 Channel 配置不正确,将被忽略") - self.push_info_bar.emit( - "warning", - "Server酱通知推送异常", - "请正确设置 ServerChan 的 Channel", - -1, - ) - - # 请求发送 - params = {"title": title, "desp": content, **options} - headers = {"Content-Type": "application/json;charset=utf-8"} - - response = requests.post(url, json=params, headers=headers, timeout=10) - result = response.json() - - if result.get("code") == 0: - logger.info("Server酱推送通知成功") - return True - else: - error_code = result.get("code", "-1") - logger.error(f"Server酱通知推送失败:响应码:{error_code}") - self.push_info_bar.emit( - "error", "Server酱通知推送失败", f"响应码:{error_code}", -1 - ) - return f"Server酱通知推送失败:{error_code}" - - except Exception as e: - logger.exception("Server酱通知推送异常") - self.push_info_bar.emit( - "error", "Server酱通知推送异常", f"请检查相关设置,如还有问题可联系开发者", -1 - ) - return f"Server酱通知推送异常:{str(e)}" - - def CompanyWebHookBotPush(self, title, content): - """使用企业微信群机器人推送通知""" - 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}} - # 从远程服务器获取最新主题图像 - for _ in range(3): - try: - response = requests.post( - url=Config.get(Config.notify_CompanyWebHookBotUrl), - json=data, - timeout=10, - ) - info = response.json() - break - except Exception as e: - err = e - time.sleep(0.1) + raise ValueError("SendKey 格式错误(sctp)") else: - logger.error(f"推送企业微信群机器人时出错:{err}") + url = f"https://sctapi.ftqq.com/{send_key}.send" + + # 构建 tags 和 channel + def is_valid(s): + return s == "" or ( + s == "|".join(s.split("|")) + and (s.count("|") == 0 or all(s.split("|"))) + ) + + tags = "|".join(_.strip() for _ in tag.split("|")) + channels = "|".join(_.strip() for _ in channel.split("|")) + + options = {} + if is_valid(tags): + options["tags"] = tags + else: + logger.warning("Server酱 Tag 配置不正确,将被忽略") self.push_info_bar.emit( - "error", - "企业微信群机器人通知推送失败", - f'使用企业微信群机器人推送通知时出错:{info["errmsg"]}', + "warning", + "Server酱通知推送异常", + "请正确设置 ServerChan 的 Tag", -1, ) - return None - if info["errcode"] == 0: - logger.info("企业微信群机器人推送通知成功") + if is_valid(channels): + options["channel"] = channels + else: + logger.warning("Server酱 Channel 配置不正确,将被忽略") + self.push_info_bar.emit( + "warning", + "Server酱通知推送异常", + "请正确设置 ServerChan 的 Channel", + -1, + ) + + # 请求发送 + params = {"title": title, "desp": content, **options} + headers = {"Content-Type": "application/json;charset=utf-8"} + + response = requests.post(url, json=params, headers=headers, timeout=10) + result = response.json() + + if result.get("code") == 0: + logger.info("Server酱推送通知成功") return True else: - logger.error(f"企业微信群机器人推送通知失败:{info}") + error_code = result.get("code", "-1") + logger.error(f"Server酱通知推送失败:响应码:{error_code}") self.push_info_bar.emit( - "error", - "企业微信群机器人通知推送失败", - f'使用企业微信群机器人推送通知时出错:{info["errmsg"]}', - -1, + "error", "Server酱通知推送失败", f"响应码:{error_code}", -1 ) - return f'使用企业微信群机器人推送通知时出错:{info["errmsg"]}' + return f"Server酱通知推送失败:{error_code}" + + except Exception as e: + logger.exception("Server酱通知推送异常") + self.push_info_bar.emit( + "error", + "Server酱通知推送异常", + "请检查相关设置和网络连接。如全部配置正确,请稍后再试。", + -1, + ) + return f"Server酱通知推送异常:{str(e)}" + + def CompanyWebHookBotPush(self, title, content, webhook_url): + """使用企业微信群机器人推送通知""" + if webhook_url == "": + logger.error("请正确设置企业微信群机器人的WebHook地址") + self.push_info_bar.emit( + "error", + "企业微信群机器人通知推送异常", + "请正确设置企业微信群机器人的WebHook地址", + -1, + ) + return None + + content = f"{title}\n{content}" + data = {"msgtype": "text", "text": {"content": content}} + + for _ in range(3): + try: + response = requests.post( + url=webhook_url, + json=data, + timeout=10, + ) + info = response.json() + break + except Exception as e: + err = e + time.sleep(0.1) + else: + logger.error(f"推送企业微信群机器人时出错:{err}") + self.push_info_bar.emit( + "error", + "企业微信群机器人通知推送失败", + f"使用企业微信群机器人推送通知时出错:{err}", + -1, + ) + return None + + if info["errcode"] == 0: + logger.info("企业微信群机器人推送通知成功") + return True + else: + logger.error(f"企业微信群机器人推送通知失败:{info}") + self.push_info_bar.emit( + "error", + "企业微信群机器人通知推送失败", + f"使用企业微信群机器人推送通知时出错:{err}", + -1, + ) + return f"使用企业微信群机器人推送通知时出错:{err}" def send_test_notification(self): """发送测试通知到所有已启用的通知渠道""" @@ -294,6 +288,7 @@ class Notification(QWidget): "文本", "AUTO_MAA测试通知", "这是 AUTO_MAA 外部通知测试信息。如果你看到了这段内容,说明 AUTO_MAA 的通知功能已经正确配置且可以正常工作!", + Config.get(Config.notify_ToAddress), ) # 发送Server酱通知 @@ -301,6 +296,9 @@ class Notification(QWidget): self.ServerChanPush( "AUTO_MAA测试通知", "这是 AUTO_MAA 外部通知测试信息。如果你看到了这段内容,说明 AUTO_MAA 的通知功能已经正确配置且可以正常工作!", + Config.get(Config.notify_ServerChanKey), + Config.get(Config.notify_ServerChanTag), + Config.get(Config.notify_ServerChanChannel), ) # 发送企业微信机器人通知 @@ -308,6 +306,7 @@ class Notification(QWidget): self.CompanyWebHookBotPush( "AUTO_MAA测试通知", "这是 AUTO_MAA 外部通知测试信息。如果你看到了这段内容,说明 AUTO_MAA 的通知功能已经正确配置且可以正常工作!", + Config.get(Config.notify_CompanyWebHookBotUrl), ) return True diff --git a/app/ui/Widget.py b/app/ui/Widget.py index cf7f201..16b40be 100644 --- a/app/ui/Widget.py +++ b/app/ui/Widget.py @@ -25,17 +25,24 @@ v4.3 作者:DLmaster_361 """ +import os +import re +from datetime import datetime +from functools import partial +from typing import Optional, Union, List, Dict +from urllib.parse import urlparse + +import markdown +from PySide6.QtCore import Qt, QTime, QTimer, QEvent, QSize +from PySide6.QtGui import QIcon, QPixmap, QPainter, QPainterPath from PySide6.QtWidgets import ( QApplication, 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, @@ -74,15 +81,10 @@ from qfluentwidgets import ( ScrollArea, Pivot, PivotItem, + FlyoutViewBase, + PushSettingCard, ) from qfluentwidgets.common.overload import singledispatchmethod -import os -import re -import markdown -from datetime import datetime -from urllib.parse import urlparse -from functools import partial -from typing import Optional, Union, List, Dict from app.core import Config from app.services import Crypto @@ -108,6 +110,8 @@ class LineEditMessageBox(MessageBoxBase): self.viewLayout.addWidget(self.title) self.viewLayout.addWidget(self.input) + self.input.setFocus() + class ComboBoxMessageBox(MessageBoxBase): """选择对话框""" @@ -271,6 +275,41 @@ class NoticeMessageBox(MessageBoxBase): self.Layout.addStretch(1) +class SettingFlyoutView(FlyoutViewBase): + """设置卡二级菜单弹出组件""" + + def __init__( + self, + parent, + title: str, + setting_cards: List[Union[SettingCard, HeaderCardWidget]], + ): + super().__init__(parent) + + self.title = SubtitleLabel(title) + + content_widget = QWidget() + content_layout = QVBoxLayout(content_widget) + content_layout.setSpacing(0) + content_layout.setContentsMargins(0, 0, 11, 0) + for setting_card in setting_cards: + content_layout.addWidget(setting_card) + + scrollArea = ScrollArea() + scrollArea.setWidgetResizable(True) + scrollArea.setContentsMargins(0, 0, 0, 0) + scrollArea.setStyleSheet("background: transparent; border: none;") + scrollArea.setWidget(content_widget) + + self.viewLayout = QVBoxLayout(self) + self.viewLayout.setSpacing(12) + self.viewLayout.setContentsMargins(20, 16, 9, 16) + self.viewLayout.addWidget(self.title) + self.viewLayout.addWidget(scrollArea) + + self.setVisible(False) + + class SwitchSettingCard(SettingCard): """Setting card with switch button""" @@ -431,24 +470,27 @@ class LineEditSettingCard(SettingCard): self.LineEdit.setMinimumWidth(250) self.LineEdit.setPlaceholderText(text) - if configItem: - self.setValue(self.qconfig.get(configItem)) - configItem.valueChanged.connect(self.setValue) - self.hBoxLayout.addWidget(self.LineEdit, 0, Qt.AlignRight) self.hBoxLayout.addSpacing(16) + self.configItem.valueChanged.connect(self.setValue) self.LineEdit.textChanged.connect(self.__textChanged) + self.setValue(self.qconfig.get(configItem)) + def __textChanged(self, content: str): - self.setValue(content.strip()) + + self.configItem.valueChanged.disconnect(self.setValue) + self.qconfig.set(self.configItem, content.strip()) + self.configItem.valueChanged.connect(self.setValue) + self.textChanged.emit(content.strip()) def setValue(self, content: str): - if self.configItem: - self.qconfig.set(self.configItem, content.strip()) + self.LineEdit.textChanged.disconnect(self.__textChanged) self.LineEdit.setText(content.strip()) + self.LineEdit.textChanged.connect(self.__textChanged) class PasswordLineEditSettingCard(SettingCard): @@ -477,35 +519,29 @@ class PasswordLineEditSettingCard(SettingCard): self.LineEdit.setPlaceholderText(text) if algorithm == "AUTO": self.LineEdit.setViewPasswordButtonVisible(False) - self.if_setValue = False - - if configItem: - self.setValue(self.qconfig.get(configItem)) - configItem.valueChanged.connect(self.setValue) self.hBoxLayout.addWidget(self.LineEdit, 0, Qt.AlignRight) self.hBoxLayout.addSpacing(16) + self.configItem.valueChanged.connect(self.setValue) self.LineEdit.textChanged.connect(self.__textChanged) + self.setValue(self.qconfig.get(configItem)) + def __textChanged(self, content: str): - if self.if_setValue: - return None - + self.configItem.valueChanged.disconnect(self.setValue) if self.algorithm == "DPAPI": - self.setValue(Crypto.win_encryptor(content)) + self.qconfig.set(self.configItem, Crypto.win_encryptor(content)) elif self.algorithm == "AUTO": - self.setValue(Crypto.AUTO_encryptor(content)) + self.qconfig.set(self.configItem, Crypto.AUTO_encryptor(content)) + self.configItem.valueChanged.connect(self.setValue) + self.textChanged.emit() def setValue(self, content: str): - self.if_setValue = True - - if self.configItem: - self.qconfig.set(self.configItem, content) - + self.LineEdit.textChanged.disconnect(self.__textChanged) if self.algorithm == "DPAPI": self.LineEdit.setText(Crypto.win_decryptor(content)) elif self.algorithm == "AUTO": @@ -521,59 +557,7 @@ class PasswordLineEditSettingCard(SettingCard): 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().strftime("%Y-%m-%d") - == self.qconfig.get(self.configItems["LastProxyDate"]) - else "今日未进行代理" - ) - text_list.append( - "本周剿灭已完成" - if datetime.strptime( - self.qconfig.get(self.configItems["LastAnnihilationDate"]), - "%Y-%m-%d", - ).isocalendar()[:2] - == Config.server_date().isocalendar()[:2] - else "本周剿灭未完成" - ) - - self.Lable.setText(" | ".join(text_list)) + self.LineEdit.textChanged.connect(self.__textChanged) class PushAndSwitchButtonSettingCard(SettingCard): @@ -724,7 +708,7 @@ class NoOptionComboBoxSettingCard(SettingCard): value: List[str], texts: List[str], qconfig: QConfig, - configItem: OptionsConfigItem, + configItem: ConfigItem, parent=None, ): @@ -757,7 +741,7 @@ class NoOptionComboBoxSettingCard(SettingCard): def reLoadOptions(self, value: List[str], texts: List[str]): - self.comboBox.currentIndexChanged.disconnect() + self.comboBox.currentIndexChanged.disconnect(self._onCurrentIndexChanged) self.comboBox.clear() self.optionToText = {o: t for o, t in zip(value, texts)} for text, option in zip(texts, value): @@ -779,7 +763,7 @@ class EditableComboBoxSettingCard(SettingCard): value: List[str], texts: List[str], qconfig: QConfig, - configItem: OptionsConfigItem, + configItem: ConfigItem, parent=None, ): @@ -829,7 +813,7 @@ class EditableComboBoxSettingCard(SettingCard): def reLoadOptions(self, value: List[str], texts: List[str]): - self.comboBox.currentIndexChanged.disconnect() + self.comboBox.currentIndexChanged.disconnect(self._onCurrentIndexChanged) self.comboBox.clear() self.optionToText = {o: t for o, t in zip(value, texts)} for text, option in zip(texts, value): @@ -867,6 +851,169 @@ class EditableComboBoxSettingCard(SettingCard): self.currentIndexChanged.emit(self.count() - 1) +class SpinBoxWithPlanSettingCard(SpinBoxSettingCard): + + textChanged = Signal(int) + + def __init__( + self, + icon: Union[str, QIcon, FluentIconBase], + title: str, + content: Union[str, None], + range: tuple[int, int], + qconfig: QConfig, + configItem: ConfigItem, + parent=None, + ): + + super().__init__(icon, title, content, range, qconfig, configItem, parent) + + self.configItem_plan = None + + self.LineEdit = LineEdit(self) + self.LineEdit.setMinimumWidth(150) + self.LineEdit.setReadOnly(True) + self.LineEdit.setVisible(False) + + self.hBoxLayout.insertWidget(5, self.LineEdit, 0, Qt.AlignRight) + + def setText(self, value: int) -> None: + self.LineEdit.setText(str(value)) + + def switch_mode(self, mode: str) -> None: + """切换模式""" + + if mode == "固定": + + self.LineEdit.setVisible(False) + self.SpinBox.setVisible(True) + + elif mode == "计划": + + self.SpinBox.setVisible(False) + self.LineEdit.setVisible(True) + + def change_plan(self, configItem_plan: ConfigItem) -> None: + """切换计划""" + + if self.configItem_plan is not None: + self.configItem_plan.valueChanged.disconnect(self.setText) + self.configItem_plan = configItem_plan + self.configItem_plan.valueChanged.connect(self.setText) + self.setText(self.qconfig.get(self.configItem_plan)) + + +class ComboBoxWithPlanSettingCard(ComboBoxSettingCard): + + 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, texts, qconfig, configItem, parent) + + self.configItem_plan = None + + self.LineEdit = LineEdit(self) + self.LineEdit.setMinimumWidth(150) + self.LineEdit.setReadOnly(True) + self.LineEdit.setVisible(False) + + self.hBoxLayout.insertWidget(5, self.LineEdit, 0, Qt.AlignRight) + + def setText(self, value: str) -> None: + + if value not in self.optionToText: + self.optionToText[value] = value + + self.LineEdit.setText(self.optionToText[value]) + + def switch_mode(self, mode: str) -> None: + """切换模式""" + + if mode == "固定": + + self.LineEdit.setVisible(False) + self.comboBox.setVisible(True) + + elif mode == "计划": + + self.comboBox.setVisible(False) + self.LineEdit.setVisible(True) + + def change_plan(self, configItem_plan: ConfigItem) -> None: + """切换计划""" + + if self.configItem_plan is not None: + self.configItem_plan.valueChanged.disconnect(self.setText) + self.configItem_plan = configItem_plan + self.configItem_plan.valueChanged.connect(self.setText) + self.setText(self.qconfig.get(self.configItem_plan)) + + +class EditableComboBoxWithPlanSettingCard(EditableComboBoxSettingCard): + + def __init__( + self, + icon: Union[str, QIcon, FluentIconBase], + title: str, + content: Union[str, None], + value: List[str], + texts: List[str], + qconfig: QConfig, + configItem: ConfigItem, + parent=None, + ): + + super().__init__( + icon, title, content, value, texts, qconfig, configItem, parent + ) + + self.configItem_plan = None + + self.LineEdit = LineEdit(self) + self.LineEdit.setMinimumWidth(150) + self.LineEdit.setReadOnly(True) + self.LineEdit.setVisible(False) + + self.hBoxLayout.insertWidget(5, self.LineEdit, 0, Qt.AlignRight) + + def setText(self, value: str) -> None: + + if value not in self.optionToText: + self.optionToText[value] = value + + self.LineEdit.setText(self.optionToText[value]) + + def switch_mode(self, mode: str) -> None: + """切换模式""" + + if mode == "固定": + + self.LineEdit.setVisible(False) + self.comboBox.setVisible(True) + + elif mode == "计划": + + self.comboBox.setVisible(False) + self.LineEdit.setVisible(True) + + def change_plan(self, configItem_plan: ConfigItem) -> None: + """切换计划""" + + if self.configItem_plan is not None: + self.configItem_plan.valueChanged.disconnect(self.setText) + self.configItem_plan = configItem_plan + self.configItem_plan.valueChanged.connect(self.setText) + self.setText(self.qconfig.get(self.configItem_plan)) + + class TimeEditSettingCard(SettingCard): enabledChanged = Signal(bool) @@ -933,6 +1080,418 @@ class TimeEditSettingCard(SettingCard): self.TimeEdit.setTime(QTime.fromString(value, "HH:mm")) +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): + + text_list = [] + + if self.configItems: + + 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().strftime("%Y-%m-%d") + == self.qconfig.get(self.configItems["LastProxyDate"]) + else "今日未进行代理" + ) + text_list.append( + "本周剿灭已完成" + if datetime.strptime( + self.qconfig.get(self.configItems["LastAnnihilationDate"]), + "%Y-%m-%d", + ).isocalendar()[:2] + == Config.server_date().isocalendar()[:2] + else "本周剿灭未完成" + ) + + self.Lable.setText(" | ".join(text_list)) + + +class UserTaskSettingCard(PushSettingCard): + """Setting card with User's Task""" + + def __init__( + self, + icon: Union[str, QIcon, FluentIconBase], + title: str, + content: Union[str, None], + text: str, + qconfig: QConfig, + configItems: Dict[str, ConfigItem], + parent=None, + ): + + super().__init__(text, icon, title, content, parent) + self.qconfig = qconfig + self.configItems = configItems + self.Lable = SubtitleLabel(self) + + if configItems: + for config_item in configItems.values(): + config_item.valueChanged.connect(self.setValues) + self.setValues() + + self.hBoxLayout.addWidget(self.Lable, 0, Qt.AlignRight) + self.hBoxLayout.addSpacing(16) + + def setValues(self): + + text_list = [] + + if self.configItems: + + if self.qconfig.get(self.configItems["IfWakeUp"]): + text_list.append("开始唤醒") + if self.qconfig.get(self.configItems["IfRecruiting"]): + text_list.append("自动公招") + if self.qconfig.get(self.configItems["IfBase"]): + text_list.append("基建换班") + if self.qconfig.get(self.configItems["IfCombat"]): + text_list.append("刷理智") + if self.qconfig.get(self.configItems["IfMall"]): + text_list.append("获取信用及购物") + if self.qconfig.get(self.configItems["IfMission"]): + text_list.append("领取奖励") + if self.qconfig.get(self.configItems["IfAutoRoguelike"]): + text_list.append("自动肉鸽") + if self.qconfig.get(self.configItems["IfReclamation"]): + text_list.append("生息演算") + + if text_list: + self.setContent(f"任务序列:{" - ".join(text_list)}") + else: + self.setContent("未启用任何任务项") + + +class UserNoticeSettingCard(PushAndSwitchButtonSettingCard): + """Setting card with User's Notice""" + + def __init__( + self, + icon: Union[str, QIcon, FluentIconBase], + title: str, + content: Union[str, None], + text: str, + qconfig: QConfig, + configItem: ConfigItem, + configItems: Dict[str, ConfigItem], + parent=None, + ): + + super().__init__(icon, title, content, text, qconfig, configItem, parent) + self.qconfig = qconfig + self.configItems = configItems + self.Lable = SubtitleLabel(self) + + if configItems: + for config_item in configItems.values(): + config_item.valueChanged.connect(self.setValues) + self.setValues() + + self.hBoxLayout.addWidget(self.Lable, 0, Qt.AlignRight) + self.hBoxLayout.addSpacing(16) + + def setValues(self): + + def short_str(s: str) -> str: + if s.startswith(("SC", "sc")): + # SendKey:首4 + 末4 + return f"{s[:4]}***{s[-4:]}" if len(s) > 8 else s + + elif s.startswith(("http://", "https://")): + # Webhook URL:域名 + 路径尾3 + parsed_url = urlparse(s) + domain = parsed_url.netloc + path_tail = ( + parsed_url.path[-3:] + if len(parsed_url.path) > 3 + else parsed_url.path + ) + return f"{domain}***{path_tail}" + + elif "@" in s: + # 邮箱:@前3/6 + 域名 + username, domain = s.split("@", 1) + displayed_name = f"{username[:3]}***" if len(username) > 6 else username + return f"{displayed_name}@{domain}" + + else: + # 普通字符串:末尾3字符 + return f"***{s[-3:]}" if len(s) > 3 else s + + text_list = [] + + if self.configItems: + + if not ( + self.qconfig.get(self.configItems["IfSendStatistic"]) + or self.qconfig.get(self.configItems["IfSendSixStar"]) + ): + text_list.append("未启用任何通知项") + + if self.qconfig.get(self.configItems["IfSendStatistic"]): + text_list.append("统计信息已启用") + if self.qconfig.get(self.configItems["IfSendSixStar"]): + text_list.append("六星喜报已启用") + + if self.qconfig.get(self.configItems["IfSendMail"]): + text_list.append( + f"邮箱通知:{short_str(self.qconfig.get(self.configItems["ToAddress"]))}" + ) + if self.qconfig.get(self.configItems["IfServerChan"]): + text_list.append( + f"Server酱通知:{short_str(self.qconfig.get(self.configItems["ServerChanKey"]))}" + ) + if self.qconfig.get(self.configItems["IfCompanyWebHookBot"]): + text_list.append( + f"企业微信通知:{short_str(self.qconfig.get(self.configItems["CompanyWebHookBotUrl"]))}" + ) + + self.setContent(" | ".join(text_list)) + + +class StatusSwitchSetting(SwitchButton): + + def __init__( + self, + qconfig: QConfig, + configItem_check: ConfigItem, + configItem_enable: ConfigItem, + parent=None, + ): + super().__init__(parent) + self.qconfig = qconfig + self.configItem_check = configItem_check + self.configItem_enable = configItem_enable + self.setOffText("") + self.setOnText("") + + if configItem_check: + self.setValue(self.qconfig.get(configItem_check)) + configItem_check.valueChanged.connect(self.setValue) + if configItem_enable: + self.setEnabled(self.qconfig.get(configItem_enable)) + configItem_enable.valueChanged.connect(self.setEnabled) + + self.checkedChanged.connect(self.setValue) + + def setValue(self, isChecked: bool): + if self.configItem_check: + self.qconfig.set(self.configItem_check, isChecked) + + self.setChecked(isChecked) + + +class ComboBoxSetting(ComboBox): + + def __init__( + self, + texts: List[str], + qconfig: QConfig, + configItem: OptionsConfigItem, + parent=None, + ): + + super().__init__(parent) + self.qconfig = qconfig + self.configItem = configItem + + self.optionToText = {o: t for o, t in zip(configItem.options, texts)} + for text, option in zip(texts, configItem.options): + self.addItem(text, userData=option) + + self.setCurrentText(self.optionToText[self.qconfig.get(configItem)]) + self.currentIndexChanged.connect(self._onCurrentIndexChanged) + configItem.valueChanged.connect(self.setValue) + + def _onCurrentIndexChanged(self, index: int): + + self.qconfig.set(self.configItem, self.itemData(index)) + + def setValue(self, value): + if value not in self.optionToText: + return + + self.setCurrentText(self.optionToText[value]) + self.qconfig.set(self.configItem, value) + + +class NoOptionComboBoxSetting(ComboBox): + + def __init__( + self, + value: List[str], + texts: List[str], + qconfig: QConfig, + configItem: OptionsConfigItem, + parent=None, + ): + + super().__init__(parent) + self.qconfig = qconfig + self.configItem = configItem + + self.optionToText = {o: t for o, t in zip(value, texts)} + for text, option in zip(texts, value): + self.addItem(text, userData=option) + + self.setCurrentText(self.optionToText[self.qconfig.get(configItem)]) + self.currentIndexChanged.connect(self._onCurrentIndexChanged) + configItem.valueChanged.connect(self.setValue) + + def _onCurrentIndexChanged(self, index: int): + + self.qconfig.set(self.configItem, self.itemData(index)) + + def setValue(self, value): + if value not in self.optionToText: + return + + self.setCurrentText(self.optionToText[value]) + self.qconfig.set(self.configItem, value) + + def reLoadOptions(self, value: List[str], texts: List[str]): + + self.currentIndexChanged.disconnect(self._onCurrentIndexChanged) + self.clear() + self.optionToText = {o: t for o, t in zip(value, texts)} + for text, option in zip(texts, value): + self.addItem(text, userData=option) + self.setCurrentText(self.optionToText[self.qconfig.get(self.configItem)]) + self.currentIndexChanged.connect(self._onCurrentIndexChanged) + + +class EditableComboBoxSetting(EditableComboBox): + + def __init__( + self, + value: List[str], + texts: List[str], + qconfig: QConfig, + configItem: OptionsConfigItem, + parent=None, + ): + + super().__init__(parent) + self.qconfig = qconfig + self.configItem = configItem + + self.optionToText = {o: t for o, t in zip(value, texts)} + for text, option in zip(texts, value): + self.addItem(text, userData=option) + + if qconfig.get(configItem) not in self.optionToText: + self.optionToText[qconfig.get(configItem)] = qconfig.get(configItem) + self.addItem(qconfig.get(configItem), userData=qconfig.get(configItem)) + + self.setCurrentText(self.optionToText[qconfig.get(configItem)]) + self.currentIndexChanged.connect(self._onCurrentIndexChanged) + configItem.valueChanged.connect(self.setValue) + + def _onCurrentIndexChanged(self, index: int): + + self.qconfig.set( + self.configItem, + (self.itemData(index) if self.itemData(index) else self.itemText(index)), + ) + + def setValue(self, value): + if value not in self.optionToText: + self.optionToText[value] = value + if self.findText(value) == -1: + self.addItem(value, userData=value) + else: + self.setItemData(self.findText(value), value) + + self.setCurrentText(self.optionToText[value]) + self.qconfig.set(self.configItem, value) + + def reLoadOptions(self, value: List[str], texts: List[str]): + + self.currentIndexChanged.disconnect(self._onCurrentIndexChanged) + self.clear() + self.optionToText = {o: t for o, t in zip(value, texts)} + for text, option in zip(texts, value): + self.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.addItem( + self.qconfig.get(self.configItem), + userData=self.qconfig.get(self.configItem), + ) + self.setCurrentText(self.optionToText[self.qconfig.get(self.configItem)]) + self.currentIndexChanged.connect(self._onCurrentIndexChanged) + + 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 SpinBoxSetting(SpinBox): + + def __init__( + self, + range: tuple[int, int], + qconfig: QConfig, + configItem: ConfigItem, + parent=None, + ): + + super().__init__(parent) + self.qconfig = qconfig + self.configItem = configItem + self.setRange(range[0], range[1]) + + if configItem: + self.set_value(qconfig.get(configItem)) + configItem.valueChanged.connect(self.set_value) + + self.valueChanged.connect(self.set_value) + + def set_value(self, value: int): + if self.configItem: + self.qconfig.set(self.configItem, value) + + self.setValue(value) + + class HistoryCard(HeaderCardWidget): def __init__(self, qconfig: QConfig, configItem: ConfigItem, parent=None): @@ -1052,9 +1611,7 @@ class UrlListSettingCard(ExpandSettingCard): """show confirm dialog""" choice = MessageBox( - "确认", - f"确定要删除 {item.url} 代理网址吗?", - self.window(), + "确认", f"确定要删除 {item.url} 代理网址吗?", self.window() ) if choice.exec(): self.__removeUrl(item) diff --git a/app/ui/dispatch_center.py b/app/ui/dispatch_center.py index 8ecb47a..70c0747 100644 --- a/app/ui/dispatch_center.py +++ b/app/ui/dispatch_center.py @@ -333,28 +333,24 @@ class DispatchCenter(QWidget): self.setObjectName(name) - layout = QVBoxLayout() + self.top_bar = self.DispatchTopBar(self, name) + self.info = self.DispatchInfoCard(self) + + content_widget = QWidget() + content_layout = QVBoxLayout(content_widget) + content_layout.setContentsMargins(0, 0, 0, 0) + content_layout.addWidget(self.top_bar) + content_layout.addWidget(self.info) scrollArea = ScrollArea() scrollArea.setWidgetResizable(True) scrollArea.setContentsMargins(0, 0, 0, 0) scrollArea.setStyleSheet("background: transparent; border: none;") - - content_widget = QWidget() - content_layout = QVBoxLayout(content_widget) - - self.top_bar = self.DispatchTopBar(self, name) - self.info = self.DispatchInfoCard(self) - - content_layout.addWidget(self.top_bar) - content_layout.addWidget(self.info) - scrollArea.setWidget(content_widget) + layout = QVBoxLayout(self) layout.addWidget(scrollArea) - self.setLayout(layout) - class DispatchTopBar(CardWidget): def __init__(self, parent=None, name: str = None): diff --git a/app/ui/history.py b/app/ui/history.py index 6da7a8e..ddf5d95 100644 --- a/app/ui/history.py +++ b/app/ui/history.py @@ -61,21 +61,22 @@ class History(QWidget): super().__init__(parent) self.setObjectName("历史记录") + self.history_top_bar = self.HistoryTopBar(self) + self.history_top_bar.search_history.connect(self.reload_history) + content_widget = QWidget() self.content_layout = QVBoxLayout(content_widget) - self.history_top_bar = self.HistoryTopBar(self) - - self.history_top_bar.search_history.connect(self.reload_history) + self.content_layout.setContentsMargins(0, 0, 11, 0) scrollArea = ScrollArea() scrollArea.setWidgetResizable(True) scrollArea.setContentsMargins(0, 0, 0, 0) scrollArea.setStyleSheet("background: transparent; border: none;") scrollArea.setWidget(content_widget) - layout = QVBoxLayout() + + layout = QVBoxLayout(self) layout.addWidget(self.history_top_bar) layout.addWidget(scrollArea) - self.setLayout(layout) self.history_card_list = [] diff --git a/app/ui/home.py b/app/ui/home.py index 3bd08c5..9a60344 100644 --- a/app/ui/home.py +++ b/app/ui/home.py @@ -62,14 +62,6 @@ class Home(QWidget): self.banner = Banner() self.banner_text = TextBrowser() - widget = QWidget() - Layout = QVBoxLayout(widget) - - Layout.addWidget(self.banner) - Layout.addWidget(self.banner_text) - Layout.setStretch(0, 2) - Layout.setStretch(1, 3) - v_layout = QVBoxLayout(self.banner) v_layout.setContentsMargins(0, 0, 0, 15) v_layout.setSpacing(5) @@ -146,14 +138,22 @@ class Home(QWidget): # 将底部水平布局添加到垂直布局 v_layout.addLayout(h2_layout) - layout = QVBoxLayout() + content_widget = QWidget() + content_layout = QVBoxLayout(content_widget) + content_layout.setContentsMargins(0, 0, 0, 0) + content_layout.addWidget(self.banner) + content_layout.addWidget(self.banner_text) + content_layout.setStretch(0, 2) + content_layout.setStretch(1, 3) + scrollArea = ScrollArea() scrollArea.setWidgetResizable(True) scrollArea.setContentsMargins(0, 0, 0, 0) scrollArea.setStyleSheet("background: transparent; border: none;") - scrollArea.setWidget(widget) + scrollArea.setWidget(content_widget) + + layout = QVBoxLayout(self) layout.addWidget(scrollArea) - self.setLayout(layout) self.set_banner() diff --git a/app/ui/main_window.py b/app/ui/main_window.py index 9509809..a7a72d0 100644 --- a/app/ui/main_window.py +++ b/app/ui/main_window.py @@ -49,6 +49,7 @@ from app.core import Config, TaskManager, MainTimer, MainInfoBar from app.services import Notify, Crypto, System from .home import Home from .member_manager import MemberManager +from .plan_manager import PlanManager from .queue_manager import QueueManager from .dispatch_center import DispatchCenter from .history import History @@ -80,6 +81,7 @@ class AUTO_MAA(MSFluentWindow): # 创建主窗口 self.home = Home(self) + self.plan_manager = PlanManager(self) self.member_manager = MemberManager(self) self.queue_manager = QueueManager(self) self.dispatch_center = DispatchCenter(self) @@ -100,6 +102,13 @@ class AUTO_MAA(MSFluentWindow): FluentIcon.ROBOT, NavigationItemPosition.TOP, ) + self.addSubInterface( + self.plan_manager, + FluentIcon.CALENDAR, + "计划管理", + FluentIcon.CALENDAR, + NavigationItemPosition.TOP, + ) self.addSubInterface( self.queue_manager, FluentIcon.BOOK_SHELF, @@ -128,23 +137,7 @@ class AUTO_MAA(MSFluentWindow): FluentIcon.SETTING, NavigationItemPosition.BOTTOM, ) - self.stackedWidget.currentChanged.connect( - lambda index: ( - self.queue_manager.reload_member_name() if index == 2 else None - ) - ) - self.stackedWidget.currentChanged.connect( - lambda index: ( - self.dispatch_center.pivot.setCurrentItem("主调度台") - if index == 3 - else None - ) - ) - self.stackedWidget.currentChanged.connect( - lambda index: ( - self.dispatch_center.update_top_bar() if index == 3 else None - ) - ) + self.stackedWidget.currentChanged.connect(self.__currentChanged) # 创建系统托盘及其菜单 self.tray = QSystemTrayIcon( @@ -241,6 +234,113 @@ class AUTO_MAA(MSFluentWindow): else: self.setStyleSheet("background-color: #ffffff;") + def set_min_method(self) -> None: + """设置最小化方法""" + + if Config.get(Config.ui_IfToTray): + + self.titleBar.minBtn.clicked.disconnect() + self.titleBar.minBtn.clicked.connect(lambda: self.show_ui("隐藏到托盘")) + + else: + + self.titleBar.minBtn.clicked.disconnect() + self.titleBar.minBtn.clicked.connect(self.window().showMinimized) + + def on_tray_activated(self, reason): + """双击返回主界面""" + if reason == QSystemTrayIcon.DoubleClick: + self.show_ui("显示主窗口") + + def show_ui( + self, mode: str, if_quick: bool = False, if_start: bool = False + ) -> None: + """配置窗口状态""" + + self.switch_theme() + + if mode == "显示主窗口": + + # 配置主窗口 + if not self.window().isVisible(): + size = list( + map( + int, + Config.get(Config.ui_size).split("x"), + ) + ) + location = list( + map( + int, + Config.get(Config.ui_location).split("x"), + ) + ) + if self.window().isMaximized(): + self.window().showNormal() + self.window().setGeometry(location[0], location[1], size[0], size[1]) + self.window().show() + if not if_quick: + if ( + Config.get(Config.ui_maximized) + and not self.window().isMaximized() + ): + self.titleBar.maxBtn.click() + self.show_ui("配置托盘") + elif if_start: + if Config.get(Config.ui_maximized) and not self.window().isMaximized(): + self.titleBar.maxBtn.click() + self.show_ui("配置托盘") + + # 如果窗口不在屏幕内,则重置窗口位置 + if not any( + self.window().geometry().intersects(screen.availableGeometry()) + for screen in QApplication.screens() + ): + self.window().showNormal() + self.window().setGeometry(100, 100, 1200, 700) + + self.window().raise_() + self.window().activateWindow() + + while Config.info_bar_list: + info_bar_item = Config.info_bar_list.pop(0) + MainInfoBar.push_info_bar( + info_bar_item["mode"], + info_bar_item["title"], + info_bar_item["content"], + info_bar_item["time"], + ) + + elif mode == "配置托盘": + + if Config.get(Config.ui_IfShowTray): + self.tray.show() + else: + self.tray.hide() + + elif mode == "隐藏到托盘": + + # 保存窗口相关属性 + if not self.window().isMaximized(): + + Config.set( + Config.ui_size, + f"{self.geometry().width()}x{self.geometry().height()}", + ) + Config.set( + Config.ui_location, + f"{self.geometry().x()}x{self.geometry().y()}", + ) + + Config.set(Config.ui_maximized, self.window().isMaximized()) + Config.save() + + # 隐藏主窗口 + if not if_quick: + + self.window().hide() + self.tray.show() + def start_up_task(self) -> None: """启动时任务""" @@ -278,24 +378,6 @@ class AUTO_MAA(MSFluentWindow): self.titleBar.minBtn.click() - def set_min_method(self) -> None: - """设置最小化方法""" - - if Config.get(Config.ui_IfToTray): - - self.titleBar.minBtn.clicked.disconnect() - self.titleBar.minBtn.clicked.connect(lambda: self.show_ui("隐藏到托盘")) - - else: - - self.titleBar.minBtn.clicked.disconnect() - self.titleBar.minBtn.clicked.connect(self.window().showMinimized) - - def on_tray_activated(self, reason): - """双击返回主界面""" - if reason == QSystemTrayIcon.DoubleClick: - self.show_ui("显示主窗口") - def clean_old_logs(self): """ 删除超过用户设定天数的日志文件(基于目录日期) @@ -351,75 +433,16 @@ class AUTO_MAA(MSFluentWindow): "warning", "启动主任务失败", "“调度队列_1”与“脚本_1”均不存在", -1 ) - def show_ui(self, mode: str, if_quick: bool = False) -> None: - """配置窗口状态""" + def __currentChanged(self, index: int) -> None: + """切换界面时任务""" - self.switch_theme() - - if mode == "显示主窗口": - - # 配置主窗口 - if not self.window().isVisible(): - size = list( - map( - int, - Config.get(Config.ui_size).split("x"), - ) - ) - location = list( - map( - int, - Config.get(Config.ui_location).split("x"), - ) - ) - if self.window().isMaximized(): - self.window().showNormal() - self.window().setGeometry(location[0], location[1], size[0], size[1]) - self.window().show() - if not if_quick: - if Config.get(Config.ui_maximized): - self.titleBar.maxBtn.click() - self.show_ui("配置托盘") - - if not any( - self.window().geometry().intersects(screen.availableGeometry()) - for screen in QApplication.screens() - ): - self.window().showNormal() - self.window().setGeometry(100, 100, 1200, 700) - - self.window().raise_() - self.window().activateWindow() - - elif mode == "配置托盘": - - if Config.get(Config.ui_IfShowTray): - self.tray.show() - else: - self.tray.hide() - - elif mode == "隐藏到托盘": - - # 保存窗口相关属性 - if not self.window().isMaximized(): - - Config.set( - Config.ui_size, - f"{self.geometry().width()}x{self.geometry().height()}", - ) - Config.set( - Config.ui_location, - f"{self.geometry().x()}x{self.geometry().y()}", - ) - - Config.set(Config.ui_maximized, self.window().isMaximized()) - Config.save() - - # 隐藏主窗口 - if not if_quick: - - self.window().hide() - self.tray.show() + if index == 1: + self.member_manager.reload_plan_name() + elif index == 3: + self.queue_manager.reload_member_name() + elif index == 4: + self.dispatch_center.pivot.setCurrentItem("主调度台") + self.dispatch_center.update_top_bar() def closeEvent(self, event: QCloseEvent): """清理残余进程""" diff --git a/app/ui/member_manager.py b/app/ui/member_manager.py index 1e7c407..82865af 100644 --- a/app/ui/member_manager.py +++ b/app/ui/member_manager.py @@ -47,6 +47,8 @@ from qfluentwidgets import ( PushSettingCard, TableWidget, PrimaryToolButton, + Flyout, + FlyoutAnimationType, ) from PySide6.QtCore import Signal from datetime import datetime @@ -64,13 +66,20 @@ from .Widget import ( LineEditSettingCard, SpinBoxSettingCard, ComboBoxMessageBox, - EditableComboBoxSettingCard, + SettingFlyoutView, + NoOptionComboBoxSettingCard, + ComboBoxWithPlanSettingCard, + EditableComboBoxWithPlanSettingCard, + SpinBoxWithPlanSettingCard, PasswordLineEditSettingCard, UserLableSettingCard, + UserTaskSettingCard, ComboBoxSettingCard, SwitchSettingCard, PushAndSwitchButtonSettingCard, PushAndComboBoxSettingCard, + StatusSwitchSetting, + UserNoticeSettingCard, PivotArea, ) @@ -178,7 +187,7 @@ class MemberManager(QWidget): name = self.member_manager.pivot.currentRouteKey() - if name == None: + if name is None: logger.warning("删除脚本实例时未选择脚本实例") MainInfoBar.push_info_bar( "warning", "未选择脚本实例", "请选择一个脚本实例", 5000 @@ -192,11 +201,7 @@ class MemberManager(QWidget): ) return None - choice = MessageBox( - "确认", - f"确定要删除 {name} 实例吗?", - self.window(), - ) + choice = MessageBox("确认", f"确定要删除 {name} 实例吗?", self.window()) if choice.exec(): self.member_manager.clear_SettingBox() @@ -222,7 +227,7 @@ class MemberManager(QWidget): name = self.member_manager.pivot.currentRouteKey() - if name == None: + if name is None: logger.warning("向左移动脚本实例时未选择脚本实例") MainInfoBar.push_info_bar( "warning", "未选择脚本实例", "请选择一个脚本实例", 5000 @@ -270,7 +275,7 @@ class MemberManager(QWidget): name = self.member_manager.pivot.currentRouteKey() - if name == None: + if name is None: logger.warning("向右移动脚本实例时未选择脚本实例") MainInfoBar.push_info_bar( "warning", "未选择脚本实例", "请选择一个脚本实例", 5000 @@ -471,11 +476,57 @@ class MemberManager(QWidget): self.key.setIcon(FluentIcon.HIDE) self.key.setChecked(False) + def reload_plan_name(self): + """刷新计划表名称""" + + plan_list = [ + ["固定"] + [_ for _ in Config.plan_dict.keys()], + ["固定"] + + [ + ( + k + if v["Config"].get(v["Config"].Info_Name) == "" + else f"{k} - {v["Config"].get(v["Config"].Info_Name)}" + ) + for k, v in Config.plan_dict.items() + ], + ] + for member in self.member_manager.script_list: + + if isinstance(member, MemberManager.MemberSettingBox.MaaSettingBox): + + for user_setting in member.user_setting.user_manager.script_list: + + user_setting.card_GameIdMode.comboBox.currentIndexChanged.disconnect( + user_setting.switch_gameid_mode + ) + user_setting.card_GameIdMode.reLoadOptions( + plan_list[0], plan_list[1] + ) + user_setting.card_GameIdMode.comboBox.currentIndexChanged.connect( + user_setting.switch_gameid_mode + ) + + self.refresh_plan_info() + def refresh_dashboard(self): """刷新所有脚本实例的用户仪表盘""" - for script in self.member_manager.script_list: - script.user_setting.user_manager.user_dashboard.load_info() + for member in self.member_manager.script_list: + + if isinstance(member, MemberManager.MemberSettingBox.MaaSettingBox): + member.user_setting.user_manager.user_dashboard.load_info() + + def refresh_plan_info(self): + """刷新所有计划信息""" + + for member in self.member_manager.script_list: + + if isinstance(member, MemberManager.MemberSettingBox.MaaSettingBox): + + member.user_setting.user_manager.user_dashboard.load_info() + for user_setting in member.user_setting.user_manager.script_list: + user_setting.switch_gameid_mode() class MemberSettingBox(QWidget): """脚本管理子页面组""" @@ -563,29 +614,25 @@ class MemberManager(QWidget): self.setObjectName(f"脚本_{uid}") self.config = Config.member_dict[f"脚本_{uid}"]["Config"] - layout = QVBoxLayout() + self.app_setting = self.AppSettingCard(f"脚本_{uid}", self.config, self) + self.user_setting = self.UserManager(f"脚本_{uid}", self) + + content_widget = QWidget() + content_layout = QVBoxLayout(content_widget) + content_layout.setContentsMargins(0, 0, 11, 0) + content_layout.addWidget(self.app_setting) + content_layout.addWidget(self.user_setting) + content_layout.addStretch(1) scrollArea = ScrollArea() scrollArea.setWidgetResizable(True) scrollArea.setContentsMargins(0, 0, 0, 0) scrollArea.setStyleSheet("background: transparent; border: none;") - - content_widget = QWidget() - content_layout = QVBoxLayout(content_widget) - - self.app_setting = self.AppSettingCard(f"脚本_{uid}", self.config, self) - self.user_setting = self.UserManager(f"脚本_{uid}", self) - - content_layout.addWidget(self.app_setting) - content_layout.addWidget(self.user_setting) - content_layout.addStretch(1) - scrollArea.setWidget(content_widget) + layout = QVBoxLayout(self) layout.addWidget(scrollArea) - self.setLayout(layout) - class AppSettingCard(HeaderCardWidget): def __init__(self, name: str, config: MaaConfig, parent=None): @@ -698,6 +745,15 @@ class MemberManager(QWidget): configItem=self.config.RunSet_ProxyTimesLimit, parent=self, ) + self.card_ADBSearchRange = SpinBoxSettingCard( + icon=FluentIcon.PAGE_RIGHT, + title="ADB端口号搜索范围", + content="在【±端口号范围】内搜索实际ADB端口号", + range=(0, 3), + qconfig=self.config, + configItem=self.config.RunSet_ADBSearchRange, + parent=self, + ) self.card_RunTimesLimit = SpinBoxSettingCard( icon=FluentIcon.PAGE_RIGHT, title="代理重试次数限制", @@ -746,6 +802,7 @@ class MemberManager(QWidget): Layout = QVBoxLayout(widget) Layout.addWidget(self.card_TaskTransitionMethod) Layout.addWidget(self.card_ProxyTimesLimit) + Layout.addWidget(self.card_ADBSearchRange) Layout.addWidget(self.card_RunTimesLimit) Layout.addWidget(self.card_AnnihilationTimeLimit) Layout.addWidget(self.card_RoutineTimeLimit) @@ -834,7 +891,7 @@ class MemberManager(QWidget): name = self.user_manager.pivot.currentRouteKey() - if name == None: + if name is None: logger.warning("未选择用户") MainInfoBar.push_info_bar( "warning", "未选择用户", "请先选择一个用户", 5000 @@ -855,9 +912,7 @@ class MemberManager(QWidget): return None choice = MessageBox( - "确认", - f"确定要删除 {name} 吗?", - self.window(), + "确认", f"确定要删除 {name} 吗?", self.window() ) if choice.exec(): @@ -895,7 +950,7 @@ class MemberManager(QWidget): name = self.user_manager.pivot.currentRouteKey() - if name == None: + if name is None: logger.warning("未选择用户") MainInfoBar.push_info_bar( "warning", "未选择用户", "请先选择一个用户", 5000 @@ -954,7 +1009,7 @@ class MemberManager(QWidget): name = self.user_manager.pivot.currentRouteKey() - if name == None: + if name is None: logger.warning("未选择用户") MainInfoBar.push_info_bar( "warning", "未选择用户", "请先选择一个用户", 5000 @@ -1190,6 +1245,8 @@ class MemberManager(QWidget): else "本周剿灭未完成" ) + gameid_info = config.get_plan_info() + button = PrimaryToolButton( FluentIcon.CHEVRON_RIGHT, self ) @@ -1220,14 +1277,14 @@ class MemberManager(QWidget): else "******" ), ) - self.dashboard.setItem( + self.dashboard.setCellWidget( int(name[3:]) - 1, 3, - QTableWidgetItem( - "启用" - if config.get(config.Info_Status) - and config.get(config.Info_RemainedDay) != 0 - else "禁用" + StatusSwitchSetting( + qconfig=config, + configItem_check=config.Info_Status, + configItem_enable=config.Info_RemainedDay, + parent=self, ), ) self.dashboard.setItem( @@ -1238,9 +1295,7 @@ class MemberManager(QWidget): self.dashboard.setItem( int(name[3:]) - 1, 5, - QTableWidgetItem( - str(config.get(config.Info_MedicineNumb)) - ), + QTableWidgetItem(str(gameid_info["MedicineNumb"])), ) self.dashboard.setItem( int(name[3:]) - 1, @@ -1248,12 +1303,12 @@ class MemberManager(QWidget): QTableWidgetItem( Config.gameid_dict["ALL"]["text"][ Config.gameid_dict["ALL"]["value"].index( - config.get(config.Info_GameId) + gameid_info["GameId"] ) ] - if config.get(config.Info_GameId) + if gameid_info["GameId"] in Config.gameid_dict["ALL"]["value"] - else config.get(config.Info_GameId) + else gameid_info["GameId"] ), ) self.dashboard.setItem( @@ -1262,12 +1317,12 @@ class MemberManager(QWidget): QTableWidgetItem( Config.gameid_dict["ALL"]["text"][ Config.gameid_dict["ALL"]["value"].index( - config.get(config.Info_GameId_1) + gameid_info["GameId_1"] ) ] - if config.get(config.Info_GameId_1) + if gameid_info["GameId_1"] in Config.gameid_dict["ALL"]["value"] - else config.get(config.Info_GameId_1) + else gameid_info["GameId_1"] ), ) self.dashboard.setItem( @@ -1276,12 +1331,12 @@ class MemberManager(QWidget): QTableWidgetItem( Config.gameid_dict["ALL"]["text"][ Config.gameid_dict["ALL"]["value"].index( - config.get(config.Info_GameId_2) + gameid_info["GameId_2"] ) ] - if config.get(config.Info_GameId_2) + if gameid_info["GameId_2"] in Config.gameid_dict["ALL"]["value"] - else config.get(config.Info_GameId_2) + else gameid_info["GameId_2"] ), ) self.dashboard.setItem( @@ -1289,22 +1344,20 @@ class MemberManager(QWidget): 9, QTableWidgetItem( "不使用" - if config.get(config.Info_GameId_Remain) == "-" + if gameid_info["GameId_Remain"] == "-" else ( ( Config.gameid_dict["ALL"]["text"][ Config.gameid_dict["ALL"][ "value" ].index( - config.get( - config.Info_GameId_Remain - ) + gameid_info["GameId_Remain"] ) ] ) - if config.get(config.Info_GameId_Remain) + if gameid_info["GameId_Remain"] in Config.gameid_dict["ALL"]["value"] - else config.get(config.Info_GameId_Remain) + else gameid_info["GameId_Remain"] ) ), ) @@ -1328,6 +1381,19 @@ class MemberManager(QWidget): f"用户_{uid}" ]["Path"] + plan_list = [ + ["固定"] + [_ for _ in Config.plan_dict.keys()], + ["固定"] + + [ + ( + k + if v["Config"].get(v["Config"].Info_Name) == "" + else f"{k} - {v["Config"].get(v["Config"].Info_Name)}" + ) + for k, v in Config.plan_dict.items() + ], + ] + self.card_Name = LineEditSettingCard( icon=FluentIcon.PEOPLE, title="用户名", @@ -1355,15 +1421,17 @@ class MemberManager(QWidget): configItem=self.config.Info_Mode, parent=self, ) - self.card_GameIdMode = ComboBoxSettingCard( + self.card_GameIdMode = NoOptionComboBoxSettingCard( icon=FluentIcon.DICTIONARY, title="关卡配置模式", content="刷理智关卡号的配置模式", - texts=["固定"], + value=plan_list[0], + texts=plan_list[1], qconfig=self.config, configItem=self.config.Info_GameIdMode, parent=self, ) + self.card_GameIdMode.comboBox.setMinimumWidth(0) self.card_Server = ComboBoxSettingCard( icon=FluentIcon.PROJECTOR, title="服务器", @@ -1441,7 +1509,7 @@ class MemberManager(QWidget): configItem=self.config.Info_Notes, parent=self, ) - self.card_MedicineNumb = SpinBoxSettingCard( + self.card_MedicineNumb = SpinBoxWithPlanSettingCard( icon=FluentIcon.GAME, title="吃理智药", content="吃理智药次数,输入0以关闭", @@ -1450,7 +1518,7 @@ class MemberManager(QWidget): configItem=self.config.Info_MedicineNumb, parent=self, ) - self.card_SeriesNumb = ComboBoxSettingCard( + self.card_SeriesNumb = ComboBoxWithPlanSettingCard( icon=FluentIcon.GAME, title="连战次数", content="连战次数较大时建议搭配剩余理智关卡使用", @@ -1460,7 +1528,7 @@ class MemberManager(QWidget): parent=self, ) self.card_SeriesNumb.comboBox.setMinimumWidth(150) - self.card_GameId = EditableComboBoxSettingCard( + self.card_GameId = EditableComboBoxWithPlanSettingCard( icon=FluentIcon.GAME, title="关卡选择", content="按下回车以添加自定义关卡号", @@ -1470,7 +1538,7 @@ class MemberManager(QWidget): configItem=self.config.Info_GameId, parent=self, ) - self.card_GameId_1 = EditableComboBoxSettingCard( + self.card_GameId_1 = EditableComboBoxWithPlanSettingCard( icon=FluentIcon.GAME, title="备选关卡 - 1", content="按下回车以添加自定义关卡号", @@ -1480,7 +1548,7 @@ class MemberManager(QWidget): configItem=self.config.Info_GameId_1, parent=self, ) - self.card_GameId_2 = EditableComboBoxSettingCard( + self.card_GameId_2 = EditableComboBoxWithPlanSettingCard( icon=FluentIcon.GAME, title="备选关卡 - 2", content="按下回车以添加自定义关卡号", @@ -1490,18 +1558,20 @@ class MemberManager(QWidget): configItem=self.config.Info_GameId_2, parent=self, ) - self.card_GameId_Remain = EditableComboBoxSettingCard( - icon=FluentIcon.GAME, - title="剩余理智关卡", - content="按下回车以添加自定义关卡号", - value=Config.gameid_dict["ALL"]["value"], - texts=[ - "不使用" if _ == "当前/上次" else _ - for _ in Config.gameid_dict["ALL"]["text"] - ], - qconfig=self.config, - configItem=self.config.Info_GameId_Remain, - parent=self, + self.card_GameId_Remain = ( + EditableComboBoxWithPlanSettingCard( + icon=FluentIcon.GAME, + title="剩余理智关卡", + content="按下回车以添加自定义关卡号", + value=Config.gameid_dict["ALL"]["value"], + texts=[ + "不使用" if _ == "当前/上次" else _ + for _ in Config.gameid_dict["ALL"]["text"] + ], + qconfig=self.config, + configItem=self.config.Info_GameId_Remain, + parent=self, + ) ) self.card_UserLable = UserLableSettingCard( @@ -1518,6 +1588,147 @@ class MemberManager(QWidget): parent=self, ) + # 单独任务卡片 + self.card_TaskSet = UserTaskSettingCard( + icon=FluentIcon.LIBRARY, + title="自动日常代理任务序列", + content="未启用任何任务项", + text="设置", + qconfig=self.config, + configItems={ + "IfWakeUp": self.config.Task_IfWakeUp, + "IfRecruiting": self.config.Task_IfRecruiting, + "IfBase": self.config.Task_IfBase, + "IfCombat": self.config.Task_IfCombat, + "IfMall": self.config.Task_IfMall, + "IfMission": self.config.Task_IfMission, + "IfAutoRoguelike": self.config.Task_IfAutoRoguelike, + "IfReclamation": self.config.Task_IfReclamation, + }, + parent=self, + ) + self.card_IfWakeUp = SwitchSettingCard( + icon=FluentIcon.TILES, + title="开始唤醒", + content="", + qconfig=self.config, + configItem=self.config.Task_IfWakeUp, + parent=self, + ) + self.card_IfRecruiting = SwitchSettingCard( + icon=FluentIcon.TILES, + title="自动公招", + content="", + qconfig=self.config, + configItem=self.config.Task_IfRecruiting, + parent=self, + ) + self.card_IfBase = SwitchSettingCard( + icon=FluentIcon.TILES, + title="基建换班", + content="", + qconfig=self.config, + configItem=self.config.Task_IfBase, + parent=self, + ) + self.card_IfCombat = SwitchSettingCard( + icon=FluentIcon.TILES, + title="刷理智", + content="", + qconfig=self.config, + configItem=self.config.Task_IfCombat, + parent=self, + ) + self.card_IfMall = SwitchSettingCard( + icon=FluentIcon.TILES, + title="获取信用及购物", + content="", + qconfig=self.config, + configItem=self.config.Task_IfMall, + parent=self, + ) + self.card_IfMission = SwitchSettingCard( + icon=FluentIcon.TILES, + title="领取奖励", + content="", + qconfig=self.config, + configItem=self.config.Task_IfMission, + parent=self, + ) + self.card_IfAutoRoguelike = SwitchSettingCard( + icon=FluentIcon.TILES, + title="自动肉鸽", + content="", + qconfig=self.config, + configItem=self.config.Task_IfAutoRoguelike, + parent=self, + ) + self.card_IfReclamation = SwitchSettingCard( + icon=FluentIcon.TILES, + title="生息演算", + content="", + qconfig=self.config, + configItem=self.config.Task_IfReclamation, + parent=self, + ) + + self.TaskSetCard = SettingFlyoutView( + self, + "自动日常代理任务序列设置", + [ + self.card_IfWakeUp, + self.card_IfRecruiting, + self.card_IfBase, + self.card_IfCombat, + self.card_IfMall, + self.card_IfMission, + self.card_IfAutoRoguelike, + self.card_IfReclamation, + ], + ) + + # 单独通知卡片 + self.card_NotifySet = UserNoticeSettingCard( + icon=FluentIcon.MAIL, + title="用户单独通知设置", + content="未启用任何通知项", + text="设置", + qconfig=self.config, + configItem=self.config.Notify_Enabled, + configItems={ + "IfSendStatistic": self.config.Notify_IfSendStatistic, + "IfSendSixStar": self.config.Notify_IfSendSixStar, + "IfSendMail": self.config.Notify_IfSendMail, + "ToAddress": self.config.Notify_ToAddress, + "IfServerChan": self.config.Notify_IfServerChan, + "ServerChanKey": self.config.Notify_ServerChanKey, + "IfCompanyWebHookBot": self.config.Notify_IfCompanyWebHookBot, + "CompanyWebHookBotUrl": self.config.Notify_CompanyWebHookBotUrl, + }, + parent=self, + ) + self.card_NotifyContent = self.NotifyContentSettingCard( + self.config, self + ) + self.card_EMail = self.EMailSettingCard(self.config, self) + self.card_ServerChan = self.ServerChanSettingCard( + self.config, self + ) + self.card_CompanyWebhookBot = ( + self.CompanyWechatPushSettingCard(self.config, self) + ) + + self.NotifySetCard = SettingFlyoutView( + self, + "用户通知设置", + [ + self.card_NotifyContent, + self.card_EMail, + self.card_ServerChan, + self.card_CompanyWebhookBot, + ], + ) + h1_layout = QHBoxLayout() h1_layout.addWidget(self.card_Name) h1_layout.addWidget(self.card_Id) @@ -1555,6 +1766,8 @@ class MemberManager(QWidget): Layout.addLayout(h6_layout) Layout.addLayout(h7_layout) Layout.addLayout(h8_layout) + Layout.addWidget(self.card_TaskSet) + Layout.addWidget(self.card_NotifySet) self.viewLayout.addLayout(Layout) self.viewLayout.setContentsMargins(3, 0, 3, 3) @@ -1574,10 +1787,16 @@ class MemberManager(QWidget): self.card_InfrastMode.clicked.connect( self.set_infrastructure ) + self.card_TaskSet.clicked.connect(self.set_task) + self.card_NotifySet.clicked.connect(self.set_notify) + self.card_GameIdMode.comboBox.currentIndexChanged.connect( + self.switch_gameid_mode + ) Config.gameid_refreshed.connect(self.refresh_gameid) Config.PASSWORD_refreshed.connect(self.refresh_password) self.switch_mode() + self.switch_gameid_mode() self.switch_infrastructure() def switch_mode(self) -> None: @@ -1596,6 +1815,40 @@ class MemberManager(QWidget): self.card_Annihilation.button.setVisible(True) self.card_Routine.setVisible(True) + def switch_gameid_mode(self) -> None: + + for card, name in zip( + [ + self.card_MedicineNumb, + self.card_SeriesNumb, + self.card_GameId, + self.card_GameId_1, + self.card_GameId_2, + self.card_GameId_Remain, + ], + [ + "MedicineNumb", + "SeriesNumb", + "GameId", + "GameId_1", + "GameId_2", + "GameId_Remain", + ], + ): + + card.switch_mode( + self.config.get(self.config.Info_GameIdMode)[:2] + ) + if ( + self.config.get(self.config.Info_GameIdMode) + != "固定" + ): + card.change_plan( + Config.plan_dict[ + self.config.get(self.config.Info_GameIdMode) + ]["Config"].get_current_info(name) + ) + def switch_infrastructure(self) -> None: if ( @@ -1698,3 +1951,178 @@ class MemberManager(QWidget): } }, ) + + def set_task(self) -> None: + """设置用户任务序列相关配置""" + + self.TaskSetCard.setVisible(True) + Flyout.make( + self.TaskSetCard, + self.card_TaskSet, + self, + aniType=FlyoutAnimationType.PULL_UP, + isDeleteOnClose=False, + ) + + def set_notify(self) -> None: + """设置用户通知相关配置""" + + self.NotifySetCard.setVisible(True) + Flyout.make( + self.NotifySetCard, + self.card_NotifySet, + self, + aniType=FlyoutAnimationType.PULL_UP, + isDeleteOnClose=False, + ) + + class NotifyContentSettingCard(HeaderCardWidget): + + def __init__(self, config: MaaUserConfig, parent=None): + super().__init__(parent) + self.setTitle("用户通知内容选项") + + self.config = config + + self.card_IfSendStatistic = SwitchSettingCard( + icon=FluentIcon.PAGE_RIGHT, + title="推送统计信息", + content="推送自动代理统计信息的通知", + qconfig=self.config, + configItem=self.config.Notify_IfSendStatistic, + parent=self, + ) + self.card_IfSendSixStar = SwitchSettingCard( + icon=FluentIcon.PAGE_RIGHT, + title="推送公招高资喜报", + content="公招出现六星词条时推送喜报", + qconfig=self.config, + configItem=self.config.Notify_IfSendSixStar, + parent=self, + ) + + Layout = QVBoxLayout() + Layout.addWidget(self.card_IfSendStatistic) + Layout.addWidget(self.card_IfSendSixStar) + self.viewLayout.addLayout(Layout) + self.viewLayout.setSpacing(3) + self.viewLayout.setContentsMargins(3, 0, 3, 3) + + class EMailSettingCard(HeaderCardWidget): + + def __init__(self, config: MaaUserConfig, parent=None): + super().__init__(parent) + self.setTitle("用户邮箱通知") + + self.config = config + + self.card_IfSendMail = SwitchSettingCard( + icon=FluentIcon.PAGE_RIGHT, + title="推送用户邮件通知", + content="是否启用用户邮件通知功能", + qconfig=self.config, + configItem=self.config.Notify_IfSendMail, + parent=self, + ) + self.card_ToAddress = LineEditSettingCard( + icon=FluentIcon.PAGE_RIGHT, + title="用户收信邮箱地址", + content="接收用户通知的邮箱地址", + text="请输入用户收信邮箱地址", + qconfig=self.config, + configItem=self.config.Notify_ToAddress, + parent=self, + ) + + Layout = QVBoxLayout() + Layout.addWidget(self.card_IfSendMail) + Layout.addWidget(self.card_ToAddress) + self.viewLayout.addLayout(Layout) + self.viewLayout.setSpacing(3) + self.viewLayout.setContentsMargins(3, 0, 3, 3) + + class ServerChanSettingCard(HeaderCardWidget): + + def __init__(self, config: MaaUserConfig, parent=None): + super().__init__(parent) + self.setTitle("用户ServerChan通知") + + self.config = config + + self.card_IfServerChan = SwitchSettingCard( + icon=FluentIcon.PAGE_RIGHT, + title="推送用户Server酱通知", + content="是否启用用户Server酱通知功能", + qconfig=self.config, + configItem=self.config.Notify_IfServerChan, + parent=self, + ) + self.card_ServerChanKey = LineEditSettingCard( + icon=FluentIcon.PAGE_RIGHT, + title="用户SendKey", + content="SC3与SCT均须填写", + text="请输入用户SendKey", + qconfig=self.config, + configItem=self.config.Notify_ServerChanKey, + parent=self, + ) + self.card_ServerChanChannel = LineEditSettingCard( + icon=FluentIcon.PAGE_RIGHT, + title="用户ServerChanChannel代码", + content="留空则默认,多个请使用“|”隔开", + text="请输入Channel代码,仅SCT生效", + qconfig=self.config, + configItem=self.config.Notify_ServerChanChannel, + parent=self, + ) + self.card_ServerChanTag = LineEditSettingCard( + icon=FluentIcon.PAGE_RIGHT, + title="用户Tag内容", + content="留空则默认,多个请使用“|”隔开", + text="请输入加入推送的Tag,仅SC3生效", + qconfig=self.config, + configItem=self.config.Notify_ServerChanTag, + parent=self, + ) + + Layout = QVBoxLayout() + Layout.addWidget(self.card_IfServerChan) + Layout.addWidget(self.card_ServerChanKey) + Layout.addWidget(self.card_ServerChanChannel) + Layout.addWidget(self.card_ServerChanTag) + self.viewLayout.addLayout(Layout) + self.viewLayout.setSpacing(3) + self.viewLayout.setContentsMargins(3, 0, 3, 3) + + class CompanyWechatPushSettingCard(HeaderCardWidget): + + def __init__(self, config: MaaUserConfig, parent=None): + super().__init__(parent) + self.setTitle("用户企业微信推送") + + self.config = config + + self.card_IfCompanyWebHookBot = SwitchSettingCard( + icon=FluentIcon.PAGE_RIGHT, + title="推送用户企业微信机器人通知", + content="是否启用用户企微机器人通知功能", + qconfig=self.config, + configItem=self.config.Notify_IfCompanyWebHookBot, + parent=self, + ) + self.card_CompanyWebHookBotUrl = LineEditSettingCard( + icon=FluentIcon.PAGE_RIGHT, + title="WebhookUrl", + content="用户企微群机器人Webhook地址", + text="请输入用户Webhook的Url", + qconfig=self.config, + configItem=self.config.Notify_CompanyWebHookBotUrl, + parent=self, + ) + + Layout = QVBoxLayout() + Layout.addWidget(self.card_IfCompanyWebHookBot) + Layout.addWidget(self.card_CompanyWebHookBotUrl) + self.viewLayout.addLayout(Layout) + self.viewLayout.setSpacing(3) + self.viewLayout.setContentsMargins(3, 0, 3, 3) diff --git a/app/ui/plan_manager.py b/app/ui/plan_manager.py new file mode 100644 index 0000000..f96cad3 --- /dev/null +++ b/app/ui/plan_manager.py @@ -0,0 +1,495 @@ +# AUTO_MAA:A MAA Multi Account Management and Automation Tool +# Copyright © 2024-2025 DLmaster361 + +# This file is part of AUTO_MAA. + +# AUTO_MAA is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published +# by the Free Software Foundation, either version 3 of the License, +# or (at your option) any later version. + +# AUTO_MAA is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty +# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See +# the GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with AUTO_MAA. If not, see . + +# Contact: DLmaster_361@163.com + +""" +AUTO_MAA +AUTO_MAA计划管理界面 +v4.3 +作者:DLmaster_361 +""" + +from loguru import logger +from PySide6.QtWidgets import ( + QWidget, + QVBoxLayout, + QStackedWidget, + QHeaderView, +) +from qfluentwidgets import ( + Action, + FluentIcon, + MessageBox, + HeaderCardWidget, + CommandBar, + TableWidget, +) +from typing import List, Dict, Union +import shutil + +from app.core import Config, MainInfoBar, MaaPlanConfig +from .Widget import ( + ComboBoxMessageBox, + LineEditSettingCard, + ComboBoxSettingCard, + SpinBoxSetting, + EditableComboBoxSetting, + ComboBoxSetting, + PivotArea, +) + + +class PlanManager(QWidget): + """计划管理父界面""" + + def __init__(self, parent=None): + super().__init__(parent) + + self.setObjectName("计划管理") + + layout = QVBoxLayout(self) + + self.tools = CommandBar() + + self.plan_manager = self.PlanSettingBox(self) + + # 逐个添加动作 + self.tools.addActions( + [ + Action(FluentIcon.ADD_TO, "新建计划表", triggered=self.add_setting_box), + Action( + FluentIcon.REMOVE_FROM, "删除计划表", triggered=self.del_setting_box + ), + ] + ) + self.tools.addSeparator() + self.tools.addActions( + [ + Action( + FluentIcon.LEFT_ARROW, "向左移动", triggered=self.left_setting_box + ), + Action( + FluentIcon.RIGHT_ARROW, "向右移动", triggered=self.right_setting_box + ), + ] + ) + self.tools.addSeparator() + + layout.addWidget(self.tools) + layout.addWidget(self.plan_manager) + + def add_setting_box(self): + """添加一个计划表""" + + choice = ComboBoxMessageBox( + self.window(), + "选择一个计划类型以添加相应计划表", + ["选择计划类型"], + [["MAA"]], + ) + if choice.exec() and choice.input[0].currentIndex() != -1: + + if choice.input[0].currentText() == "MAA": + + index = len(Config.plan_dict) + 1 + + maa_plan_config = MaaPlanConfig() + maa_plan_config.load( + Config.app_path / f"config/MaaPlanConfig/计划_{index}/config.json", + maa_plan_config, + ) + maa_plan_config.save() + + Config.plan_dict[f"计划_{index}"] = { + "Type": "Maa", + "Path": Config.app_path / f"config/MaaPlanConfig/计划_{index}", + "Config": maa_plan_config, + } + + self.plan_manager.add_MaaPlanSettingBox(index) + self.plan_manager.switch_SettingBox(index) + + logger.success(f"计划管理 计划_{index} 添加成功") + MainInfoBar.push_info_bar( + "success", "操作成功", f"添加计划表 计划_{index}", 3000 + ) + + def del_setting_box(self): + """删除一个计划表""" + + name = self.plan_manager.pivot.currentRouteKey() + + if name is None: + logger.warning("删除计划表时未选择计划表") + MainInfoBar.push_info_bar( + "warning", "未选择计划表", "请选择一个计划表", 5000 + ) + return None + + if len(Config.running_list) > 0: + logger.warning("删除计划表时调度队列未停止运行") + MainInfoBar.push_info_bar( + "warning", "调度中心正在执行任务", "请等待或手动中止任务", 5000 + ) + return None + + choice = MessageBox("确认", f"确定要删除 {name} 吗?", self.window()) + if choice.exec(): + + self.plan_manager.clear_SettingBox() + + shutil.rmtree(Config.plan_dict[name]["Path"]) + Config.change_plan(name, "禁用") + for i in range(int(name[3:]) + 1, len(Config.plan_dict) + 1): + if Config.plan_dict[f"计划_{i}"]["Path"].exists(): + Config.plan_dict[f"计划_{i}"]["Path"].rename( + Config.plan_dict[f"计划_{i}"]["Path"].with_name(f"计划_{i-1}") + ) + Config.change_plan(f"计划_{i}", f"计划_{i-1}") + + self.plan_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): + """向左移动计划表""" + + name = self.plan_manager.pivot.currentRouteKey() + + if name is None: + logger.warning("向左移动计划表时未选择计划表") + MainInfoBar.push_info_bar( + "warning", "未选择计划表", "请选择一个计划表", 5000 + ) + return None + + index = int(name[3:]) + + if index == 1: + logger.warning("向左移动计划表时已到达最左端") + MainInfoBar.push_info_bar( + "warning", "已经是第一个计划表", "无法向左移动", 5000 + ) + return None + + if len(Config.running_list) > 0: + logger.warning("向左移动计划表时调度队列未停止运行") + MainInfoBar.push_info_bar( + "warning", "调度中心正在执行任务", "请等待或手动中止任务", 5000 + ) + return None + + self.plan_manager.clear_SettingBox() + + Config.plan_dict[name]["Path"].rename( + Config.plan_dict[name]["Path"].with_name("计划_0") + ) + Config.change_plan(name, "计划_0") + Config.plan_dict[f"计划_{index-1}"]["Path"].rename( + Config.plan_dict[name]["Path"] + ) + Config.change_plan(f"计划_{index-1}", name) + Config.plan_dict[name]["Path"].with_name("计划_0").rename( + Config.plan_dict[f"计划_{index-1}"]["Path"] + ) + Config.change_plan("计划_0", f"计划_{index-1}") + + self.plan_manager.show_SettingBox(index - 1) + + logger.success(f"计划表 {name} 左移成功") + MainInfoBar.push_info_bar("success", "操作成功", f"左移计划表 {name}", 3000) + + def right_setting_box(self): + """向右移动计划表""" + + name = self.plan_manager.pivot.currentRouteKey() + + if name is None: + logger.warning("向右移动计划表时未选择计划表") + MainInfoBar.push_info_bar( + "warning", "未选择计划表", "请选择一个计划表", 5000 + ) + return None + + index = int(name[3:]) + + if index == len(Config.plan_dict): + logger.warning("向右移动计划表时已到达最右端") + MainInfoBar.push_info_bar( + "warning", "已经是最后一个计划表", "无法向右移动", 5000 + ) + return None + + if len(Config.running_list) > 0: + logger.warning("向右移动计划表时调度队列未停止运行") + MainInfoBar.push_info_bar( + "warning", "调度中心正在执行任务", "请等待或手动中止任务", 5000 + ) + return None + + self.plan_manager.clear_SettingBox() + + Config.plan_dict[name]["Path"].rename( + Config.plan_dict[name]["Path"].with_name("计划_0") + ) + Config.change_plan(name, "计划_0") + Config.plan_dict[f"计划_{index+1}"]["Path"].rename( + Config.plan_dict[name]["Path"] + ) + Config.change_plan(f"计划_{index+1}", name) + Config.plan_dict[name]["Path"].with_name("计划_0").rename( + Config.plan_dict[f"计划_{index+1}"]["Path"] + ) + Config.change_plan("计划_0", f"计划_{index+1}") + + self.plan_manager.show_SettingBox(index + 1) + + logger.success(f"计划表 {name} 右移成功") + MainInfoBar.push_info_bar("success", "操作成功", f"右移计划表 {name}", 3000) + + class PlanSettingBox(QWidget): + """计划管理子页面组""" + + def __init__(self, parent=None): + super().__init__(parent) + + self.setObjectName("计划管理页面组") + + self.pivotArea = PivotArea(self) + self.pivot = self.pivotArea.pivot + + self.stackedWidget = QStackedWidget(self) + self.stackedWidget.setContentsMargins(0, 0, 0, 0) + self.stackedWidget.setStyleSheet("background: transparent; border: none;") + + self.script_list: List[PlanManager.PlanSettingBox.MaaPlanSettingBox] = [] + + self.Layout = QVBoxLayout(self) + self.Layout.addWidget(self.pivotArea) + self.Layout.addWidget(self.stackedWidget) + self.Layout.setContentsMargins(0, 0, 0, 0) + + self.pivot.currentItemChanged.connect( + lambda index: self.switch_SettingBox( + int(index[3:]), if_chang_pivot=False + ) + ) + + self.show_SettingBox(1) + + def show_SettingBox(self, index) -> None: + """加载所有子界面""" + + Config.search_plan() + + for name, info in Config.plan_dict.items(): + if info["Type"] == "Maa": + self.add_MaaPlanSettingBox(int(name[3:])) + + self.switch_SettingBox(index) + + def switch_SettingBox(self, index: int, if_chang_pivot: bool = True) -> None: + """切换到指定的子界面""" + + if len(Config.plan_dict) == 0: + return None + + if index > len(Config.plan_dict): + return None + + 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: + Config.gameid_refreshed.disconnect(sub_interface.refresh_gameid) + self.stackedWidget.removeWidget(sub_interface) + sub_interface.deleteLater() + self.script_list.clear() + self.pivot.clear() + + def add_MaaPlanSettingBox(self, uid: int) -> None: + """添加一个MAA设置界面""" + + maa_plan_setting_box = self.MaaPlanSettingBox(uid, self) + + self.script_list.append(maa_plan_setting_box) + + self.stackedWidget.addWidget(self.script_list[-1]) + + self.pivot.addItem(routeKey=f"计划_{uid}", text=f"计划 {uid}") + + class MaaPlanSettingBox(HeaderCardWidget): + """MAA类计划设置界面""" + + def __init__(self, uid: int, parent=None): + super().__init__(parent) + + self.setObjectName(f"计划_{uid}") + self.setTitle("MAA计划表") + self.config = Config.plan_dict[f"计划_{uid}"]["Config"] + + self.card_Name = LineEditSettingCard( + icon=FluentIcon.EDIT, + title="计划表名称", + content="用于标识计划表的名称", + text="请输入计划表名称", + qconfig=self.config, + configItem=self.config.Info_Name, + parent=self, + ) + self.card_Mode = ComboBoxSettingCard( + icon=FluentIcon.DICTIONARY, + title="计划模式", + content="全局模式下计划内容固定,周计划模式下计划按周一到周日切换", + texts=["全局", "周计划"], + qconfig=self.config, + configItem=self.config.Info_Mode, + parent=self, + ) + + self.table = TableWidget(self) + self.table.setColumnCount(8) + self.table.setRowCount(6) + self.table.setHorizontalHeaderLabels( + ["全局", "周一", "周二", "周三", "周四", "周五", "周六", "周日"] + ) + self.table.setVerticalHeaderLabels( + [ + "吃理智药", + "连战次数", + "关卡选择", + "备选 - 1", + "备选 - 2", + "剩余理智", + ] + ) + self.table.setAlternatingRowColors(False) + self.table.setEditTriggers(TableWidget.NoEditTriggers) + for col in range(8): + self.table.horizontalHeader().setSectionResizeMode( + col, QHeaderView.ResizeMode.Stretch + ) + for row in range(6): + self.table.verticalHeader().setSectionResizeMode( + row, QHeaderView.ResizeMode.ResizeToContents + ) + + self.item_dict: Dict[ + str, + Dict[ + str, + Union[SpinBoxSetting, ComboBoxSetting, EditableComboBoxSetting], + ], + ] = {} + + for col, (group, name_dict) in enumerate( + self.config.config_item_dict.items() + ): + + self.item_dict[group] = {} + + for row, (name, configItem) in enumerate(name_dict.items()): + + if name == "MedicineNumb": + self.item_dict[group][name] = SpinBoxSetting( + range=(0, 1024), + qconfig=self.config, + configItem=configItem, + parent=self, + ) + elif name == "SeriesNumb": + self.item_dict[group][name] = ComboBoxSetting( + texts=["AUTO", "6", "5", "4", "3", "2", "1", "不选择"], + qconfig=self.config, + configItem=configItem, + parent=self, + ) + elif name == "GameId_Remain": + self.item_dict[group][name] = EditableComboBoxSetting( + value=Config.gameid_dict[group]["value"], + texts=[ + "不使用" if _ == "当前/上次" else _ + for _ in Config.gameid_dict[group]["text"] + ], + qconfig=self.config, + configItem=configItem, + parent=self, + ) + elif "GameId" in name: + self.item_dict[group][name] = EditableComboBoxSetting( + value=Config.gameid_dict[group]["value"], + texts=Config.gameid_dict[group]["text"], + qconfig=self.config, + configItem=configItem, + parent=self, + ) + + self.table.setCellWidget(row, col, self.item_dict[group][name]) + + Layout = QVBoxLayout() + Layout.addWidget(self.card_Name) + Layout.addWidget(self.card_Mode) + Layout.addWidget(self.table) + + self.viewLayout.addLayout(Layout) + self.viewLayout.setSpacing(3) + self.viewLayout.setContentsMargins(3, 0, 3, 3) + + self.card_Mode.comboBox.currentIndexChanged.connect(self.switch_mode) + Config.gameid_refreshed.connect(self.refresh_gameid) + + self.switch_mode() + + def switch_mode(self) -> None: + """切换计划模式""" + + for group, name_dict in self.item_dict.items(): + for name, setting_item in name_dict.items(): + setting_item.setEnabled( + (group == "ALL") + == (self.config.get(self.config.Info_Mode) == "ALL") + ) + + def refresh_gameid(self): + + for group, name_dict in self.item_dict.items(): + + for name, setting_item in name_dict.items(): + + if name == "GameId_Remain": + + setting_item.reLoadOptions( + Config.gameid_dict[group]["value"], + [ + "不使用" if _ == "当前/上次" else _ + for _ in Config.gameid_dict[group]["text"] + ], + ) + + elif "GameId" in name: + + setting_item.reLoadOptions( + Config.gameid_dict[group]["value"], + Config.gameid_dict[group]["text"], + ) diff --git a/app/ui/queue_manager.py b/app/ui/queue_manager.py index c450071..ee013b6 100644 --- a/app/ui/queue_manager.py +++ b/app/ui/queue_manager.py @@ -122,7 +122,7 @@ class QueueManager(QWidget): name = self.queue_manager.pivot.currentRouteKey() - if name == None: + if name is None: logger.warning("未选择调度队列") MainInfoBar.push_info_bar( "warning", "未选择调度队列", "请先选择一个调度队列", 5000 @@ -136,11 +136,7 @@ class QueueManager(QWidget): ) return None - choice = MessageBox( - "确认", - f"确定要删除 {name} 吗?", - self.window(), - ) + choice = MessageBox("确认", f"确定要删除 {name} 吗?", self.window()) if choice.exec(): self.queue_manager.clear_SettingBox() @@ -164,7 +160,7 @@ class QueueManager(QWidget): name = self.queue_manager.pivot.currentRouteKey() - if name == None: + if name is None: logger.warning("未选择调度队列") MainInfoBar.push_info_bar( "warning", "未选择调度队列", "请先选择一个调度队列", 5000 @@ -209,7 +205,7 @@ class QueueManager(QWidget): name = self.queue_manager.pivot.currentRouteKey() - if name == None: + if name is None: logger.warning("未选择调度队列") MainInfoBar.push_info_bar( "warning", "未选择调度队列", "请先选择一个调度队列", 5000 @@ -379,16 +375,6 @@ class QueueManager(QWidget): self.setObjectName(f"调度队列_{uid}") self.config = Config.queue_dict[f"调度队列_{uid}"]["Config"] - layout = QVBoxLayout() - - scrollArea = ScrollArea() - scrollArea.setWidgetResizable(True) - scrollArea.setContentsMargins(0, 0, 0, 0) - scrollArea.setStyleSheet("background: transparent; border: none;") - - 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) @@ -398,18 +384,24 @@ class QueueManager(QWidget): parent=self, ) + content_widget = QWidget() + content_layout = QVBoxLayout(content_widget) + content_layout.setContentsMargins(0, 0, 11, 0) 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 = ScrollArea() + scrollArea.setWidgetResizable(True) + scrollArea.setContentsMargins(0, 0, 0, 0) + scrollArea.setStyleSheet("background: transparent; border: none;") scrollArea.setWidget(content_widget) + layout = QVBoxLayout(self) layout.addWidget(scrollArea) - self.setLayout(layout) - class QueueSetSettingCard(HeaderCardWidget): def __init__(self, config: QueueConfig, parent=None): diff --git a/app/ui/setting.py b/app/ui/setting.py index 3dbcf7c..84fda41 100644 --- a/app/ui/setting.py +++ b/app/ui/setting.py @@ -70,9 +70,6 @@ class Setting(QWidget): super().__init__(parent) self.setObjectName("设置") - content_widget = QWidget() - content_layout = QVBoxLayout(content_widget) - self.function = FunctionSettingCard(self) self.start = StartSettingCard(self) self.ui = UiSettingCard(self) @@ -93,6 +90,9 @@ class Setting(QWidget): ) self.other.card_Notice.clicked.connect(lambda: self.show_notice(if_show=True)) + content_widget = QWidget() + content_layout = QVBoxLayout(content_widget) + content_layout.setContentsMargins(0, 0, 11, 0) content_layout.addWidget(self.function) content_layout.addWidget(self.start) content_layout.addWidget(self.ui) @@ -106,9 +106,9 @@ class Setting(QWidget): scrollArea.setContentsMargins(0, 0, 0, 0) scrollArea.setStyleSheet("background: transparent; border: none;") scrollArea.setWidget(content_widget) - layout = QVBoxLayout() + + layout = QVBoxLayout(self) layout.addWidget(scrollArea) - self.setLayout(layout) def agree_bilibili(self) -> None: """授权bilibili游戏隐私政策""" @@ -252,9 +252,7 @@ class Setting(QWidget): choice.exec() else: choice = MessageBox( - "确认", - "您没有输入管理密钥,是否取消修改管理密钥?", - self.window(), + "确认", "您没有输入管理密钥,是否取消修改管理密钥?", self.window() ) if choice.exec(): break @@ -464,6 +462,7 @@ class Setting(QWidget): "发现新版本", f"{version_text(current_version)} --> {version_text(remote_version)}", 3600000, + if_force=True, ) else: MainInfoBar.push_info_bar("success", "更新检查", "已是最新版本~", 3000) @@ -547,7 +546,7 @@ class Setting(QWidget): ): MainInfoBar.push_info_bar( - "info", "有新公告", "请前往设置界面查看公告", 3600000 + "info", "有新公告", "请前往设置界面查看公告", 3600000, if_force=True ) return None diff --git a/main.py b/main.py index d38ec7d..94befdc 100644 --- a/main.py +++ b/main.py @@ -42,8 +42,7 @@ def main(): from app.ui.main_window import AUTO_MAA window = AUTO_MAA() - window.show_ui("显示主窗口") - window.show_ui("配置托盘") + window.show_ui("显示主窗口", if_start=True) window.start_up_task() sys.exit(application.exec()) diff --git a/resources/docs/MAA_config_info.txt b/resources/docs/MAA_config_info.txt index ddab858..c7e0d9d 100644 --- a/resources/docs/MAA_config_info.txt +++ b/resources/docs/MAA_config_info.txt @@ -58,4 +58,5 @@ G"GUI.UseTray": "True" #显示托盘图标 G"GUI.MinimizeToTray": "False" #最小化时隐藏至托盘 "Start.EmulatorPath" #模拟器路径 "Start.EmulatorAddCommand": "-v 2" #附加命令 +"Start.EmulatorWaitSeconds": "10" #等待模拟器启动时间 G"VersionUpdate.package": "MirrorChyanAppv5.15.6.zip" #更新包标识 \ No newline at end of file diff --git a/resources/version.json b/resources/version.json index ae63f5e..c0e4e71 100644 --- a/resources/version.json +++ b/resources/version.json @@ -1,9 +1,40 @@ { - "main_version": "4.3.8.2", + "main_version": "4.3.8.0", "version_info": { - "4.3.8.2": { + "4.3.8.0": { + "新增功能": [ + "吐司通知在主窗口隐藏时不再弹出" + ] + }, + "4.3.8.4": { + "新增功能": [ + "支持为每一个用户执行独立通知", + "输入文本框适配文本插入操作", + "计划表功能上线", + "静默控制时长从全任务内缩短至搜索ADB时段内", + "UI界面添加自动日常代理任务序列设置项" + ], "修复bug": [ - "日志分析忽略MAA超时提示" + "修复雷电模拟器静默模式无法正常识别模拟器是否隐藏相关问题" + ] + }, + "4.3.8.3": { + "新增功能": [ + "用户仪表盘支持直接控制用户状态" + ], + "修复bug": [ + "修复雷电ADB端口号相关问题" + ] + }, + "4.3.8.2": { + "新增功能": [ + "添加ADB端口号宽幅适配能力" + ], + "修复bug": [ + "日志分析忽略MAA超时提示" + ], + "程序优化": [ + "配置类定义方法优化" ] }, "4.3.8.1": { @@ -18,14 +49,6 @@ "程序优化": [ "UI样式优化,进一步适配win10主题" ] - }, - "4.3.7.0": { - "新增功能": [ - "下载器支持完整mirrorc列表" - ], - "程序优化": [ - "重构更新逻辑,去除独立更新器" - ] } } } \ No newline at end of file