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