From a5d733c8df71be1b838951c59e984b3bfbd52778 Mon Sep 17 00:00:00 2001 From: aoxuan Date: Thu, 15 May 2025 16:39:56 +0800 Subject: [PATCH 01/28] =?UTF-8?q?feat(notification):=20=E4=B8=BA=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E4=BB=A3=E7=90=86=E7=BB=9F=E8=AE=A1=E6=8A=A5=E5=91=8A?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=97=A5=E6=9C=9F=E5=89=8D=E7=BC=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/MAA.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/models/MAA.py b/app/models/MAA.py index 03727da..445d050 100644 --- a/app/models/MAA.py +++ b/app/models/MAA.py @@ -631,8 +631,9 @@ class MaaManager(QObject): if (run_book["Annihilation"] and run_book["Routine"]) else "代理任务未全部完成" ) + current_date = datetime.now().strftime("%m-%d") self.push_notification( - "统计信息", f"用户 {user[0]} 的自动代理统计报告", statistics + "统计信息", f"{current_date} | 用户 {user[0]} 的自动代理统计报告", statistics ) if run_book["Annihilation"] and run_book["Routine"]: From 5bdb5ad2bfa21acd5773d0f12790a8296ac8c6e0 Mon Sep 17 00:00:00 2001 From: Zrief <103112786+Zrief@users.noreply.github.com> Date: Thu, 15 May 2025 21:20:30 +0800 Subject: [PATCH 02/28] =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=84=9A=E6=9C=AC?= =?UTF-8?q?=E4=BB=AA=E8=A1=A8=E7=9B=98=E6=8E=92=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 可以直接在仪表盘里面开关用户状态了 --- app/ui/Widget.py | 12 ++++++++---- app/ui/member_manager.py | 33 +++++++++++++++++++++++---------- 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/app/ui/Widget.py b/app/ui/Widget.py index cf7f201..7b73003 100644 --- a/app/ui/Widget.py +++ b/app/ui/Widget.py @@ -311,12 +311,16 @@ class SwitchSettingCard(SettingCard): self.switchButton.setChecked(isChecked) self.switchButton.setText(self.tr("On") if isChecked else self.tr("Off")) + + # 这两个函数似乎没用?我注释掉仍能运行。 + # setChecked是qfluentw中SwitchButton类的内置方法, + # Example:switchButton.setChecked(True)# 更改按钮状态为打开 - def setChecked(self, isChecked: bool): - self.setValue(isChecked) + # def setChecked(self, isChecked: bool): + # self.setValue(isChecked) - def isChecked(self): - return self.switchButton.isChecked() + # def isChecked(self): + # return self.switchButton.isChecked() class RangeSettingCard(SettingCard): diff --git a/app/ui/member_manager.py b/app/ui/member_manager.py index 1e7c407..2a4014e 100644 --- a/app/ui/member_manager.py +++ b/app/ui/member_manager.py @@ -47,8 +47,9 @@ from qfluentwidgets import ( PushSettingCard, TableWidget, PrimaryToolButton, + SwitchButton, ) -from PySide6.QtCore import Signal +from PySide6.QtCore import Signal,Qt from datetime import datetime from functools import partial from pathlib import Path @@ -1162,6 +1163,12 @@ class MemberManager(QWidget): Config.PASSWORD_refreshed.connect(self.load_info) def load_info(self): + # 使用闭包工厂函数捕获当前config: + def create_handler(config_obj): + def handler(checked): + # 使用配置项的set方法自动触发信号 + config_obj.set(config_obj.Info_Status, checked) + return handler self.user_data = Config.member_dict[self.name]["UserData"] @@ -1170,6 +1177,7 @@ class MemberManager(QWidget): for name, info in self.user_data.items(): config = info["Config"] + handler = create_handler(config) text_list = [] if not config.get(config.Data_IfPassCheck): @@ -1190,6 +1198,7 @@ class MemberManager(QWidget): else "本周剿灭未完成" ) + # 跳转按钮 button = PrimaryToolButton( FluentIcon.CHEVRON_RIGHT, self ) @@ -1198,6 +1207,15 @@ class MemberManager(QWidget): partial(self.switch_to.emit, name) ) + # 状态开关 + switch_button = SwitchButton() + switch_button.setOffText("") + switch_button.setOnText("") + switch_button.setChecked(True if config.get(config.Info_Status) + and config.get(config.Info_RemainedDay) != 0 else False )# 初始化开关状态 + # 将开关的bool同步 + switch_button.checkedChanged.connect(handler) + self.dashboard.setItem( int(name[3:]) - 1, 0, @@ -1220,15 +1238,10 @@ class MemberManager(QWidget): else "******" ), ) - self.dashboard.setItem( - int(name[3:]) - 1, - 3, - QTableWidgetItem( - "启用" - if config.get(config.Info_Status) - and config.get(config.Info_RemainedDay) != 0 - else "禁用" - ), + self.dashboard.setCellWidget( + int(name[3:]) - 1, + 3, + switch_button ) self.dashboard.setItem( int(name[3:]) - 1, From 13d011547537a00087278cf386c29da7b237f9b8 Mon Sep 17 00:00:00 2001 From: DLmaster361 Date: Fri, 16 May 2025 20:09:29 +0800 Subject: [PATCH 03/28] =?UTF-8?q?feat(core):=20=E6=B7=BB=E5=8A=A0ADB?= =?UTF-8?q?=E7=AB=AF=E5=8F=A3=E5=8F=B7=E5=AE=BD=E5=B9=85=E9=80=82=E9=85=8D?= =?UTF-8?q?=E8=83=BD=E5=8A=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/core/__init__.py | 3 +- app/core/config.py | 463 +++++++++++------------- app/models/MAA.py | 162 +++++++-- app/ui/main_window.py | 10 +- app/ui/member_manager.py | 10 + app/ui/plan_manager.py | 541 +++++++++++++++++++++++++++++ main.py | 3 +- resources/docs/MAA_config_info.txt | 1 + resources/version.json | 8 +- 9 files changed, 911 insertions(+), 290 deletions(-) create mode 100644 app/ui/plan_manager.py 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..9752465 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -78,7 +78,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: @@ -182,72 +253,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 +341,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 +360,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 +379,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: @@ -537,8 +419,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,69 +439,126 @@ 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 - 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] = {} +class MaaPlanConfig(LQConfig): + """MAA计划表配置""" - if item.name: - items[item.group][item.name] = value + def __init__(self) -> None: + super().__init__() - return items + self.Info_Name = ConfigItem("Info", "Name", "新表格") - @exceptionHandler() - def load(self, file=None, config=None): - """load config + self.Global_MedicineNumb = ConfigItem( + "Global", "MedicineNumb", 0, RangeValidator(0, 1024) + ) + self.Global_SeriesNumb = OptionsConfigItem( + "Global", + "SeriesNumb", + "0", + OptionsValidator(["0", "6", "5", "4", "3", "2", "1", "-1"]), + ) + self.Global_GameId = ConfigItem("Global", "GameId", "-") + self.Global_GameId_1 = ConfigItem("Global", "GameId_1", "-") + self.Global_GameId_2 = ConfigItem("Global", "GameId_2", "-") + self.Global_GameId_Remain = ConfigItem("Global", "GameId_Remain", "-") - Parameters - ---------- - file: str or Path - the path of json config file + self.Monday_MedicineNumb = ConfigItem( + "Monday", "MedicineNumb", 0, RangeValidator(0, 1024) + ) + self.Monday_SeriesNumb = OptionsConfigItem( + "Monday", + "SeriesNumb", + "0", + OptionsValidator(["0", "6", "5", "4", "3", "2", "1", "-1"]), + ) + self.Monday_GameId = ConfigItem("Monday", "GameId", "-") + self.Monday_GameId_1 = ConfigItem("Monday", "GameId_1", "-") + self.Monday_GameId_2 = ConfigItem("Monday", "GameId_2", "-") + self.Monday_GameId_Remain = ConfigItem("Monday", "GameId_Remain", "-") - config: Config - config object to be initialized - """ - if isinstance(config, QConfig): - self._cfg = config - self._cfg.themeChanged.connect(self.themeChanged) + self.Tuesday_MedicineNumb = ConfigItem( + "Tuesday", "MedicineNumb", 0, RangeValidator(0, 1024) + ) + self.Tuesday_SeriesNumb = OptionsConfigItem( + "Tuesday", + "SeriesNumb", + "0", + OptionsValidator(["0", "6", "5", "4", "3", "2", "1", "-1"]), + ) + self.Tuesday_GameId = ConfigItem("Tuesday", "GameId", "-") + self.Tuesday_GameId_1 = ConfigItem("Tuesday", "GameId_1", "-") + self.Tuesday_GameId_2 = ConfigItem("Tuesday", "GameId_2", "-") + self.Tuesday_GameId_Remain = ConfigItem("Tuesday", "GameId_Remain", "-") - if isinstance(file, (str, Path)): - self._cfg.file = Path(file) + self.Wednesday_MedicineNumb = ConfigItem( + "Wednesday", "MedicineNumb", 0, RangeValidator(0, 1024) + ) + self.Wednesday_SeriesNumb = OptionsConfigItem( + "Wednesday", + "SeriesNumb", + "0", + OptionsValidator(["0", "6", "5", "4", "3", "2", "1", "-1"]), + ) + self.Wednesday_GameId = ConfigItem("Wednesday", "GameId", "-") + self.Wednesday_GameId_1 = ConfigItem("Wednesday", "GameId_1", "-") + self.Wednesday_GameId_2 = ConfigItem("Wednesday", "GameId_2", "-") + self.Wednesday_GameId_Remain = ConfigItem("Wednesday", "GameId_Remain", "-") - try: - with open(self._cfg.file, encoding="utf-8") as f: - cfg = json.load(f) - except: - cfg = {} + self.Thursday_MedicineNumb = ConfigItem( + "Thursday", "MedicineNumb", 0, RangeValidator(0, 1024) + ) + self.Thursday_SeriesNumb = OptionsConfigItem( + "Thursday", + "SeriesNumb", + "0", + OptionsValidator(["0", "6", "5", "4", "3", "2", "1", "-1"]), + ) + self.Thursday_GameId = ConfigItem("Thursday", "GameId", "-") + self.Thursday_GameId_1 = ConfigItem("Thursday", "GameId_1", "-") + self.Thursday_GameId_2 = ConfigItem("Thursday", "GameId_2", "-") + self.Thursday_GameId_Remain = ConfigItem("Thursday", "GameId_Remain", "-") - # 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 + self.Friday_MedicineNumb = ConfigItem( + "Friday", "MedicineNumb", 0, RangeValidator(0, 1024) + ) + self.Friday_SeriesNumb = OptionsConfigItem( + "Friday", + "SeriesNumb", + "0", + OptionsValidator(["0", "6", "5", "4", "3", "2", "1", "-1"]), + ) + self.Friday_GameId = ConfigItem("Friday", "GameId", "-") + self.Friday_GameId_1 = ConfigItem("Friday", "GameId_1", "-") + self.Friday_GameId_2 = ConfigItem("Friday", "GameId_2", "-") + self.Friday_GameId_Remain = ConfigItem("Friday", "GameId_Remain", "-") - # 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.Saturday_MedicineNumb = ConfigItem( + "Saturday", "MedicineNumb", 0, RangeValidator(0, 1024) + ) + self.Saturday_SeriesNumb = OptionsConfigItem( + "Saturday", + "SeriesNumb", + "0", + OptionsValidator(["0", "6", "5", "4", "3", "2", "1", "-1"]), + ) + self.Saturday_GameId = ConfigItem("Saturday", "GameId", "-") + self.Saturday_GameId_1 = ConfigItem("Saturday", "GameId_1", "-") + self.Saturday_GameId_2 = ConfigItem("Saturday", "GameId_2", "-") + self.Saturday_GameId_Remain = ConfigItem("Saturday", "GameId_Remain", "-") - self.theme = self.get(self._cfg.themeMode) + self.Sunday_MedicineNumb = ConfigItem( + "Sunday", "MedicineNumb", 0, RangeValidator(0, 1024) + ) + self.Sunday_SeriesNumb = OptionsConfigItem( + "Sunday", + "SeriesNumb", + "0", + OptionsValidator(["0", "6", "5", "4", "3", "2", "1", "-1"]), + ) + self.Sunday_GameId = ConfigItem("Sunday", "GameId", "-") + self.Sunday_GameId_1 = ConfigItem("Sunday", "GameId_1", "-") + self.Sunday_GameId_2 = ConfigItem("Sunday", "GameId_2", "-") + self.Sunday_GameId_Remain = ConfigItem("Sunday", "GameId_Remain", "-") class AppConfig(GlobalConfig): @@ -704,7 +643,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() @@ -1235,6 +1174,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.member_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): """搜索所有调度队列实例""" diff --git a/app/models/MAA.py b/app/models/MAA.py index 03727da..60d737a 100644 --- a/app/models/MAA.py +++ b/app/models/MAA.py @@ -114,6 +114,10 @@ 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代理进程""" @@ -386,6 +390,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 +424,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 +443,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 +460,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 +533,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"] @@ -550,6 +572,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}") @@ -567,29 +590,6 @@ class MaaManager(QObject): self.emulator_process.wait() self.if_open_emulator = True - # 执行MAA解压更新动作 - if self.maa_update_package: - - logger.info( - f"{self.name} | 检测到MAA更新,正在执行更新动作" - ) - - self.update_log_text.emit( - f"检测到MAA存在更新\nMAA正在执行更新动作\n请等待10s" - ) - self.set_maa("更新MAA", None) - subprocess.Popen( - [self.maa_exe_path], - creationflags=subprocess.CREATE_NO_WINDOW, - ) - for _ in range(10): - if self.isInterruptionRequested: - break - time.sleep(1) - System.kill_process(self.maa_exe_path) - - logger.info(f"{self.name} | 更新动作结束") - # 记录剿灭情况 if ( mode == "Annihilation" @@ -616,6 +616,29 @@ class MaaManager(QObject): {"user_name": user_data["Info"]["Name"]}, ) + # 执行MAA解压更新动作 + if self.maa_update_package: + + logger.info( + f"{self.name} | 检测到MAA更新,正在执行更新动作" + ) + + self.update_log_text.emit( + f"检测到MAA存在更新\nMAA正在执行更新动作\n请等待10s" + ) + self.set_maa("更新MAA", None) + subprocess.Popen( + [self.maa_exe_path], + creationflags=subprocess.CREATE_NO_WINDOW, + ) + for _ in range(10): + if self.isInterruptionRequested: + break + time.sleep(1) + System.kill_process(self.maa_exe_path) + + logger.info(f"{self.name} | 更新动作结束") + if Config.get(Config.notify_IfSendStatistic): statistics = Config.merge_maa_logs("指定项", user_logs_list) @@ -866,6 +889,80 @@ 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" + ) + + time.sleep(self.wait_time) + + if "-" in self.ADB_address: + ADB_ip = f"{self.ADB_address.split("-")[0]}-" + ADB_port = 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日志""" @@ -1418,12 +1515,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 +1544,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" ][ diff --git a/app/ui/main_window.py b/app/ui/main_window.py index 9509809..9f93fb1 100644 --- a/app/ui/main_window.py +++ b/app/ui/main_window.py @@ -351,7 +351,9 @@ class AUTO_MAA(MSFluentWindow): "warning", "启动主任务失败", "“调度队列_1”与“脚本_1”均不存在", -1 ) - def show_ui(self, mode: str, if_quick: bool = False) -> None: + def show_ui( + self, mode: str, if_quick: bool = False, if_start: bool = False + ) -> None: """配置窗口状态""" self.switch_theme() @@ -378,8 +380,12 @@ class AUTO_MAA(MSFluentWindow): self.window().show() if not if_quick: if Config.get(Config.ui_maximized): - self.titleBar.maxBtn.click() + self.window().showMaximized() self.show_ui("配置托盘") + elif if_start: + if Config.get(Config.ui_maximized): + self.window().showMaximized() + self.show_ui("配置托盘") if not any( self.window().geometry().intersects(screen.availableGeometry()) diff --git a/app/ui/member_manager.py b/app/ui/member_manager.py index 1e7c407..11baa31 100644 --- a/app/ui/member_manager.py +++ b/app/ui/member_manager.py @@ -698,6 +698,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 +755,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) diff --git a/app/ui/plan_manager.py b/app/ui/plan_manager.py new file mode 100644 index 0000000..b0fab53 --- /dev/null +++ b/app/ui/plan_manager.py @@ -0,0 +1,541 @@ +# 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, + QFileDialog, + QHBoxLayout, + QVBoxLayout, + QStackedWidget, + QTableWidgetItem, + QHeaderView, +) +from PySide6.QtGui import QIcon +from qfluentwidgets import ( + Action, + ScrollArea, + FluentIcon, + MessageBox, + HeaderCardWidget, + CommandBar, + ExpandGroupSettingCard, + PushSettingCard, + TableWidget, + PrimaryToolButton, +) +from PySide6.QtCore import Signal +from datetime import datetime +from functools import partial +from pathlib import Path +from typing import List +import shutil +import json + +from app.core import Config, MainInfoBar, TaskManager, MaaPlanConfig, Network +from app.services import Crypto +from .downloader import DownloadManager +from .Widget import ( + LineEditMessageBox, + LineEditSettingCard, + SpinBoxSettingCard, + ComboBoxMessageBox, + EditableComboBoxSettingCard, + PasswordLineEditSettingCard, + UserLableSettingCard, + ComboBoxSettingCard, + SwitchSettingCard, + PushAndSwitchButtonSettingCard, + PushAndComboBoxSettingCard, + 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_MaaSettingBox(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 == 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_queue(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 == 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_queue(name, "计划_0") + Config.plan_dict[f"计划_{index-1}"]["Path"].rename( + Config.plan_dict[name]["Path"] + ) + Config.change_queue(f"计划_{index-1}", name) + Config.plan_dict[name]["Path"].with_name("计划_0").rename( + Config.plan_dict[f"计划_{index-1}"]["Path"] + ) + Config.change_queue("计划_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 == 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_queue(name, "计划_0") + Config.plan_dict[f"计划_{index+1}"]["Path"].rename( + Config.plan_dict[name]["Path"] + ) + Config.change_queue(f"计划_{index+1}", name) + Config.plan_dict[name]["Path"].with_name("计划_0").rename( + Config.plan_dict[f"计划_{index+1}"]["Path"] + ) + Config.change_queue("计划_0", f"计划_{index+1}") + + self.plan_manager.show_SettingBox(index + 1) + + logger.success(f"计划表 {name} 右移成功") + MainInfoBar.push_info_bar("success", "操作成功", f"右移计划表 {name}", 3000) + + def refresh_dashboard(self): + """刷新所有计划表的用户仪表盘""" + + for script in self.plan_manager.script_list: + script.user_setting.user_manager.user_dashboard.load_info() + + 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_MaaSettingBox(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: + 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.config = Config.plan_dict[f"计划_{uid}"]["Config"] + + self.dashboard = TableWidget(self) + self.dashboard.setColumnCount(11) + self.dashboard.setHorizontalHeaderLabels( + [ + "吃理智药", + "连战次数", + "关卡选择", + "备选关卡 - 1", + "备选关卡 - 2", + "剩余理智关卡", + ] + ) + self.dashboard.setEditTriggers(TableWidget.NoEditTriggers) + self.dashboard.verticalHeader().setVisible(False) + for col in range(6): + self.dashboard.horizontalHeader().setSectionResizeMode( + col, QHeaderView.ResizeMode.Stretch + ) + + self.viewLayout.addWidget(self.dashboard) + self.viewLayout.setContentsMargins(3, 0, 3, 3) + + Config.PASSWORD_refreshed.connect(self.load_info) + + def load_info(self): + + self.user_data = Config.plan_dict[self.name]["UserData"] + + self.dashboard.setRowCount(len(self.user_data)) + + for name, info in self.user_data.items(): + + config = info["Config"] + + text_list = [] + if not config.get(config.Data_IfPassCheck): + text_list.append("未通过人工排查") + text_list.append( + f"今日已代理{config.get(config.Data_ProxyTimes)}次" + if Config.server_date().strftime("%Y-%m-%d") + == config.get(config.Data_LastProxyDate) + else "今日未进行代理" + ) + text_list.append( + "本周剿灭已完成" + if datetime.strptime( + config.get(config.Data_LastAnnihilationDate), + "%Y-%m-%d", + ).isocalendar()[:2] + == Config.server_date().isocalendar()[:2] + else "本周剿灭未完成" + ) + + button = PrimaryToolButton(FluentIcon.CHEVRON_RIGHT, self) + button.setFixedSize(32, 32) + button.clicked.connect(partial(self.switch_to.emit, name)) + + self.dashboard.setItem( + int(name[3:]) - 1, + 0, + QTableWidgetItem(config.get(config.Info_Name)), + ) + self.dashboard.setItem( + int(name[3:]) - 1, + 1, + QTableWidgetItem(config.get(config.Info_Id)), + ) + self.dashboard.setItem( + int(name[3:]) - 1, + 2, + QTableWidgetItem( + Crypto.AUTO_decryptor( + config.get(config.Info_Password), + Config.PASSWORD, + ) + if Config.PASSWORD + else "******" + ), + ) + self.dashboard.setItem( + int(name[3:]) - 1, + 3, + QTableWidgetItem( + "启用" + if config.get(config.Info_Status) + and config.get(config.Info_RemainedDay) != 0 + else "禁用" + ), + ) + self.dashboard.setItem( + int(name[3:]) - 1, + 4, + QTableWidgetItem(" | ".join(text_list)), + ) + self.dashboard.setItem( + int(name[3:]) - 1, + 5, + QTableWidgetItem(str(config.get(config.Info_MedicineNumb))), + ) + self.dashboard.setItem( + int(name[3:]) - 1, + 6, + QTableWidgetItem( + Config.gameid_dict["ALL"]["text"][ + Config.gameid_dict["ALL"]["value"].index( + config.get(config.Info_GameId) + ) + ] + if config.get(config.Info_GameId) + in Config.gameid_dict["ALL"]["value"] + else config.get(config.Info_GameId) + ), + ) + self.dashboard.setItem( + int(name[3:]) - 1, + 7, + QTableWidgetItem( + Config.gameid_dict["ALL"]["text"][ + Config.gameid_dict["ALL"]["value"].index( + config.get(config.Info_GameId_1) + ) + ] + if config.get(config.Info_GameId_1) + in Config.gameid_dict["ALL"]["value"] + else config.get(config.Info_GameId_1) + ), + ) + self.dashboard.setItem( + int(name[3:]) - 1, + 8, + QTableWidgetItem( + Config.gameid_dict["ALL"]["text"][ + Config.gameid_dict["ALL"]["value"].index( + config.get(config.Info_GameId_2) + ) + ] + if config.get(config.Info_GameId_2) + in Config.gameid_dict["ALL"]["value"] + else config.get(config.Info_GameId_2) + ), + ) + self.dashboard.setItem( + int(name[3:]) - 1, + 9, + QTableWidgetItem( + "不使用" + if config.get(config.Info_GameId_Remain) == "-" + else ( + ( + Config.gameid_dict["ALL"]["text"][ + Config.gameid_dict["ALL"]["value"].index( + config.get(config.Info_GameId_Remain) + ) + ] + ) + if config.get(config.Info_GameId_Remain) + in Config.gameid_dict["ALL"]["value"] + else config.get(config.Info_GameId_Remain) + ) + ), + ) + self.dashboard.setCellWidget(int(name[3:]) - 1, 10, button) 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..c4f9648 100644 --- a/resources/version.json +++ b/resources/version.json @@ -2,8 +2,14 @@ "main_version": "4.3.8.2", "version_info": { "4.3.8.2": { + "新增功能": [ + "添加ADB端口号宽幅适配能力" + ], "修复bug": [ - "日志分析忽略MAA超时提示" + "日志分析忽略MAA超时提示" + ], + "程序优化": [ + "配置类定义方法更新,预载入计划表相关配置" ] }, "4.3.8.1": { From 5d966f98dfdc984dbbdcc7365239fea1e673e7d3 Mon Sep 17 00:00:00 2001 From: Zrief <103112786+Zrief@users.noreply.github.com> Date: Fri, 16 May 2025 21:53:03 +0800 Subject: [PATCH 04/28] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E9=80=BB=E8=BE=91?= =?UTF-8?q?=E4=B8=8D=E5=AE=8C=E5=A4=87=E7=9A=84=E5=9C=B0=E6=96=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/ui/member_manager.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/ui/member_manager.py b/app/ui/member_manager.py index 2a4014e..b7b0932 100644 --- a/app/ui/member_manager.py +++ b/app/ui/member_manager.py @@ -1211,8 +1211,7 @@ class MemberManager(QWidget): switch_button = SwitchButton() switch_button.setOffText("") switch_button.setOnText("") - switch_button.setChecked(True if config.get(config.Info_Status) - and config.get(config.Info_RemainedDay) != 0 else False )# 初始化开关状态 + switch_button.setChecked(config.get(config.Info_Status))# 初始化开关状态 # 将开关的bool同步 switch_button.checkedChanged.connect(handler) From aa83058e39b8d834a4fe20c5cdc280677e757848 Mon Sep 17 00:00:00 2001 From: Zrief <103112786+Zrief@users.noreply.github.com> Date: Fri, 16 May 2025 21:56:20 +0800 Subject: [PATCH 05/28] Update member_manager.py --- app/ui/member_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/ui/member_manager.py b/app/ui/member_manager.py index 746d302..9f2922f 100644 --- a/app/ui/member_manager.py +++ b/app/ui/member_manager.py @@ -49,7 +49,7 @@ from qfluentwidgets import ( PrimaryToolButton, SwitchButton, ) -from PySide6.QtCore import Signal,Qt +from PySide6.QtCore import Signal from datetime import datetime from functools import partial from pathlib import Path From 1162d5dcc1312e431d8a057b603531d29e0d159a Mon Sep 17 00:00:00 2001 From: DLmaster361 Date: Fri, 16 May 2025 22:50:54 +0800 Subject: [PATCH 06/28] =?UTF-8?q?feat(ui):=20=E6=B7=BB=E5=8A=A0=E5=AF=B9?= =?UTF-8?q?=E5=89=A9=E4=BD=99=E5=A4=A9=E6=95=B0=E4=B8=BA0=E7=9A=84?= =?UTF-8?q?=E5=B1=95=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/core/config.py | 2 +- app/ui/Widget.py | 12 ++++-------- app/ui/member_manager.py | 11 +++++++---- resources/version.json | 7 ++++++- 4 files changed, 18 insertions(+), 14 deletions(-) diff --git a/app/core/config.py b/app/core/config.py index 9752465..77cfbbf 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -563,7 +563,7 @@ class MaaPlanConfig(LQConfig): class AppConfig(GlobalConfig): - VERSION = "4.3.8.2" + VERSION = "4.3.8.3" gameid_refreshed = Signal() PASSWORD_refreshed = Signal() diff --git a/app/ui/Widget.py b/app/ui/Widget.py index 7b73003..cf7f201 100644 --- a/app/ui/Widget.py +++ b/app/ui/Widget.py @@ -311,16 +311,12 @@ class SwitchSettingCard(SettingCard): self.switchButton.setChecked(isChecked) self.switchButton.setText(self.tr("On") if isChecked else self.tr("Off")) - - # 这两个函数似乎没用?我注释掉仍能运行。 - # setChecked是qfluentw中SwitchButton类的内置方法, - # Example:switchButton.setChecked(True)# 更改按钮状态为打开 - # def setChecked(self, isChecked: bool): - # self.setValue(isChecked) + def setChecked(self, isChecked: bool): + self.setValue(isChecked) - # def isChecked(self): - # return self.switchButton.isChecked() + def isChecked(self): + return self.switchButton.isChecked() class RangeSettingCard(SettingCard): diff --git a/app/ui/member_manager.py b/app/ui/member_manager.py index 9f2922f..0de0e92 100644 --- a/app/ui/member_manager.py +++ b/app/ui/member_manager.py @@ -1178,6 +1178,7 @@ class MemberManager(QWidget): def handler(checked): # 使用配置项的set方法自动触发信号 config_obj.set(config_obj.Info_Status, checked) + return handler self.user_data = Config.member_dict[self.name]["UserData"] @@ -1221,7 +1222,11 @@ class MemberManager(QWidget): switch_button = SwitchButton() switch_button.setOffText("") switch_button.setOnText("") - switch_button.setChecked(config.get(config.Info_Status))# 初始化开关状态 + # 初始化开关状态 + switch_button.setChecked(config.get(config.Info_Status)) + switch_button.setEnabled( + config.get(config.Info_RemainedDay) != 0 + ) # 将开关的bool同步 switch_button.checkedChanged.connect(handler) @@ -1248,9 +1253,7 @@ class MemberManager(QWidget): ), ) self.dashboard.setCellWidget( - int(name[3:]) - 1, - 3, - switch_button + int(name[3:]) - 1, 3, switch_button ) self.dashboard.setItem( int(name[3:]) - 1, diff --git a/resources/version.json b/resources/version.json index c4f9648..071b9bc 100644 --- a/resources/version.json +++ b/resources/version.json @@ -1,6 +1,11 @@ { - "main_version": "4.3.8.2", + "main_version": "4.3.8.3", "version_info": { + "4.3.8.3": { + "新增功能": [ + "用户仪表盘支持直接控制用户状态" + ] + }, "4.3.8.2": { "新增功能": [ "添加ADB端口号宽幅适配能力" From 803fe4568fcd9c55d2e17f2a2814176031058af3 Mon Sep 17 00:00:00 2001 From: DLmaster361 Date: Fri, 16 May 2025 23:23:02 +0800 Subject: [PATCH 07/28] =?UTF-8?q?chore(ui):=20=E4=BC=98=E5=8C=96=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/ui/Widget.py | 32 ++++++++++++++++++++++++++++++++ app/ui/member_manager.py | 32 +++++++++----------------------- 2 files changed, 41 insertions(+), 23 deletions(-) diff --git a/app/ui/Widget.py b/app/ui/Widget.py index cf7f201..55b3c1d 100644 --- a/app/ui/Widget.py +++ b/app/ui/Widget.py @@ -933,6 +933,38 @@ class TimeEditSettingCard(SettingCard): self.TimeEdit.setTime(QTime.fromString(value, "HH:mm")) +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 HistoryCard(HeaderCardWidget): def __init__(self, qconfig: QConfig, configItem: ConfigItem, parent=None): diff --git a/app/ui/member_manager.py b/app/ui/member_manager.py index 0de0e92..34ff739 100644 --- a/app/ui/member_manager.py +++ b/app/ui/member_manager.py @@ -47,7 +47,6 @@ from qfluentwidgets import ( PushSettingCard, TableWidget, PrimaryToolButton, - SwitchButton, ) from PySide6.QtCore import Signal from datetime import datetime @@ -72,6 +71,7 @@ from .Widget import ( SwitchSettingCard, PushAndSwitchButtonSettingCard, PushAndComboBoxSettingCard, + StatusSwitchSetting, PivotArea, ) @@ -1173,13 +1173,6 @@ class MemberManager(QWidget): Config.PASSWORD_refreshed.connect(self.load_info) def load_info(self): - # 使用闭包工厂函数捕获当前config: - def create_handler(config_obj): - def handler(checked): - # 使用配置项的set方法自动触发信号 - config_obj.set(config_obj.Info_Status, checked) - - return handler self.user_data = Config.member_dict[self.name]["UserData"] @@ -1188,7 +1181,6 @@ class MemberManager(QWidget): for name, info in self.user_data.items(): config = info["Config"] - handler = create_handler(config) text_list = [] if not config.get(config.Data_IfPassCheck): @@ -1209,7 +1201,6 @@ class MemberManager(QWidget): else "本周剿灭未完成" ) - # 跳转按钮 button = PrimaryToolButton( FluentIcon.CHEVRON_RIGHT, self ) @@ -1218,18 +1209,6 @@ class MemberManager(QWidget): partial(self.switch_to.emit, name) ) - # 状态开关 - switch_button = SwitchButton() - switch_button.setOffText("") - switch_button.setOnText("") - # 初始化开关状态 - switch_button.setChecked(config.get(config.Info_Status)) - switch_button.setEnabled( - config.get(config.Info_RemainedDay) != 0 - ) - # 将开关的bool同步 - switch_button.checkedChanged.connect(handler) - self.dashboard.setItem( int(name[3:]) - 1, 0, @@ -1253,7 +1232,14 @@ class MemberManager(QWidget): ), ) self.dashboard.setCellWidget( - int(name[3:]) - 1, 3, switch_button + int(name[3:]) - 1, + 3, + StatusSwitchSetting( + qconfig=config, + configItem_check=config.Info_Status, + configItem_enable=config.Info_RemainedDay, + parent=self, + ), ) self.dashboard.setItem( int(name[3:]) - 1, From d78a764d8766a9f6cc850fee29500372dcd802c8 Mon Sep 17 00:00:00 2001 From: DLmaster361 Date: Sat, 17 May 2025 00:07:34 +0800 Subject: [PATCH 08/28] =?UTF-8?q?fix:=20=E4=BF=AE=E6=AD=A3=E6=90=9C?= =?UTF-8?q?=E7=B4=A2ADB=E6=97=B6=E7=9A=84=E7=AD=89=E5=BE=85=E6=96=B9?= =?UTF-8?q?=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/MAA.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app/models/MAA.py b/app/models/MAA.py index 90e9bc2..f946a61 100644 --- a/app/models/MAA.py +++ b/app/models/MAA.py @@ -656,7 +656,9 @@ class MaaManager(QObject): ) current_date = datetime.now().strftime("%m-%d") self.push_notification( - "统计信息", f"{current_date} | 用户 {user[0]} 的自动代理统计报告", statistics + "统计信息", + f"{current_date} | 用户 {user[0]} 的自动代理统计报告", + statistics, ) if run_book["Annihilation"] and run_book["Routine"]: @@ -897,7 +899,10 @@ class MaaManager(QObject): f"即将搜索ADB实际地址\n正在等待模拟器完成启动\n请等待{self.wait_time}s" ) - time.sleep(self.wait_time) + for _ in range(self.wait_time): + if self.isInterruptionRequested: + break + time.sleep(1) if "-" in self.ADB_address: ADB_ip = f"{self.ADB_address.split("-")[0]}-" From cf95075d011ee829ae8b2223d2104d907247b63f Mon Sep 17 00:00:00 2001 From: DLmaster361 Date: Sat, 17 May 2025 22:26:41 +0800 Subject: [PATCH 09/28] =?UTF-8?q?fix(ui):=20=E4=BF=AE=E5=A4=8D=E7=AA=97?= =?UTF-8?q?=E5=8F=A3=E6=9C=80=E5=A4=A7=E5=8C=96=E5=8A=9F=E8=83=BD=E5=BC=82?= =?UTF-8?q?=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/ui/main_window.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/app/ui/main_window.py b/app/ui/main_window.py index 9f93fb1..1f31457 100644 --- a/app/ui/main_window.py +++ b/app/ui/main_window.py @@ -379,14 +379,18 @@ class AUTO_MAA(MSFluentWindow): 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.window().showMaximized() + 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): - self.window().showMaximized() + 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() From c0f887ff9d81ac0c30e25c05b9b8199ebf86ebc4 Mon Sep 17 00:00:00 2001 From: aoxuan Date: Sun, 18 May 2025 00:17:06 +0800 Subject: [PATCH 10/28] =?UTF-8?q?refactor(notification):=20=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E4=BB=BB=E5=8A=A1=E6=8A=A5=E5=91=8A=E6=A0=87=E9=A2=98?= =?UTF-8?q?=E6=A0=BC=E5=BC=8F=EF=BC=8C=E5=A2=9E=E5=8A=A0=E6=97=A5=E6=9C=9F?= =?UTF-8?q?=E5=89=8D=E7=BC=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/MAA.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/models/MAA.py b/app/models/MAA.py index f946a61..260d0ca 100644 --- a/app/models/MAA.py +++ b/app/models/MAA.py @@ -122,6 +122,7 @@ class MaaManager(QObject): 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") @@ -654,7 +655,6 @@ class MaaManager(QObject): if (run_book["Annihilation"] and run_book["Routine"]) else "代理任务未全部完成" ) - current_date = datetime.now().strftime("%m-%d") self.push_notification( "统计信息", f"{current_date} | 用户 {user[0]} 的自动代理统计报告", @@ -830,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]}任务报告", From 0d904b229e622932ea928147e7fc594e5653f516 Mon Sep 17 00:00:00 2001 From: aoxuan Date: Sun, 18 May 2025 01:57:48 +0800 Subject: [PATCH 11/28] =?UTF-8?q?feat(core):=20=E5=88=9D=E6=AD=A5=E5=AE=8C?= =?UTF-8?q?=E6=88=90=E6=96=B0=E5=A2=9E=E7=94=A8=E6=88=B7=E5=8D=95=E7=8B=AC?= =?UTF-8?q?=E9=80=9A=E7=9F=A5=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/core/config.py | 14 +++ app/models/MAA.py | 41 ++++++- app/services/notification.py | 122 +++++++++++++++++++++ app/ui/member_manager.py | 207 +++++++++++++++++++++++++++++++++++ 4 files changed, 381 insertions(+), 3 deletions(-) diff --git a/app/core/config.py b/app/core/config.py index 77cfbbf..6a5fb17 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -439,6 +439,20 @@ class MaaUserConfig(LQConfig): "Data", "CustomInfrastPlanIndex", "0" ) + # 新增用户单独通知字段 + self.Notify_Enable = ConfigItem("Notify_Enable", False, False) + self.Notify_IfSMTP = ConfigItem("Notify_IfSMTP", False, False) + self.Notify_SMTPServerAddress = ConfigItem("Notify_SMTPServerAddress", "", "") + self.Notify_AuthorizationCode = ConfigItem("Notify_AuthorizationCode", "", "") + self.Notify_FromAddress = ConfigItem("Notify_FromAddress", "", "") + self.Notify_ToAddress = ConfigItem("Notify_ToAddress", "", "") + self.Notify_IfServerChan = ConfigItem("Notify_IfServerChan", False, False) + 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, False) + self.Notify_CompanyWebHookBotUrl = ConfigItem("Notify_CompanyWebHookBotUrl", "", "") + class MaaPlanConfig(LQConfig): """MAA计划表配置""" diff --git a/app/models/MAA.py b/app/models/MAA.py index 260d0ca..6662a30 100644 --- a/app/models/MAA.py +++ b/app/models/MAA.py @@ -40,6 +40,7 @@ from typing import Union, List, Dict from app.core import Config, MaaConfig, MaaUserConfig from app.services import Notify, System +from app.services.notification import Notification, UserNotification class MaaManager(QObject): @@ -1746,7 +1747,6 @@ class MaaManager(QObject): ) if mode == "代理结果": - # 生成文本通知内容 message_text = ( f"任务开始时间:{message["start_time"]},结束时间:{message["end_time"]}\n" @@ -1768,14 +1768,26 @@ 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 敬上") + # 发送用户单独通知 + for user_name in message["failed_user"].split("、") + message["waiting_user"].split("、"): + if not user_name: # 跳过空字符串 + continue + user_config = Config.member_dict.get(user_name) + if user_config and user_config.get(user_config.Notify_Enable): + user_notify = UserNotification(user_config) + user_notify.send_notification( + f"{self.mode[2:4]}任务未完成通知", + f"您的{self.mode[2:4]}任务未完成,请检查相关设置。\n\n{message_text}" + ) + return message_text elif mode == "统计信息": - # 生成文本通知内容 formatted = [] for stage, items in message["drop_statistics"].items(): @@ -1801,18 +1813,41 @@ 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 敬上") - elif mode == "公招六星": + # 发送用户单独通知 + user_name = message.get("user_info") + if user_name: + user_config = Config.member_dict.get(user_name) + if user_config and user_config.get(user_config.Notify_Enable): + user_notify = UserNotification(user_config) + user_notify.send_notification( + f"{self.mode[2:4]}任务统计报告", + f"您的{self.mode[2:4]}任务统计报告如下:\n\n{message_text}" + ) + 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 敬上") + + # 发送用户单独通知 + user_name = message.get("user_name") + if user_name: + user_config = Config.member_dict.get(user_name) + if user_config and user_config.get(user_config.Notify_Enable): + user_notify = UserNotification(user_config) + user_notify.send_notification( + "公招六星通知", + "恭喜您在公招中获得了六星干员!" + ) diff --git a/app/services/notification.py b/app/services/notification.py index 0f63acf..9666f9a 100644 --- a/app/services/notification.py +++ b/app/services/notification.py @@ -313,4 +313,126 @@ class Notification(QWidget): return True +class UserNotification: + """用户单独通知服务""" + + def __init__(self, user_config): + self.config = user_config + + def send_notification(self, title: str, content: str) -> bool: + """发送用户通知 + + Args: + title: 通知标题 + content: 通知内容 + + Returns: + bool: 是否发送成功 + """ + if not self.config.get(self.config.Notify_Enable): + return False + + success = False + + # 发送邮件通知 + if self._check_smtp_config(): + try: + self._send_email(title, content) + success = True + except Exception as e: + logger.error(f"发送邮件通知失败: {str(e)}") + + # 发送ServerChan通知 + if self.config.get(self.config.Notify_IfServerChan) and self._check_serverchan_config(): + try: + self._send_serverchan(title, content) + success = True + except Exception as e: + logger.error(f"发送ServerChan通知失败: {str(e)}") + + # 发送企业微信机器人通知 + if self.config.get(self.config.Notify_IfCompanyWebHookBot) and self._check_webhook_config(): + try: + self._send_webhook(title, content) + success = True + except Exception as e: + logger.error(f"发送企业微信机器人通知失败: {str(e)}") + + return success + + def _check_smtp_config(self) -> bool: + """检查SMTP配置是否完整""" + return all([ + self.config.get(self.config.Notify_IfSMTP), + self.config.get(self.config.Notify_SMTPServerAddress), + self.config.get(self.config.Notify_AuthorizationCode), + self.config.get(self.config.Notify_FromAddress), + self.config.get(self.config.Notify_ToAddress) + ]) + + def _check_serverchan_config(self) -> bool: + """检查ServerChan配置是否完整""" + return bool(self.config.get(self.config.Notify_ServerChanKey)) + + def _check_webhook_config(self) -> bool: + """检查企业微信机器人配置是否完整""" + return bool(self.config.get(self.config.Notify_CompanyWebHookBotUrl)) + + def _send_email(self, title: str, content: str): + """发送邮件通知""" + import smtplib + from email.mime.text import MIMEText + from email.header import Header + + msg = MIMEText(content, 'plain', 'utf-8') + msg['Subject'] = Header(title, 'utf-8') + msg['From'] = self.config.get(self.config.Notify_FromAddress) + msg['To'] = self.config.get(self.config.Notify_ToAddress) + + server = smtplib.SMTP_SSL(self.config.get(self.config.Notify_SMTPServerAddress)) + server.login( + self.config.get(self.config.Notify_FromAddress), + self.config.get(self.config.Notify_AuthorizationCode) + ) + server.send_message(msg) + server.quit() + + def _send_serverchan(self, title: str, content: str): + """发送ServerChan通知""" + import requests + + key = self.config.get(self.config.Notify_ServerChanKey) + channel = self.config.get(self.config.Notify_ServerChanChannel) + tag = self.config.get(self.config.Notify_ServerChanTag) + + url = f"https://sctapi.ftqq.com/{key}.send" + data = { + "title": title, + "desp": content, + "channel": channel if channel else 9, # 默认使用企业微信通道 + "tag": tag if tag else "" + } + + response = requests.post(url, data=data) + if response.status_code != 200: + raise Exception(f"ServerChan API返回错误: {response.text}") + + def _send_webhook(self, title: str, content: str): + """发送企业微信机器人通知""" + import requests + import json + + url = self.config.get(self.config.Notify_CompanyWebHookBotUrl) + data = { + "msgtype": "markdown", + "markdown": { + "content": f"### {title}\n{content}" + } + } + + response = requests.post(url, json=data) + if response.status_code != 200: + raise Exception(f"企业微信机器人API返回错误: {response.text}") + + Notify = Notification() diff --git a/app/ui/member_manager.py b/app/ui/member_manager.py index 34ff739..71fc30f 100644 --- a/app/ui/member_manager.py +++ b/app/ui/member_manager.py @@ -1529,6 +1529,140 @@ class MemberManager(QWidget): parent=self, ) + # 新增单独通知卡片 + self.card_NotifyEnable = SwitchSettingCard( + icon=FluentIcon.INFO, + title="启用单独通知", + content="启用后,任务结束将向该用户单独推送通知", + qconfig=self.config, + configItem=self.config.Notify_Enable, + parent=self + ) + self.card_NotifyIfSMTP = SwitchSettingCard( + icon=FluentIcon.INFO, + title="启用SMTP通知", + content="是否启用单独通知的SMTP邮件推送", + qconfig=self.config, + configItem=self.config.Notify_IfSMTP, + parent=self + ) + self.card_NotifySMTP = LineEditSettingCard( + icon=FluentIcon.INFO, + title="SMTP服务器地址", + content="单独通知的SMTP服务器地址", + text="请输入SMTP服务器地址", + qconfig=self.config, + configItem=self.config.Notify_SMTPServerAddress, + parent=self + ) + self.card_NotifyAuthCode = PasswordLineEditSettingCard( + icon=FluentIcon.INFO, + title="授权码", + content="单独通知的邮箱授权码", + text="请输入授权码", + algorithm="AUTO", + qconfig=self.config, + configItem=self.config.Notify_AuthorizationCode, + parent=self + ) + self.card_NotifyFrom = LineEditSettingCard( + icon=FluentIcon.INFO, + title="发件人邮箱", + content="单独通知的发件人邮箱", + text="请输入发件人邮箱", + qconfig=self.config, + configItem=self.config.Notify_FromAddress, + parent=self + ) + self.card_NotifyTo = LineEditSettingCard( + icon=FluentIcon.INFO, + title="收件人邮箱", + content="单独通知的收件人邮箱", + text="请输入收件人邮箱", + qconfig=self.config, + configItem=self.config.Notify_ToAddress, + parent=self + ) + self.card_NotifyIfServerChan = SwitchSettingCard( + icon=FluentIcon.INFO, + title="启用ServerChan", + content="是否启用单独通知的ServerChan推送", + qconfig=self.config, + configItem=self.config.Notify_IfServerChan, + parent=self + ) + self.card_NotifyServerChanKey = LineEditSettingCard( + icon=FluentIcon.INFO, + title="ServerChanKey", + content="单独通知的ServerChan SendKey", + text="请输入ServerChanKey", + qconfig=self.config, + configItem=self.config.Notify_ServerChanKey, + parent=self + ) + self.card_NotifyServerChanChannel = LineEditSettingCard( + icon=FluentIcon.INFO, + title="ServerChanChannel", + content="单独通知的ServerChan Channel", + text="请输入ServerChanChannel", + qconfig=self.config, + configItem=self.config.Notify_ServerChanChannel, + parent=self + ) + self.card_NotifyServerChanTag = LineEditSettingCard( + icon=FluentIcon.INFO, + title="ServerChanTag", + content="单独通知的ServerChan Tag", + text="请输入ServerChanTag", + qconfig=self.config, + configItem=self.config.Notify_ServerChanTag, + parent=self + ) + self.card_NotifyIfCompanyWebHookBot = SwitchSettingCard( + icon=FluentIcon.INFO, + title="启用企业微信机器人", + content="是否启用单独通知的企业微信机器人推送", + qconfig=self.config, + configItem=self.config.Notify_IfCompanyWebHookBot, + parent=self + ) + self.card_NotifyCompanyWebHookBotUrl = LineEditSettingCard( + icon=FluentIcon.INFO, + title="企业微信机器人WebhookUrl", + content="单独通知的企业微信机器人Webhook地址", + text="请输入WebhookUrl", + qconfig=self.config, + configItem=self.config.Notify_CompanyWebHookBotUrl, + parent=self + ) + + # 设置通知卡片默认隐藏 + self.card_NotifyIfSMTP.setVisible(False) + self.card_NotifySMTP.setVisible(False) + self.card_NotifyAuthCode.setVisible(False) + self.card_NotifyFrom.setVisible(False) + self.card_NotifyTo.setVisible(False) + self.card_NotifyIfServerChan.setVisible(False) + self.card_NotifyServerChanKey.setVisible(False) + self.card_NotifyServerChanChannel.setVisible(False) + self.card_NotifyServerChanTag.setVisible(False) + self.card_NotifyIfCompanyWebHookBot.setVisible(False) + self.card_NotifyCompanyWebHookBotUrl.setVisible(False) + + # 连接通知启用开关的信号 + self.card_NotifyEnable.checkedChanged.connect(self.toggle_notify_settings) + self.card_NotifyIfSMTP.checkedChanged.connect(self.toggle_smtp_settings) + + # 根据配置状态初始化显示 + if self.config.get(self.config.Notify_Enable): + self.toggle_notify_settings(True) + if self.config.get(self.config.Notify_IfSMTP): + self.toggle_smtp_settings(True) + if self.config.get(self.config.Notify_IfServerChan): + self.toggle_serverchan_settings(True) + if self.config.get(self.config.Notify_IfCompanyWebHookBot): + self.toggle_webhook_settings(True) + h1_layout = QHBoxLayout() h1_layout.addWidget(self.card_Name) h1_layout.addWidget(self.card_Id) @@ -1566,6 +1700,27 @@ class MemberManager(QWidget): Layout.addLayout(h6_layout) Layout.addLayout(h7_layout) Layout.addLayout(h8_layout) + + # 创建通知设置容器 + notify_container = QWidget() + notify_layout = QVBoxLayout(notify_container) + notify_layout.setContentsMargins(0, 0, 0, 0) + notify_layout.setSpacing(0) + + notify_layout.addWidget(self.card_NotifyEnable) + notify_layout.addWidget(self.card_NotifyIfSMTP) + notify_layout.addWidget(self.card_NotifySMTP) + notify_layout.addWidget(self.card_NotifyAuthCode) + notify_layout.addWidget(self.card_NotifyFrom) + notify_layout.addWidget(self.card_NotifyTo) + notify_layout.addWidget(self.card_NotifyIfServerChan) + notify_layout.addWidget(self.card_NotifyServerChanKey) + notify_layout.addWidget(self.card_NotifyServerChanChannel) + notify_layout.addWidget(self.card_NotifyServerChanTag) + notify_layout.addWidget(self.card_NotifyIfCompanyWebHookBot) + notify_layout.addWidget(self.card_NotifyCompanyWebHookBotUrl) + + Layout.addWidget(notify_container) self.viewLayout.addLayout(Layout) self.viewLayout.setContentsMargins(3, 0, 3, 3) @@ -1588,6 +1743,14 @@ class MemberManager(QWidget): Config.gameid_refreshed.connect(self.refresh_gameid) Config.PASSWORD_refreshed.connect(self.refresh_password) + # 连接ServerChan和企业微信机器人的开关信号 + self.card_NotifyIfServerChan.checkedChanged.connect( + lambda checked: self.toggle_serverchan_settings(checked) + ) + self.card_NotifyIfCompanyWebHookBot.checkedChanged.connect( + lambda checked: self.toggle_webhook_settings(checked) + ) + self.switch_mode() self.switch_infrastructure() @@ -1709,3 +1872,47 @@ class MemberManager(QWidget): } }, ) + + def toggle_notify_settings(self, checked: bool): + """切换通知设置卡片的显示状态""" + self.card_NotifyIfSMTP.setVisible(checked) + self.card_NotifyIfServerChan.setVisible(checked) + self.card_NotifyIfCompanyWebHookBot.setVisible(checked) + + # 根据SMTP开关状态控制相关设置 + if checked and self.config.get(self.config.Notify_IfSMTP): + self.toggle_smtp_settings(True) + else: + self.toggle_smtp_settings(False) + + # 根据ServerChan开关状态控制相关设置 + if checked and self.config.get(self.config.Notify_IfServerChan): + self.toggle_serverchan_settings(True) + else: + self.toggle_serverchan_settings(False) + + # 根据企业微信机器人开关状态控制相关设置 + if checked and self.config.get(self.config.Notify_IfCompanyWebHookBot): + self.toggle_webhook_settings(True) + else: + self.toggle_webhook_settings(False) + + def toggle_smtp_settings(self, checked: bool): + """切换SMTP相关设置的显示状态""" + if self.config.get(self.config.Notify_Enable): + self.card_NotifySMTP.setVisible(checked) + self.card_NotifyAuthCode.setVisible(checked) + self.card_NotifyFrom.setVisible(checked) + self.card_NotifyTo.setVisible(checked) + + def toggle_serverchan_settings(self, checked: bool): + """切换ServerChan相关设置的显示状态""" + if self.config.get(self.config.Notify_Enable): + self.card_NotifyServerChanKey.setVisible(checked) + self.card_NotifyServerChanChannel.setVisible(checked) + self.card_NotifyServerChanTag.setVisible(checked) + + def toggle_webhook_settings(self, checked: bool): + """切换企业微信机器人相关设置的显示状态""" + if self.config.get(self.config.Notify_Enable): + self.card_NotifyCompanyWebHookBotUrl.setVisible(checked) From 34ac0c5ab3af52be6d79e9b795cff07e068206a0 Mon Sep 17 00:00:00 2001 From: DLmaster361 Date: Sun, 18 May 2025 13:54:14 +0800 Subject: [PATCH 12/28] =?UTF-8?q?fix:=20=E4=B8=BB=E5=8A=A8=E5=85=B3?= =?UTF-8?q?=E9=97=AD=E5=89=A9=E4=BD=99=E7=90=86=E6=99=BA=E9=80=89=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/MAA.py | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/app/models/MAA.py b/app/models/MAA.py index f946a61..8d77088 100644 --- a/app/models/MAA.py +++ b/app/models/MAA.py @@ -1397,7 +1397,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小时内过期的理智药 @@ -1495,7 +1497,9 @@ class MaaManager(QObject): ] = "True" # 备选关卡 data["Configurations"]["Default"][ "Fight.UseRemainingSanityStage" - ] = "True" # 使用剩余理智 + ] = ( + "True" if user_data["Info"]["GameId_Remain"] != "-" else "False" + ) # 使用剩余理智 # 基建模式 if ( @@ -1596,6 +1600,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"][ @@ -1604,15 +1617,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" # 开始唤醒 From 0b06b499e42c19f9cc192bd2a8706cd568d53a89 Mon Sep 17 00:00:00 2001 From: DLmaster361 Date: Sun, 18 May 2025 23:35:35 +0800 Subject: [PATCH 13/28] =?UTF-8?q?fix(models):=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E9=9B=B7=E7=94=B5ADB=E7=AB=AF=E5=8F=A3=E5=8F=B7=E7=9B=B8?= =?UTF-8?q?=E5=85=B3=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/MAA.py | 2 +- resources/version.json | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/models/MAA.py b/app/models/MAA.py index 6c0ba9c..e7b1e3c 100644 --- a/app/models/MAA.py +++ b/app/models/MAA.py @@ -906,7 +906,7 @@ class MaaManager(QObject): if "-" in self.ADB_address: ADB_ip = f"{self.ADB_address.split("-")[0]}-" - ADB_port = self.ADB_address.split("-")[1] + ADB_port = int(self.ADB_address.split("-")[1]) elif ":" in self.ADB_address: ADB_ip = f"{self.ADB_address.split(':')[0]}:" diff --git a/resources/version.json b/resources/version.json index 071b9bc..f1dea9d 100644 --- a/resources/version.json +++ b/resources/version.json @@ -4,6 +4,9 @@ "4.3.8.3": { "新增功能": [ "用户仪表盘支持直接控制用户状态" + ], + "修复bug": [ + "修复雷电ADB端口号相关问题" ] }, "4.3.8.2": { From bc5b15cec2afd41aaafa6f18622e64bcf3ab9406 Mon Sep 17 00:00:00 2001 From: aoxuan Date: Mon, 19 May 2025 01:10:20 +0800 Subject: [PATCH 14/28] =?UTF-8?q?feat(notification):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E9=80=9A=E7=9F=A5=E6=9C=8D=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/services/notification.py | 173 +++++++++++++++++++++++------------ 1 file changed, 114 insertions(+), 59 deletions(-) diff --git a/app/services/notification.py b/app/services/notification.py index 9666f9a..d5514c1 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 @@ -315,51 +317,57 @@ class Notification(QWidget): class UserNotification: """用户单独通知服务""" - + def __init__(self, user_config): self.config = user_config - + def send_notification(self, title: str, content: str) -> bool: - """发送用户通知 - - Args: - title: 通知标题 - content: 通知内容 - - Returns: - bool: 是否发送成功 - """ + """发送用户通知""" + logger.info(f"单独通知-准备发送用户通知,标题: {title}") + if not self.config.get(self.config.Notify_Enable): + logger.warning("单独通知-用户通知功能未启用,跳过发送") return False - + success = False - - # 发送邮件通知 + + # 邮件通知 if self._check_smtp_config(): try: self._send_email(title, content) success = True except Exception as e: - logger.error(f"发送邮件通知失败: {str(e)}") - - # 发送ServerChan通知 - if self.config.get(self.config.Notify_IfServerChan) and self._check_serverchan_config(): + logger.error(f"单独通知-发送邮件通知失败: {str(e)}") + + # Server酱通知 + if ( + self.config.get(self.config.Notify_IfServerChan) + and self._check_serverchan_config() + ): try: self._send_serverchan(title, content) success = True except Exception as e: - logger.error(f"发送ServerChan通知失败: {str(e)}") - - # 发送企业微信机器人通知 - if self.config.get(self.config.Notify_IfCompanyWebHookBot) and self._check_webhook_config(): + logger.error(f"单独通知-发送 Server酱 通知失败: {str(e)}") + + # 企业微信群机器人 + if ( + self.config.get(self.config.Notify_IfCompanyWebHookBot) + and self._check_webhook_config() + ): try: self._send_webhook(title, content) success = True except Exception as e: - logger.error(f"发送企业微信机器人通知失败: {str(e)}") - + logger.error(f"单独通知-发送企业微信机器人通知失败: {str(e)}") + + if success: + logger.info("单独通知-用户通知发送完成") + else: + logger.warning("单独通知-所有通知方式均发送失败") + return success - + def _check_smtp_config(self) -> bool: """检查SMTP配置是否完整""" return all([ @@ -369,26 +377,27 @@ class UserNotification: self.config.get(self.config.Notify_FromAddress), self.config.get(self.config.Notify_ToAddress) ]) - + def _check_serverchan_config(self) -> bool: """检查ServerChan配置是否完整""" return bool(self.config.get(self.config.Notify_ServerChanKey)) - + def _check_webhook_config(self) -> bool: """检查企业微信机器人配置是否完整""" return bool(self.config.get(self.config.Notify_CompanyWebHookBotUrl)) - + def _send_email(self, title: str, content: str): """发送邮件通知""" + logger.debug("单独通知-开始发送邮件通知") import smtplib from email.mime.text import MIMEText from email.header import Header - + msg = MIMEText(content, 'plain', 'utf-8') msg['Subject'] = Header(title, 'utf-8') msg['From'] = self.config.get(self.config.Notify_FromAddress) msg['To'] = self.config.get(self.config.Notify_ToAddress) - + server = smtplib.SMTP_SSL(self.config.get(self.config.Notify_SMTPServerAddress)) server.login( self.config.get(self.config.Notify_FromAddress), @@ -396,32 +405,77 @@ class UserNotification: ) server.send_message(msg) server.quit() - + logger.success("单独通知-邮件通知发送成功") + def _send_serverchan(self, title: str, content: str): - """发送ServerChan通知""" + """发送 ServerChan 通知,支持 SCT、SC3、自定义域名等""" + logger.debug("单独通知-开始发送 ServerChan 通知") import requests - + import re + key = self.config.get(self.config.Notify_ServerChanKey) - channel = self.config.get(self.config.Notify_ServerChanChannel) tag = self.config.get(self.config.Notify_ServerChanTag) - - url = f"https://sctapi.ftqq.com/{key}.send" - data = { - "title": title, - "desp": content, - "channel": channel if channel else 9, # 默认使用企业微信通道 - "tag": tag if tag else "" - } - - response = requests.post(url, data=data) - if response.status_code != 200: - raise Exception(f"ServerChan API返回错误: {response.text}") - + channel = self.config.get(self.config.Notify_ServerChanChannel) + + if not key: + raise Exception("ServerChan SendKey 未设置") + + # 1. 构造 URL(支持 sctpN 和 sct 开头的) + if key.startswith("sctp"): + match = re.match(r"^sctp(\d+)t", key) + if match: + url = f"https://{match.group(1)}.push.ft07.com/send/{key}.send" + else: + raise ValueError("SendKey 格式错误,sctp 开头但不符合规范") + else: + url = f"https://sctapi.ftqq.com/{key}.send" + + logger.debug(f"单独通知-Server酱推送URL: {url}") + + # 2. 校验 tag 和 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("|")]) if tag else "" + channels = "|".join([_.strip() for _ in channel.split("|")]) if channel else "" + + options = {} + if is_valid(tags): + options["tags"] = tags + else: + logger.warning("单独通知-ServerChan Tag 格式不正确,已忽略") + + if is_valid(channels): + options["channel"] = channels + else: + logger.warning("单独通知-ServerChan Channel 格式不正确,已忽略") + + # 3. 构造 payload + payload = {"title": title, "desp": content, **options} + + headers = {"Content-Type": "application/json;charset=utf-8"} + logger.info(f"单独通知-发送 Server酱通知: {payload}") + + try: + response = requests.post(url, json=payload, headers=headers, timeout=10) + result = response.json() + + if result.get("code") == 0: + logger.success("Server酱通知推送成功") + else: + raise Exception( + f"推送失败,响应码:{result.get('code')}, 信息:{result}" + ) + except Exception as e: + raise Exception(f"Server酱推送失败: {e}") + def _send_webhook(self, title: str, content: str): """发送企业微信机器人通知""" + logger.debug("单独通知-开始发送企业微信机器人通知") import requests - import json - + url = self.config.get(self.config.Notify_CompanyWebHookBotUrl) data = { "msgtype": "markdown", @@ -429,10 +483,11 @@ class UserNotification: "content": f"### {title}\n{content}" } } - + response = requests.post(url, json=data) if response.status_code != 200: raise Exception(f"企业微信机器人API返回错误: {response.text}") - + logger.success("单独通知-企业微信机器人通知发送成功") + Notify = Notification() From a482087abdbc527fd62936b13bc57f519368054c Mon Sep 17 00:00:00 2001 From: DLmaster361 Date: Mon, 19 May 2025 16:54:14 +0800 Subject: [PATCH 15/28] =?UTF-8?q?feat(ui):=20=E5=88=9D=E6=AD=A5=E5=AE=8C?= =?UTF-8?q?=E6=88=90=E7=94=A8=E6=88=B7=E9=80=9A=E7=9F=A5=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E7=95=8C=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/core/config.py | 39 +++-- app/ui/Widget.py | 38 +++++ app/ui/member_manager.py | 332 ++++++++++++++++----------------------- 3 files changed, 198 insertions(+), 211 deletions(-) diff --git a/app/core/config.py b/app/core/config.py index 6a5fb17..3c5781c 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -234,10 +234,6 @@ class GlobalConfig(LQConfig): 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() @@ -440,18 +436,29 @@ class MaaUserConfig(LQConfig): ) # 新增用户单独通知字段 - self.Notify_Enable = ConfigItem("Notify_Enable", False, False) - self.Notify_IfSMTP = ConfigItem("Notify_IfSMTP", False, False) - self.Notify_SMTPServerAddress = ConfigItem("Notify_SMTPServerAddress", "", "") - self.Notify_AuthorizationCode = ConfigItem("Notify_AuthorizationCode", "", "") - self.Notify_FromAddress = ConfigItem("Notify_FromAddress", "", "") - self.Notify_ToAddress = ConfigItem("Notify_ToAddress", "", "") - self.Notify_IfServerChan = ConfigItem("Notify_IfServerChan", False, False) - 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, False) - self.Notify_CompanyWebHookBotUrl = ConfigItem("Notify_CompanyWebHookBotUrl", "", "") + 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", "" + ) class MaaPlanConfig(LQConfig): diff --git a/app/ui/Widget.py b/app/ui/Widget.py index 55b3c1d..0e9de98 100644 --- a/app/ui/Widget.py +++ b/app/ui/Widget.py @@ -191,6 +191,44 @@ class ProgressRingMessageBox(MessageBoxBase): self.timer.deleteLater() +class SettingMessageBox(MessageBoxBase): + """设置二级菜单对话框""" + + def __init__( + self, + parent, + title: str, + setting_cards: List[Union[SettingCard, HeaderCardWidget]], + ): + super().__init__(parent) + + self.title = SubtitleLabel(title) + self.button_yes = PrimaryPushButton("确认", self) + self.v_layout = QVBoxLayout() + self.v_layout.addStretch() + self.v_layout.addWidget(self.button_yes) + + self.buttonGroup.hide() + + 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) + for setting_card in setting_cards: + content_layout.addWidget(setting_card) + scrollArea.setWidget(content_widget) + + # 将组件添加到布局中 + self.viewLayout.addWidget(self.title) + self.viewLayout.addWidget(scrollArea) + self.viewLayout.addLayout(self.v_layout) + + self.button_yes.clicked.connect(self.yesButton.click) + + class NoticeMessageBox(MessageBoxBase): """公告对话框""" diff --git a/app/ui/member_manager.py b/app/ui/member_manager.py index 71fc30f..f2f75e6 100644 --- a/app/ui/member_manager.py +++ b/app/ui/member_manager.py @@ -64,6 +64,7 @@ from .Widget import ( LineEditSettingCard, SpinBoxSettingCard, ComboBoxMessageBox, + SettingMessageBox, EditableComboBoxSettingCard, PasswordLineEditSettingCard, UserLableSettingCard, @@ -1530,138 +1531,32 @@ class MemberManager(QWidget): ) # 新增单独通知卡片 - self.card_NotifyEnable = SwitchSettingCard( - icon=FluentIcon.INFO, - title="启用单独通知", - content="启用后,任务结束将向该用户单独推送通知", + self.card_NotifySet = PushAndSwitchButtonSettingCard( + icon=FluentIcon.MAIL, + title="用户单独通知设置", + content="", + text="设置", qconfig=self.config, - configItem=self.config.Notify_Enable, - parent=self + configItem=self.config.Notify_Enabled, + parent=self, ) - self.card_NotifyIfSMTP = SwitchSettingCard( - icon=FluentIcon.INFO, - title="启用SMTP通知", - content="是否启用单独通知的SMTP邮件推送", - qconfig=self.config, - configItem=self.config.Notify_IfSMTP, - parent=self + self.card_EMail = self.EMailSettingCard(self.config, self) + self.card_ServerChan = self.ServerChanSettingCard( + self.config, self ) - self.card_NotifySMTP = LineEditSettingCard( - icon=FluentIcon.INFO, - title="SMTP服务器地址", - content="单独通知的SMTP服务器地址", - text="请输入SMTP服务器地址", - qconfig=self.config, - configItem=self.config.Notify_SMTPServerAddress, - parent=self - ) - self.card_NotifyAuthCode = PasswordLineEditSettingCard( - icon=FluentIcon.INFO, - title="授权码", - content="单独通知的邮箱授权码", - text="请输入授权码", - algorithm="AUTO", - qconfig=self.config, - configItem=self.config.Notify_AuthorizationCode, - parent=self - ) - self.card_NotifyFrom = LineEditSettingCard( - icon=FluentIcon.INFO, - title="发件人邮箱", - content="单独通知的发件人邮箱", - text="请输入发件人邮箱", - qconfig=self.config, - configItem=self.config.Notify_FromAddress, - parent=self - ) - self.card_NotifyTo = LineEditSettingCard( - icon=FluentIcon.INFO, - title="收件人邮箱", - content="单独通知的收件人邮箱", - text="请输入收件人邮箱", - qconfig=self.config, - configItem=self.config.Notify_ToAddress, - parent=self - ) - self.card_NotifyIfServerChan = SwitchSettingCard( - icon=FluentIcon.INFO, - title="启用ServerChan", - content="是否启用单独通知的ServerChan推送", - qconfig=self.config, - configItem=self.config.Notify_IfServerChan, - parent=self - ) - self.card_NotifyServerChanKey = LineEditSettingCard( - icon=FluentIcon.INFO, - title="ServerChanKey", - content="单独通知的ServerChan SendKey", - text="请输入ServerChanKey", - qconfig=self.config, - configItem=self.config.Notify_ServerChanKey, - parent=self - ) - self.card_NotifyServerChanChannel = LineEditSettingCard( - icon=FluentIcon.INFO, - title="ServerChanChannel", - content="单独通知的ServerChan Channel", - text="请输入ServerChanChannel", - qconfig=self.config, - configItem=self.config.Notify_ServerChanChannel, - parent=self - ) - self.card_NotifyServerChanTag = LineEditSettingCard( - icon=FluentIcon.INFO, - title="ServerChanTag", - content="单独通知的ServerChan Tag", - text="请输入ServerChanTag", - qconfig=self.config, - configItem=self.config.Notify_ServerChanTag, - parent=self - ) - self.card_NotifyIfCompanyWebHookBot = SwitchSettingCard( - icon=FluentIcon.INFO, - title="启用企业微信机器人", - content="是否启用单独通知的企业微信机器人推送", - qconfig=self.config, - configItem=self.config.Notify_IfCompanyWebHookBot, - parent=self - ) - self.card_NotifyCompanyWebHookBotUrl = LineEditSettingCard( - icon=FluentIcon.INFO, - title="企业微信机器人WebhookUrl", - content="单独通知的企业微信机器人Webhook地址", - text="请输入WebhookUrl", - qconfig=self.config, - configItem=self.config.Notify_CompanyWebHookBotUrl, - parent=self + self.card_CompanyWebhookBot = ( + self.CompanyWechatPushSettingCard(self.config, self) ) - # 设置通知卡片默认隐藏 - self.card_NotifyIfSMTP.setVisible(False) - self.card_NotifySMTP.setVisible(False) - self.card_NotifyAuthCode.setVisible(False) - self.card_NotifyFrom.setVisible(False) - self.card_NotifyTo.setVisible(False) - self.card_NotifyIfServerChan.setVisible(False) - self.card_NotifyServerChanKey.setVisible(False) - self.card_NotifyServerChanChannel.setVisible(False) - self.card_NotifyServerChanTag.setVisible(False) - self.card_NotifyIfCompanyWebHookBot.setVisible(False) - self.card_NotifyCompanyWebHookBotUrl.setVisible(False) + self.card_NotifySet_list = [ + self.card_EMail, + self.card_ServerChan, + self.card_CompanyWebhookBot, + ] - # 连接通知启用开关的信号 - self.card_NotifyEnable.checkedChanged.connect(self.toggle_notify_settings) - self.card_NotifyIfSMTP.checkedChanged.connect(self.toggle_smtp_settings) - - # 根据配置状态初始化显示 - if self.config.get(self.config.Notify_Enable): - self.toggle_notify_settings(True) - if self.config.get(self.config.Notify_IfSMTP): - self.toggle_smtp_settings(True) - if self.config.get(self.config.Notify_IfServerChan): - self.toggle_serverchan_settings(True) - if self.config.get(self.config.Notify_IfCompanyWebHookBot): - self.toggle_webhook_settings(True) + self.NotifySetCard = SettingMessageBox( + self.window(), "用户通知设置", self.card_NotifySet_list + ) h1_layout = QHBoxLayout() h1_layout.addWidget(self.card_Name) @@ -1700,27 +1595,7 @@ class MemberManager(QWidget): Layout.addLayout(h6_layout) Layout.addLayout(h7_layout) Layout.addLayout(h8_layout) - - # 创建通知设置容器 - notify_container = QWidget() - notify_layout = QVBoxLayout(notify_container) - notify_layout.setContentsMargins(0, 0, 0, 0) - notify_layout.setSpacing(0) - - notify_layout.addWidget(self.card_NotifyEnable) - notify_layout.addWidget(self.card_NotifyIfSMTP) - notify_layout.addWidget(self.card_NotifySMTP) - notify_layout.addWidget(self.card_NotifyAuthCode) - notify_layout.addWidget(self.card_NotifyFrom) - notify_layout.addWidget(self.card_NotifyTo) - notify_layout.addWidget(self.card_NotifyIfServerChan) - notify_layout.addWidget(self.card_NotifyServerChanKey) - notify_layout.addWidget(self.card_NotifyServerChanChannel) - notify_layout.addWidget(self.card_NotifyServerChanTag) - notify_layout.addWidget(self.card_NotifyIfCompanyWebHookBot) - notify_layout.addWidget(self.card_NotifyCompanyWebHookBotUrl) - - Layout.addWidget(notify_container) + Layout.addWidget(self.card_NotifySet) self.viewLayout.addLayout(Layout) self.viewLayout.setContentsMargins(3, 0, 3, 3) @@ -1740,17 +1615,12 @@ class MemberManager(QWidget): self.card_InfrastMode.clicked.connect( self.set_infrastructure ) + self.card_NotifySet.clicked.connect( + self.NotifySetCard.exec_ + ) Config.gameid_refreshed.connect(self.refresh_gameid) Config.PASSWORD_refreshed.connect(self.refresh_password) - # 连接ServerChan和企业微信机器人的开关信号 - self.card_NotifyIfServerChan.checkedChanged.connect( - lambda checked: self.toggle_serverchan_settings(checked) - ) - self.card_NotifyIfCompanyWebHookBot.checkedChanged.connect( - lambda checked: self.toggle_webhook_settings(checked) - ) - self.switch_mode() self.switch_infrastructure() @@ -1873,46 +1743,118 @@ class MemberManager(QWidget): }, ) - def toggle_notify_settings(self, checked: bool): - """切换通知设置卡片的显示状态""" - self.card_NotifyIfSMTP.setVisible(checked) - self.card_NotifyIfServerChan.setVisible(checked) - self.card_NotifyIfCompanyWebHookBot.setVisible(checked) - - # 根据SMTP开关状态控制相关设置 - if checked and self.config.get(self.config.Notify_IfSMTP): - self.toggle_smtp_settings(True) - else: - self.toggle_smtp_settings(False) - - # 根据ServerChan开关状态控制相关设置 - if checked and self.config.get(self.config.Notify_IfServerChan): - self.toggle_serverchan_settings(True) - else: - self.toggle_serverchan_settings(False) - - # 根据企业微信机器人开关状态控制相关设置 - if checked and self.config.get(self.config.Notify_IfCompanyWebHookBot): - self.toggle_webhook_settings(True) - else: - self.toggle_webhook_settings(False) + class EMailSettingCard(HeaderCardWidget): - def toggle_smtp_settings(self, checked: bool): - """切换SMTP相关设置的显示状态""" - if self.config.get(self.config.Notify_Enable): - self.card_NotifySMTP.setVisible(checked) - self.card_NotifyAuthCode.setVisible(checked) - self.card_NotifyFrom.setVisible(checked) - self.card_NotifyTo.setVisible(checked) + def __init__(self, config: MaaUserConfig, parent=None): + super().__init__(parent) + self.setTitle("用户邮箱通知") - def toggle_serverchan_settings(self, checked: bool): - """切换ServerChan相关设置的显示状态""" - if self.config.get(self.config.Notify_Enable): - self.card_NotifyServerChanKey.setVisible(checked) - self.card_NotifyServerChanChannel.setVisible(checked) - self.card_NotifyServerChanTag.setVisible(checked) + self.config = config - def toggle_webhook_settings(self, checked: bool): - """切换企业微信机器人相关设置的显示状态""" - if self.config.get(self.config.Notify_Enable): - self.card_NotifyCompanyWebHookBotUrl.setVisible(checked) + 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.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.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_IfCompanyWechat = 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_IfCompanyWechat) + Layout.addWidget(self.card_CompanyWebHookBotUrl) + self.viewLayout.addLayout(Layout) + self.viewLayout.setContentsMargins(3, 0, 3, 3) From 1641e32e3dcdfb93c8b302ba3bc71b77260a6a8d Mon Sep 17 00:00:00 2001 From: DLmaster361 Date: Mon, 19 May 2025 17:31:50 +0800 Subject: [PATCH 16/28] =?UTF-8?q?feat(ui):=20=E5=AE=8C=E6=88=90=E5=AE=8C?= =?UTF-8?q?=E6=95=B4=E7=94=A8=E6=88=B7=E9=80=9A=E7=9F=A5=E5=AD=90=E8=8F=9C?= =?UTF-8?q?=E5=8D=95=E8=AE=BE=E8=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/ui/member_manager.py | 81 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 77 insertions(+), 4 deletions(-) diff --git a/app/ui/member_manager.py b/app/ui/member_manager.py index f2f75e6..b8378e9 100644 --- a/app/ui/member_manager.py +++ b/app/ui/member_manager.py @@ -1534,12 +1534,15 @@ class MemberManager(QWidget): self.card_NotifySet = PushAndSwitchButtonSettingCard( icon=FluentIcon.MAIL, title="用户单独通知设置", - content="", + content="未启用任何通知项", text="设置", qconfig=self.config, configItem=self.config.Notify_Enabled, 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 @@ -1549,6 +1552,7 @@ class MemberManager(QWidget): ) self.card_NotifySet_list = [ + self.card_NotifyContent, self.card_EMail, self.card_ServerChan, self.card_CompanyWebhookBot, @@ -1615,14 +1619,13 @@ class MemberManager(QWidget): self.card_InfrastMode.clicked.connect( self.set_infrastructure ) - self.card_NotifySet.clicked.connect( - self.NotifySetCard.exec_ - ) + self.card_NotifySet.clicked.connect(self.set_notify) Config.gameid_refreshed.connect(self.refresh_gameid) Config.PASSWORD_refreshed.connect(self.refresh_password) self.switch_mode() self.switch_infrastructure() + self.set_notify(if_show=False) def switch_mode(self) -> None: @@ -1743,6 +1746,76 @@ class MemberManager(QWidget): }, ) + def set_notify(self, if_show: bool = True) -> None: + """设置用户通知相关配置""" + + def short_str(s: str) -> str: + if len(s) <= 10: + return s + return s[:7] + "..." + + if if_show: + self.NotifySetCard.exec_() + + content_list = [] + + if not ( + self.config.get(self.config.Notify_IfSendStatistic) + or self.config.get(self.config.Notify_IfSendSixStar) + ): + content_list.append("未启用任何通知项") + + if self.config.get(self.config.Notify_IfSendStatistic): + content_list.append("统计信息已启用") + if self.config.get(self.config.Notify_IfSendSixStar): + content_list.append("六星喜报已启用") + + if self.config.get(self.config.Notify_IfSendMail): + content_list.append( + f"邮箱通知:{short_str(self.config.get(self.config.Notify_ToAddress))}" + ) + if self.config.get(self.config.Notify_IfServerChan): + content_list.append( + f"Server酱通知:{short_str(self.config.get(self.config.Notify_ServerChanKey))}" + ) + if self.config.get(self.config.Notify_IfCompanyWebHookBot): + content_list.append( + f"企业微信通知:{short_str(self.config.get(self.config.Notify_CompanyWebHookBotUrl))}" + ) + + self.card_NotifySet.setContent(" | ".join(content_list)) + + 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.setContentsMargins(3, 0, 3, 3) + class EMailSettingCard(HeaderCardWidget): def __init__(self, config: MaaUserConfig, parent=None): From 59ff9bf818865edd027c208029f4e08759d8b9fb Mon Sep 17 00:00:00 2001 From: aoxuan Date: Tue, 20 May 2025 02:11:54 +0800 Subject: [PATCH 17/28] =?UTF-8?q?refactor(notification):=20=E9=87=8D?= =?UTF-8?q?=E6=9E=84=E9=80=9A=E7=9F=A5=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/MAA.py | 82 +++++++------- app/services/notification.py | 211 ++++------------------------------- 2 files changed, 63 insertions(+), 230 deletions(-) diff --git a/app/models/MAA.py b/app/models/MAA.py index e03c18b..a2bd23e 100644 --- a/app/models/MAA.py +++ b/app/models/MAA.py @@ -40,7 +40,6 @@ from typing import Union, List, Dict from app.core import Config, MaaConfig, MaaUserConfig from app.services import Notify, System -from app.services.notification import Notification, UserNotification class MaaManager(QObject): @@ -1773,21 +1772,22 @@ class MaaManager(QObject): 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 敬上") + Notify.send_mail("网页", title, message_html,Config.get(Config.notify_ToAddress)) + serverchan_message = message_text.replace("\n", "\n\n") + Notify.ServerChanPush(title, f"{serverchan_message}\n\nAUTO_MAA 敬上",Config.get(Config.notify_ServerChanKey),Config.get(Config.notify_ServerChanTag),Config.get(Config.notify_ServerChanChannel)) + Notify.CompanyWebHookBotPush(title, f"{message_text}\n\nAUTO_MAA 敬上",Config.get(Config.notify_CompanyWebHookBotUrl)) - # 发送用户单独通知 - for user_name in message["failed_user"].split("、") + message["waiting_user"].split("、"): - if not user_name: # 跳过空字符串 - continue - user_config = Config.member_dict.get(user_name) - if user_config and user_config.get(user_config.Notify_Enable): - user_notify = UserNotification(user_config) - user_notify.send_notification( - f"{self.mode[2:4]}任务未完成通知", - f"您的{self.mode[2:4]}任务未完成,请检查相关设置。\n\n{message_text}" - ) + # # 发送用户单独通知 + # for user_name in message["failed_user"].split("、") + message["waiting_user"].split("、"): + # if not user_name: # 跳过空字符串 + # continue + # user_config = Config.member_dict.get(user_name) + # if user_config and user_config.get(user_config.Notify_Enable): + # user_notify = UserNotification(user_config) + # user_notify.send_notification( + # f"{self.mode[2:4]}任务未完成通知", + # f"您的{self.mode[2:4]}任务未完成,请检查相关设置。\n\n{message_text}" + # ) return message_text @@ -1818,22 +1818,22 @@ class MaaManager(QObject): message_html = template.render(message) # 发送全局通知 - Notify.send_mail("网页", title, message_html) + Notify.send_mail("网页", title, message_html,Config.get(Config.notify_ToAddress)) # 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 敬上") + Notify.ServerChanPush(title, f"{serverchan_message}\n\nAUTO_MAA 敬上",Config.get(Config.notify_ServerChanKey),Config.get(Config.notify_ServerChanTag),Config.get(Config.notify_ServerChanChannel)) + Notify.CompanyWebHookBotPush(title, f"{message_text}\n\nAUTO_MAA 敬上",Config.get(Config.notify_CompanyWebHookBotUrl)) - # 发送用户单独通知 - user_name = message.get("user_info") - if user_name: - user_config = Config.member_dict.get(user_name) - if user_config and user_config.get(user_config.Notify_Enable): - user_notify = UserNotification(user_config) - user_notify.send_notification( - f"{self.mode[2:4]}任务统计报告", - f"您的{self.mode[2:4]}任务统计报告如下:\n\n{message_text}" - ) + # # 发送用户单独通知 + # user_name = message.get("user_info") + # if user_name: + # user_config = Config.member_dict.get(user_name) + # if user_config and user_config.get(user_config.Notify_Enable): + # user_notify = UserNotification(user_config) + # user_notify.send_notification( + # f"{self.mode[2:4]}任务统计报告", + # f"您的{self.mode[2:4]}任务统计报告如下:\n\n{message_text}" + # ) elif mode == "公招六星": # 生成HTML通知内容 @@ -1841,17 +1841,17 @@ class MaaManager(QObject): message_html = template.render(message) # 发送全局通知 - Notify.send_mail("网页", title, message_html) - Notify.ServerChanPush(title, "好羡慕~\n\nAUTO_MAA 敬上") - Notify.CompanyWebHookBotPush(title, "好羡慕~\n\nAUTO_MAA 敬上") + Notify.send_mail("网页", title, message_html,Config.get(Config.notify_ToAddress)) + Notify.ServerChanPush(title, "好羡慕~\n\nAUTO_MAA 敬上",Config.get(Config.notify_ServerChanKey),Config.get(Config.notify_ServerChanTag),Config.get(Config.notify_ServerChanChannel)) + Notify.CompanyWebHookBotPush(title, "好羡慕~\n\nAUTO_MAA 敬上",Config.get(Config.notify_CompanyWebHookBotUrl)) - # 发送用户单独通知 - user_name = message.get("user_name") - if user_name: - user_config = Config.member_dict.get(user_name) - if user_config and user_config.get(user_config.Notify_Enable): - user_notify = UserNotification(user_config) - user_notify.send_notification( - "公招六星通知", - "恭喜您在公招中获得了六星干员!" - ) + # # 发送用户单独通知 + # user_name = message.get("user_name") + # if user_name: + # user_config = Config.member_dict.get(user_name) + # if user_config and user_config.get(user_config.Notify_Enable): + # user_notify = UserNotification(user_config) + # user_notify.send_notification( + # "公招六星通知", + # "恭喜您在公招中获得了六星干员!" + # ) diff --git a/app/services/notification.py b/app/services/notification.py index d5514c1..639e3ac 100644 --- a/app/services/notification.py +++ b/app/services/notification.py @@ -67,7 +67,7 @@ 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): @@ -84,7 +84,7 @@ class Notification(QWidget): or not bool( re.match( r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$", - Config.get(Config.notify_ToAddress), + to_address, ) ) ): @@ -114,7 +114,7 @@ class Notification(QWidget): message["To"] = formataddr( ( Header("AUTO_MAA用户", "utf-8").encode(), - Config.get(Config.notify_ToAddress), + to_address, ) ) # 收件人显示的名字 message["Subject"] = Header(title, "utf-8") @@ -132,20 +132,21 @@ class Notification(QWidget): ) smtpObj.sendmail( Config.get(Config.notify_FromAddress), - Config.get(Config.notify_ToAddress), + 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): - """使用Server酱推送通知(支持 tag 和 channel,避免使用SDK)""" + def ServerChanPush(self, title, content, send_key, tag, channel): + """使用Server酱推送通知""" 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( @@ -173,11 +174,11 @@ class Notification(QWidget): tags = "|".join( _.strip() - for _ in Config.get(Config.notify_ServerChanTag).split("|") + for _ in tag.split("|") ) channels = "|".join( _.strip() - for _ in Config.get(Config.notify_ServerChanChannel).split("|") + for _ in channel.split("|") ) options = {} @@ -227,12 +228,13 @@ class Notification(QWidget): "error", "Server酱通知推送异常", f"请检查相关设置,如还有问题可联系开发者", -1 ) return f"Server酱通知推送异常:{str(e)}" + return None - def CompanyWebHookBotPush(self, title, content): + def CompanyWebHookBotPush(self, title, content,webhook_url): """使用企业微信群机器人推送通知""" if Config.get(Config.notify_IfCompanyWebHookBot): - if Config.get(Config.notify_CompanyWebHookBotUrl) == "": + if webhook_url == "": logger.error("请正确设置企业微信群机器人的WebHook地址") self.push_info_bar.emit( "error", @@ -244,11 +246,11 @@ class Notification(QWidget): 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), + url=webhook_url, json=data, timeout=10, ) @@ -279,6 +281,7 @@ class Notification(QWidget): -1, ) return f'使用企业微信群机器人推送通知时出错:{info["errmsg"]}' + return None def send_test_notification(self): """发送测试通知到所有已启用的通知渠道""" @@ -296,6 +299,7 @@ class Notification(QWidget): "文本", "AUTO_MAA测试通知", "这是 AUTO_MAA 外部通知测试信息。如果你看到了这段内容,说明 AUTO_MAA 的通知功能已经正确配置且可以正常工作!", + Config.get(Config.notify_ToAddress), ) # 发送Server酱通知 @@ -303,6 +307,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), ) # 发送企业微信机器人通知 @@ -310,184 +317,10 @@ class Notification(QWidget): self.CompanyWebHookBotPush( "AUTO_MAA测试通知", "这是 AUTO_MAA 外部通知测试信息。如果你看到了这段内容,说明 AUTO_MAA 的通知功能已经正确配置且可以正常工作!", + Config.get(Config.notify_CompanyWebHookBotUrl), ) return True -class UserNotification: - """用户单独通知服务""" - - def __init__(self, user_config): - self.config = user_config - - def send_notification(self, title: str, content: str) -> bool: - """发送用户通知""" - logger.info(f"单独通知-准备发送用户通知,标题: {title}") - - if not self.config.get(self.config.Notify_Enable): - logger.warning("单独通知-用户通知功能未启用,跳过发送") - return False - - success = False - - # 邮件通知 - if self._check_smtp_config(): - try: - self._send_email(title, content) - success = True - except Exception as e: - logger.error(f"单独通知-发送邮件通知失败: {str(e)}") - - # Server酱通知 - if ( - self.config.get(self.config.Notify_IfServerChan) - and self._check_serverchan_config() - ): - try: - self._send_serverchan(title, content) - success = True - except Exception as e: - logger.error(f"单独通知-发送 Server酱 通知失败: {str(e)}") - - # 企业微信群机器人 - if ( - self.config.get(self.config.Notify_IfCompanyWebHookBot) - and self._check_webhook_config() - ): - try: - self._send_webhook(title, content) - success = True - except Exception as e: - logger.error(f"单独通知-发送企业微信机器人通知失败: {str(e)}") - - if success: - logger.info("单独通知-用户通知发送完成") - else: - logger.warning("单独通知-所有通知方式均发送失败") - - return success - - def _check_smtp_config(self) -> bool: - """检查SMTP配置是否完整""" - return all([ - self.config.get(self.config.Notify_IfSMTP), - self.config.get(self.config.Notify_SMTPServerAddress), - self.config.get(self.config.Notify_AuthorizationCode), - self.config.get(self.config.Notify_FromAddress), - self.config.get(self.config.Notify_ToAddress) - ]) - - def _check_serverchan_config(self) -> bool: - """检查ServerChan配置是否完整""" - return bool(self.config.get(self.config.Notify_ServerChanKey)) - - def _check_webhook_config(self) -> bool: - """检查企业微信机器人配置是否完整""" - return bool(self.config.get(self.config.Notify_CompanyWebHookBotUrl)) - - def _send_email(self, title: str, content: str): - """发送邮件通知""" - logger.debug("单独通知-开始发送邮件通知") - import smtplib - from email.mime.text import MIMEText - from email.header import Header - - msg = MIMEText(content, 'plain', 'utf-8') - msg['Subject'] = Header(title, 'utf-8') - msg['From'] = self.config.get(self.config.Notify_FromAddress) - msg['To'] = self.config.get(self.config.Notify_ToAddress) - - server = smtplib.SMTP_SSL(self.config.get(self.config.Notify_SMTPServerAddress)) - server.login( - self.config.get(self.config.Notify_FromAddress), - self.config.get(self.config.Notify_AuthorizationCode) - ) - server.send_message(msg) - server.quit() - logger.success("单独通知-邮件通知发送成功") - - def _send_serverchan(self, title: str, content: str): - """发送 ServerChan 通知,支持 SCT、SC3、自定义域名等""" - logger.debug("单独通知-开始发送 ServerChan 通知") - import requests - import re - - key = self.config.get(self.config.Notify_ServerChanKey) - tag = self.config.get(self.config.Notify_ServerChanTag) - channel = self.config.get(self.config.Notify_ServerChanChannel) - - if not key: - raise Exception("ServerChan SendKey 未设置") - - # 1. 构造 URL(支持 sctpN 和 sct 开头的) - if key.startswith("sctp"): - match = re.match(r"^sctp(\d+)t", key) - if match: - url = f"https://{match.group(1)}.push.ft07.com/send/{key}.send" - else: - raise ValueError("SendKey 格式错误,sctp 开头但不符合规范") - else: - url = f"https://sctapi.ftqq.com/{key}.send" - - logger.debug(f"单独通知-Server酱推送URL: {url}") - - # 2. 校验 tag 和 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("|")]) if tag else "" - channels = "|".join([_.strip() for _ in channel.split("|")]) if channel else "" - - options = {} - if is_valid(tags): - options["tags"] = tags - else: - logger.warning("单独通知-ServerChan Tag 格式不正确,已忽略") - - if is_valid(channels): - options["channel"] = channels - else: - logger.warning("单独通知-ServerChan Channel 格式不正确,已忽略") - - # 3. 构造 payload - payload = {"title": title, "desp": content, **options} - - headers = {"Content-Type": "application/json;charset=utf-8"} - logger.info(f"单独通知-发送 Server酱通知: {payload}") - - try: - response = requests.post(url, json=payload, headers=headers, timeout=10) - result = response.json() - - if result.get("code") == 0: - logger.success("Server酱通知推送成功") - else: - raise Exception( - f"推送失败,响应码:{result.get('code')}, 信息:{result}" - ) - except Exception as e: - raise Exception(f"Server酱推送失败: {e}") - - def _send_webhook(self, title: str, content: str): - """发送企业微信机器人通知""" - logger.debug("单独通知-开始发送企业微信机器人通知") - import requests - - url = self.config.get(self.config.Notify_CompanyWebHookBotUrl) - data = { - "msgtype": "markdown", - "markdown": { - "content": f"### {title}\n{content}" - } - } - - response = requests.post(url, json=data) - if response.status_code != 200: - raise Exception(f"企业微信机器人API返回错误: {response.text}") - logger.success("单独通知-企业微信机器人通知发送成功") - - Notify = Notification() From 86df9e7a50a44001027cc0cbbd65bff5e992fec8 Mon Sep 17 00:00:00 2001 From: DLmaster361 Date: Wed, 21 May 2025 01:00:38 +0800 Subject: [PATCH 18/28] =?UTF-8?q?chore(ui):=20=E4=BC=98=E5=8C=96=E4=BA=8C?= =?UTF-8?q?=E7=BA=A7=E8=8F=9C=E5=8D=95=E6=98=BE=E7=A4=BA=E6=95=88=E6=9E=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/ui/Widget.py | 138 +++++++++++++++++++++++++++----------- app/ui/dispatch_center.py | 22 +++--- app/ui/history.py | 11 +-- app/ui/home.py | 22 +++--- app/ui/member_manager.py | 100 ++++++++++++--------------- app/ui/queue_manager.py | 20 +++--- app/ui/setting.py | 10 +-- 7 files changed, 182 insertions(+), 141 deletions(-) diff --git a/app/ui/Widget.py b/app/ui/Widget.py index 0e9de98..4aa98be 100644 --- a/app/ui/Widget.py +++ b/app/ui/Widget.py @@ -74,6 +74,7 @@ from qfluentwidgets import ( ScrollArea, Pivot, PivotItem, + FlyoutViewBase, ) from qfluentwidgets.common.overload import singledispatchmethod import os @@ -191,44 +192,6 @@ class ProgressRingMessageBox(MessageBoxBase): self.timer.deleteLater() -class SettingMessageBox(MessageBoxBase): - """设置二级菜单对话框""" - - def __init__( - self, - parent, - title: str, - setting_cards: List[Union[SettingCard, HeaderCardWidget]], - ): - super().__init__(parent) - - self.title = SubtitleLabel(title) - self.button_yes = PrimaryPushButton("确认", self) - self.v_layout = QVBoxLayout() - self.v_layout.addStretch() - self.v_layout.addWidget(self.button_yes) - - self.buttonGroup.hide() - - 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) - for setting_card in setting_cards: - content_layout.addWidget(setting_card) - scrollArea.setWidget(content_widget) - - # 将组件添加到布局中 - self.viewLayout.addWidget(self.title) - self.viewLayout.addWidget(scrollArea) - self.viewLayout.addLayout(self.v_layout) - - self.button_yes.clicked.connect(self.yesButton.click) - - class NoticeMessageBox(MessageBoxBase): """公告对话框""" @@ -309,6 +272,39 @@ 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) + + class SwitchSettingCard(SettingCard): """Setting card with switch button""" @@ -971,6 +967,72 @@ class TimeEditSettingCard(SettingCard): self.TimeEdit.setTime(QTime.fromString(value, "HH:mm")) +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 len(s) <= 10: + return s + return s[:10] + "..." + + content_list = [] + + if self.configItems: + + if not ( + self.qconfig.get(self.configItems["IfSendStatistic"]) + or self.qconfig.get(self.configItems["IfSendSixStar"]) + ): + content_list.append("未启用任何通知项") + + if self.qconfig.get(self.configItems["IfSendStatistic"]): + content_list.append("统计信息已启用") + if self.qconfig.get(self.configItems["IfSendSixStar"]): + content_list.append("六星喜报已启用") + + if self.qconfig.get(self.configItems["IfSendMail"]): + content_list.append( + f"邮箱通知:{short_str(self.qconfig.get(self.configItems["ToAddress"]))}" + ) + if self.qconfig.get(self.configItems["IfServerChan"]): + content_list.append( + f"Server酱通知:{short_str(self.qconfig.get(self.configItems["ServerChanKey"]))}" + ) + if self.qconfig.get(self.configItems["IfCompanyWebHookBot"]): + content_list.append( + f"企业微信通知:{short_str(self.qconfig.get(self.configItems["CompanyWebHookBotUrl"]))}" + ) + + self.setContent(" | ".join(content_list)) + + class StatusSwitchSetting(SwitchButton): def __init__( 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/member_manager.py b/app/ui/member_manager.py index b8378e9..4e96e69 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,7 +66,7 @@ from .Widget import ( LineEditSettingCard, SpinBoxSettingCard, ComboBoxMessageBox, - SettingMessageBox, + SettingFlyoutView, EditableComboBoxSettingCard, PasswordLineEditSettingCard, UserLableSettingCard, @@ -73,6 +75,7 @@ from .Widget import ( PushAndSwitchButtonSettingCard, PushAndComboBoxSettingCard, StatusSwitchSetting, + UserNoticeSettingCard, PivotArea, ) @@ -565,29 +568,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): @@ -1531,13 +1530,23 @@ class MemberManager(QWidget): ) # 新增单独通知卡片 - self.card_NotifySet = PushAndSwitchButtonSettingCard( + 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( @@ -1558,9 +1567,10 @@ class MemberManager(QWidget): self.card_CompanyWebhookBot, ] - self.NotifySetCard = SettingMessageBox( - self.window(), "用户通知设置", self.card_NotifySet_list + self.NotifySetCard = SettingFlyoutView( + self, "用户通知设置", self.card_NotifySet_list ) + self.NotifySetCard.setVisible(False) h1_layout = QHBoxLayout() h1_layout.addWidget(self.card_Name) @@ -1625,7 +1635,6 @@ class MemberManager(QWidget): self.switch_mode() self.switch_infrastructure() - self.set_notify(if_show=False) def switch_mode(self) -> None: @@ -1746,44 +1755,17 @@ class MemberManager(QWidget): }, ) - def set_notify(self, if_show: bool = True) -> None: + def set_notify(self) -> None: """设置用户通知相关配置""" - def short_str(s: str) -> str: - if len(s) <= 10: - return s - return s[:7] + "..." - - if if_show: - self.NotifySetCard.exec_() - - content_list = [] - - if not ( - self.config.get(self.config.Notify_IfSendStatistic) - or self.config.get(self.config.Notify_IfSendSixStar) - ): - content_list.append("未启用任何通知项") - - if self.config.get(self.config.Notify_IfSendStatistic): - content_list.append("统计信息已启用") - if self.config.get(self.config.Notify_IfSendSixStar): - content_list.append("六星喜报已启用") - - if self.config.get(self.config.Notify_IfSendMail): - content_list.append( - f"邮箱通知:{short_str(self.config.get(self.config.Notify_ToAddress))}" - ) - if self.config.get(self.config.Notify_IfServerChan): - content_list.append( - f"Server酱通知:{short_str(self.config.get(self.config.Notify_ServerChanKey))}" - ) - if self.config.get(self.config.Notify_IfCompanyWebHookBot): - content_list.append( - f"企业微信通知:{short_str(self.config.get(self.config.Notify_CompanyWebHookBotUrl))}" - ) - - self.card_NotifySet.setContent(" | ".join(content_list)) + self.NotifySetCard.setVisible(True) + Flyout.make( + self.NotifySetCard, + self.card_NotifySet, + self, + aniType=FlyoutAnimationType.PULL_UP, + isDeleteOnClose=False, + ) class NotifyContentSettingCard(HeaderCardWidget): @@ -1814,6 +1796,7 @@ class MemberManager(QWidget): 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): @@ -1846,6 +1829,7 @@ class MemberManager(QWidget): 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): @@ -1898,6 +1882,7 @@ class MemberManager(QWidget): 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): @@ -1908,7 +1893,7 @@ class MemberManager(QWidget): self.config = config - self.card_IfCompanyWechat = SwitchSettingCard( + self.card_IfCompanyWebHookBot = SwitchSettingCard( icon=FluentIcon.PAGE_RIGHT, title="推送用户企业微信机器人通知", content="是否启用用户企微机器人通知功能", @@ -1927,7 +1912,8 @@ class MemberManager(QWidget): ) Layout = QVBoxLayout() - Layout.addWidget(self.card_IfCompanyWechat) + Layout.addWidget(self.card_IfCompanyWebHookBot) Layout.addWidget(self.card_CompanyWebHookBotUrl) self.viewLayout.addLayout(Layout) + self.viewLayout.setSpacing(3) self.viewLayout.setContentsMargins(3, 0, 3, 3) diff --git a/app/ui/queue_manager.py b/app/ui/queue_manager.py index c450071..a8ff7a4 100644 --- a/app/ui/queue_manager.py +++ b/app/ui/queue_manager.py @@ -379,16 +379,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 +388,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..2f2be41 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游戏隐私政策""" From 8d69e43f720f47e4e3a608db3a66dd76f47830ba Mon Sep 17 00:00:00 2001 From: DLmaster361 Date: Thu, 22 May 2025 23:20:38 +0800 Subject: [PATCH 19/28] =?UTF-8?q?feat(ui):=20=E5=88=9D=E6=AD=A5=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E5=91=A8=E8=AE=A1=E5=88=92=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/core/config.py | 222 +++++++++++++++--------------------- app/ui/Widget.py | 188 ++++++++++++++++++++++++++++++- app/ui/main_window.py | 9 ++ app/ui/member_manager.py | 10 +- app/ui/plan_manager.py | 236 ++++++++------------------------------- app/ui/queue_manager.py | 6 +- app/ui/setting.py | 4 +- 7 files changed, 334 insertions(+), 341 deletions(-) diff --git a/app/core/config.py b/app/core/config.py index 3c5781c..e28c9e9 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 @@ -469,117 +470,49 @@ class MaaPlanConfig(LQConfig): self.Info_Name = ConfigItem("Info", "Name", "新表格") - self.Global_MedicineNumb = ConfigItem( - "Global", "MedicineNumb", 0, RangeValidator(0, 1024) - ) - self.Global_SeriesNumb = OptionsConfigItem( - "Global", - "SeriesNumb", - "0", - OptionsValidator(["0", "6", "5", "4", "3", "2", "1", "-1"]), - ) - self.Global_GameId = ConfigItem("Global", "GameId", "-") - self.Global_GameId_1 = ConfigItem("Global", "GameId_1", "-") - self.Global_GameId_2 = ConfigItem("Global", "GameId_2", "-") - self.Global_GameId_Remain = ConfigItem("Global", "GameId_Remain", "-") + self.config_item_dict: dict[str, Dict[str, ConfigItem]] = {} - self.Monday_MedicineNumb = ConfigItem( - "Monday", "MedicineNumb", 0, RangeValidator(0, 1024) - ) - self.Monday_SeriesNumb = OptionsConfigItem( + for group in [ + "ALL", "Monday", - "SeriesNumb", - "0", - OptionsValidator(["0", "6", "5", "4", "3", "2", "1", "-1"]), - ) - self.Monday_GameId = ConfigItem("Monday", "GameId", "-") - self.Monday_GameId_1 = ConfigItem("Monday", "GameId_1", "-") - self.Monday_GameId_2 = ConfigItem("Monday", "GameId_2", "-") - self.Monday_GameId_Remain = ConfigItem("Monday", "GameId_Remain", "-") - - self.Tuesday_MedicineNumb = ConfigItem( - "Tuesday", "MedicineNumb", 0, RangeValidator(0, 1024) - ) - self.Tuesday_SeriesNumb = OptionsConfigItem( "Tuesday", - "SeriesNumb", - "0", - OptionsValidator(["0", "6", "5", "4", "3", "2", "1", "-1"]), - ) - self.Tuesday_GameId = ConfigItem("Tuesday", "GameId", "-") - self.Tuesday_GameId_1 = ConfigItem("Tuesday", "GameId_1", "-") - self.Tuesday_GameId_2 = ConfigItem("Tuesday", "GameId_2", "-") - self.Tuesday_GameId_Remain = ConfigItem("Tuesday", "GameId_Remain", "-") - - self.Wednesday_MedicineNumb = ConfigItem( - "Wednesday", "MedicineNumb", 0, RangeValidator(0, 1024) - ) - self.Wednesday_SeriesNumb = OptionsConfigItem( "Wednesday", - "SeriesNumb", - "0", - OptionsValidator(["0", "6", "5", "4", "3", "2", "1", "-1"]), - ) - self.Wednesday_GameId = ConfigItem("Wednesday", "GameId", "-") - self.Wednesday_GameId_1 = ConfigItem("Wednesday", "GameId_1", "-") - self.Wednesday_GameId_2 = ConfigItem("Wednesday", "GameId_2", "-") - self.Wednesday_GameId_Remain = ConfigItem("Wednesday", "GameId_Remain", "-") - - self.Thursday_MedicineNumb = ConfigItem( - "Thursday", "MedicineNumb", 0, RangeValidator(0, 1024) - ) - self.Thursday_SeriesNumb = OptionsConfigItem( "Thursday", - "SeriesNumb", - "0", - OptionsValidator(["0", "6", "5", "4", "3", "2", "1", "-1"]), - ) - self.Thursday_GameId = ConfigItem("Thursday", "GameId", "-") - self.Thursday_GameId_1 = ConfigItem("Thursday", "GameId_1", "-") - self.Thursday_GameId_2 = ConfigItem("Thursday", "GameId_2", "-") - self.Thursday_GameId_Remain = ConfigItem("Thursday", "GameId_Remain", "-") - - self.Friday_MedicineNumb = ConfigItem( - "Friday", "MedicineNumb", 0, RangeValidator(0, 1024) - ) - self.Friday_SeriesNumb = OptionsConfigItem( "Friday", - "SeriesNumb", - "0", - OptionsValidator(["0", "6", "5", "4", "3", "2", "1", "-1"]), - ) - self.Friday_GameId = ConfigItem("Friday", "GameId", "-") - self.Friday_GameId_1 = ConfigItem("Friday", "GameId_1", "-") - self.Friday_GameId_2 = ConfigItem("Friday", "GameId_2", "-") - self.Friday_GameId_Remain = ConfigItem("Friday", "GameId_Remain", "-") - - self.Saturday_MedicineNumb = ConfigItem( - "Saturday", "MedicineNumb", 0, RangeValidator(0, 1024) - ) - self.Saturday_SeriesNumb = OptionsConfigItem( "Saturday", - "SeriesNumb", - "0", - OptionsValidator(["0", "6", "5", "4", "3", "2", "1", "-1"]), - ) - self.Saturday_GameId = ConfigItem("Saturday", "GameId", "-") - self.Saturday_GameId_1 = ConfigItem("Saturday", "GameId_1", "-") - self.Saturday_GameId_2 = ConfigItem("Saturday", "GameId_2", "-") - self.Saturday_GameId_Remain = ConfigItem("Saturday", "GameId_Remain", "-") - - self.Sunday_MedicineNumb = ConfigItem( - "Sunday", "MedicineNumb", 0, RangeValidator(0, 1024) - ) - self.Sunday_SeriesNumb = OptionsConfigItem( "Sunday", - "SeriesNumb", - "0", - OptionsValidator(["0", "6", "5", "4", "3", "2", "1", "-1"]), - ) - self.Sunday_GameId = ConfigItem("Sunday", "GameId", "-") - self.Sunday_GameId_1 = ConfigItem("Sunday", "GameId_1", "-") - self.Sunday_GameId_2 = ConfigItem("Sunday", "GameId_2", "-") - self.Sunday_GameId_Remain = ConfigItem("Sunday", "GameId_Remain", "-") + ]: + self.config_item_dict[group] = {} + + 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", "-" + ) + + for name in [ + "MedicineNumb", + "SeriesNumb", + "GameId", + "GameId_1", + "GameId_2", + "GameId_Remain", + ]: + setattr(self, f"{group}_{name}", self.config_item_dict[group][name]) class AppConfig(GlobalConfig): @@ -610,7 +543,13 @@ class AppConfig(GlobalConfig): self.silence_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 @@ -732,39 +671,42 @@ class AppConfig(GlobalConfig): "近/特芯片组", ] - # 生成本日关卡信息 - days = self.server_date().isoweekday() + # # 生成本日关卡信息 + # days = self.server_date().isoweekday() - gameid_list = [ - {"value": "-", "text": "当前/上次", "days": [1, 2, 3, 4, 5, 6, 7]}, - {"value": "1-7", "text": "1-7", "days": [1, 2, 3, 4, 5, 6, 7]}, - {"value": "R8-11", "text": "R8-11", "days": [1, 2, 3, 4, 5, 6, 7]}, - { - "value": "12-17-HARD", - "text": "12-17-HARD", - "days": [1, 2, 3, 4, 5, 6, 7], - }, - {"value": "CE-6", "text": "龙门币-6/5", "days": [2, 4, 6, 7]}, - {"value": "AP-5", "text": "红票-5", "days": [1, 4, 6, 7]}, - {"value": "CA-5", "text": "技能-5", "days": [2, 3, 5, 7]}, - {"value": "LS-6", "text": "经验-6/5", "days": [1, 2, 3, 4, 5, 6, 7]}, - {"value": "SK-5", "text": "碳-5", "days": [1, 3, 5, 6]}, - {"value": "PR-A-1", "text": "奶/盾芯片", "days": [1, 4, 5, 7]}, - {"value": "PR-A-2", "text": "奶/盾芯片组", "days": [1, 4, 5, 7]}, - {"value": "PR-B-1", "text": "术/狙芯片", "days": [1, 2, 5, 6]}, - {"value": "PR-B-2", "text": "术/狙芯片组", "days": [1, 2, 5, 6]}, - {"value": "PR-C-1", "text": "先/辅芯片", "days": [3, 4, 6, 7]}, - {"value": "PR-C-2", "text": "先/辅芯片组", "days": [3, 4, 6, 7]}, - {"value": "PR-D-1", "text": "近/特芯片", "days": [2, 3, 6, 7]}, - {"value": "PR-D-2", "text": "近/特芯片组", "days": [2, 3, 6, 7]}, - ] + # 生成每日关卡信息 + for day in range(1, 8): - 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"]) + gameid_list = [ + {"value": "-", "text": "当前/上次", "days": [1, 2, 3, 4, 5, 6, 7]}, + {"value": "1-7", "text": "1-7", "days": [1, 2, 3, 4, 5, 6, 7]}, + {"value": "R8-11", "text": "R8-11", "days": [1, 2, 3, 4, 5, 6, 7]}, + { + "value": "12-17-HARD", + "text": "12-17-HARD", + "days": [1, 2, 3, 4, 5, 6, 7], + }, + {"value": "CE-6", "text": "龙门币-6/5", "days": [2, 4, 6, 7]}, + {"value": "AP-5", "text": "红票-5", "days": [1, 4, 6, 7]}, + {"value": "CA-5", "text": "技能-5", "days": [2, 3, 5, 7]}, + {"value": "LS-6", "text": "经验-6/5", "days": [1, 2, 3, 4, 5, 6, 7]}, + {"value": "SK-5", "text": "碳-5", "days": [1, 3, 5, 6]}, + {"value": "PR-A-1", "text": "奶/盾芯片", "days": [1, 4, 5, 7]}, + {"value": "PR-A-2", "text": "奶/盾芯片组", "days": [1, 4, 5, 7]}, + {"value": "PR-B-1", "text": "术/狙芯片", "days": [1, 2, 5, 6]}, + {"value": "PR-B-2", "text": "术/狙芯片组", "days": [1, 2, 5, 6]}, + {"value": "PR-C-1", "text": "先/辅芯片", "days": [3, 4, 6, 7]}, + {"value": "PR-C-2", "text": "先/辅芯片组", "days": [3, 4, 6, 7]}, + {"value": "PR-D-1", "text": "近/特芯片", "days": [2, 3, 6, 7]}, + {"value": "PR-D-2", "text": "近/特芯片组", "days": [2, 3, 6, 7]}, + ] - self.gameid_dict["Today"] = gameid_dict + for gameid_info in gameid_list: + if day in gameid_info["days"]: + gameid_dict["value"].append(gameid_info["value"]) + gameid_dict["text"].append(gameid_info["text"]) + + self.gameid_dict[calendar.day_name[day - 1]] = gameid_dict self.gameid_refreshed.emit() @@ -1207,7 +1149,7 @@ class AppConfig(GlobalConfig): maa_plan_config.load(maa_plan_dir / "config.json", maa_plan_config) maa_plan_config.save() - self.member_dict[maa_plan_dir.name] = { + self.plan_dict[maa_plan_dir.name] = { "Type": "Maa", "Path": maa_plan_dir, "Config": maa_plan_config, @@ -1264,6 +1206,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/ui/Widget.py b/app/ui/Widget.py index 4aa98be..e308046 100644 --- a/app/ui/Widget.py +++ b/app/ui/Widget.py @@ -1065,6 +1065,190 @@ class StatusSwitchSetting(SwitchButton): 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.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.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): @@ -1184,9 +1368,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/main_window.py b/app/ui/main_window.py index 1f31457..9fc9a69 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 @@ -81,6 +82,7 @@ class AUTO_MAA(MSFluentWindow): # 创建主窗口 self.home = Home(self) self.member_manager = MemberManager(self) + self.plan_manager = PlanManager(self) self.queue_manager = QueueManager(self) self.dispatch_center = DispatchCenter(self) self.history = History(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, diff --git a/app/ui/member_manager.py b/app/ui/member_manager.py index 4e96e69..cf4e1d6 100644 --- a/app/ui/member_manager.py +++ b/app/ui/member_manager.py @@ -197,11 +197,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() @@ -866,9 +862,7 @@ class MemberManager(QWidget): return None choice = MessageBox( - "确认", - f"确定要删除 {name} 吗?", - self.window(), + "确认", f"确定要删除 {name} 吗?", self.window() ) if choice.exec(): diff --git a/app/ui/plan_manager.py b/app/ui/plan_manager.py index b0fab53..f236b88 100644 --- a/app/ui/plan_manager.py +++ b/app/ui/plan_manager.py @@ -28,49 +28,28 @@ v4.3 from loguru import logger from PySide6.QtWidgets import ( QWidget, - QFileDialog, - QHBoxLayout, QVBoxLayout, QStackedWidget, - QTableWidgetItem, QHeaderView, ) -from PySide6.QtGui import QIcon from qfluentwidgets import ( Action, - ScrollArea, FluentIcon, MessageBox, HeaderCardWidget, CommandBar, - ExpandGroupSettingCard, - PushSettingCard, TableWidget, - PrimaryToolButton, ) -from PySide6.QtCore import Signal -from datetime import datetime -from functools import partial -from pathlib import Path from typing import List import shutil -import json -from app.core import Config, MainInfoBar, TaskManager, MaaPlanConfig, Network +from app.core import Config, MainInfoBar, MaaPlanConfig from app.services import Crypto -from .downloader import DownloadManager from .Widget import ( - LineEditMessageBox, - LineEditSettingCard, - SpinBoxSettingCard, ComboBoxMessageBox, - EditableComboBoxSettingCard, - PasswordLineEditSettingCard, - UserLableSettingCard, - ComboBoxSettingCard, - SwitchSettingCard, - PushAndSwitchButtonSettingCard, - PushAndComboBoxSettingCard, + SpinBoxSetting, + EditableComboBoxSetting, + ComboBoxSetting, PivotArea, ) @@ -142,7 +121,7 @@ class PlanManager(QWidget): "Config": maa_plan_config, } - self.plan_manager.add_MaaSettingBox(index) + self.plan_manager.add_MaaPlanSettingBox(index) self.plan_manager.switch_SettingBox(index) logger.success(f"计划管理 计划_{index} 添加成功") @@ -169,11 +148,7 @@ class PlanManager(QWidget): ) return None - choice = MessageBox( - "确认", - f"确定要删除 {name} 吗?", - self.window(), - ) + choice = MessageBox("确认", f"确定要删除 {name} 吗?", self.window()) if choice.exec(): self.plan_manager.clear_SettingBox() @@ -185,7 +160,7 @@ class PlanManager(QWidget): Config.plan_dict[f"计划_{i}"]["Path"].rename( Config.plan_dict[f"计划_{i}"]["Path"].with_name(f"计划_{i-1}") ) - Config.change_queue(f"计划_{i}", f"计划_{i-1}") + Config.change_plan(f"计划_{i}", f"计划_{i-1}") self.plan_manager.show_SettingBox(max(int(name[3:]) - 1, 1)) @@ -225,15 +200,15 @@ class PlanManager(QWidget): Config.plan_dict[name]["Path"].rename( Config.plan_dict[name]["Path"].with_name("计划_0") ) - Config.change_queue(name, "计划_0") + Config.change_plan(name, "计划_0") Config.plan_dict[f"计划_{index-1}"]["Path"].rename( Config.plan_dict[name]["Path"] ) - Config.change_queue(f"计划_{index-1}", name) + 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_queue("计划_0", f"计划_{index-1}") + Config.change_plan("计划_0", f"计划_{index-1}") self.plan_manager.show_SettingBox(index - 1) @@ -273,27 +248,21 @@ class PlanManager(QWidget): Config.plan_dict[name]["Path"].rename( Config.plan_dict[name]["Path"].with_name("计划_0") ) - Config.change_queue(name, "计划_0") + Config.change_plan(name, "计划_0") Config.plan_dict[f"计划_{index+1}"]["Path"].rename( Config.plan_dict[name]["Path"] ) - Config.change_queue(f"计划_{index+1}", name) + 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_queue("计划_0", f"计划_{index+1}") + 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 refresh_dashboard(self): - """刷新所有计划表的用户仪表盘""" - - for script in self.plan_manager.script_list: - script.user_setting.user_manager.user_dashboard.load_info() - class PlanSettingBox(QWidget): """计划管理子页面组""" @@ -331,7 +300,7 @@ class PlanManager(QWidget): for name, info in Config.plan_dict.items(): if info["Type"] == "Maa": - self.add_MaaSettingBox(int(name[3:])) + self.add_MaaPlanSettingBox(int(name[3:])) self.switch_SettingBox(index) @@ -378,20 +347,23 @@ class PlanManager(QWidget): self.config = Config.plan_dict[f"计划_{uid}"]["Config"] self.dashboard = TableWidget(self) - self.dashboard.setColumnCount(11) + self.dashboard.setColumnCount(8) + self.dashboard.setRowCount(6) self.dashboard.setHorizontalHeaderLabels( + ["全局", "周一", "周二", "周三", "周四", "周五", "周六", "周日"] + ) + self.dashboard.setVerticalHeaderLabels( [ "吃理智药", "连战次数", "关卡选择", - "备选关卡 - 1", - "备选关卡 - 2", - "剩余理智关卡", + "备选 - 1", + "备选 - 2", + "剩余理智", ] ) self.dashboard.setEditTriggers(TableWidget.NoEditTriggers) - self.dashboard.verticalHeader().setVisible(False) - for col in range(6): + for col in range(8): self.dashboard.horizontalHeader().setSectionResizeMode( col, QHeaderView.ResizeMode.Stretch ) @@ -399,143 +371,33 @@ class PlanManager(QWidget): self.viewLayout.addWidget(self.dashboard) self.viewLayout.setContentsMargins(3, 0, 3, 3) - Config.PASSWORD_refreshed.connect(self.load_info) + for col, (group, name_dict) in enumerate( + self.config.config_item_dict.items() + ): - def load_info(self): + for row, (name, configItem) in enumerate(name_dict.items()): - self.user_data = Config.plan_dict[self.name]["UserData"] - - self.dashboard.setRowCount(len(self.user_data)) - - for name, info in self.user_data.items(): - - config = info["Config"] - - text_list = [] - if not config.get(config.Data_IfPassCheck): - text_list.append("未通过人工排查") - text_list.append( - f"今日已代理{config.get(config.Data_ProxyTimes)}次" - if Config.server_date().strftime("%Y-%m-%d") - == config.get(config.Data_LastProxyDate) - else "今日未进行代理" - ) - text_list.append( - "本周剿灭已完成" - if datetime.strptime( - config.get(config.Data_LastAnnihilationDate), - "%Y-%m-%d", - ).isocalendar()[:2] - == Config.server_date().isocalendar()[:2] - else "本周剿灭未完成" - ) - - button = PrimaryToolButton(FluentIcon.CHEVRON_RIGHT, self) - button.setFixedSize(32, 32) - button.clicked.connect(partial(self.switch_to.emit, name)) - - self.dashboard.setItem( - int(name[3:]) - 1, - 0, - QTableWidgetItem(config.get(config.Info_Name)), - ) - self.dashboard.setItem( - int(name[3:]) - 1, - 1, - QTableWidgetItem(config.get(config.Info_Id)), - ) - self.dashboard.setItem( - int(name[3:]) - 1, - 2, - QTableWidgetItem( - Crypto.AUTO_decryptor( - config.get(config.Info_Password), - Config.PASSWORD, + if name == "MedicineNumb": + setting_item = SpinBoxSetting( + range=(0, 1024), + qconfig=self.config, + configItem=configItem, + parent=self, ) - if Config.PASSWORD - else "******" - ), - ) - self.dashboard.setItem( - int(name[3:]) - 1, - 3, - QTableWidgetItem( - "启用" - if config.get(config.Info_Status) - and config.get(config.Info_RemainedDay) != 0 - else "禁用" - ), - ) - self.dashboard.setItem( - int(name[3:]) - 1, - 4, - QTableWidgetItem(" | ".join(text_list)), - ) - self.dashboard.setItem( - int(name[3:]) - 1, - 5, - QTableWidgetItem(str(config.get(config.Info_MedicineNumb))), - ) - self.dashboard.setItem( - int(name[3:]) - 1, - 6, - QTableWidgetItem( - Config.gameid_dict["ALL"]["text"][ - Config.gameid_dict["ALL"]["value"].index( - config.get(config.Info_GameId) - ) - ] - if config.get(config.Info_GameId) - in Config.gameid_dict["ALL"]["value"] - else config.get(config.Info_GameId) - ), - ) - self.dashboard.setItem( - int(name[3:]) - 1, - 7, - QTableWidgetItem( - Config.gameid_dict["ALL"]["text"][ - Config.gameid_dict["ALL"]["value"].index( - config.get(config.Info_GameId_1) - ) - ] - if config.get(config.Info_GameId_1) - in Config.gameid_dict["ALL"]["value"] - else config.get(config.Info_GameId_1) - ), - ) - self.dashboard.setItem( - int(name[3:]) - 1, - 8, - QTableWidgetItem( - Config.gameid_dict["ALL"]["text"][ - Config.gameid_dict["ALL"]["value"].index( - config.get(config.Info_GameId_2) - ) - ] - if config.get(config.Info_GameId_2) - in Config.gameid_dict["ALL"]["value"] - else config.get(config.Info_GameId_2) - ), - ) - self.dashboard.setItem( - int(name[3:]) - 1, - 9, - QTableWidgetItem( - "不使用" - if config.get(config.Info_GameId_Remain) == "-" - else ( - ( - Config.gameid_dict["ALL"]["text"][ - Config.gameid_dict["ALL"]["value"].index( - config.get(config.Info_GameId_Remain) - ) - ] - ) - if config.get(config.Info_GameId_Remain) - in Config.gameid_dict["ALL"]["value"] - else config.get(config.Info_GameId_Remain) + elif name == "SeriesNumb": + setting_item = ComboBoxSetting( + texts=["AUTO", "6", "5", "4", "3", "2", "1", "不选择"], + qconfig=self.config, + configItem=configItem, + parent=self, ) - ), - ) - self.dashboard.setCellWidget(int(name[3:]) - 1, 10, button) + elif "GameId" in name: + setting_item = EditableComboBoxSetting( + value=Config.gameid_dict[group]["value"], + texts=Config.gameid_dict[group]["text"], + qconfig=self.config, + configItem=configItem, + parent=self, + ) + + self.dashboard.setCellWidget(row, col, setting_item) diff --git a/app/ui/queue_manager.py b/app/ui/queue_manager.py index a8ff7a4..5120167 100644 --- a/app/ui/queue_manager.py +++ b/app/ui/queue_manager.py @@ -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() diff --git a/app/ui/setting.py b/app/ui/setting.py index 2f2be41..e26ead3 100644 --- a/app/ui/setting.py +++ b/app/ui/setting.py @@ -252,9 +252,7 @@ class Setting(QWidget): choice.exec() else: choice = MessageBox( - "确认", - "您没有输入管理密钥,是否取消修改管理密钥?", - self.window(), + "确认", "您没有输入管理密钥,是否取消修改管理密钥?", self.window() ) if choice.exec(): break From 3127c836039cb339a5fce869f681b4a705ef4415 Mon Sep 17 00:00:00 2001 From: aoxuan Date: Fri, 23 May 2025 15:01:26 +0800 Subject: [PATCH 20/28] =?UTF-8?q?feat(notification):=20=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E5=8D=95=E7=8B=AC=E9=80=9A=E7=9F=A5=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/MAA.py | 224 ++++++++++++++++----- app/services/notification.py | 377 +++++++++++++++++------------------ 2 files changed, 361 insertions(+), 240 deletions(-) diff --git a/app/models/MAA.py b/app/models/MAA.py index a2bd23e..2cb5810 100644 --- a/app/models/MAA.py +++ b/app/models/MAA.py @@ -608,13 +608,14 @@ class MaaManager(QObject): 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: - + if if_six_star: self.push_notification( "公招六星", f"喜报:用户 {user[0]} 公招出六星啦!", - {"user_name": user_data["Info"]["Name"]}, + { + "user_name": user_data["Info"]["Name"], + "user_index": user[2], + }, ) # 执行MAA解压更新动作 @@ -643,6 +644,7 @@ class MaaManager(QObject): if Config.get(Config.notify_IfSendStatistic): 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" @@ -1772,26 +1774,30 @@ class MaaManager(QObject): message_html = template.render(message) # 发送全局通知 - Notify.send_mail("网页", title, message_html,Config.get(Config.notify_ToAddress)) + if Config.get(Config.notify_IfSendMail): + Notify.send_mail( + "网页", title, message_html, Config.get(Config.notify_ToAddress) + ) serverchan_message = message_text.replace("\n", "\n\n") - Notify.ServerChanPush(title, f"{serverchan_message}\n\nAUTO_MAA 敬上",Config.get(Config.notify_ServerChanKey),Config.get(Config.notify_ServerChanTag),Config.get(Config.notify_ServerChanChannel)) - Notify.CompanyWebHookBotPush(title, f"{message_text}\n\nAUTO_MAA 敬上",Config.get(Config.notify_CompanyWebHookBotUrl)) - - # # 发送用户单独通知 - # for user_name in message["failed_user"].split("、") + message["waiting_user"].split("、"): - # if not user_name: # 跳过空字符串 - # continue - # user_config = Config.member_dict.get(user_name) - # if user_config and user_config.get(user_config.Notify_Enable): - # user_notify = UserNotification(user_config) - # user_notify.send_notification( - # f"{self.mode[2:4]}任务未完成通知", - # f"您的{self.mode[2:4]}任务未完成,请检查相关设置。\n\n{message_text}" - # ) + 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), + ) return message_text elif mode == "统计信息": + user_index = message.get("user_index") # 生成文本通知内容 formatted = [] for stage, items in message["drop_statistics"].items(): @@ -1818,40 +1824,166 @@ class MaaManager(QObject): message_html = template.render(message) # 发送全局通知 - Notify.send_mail("网页", title, message_html,Config.get(Config.notify_ToAddress)) + if Config.get(Config.notify_IfSendMail): + Notify.send_mail( + "网页", title, message_html, Config.get(Config.notify_ToAddress) + ) # ServerChan的换行是两个换行符。故而将\n替换为\n\n serverchan_message = message_text.replace("\n", "\n\n") - Notify.ServerChanPush(title, f"{serverchan_message}\n\nAUTO_MAA 敬上",Config.get(Config.notify_ServerChanKey),Config.get(Config.notify_ServerChanTag),Config.get(Config.notify_ServerChanChannel)) - Notify.CompanyWebHookBotPush(title, f"{message_text}\n\nAUTO_MAA 敬上",Config.get(Config.notify_CompanyWebHookBotUrl)) + 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), + ) - # # 发送用户单独通知 - # user_name = message.get("user_info") - # if user_name: - # user_config = Config.member_dict.get(user_name) - # if user_config and user_config.get(user_config.Notify_Enable): - # user_notify = UserNotification(user_config) - # user_notify.send_notification( - # f"{self.mode[2:4]}任务统计报告", - # f"您的{self.mode[2:4]}任务统计报告如下:\n\n{message_text}" - # ) + # 发送用户单独通知 + user_data = self.data.get(user_index, {}).get("Config", {}) + if user_data["Notify"].get("IfSendStatistic", False): + if user_data.get("Notify") and user_data["Notify"].get( + "Enabled", False + ): + # 发送邮件通知 + if user_data.get("Notify", {}).get("IfSendMail", False): + userToAddress = user_data.get("Notify", {}).get("ToAddress") + if userToAddress: + Notify.send_mail("网页", title, message_text, userToAddress) + else: + logger.error( + f"{self.name} | 用户邮箱地址为空,无法发送用户单独的邮件通知" + ) + + # 发送ServerChan通知 + if user_data.get("Notify", {}).get("IfServerChan", False): + userServerChanKey = user_data.get("Notify", {}).get( + "ServerChanKey" + ) + userServerChanTag = user_data.get("Notify", {}).get( + "ServerChanTag" + ) + userServerChanChannel = user_data.get("Notify", {}).get( + "ServerChanChannel" + ) + if userServerChanKey: + Notify.ServerChanPush( + title, + f"{serverchan_message}\n\nAUTO_MAA 敬上", + userServerChanKey, + userServerChanTag, + userServerChanChannel, + ) + else: + logger.error( + f"{self.name} |用户ServerChan密钥为空,无法发送用户单独的ServerChan通知" + ) + + # 推送CompanyWebHookBot通知 + if user_data.get("Notify", {}).get("IfCompanyWebHookBot", False): + userCompanyWebHookBotUrl = user_data.get("Notify", {}).get( + "CompanyWebHookBotUrl" + ) + if userCompanyWebHookBotUrl: + Notify.CompanyWebHookBotPush( + title, + f"{message_text}\n\nAUTO_MAA 敬上", + userCompanyWebHookBotUrl, + ) + else: + logger.error( + f"{self.name} |用户CompanyWebHookBot密钥为空,无法发送用户单独的CompanyWebHookBot通知" + ) elif mode == "公招六星": + user_index = message.get("user_index") # 生成HTML通知内容 template = env.get_template("MAA_six_star.html") + + # 这里需要看一下,我给message加了个user_index message_html = template.render(message) - # 发送全局通知 - Notify.send_mail("网页", title, message_html,Config.get(Config.notify_ToAddress)) - Notify.ServerChanPush(title, "好羡慕~\n\nAUTO_MAA 敬上",Config.get(Config.notify_ServerChanKey),Config.get(Config.notify_ServerChanTag),Config.get(Config.notify_ServerChanChannel)) - Notify.CompanyWebHookBotPush(title, "好羡慕~\n\nAUTO_MAA 敬上",Config.get(Config.notify_CompanyWebHookBotUrl)) + 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), + ) + # 发送用户单独通知 + user_data = self.data.get(user_index, {}).get("Config", {}) - # # 发送用户单独通知 - # user_name = message.get("user_name") - # if user_name: - # user_config = Config.member_dict.get(user_name) - # if user_config and user_config.get(user_config.Notify_Enable): - # user_notify = UserNotification(user_config) - # user_notify.send_notification( - # "公招六星通知", - # "恭喜您在公招中获得了六星干员!" - # ) + # 判断是否单独发送六星 + if user_data["Notify"].get("IfSendSixStar", False): + if user_data.get("Notify") and user_data["Notify"].get( + "Enabled", False + ): + # 发送邮件通知 + if user_data.get("Notify", {}).get("IfSendMail", False): + userToAddress = user_data.get("Notify", {}).get("ToAddress") + if userToAddress: + Notify.send_mail("网页", title, message_html, userToAddress) + else: + logger.error( + f"{self.name} | 用户邮箱地址为空,无法发送用户单独的邮件通知" + ) + + # 发送ServerChan通知 + if user_data.get("Notify", {}).get("IfServerChan", False): + userServerChanKey = user_data.get("Notify", {}).get( + "ServerChanKey" + ) + userServerChanTag = user_data.get("Notify", {}).get( + "ServerChanTag" + ) + userServerChanChannel = user_data.get("Notify", {}).get( + "ServerChanChannel" + ) + if userServerChanKey: + Notify.ServerChanPush( + title, + "好羡慕~\n\nAUTO_MAA 敬上", + userServerChanKey, + userServerChanTag, + userServerChanChannel, + ) + else: + logger.error( + f"{self.name} |用户ServerChan密钥为空,无法发送用户单独的ServerChan通知" + ) + + # 推送CompanyWebHookBot通知 + if user_data.get("Notify", {}).get("IfCompanyWebHookBot", False): + userCompanyWebHookBotUrl = user_data.get("Notify", {}).get( + "CompanyWebHookBotUrl" + ) + if userCompanyWebHookBotUrl: + Notify.CompanyWebHookBotPush( + title, + "好羡慕~\n\nAUTO_MAA 敬上", + userCompanyWebHookBotUrl, + ) + else: + logger.error( + f"{self.name} |用户CompanyWebHookBot密钥为空,无法发送用户单独的CompanyWebHookBot通知" + ) + return None diff --git a/app/services/notification.py b/app/services/notification.py index 639e3ac..4cb60bb 100644 --- a/app/services/notification.py +++ b/app/services/notification.py @@ -69,219 +69,208 @@ class Notification(QWidget): 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,}$", - 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), - ) - ) # 发件人显示的名字 - message["To"] = formataddr( - ( - Header("AUTO_MAA用户", "utf-8").encode(), - to_address, - ) - ) # 收件人显示的名字 - message["Subject"] = Header(title, "utf-8") - - if mode == "网页": - message.attach(MIMEText(content, "html", "utf-8")) - - smtpObj = smtplib.SMTP_SSL( - Config.get(Config.notify_SMTPServerAddress), - 465, - ) - smtpObj.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( - Config.get(Config.notify_FromAddress), + ) + or not bool( + re.match( + r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$", 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 + ) + ): + 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(), + to_address, + ) + ) # 收件人显示的名字 + message["Subject"] = Header(title, "utf-8") + + if mode == "网页": + message.attach(MIMEText(content, "html", "utf-8")) + + smtpObj = smtplib.SMTP_SSL( + Config.get(Config.notify_SMTPServerAddress), + 465, + ) + smtpObj.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 Config.get(Config.notify_IfServerChan): - if not send_key: - logger.error("请正确设置Server酱的SendKey") - self.push_info_bar.emit( - "error", "Server酱通知推送异常", "请正确设置Server酱的SendKey", -1 - ) - return None + 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: - raise ValueError("SendKey 格式错误(sctp)") + 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 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( - "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)}" - return None - - def CompanyWebHookBotPush(self, title, content,webhook_url): - """使用企业微信群机器人推送通知""" - if Config.get(Config.notify_IfCompanyWebHookBot): - - 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) + 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 None + 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'使用企业微信群机器人推送通知时出错:{info["errmsg"]}', + -1, + ) + return None + + if info["errcode"] == 0: + logger.info("企业微信群机器人推送通知成功") + return True + else: + logger.error(f"企业微信群机器人推送通知失败:{info}") + self.push_info_bar.emit( + "error", + "企业微信群机器人通知推送失败", + f'使用企业微信群机器人推送通知时出错:{info["errmsg"]}', + -1, + ) + return f'使用企业微信群机器人推送通知时出错:{info["errmsg"]}' def send_test_notification(self): """发送测试通知到所有已启用的通知渠道""" From 974a4b634a12e5fd886900da263c555bf80d9565 Mon Sep 17 00:00:00 2001 From: aoxuan Date: Fri, 23 May 2025 15:19:35 +0800 Subject: [PATCH 21/28] =?UTF-8?q?feat(ui):=20=E4=BC=98=E5=8C=96=E6=95=8F?= =?UTF-8?q?=E6=84=9F=E4=BF=A1=E6=81=AF=E6=98=BE=E7=A4=BA=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/ui/Widget.py | 48 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 13 deletions(-) diff --git a/app/ui/Widget.py b/app/ui/Widget.py index 4aa98be..cf21c1d 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, @@ -77,13 +84,6 @@ from qfluentwidgets import ( FlyoutViewBase, ) 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 @@ -998,9 +998,31 @@ class UserNoticeSettingCard(PushAndSwitchButtonSettingCard): def setValues(self): def short_str(s: str) -> str: - if len(s) <= 10: - return s - return s[:10] + "..." + 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:域名 + 路径尾4 + parsed_url = urlparse(s) + domain = parsed_url.netloc + path_tail = ( + parsed_url.path[-4:] + if len(parsed_url.path) > 4 + else parsed_url.path + ) + return f"{domain}" + + elif "@" in s: + # # 邮箱:显示@前最多3字符 + 域名 + # username, domain = s.split("@", 1) + # displayed_name = username[-3:] if len(username) > 3 else username + # 邮箱展示全部 + return f"{s}" + + else: + # 普通字符串:末尾3字符 + return f"***{s[-3:]}" if len(s) > 3 else s content_list = [] From f5d898c89ef09941230ecc60035aa7253888d894 Mon Sep 17 00:00:00 2001 From: DLmaster361 Date: Fri, 23 May 2025 22:59:09 +0800 Subject: [PATCH 22/28] =?UTF-8?q?fix(maa):=20=E5=B0=86=E9=80=9A=E7=9F=A5?= =?UTF-8?q?=E5=88=A4=E5=AE=9A=E8=BF=87=E7=A8=8B=E5=85=A8=E9=83=A8=E7=A7=BB?= =?UTF-8?q?=E5=85=A5`push=5Fnotification`=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/core/config.py | 2 +- app/models/MAA.py | 347 ++++++++++++++++++++--------------------- app/ui/Widget.py | 25 +-- resources/version.json | 7 +- 4 files changed, 192 insertions(+), 189 deletions(-) diff --git a/app/core/config.py b/app/core/config.py index 3c5781c..77dbb72 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -584,7 +584,7 @@ class MaaPlanConfig(LQConfig): class AppConfig(GlobalConfig): - VERSION = "4.3.8.3" + VERSION = "4.3.8.4" gameid_refreshed = Signal() PASSWORD_refreshed = Signal() diff --git a/app/models/MAA.py b/app/models/MAA.py index 2cb5810..5b1ceef 100644 --- a/app/models/MAA.py +++ b/app/models/MAA.py @@ -614,8 +614,8 @@ class MaaManager(QObject): f"喜报:用户 {user[0]} 公招出六星啦!", { "user_name": user_data["Info"]["Name"], - "user_index": user[2], }, + user_data, ) # 执行MAA解压更新动作 @@ -641,27 +641,23 @@ class MaaManager(QObject): logger.info(f"{self.name} | 更新动作结束") - if Config.get(Config.notify_IfSendStatistic): - - 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, - ) + # 发送统计信息 + 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"]: # 成功完成代理的用户修改相关参数 @@ -854,6 +850,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("报告", "已完成!"), @@ -861,15 +868,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() @@ -1743,15 +1742,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" @@ -1763,9 +1768,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"]) @@ -1773,12 +1775,16 @@ class MaaManager(QObject): template = env.get_template("MAA_result.html") message_html = template.render(message) + # ServerChan的换行是两个换行符。故而将\n替换为\n\n + serverchan_message = message_text.replace("\n", "\n\n") + # 发送全局通知 + if Config.get(Config.notify_IfSendMail): Notify.send_mail( "网页", title, message_html, Config.get(Config.notify_ToAddress) ) - serverchan_message = message_text.replace("\n", "\n\n") + if Config.get(Config.notify_IfServerChan): Notify.ServerChanPush( title, @@ -1787,6 +1793,7 @@ class MaaManager(QObject): Config.get(Config.notify_ServerChanTag), Config.get(Config.notify_ServerChanChannel), ) + if Config.get(Config.notify_IfCompanyWebHookBot): Notify.CompanyWebHookBotPush( title, @@ -1794,10 +1801,8 @@ class MaaManager(QObject): Config.get(Config.notify_CompanyWebHookBotUrl), ) - return message_text - elif mode == "统计信息": - user_index = message.get("user_index") + # 生成文本通知内容 formatted = [] for stage, items in message["drop_statistics"].items(): @@ -1823,98 +1828,96 @@ class MaaManager(QObject): template = env.get_template("MAA_statistics.html") message_html = template.render(message) - # 发送全局通知 - if Config.get(Config.notify_IfSendMail): - Notify.send_mail( - "网页", title, message_html, Config.get(Config.notify_ToAddress) - ) # ServerChan的换行是两个换行符。故而将\n替换为\n\n serverchan_message = message_text.replace("\n", "\n\n") - 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), - ) - # 发送用户单独通知 - user_data = self.data.get(user_index, {}).get("Config", {}) - if user_data["Notify"].get("IfSendStatistic", False): - if user_data.get("Notify") and user_data["Notify"].get( - "Enabled", False - ): - # 发送邮件通知 - if user_data.get("Notify", {}).get("IfSendMail", False): - userToAddress = user_data.get("Notify", {}).get("ToAddress") - if userToAddress: - Notify.send_mail("网页", title, message_text, userToAddress) - else: - logger.error( - f"{self.name} | 用户邮箱地址为空,无法发送用户单独的邮件通知" - ) + # 发送全局通知 + if Config.get(Config.notify_IfSendStatistic): - # 发送ServerChan通知 - if user_data.get("Notify", {}).get("IfServerChan", False): - userServerChanKey = user_data.get("Notify", {}).get( - "ServerChanKey" - ) - userServerChanTag = user_data.get("Notify", {}).get( - "ServerChanTag" - ) - userServerChanChannel = user_data.get("Notify", {}).get( - "ServerChanChannel" - ) - if userServerChanKey: - Notify.ServerChanPush( - title, - f"{serverchan_message}\n\nAUTO_MAA 敬上", - userServerChanKey, - userServerChanTag, - userServerChanChannel, - ) - else: - logger.error( - f"{self.name} |用户ServerChan密钥为空,无法发送用户单独的ServerChan通知" - ) - - # 推送CompanyWebHookBot通知 - if user_data.get("Notify", {}).get("IfCompanyWebHookBot", False): - userCompanyWebHookBotUrl = user_data.get("Notify", {}).get( - "CompanyWebHookBotUrl" - ) - if userCompanyWebHookBotUrl: - Notify.CompanyWebHookBotPush( - title, - f"{message_text}\n\nAUTO_MAA 敬上", - userCompanyWebHookBotUrl, - ) - else: - logger.error( - f"{self.name} |用户CompanyWebHookBot密钥为空,无法发送用户单独的CompanyWebHookBot通知" - ) - - elif mode == "公招六星": - user_index = message.get("user_index") - # 生成HTML通知内容 - template = env.get_template("MAA_six_star.html") - - # 这里需要看一下,我给message加了个user_index - message_html = template.render(message) - - 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, + 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) + + # 发送全局通知 + 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, @@ -1923,67 +1926,57 @@ class MaaManager(QObject): 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), ) + # 发送用户单独通知 - user_data = self.data.get(user_index, {}).get("Config", {}) + if user_data["Notify"]["Enabled"] and user_data["Notify"]["IfSendSixStar"]: - # 判断是否单独发送六星 - if user_data["Notify"].get("IfSendSixStar", False): - if user_data.get("Notify") and user_data["Notify"].get( - "Enabled", False - ): - # 发送邮件通知 - if user_data.get("Notify", {}).get("IfSendMail", False): - userToAddress = user_data.get("Notify", {}).get("ToAddress") - if userToAddress: - Notify.send_mail("网页", title, message_html, userToAddress) - else: - logger.error( - f"{self.name} | 用户邮箱地址为空,无法发送用户单独的邮件通知" - ) + # 发送邮件通知 + 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.get("Notify", {}).get("IfServerChan", False): - userServerChanKey = user_data.get("Notify", {}).get( - "ServerChanKey" - ) - userServerChanTag = user_data.get("Notify", {}).get( - "ServerChanTag" - ) - userServerChanChannel = user_data.get("Notify", {}).get( - "ServerChanChannel" - ) - if userServerChanKey: - Notify.ServerChanPush( - title, - "好羡慕~\n\nAUTO_MAA 敬上", - userServerChanKey, - userServerChanTag, - userServerChanChannel, - ) - else: - logger.error( - f"{self.name} |用户ServerChan密钥为空,无法发送用户单独的ServerChan通知" - ) + # 发送ServerChan通知 + if user_data["Notify"]["IfServerChan"]: - # 推送CompanyWebHookBot通知 - if user_data.get("Notify", {}).get("IfCompanyWebHookBot", False): - userCompanyWebHookBotUrl = user_data.get("Notify", {}).get( - "CompanyWebHookBotUrl" + 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通知" ) - if userCompanyWebHookBotUrl: - Notify.CompanyWebHookBotPush( - title, - "好羡慕~\n\nAUTO_MAA 敬上", - userCompanyWebHookBotUrl, - ) - else: - logger.error( - f"{self.name} |用户CompanyWebHookBot密钥为空,无法发送用户单独的CompanyWebHookBot通知" - ) return None diff --git a/app/ui/Widget.py b/app/ui/Widget.py index cf21c1d..2647da6 100644 --- a/app/ui/Widget.py +++ b/app/ui/Widget.py @@ -1003,22 +1003,27 @@ class UserNoticeSettingCard(PushAndSwitchButtonSettingCard): return f"{s[:4]}***{s[-4:]}" if len(s) > 8 else s elif s.startswith(("http://", "https://")): - # Webhook URL:域名 + 路径尾4 + # Webhook URL:域名前5 + 路径尾5 parsed_url = urlparse(s) - domain = parsed_url.netloc + domain = ( + parsed_url.netloc[:5] + if len(parsed_url.netloc) > 5 + else parsed_url.netloc + ) path_tail = ( - parsed_url.path[-4:] - if len(parsed_url.path) > 4 + parsed_url.path[-5:] + if len(parsed_url.path) > 5 else parsed_url.path ) - return f"{domain}" + return f"{domain}......{path_tail}" elif "@" in s: - # # 邮箱:显示@前最多3字符 + 域名 - # username, domain = s.split("@", 1) - # displayed_name = username[-3:] if len(username) > 3 else username - # 邮箱展示全部 - return f"{s}" + # 邮箱:@前4/7 + 域名 + username, domain = s.split("@", 1) + displayed_name = ( + f"{username[:4]}......" if len(username) > 7 else username + ) + return f"{displayed_name}@{domain}" else: # 普通字符串:末尾3字符 diff --git a/resources/version.json b/resources/version.json index f1dea9d..8bc0c5c 100644 --- a/resources/version.json +++ b/resources/version.json @@ -1,6 +1,11 @@ { - "main_version": "4.3.8.3", + "main_version": "4.3.8.4", "version_info": { + "4.3.8.4": { + "新增功能": [ + "支持为每一个用户执行独立通知" + ] + }, "4.3.8.3": { "新增功能": [ "用户仪表盘支持直接控制用户状态" From fac85a889f93c09b18f789a804389646d733fe63 Mon Sep 17 00:00:00 2001 From: DLmaster361 Date: Sat, 24 May 2025 18:33:46 +0800 Subject: [PATCH 23/28] =?UTF-8?q?fix(ui):=20=E7=AE=80=E5=8D=95=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E7=94=A8=E6=88=B7=E9=80=9A=E7=9F=A5=E6=98=BE=E7=A4=BA?= =?UTF-8?q?=E6=95=88=E6=9E=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/ui/Widget.py | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/app/ui/Widget.py b/app/ui/Widget.py index 2647da6..fa6eca5 100644 --- a/app/ui/Widget.py +++ b/app/ui/Widget.py @@ -1003,26 +1003,20 @@ class UserNoticeSettingCard(PushAndSwitchButtonSettingCard): return f"{s[:4]}***{s[-4:]}" if len(s) > 8 else s elif s.startswith(("http://", "https://")): - # Webhook URL:域名前5 + 路径尾5 + # Webhook URL:域名 + 路径尾3 parsed_url = urlparse(s) - domain = ( - parsed_url.netloc[:5] - if len(parsed_url.netloc) > 5 - else parsed_url.netloc - ) + domain = parsed_url.netloc path_tail = ( - parsed_url.path[-5:] - if len(parsed_url.path) > 5 + parsed_url.path[-3:] + if len(parsed_url.path) > 3 else parsed_url.path ) - return f"{domain}......{path_tail}" + return f"{domain}***{path_tail}" elif "@" in s: - # 邮箱:@前4/7 + 域名 + # 邮箱:@前3/6 + 域名 username, domain = s.split("@", 1) - displayed_name = ( - f"{username[:4]}......" if len(username) > 7 else username - ) + displayed_name = f"{username[:3]}***" if len(username) > 6 else username return f"{displayed_name}@{domain}" else: From 26328920a2e7994eeda1c73f0b65f2e5645a22d6 Mon Sep 17 00:00:00 2001 From: DLmaster361 Date: Sat, 24 May 2025 19:55:49 +0800 Subject: [PATCH 24/28] =?UTF-8?q?chore(ui):=20=E8=BE=93=E5=85=A5=E6=96=87?= =?UTF-8?q?=E6=9C=AC=E6=A1=86=E9=80=82=E9=85=8D=E6=96=87=E6=9C=AC=E6=8F=92?= =?UTF-8?q?=E5=85=A5=E6=93=8D=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 13 +++++++----- app/ui/Widget.py | 46 ++++++++++++++++++++---------------------- resources/version.json | 3 ++- 3 files changed, 32 insertions(+), 30 deletions(-) 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/ui/Widget.py b/app/ui/Widget.py index fa6eca5..a459ee4 100644 --- a/app/ui/Widget.py +++ b/app/ui/Widget.py @@ -109,6 +109,8 @@ class LineEditMessageBox(MessageBoxBase): self.viewLayout.addWidget(self.title) self.viewLayout.addWidget(self.input) + self.input.setFocus() + class ComboBoxMessageBox(MessageBoxBase): """选择对话框""" @@ -465,24 +467,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.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.LineEdit.setText(content.strip()) + self.LineEdit.textChanged.connect(self.__textChanged) class PasswordLineEditSettingCard(SettingCard): @@ -511,35 +516,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() 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() if self.algorithm == "DPAPI": self.LineEdit.setText(Crypto.win_decryptor(content)) elif self.algorithm == "AUTO": @@ -555,8 +554,7 @@ class PasswordLineEditSettingCard(SettingCard): self.LineEdit.setText("************") self.LineEdit.setPasswordVisible(False) self.LineEdit.setReadOnly(True) - - self.if_setValue = False + self.LineEdit.textChanged.connect(self.__textChanged) class UserLableSettingCard(SettingCard): diff --git a/resources/version.json b/resources/version.json index 8bc0c5c..622187a 100644 --- a/resources/version.json +++ b/resources/version.json @@ -3,7 +3,8 @@ "version_info": { "4.3.8.4": { "新增功能": [ - "支持为每一个用户执行独立通知" + "支持为每一个用户执行独立通知", + "输入文本框适配文本插入操作" ] }, "4.3.8.3": { From de1058a28c25b9cde2c08b9f8d47799f2e2afba2 Mon Sep 17 00:00:00 2001 From: DLmaster361 Date: Sun, 25 May 2025 21:12:22 +0800 Subject: [PATCH 25/28] =?UTF-8?q?feat(core):=20=E8=AE=A1=E5=88=92=E8=A1=A8?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E4=B8=8A=E7=BA=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/core/config.py | 157 +++++++++++++------------ app/models/MAA.py | 3 + app/services/notification.py | 6 +- app/ui/Widget.py | 183 +++++++++++++++++++++++++++-- app/ui/main_window.py | 215 +++++++++++++++++------------------ app/ui/member_manager.py | 194 +++++++++++++++++++++++-------- app/ui/plan_manager.py | 128 ++++++++++++++++++--- app/ui/queue_manager.py | 6 +- resources/version.json | 5 +- 9 files changed, 626 insertions(+), 271 deletions(-) diff --git a/app/core/config.py b/app/core/config.py index 6070b52..c27f409 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -388,9 +388,7 @@ class MaaUserConfig(LQConfig): 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"]) ) @@ -461,6 +459,29 @@ class MaaUserConfig(LQConfig): "Notify", "CompanyWebHookBotUrl", "" ) + def get_plan_info(self) -> Dict[str, Union[str, int]]: + """获取当前的计划下信息""" + + 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")), + } + class MaaPlanConfig(LQConfig): """MAA计划表配置""" @@ -468,7 +489,10 @@ class MaaPlanConfig(LQConfig): def __init__(self) -> None: super().__init__() - self.Info_Name = ConfigItem("Info", "Name", "新表格") + self.Info_Name = ConfigItem("Info", "Name", "") + self.Info_Mode = OptionsConfigItem( + "Info", "Mode", "ALL", OptionsValidator(["ALL", "Weekly"]) + ) self.config_item_dict: dict[str, Dict[str, ConfigItem]] = {} @@ -514,6 +538,18 @@ class MaaPlanConfig(LQConfig): ]: 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): @@ -615,7 +651,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: @@ -628,85 +664,48 @@ 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"]) - - # 生成全部关卡信息 - 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() + ss_gameid_dict["value"].append(gameid_info["Value"]) + ss_gameid_dict["text"].append(gameid_info["Value"]) # 生成每日关卡信息 - for day in range(1, 8): + 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]}, + { + "value": "12-17-HARD", + "text": "12-17-HARD", + "days": [1, 2, 3, 4, 5, 6, 7], + }, + {"value": "CE-6", "text": "龙门币-6/5", "days": [2, 4, 6, 7]}, + {"value": "AP-5", "text": "红票-5", "days": [1, 4, 6, 7]}, + {"value": "CA-5", "text": "技能-5", "days": [2, 3, 5, 7]}, + {"value": "LS-6", "text": "经验-6/5", "days": [1, 2, 3, 4, 5, 6, 7]}, + {"value": "SK-5", "text": "碳-5", "days": [1, 3, 5, 6]}, + {"value": "PR-A-1", "text": "奶/盾芯片", "days": [1, 4, 5, 7]}, + {"value": "PR-A-2", "text": "奶/盾芯片组", "days": [1, 4, 5, 7]}, + {"value": "PR-B-1", "text": "术/狙芯片", "days": [1, 2, 5, 6]}, + {"value": "PR-B-2", "text": "术/狙芯片组", "days": [1, 2, 5, 6]}, + {"value": "PR-C-1", "text": "先/辅芯片", "days": [3, 4, 6, 7]}, + {"value": "PR-C-2", "text": "先/辅芯片组", "days": [3, 4, 6, 7]}, + {"value": "PR-D-1", "text": "近/特芯片", "days": [2, 3, 6, 7]}, + {"value": "PR-D-2", "text": "近/特芯片组", "days": [2, 3, 6, 7]}, + ] - gameid_list = [ - {"value": "-", "text": "当前/上次", "days": [1, 2, 3, 4, 5, 6, 7]}, - {"value": "1-7", "text": "1-7", "days": [1, 2, 3, 4, 5, 6, 7]}, - {"value": "R8-11", "text": "R8-11", "days": [1, 2, 3, 4, 5, 6, 7]}, - { - "value": "12-17-HARD", - "text": "12-17-HARD", - "days": [1, 2, 3, 4, 5, 6, 7], - }, - {"value": "CE-6", "text": "龙门币-6/5", "days": [2, 4, 6, 7]}, - {"value": "AP-5", "text": "红票-5", "days": [1, 4, 6, 7]}, - {"value": "CA-5", "text": "技能-5", "days": [2, 3, 5, 7]}, - {"value": "LS-6", "text": "经验-6/5", "days": [1, 2, 3, 4, 5, 6, 7]}, - {"value": "SK-5", "text": "碳-5", "days": [1, 3, 5, 6]}, - {"value": "PR-A-1", "text": "奶/盾芯片", "days": [1, 4, 5, 7]}, - {"value": "PR-A-2", "text": "奶/盾芯片组", "days": [1, 4, 5, 7]}, - {"value": "PR-B-1", "text": "术/狙芯片", "days": [1, 2, 5, 6]}, - {"value": "PR-B-2", "text": "术/狙芯片组", "days": [1, 2, 5, 6]}, - {"value": "PR-C-1", "text": "先/辅芯片", "days": [3, 4, 6, 7]}, - {"value": "PR-C-2", "text": "先/辅芯片组", "days": [3, 4, 6, 7]}, - {"value": "PR-D-1", "text": "近/特芯片", "days": [2, 3, 6, 7]}, - {"value": "PR-D-2", "text": "近/特芯片组", "days": [2, 3, 6, 7]}, - ] + for day in range(0, 8): - for gameid_info in gameid_list: - if day in gameid_info["days"]: - gameid_dict["value"].append(gameid_info["value"]) - gameid_dict["text"].append(gameid_info["text"]) + today_gameid_dict = {"value": [], "text": []} - self.gameid_dict[calendar.day_name[day - 1]] = gameid_dict + 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() diff --git a/app/models/MAA.py b/app/models/MAA.py index 5b1ceef..edbef87 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:]))) diff --git a/app/services/notification.py b/app/services/notification.py index 4cb60bb..7d17545 100644 --- a/app/services/notification.py +++ b/app/services/notification.py @@ -254,7 +254,7 @@ class Notification(QWidget): self.push_info_bar.emit( "error", "企业微信群机器人通知推送失败", - f'使用企业微信群机器人推送通知时出错:{info["errmsg"]}', + f"使用企业微信群机器人推送通知时出错:{err}", -1, ) return None @@ -267,10 +267,10 @@ class Notification(QWidget): self.push_info_bar.emit( "error", "企业微信群机器人通知推送失败", - f'使用企业微信群机器人推送通知时出错:{info["errmsg"]}', + f"使用企业微信群机器人推送通知时出错:{err}", -1, ) - return f'使用企业微信群机器人推送通知时出错:{info["errmsg"]}' + return f"使用企业微信群机器人推送通知时出错:{err}" def send_test_notification(self): """发送测试通知到所有已启用的通知渠道""" diff --git a/app/ui/Widget.py b/app/ui/Widget.py index ea5f8d0..db85a2d 100644 --- a/app/ui/Widget.py +++ b/app/ui/Widget.py @@ -477,7 +477,7 @@ class LineEditSettingCard(SettingCard): def __textChanged(self, content: str): - self.configItem.valueChanged.disconnect() + self.configItem.valueChanged.disconnect(self.setValue) self.qconfig.set(self.configItem, content.strip()) self.configItem.valueChanged.connect(self.setValue) @@ -485,7 +485,7 @@ class LineEditSettingCard(SettingCard): def setValue(self, content: str): - self.LineEdit.textChanged.disconnect() + self.LineEdit.textChanged.disconnect(self.__textChanged) self.LineEdit.setText(content.strip()) self.LineEdit.textChanged.connect(self.__textChanged) @@ -527,7 +527,7 @@ class PasswordLineEditSettingCard(SettingCard): def __textChanged(self, content: str): - self.configItem.valueChanged.disconnect() + self.configItem.valueChanged.disconnect(self.setValue) if self.algorithm == "DPAPI": self.qconfig.set(self.configItem, Crypto.win_encryptor(content)) elif self.algorithm == "AUTO": @@ -538,7 +538,7 @@ class PasswordLineEditSettingCard(SettingCard): def setValue(self, content: str): - self.LineEdit.textChanged.disconnect() + self.LineEdit.textChanged.disconnect(self.__textChanged) if self.algorithm == "DPAPI": self.LineEdit.setText(Crypto.win_decryptor(content)) elif self.algorithm == "AUTO": @@ -756,7 +756,7 @@ class NoOptionComboBoxSettingCard(SettingCard): value: List[str], texts: List[str], qconfig: QConfig, - configItem: OptionsConfigItem, + configItem: ConfigItem, parent=None, ): @@ -789,7 +789,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): @@ -811,7 +811,7 @@ class EditableComboBoxSettingCard(SettingCard): value: List[str], texts: List[str], qconfig: QConfig, - configItem: OptionsConfigItem, + configItem: ConfigItem, parent=None, ): @@ -861,7 +861,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): @@ -899,6 +899,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) @@ -1154,7 +1317,7 @@ class NoOptionComboBoxSetting(ComboBox): def reLoadOptions(self, value: List[str], texts: List[str]): - self.currentIndexChanged.disconnect() + 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): @@ -1210,7 +1373,7 @@ class EditableComboBoxSetting(EditableComboBox): def reLoadOptions(self, value: List[str], texts: List[str]): - self.currentIndexChanged.disconnect() + 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): diff --git a/app/ui/main_window.py b/app/ui/main_window.py index 9fc9a69..76aac32 100644 --- a/app/ui/main_window.py +++ b/app/ui/main_window.py @@ -81,8 +81,8 @@ class AUTO_MAA(MSFluentWindow): # 创建主窗口 self.home = Home(self) - self.member_manager = MemberManager(self) self.plan_manager = PlanManager(self) + self.member_manager = MemberManager(self) self.queue_manager = QueueManager(self) self.dispatch_center = DispatchCenter(self) self.history = History(self) @@ -137,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( @@ -250,43 +234,6 @@ class AUTO_MAA(MSFluentWindow): else: self.setStyleSheet("background-color: #ffffff;") - def start_up_task(self) -> None: - """启动时任务""" - - # 清理旧日志 - self.clean_old_logs() - - # 清理安装包 - if (Config.app_path / "AUTO_MAA-Setup.exe").exists(): - try: - (Config.app_path / "AUTO_MAA-Setup.exe").unlink() - except Exception: - pass - - # 检查密码 - self.setting.check_PASSWORD() - - # 获取主题图像 - if Config.get(Config.function_HomeImageMode) == "主题图像": - self.home.get_home_image() - - # 直接运行主任务 - if Config.get(Config.start_IfRunDirectly): - - self.start_main_task() - - # 获取公告 - self.setting.show_notice(if_first=True) - - # 检查更新 - if Config.get(Config.update_IfAutoUpdate): - self.setting.check_update(if_first=True) - - # 直接最小化 - if Config.get(Config.start_IfMinimizeDirectly): - - self.titleBar.minBtn.click() - def set_min_method(self) -> None: """设置最小化方法""" @@ -305,61 +252,6 @@ class AUTO_MAA(MSFluentWindow): if reason == QSystemTrayIcon.DoubleClick: self.show_ui("显示主窗口") - def clean_old_logs(self): - """ - 删除超过用户设定天数的日志文件(基于目录日期) - """ - - if Config.get(Config.function_HistoryRetentionTime) == 0: - logger.info("由于用户设置日志永久保留,跳过日志清理") - return - - deleted_count = 0 - - for date_folder in (Config.app_path / "history").iterdir(): - if not date_folder.is_dir(): - continue # 只处理日期文件夹 - - try: - # 只检查 `YYYY-MM-DD` 格式的文件夹 - folder_date = datetime.strptime(date_folder.name, "%Y-%m-%d") - if datetime.now() - folder_date > timedelta( - days=Config.get(Config.function_HistoryRetentionTime) - ): - shutil.rmtree(date_folder, ignore_errors=True) - deleted_count += 1 - logger.info(f"已删除超期日志目录: {date_folder}") - except ValueError: - logger.warning(f"非日期格式的目录: {date_folder}") - - logger.info(f"清理完成: {deleted_count} 个日期目录") - - def start_main_task(self) -> None: - """启动主任务""" - - if "调度队列_1" in Config.queue_dict: - - logger.info("自动添加任务:调度队列_1") - TaskManager.add_task( - "自动代理_主调度台", - "调度队列_1", - Config.queue_dict["调度队列_1"]["Config"].toDict(), - ) - - elif "脚本_1" in Config.member_dict: - - logger.info("自动添加任务:脚本_1") - TaskManager.add_task( - "自动代理_主调度台", "自定义队列", {"Queue": {"Member_1": "脚本_1"}} - ) - - else: - - logger.warning("启动主任务失败:未找到有效的主任务配置文件") - MainInfoBar.push_info_bar( - "warning", "启动主任务失败", "“调度队列_1”与“脚本_1”均不存在", -1 - ) - def show_ui( self, mode: str, if_quick: bool = False, if_start: bool = False ) -> None: @@ -440,6 +332,109 @@ class AUTO_MAA(MSFluentWindow): self.window().hide() self.tray.show() + def start_up_task(self) -> None: + """启动时任务""" + + # 清理旧日志 + self.clean_old_logs() + + # 清理安装包 + if (Config.app_path / "AUTO_MAA-Setup.exe").exists(): + try: + (Config.app_path / "AUTO_MAA-Setup.exe").unlink() + except Exception: + pass + + # 检查密码 + self.setting.check_PASSWORD() + + # 获取主题图像 + if Config.get(Config.function_HomeImageMode) == "主题图像": + self.home.get_home_image() + + # 直接运行主任务 + if Config.get(Config.start_IfRunDirectly): + + self.start_main_task() + + # 获取公告 + self.setting.show_notice(if_first=True) + + # 检查更新 + if Config.get(Config.update_IfAutoUpdate): + self.setting.check_update(if_first=True) + + # 直接最小化 + if Config.get(Config.start_IfMinimizeDirectly): + + self.titleBar.minBtn.click() + + def clean_old_logs(self): + """ + 删除超过用户设定天数的日志文件(基于目录日期) + """ + + if Config.get(Config.function_HistoryRetentionTime) == 0: + logger.info("由于用户设置日志永久保留,跳过日志清理") + return + + deleted_count = 0 + + for date_folder in (Config.app_path / "history").iterdir(): + if not date_folder.is_dir(): + continue # 只处理日期文件夹 + + try: + # 只检查 `YYYY-MM-DD` 格式的文件夹 + folder_date = datetime.strptime(date_folder.name, "%Y-%m-%d") + if datetime.now() - folder_date > timedelta( + days=Config.get(Config.function_HistoryRetentionTime) + ): + shutil.rmtree(date_folder, ignore_errors=True) + deleted_count += 1 + logger.info(f"已删除超期日志目录: {date_folder}") + except ValueError: + logger.warning(f"非日期格式的目录: {date_folder}") + + logger.info(f"清理完成: {deleted_count} 个日期目录") + + def start_main_task(self) -> None: + """启动主任务""" + + if "调度队列_1" in Config.queue_dict: + + logger.info("自动添加任务:调度队列_1") + TaskManager.add_task( + "自动代理_主调度台", + "调度队列_1", + Config.queue_dict["调度队列_1"]["Config"].toDict(), + ) + + elif "脚本_1" in Config.member_dict: + + logger.info("自动添加任务:脚本_1") + TaskManager.add_task( + "自动代理_主调度台", "自定义队列", {"Queue": {"Member_1": "脚本_1"}} + ) + + else: + + logger.warning("启动主任务失败:未找到有效的主任务配置文件") + MainInfoBar.push_info_bar( + "warning", "启动主任务失败", "“调度队列_1”与“脚本_1”均不存在", -1 + ) + + def __currentChanged(self, index: int) -> None: + """切换界面时任务""" + + 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 cf4e1d6..6adeead 100644 --- a/app/ui/member_manager.py +++ b/app/ui/member_manager.py @@ -67,7 +67,10 @@ from .Widget import ( SpinBoxSettingCard, ComboBoxMessageBox, SettingFlyoutView, - EditableComboBoxSettingCard, + NoOptionComboBoxSettingCard, + ComboBoxWithPlanSettingCard, + EditableComboBoxWithPlanSettingCard, + SpinBoxWithPlanSettingCard, PasswordLineEditSettingCard, UserLableSettingCard, ComboBoxSettingCard, @@ -183,7 +186,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 @@ -223,7 +226,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 @@ -271,7 +274,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 @@ -472,11 +475,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): """脚本管理子页面组""" @@ -841,7 +890,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 @@ -900,7 +949,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 @@ -959,7 +1008,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 @@ -1195,6 +1244,8 @@ class MemberManager(QWidget): else "本周剿灭未完成" ) + gameid_info = config.get_plan_info() + button = PrimaryToolButton( FluentIcon.CHEVRON_RIGHT, self ) @@ -1243,9 +1294,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, @@ -1253,12 +1302,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( @@ -1267,12 +1316,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( @@ -1281,12 +1330,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( @@ -1294,22 +1343,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"] ) ), ) @@ -1333,6 +1380,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="用户名", @@ -1360,15 +1420,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="服务器", @@ -1446,7 +1508,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以关闭", @@ -1455,7 +1517,7 @@ class MemberManager(QWidget): configItem=self.config.Info_MedicineNumb, parent=self, ) - self.card_SeriesNumb = ComboBoxSettingCard( + self.card_SeriesNumb = ComboBoxWithPlanSettingCard( icon=FluentIcon.GAME, title="连战次数", content="连战次数较大时建议搭配剩余理智关卡使用", @@ -1465,7 +1527,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="按下回车以添加自定义关卡号", @@ -1475,7 +1537,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="按下回车以添加自定义关卡号", @@ -1485,7 +1547,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="按下回车以添加自定义关卡号", @@ -1495,18 +1557,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( @@ -1624,10 +1688,14 @@ class MemberManager(QWidget): self.set_infrastructure ) 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: @@ -1646,6 +1714,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 ( diff --git a/app/ui/plan_manager.py b/app/ui/plan_manager.py index f236b88..f96cad3 100644 --- a/app/ui/plan_manager.py +++ b/app/ui/plan_manager.py @@ -40,13 +40,14 @@ from qfluentwidgets import ( CommandBar, TableWidget, ) -from typing import List +from typing import List, Dict, Union import shutil from app.core import Config, MainInfoBar, MaaPlanConfig -from app.services import Crypto from .Widget import ( ComboBoxMessageBox, + LineEditSettingCard, + ComboBoxSettingCard, SpinBoxSetting, EditableComboBoxSetting, ComboBoxSetting, @@ -134,7 +135,7 @@ class PlanManager(QWidget): name = self.plan_manager.pivot.currentRouteKey() - if name == None: + if name is None: logger.warning("删除计划表时未选择计划表") MainInfoBar.push_info_bar( "warning", "未选择计划表", "请选择一个计划表", 5000 @@ -172,7 +173,7 @@ class PlanManager(QWidget): name = self.plan_manager.pivot.currentRouteKey() - if name == None: + if name is None: logger.warning("向左移动计划表时未选择计划表") MainInfoBar.push_info_bar( "warning", "未选择计划表", "请选择一个计划表", 5000 @@ -220,7 +221,7 @@ class PlanManager(QWidget): name = self.plan_manager.pivot.currentRouteKey() - if name == None: + if name is None: logger.warning("向右移动计划表时未选择计划表") MainInfoBar.push_info_bar( "warning", "未选择计划表", "请选择一个计划表", 5000 @@ -321,6 +322,7 @@ class PlanManager(QWidget): """清空所有子界面""" 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() @@ -344,15 +346,35 @@ class PlanManager(QWidget): super().__init__(parent) self.setObjectName(f"计划_{uid}") + self.setTitle("MAA计划表") self.config = Config.plan_dict[f"计划_{uid}"]["Config"] - self.dashboard = TableWidget(self) - self.dashboard.setColumnCount(8) - self.dashboard.setRowCount(6) - self.dashboard.setHorizontalHeaderLabels( + 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.dashboard.setVerticalHeaderLabels( + self.table.setVerticalHeaderLabels( [ "吃理智药", "连战次数", @@ -362,37 +384,60 @@ class PlanManager(QWidget): "剩余理智", ] ) - self.dashboard.setEditTriggers(TableWidget.NoEditTriggers) + self.table.setAlternatingRowColors(False) + self.table.setEditTriggers(TableWidget.NoEditTriggers) for col in range(8): - self.dashboard.horizontalHeader().setSectionResizeMode( + self.table.horizontalHeader().setSectionResizeMode( col, QHeaderView.ResizeMode.Stretch ) + for row in range(6): + self.table.verticalHeader().setSectionResizeMode( + row, QHeaderView.ResizeMode.ResizeToContents + ) - self.viewLayout.addWidget(self.dashboard) - self.viewLayout.setContentsMargins(3, 0, 3, 3) + 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": - setting_item = SpinBoxSetting( + self.item_dict[group][name] = SpinBoxSetting( range=(0, 1024), qconfig=self.config, configItem=configItem, parent=self, ) elif name == "SeriesNumb": - setting_item = ComboBoxSetting( + 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: - setting_item = EditableComboBoxSetting( + self.item_dict[group][name] = EditableComboBoxSetting( value=Config.gameid_dict[group]["value"], texts=Config.gameid_dict[group]["text"], qconfig=self.config, @@ -400,4 +445,51 @@ class PlanManager(QWidget): parent=self, ) - self.dashboard.setCellWidget(row, col, setting_item) + 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 5120167..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 @@ -160,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 @@ -205,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 diff --git a/resources/version.json b/resources/version.json index 622187a..97ed255 100644 --- a/resources/version.json +++ b/resources/version.json @@ -4,7 +4,8 @@ "4.3.8.4": { "新增功能": [ "支持为每一个用户执行独立通知", - "输入文本框适配文本插入操作" + "输入文本框适配文本插入操作", + "计划表功能上线" ] }, "4.3.8.3": { @@ -23,7 +24,7 @@ "日志分析忽略MAA超时提示" ], "程序优化": [ - "配置类定义方法更新,预载入计划表相关配置" + "配置类定义方法更新" ] }, "4.3.8.1": { From 078736337d34d2dd780fb32ca0e2e0f77dd9122d Mon Sep 17 00:00:00 2001 From: DLmaster361 Date: Sun, 25 May 2025 23:31:37 +0800 Subject: [PATCH 26/28] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E9=9B=B7?= =?UTF-8?q?=E7=94=B5=E6=A8=A1=E6=8B=9F=E5=99=A8=E9=9D=99=E9=BB=98=E6=A8=A1?= =?UTF-8?q?=E5=BC=8F=E6=97=A0=E6=B3=95=E6=AD=A3=E5=B8=B8=E8=AF=86=E5=88=AB?= =?UTF-8?q?=E6=A8=A1=E6=8B=9F=E5=99=A8=E6=98=AF=E5=90=A6=E9=9A=90=E8=97=8F?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/core/timer.py | 11 +++++++++++ app/models/MAA.py | 6 +++--- resources/version.json | 6 +++++- 3 files changed, 19 insertions(+), 4 deletions(-) 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 edbef87..c3ccf2a 100644 --- a/app/models/MAA.py +++ b/app/models/MAA.py @@ -567,9 +567,6 @@ class MaaManager(QObject): break time.sleep(1) - # 移除静默进程标记 - Config.silence_list.remove(self.emulator_path) - # 任务结束后释放ADB try: subprocess.run( @@ -908,6 +905,9 @@ class MaaManager(QObject): 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]) diff --git a/resources/version.json b/resources/version.json index 97ed255..46119a3 100644 --- a/resources/version.json +++ b/resources/version.json @@ -5,7 +5,11 @@ "新增功能": [ "支持为每一个用户执行独立通知", "输入文本框适配文本插入操作", - "计划表功能上线" + "计划表功能上线", + "静默控制时长从全任务内缩短至搜索ADB时段内" + ], + "修复bug": [ + "修复雷电模拟器静默模式无法正常识别模拟器是否隐藏相关问题" ] }, "4.3.8.3": { From a752b67ca16c61fe93a3487bd9edef5990cc7367 Mon Sep 17 00:00:00 2001 From: DLmaster361 Date: Mon, 26 May 2025 16:05:46 +0800 Subject: [PATCH 27/28] =?UTF-8?q?feat(ui):=20UI=E7=95=8C=E9=9D=A2=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E8=87=AA=E5=8A=A8=E6=97=A5=E5=B8=B8=E4=BB=A3=E7=90=86?= =?UTF-8?q?=E4=BB=BB=E5=8A=A1=E5=BA=8F=E5=88=97=E8=AE=BE=E7=BD=AE=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/core/config.py | 16 +++- app/models/MAA.py | 99 +++++++++++----------- app/ui/Widget.py | 179 ++++++++++++++++++++++++++------------- app/ui/member_manager.py | 133 ++++++++++++++++++++++++++--- resources/version.json | 3 +- 5 files changed, 310 insertions(+), 120 deletions(-) diff --git a/app/core/config.py b/app/core/config.py index c27f409..4e72895 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -434,7 +434,21 @@ class MaaUserConfig(LQConfig): "Data", "CustomInfrastPlanIndex", "0" ) - # 新增用户单独通知字段 + 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() + ) + self.Notify_Enabled = ConfigItem("Notify", "Enabled", False, BoolValidator()) self.Notify_IfSendStatistic = ConfigItem( "Notify", "IfSendStatistic", False, BoolValidator() diff --git a/app/models/MAA.py b/app/models/MAA.py index c3ccf2a..d43de4e 100644 --- a/app/models/MAA.py +++ b/app/models/MAA.py @@ -274,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", @@ -288,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"]): @@ -1275,6 +1277,9 @@ class MaaManager(QObject): ]["Id"] # 按预设设定任务 + data["Configurations"]["Default"][ + "TaskQueue.WakeUp.IsChecked" + ] = "True" # 开始唤醒 data["Configurations"]["Default"]["TaskQueue.Recruiting.IsChecked"] = ( self.task_dict["Recruiting"] ) # 自动公招 @@ -1299,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" ][ diff --git a/app/ui/Widget.py b/app/ui/Widget.py index db85a2d..16b40be 100644 --- a/app/ui/Widget.py +++ b/app/ui/Widget.py @@ -82,6 +82,7 @@ from qfluentwidgets import ( Pivot, PivotItem, FlyoutViewBase, + PushSettingCard, ) from qfluentwidgets.common.overload import singledispatchmethod @@ -306,6 +307,8 @@ class SettingFlyoutView(FlyoutViewBase): self.viewLayout.addWidget(self.title) self.viewLayout.addWidget(scrollArea) + self.setVisible(False) + class SwitchSettingCard(SettingCard): """Setting card with switch button""" @@ -557,57 +560,6 @@ class PasswordLineEditSettingCard(SettingCard): self.LineEdit.textChanged.connect(self.__textChanged) -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)) - - class PushAndSwitchButtonSettingCard(SettingCard): """Setting card with push & switch button""" @@ -1128,6 +1080,115 @@ 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""" @@ -1184,7 +1245,7 @@ class UserNoticeSettingCard(PushAndSwitchButtonSettingCard): # 普通字符串:末尾3字符 return f"***{s[-3:]}" if len(s) > 3 else s - content_list = [] + text_list = [] if self.configItems: @@ -1192,27 +1253,27 @@ class UserNoticeSettingCard(PushAndSwitchButtonSettingCard): self.qconfig.get(self.configItems["IfSendStatistic"]) or self.qconfig.get(self.configItems["IfSendSixStar"]) ): - content_list.append("未启用任何通知项") + text_list.append("未启用任何通知项") if self.qconfig.get(self.configItems["IfSendStatistic"]): - content_list.append("统计信息已启用") + text_list.append("统计信息已启用") if self.qconfig.get(self.configItems["IfSendSixStar"]): - content_list.append("六星喜报已启用") + text_list.append("六星喜报已启用") if self.qconfig.get(self.configItems["IfSendMail"]): - content_list.append( + text_list.append( f"邮箱通知:{short_str(self.qconfig.get(self.configItems["ToAddress"]))}" ) if self.qconfig.get(self.configItems["IfServerChan"]): - content_list.append( + text_list.append( f"Server酱通知:{short_str(self.qconfig.get(self.configItems["ServerChanKey"]))}" ) if self.qconfig.get(self.configItems["IfCompanyWebHookBot"]): - content_list.append( + text_list.append( f"企业微信通知:{short_str(self.qconfig.get(self.configItems["CompanyWebHookBotUrl"]))}" ) - self.setContent(" | ".join(content_list)) + self.setContent(" | ".join(text_list)) class StatusSwitchSetting(SwitchButton): diff --git a/app/ui/member_manager.py b/app/ui/member_manager.py index 6adeead..82865af 100644 --- a/app/ui/member_manager.py +++ b/app/ui/member_manager.py @@ -73,6 +73,7 @@ from .Widget import ( SpinBoxWithPlanSettingCard, PasswordLineEditSettingCard, UserLableSettingCard, + UserTaskSettingCard, ComboBoxSettingCard, SwitchSettingCard, PushAndSwitchButtonSettingCard, @@ -1587,7 +1588,106 @@ 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="用户单独通知设置", @@ -1618,17 +1718,16 @@ class MemberManager(QWidget): self.CompanyWechatPushSettingCard(self.config, self) ) - self.card_NotifySet_list = [ - self.card_NotifyContent, - self.card_EMail, - self.card_ServerChan, - self.card_CompanyWebhookBot, - ] - self.NotifySetCard = SettingFlyoutView( - self, "用户通知设置", self.card_NotifySet_list + self, + "用户通知设置", + [ + self.card_NotifyContent, + self.card_EMail, + self.card_ServerChan, + self.card_CompanyWebhookBot, + ], ) - self.NotifySetCard.setVisible(False) h1_layout = QHBoxLayout() h1_layout.addWidget(self.card_Name) @@ -1667,6 +1766,7 @@ 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) @@ -1687,6 +1787,7 @@ 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 @@ -1851,6 +1952,18 @@ 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: """设置用户通知相关配置""" diff --git a/resources/version.json b/resources/version.json index 46119a3..bc0525c 100644 --- a/resources/version.json +++ b/resources/version.json @@ -6,7 +6,8 @@ "支持为每一个用户执行独立通知", "输入文本框适配文本插入操作", "计划表功能上线", - "静默控制时长从全任务内缩短至搜索ADB时段内" + "静默控制时长从全任务内缩短至搜索ADB时段内", + "UI界面添加自动日常代理任务序列设置项" ], "修复bug": [ "修复雷电模拟器静默模式无法正常识别模拟器是否隐藏相关问题" From d50504181e034f05d748485306b5decaca7b368d Mon Sep 17 00:00:00 2001 From: DLmaster361 Date: Wed, 28 May 2025 21:17:14 +0800 Subject: [PATCH 28/28] =?UTF-8?q?feat(ui):=20=E5=90=90=E5=8F=B8=E9=80=9A?= =?UTF-8?q?=E7=9F=A5=E5=9C=A8=E4=B8=BB=E7=AA=97=E5=8F=A3=E9=9A=90=E8=97=8F?= =?UTF-8?q?=E6=97=B6=E4=B8=8D=E5=86=8D=E5=BC=B9=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/core/config.py | 3 ++- app/core/main_info_bar.py | 41 ++++++++++++++++++++++++++------------- app/ui/main_window.py | 9 +++++++++ app/ui/setting.py | 3 ++- resources/version.json | 17 +++++++--------- 5 files changed, 48 insertions(+), 25 deletions(-) diff --git a/app/core/config.py b/app/core/config.py index 4e72895..b99afff 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -567,7 +567,7 @@ class MaaPlanConfig(LQConfig): class AppConfig(GlobalConfig): - VERSION = "4.3.8.4" + VERSION = "4.3.8.0" gameid_refreshed = Signal() PASSWORD_refreshed = Signal() @@ -591,6 +591,7 @@ class AppConfig(GlobalConfig): self.PASSWORD = "" self.running_list = [] self.silence_list = [] + self.info_bar_list = [] self.gameid_dict = { "ALL": {"value": [], "text": []}, "Monday": {"value": [], "text": []}, 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/ui/main_window.py b/app/ui/main_window.py index 76aac32..a7a72d0 100644 --- a/app/ui/main_window.py +++ b/app/ui/main_window.py @@ -302,6 +302,15 @@ class AUTO_MAA(MSFluentWindow): 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): diff --git a/app/ui/setting.py b/app/ui/setting.py index e26ead3..84fda41 100644 --- a/app/ui/setting.py +++ b/app/ui/setting.py @@ -462,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) @@ -545,7 +546,7 @@ class Setting(QWidget): ): MainInfoBar.push_info_bar( - "info", "有新公告", "请前往设置界面查看公告", 3600000 + "info", "有新公告", "请前往设置界面查看公告", 3600000, if_force=True ) return None diff --git a/resources/version.json b/resources/version.json index bc0525c..c0e4e71 100644 --- a/resources/version.json +++ b/resources/version.json @@ -1,6 +1,11 @@ { - "main_version": "4.3.8.4", + "main_version": "4.3.8.0", "version_info": { + "4.3.8.0": { + "新增功能": [ + "吐司通知在主窗口隐藏时不再弹出" + ] + }, "4.3.8.4": { "新增功能": [ "支持为每一个用户执行独立通知", @@ -29,7 +34,7 @@ "日志分析忽略MAA超时提示" ], "程序优化": [ - "配置类定义方法更新" + "配置类定义方法优化" ] }, "4.3.8.1": { @@ -44,14 +49,6 @@ "程序优化": [ "UI样式优化,进一步适配win10主题" ] - }, - "4.3.7.0": { - "新增功能": [ - "下载器支持完整mirrorc列表" - ], - "程序优化": [ - "重构更新逻辑,去除独立更新器" - ] } } } \ No newline at end of file