# 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.4 作者:DLmaster_361 """ from loguru import logger from PySide6.QtWidgets import ( QWidget, QFileDialog, QHBoxLayout, QVBoxLayout, QStackedWidget, QTableWidgetItem, QHeaderView, ) from PySide6.QtGui import QIcon, Qt from qfluentwidgets import ( Action, ConfigItem, ScrollArea, FluentIcon, MessageBox, HeaderCardWidget, CommandBar, ExpandGroupSettingCard, PushSettingCard, TableWidget, PrimaryToolButton, Flyout, FlyoutAnimationType, ) from PySide6.QtCore import Signal from datetime import datetime from functools import partial from pathlib import Path from typing import List, Union, Type import shutil import json from app.core import ( Config, MainInfoBar, TaskManager, MaaConfig, MaaUserConfig, GeneralConfig, GeneralSubConfig, Network, SoundPlayer, ) from app.services import Crypto from .downloader import DownloadManager from .Widget import ( LineEditMessageBox, LineEditSettingCard, SpinBoxSettingCard, ComboBoxMessageBox, SettingFlyoutView, NoOptionComboBoxSettingCard, ComboBoxWithPlanSettingCard, EditableComboBoxWithPlanSettingCard, SpinBoxWithPlanSettingCard, PasswordLineEditSettingCard, PasswordLineAndSwitchButtonSettingCard, UserLableSettingCard, UserTaskSettingCard, SubLableSettingCard, ComboBoxSettingCard, SwitchSettingCard, PathSettingCard, PushAndSwitchButtonSettingCard, PushAndComboBoxSettingCard, StatusSwitchSetting, UserNoticeSettingCard, PivotArea, ) class MemberManager(QWidget): """脚本管理父界面""" def __init__(self, parent=None): super().__init__(parent) self.setObjectName("脚本管理") layout = QVBoxLayout(self) self.tools = CommandBar() self.member_manager = self.MemberSettingBox(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() self.tools.addAction( Action( FluentIcon.DOWNLOAD, "脚本下载器", triggered=self.member_downloader, ) ) self.tools.addSeparator() self.key = Action( FluentIcon.HIDE, "显示/隐藏密码", checkable=True, triggered=self.show_password, ) self.tools.addAction(self.key) layout.addWidget(self.tools) layout.addWidget(self.member_manager) 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.member_dict) + 1 maa_config = MaaConfig() maa_config.load( Config.app_path / f"config/MaaConfig/脚本_{index}/config.json", maa_config, ) maa_config.save() (Config.app_path / f"config/MaaConfig/脚本_{index}/UserData").mkdir( parents=True, exist_ok=True ) Config.member_dict[f"脚本_{index}"] = { "Type": "Maa", "Path": Config.app_path / f"config/MaaConfig/脚本_{index}", "Config": maa_config, "UserData": {}, } self.member_manager.add_SettingBox( index, self.MemberSettingBox.MaaSettingBox ) self.member_manager.switch_SettingBox(index) logger.success(f"MAA实例 脚本_{index} 添加成功") MainInfoBar.push_info_bar( "success", "操作成功", f"添加 MAA 实例 脚本_{index}", 3000 ) SoundPlayer.play("添加脚本实例") elif choice.input[0].currentText() == "通用": index = len(Config.member_dict) + 1 general_config = GeneralConfig() general_config.load( Config.app_path / f"config/GeneralConfig/脚本_{index}/config.json", general_config, ) general_config.save() (Config.app_path / f"config/GeneralConfig/脚本_{index}/SubData").mkdir( parents=True, exist_ok=True ) Config.member_dict[f"脚本_{index}"] = { "Type": "General", "Path": Config.app_path / f"config/GeneralConfig/脚本_{index}", "Config": general_config, "SubData": {}, } self.member_manager.add_SettingBox( index, self.MemberSettingBox.GeneralSettingBox ) self.member_manager.switch_SettingBox(index) logger.success(f"通用实例 脚本_{index} 添加成功") MainInfoBar.push_info_bar( "success", "操作成功", f"添加通用实例 脚本_{index}", 3000 ) SoundPlayer.play("添加脚本实例") def del_setting_box(self): """删除一个脚本实例""" name = self.member_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.member_manager.clear_SettingBox() shutil.rmtree(Config.member_dict[name]["Path"]) Config.change_queue(name, "禁用") for i in range(int(name[3:]) + 1, len(Config.member_dict) + 1): if Config.member_dict[f"脚本_{i}"]["Path"].exists(): Config.member_dict[f"脚本_{i}"]["Path"].rename( Config.member_dict[f"脚本_{i}"]["Path"].with_name(f"脚本_{i-1}") ) Config.change_queue(f"脚本_{i}", f"脚本_{i-1}") self.member_manager.show_SettingBox(max(int(name[3:]) - 1, 1)) logger.success(f"脚本实例 {name} 删除成功") MainInfoBar.push_info_bar( "success", "操作成功", f"删除脚本实例 {name}", 3000 ) SoundPlayer.play("删除脚本实例") def left_setting_box(self): """向左移动脚本实例""" name = self.member_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.member_manager.clear_SettingBox() Config.member_dict[name]["Path"].rename( Config.member_dict[name]["Path"].with_name("脚本_0") ) Config.change_queue(name, "脚本_0") Config.member_dict[f"脚本_{index-1}"]["Path"].rename( Config.member_dict[f"脚本_{index-1}"]["Path"].with_name(name) ) Config.change_queue(f"脚本_{index-1}", name) Config.member_dict[name]["Path"].with_name("脚本_0").rename( Config.member_dict[name]["Path"].with_name(f"脚本_{index-1}") ) Config.change_queue("脚本_0", f"脚本_{index-1}") self.member_manager.show_SettingBox(index - 1) logger.success(f"脚本实例 {name} 左移成功") MainInfoBar.push_info_bar("success", "操作成功", f"左移脚本实例 {name}", 3000) def right_setting_box(self): """向右移动脚本实例""" name = self.member_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.member_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.member_manager.clear_SettingBox() Config.member_dict[name]["Path"].rename( Config.member_dict[name]["Path"].with_name("脚本_0") ) Config.change_queue(name, "脚本_0") Config.member_dict[f"脚本_{index+1}"]["Path"].rename( Config.member_dict[f"脚本_{index+1}"]["Path"].with_name(name) ) Config.change_queue(f"脚本_{index+1}", name) Config.member_dict[name]["Path"].with_name("脚本_0").rename( Config.member_dict[name]["Path"].with_name(f"脚本_{index+1}") ) Config.change_queue("脚本_0", f"脚本_{index+1}") self.member_manager.show_SettingBox(index + 1) logger.success(f"脚本实例 {name} 右移成功") MainInfoBar.push_info_bar("success", "操作成功", f"右移脚本实例 {name}", 3000) def member_downloader(self): """脚本下载器""" if not Config.get(Config.update_MirrorChyanCDK): logger.warning("脚本下载器未设置CDK") MainInfoBar.push_info_bar( "warning", "未设置Mirror酱CDK", "下载器依赖于Mirror酱,未设置CDK时无法使用", 5000, ) return None # 从远程服务器获取应用列表 network = Network.add_task( mode="get", url="https://gitee.com/DLmaster_361/AUTO_MAA/raw/server/apps_info.json", ) network.loop.exec() network_result = Network.get_result(network) if network_result["status_code"] == 200: apps_info = network_result["response_json"] else: logger.warning(f"获取应用列表时出错:{network_result['error_message']}") MainInfoBar.push_info_bar( "warning", "获取应用列表时出错", f"网络错误:{network_result['status_code']}", 5000, ) return None choice = ComboBoxMessageBox( self.window(), "选择一个脚本类型以下载相应脚本", ["选择脚本类型"], [list(apps_info.keys())], ) if choice.exec() and choice.input[0].currentIndex() != -1: app_name = choice.input[0].currentText() app_rid = apps_info[app_name]["rid"] (Config.app_path / f"script/{app_rid}").mkdir(parents=True, exist_ok=True) folder = QFileDialog.getExistingDirectory( self, f"选择{app_name}下载目录", str(Config.app_path / f"script/{app_rid}"), ) if not folder: logger.warning(f"选择{app_name}下载目录时未选择文件夹") MainInfoBar.push_info_bar( "warning", "警告", f"未选择{app_name}下载目录", 5000 ) return None # 从mirrorc服务器获取最新版本信息 network = Network.add_task( mode="get", url=f"https://mirrorchyan.com/api/resources/{app_rid}/latest?user_agent=AutoMaaGui&cdk={Crypto.win_decryptor(Config.get(Config.update_MirrorChyanCDK))}&os={apps_info[app_name]["os"]}&arch={apps_info[app_name]["arch"]}&channel=stable", ) network.loop.exec() network_result = Network.get_result(network) if network_result["status_code"] == 200: app_info = network_result["response_json"] else: if network_result["response_json"]: app_info = network_result["response_json"] if app_info["code"] != 0: logger.error(f"获取版本信息时出错:{app_info["msg"]}") error_remark_dict = { 1001: "获取版本信息的URL参数不正确", 7001: "填入的 CDK 已过期", 7002: "填入的 CDK 错误", 7003: "填入的 CDK 今日下载次数已达上限", 7004: "填入的 CDK 类型和待下载的资源不匹配", 7005: "填入的 CDK 已被封禁", 8001: "对应架构和系统下的资源不存在", 8002: "错误的系统参数", 8003: "错误的架构参数", 8004: "错误的更新通道参数", 1: app_info["msg"], } if app_info["code"] in error_remark_dict: MainInfoBar.push_info_bar( "error", "获取版本信息时出错", error_remark_dict[app_info["code"]], -1, ) else: MainInfoBar.push_info_bar( "error", "获取版本信息时出错", "意料之外的错误,请及时联系项目组以获取来自 Mirror 酱的技术支持", -1, ) return None logger.warning(f"获取版本信息时出错:{network_result['error_message']}") MainInfoBar.push_info_bar( "warning", "获取版本信息时出错", f"网络错误:{network_result['status_code']}", 5000, ) return None self.downloader = DownloadManager( Path(folder), app_rid, None, { "mode": "MirrorChyan", "thread_numb": 1, "url": app_info["data"]["url"], }, ) self.downloader.setWindowTitle("AUTO_MAA下载器 - Mirror酱渠道") self.downloader.setWindowIcon( QIcon(str(Config.app_path / "resources/icons/MirrorChyan.ico")) ) self.downloader.show() self.downloader.run() def show_password(self): if Config.PASSWORD == "": choice = LineEditMessageBox( self.window(), "请输入管理密钥", "管理密钥", "密码", ) if choice.exec() and choice.input.text() != "": Config.PASSWORD = choice.input.text() Config.PASSWORD_refreshed.emit() self.key.setIcon(FluentIcon.VIEW) self.key.setChecked(True) else: Config.PASSWORD = "" Config.PASSWORD_refreshed.emit() self.key.setIcon(FluentIcon.HIDE) self.key.setChecked(False) else: Config.PASSWORD = "" Config.PASSWORD_refreshed.emit() 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_StageMode.comboBox.currentIndexChanged.disconnect( user_setting.switch_stage_mode ) user_setting.card_StageMode.reLoadOptions( plan_list[0], plan_list[1] ) user_setting.card_StageMode.comboBox.currentIndexChanged.connect( user_setting.switch_stage_mode ) self.refresh_plan_info() def refresh_dashboard(self): """刷新所有脚本实例的仪表盘""" for member in self.member_manager.script_list: if isinstance(member, MemberManager.MemberSettingBox.MaaSettingBox): member.user_setting.user_manager.user_dashboard.load_info() elif isinstance(member, MemberManager.MemberSettingBox.GeneralSettingBox): member.branch_manager.sub_manager.sub_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_stage_mode() class MemberSettingBox(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[ Union[ MemberManager.MemberSettingBox.MaaSettingBox, MemberManager.MemberSettingBox.GeneralSettingBox, ] ] = [] 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_member() for name, info in Config.member_dict.items(): if info["Type"] == "Maa": self.add_SettingBox(int(name[3:]), self.MaaSettingBox) elif info["Type"] == "General": self.add_SettingBox(int(name[3:]), self.GeneralSettingBox) self.switch_SettingBox(index) def switch_SettingBox(self, index: int, if_chang_pivot: bool = True) -> None: """切换到指定的子界面""" if len(Config.member_dict) == 0: return None if index > len(Config.member_dict): return None if if_chang_pivot: self.pivot.setCurrentItem(self.script_list[index - 1].objectName()) self.stackedWidget.setCurrentWidget(self.script_list[index - 1]) if isinstance( self.script_list[index - 1], MemberManager.MemberSettingBox.MaaSettingBox, ): self.script_list[index - 1].user_setting.user_manager.switch_SettingBox( "用户仪表盘" ) elif isinstance( self.script_list[index - 1], MemberManager.MemberSettingBox.GeneralSettingBox, ): self.script_list[ index - 1 ].branch_manager.sub_manager.switch_SettingBox("配置仪表盘") def clear_SettingBox(self) -> None: """清空所有子界面""" for sub_interface in self.script_list: self.stackedWidget.removeWidget(sub_interface) sub_interface.deleteLater() self.script_list.clear() self.pivot.clear() def add_SettingBox(self, uid: int, type: Type) -> None: """添加指定类型设置子界面""" if type == self.MaaSettingBox: setting_box = self.MaaSettingBox(uid, self) elif type == self.GeneralSettingBox: setting_box = self.GeneralSettingBox(uid, self) else: return None self.script_list.append(setting_box) self.stackedWidget.addWidget(self.script_list[-1]) self.pivot.addItem(routeKey=f"脚本_{uid}", text=f"脚本 {uid}") class MaaSettingBox(QWidget): """MAA类脚本设置界面""" def __init__(self, uid: int, parent=None): super().__init__(parent) self.setObjectName(f"脚本_{uid}") self.config = Config.member_dict[f"脚本_{uid}"]["Config"] 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;") scrollArea.setWidget(content_widget) layout = QVBoxLayout(self) layout.addWidget(scrollArea) class AppSettingCard(HeaderCardWidget): def __init__(self, name: str, config: MaaConfig, parent=None): super().__init__(parent) self.setTitle("MAA实例") self.name = name self.config = config Layout = QVBoxLayout() self.card_Name = LineEditSettingCard( icon=FluentIcon.EDIT, title="实例名称", content="用于标识MAA实例的名称", text="请输入实例名称", qconfig=self.config, configItem=self.config.MaaSet_Name, parent=self, ) self.card_Path = PushSettingCard( text="选择文件夹", icon=FluentIcon.FOLDER, title="MAA目录", content=self.config.get(self.config.MaaSet_Path), parent=self, ) self.card_Set = PushSettingCard( text="设置", icon=FluentIcon.HOME, title="MAA全局配置", content="简洁模式下MAA将继承全局配置", parent=self, ) self.RunSet = self.RunSetSettingCard(self.config, self) self.card_Path.clicked.connect(self.PathClicked) self.config.MaaSet_Path.valueChanged.connect( lambda: self.card_Path.setContent( self.config.get(self.config.MaaSet_Path) ) ) self.card_Set.clicked.connect( lambda: TaskManager.add_task("设置MAA_全局", self.name, None) ) Layout.addWidget(self.card_Name) Layout.addWidget(self.card_Path) Layout.addWidget(self.card_Set) Layout.addWidget(self.RunSet) self.viewLayout.addLayout(Layout) def PathClicked(self): folder = QFileDialog.getExistingDirectory( self, "选择MAA目录", self.config.get(self.config.MaaSet_Path), ) if not folder or self.config.get(self.config.MaaSet_Path) == folder: logger.warning("选择MAA目录时未选择文件夹或未更改文件夹") MainInfoBar.push_info_bar( "warning", "警告", "未选择文件夹或未更改文件夹", 5000 ) return None elif ( not (Path(folder) / "config/gui.json").exists() or not (Path(folder) / "MAA.exe").exists() ): logger.warning("选择MAA目录时未找到MAA程序或配置文件") MainInfoBar.push_info_bar( "warning", "警告", "未找到MAA程序或配置文件", 5000 ) return None (Config.member_dict[self.name]["Path"] / "Default").mkdir( parents=True, exist_ok=True ) shutil.copy( Path(folder) / "config/gui.json", Config.member_dict[self.name]["Path"] / "Default/gui.json", ) self.config.set(self.config.MaaSet_Path, folder) class RunSetSettingCard(ExpandGroupSettingCard): def __init__(self, config: MaaConfig, parent=None): super().__init__( FluentIcon.SETTING, "运行", "MAA运行调控选项", parent ) self.config = config self.card_TaskTransitionMethod = ComboBoxSettingCard( icon=FluentIcon.PAGE_RIGHT, title="任务切换方式", content="相邻两个任务间的切换方式,使用「详细」配置的用户固定为「重启模拟器」", texts=["直接切换账号", "重启明日方舟", "重启模拟器"], qconfig=self.config, configItem=self.config.RunSet_TaskTransitionMethod, parent=self, ) self.card_ProxyTimesLimit = SpinBoxSettingCard( icon=FluentIcon.PAGE_RIGHT, title="用户单日代理次数上限", content="当用户本日代理成功次数达到该阈值时跳过代理,阈值为「0」时视为无代理次数上限", range=(0, 1024), qconfig=self.config, 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="代理重试次数限制", content="若超过该次数限制仍未完成代理,视为代理失败", range=(1, 1024), qconfig=self.config, configItem=self.config.RunSet_RunTimesLimit, parent=self, ) self.card_AnnihilationTimeLimit = SpinBoxSettingCard( icon=FluentIcon.PAGE_RIGHT, title="剿灭代理超时限制", content="MAA日志无变化时间超过该阈值视为超时,单位为分钟", range=(1, 1024), qconfig=self.config, configItem=self.config.RunSet_AnnihilationTimeLimit, parent=self, ) self.card_RoutineTimeLimit = SpinBoxSettingCard( icon=FluentIcon.PAGE_RIGHT, title="自动代理超时限制", content="MAA日志无变化时间超过该阈值视为超时,单位为分钟", range=(1, 1024), qconfig=self.config, configItem=self.config.RunSet_RoutineTimeLimit, parent=self, ) self.card_AnnihilationWeeklyLimit = SwitchSettingCard( icon=FluentIcon.PAGE_RIGHT, title="每周剿灭仅执行到上限", content="每周剿灭模式执行到上限,本周剩下时间不再执行剿灭任务", qconfig=self.config, configItem=self.config.RunSet_AnnihilationWeeklyLimit, parent=self, ) self.card_AutoUpdateMaa = SwitchSettingCard( icon=FluentIcon.PAGE_RIGHT, title="自动代理时自动更新MAA", content="执行自动代理任务时自动更新MAA,关闭后仍会进行MAA版本检查", qconfig=self.config, configItem=self.config.RunSet_AutoUpdateMaa, parent=self, ) widget = 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) Layout.addWidget(self.card_AnnihilationWeeklyLimit) Layout.addWidget(self.card_AutoUpdateMaa) self.viewLayout.setContentsMargins(0, 0, 0, 0) self.viewLayout.setSpacing(0) self.addGroupWidget(widget) class UserManager(HeaderCardWidget): """用户管理父页面""" def __init__(self, name: str, parent=None): super().__init__(parent) self.setObjectName(f"{name}_用户管理") self.setTitle("下属用户") self.name = name self.tools = CommandBar() self.user_manager = self.UserSettingBox(self.name, self) # 逐个添加动作 self.tools.addActions( [ Action( FluentIcon.ADD_TO, "新建用户", triggered=self.add_user ), Action( FluentIcon.REMOVE_FROM, "删除用户", triggered=self.del_user, ), ] ) self.tools.addSeparator() self.tools.addActions( [ Action( FluentIcon.LEFT_ARROW, "向前移动", triggered=self.left_user, ), Action( FluentIcon.RIGHT_ARROW, "向后移动", triggered=self.right_user, ), ] ) layout = QVBoxLayout() layout.addWidget(self.tools) layout.addWidget(self.user_manager) self.viewLayout.addLayout(layout) def add_user(self): """添加一个用户""" index = len(Config.member_dict[self.name]["UserData"]) + 1 user_config = MaaUserConfig() user_config.load( Config.member_dict[self.name]["Path"] / f"UserData/用户_{index}/config.json", user_config, ) user_config.save() Config.member_dict[self.name]["UserData"][f"用户_{index}"] = { "Path": Config.member_dict[self.name]["Path"] / f"UserData/用户_{index}", "Config": user_config, } self.user_manager.add_userSettingBox(index) self.user_manager.switch_SettingBox(f"用户_{index}") logger.success(f"{self.name} 用户_{index} 添加成功") MainInfoBar.push_info_bar( "success", "操作成功", f"{self.name} 添加 用户_{index}", 3000 ) SoundPlayer.play("添加用户") def del_user(self): """删除一个用户""" name = self.user_manager.pivot.currentRouteKey() if name is None: logger.warning("未选择用户") MainInfoBar.push_info_bar( "warning", "未选择用户", "请先选择一个用户", 5000 ) return None if name == "用户仪表盘": logger.warning("试图删除用户仪表盘") MainInfoBar.push_info_bar( "warning", "未选择用户", "请勿尝试删除用户仪表盘", 5000 ) return None if self.name in Config.running_list: logger.warning("所属脚本正在运行") MainInfoBar.push_info_bar( "warning", "所属脚本正在运行", "请先停止任务", 5000 ) return None choice = MessageBox( "确认", f"确定要删除 {name} 吗?", self.window() ) if choice.exec(): self.user_manager.clear_SettingBox() shutil.rmtree( Config.member_dict[self.name]["UserData"][name]["Path"] ) for i in range( int(name[3:]) + 1, len(Config.member_dict[self.name]["UserData"]) + 1, ): if Config.member_dict[self.name]["UserData"][f"用户_{i}"][ "Path" ].exists(): Config.member_dict[self.name]["UserData"][f"用户_{i}"][ "Path" ].rename( Config.member_dict[self.name]["UserData"][ f"用户_{i}" ]["Path"].with_name(f"用户_{i-1}") ) self.user_manager.show_SettingBox( f"用户_{max(int(name[3:]) - 1, 1)}" ) logger.success(f"{self.name} {name} 删除成功") MainInfoBar.push_info_bar( "success", "操作成功", f"{self.name} 删除 {name}", 3000 ) SoundPlayer.play("删除用户") def left_user(self): """向前移动用户""" name = self.user_manager.pivot.currentRouteKey() if name is None: logger.warning("未选择用户") MainInfoBar.push_info_bar( "warning", "未选择用户", "请先选择一个用户", 5000 ) return None if name == "用户仪表盘": logger.warning("试图移动用户仪表盘") MainInfoBar.push_info_bar( "warning", "未选择用户", "请勿尝试移动用户仪表盘", 5000 ) return None index = int(name[3:]) if index == 1: logger.warning("向前移动用户时已到达最左端") MainInfoBar.push_info_bar( "warning", "已经是第一个用户", "无法向前移动", 5000 ) return None if self.name in Config.running_list: logger.warning("所属脚本正在运行") MainInfoBar.push_info_bar( "warning", "所属脚本正在运行", "请先停止任务", 5000 ) return None self.user_manager.clear_SettingBox() Config.member_dict[self.name]["UserData"][name]["Path"].rename( Config.member_dict[self.name]["UserData"][name][ "Path" ].with_name("用户_0") ) Config.member_dict[self.name]["UserData"][f"用户_{index-1}"][ "Path" ].rename(Config.member_dict[self.name]["UserData"][name]["Path"]) Config.member_dict[self.name]["UserData"][name]["Path"].with_name( "用户_0" ).rename( Config.member_dict[self.name]["UserData"][f"用户_{index-1}"][ "Path" ] ) self.user_manager.show_SettingBox(f"用户_{index - 1}") logger.success(f"{self.name} {name} 前移成功") MainInfoBar.push_info_bar( "success", "操作成功", f"{self.name} 前移 {name}", 3000 ) def right_user(self): """向后移动用户""" name = self.user_manager.pivot.currentRouteKey() if name is None: logger.warning("未选择用户") MainInfoBar.push_info_bar( "warning", "未选择用户", "请先选择一个用户", 5000 ) return None if name == "用户仪表盘": logger.warning("试图删除用户仪表盘") MainInfoBar.push_info_bar( "warning", "未选择用户", "请勿尝试移动用户仪表盘", 5000 ) return None index = int(name[3:]) if index == len(Config.member_dict[self.name]["UserData"]): logger.warning("向后移动用户时已到达最右端") MainInfoBar.push_info_bar( "warning", "已经是最后一个用户", "无法向后移动", 5000 ) return None if self.name in Config.running_list: logger.warning("所属脚本正在运行") MainInfoBar.push_info_bar( "warning", "所属脚本正在运行", "请先停止任务", 5000 ) return None self.user_manager.clear_SettingBox() Config.member_dict[self.name]["UserData"][name]["Path"].rename( Config.member_dict[self.name]["UserData"][name][ "Path" ].with_name("用户_0") ) Config.member_dict[self.name]["UserData"][f"用户_{index+1}"][ "Path" ].rename(Config.member_dict[self.name]["UserData"][name]["Path"]) Config.member_dict[self.name]["UserData"][name]["Path"].with_name( "用户_0" ).rename( Config.member_dict[self.name]["UserData"][f"用户_{index+1}"][ "Path" ] ) self.user_manager.show_SettingBox(f"用户_{index + 1}") logger.success(f"{self.name} {name} 后移成功") MainInfoBar.push_info_bar( "success", "操作成功", f"{self.name} 后移 {name}", 3000 ) class UserSettingBox(QWidget): """用户管理子页面组""" def __init__(self, name: str, parent=None): super().__init__(parent) self.setObjectName("用户管理") self.name = name self.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[ MemberManager.MemberSettingBox.MaaSettingBox.UserManager.UserSettingBox.UserMemberSettingBox ] = [] self.user_dashboard = self.UserDashboard(self.name, self) self.user_dashboard.switch_to.connect(self.switch_SettingBox) self.stackedWidget.addWidget(self.user_dashboard) self.pivot.addItem(routeKey="用户仪表盘", text="用户仪表盘") self.Layout = 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( index, if_change_pivot=False ) ) self.show_SettingBox("用户仪表盘") def show_SettingBox(self, index: str) -> None: """加载所有子界面""" Config.search_maa_user(self.name) for name in Config.member_dict[self.name]["UserData"].keys(): self.add_userSettingBox(name[3:]) self.switch_SettingBox(index) def switch_SettingBox( self, index: str, if_change_pivot: bool = True ) -> None: """切换到指定的子界面""" if len(Config.member_dict[self.name]["UserData"]) == 0: index = "用户仪表盘" if index != "用户仪表盘" and int(index[3:]) > len( Config.member_dict[self.name]["UserData"] ): return None if index == "用户仪表盘": self.user_dashboard.load_info() if if_change_pivot: self.pivot.setCurrentItem(index) self.stackedWidget.setCurrentWidget( self.user_dashboard if index == "用户仪表盘" else self.script_list[int(index[3:]) - 1] ) def clear_SettingBox(self) -> None: """清空所有子界面""" for sub_interface in self.script_list: Config.stage_refreshed.disconnect( sub_interface.refresh_stage ) Config.PASSWORD_refreshed.disconnect( sub_interface.refresh_password ) self.stackedWidget.removeWidget(sub_interface) sub_interface.deleteLater() self.script_list.clear() self.pivot.clear() self.user_dashboard.dashboard.setRowCount(0) self.stackedWidget.addWidget(self.user_dashboard) self.pivot.addItem(routeKey="用户仪表盘", text="用户仪表盘") def add_userSettingBox(self, uid: int) -> None: """添加一个用户设置界面""" setting_box = self.UserMemberSettingBox(self.name, uid, self) self.script_list.append(setting_box) self.stackedWidget.addWidget(self.script_list[-1]) self.pivot.addItem(routeKey=f"用户_{uid}", text=f"用户 {uid}") class UserDashboard(HeaderCardWidget): """用户仪表盘页面""" switch_to = Signal(str) def __init__(self, name: str, parent=None): super().__init__(parent) self.setObjectName("用户仪表盘") self.setTitle("用户仪表盘") self.name = name self.dashboard = TableWidget(self) self.dashboard.setColumnCount(12) self.dashboard.setHorizontalHeaderLabels( [ "用户名", "账号ID", "密码", "状态", "代理情况", "给药量", "关卡选择", "备选 - 1", "备选 - 2", "备选 - 3", "剩余理智", "详", ] ) self.dashboard.setEditTriggers(TableWidget.NoEditTriggers) self.dashboard.verticalHeader().setVisible(False) for col in range(6): self.dashboard.horizontalHeader().setSectionResizeMode( col, QHeaderView.ResizeMode.ResizeToContents ) for col in range(6, 11): self.dashboard.horizontalHeader().setSectionResizeMode( col, QHeaderView.ResizeMode.Stretch ) self.dashboard.horizontalHeader().setSectionResizeMode( 11, QHeaderView.ResizeMode.Fixed ) self.dashboard.setColumnWidth(11, 32) self.viewLayout.addWidget(self.dashboard) self.viewLayout.setContentsMargins(3, 0, 3, 3) Config.PASSWORD_refreshed.connect(self.load_info) def load_info(self): self.user_data = Config.member_dict[self.name]["UserData"] self.dashboard.setRowCount(len(self.user_data)) for name, info in self.user_data.items(): config = info["Config"] text_list = [] if not config.get(config.Data_IfPassCheck): text_list.append("未通过人工排查") text_list.append( f"今日已代理{config.get(config.Data_ProxyTimes)}次" if Config.server_date().strftime("%Y-%m-%d") == config.get(config.Data_LastProxyDate) else "今日未进行代理" ) text_list.append( "本周剿灭已完成" if datetime.strptime( config.get(config.Data_LastAnnihilationDate), "%Y-%m-%d", ).isocalendar()[:2] == Config.server_date().isocalendar()[:2] else "本周剿灭未完成" ) stage_info = config.get_plan_info() button = PrimaryToolButton( FluentIcon.CHEVRON_RIGHT, self ) button.setFixedSize(32, 32) button.clicked.connect( partial(self.switch_to.emit, name) ) self.dashboard.setItem( int(name[3:]) - 1, 0, QTableWidgetItem(config.get(config.Info_Name)), ) self.dashboard.setItem( int(name[3:]) - 1, 1, QTableWidgetItem(config.get(config.Info_Id)), ) self.dashboard.setItem( int(name[3:]) - 1, 2, QTableWidgetItem( Crypto.AUTO_decryptor( config.get(config.Info_Password), Config.PASSWORD, ) if Config.PASSWORD else "******" ), ) self.dashboard.setCellWidget( int(name[3:]) - 1, 3, StatusSwitchSetting( qconfig=config, configItem_check=config.Info_Status, configItem_enable=config.Info_RemainedDay, parent=self, ), ) self.dashboard.setItem( int(name[3:]) - 1, 4, QTableWidgetItem(" | ".join(text_list)), ) self.dashboard.setItem( int(name[3:]) - 1, 5, QTableWidgetItem(str(stage_info["MedicineNumb"])), ) self.dashboard.setItem( int(name[3:]) - 1, 6, QTableWidgetItem( Config.stage_dict["ALL"]["text"][ Config.stage_dict["ALL"]["value"].index( stage_info["Stage"] ) ] if stage_info["Stage"] in Config.stage_dict["ALL"]["value"] else stage_info["Stage"] ), ) self.dashboard.setItem( int(name[3:]) - 1, 7, QTableWidgetItem( Config.stage_dict["ALL"]["text"][ Config.stage_dict["ALL"]["value"].index( stage_info["Stage"] ) ] if stage_info["Stage"] in Config.stage_dict["ALL"]["value"] else stage_info["Stage"] ), ) self.dashboard.setItem( int(name[3:]) - 1, 8, QTableWidgetItem( Config.stage_dict["ALL"]["text"][ Config.stage_dict["ALL"]["value"].index( stage_info["Stage_2"] ) ] if stage_info["Stage_2"] in Config.stage_dict["ALL"]["value"] else stage_info["Stage_2"] ), ) self.dashboard.setItem( int(name[3:]) - 1, 9, QTableWidgetItem( Config.stage_dict["ALL"]["text"][ Config.stage_dict["ALL"]["value"].index( stage_info["Stage_3"] ) ] if stage_info["Stage_3"] in Config.stage_dict["ALL"]["value"] else stage_info["Stage_3"] ), ) self.dashboard.setItem( int(name[3:]) - 1, 10, QTableWidgetItem( "不使用" if stage_info["Stage_Remain"] == "-" else ( ( Config.stage_dict["ALL"]["text"][ Config.stage_dict["ALL"][ "value" ].index(stage_info["Stage_Remain"]) ] ) if stage_info["Stage_Remain"] in Config.stage_dict["ALL"]["value"] else stage_info["Stage_Remain"] ) ), ) self.dashboard.setCellWidget( int(name[3:]) - 1, 11, button ) class UserMemberSettingBox(HeaderCardWidget): """用户管理子页面""" def __init__(self, name: str, uid: int, parent=None): super().__init__(parent) self.setObjectName(f"用户_{uid}") self.setTitle(f"用户 {uid}") self.name = name self.config = Config.member_dict[self.name]["UserData"][ f"用户_{uid}" ]["Config"] self.user_path = Config.member_dict[self.name]["UserData"][ f"用户_{uid}" ]["Path"] 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="用户名", content="用户的昵称", text="请输入用户名", qconfig=self.config, configItem=self.config.Info_Name, parent=self, ) self.card_Id = LineEditSettingCard( icon=FluentIcon.PEOPLE, title="账号ID", content="官服输入手机号,B服输入B站ID", text="请输入账号ID", qconfig=self.config, configItem=self.config.Info_Id, parent=self, ) self.card_Mode = ComboBoxSettingCard( icon=FluentIcon.DICTIONARY, title="用户配置模式", content="用户信息配置模式", texts=["简洁", "详细"], qconfig=self.config, configItem=self.config.Info_Mode, parent=self, ) self.card_Server = ComboBoxSettingCard( icon=FluentIcon.PROJECTOR, title="服务器", content="选择服务器类型", texts=["官服", "B服"], qconfig=self.config, configItem=self.config.Info_Server, parent=self, ) self.card_Status = SwitchSettingCard( icon=FluentIcon.CHECKBOX, title="用户状态", content="启用或禁用该用户", qconfig=self.config, configItem=self.config.Info_Status, parent=self, ) self.card_RemainedDay = SpinBoxSettingCard( icon=FluentIcon.CALENDAR, title="剩余天数", content="剩余代理天数,-1表示无限代理", range=(-1, 1024), qconfig=self.config, configItem=self.config.Info_RemainedDay, parent=self, ) self.card_Annihilation = PushAndSwitchButtonSettingCard( icon=FluentIcon.CAFE, title="剿灭代理", content="剿灭代理子任务相关设置", text="设置具体配置", qconfig=self.config, configItem=self.config.Info_Annihilation, parent=self, ) self.card_Routine = PushAndSwitchButtonSettingCard( icon=FluentIcon.CAFE, title="日常代理", content="日常代理子任务相关设置", text="设置具体配置", qconfig=self.config, configItem=self.config.Info_Routine, parent=self, ) self.card_InfrastMode = PushAndComboBoxSettingCard( icon=FluentIcon.CAFE, title="基建模式", content="自定义基建配置文件未生效", text="选择配置文件", texts=[ "常规模式", "一键轮休", "自定义基建", ], qconfig=self.config, configItem=self.config.Info_InfrastMode, parent=self, ) self.card_Password = PasswordLineEditSettingCard( icon=FluentIcon.VPN, title="密码", content="仅用于用户密码记录", text="请输入用户密码", algorithm="AUTO", qconfig=self.config, configItem=self.config.Info_Password, parent=self, ) self.card_Notes = LineEditSettingCard( icon=FluentIcon.PENCIL_INK, title="备注", content="用户备注信息", text="请输入备注", qconfig=self.config, configItem=self.config.Info_Notes, parent=self, ) self.card_MedicineNumb = SpinBoxWithPlanSettingCard( icon=FluentIcon.GAME, title="吃理智药", content="吃理智药次数,输入0以关闭", range=(0, 1024), qconfig=self.config, configItem=self.config.Info_MedicineNumb, parent=self, ) self.card_SeriesNumb = ComboBoxWithPlanSettingCard( icon=FluentIcon.GAME, title="连战次数", content="连战次数较大时建议搭配剩余理智关卡使用", texts=["AUTO", "6", "5", "4", "3", "2", "1", "不选择"], qconfig=self.config, configItem=self.config.Info_SeriesNumb, parent=self, ) self.card_SeriesNumb.comboBox.setMinimumWidth(150) self.card_StageMode = NoOptionComboBoxSettingCard( icon=FluentIcon.DICTIONARY, title="关卡配置模式", content="刷理智关卡号的配置模式", value=plan_list[0], texts=plan_list[1], qconfig=self.config, configItem=self.config.Info_StageMode, parent=self, ) self.card_StageMode.comboBox.setMinimumWidth(150) self.card_Stage = EditableComboBoxWithPlanSettingCard( icon=FluentIcon.GAME, title="关卡选择", content="按下回车以添加自定义关卡号", value=Config.stage_dict["ALL"]["value"], texts=Config.stage_dict["ALL"]["text"], qconfig=self.config, configItem=self.config.Info_Stage, parent=self, ) self.card_Stage_1 = EditableComboBoxWithPlanSettingCard( icon=FluentIcon.GAME, title="备选关卡 - 1", content="按下回车以添加自定义关卡号", value=Config.stage_dict["ALL"]["value"], texts=Config.stage_dict["ALL"]["text"], qconfig=self.config, configItem=self.config.Info_Stage_1, parent=self, ) self.card_Stage_2 = EditableComboBoxWithPlanSettingCard( icon=FluentIcon.GAME, title="备选关卡 - 2", content="按下回车以添加自定义关卡号", value=Config.stage_dict["ALL"]["value"], texts=Config.stage_dict["ALL"]["text"], qconfig=self.config, configItem=self.config.Info_Stage_2, parent=self, ) self.card_Stage_3 = EditableComboBoxWithPlanSettingCard( icon=FluentIcon.GAME, title="备选关卡 - 3", content="按下回车以添加自定义关卡号", value=Config.stage_dict["ALL"]["value"], texts=Config.stage_dict["ALL"]["text"], qconfig=self.config, configItem=self.config.Info_Stage_3, parent=self, ) self.card_Stage_Remain = ( EditableComboBoxWithPlanSettingCard( icon=FluentIcon.GAME, title="剩余理智关卡", content="按下回车以添加自定义关卡号", value=Config.stage_dict["ALL"]["value"], texts=[ "不使用" if _ == "当前/上次" else _ for _ in Config.stage_dict["ALL"]["text"] ], qconfig=self.config, configItem=self.config.Info_Stage_Remain, parent=self, ) ) self.card_Skland = PasswordLineAndSwitchButtonSettingCard( icon=FluentIcon.CERTIFICATE, title="森空岛签到", content="此功能具有一定风险,请谨慎使用!获取登录凭证请查阅「文档-进阶功能」。", text="鹰角网络通行证登录凭证", algorithm="DPAPI", qconfig=self.config, configItem_bool=self.config.Info_IfSkland, configItem_info=self.config.Info_SklandToken, parent=self, ) self.card_Skland.LineEdit.setMinimumWidth(250) self.card_UserLable = UserLableSettingCard( icon=FluentIcon.INFO, title="状态信息", content="用户的代理情况汇总", qconfig=self.config, configItems={ "LastProxyDate": self.config.Data_LastProxyDate, "LastAnnihilationDate": self.config.Data_LastAnnihilationDate, "ProxyTimes": self.config.Data_ProxyTimes, "IfPassCheck": self.config.Data_IfPassCheck, "IfSkland": self.config.Info_IfSkland, "LastSklandDate": self.config.Data_LastSklandDate, }, 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) h2_layout = QHBoxLayout() h2_layout.addWidget(self.card_Mode) h2_layout.addWidget(self.card_Server) h3_layout = QHBoxLayout() h3_layout.addWidget(self.card_Status) h3_layout.addWidget(self.card_RemainedDay) h4_layout = QHBoxLayout() h4_layout.addWidget(self.card_Annihilation) h4_layout.addWidget(self.card_Routine) h4_layout.addWidget(self.card_InfrastMode) h5_layout = QHBoxLayout() h5_layout.addWidget(self.card_Password) h5_layout.addWidget(self.card_Notes) h6_layout = QHBoxLayout() h6_layout.addWidget(self.card_MedicineNumb) h6_layout.addWidget(self.card_SeriesNumb) h7_layout = QHBoxLayout() h7_layout.addWidget(self.card_StageMode) h7_layout.addWidget(self.card_Stage) h8_layout = QHBoxLayout() h8_layout.addWidget(self.card_Stage_1) h8_layout.addWidget(self.card_Stage_2) h9_layout = QHBoxLayout() h9_layout.addWidget(self.card_Stage_3) h9_layout.addWidget(self.card_Stage_Remain) Layout = QVBoxLayout() Layout.addLayout(h1_layout) Layout.addLayout(h2_layout) Layout.addLayout(h3_layout) Layout.addWidget(self.card_UserLable) Layout.addLayout(h4_layout) Layout.addLayout(h5_layout) Layout.addLayout(h6_layout) Layout.addLayout(h7_layout) Layout.addLayout(h8_layout) Layout.addLayout(h9_layout) Layout.addWidget(self.card_Skland) Layout.addWidget(self.card_TaskSet) Layout.addWidget(self.card_NotifySet) self.viewLayout.addLayout(Layout) self.viewLayout.setContentsMargins(3, 0, 3, 3) self.card_Mode.comboBox.currentIndexChanged.connect( self.switch_mode ) self.card_InfrastMode.comboBox.currentIndexChanged.connect( self.switch_infrastructure ) self.card_Annihilation.clicked.connect( lambda: self.set_maa("Annihilation") ) self.card_Routine.clicked.connect( lambda: self.set_maa("Routine") ) 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_StageMode.comboBox.currentIndexChanged.connect( self.switch_stage_mode ) Config.stage_refreshed.connect(self.refresh_stage) Config.PASSWORD_refreshed.connect(self.refresh_password) self.switch_mode() self.switch_stage_mode() self.switch_infrastructure() def switch_mode(self) -> None: if self.config.get(self.config.Info_Mode) == "简洁": self.card_Routine.setVisible(False) self.card_Server.setVisible(True) self.card_Annihilation.button.setVisible(False) self.card_InfrastMode.setVisible(True) elif self.config.get(self.config.Info_Mode) == "详细": self.card_Server.setVisible(False) self.card_InfrastMode.setVisible(False) self.card_Annihilation.button.setVisible(True) self.card_Routine.setVisible(True) def switch_stage_mode(self) -> None: for card, name in zip( [ self.card_MedicineNumb, self.card_SeriesNumb, self.card_Stage, self.card_Stage_1, self.card_Stage_2, self.card_Stage_3, self.card_Stage_Remain, ], [ "MedicineNumb", "SeriesNumb", "Stage", "Stage_1", "Stage_2", "Stage_3", "Stage_Remain", ], ): card.switch_mode( self.config.get(self.config.Info_StageMode)[:2] ) if ( self.config.get(self.config.Info_StageMode) != "固定" ): card.change_plan( Config.plan_dict[ self.config.get(self.config.Info_StageMode) ]["Config"].get_current_info(name) ) def switch_infrastructure(self) -> None: if ( self.config.get(self.config.Info_InfrastMode) == "Custom" ): self.card_InfrastMode.button.setVisible(True) with ( self.user_path / "Infrastructure/infrastructure.json" ).open(mode="r", encoding="utf-8") as f: infrastructure = json.load(f) self.card_InfrastMode.setContent( f"当前基建配置:{infrastructure.get("title","未命名")}" ) else: self.card_InfrastMode.button.setVisible(False) self.card_InfrastMode.setContent( "自定义基建配置文件未生效" ) def refresh_stage(self): self.card_Stage.reLoadOptions( Config.stage_dict["ALL"]["value"], Config.stage_dict["ALL"]["text"], ) self.card_Stage_1.reLoadOptions( Config.stage_dict["ALL"]["value"], Config.stage_dict["ALL"]["text"], ) self.card_Stage_2.reLoadOptions( Config.stage_dict["ALL"]["value"], Config.stage_dict["ALL"]["text"], ) self.card_Stage_3.reLoadOptions( Config.stage_dict["ALL"]["value"], Config.stage_dict["ALL"]["text"], ) self.card_Stage_Remain.reLoadOptions( Config.stage_dict["ALL"]["value"], Config.stage_dict["ALL"]["text"], ) def refresh_password(self): self.card_Password.setValue( self.card_Password.qconfig.get( self.card_Password.configItem ) ) def set_infrastructure(self) -> None: """配置自定义基建""" if self.name in Config.running_list: logger.warning("所属脚本正在运行") MainInfoBar.push_info_bar( "warning", "所属脚本正在运行", "请先停止任务", 5000 ) return None file_path, _ = QFileDialog.getOpenFileName( self, "选择自定义基建文件", ".", "JSON 文件 (*.json)", ) if file_path != "": (self.user_path / "Infrastructure").mkdir( parents=True, exist_ok=True ) shutil.copy( file_path, self.user_path / "Infrastructure/infrastructure.json", ) self.switch_infrastructure() else: logger.warning("未选择自定义基建文件") MainInfoBar.push_info_bar( "warning", "警告", "未选择自定义基建文件", 5000 ) def set_maa(self, mode: str) -> None: """配置MAA子配置""" if self.name in Config.running_list: logger.warning("所属脚本正在运行") MainInfoBar.push_info_bar( "warning", "所属脚本正在运行", "请先停止任务", 5000 ) return None TaskManager.add_task( "设置MAA_用户", self.name, { "SetMaaInfo": { "Path": self.user_path / mode, } }, ) 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) class GeneralSettingBox(QWidget): """通用脚本设置界面""" def __init__(self, uid: int, parent=None): super().__init__(parent) self.setObjectName(f"脚本_{uid}") self.config = Config.member_dict[f"脚本_{uid}"]["Config"] self.app_setting = self.AppSettingCard(f"脚本_{uid}", self.config, self) self.branch_manager = self.BranchManager(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.branch_manager) 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) class AppSettingCard(HeaderCardWidget): def __init__(self, name: str, config: GeneralConfig, parent=None): super().__init__(parent) self.setTitle("通用实例") self.name = name self.config = config Layout = QVBoxLayout() self.card_Name = LineEditSettingCard( icon=FluentIcon.EDIT, title="实例名称", content="用于标识通用实例的名称", text="请输入实例名称", qconfig=self.config, configItem=self.config.Script_Name, parent=self, ) self.card_Script = self.ScriptSettingCard(self.config, self) self.card_Game = self.GameSettingCard(self.config, self) self.card_Run = self.RunSettingCard(self.config, self) Layout.addWidget(self.card_Name) Layout.addWidget(self.card_Script) Layout.addWidget(self.card_Game) Layout.addWidget(self.card_Run) self.viewLayout.addLayout(Layout) class ScriptSettingCard(ExpandGroupSettingCard): def __init__(self, config: GeneralConfig, parent=None): super().__init__( FluentIcon.SETTING, "脚本设置", "脚本属性配置选项", parent ) self.config = config self.card_RootPath = PathSettingCard( icon=FluentIcon.FOLDER, title="脚本根目录 - [必填]", mode="文件夹", text="选择文件夹", qconfig=self.config, configItem=self.config.Script_RootPath, parent=self, ) self.card_ScriptPath = PathSettingCard( icon=FluentIcon.FOLDER, title="脚本路径 - [必填]", mode="可执行文件 (*.exe *.bat)", text="选择程序", qconfig=self.config, configItem=self.config.Script_ScriptPath, parent=self, ) self.card_Arguments = LineEditSettingCard( icon=FluentIcon.PAGE_RIGHT, title="脚本启动参数", content="脚本启动时的附加参数", text="请输入脚本参数", qconfig=self.config, configItem=self.config.Script_Arguments, parent=self, ) self.card_IfTrackProcess = SwitchSettingCard( icon=FluentIcon.PAGE_RIGHT, title="追踪脚本子进程", content="启用后将在脚本启动后 60s 内追踪其子进程,并仅在所有子进程结束后判定脚本中止", qconfig=self.config, configItem=self.config.Script_IfTrackProcess, parent=self, ) self.card_ConfigPath = PathSettingCard( icon=FluentIcon.FOLDER, title="脚本配置文件路径 - [必填]", mode=self.config.Script_ConfigPathMode, text="选择路径", qconfig=self.config, configItem=self.config.Script_ConfigPath, parent=self, ) self.card_LogPath = PathSettingCard( icon=FluentIcon.FOLDER, title="脚本日志文件路径 - [必填]", mode="所有文件 (*)", text="选择文件", qconfig=self.config, configItem=self.config.Script_LogPath, parent=self, ) self.card_LogPathFormat = LineEditSettingCard( icon=FluentIcon.PAGE_RIGHT, title="脚本日志文件名格式", content="若脚本日志文件名中随时间变化,请填入时间格式,留空则不启用", text="请输入脚本日志文件名格式", qconfig=self.config, configItem=self.config.Script_LogPathFormat, parent=self, ) self.card_LogTimeStart = SpinBoxSettingCard( icon=FluentIcon.PAGE_RIGHT, title="脚本日志时间起始位置 - [必填]", content="脚本日志中时间的起始位置,单位为字符", range=(1, 1024), qconfig=self.config, configItem=self.config.Script_LogTimeStart, parent=self, ) self.card_LogTimeEnd = SpinBoxSettingCard( icon=FluentIcon.PAGE_RIGHT, title="脚本日志时间结束位置 - [必填]", content="脚本日志中时间的结束位置,单位为字符", range=(1, 1024), qconfig=self.config, configItem=self.config.Script_LogTimeEnd, parent=self, ) self.card_LogTimeFormat = LineEditSettingCard( icon=FluentIcon.PAGE_RIGHT, title="脚本日志时间格式 - [必填]", content="脚本日志中时间的格式", text="请输入脚本日志时间格式", qconfig=self.config, configItem=self.config.Script_LogTimeFormat, parent=self, ) self.card_SuccessLog = LineEditSettingCard( icon=FluentIcon.PAGE_RIGHT, title="脚本成功日志", content="任务成功完成时出现的日志,多条请使用「|」隔开", text="请输入脚本成功日志内容", qconfig=self.config, configItem=self.config.Script_SuccessLog, parent=self, ) self.card_ErrorLog = LineEditSettingCard( icon=FluentIcon.PAGE_RIGHT, title="脚本异常日志 - [必填]", content="脚本运行异常时的日志内容,多条请使用「|」隔开", text="请输入脚本异常日志内容", qconfig=self.config, configItem=self.config.Script_ErrorLog, parent=self, ) self.card_RootPath.pathChanged.connect(self.change_path) self.card_ScriptPath.pathChanged.connect( lambda old, new: self.check_path( self.config.Script_ScriptPath, old, new ) ) self.card_ConfigPath.pathChanged.connect( lambda old, new: self.check_path( self.config.Script_ConfigPath, old, new ) ) self.card_LogPath.pathChanged.connect( lambda old, new: self.check_path( self.config.Script_LogPath, old, new ) ) h_layout = QHBoxLayout() h_layout.addWidget(self.card_LogTimeStart) h_layout.addWidget(self.card_LogTimeEnd) widget = QWidget() Layout = QVBoxLayout(widget) Layout.addWidget(self.card_RootPath) Layout.addWidget(self.card_ScriptPath) Layout.addWidget(self.card_Arguments) Layout.addWidget(self.card_IfTrackProcess) Layout.addWidget(self.card_ConfigPath) Layout.addWidget(self.card_LogPath) Layout.addWidget(self.card_LogPathFormat) Layout.addLayout(h_layout) Layout.addWidget(self.card_LogTimeFormat) Layout.addWidget(self.card_SuccessLog) Layout.addWidget(self.card_ErrorLog) self.viewLayout.setContentsMargins(0, 0, 0, 0) self.viewLayout.setSpacing(0) self.addGroupWidget(widget) def change_path(self, old_path: Path, new_path: Path) -> None: """根据脚本根目录重新计算配置文件路径""" path_list = [ self.config.Script_ScriptPath, self.config.Script_ConfigPath, self.config.Script_LogPath, ] for path in path_list: if Path(self.config.get(path)).is_relative_to(old_path): relative_path = Path(self.config.get(path)).relative_to( old_path ) self.config.set(path, str(new_path / relative_path)) def check_path( self, configItem: ConfigItem, old_path: Path, new_path: Path ) -> None: """检查配置路径是否合法""" if not new_path.is_relative_to( Path(self.config.get(self.config.Script_RootPath)) ): self.config.set(configItem, str(old_path)) logger.warning( f"配置路径 {new_path} 不在脚本根目录下,已重置为 {old_path}" ) MainInfoBar.push_info_bar( "warning", "路径异常", "所选路径不在脚本根目录下", 5000 ) class GameSettingCard(ExpandGroupSettingCard): def __init__(self, config: GeneralConfig, parent=None): super().__init__( FluentIcon.SETTING, "游戏设置", "游戏/模拟器属性配置选项", parent, ) self.config = config self.card_Enabled = SwitchSettingCard( icon=FluentIcon.PAGE_RIGHT, title="游戏/模拟器相关功能", content="是否由AUTO_MAA管理游戏/模拟器相关进程", qconfig=self.config, configItem=self.config.Game_Enabled, parent=self, ) self.card_Style = ComboBoxSettingCard( icon=FluentIcon.PAGE_RIGHT, title="游戏平台类型", content="游戏运行在安卓模拟器还是客户端上", texts=["安卓模拟器", "客户端"], qconfig=self.config, configItem=self.config.Game_Style, parent=self, ) self.card_Path = PathSettingCard( icon=FluentIcon.FOLDER, title="游戏/模拟器路径", mode="可执行文件 (*.exe *.bat)", text="选择文件", qconfig=self.config, configItem=self.config.Game_Path, parent=self, ) self.card_Arguments = LineEditSettingCard( icon=FluentIcon.PAGE_RIGHT, title="游戏/模拟器启动参数", content="游戏/模拟器启动时的附加参数", text="请输入游戏/模拟器参数", qconfig=self.config, configItem=self.config.Game_Arguments, parent=self, ) self.card_WaitTime = SpinBoxSettingCard( icon=FluentIcon.PAGE_RIGHT, title="等待游戏/模拟器启动时间", content="启动游戏/模拟器与启动对应脚本的间隔时间,单位为秒", range=(0, 1024), qconfig=self.config, configItem=self.config.Game_WaitTime, parent=self, ) self.card_IfForceClose = SwitchSettingCard( icon=FluentIcon.PAGE_RIGHT, title="游戏/模拟器强制关闭", content="是否强制结束所有同路径进程", qconfig=self.config, configItem=self.config.Game_IfForceClose, parent=self, ) widget = QWidget() Layout = QVBoxLayout(widget) Layout.addWidget(self.card_Enabled) Layout.addWidget(self.card_Style) Layout.addWidget(self.card_Path) Layout.addWidget(self.card_Arguments) Layout.addWidget(self.card_WaitTime) Layout.addWidget(self.card_IfForceClose) self.viewLayout.setContentsMargins(0, 0, 0, 0) self.viewLayout.setSpacing(0) self.addGroupWidget(widget) class RunSettingCard(ExpandGroupSettingCard): def __init__(self, config: GeneralConfig, parent=None): super().__init__( FluentIcon.SETTING, "运行设置", "运行调控配置选项", parent ) self.config = config self.card_ProxyTimesLimit = SpinBoxSettingCard( icon=FluentIcon.PAGE_RIGHT, title="子配置单日代理次数上限", content="当子配置本日代理成功次数达到该阈值时跳过代理,阈值为「0」时视为无代理次数上限", range=(0, 1024), qconfig=self.config, configItem=self.config.Run_ProxyTimesLimit, parent=self, ) self.card_RunTimesLimit = SpinBoxSettingCard( icon=FluentIcon.PAGE_RIGHT, title="代理重试次数限制", content="若超过该次数限制仍未完成代理,视为代理失败", range=(1, 1024), qconfig=self.config, configItem=self.config.Run_RunTimesLimit, parent=self, ) self.card_RunTimeLimit = SpinBoxSettingCard( icon=FluentIcon.PAGE_RIGHT, title="自动代理超时限制", content="脚本日志无变化时间超过该阈值视为超时,单位为分钟", range=(1, 1024), qconfig=self.config, configItem=self.config.Run_RunTimeLimit, parent=self, ) widget = QWidget() Layout = QVBoxLayout(widget) Layout.addWidget(self.card_ProxyTimesLimit) Layout.addWidget(self.card_RunTimesLimit) Layout.addWidget(self.card_RunTimeLimit) self.viewLayout.setContentsMargins(0, 0, 0, 0) self.viewLayout.setSpacing(0) self.addGroupWidget(widget) class BranchManager(HeaderCardWidget): """分支管理父页面""" def __init__(self, name: str, parent=None): super().__init__(parent) self.setObjectName(f"{name}_分支管理") self.setTitle("下属配置") self.name = name self.tools = CommandBar() self.sub_manager = self.SubConfigSettingBox(self.name, self) # 逐个添加动作 self.tools.addActions( [ Action( FluentIcon.ADD_TO, "新建配置", triggered=self.add_sub ), Action( FluentIcon.REMOVE_FROM, "删除配置", triggered=self.del_sub, ), ] ) self.tools.addSeparator() self.tools.addActions( [ Action( FluentIcon.LEFT_ARROW, "向前移动", triggered=self.left_sub, ), Action( FluentIcon.RIGHT_ARROW, "向后移动", triggered=self.right_sub, ), ] ) layout = QVBoxLayout() layout.addWidget(self.tools) layout.addWidget(self.sub_manager) self.viewLayout.addLayout(layout) def add_sub(self): """添加一个配置""" index = len(Config.member_dict[self.name]["SubData"]) + 1 sub_config = GeneralSubConfig() sub_config.load( Config.member_dict[self.name]["Path"] / f"SubData/配置_{index}/config.json", sub_config, ) sub_config.save() Config.member_dict[self.name]["SubData"][f"配置_{index}"] = { "Path": Config.member_dict[self.name]["Path"] / f"SubData/配置_{index}", "Config": sub_config, } self.sub_manager.add_SettingBox(index) self.sub_manager.switch_SettingBox(f"配置_{index}") logger.success(f"{self.name} 配置_{index} 添加成功") MainInfoBar.push_info_bar( "success", "操作成功", f"{self.name} 添加 配置_{index}", 3000 ) SoundPlayer.play("添加配置") def del_sub(self): """删除一个配置""" name = self.sub_manager.pivot.currentRouteKey() if name is None: logger.warning("未选择配置") MainInfoBar.push_info_bar( "warning", "未选择配置", "请先选择一个配置", 5000 ) return None if name == "配置仪表盘": logger.warning("试图删除配置仪表盘") MainInfoBar.push_info_bar( "warning", "未选择配置", "请勿尝试删除配置仪表盘", 5000 ) return None if self.name in Config.running_list: logger.warning("所属脚本正在运行") MainInfoBar.push_info_bar( "warning", "所属脚本正在运行", "请先停止任务", 5000 ) return None choice = MessageBox( "确认", f"确定要删除 {name} 吗?", self.window() ) if choice.exec(): self.sub_manager.clear_SettingBox() shutil.rmtree( Config.member_dict[self.name]["SubData"][name]["Path"] ) for i in range( int(name[3:]) + 1, len(Config.member_dict[self.name]["SubData"]) + 1, ): if Config.member_dict[self.name]["SubData"][f"配置_{i}"][ "Path" ].exists(): Config.member_dict[self.name]["SubData"][f"配置_{i}"][ "Path" ].rename( Config.member_dict[self.name]["SubData"][ f"配置_{i}" ]["Path"].with_name(f"配置_{i-1}") ) self.sub_manager.show_SettingBox( f"配置_{max(int(name[3:]) - 1, 1)}" ) logger.success(f"{self.name} {name} 删除成功") MainInfoBar.push_info_bar( "success", "操作成功", f"{self.name} 删除 {name}", 3000 ) SoundPlayer.play("删除配置") def left_sub(self): """向前移动配置""" name = self.sub_manager.pivot.currentRouteKey() if name is None: logger.warning("未选择配置") MainInfoBar.push_info_bar( "warning", "未选择配置", "请先选择一个配置", 5000 ) return None if name == "配置仪表盘": logger.warning("试图移动配置仪表盘") MainInfoBar.push_info_bar( "warning", "未选择配置", "请勿尝试移动配置仪表盘", 5000 ) return None index = int(name[3:]) if index == 1: logger.warning("向前移动配置时已到达最左端") MainInfoBar.push_info_bar( "warning", "已经是第一个配置", "无法向前移动", 5000 ) return None if self.name in Config.running_list: logger.warning("所属脚本正在运行") MainInfoBar.push_info_bar( "warning", "所属脚本正在运行", "请先停止任务", 5000 ) return None self.sub_manager.clear_SettingBox() Config.member_dict[self.name]["SubData"][name]["Path"].rename( Config.member_dict[self.name]["SubData"][name][ "Path" ].with_name("配置_0") ) Config.member_dict[self.name]["SubData"][f"配置_{index-1}"][ "Path" ].rename(Config.member_dict[self.name]["SubData"][name]["Path"]) Config.member_dict[self.name]["SubData"][name]["Path"].with_name( "配置_0" ).rename( Config.member_dict[self.name]["SubData"][f"配置_{index-1}"][ "Path" ] ) self.sub_manager.show_SettingBox(f"配置_{index - 1}") logger.success(f"{self.name} {name} 前移成功") MainInfoBar.push_info_bar( "success", "操作成功", f"{self.name} 前移 {name}", 3000 ) def right_sub(self): """向后移动配置""" name = self.sub_manager.pivot.currentRouteKey() if name is None: logger.warning("未选择配置") MainInfoBar.push_info_bar( "warning", "未选择配置", "请先选择一个配置", 5000 ) return None if name == "配置仪表盘": logger.warning("试图删除配置仪表盘") MainInfoBar.push_info_bar( "warning", "未选择配置", "请勿尝试移动配置仪表盘", 5000 ) return None index = int(name[3:]) if index == len(Config.member_dict[self.name]["SubData"]): logger.warning("向后移动配置时已到达最右端") MainInfoBar.push_info_bar( "warning", "已经是最后一个配置", "无法向后移动", 5000 ) return None if self.name in Config.running_list: logger.warning("所属脚本正在运行") MainInfoBar.push_info_bar( "warning", "所属脚本正在运行", "请先停止任务", 5000 ) return None self.sub_manager.clear_SettingBox() Config.member_dict[self.name]["SubData"][name]["Path"].rename( Config.member_dict[self.name]["SubData"][name][ "Path" ].with_name("配置_0") ) Config.member_dict[self.name]["SubData"][f"配置_{index+1}"][ "Path" ].rename(Config.member_dict[self.name]["SubData"][name]["Path"]) Config.member_dict[self.name]["SubData"][name]["Path"].with_name( "配置_0" ).rename( Config.member_dict[self.name]["SubData"][f"配置_{index+1}"][ "Path" ] ) self.sub_manager.show_SettingBox(f"配置_{index + 1}") logger.success(f"{self.name} {name} 后移成功") MainInfoBar.push_info_bar( "success", "操作成功", f"{self.name} 后移 {name}", 3000 ) class SubConfigSettingBox(QWidget): """配置管理子页面组""" def __init__(self, name: str, parent=None): super().__init__(parent) self.setObjectName("配置管理") self.name = name 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[ MemberManager.MemberSettingBox.GeneralSettingBox.BranchManager.SubConfigSettingBox.SubMemberSettingBox ] = [] self.sub_dashboard = self.SubDashboard(self.name, self) self.sub_dashboard.switch_to.connect(self.switch_SettingBox) self.stackedWidget.addWidget(self.sub_dashboard) self.pivot.addItem(routeKey="配置仪表盘", text="配置仪表盘") 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( index, if_change_pivot=False ) ) self.show_SettingBox("配置仪表盘") def show_SettingBox(self, index: str) -> None: """加载所有子界面""" Config.search_general_sub(self.name) for name in Config.member_dict[self.name]["SubData"].keys(): self.add_SettingBox(name[3:]) self.switch_SettingBox(index) def switch_SettingBox( self, index: str, if_change_pivot: bool = True ) -> None: """切换到指定的子界面""" if len(Config.member_dict[self.name]["SubData"]) == 0: index = "配置仪表盘" if index != "配置仪表盘" and int(index[3:]) > len( Config.member_dict[self.name]["SubData"] ): return None if index == "配置仪表盘": self.sub_dashboard.load_info() if if_change_pivot: self.pivot.setCurrentItem(index) self.stackedWidget.setCurrentWidget( self.sub_dashboard if index == "配置仪表盘" else self.script_list[int(index[3:]) - 1] ) def clear_SettingBox(self) -> None: """清空所有子界面""" for sub_interface in self.script_list: self.stackedWidget.removeWidget(sub_interface) sub_interface.deleteLater() self.script_list.clear() self.pivot.clear() self.sub_dashboard.dashboard.setRowCount(0) self.stackedWidget.addWidget(self.sub_dashboard) self.pivot.addItem(routeKey="配置仪表盘", text="配置仪表盘") def add_SettingBox(self, uid: int) -> None: """添加一个配置设置界面""" setting_box = self.SubMemberSettingBox(self.name, uid, self) self.script_list.append(setting_box) self.stackedWidget.addWidget(self.script_list[-1]) self.pivot.addItem(routeKey=f"配置_{uid}", text=f"配置 {uid}") class SubDashboard(HeaderCardWidget): """配置仪表盘页面""" switch_to = Signal(str) def __init__(self, name: str, parent=None): super().__init__(parent) self.setObjectName("配置仪表盘") self.setTitle("配置仪表盘") self.name = name self.dashboard = TableWidget(self) self.dashboard.setColumnCount(5) self.dashboard.setHorizontalHeaderLabels( ["配置名", "状态", "代理情况", "备注", "详"] ) self.dashboard.setEditTriggers(TableWidget.NoEditTriggers) self.dashboard.verticalHeader().setVisible(False) for col in range(2): self.dashboard.horizontalHeader().setSectionResizeMode( col, QHeaderView.ResizeMode.ResizeToContents ) for col in range(2, 4): self.dashboard.horizontalHeader().setSectionResizeMode( col, QHeaderView.ResizeMode.Stretch ) self.dashboard.horizontalHeader().setSectionResizeMode( 4, QHeaderView.ResizeMode.Fixed ) self.dashboard.setColumnWidth(4, 32) self.viewLayout.addWidget(self.dashboard) self.viewLayout.setContentsMargins(3, 0, 3, 3) Config.PASSWORD_refreshed.connect(self.load_info) def load_info(self): self.sub_data = Config.member_dict[self.name]["SubData"] self.dashboard.setRowCount(len(self.sub_data)) for name, info in self.sub_data.items(): config = info["Config"] text_list = [] text_list.append( f"今日已代理{config.get(config.Data_ProxyTimes)}次" if Config.server_date().strftime("%Y-%m-%d") == config.get(config.Data_LastProxyDate) else "今日未进行代理" ) button = PrimaryToolButton( FluentIcon.CHEVRON_RIGHT, self ) button.setFixedSize(32, 32) button.clicked.connect( partial(self.switch_to.emit, name) ) self.dashboard.setItem( int(name[3:]) - 1, 0, QTableWidgetItem(config.get(config.Info_Name)), ) self.dashboard.setCellWidget( int(name[3:]) - 1, 1, StatusSwitchSetting( qconfig=config, configItem_check=config.Info_Status, configItem_enable=config.Info_RemainedDay, parent=self, ), ) self.dashboard.setItem( int(name[3:]) - 1, 2, QTableWidgetItem(" | ".join(text_list)), ) self.dashboard.setItem( int(name[3:]) - 1, 3, QTableWidgetItem(config.get(config.Info_Notes)), ) self.dashboard.setCellWidget( int(name[3:]) - 1, 4, button ) class SubMemberSettingBox(HeaderCardWidget): """配置管理子页面""" def __init__(self, name: str, uid: int, parent=None): super().__init__(parent) self.setObjectName(f"配置_{uid}") self.setTitle(f"配置 {uid}") self.name = name self.config = Config.member_dict[self.name]["SubData"][ f"配置_{uid}" ]["Config"] self.sub_path = Config.member_dict[self.name]["SubData"][ f"配置_{uid}" ]["Path"] self.card_Name = LineEditSettingCard( icon=FluentIcon.PEOPLE, title="配置名", content="用于标识配置", text="请输入配置名", qconfig=self.config, configItem=self.config.Info_Name, parent=self, ) self.card_SetConfig = PushSettingCard( text="设置具体配置", icon=FluentIcon.CAFE, title="具体配置", content="在脚本原始界面中查看具体配置内容", parent=self, ) self.card_Status = SwitchSettingCard( icon=FluentIcon.CHECKBOX, title="配置状态", content="启用或禁用该配置", qconfig=self.config, configItem=self.config.Info_Status, parent=self, ) self.card_RemainedDay = SpinBoxSettingCard( icon=FluentIcon.CALENDAR, title="剩余天数", content="剩余代理天数,-1表示无限代理", range=(-1, 1024), qconfig=self.config, configItem=self.config.Info_RemainedDay, parent=self, ) self.item_IfScriptBeforeTask = StatusSwitchSetting( qconfig=self.config, configItem_check=self.config.Info_IfScriptBeforeTask, configItem_enable=None, parent=self, ) self.card_ScriptBeforeTask = PathSettingCard( icon=FluentIcon.FOLDER, title="脚本前置任务", mode="脚本文件 (*.py *.bat *.cmd *.exe)", text="选择脚本文件", qconfig=self.config, configItem=self.config.Info_ScriptBeforeTask, parent=self, ) self.item_IfScriptAfterTask = StatusSwitchSetting( qconfig=self.config, configItem_check=self.config.Info_IfScriptAfterTask, configItem_enable=None, parent=self, ) self.card_ScriptAfterTask = PathSettingCard( icon=FluentIcon.FOLDER, title="脚本后置任务", mode="脚本文件 (*.py *.bat *.cmd *.exe)", text="选择脚本文件", qconfig=self.config, configItem=self.config.Info_ScriptAfterTask, parent=self, ) self.card_Notes = LineEditSettingCard( icon=FluentIcon.PENCIL_INK, title="备注", content="配置备注信息", text="请输入备注", qconfig=self.config, configItem=self.config.Info_Notes, parent=self, ) self.card_UserLable = SubLableSettingCard( icon=FluentIcon.INFO, title="状态信息", content="配置的代理情况汇总", qconfig=self.config, configItems={ "LastProxyDate": self.config.Data_LastProxyDate, "ProxyTimes": self.config.Data_ProxyTimes, }, parent=self, ) self.card_ScriptBeforeTask.hBoxLayout.insertWidget( 5, self.item_IfScriptBeforeTask, 0, Qt.AlignRight ) self.card_ScriptAfterTask.hBoxLayout.insertWidget( 5, self.item_IfScriptAfterTask, 0, Qt.AlignRight ) # 单独通知卡片 self.card_NotifySet = UserNoticeSettingCard( icon=FluentIcon.MAIL, title="用户单独通知设置", content="未启用任何通知项", text="设置", qconfig=self.config, configItem=self.config.Notify_Enabled, configItems={ "IfSendStatistic": self.config.Notify_IfSendStatistic, "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_SetConfig) h2_layout = QHBoxLayout() h2_layout.addWidget(self.card_Status) h2_layout.addWidget(self.card_RemainedDay) Layout = QVBoxLayout() Layout.addLayout(h1_layout) Layout.addLayout(h2_layout) Layout.addWidget(self.card_UserLable) Layout.addWidget(self.card_ScriptBeforeTask) Layout.addWidget(self.card_ScriptAfterTask) Layout.addWidget(self.card_Notes) Layout.addWidget(self.card_NotifySet) self.viewLayout.addLayout(Layout) self.viewLayout.setContentsMargins(3, 0, 3, 3) self.card_SetConfig.clicked.connect(self.set_sub) self.card_NotifySet.clicked.connect(self.set_notify) def set_sub(self) -> None: """配置子配置""" if self.name in Config.running_list: logger.warning("所属脚本正在运行") MainInfoBar.push_info_bar( "warning", "所属脚本正在运行", "请先停止任务", 5000 ) return None TaskManager.add_task( "设置通用脚本", self.name, {"SetSubInfo": {"Path": self.sub_path / "ConfigFiles"}}, ) 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, ) Layout = QVBoxLayout() Layout.addWidget(self.card_IfSendStatistic) 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)