diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index 8b14e97..28912d4 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -22,7 +22,7 @@ name: Build AUTO_MAA on: push: - branches: [ "main" ] + branches: [ "main","dev" ] paths-ignore: - '**.md' - 'LICENSE' diff --git a/README.md b/README.md index e6e67f2..381448b 100644 --- a/README.md +++ b/README.md @@ -27,14 +27,14 @@ MAA多账号管理与自动化软件 1. **配置:** 根据对应用户的配置信息,生成配置文件并将其导入MAA。 2. **监测:** 在MAA开始代理后,持续读取MAA的日志以判断其运行状态。当软件认定MAA出现异常时,通过重启MAA使之仍能继续完成任务。 -3. **循环:** 重复上述步骤,使MAA依次完成各个用户的日常代理任务。 +3. **循环:** 重复上述步骤,使MAA依次完成各个用户的自动代理任务。 ### 优势 - **节省运行开销:** 只需要一份MAA软件与一个模拟器,无需多开就能完成多账号代理,羸弱的电脑也能代理日常。 - **自定义空间大:** 依靠高级用户配置模式,支持MAA几乎所有设置选项自定义,同时保留对模拟器多开的支持。 - **一键代理无忧:** 无须中途手动修改MAA配置,将繁琐交给AUTO_MAA,把游戏留给自己。 -- **代理结果复核:** 通过人工排查功能核实各用户代理情况,堵住日常代理的最后一丝风险。 +- **代理结果复核:** 通过人工排查功能核实各用户代理情况,堵住自动代理的最后一丝风险。 ## 重要声明 @@ -127,8 +127,8 @@ MAA多账号管理与自动化软件 - `MAA路径`:该项无法直接编辑,仅用于展示当前程序所绑定MAA的路径。 - `浏览`:选择MAA文件夹。 - `设置MAA`:编辑MAA全局配置,具体使用方法参见前文。 -- `日常限制`:执行日常代理的日常部分时的超时阈值,当MAA日志无变化时间超过阈值时,视为超时。 -- `剿灭限制`:执行日常代理的剿灭部分时的超时阈值,当MAA日志无变化时间超过阈值时,视为超时。 +- `日常限制`:执行自动代理的日常部分时的超时阈值,当MAA日志无变化时间超过阈值时,视为超时。 +- `剿灭限制`:执行自动代理的剿灭部分时的超时阈值,当MAA日志无变化时间超过阈值时,视为超时。 - `运行失败重试次数上限`:对于每一用户,若超过该次数限制仍未完成代理,视为代理失败。 - `开机自动启动AUTO_MAA`:实现AUTO_MAA的自启动。 - `AUTO_MAA启动时禁止电脑休眠`:仅阻止电脑自动休眠,不会影响屏幕是否熄灭。 @@ -154,8 +154,8 @@ MAA多账号管理与自动化软件 - `状态`:用户的状态,禁用时将不再对其进行代理或排查。 - `执行情况`:当日执行情况,不可编辑。 - `关卡`、`备选关卡-1`、`备选关卡-2`:关卡号。 - - `日常`:单独设定是否进行日常代理的日常部分,可进一步配置MAA的具体代理任务,该配置与全局MAA配置相互独立。 - - `剿灭`:单独设定是否进行日常代理的剿灭部分,高级配置模式下可进一步配置MAA的具体代理任务,该配置与全局MAA配置相互独立。 + - `日常`:单独设定是否进行自动代理的日常部分,可进一步配置MAA的具体代理任务,该配置与全局MAA配置相互独立。 + - `剿灭`:单独设定是否进行自动代理的剿灭部分,高级配置模式下可进一步配置MAA的具体代理任务,该配置与全局MAA配置相互独立。 - `自定义基建`:是否启用自定义基建功能,需要进一步配置自定义基建文件,该配置与其他用户相互独立。 - `密码`:仅用于登记用户的密码,可留空。 - `备注`:用于备注用户信息。 diff --git a/app/__init__.py b/app/__init__.py index 27ce45c..0c70453 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -29,18 +29,23 @@ __version__ = "4.2.0" __author__ = "DLmaster361 " __license__ = "GPL-3.0 license" -from .config import AppConfig +from .core import AppConfig, QueueConfig, MaaConfig, Task, Task_manager, Main_timer from .models import MaaManager -from .services import Notification, CryptoHandler +from .services import Notify, Crypto, System from .ui import AUTO_MAA -from .utils import Updater, version_text +from .utils import Updater __all__ = [ "AppConfig", + "QueueConfig", + "MaaConfig", + "Task", + "Task_manager", + "Main_timer", "MaaManager", - "Notification", - "CryptoHandler", + "Notify", + "Crypto", + "System", "AUTO_MAA", "Updater", - "version_text", ] diff --git a/app/config.py b/app/config.py deleted file mode 100644 index 8323da6..0000000 --- a/app/config.py +++ /dev/null @@ -1,240 +0,0 @@ -# -# Copyright © <2024> - -# This file is part of AUTO_MAA. - -# AUTO_MAA is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published -# by the Free Software Foundation, either version 3 of the License, -# or (at your option) any later version. - -# AUTO_MAA is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty -# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See -# the GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with AUTO_MAA. If not, see . - -# DLmaster_361@163.com - -""" -AUTO_MAA -AUTO_MAA配置管理 -v4.2 -作者:DLmaster_361 -""" - -import sqlite3 -import json -import os -import sys -from pathlib import Path -from typing import Dict, Union - - -class AppConfig: - - def __init__(self) -> None: - - self.app_path = Path.cwd() # 获取软件根目录 - self.app_path_sys = os.path.realpath(sys.argv[0]) # 获取软件自身的路径 - self.app_name = os.path.basename(self.app_path) # 获取软件自身的名称 - - self.database_path = self.app_path / "data/data.db" - self.config_path = self.app_path / "config/gui.json" - self.key_path = self.app_path / "data/key" - self.gameid_path = self.app_path / "data/gameid.txt" - self.version_path = self.app_path / "resources/version.json" - - # 检查文件完整性 - self.initialize() - - def initialize(self) -> None: - """初始化程序的配置文件""" - - # 检查目录 - (self.app_path / "config").mkdir(parents=True, exist_ok=True) - (self.app_path / "data/MAAconfig/simple").mkdir(parents=True, exist_ok=True) - (self.app_path / "data/MAAconfig/beta").mkdir(parents=True, exist_ok=True) - (self.app_path / "data/MAAconfig/Default").mkdir(parents=True, exist_ok=True) - - # 生成版本信息文件 - if not self.version_path.exists(): - version = { - "main_version": "0.0.0.0", - "updater_version": "0.0.0.0", - } - with self.version_path.open(mode="w", encoding="utf-8") as f: - json.dump(version, f, indent=4) - - # 生成配置文件 - if not self.config_path.exists(): - config = {"Default": {}} - with self.config_path.open(mode="w", encoding="utf-8") as f: - json.dump(config, f, indent=4) - - # 生成预设gameid替换方案文件 - if not self.gameid_path.exists(): - self.gameid_path.write_text( - "龙门币:CE-6\n技能:CA-5\n红票:AP-5\n经验:LS-6\n剿灭模式:Annihilation", - encoding="utf-8", - ) - - self.check_config() - self.check_database() - - def check_config(self) -> None: - """检查配置文件字段完整性并补全""" - - config_list = [ - ["TimeSet.set1", "False"], - ["TimeSet.run1", "00:00"], - ["TimeSet.set2", "False"], - ["TimeSet.run2", "00:00"], - ["TimeSet.set3", "False"], - ["TimeSet.run3", "00:00"], - ["TimeSet.set4", "False"], - ["TimeSet.run4", "00:00"], - ["TimeSet.set5", "False"], - ["TimeSet.run5", "00:00"], - ["TimeSet.set6", "False"], - ["TimeSet.run6", "00:00"], - ["TimeSet.set7", "False"], - ["TimeSet.run7", "00:00"], - ["TimeSet.set8", "False"], - ["TimeSet.run8", "00:00"], - ["TimeSet.set9", "False"], - ["TimeSet.run9", "00:00"], - ["TimeSet.set10", "False"], - ["TimeSet.run10", "00:00"], - ["MaaSet.path", ""], - ["TimeLimit.routine", 10], - ["TimeLimit.annihilation", 40], - ["TimesLimit.run", 3], - ["SelfSet.IfSelfStart", "False"], - ["SelfSet.IfSleep", "False"], - ["SelfSet.IfProxyDirectly", "False"], - ["SelfSet.IfSendMail", "False"], - ["SelfSet.MailAddress", ""], - ["SelfSet.IfSendMail.OnlyError", "False"], - ["SelfSet.IfSilence", "False"], - ["SelfSet.BossKey", ""], - ["SelfSet.IfToTray", "False"], - ["SelfSet.UIsize", "1200x700"], - ["SelfSet.UIlocation", "100x100"], - ["SelfSet.UImaximized", "False"], - ["SelfSet.MainIndex", 2], - ] - - # 导入配置文件 - with self.config_path.open(mode="r", encoding="utf-8") as f: - config = json.load(f) - - # 检查并补充缺失的字段 - for i in range(len(config_list)): - if not config_list[i][0] in config["Default"]: - config["Default"][config_list[i][0]] = config_list[i][1] - - # 初始化配置信息 - self.content: Dict[str, Dict[str, Union[str, int]]] = config - - # 导出配置文件 - self.save_config() - - def check_database(self) -> None: - """检查用户数据库文件并处理数据库版本更新""" - - # 生成用户数据库 - if not self.database_path.exists(): - db = sqlite3.connect(self.database_path) - cur = db.cursor() - cur.execute( - "CREATE TABLE adminx(admin text,id text,server text,day int,status text,last date,game text,game_1 text,game_2 text,routine text,annihilation text,infrastructure text,password byte,notes text,numb int,mode text,uid int)" - ) - cur.execute("CREATE TABLE version(v text)") - cur.execute("INSERT INTO version VALUES(?)", ("v1.3",)) - db.commit() - cur.close() - db.close() - - # 数据库版本更新 - db = sqlite3.connect(self.database_path) - cur = db.cursor() - cur.execute("SELECT * FROM version WHERE True") - version = cur.fetchall() - # v1.0-->v1.1 - if version[0][0] == "v1.0": - cur.execute("SELECT * FROM adminx WHERE True") - data = cur.fetchall() - cur.execute("DROP TABLE IF EXISTS adminx") - cur.execute( - "CREATE TABLE adminx(admin text,id text,server text,day int,status text,last date,game text,game_1 text,game_2 text,routines text,annihilation text,infrastructure text,password byte,notes text,numb int,mode text,uid int)" - ) - for i in range(len(data)): - cur.execute( - "INSERT INTO adminx VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", - ( - data[i][0], # 0 0 0 - data[i][1], # 1 1 - - "Official", # 2 2 - - data[i][2], # 3 3 1 - data[i][3], # 4 4 2 - data[i][4], # 5 5 3 - data[i][5], # 6 6 - - data[i][6], # 7 7 - - data[i][7], # 8 8 - - "y", # 9 - 4 - data[i][8], # 10 9 5 - data[i][9], # 11 10 - - data[i][10], # 12 11 6 - data[i][11], # 13 12 7 - data[i][12], # 14 - - - "simple", # 15 - - - data[i][13], # 16 - - - ), - ) - cur.execute("DELETE FROM version WHERE v = ?", ("v1.0",)) - cur.execute("INSERT INTO version VALUES(?)", ("v1.1",)) - db.commit() - # v1.1-->v1.2 - if version[0][0] == "v1.1": - cur.execute("SELECT * FROM adminx WHERE True") - data = cur.fetchall() - for i in range(len(data)): - cur.execute( - "UPDATE adminx SET infrastructure = 'n' WHERE mode = ? AND uid = ?", - ( - data[i][15], - data[i][16], - ), - ) - cur.execute("DELETE FROM version WHERE v = ?", ("v1.1",)) - cur.execute("INSERT INTO version VALUES(?)", ("v1.2",)) - db.commit() - # v1.2-->v1.3 - if version[0][0] == "v1.2": - cur.execute("ALTER TABLE adminx RENAME COLUMN routines TO routine") - cur.execute("DELETE FROM version WHERE v = ?", ("v1.2",)) - cur.execute("INSERT INTO version VALUES(?)", ("v1.3",)) - db.commit() - cur.close() - db.close() - - def open_database(self) -> None: - """打开数据库""" - - self.db = sqlite3.connect(self.database_path) - self.cur = self.db.cursor() - - def close_database(self) -> None: - """关闭数据库""" - - self.cur.close() - self.db.close() - - def save_config(self) -> None: - """保存配置文件""" - - with self.config_path.open(mode="w", encoding="utf-8") as f: - json.dump(self.content, f, indent=4) diff --git a/app/utils/version.py b/app/core/__init__.py similarity index 65% rename from app/utils/version.py rename to app/core/__init__.py index 56cc393..8990b48 100644 --- a/app/utils/version.py +++ b/app/core/__init__.py @@ -20,19 +20,27 @@ """ AUTO_MAA -AUTO_MAA版本号工具 +AUTO_MAA核心组件包 v4.2 作者:DLmaster_361 """ +__version__ = "4.2.0" +__author__ = "DLmaster361 " +__license__ = "GPL-3.0 license" -def version_text(version_numb: list) -> str: - """将版本号列表转为可读的文本信息""" +from .config import AppConfig, QueueConfig, MaaConfig, Config +from .main_info_bar import MainInfoBar +from .task_manager import Task, Task_manager +from .timer import Main_timer - if version_numb[3] == 0: - version = f"v{'.'.join(str(_) for _ in version_numb[0:3])}" - else: - version = ( - f"v{'.'.join(str(_) for _ in version_numb[0:3])}-beta.{version_numb[3]}" - ) - return version +__all__ = [ + "AppConfig", + "Config", + "QueueConfig", + "MaaConfig", + "MainInfoBar", + "Task", + "Task_manager", + "Main_timer", +] diff --git a/app/core/config.py b/app/core/config.py new file mode 100644 index 0000000..9998047 --- /dev/null +++ b/app/core/config.py @@ -0,0 +1,601 @@ +# +# Copyright © <2024> + +# This file is part of AUTO_MAA. + +# AUTO_MAA is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published +# by the Free Software Foundation, either version 3 of the License, +# or (at your option) any later version. + +# AUTO_MAA is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty +# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See +# the GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with AUTO_MAA. If not, see . + +# DLmaster_361@163.com + +""" +AUTO_MAA +AUTO_MAA配置管理 +v4.2 +作者:DLmaster_361 +""" + +from loguru import logger +import sqlite3 +import json +import sys +import shutil +from pathlib import Path +from qfluentwidgets import ( + QConfig, + ConfigItem, + OptionsConfigItem, + RangeConfigItem, + FolderValidator, + BoolValidator, + RangeValidator, +) + + +class AppConfig: + + def __init__(self) -> None: + + self.app_path = Path(sys.argv[0]).resolve().parent # 获取软件根目录 + self.app_path_sys = str(Path(sys.argv[0]).resolve()) # 获取软件自身的路径 + + self.log_path = self.app_path / "debug/AUTO_MAA.log" + self.database_path = self.app_path / "data/data.db" + self.config_path = self.app_path / "config/config.json" + self.history_path = self.app_path / "config/history.json" + self.key_path = self.app_path / "data/key" + self.gameid_path = self.app_path / "data/gameid.txt" + self.version_path = self.app_path / "resources/version.json" + + self.PASSWORD = "" + self.running_list = [] + self.silence_list = [] + self.if_database_opened = False + + # 检查文件完整性 + self.initialize() + + def initialize(self) -> None: + """初始化程序的配置文件""" + + # 检查目录 + (self.app_path / "config").mkdir(parents=True, exist_ok=True) + (self.app_path / "data").mkdir(parents=True, exist_ok=True) + (self.app_path / "debug").mkdir(parents=True, exist_ok=True) + + # 生成版本信息文件 + if not self.version_path.exists(): + version = { + "main_version": "0.0.0.0", + "updater_version": "0.0.0.0", + } + with self.version_path.open(mode="w", encoding="utf-8") as f: + json.dump(version, f, ensure_ascii=False, indent=4) + + # 生成预设gameid替换方案文件 + if not self.gameid_path.exists(): + self.gameid_path.write_text( + "龙门币:CE-6\n技能:CA-5\n红票:AP-5\n经验:LS-6\n剿灭模式:Annihilation", + encoding="utf-8", + ) + + self.init_logger() + self.init_config() + self.check_data() + logger.info("程序配置管理模块初始化完成") + + def init_logger(self) -> None: + """初始化日志记录器""" + + logger.remove(0) + + logger.add( + sink=self.log_path, + level="DEBUG", + format="{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <8} | {message}", + enqueue=True, + backtrace=True, + diagnose=True, + rotation="1 week", + retention="1 month", + compression="zip", + ) + logger.info("===================================") + logger.info("AUTO_MAA 主程序") + logger.info("版本号: v4.2.1.1") + logger.info(f"根目录: {self.app_path}") + logger.info("===================================") + + logger.info("日志记录器初始化完成") + + def init_config(self) -> None: + """初始化配置类""" + + self.global_config = GlobalConfig() + self.queue_config = QueueConfig() + self.maa_config = MaaConfig() + + logger.info("配置类初始化完成") + + def init_database(self, mode: str) -> None: + """初始化用户数据库""" + + if mode == "Maa": + self.cur.execute( + "CREATE TABLE adminx(admin text,id text,server text,day int,status text,last date,game text,game_1 text,game_2 text,routine text,annihilation text,infrastructure text,password byte,notes text,numb int,mode text,uid int)" + ) + self.cur.execute("CREATE TABLE version(v text)") + self.cur.execute("INSERT INTO version VALUES(?)", ("v1.4",)) + self.db.commit() + + logger.info("用户数据库初始化完成") + + def check_data(self) -> None: + """检查用户数据文件并处理数据文件版本更新""" + + # 生成主数据库 + if not self.database_path.exists(): + db = sqlite3.connect(self.database_path) + cur = db.cursor() + cur.execute("CREATE TABLE version(v text)") + cur.execute("INSERT INTO version VALUES(?)", ("v1.4",)) + db.commit() + cur.close() + db.close() + + # 数据文件版本更新 + db = sqlite3.connect(self.database_path) + cur = db.cursor() + cur.execute("SELECT * FROM version WHERE True") + version = cur.fetchall() + + if version[0][0] != "v1.4": + logger.info("数据文件版本更新开始") + if_streaming = False + # v1.0-->v1.1 + if version[0][0] == "v1.0" or if_streaming: + logger.info("数据文件版本更新:v1.0-->v1.1") + if_streaming = True + cur.execute("SELECT * FROM adminx WHERE True") + data = cur.fetchall() + cur.execute("DROP TABLE IF EXISTS adminx") + cur.execute( + "CREATE TABLE adminx(admin text,id text,server text,day int,status text,last date,game text,game_1 text,game_2 text,routines text,annihilation text,infrastructure text,password byte,notes text,numb int,mode text,uid int)" + ) + for i in range(len(data)): + cur.execute( + "INSERT INTO adminx VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", + ( + data[i][0], # 0 0 0 + data[i][1], # 1 1 - + "Official", # 2 2 - + data[i][2], # 3 3 1 + data[i][3], # 4 4 2 + data[i][4], # 5 5 3 + data[i][5], # 6 6 - + data[i][6], # 7 7 - + data[i][7], # 8 8 - + "y", # 9 - 4 + data[i][8], # 10 9 5 + data[i][9], # 11 10 - + data[i][10], # 12 11 6 + data[i][11], # 13 12 7 + data[i][12], # 14 - - + "simple", # 15 - - + data[i][13], # 16 - - + ), + ) + cur.execute("DELETE FROM version WHERE v = ?", ("v1.0",)) + cur.execute("INSERT INTO version VALUES(?)", ("v1.1",)) + db.commit() + # v1.1-->v1.2 + if version[0][0] == "v1.1" or if_streaming: + logger.info("数据文件版本更新:v1.1-->v1.2") + if_streaming = True + cur.execute("SELECT * FROM adminx WHERE True") + data = cur.fetchall() + for i in range(len(data)): + cur.execute( + "UPDATE adminx SET infrastructure = 'n' WHERE mode = ? AND uid = ?", + ( + data[i][15], + data[i][16], + ), + ) + cur.execute("DELETE FROM version WHERE v = ?", ("v1.1",)) + cur.execute("INSERT INTO version VALUES(?)", ("v1.2",)) + db.commit() + # v1.2-->v1.3 + if version[0][0] == "v1.2" or if_streaming: + logger.info("数据文件版本更新:v1.2-->v1.3") + if_streaming = True + cur.execute("ALTER TABLE adminx RENAME COLUMN routines TO routine") + cur.execute("DELETE FROM version WHERE v = ?", ("v1.2",)) + cur.execute("INSERT INTO version VALUES(?)", ("v1.3",)) + db.commit() + # v1.3-->v1.4 + if version[0][0] == "v1.3" or if_streaming: + logger.info("数据文件版本更新:v1.3-->v1.4") + if_streaming = True + (self.app_path / "config/MaaConfig").mkdir(parents=True, exist_ok=True) + shutil.move( + self.app_path / "data/MaaConfig", + self.app_path / "config/MaaConfig", + ) + (self.app_path / "config/MaaConfig/MaaConfig").rename( + self.app_path / "config/MaaConfig/脚本_1" + ) + shutil.copy( + self.database_path, + self.app_path / "config/MaaConfig/脚本_1/user_data.db", + ) + cur.execute("DROP TABLE IF EXISTS adminx") + cur.execute("DELETE FROM version WHERE v = ?", ("v1.3",)) + cur.execute("INSERT INTO version VALUES(?)", ("v1.4",)) + db.commit() + with (self.app_path / "config/gui.json").open( + "r", encoding="utf-8" + ) as f: + info = json.load(f) + maa_config = { + "MaaSet": { + "Name": "", + "Path": info["Default"]["MaaSet.path"], + }, + "RunSet": { + "AnnihilationTimeLimit": info["Default"][ + "TimeLimit.annihilation" + ], + "RoutineTimeLimit": info["Default"]["TimeLimit.routine"], + "RunTimesLimit": info["Default"]["TimesLimit.run"], + }, + } + with (self.app_path / "config/MaaConfig/脚本_1/config.json").open( + "w", encoding="utf-8" + ) as f: + json.dump(maa_config, f, ensure_ascii=False, indent=4) + config = { + "Function": { + "BossKey": info["Default"]["SelfSet.BossKey"], + "IfAllowSleep": bool( + info["Default"]["SelfSet.IfSleep"] == "True" + ), + "IfSilence": bool( + info["Default"]["SelfSet.IfSilence"] == "True" + ), + }, + "Notify": { + "IfPushPlyer": True, + "IfSendErrorOnly": bool( + info["Default"]["SelfSet.IfSendMail.OnlyError"] == "True" + ), + "IfSendMail": bool( + info["Default"]["SelfSet.IfSendMail"] == "True" + ), + "MailAddress": info["Default"]["SelfSet.MailAddress"], + }, + "Start": { + "IfRunDirectly": bool( + info["Default"]["SelfSet.IfProxyDirectly"] == "True" + ), + "IfSelfStart": bool( + info["Default"]["SelfSet.IfSelfStart"] == "True" + ), + }, + "UI": { + "IfShowTray": bool( + info["Default"]["SelfSet.IfToTray"] == "True" + ), + "IfToTray": bool(info["Default"]["SelfSet.IfToTray"] == "True"), + "location": info["Default"]["SelfSet.UIlocation"], + "maximized": bool( + info["Default"]["SelfSet.UImaximized"] == "True" + ), + "size": info["Default"]["SelfSet.UIsize"], + }, + "Update": {"IfAutoUpdate": False}, + } + with (self.app_path / "config/config.json").open( + "w", encoding="utf-8" + ) as f: + json.dump(config, f, ensure_ascii=False, indent=4) + queue_config = { + "QueueSet": {"Enabled": True, "Name": ""}, + "Queue": { + "Member_1": "脚本_1", + "Member_10": "禁用", + "Member_2": "禁用", + "Member_3": "禁用", + "Member_4": "禁用", + "Member_5": "禁用", + "Member_6": "禁用", + "Member_7": "禁用", + "Member_8": "禁用", + "Member_9": "禁用", + }, + "Time": { + "TimeEnabled_0": bool( + info["Default"]["TimeSet.set1"] == "True" + ), + "TimeEnabled_1": bool( + info["Default"]["TimeSet.set2"] == "True" + ), + "TimeEnabled_2": bool( + info["Default"]["TimeSet.set3"] == "True" + ), + "TimeEnabled_3": bool( + info["Default"]["TimeSet.set4"] == "True" + ), + "TimeEnabled_4": bool( + info["Default"]["TimeSet.set5"] == "True" + ), + "TimeEnabled_5": bool( + info["Default"]["TimeSet.set6"] == "True" + ), + "TimeEnabled_6": bool( + info["Default"]["TimeSet.set7"] == "True" + ), + "TimeEnabled_7": bool( + info["Default"]["TimeSet.set8"] == "True" + ), + "TimeEnabled_8": bool( + info["Default"]["TimeSet.set9"] == "True" + ), + "TimeEnabled_9": bool( + info["Default"]["TimeSet.set10"] == "True" + ), + "TimeSet_0": info["Default"]["TimeSet.run1"], + "TimeSet_1": info["Default"]["TimeSet.run2"], + "TimeSet_2": info["Default"]["TimeSet.run3"], + "TimeSet_3": info["Default"]["TimeSet.run4"], + "TimeSet_4": info["Default"]["TimeSet.run5"], + "TimeSet_5": info["Default"]["TimeSet.run6"], + "TimeSet_6": info["Default"]["TimeSet.run7"], + "TimeSet_7": info["Default"]["TimeSet.run8"], + "TimeSet_8": info["Default"]["TimeSet.run9"], + "TimeSet_9": info["Default"]["TimeSet.run10"], + }, + } + (self.app_path / "config/QueueConfig").mkdir( + parents=True, exist_ok=True + ) + with (self.app_path / "config/QueueConfig/调度队列_1.json").open( + "w", encoding="utf-8" + ) as f: + json.dump(queue_config, f, ensure_ascii=False, indent=4) + (self.app_path / "config/gui.json").unlink() + cur.close() + db.close() + logger.info("数据文件版本更新完成") + + def open_database(self, mode: str, index: str = None) -> None: + """打开数据库""" + + self.close_database() + self.db = sqlite3.connect( + self.app_path / f"config/{mode}Config/{index}/user_data.db" + ) + self.cur = self.db.cursor() + self.if_database_opened = True + + def close_database(self) -> None: + """关闭数据库""" + + if self.if_database_opened: + self.cur.close() + self.db.close() + self.if_database_opened = False + + def change_user_info( + self, + data_path: Path, + modes: list, + uids: list, + days: list, + lasts: list, + notes: list, + numbs: list, + ) -> None: + """将代理完成后发生改动的用户信息同步至本地数据库""" + + db = sqlite3.connect(data_path / "user_data.db") + cur = db.cursor() + + for index in range(len(uids)): + cur.execute( + "UPDATE adminx SET day = ? WHERE mode = ? AND uid = ?", + (days[index], modes[index], uids[index]), + ) + cur.execute( + "UPDATE adminx SET last = ? WHERE mode = ? AND uid = ?", + (lasts[index], modes[index], uids[index]), + ) + cur.execute( + "UPDATE adminx SET notes = ? WHERE mode = ? AND uid = ?", + (notes[index], modes[index], uids[index]), + ) + cur.execute( + "UPDATE adminx SET numb = ? WHERE mode = ? AND uid = ?", + (numbs[index], modes[index], uids[index]), + ) + db.commit() + cur.close() + db.close() + + def save_history(self, key: str, content: dict) -> None: + """保存历史记录""" + + history = {} + if self.history_path.exists(): + with self.history_path.open(mode="r", encoding="utf-8") as f: + history = json.load(f) + history[key] = content + with self.history_path.open(mode="w", encoding="utf-8") as f: + json.dump(history, f, ensure_ascii=False, indent=4) + + def get_history(self, key: str) -> dict: + """获取历史记录""" + + history = {} + if self.history_path.exists(): + with self.history_path.open(mode="r", encoding="utf-8") as f: + history = json.load(f) + return history.get( + key, {"Time": "0000-00-00 00:00", "History": "暂无历史运行记录"} + ) + + def clear_maa_config(self) -> None: + """清空MAA配置""" + + self.maa_config.set(self.maa_config.MaaSet_Name, "") + self.maa_config.set(self.maa_config.MaaSet_Path, ".") + self.maa_config.set(self.maa_config.RunSet_AnnihilationTimeLimit, 40) + self.maa_config.set(self.maa_config.RunSet_RoutineTimeLimit, 10) + self.maa_config.set(self.maa_config.RunSet_RunTimesLimit, 3) + self.maa_config.set(self.maa_config.MaaSet_Name, "") + self.maa_config.set(self.maa_config.MaaSet_Name, "") + self.maa_config.set(self.maa_config.MaaSet_Name, "") + + def clear_queue_config(self) -> None: + """清空队列配置""" + + self.queue_config.set(self.queue_config.queueSet_Name, "") + self.queue_config.set(self.queue_config.queueSet_Enabled, False) + + self.queue_config.set(self.queue_config.time_TimeEnabled_0, False) + self.queue_config.set(self.queue_config.time_TimeSet_0, "00:00") + self.queue_config.set(self.queue_config.time_TimeEnabled_1, False) + self.queue_config.set(self.queue_config.time_TimeSet_1, "00:00") + self.queue_config.set(self.queue_config.time_TimeEnabled_2, False) + self.queue_config.set(self.queue_config.time_TimeSet_2, "00:00") + self.queue_config.set(self.queue_config.time_TimeEnabled_3, False) + self.queue_config.set(self.queue_config.time_TimeSet_3, "00:00") + self.queue_config.set(self.queue_config.time_TimeEnabled_4, False) + self.queue_config.set(self.queue_config.time_TimeSet_4, "00:00") + self.queue_config.set(self.queue_config.time_TimeEnabled_5, False) + self.queue_config.set(self.queue_config.time_TimeSet_5, "00:00") + self.queue_config.set(self.queue_config.time_TimeEnabled_6, False) + self.queue_config.set(self.queue_config.time_TimeSet_6, "00:00") + self.queue_config.set(self.queue_config.time_TimeEnabled_7, False) + self.queue_config.set(self.queue_config.time_TimeSet_7, "00:00") + self.queue_config.set(self.queue_config.time_TimeEnabled_8, False) + self.queue_config.set(self.queue_config.time_TimeSet_8, "00:00") + self.queue_config.set(self.queue_config.time_TimeEnabled_9, False) + self.queue_config.set(self.queue_config.time_TimeSet_9, "00:00") + + self.queue_config.set(self.queue_config.queue_Member_1, "禁用") + self.queue_config.set(self.queue_config.queue_Member_2, "禁用") + self.queue_config.set(self.queue_config.queue_Member_3, "禁用") + self.queue_config.set(self.queue_config.queue_Member_4, "禁用") + self.queue_config.set(self.queue_config.queue_Member_5, "禁用") + self.queue_config.set(self.queue_config.queue_Member_6, "禁用") + self.queue_config.set(self.queue_config.queue_Member_7, "禁用") + self.queue_config.set(self.queue_config.queue_Member_8, "禁用") + self.queue_config.set(self.queue_config.queue_Member_9, "禁用") + self.queue_config.set(self.queue_config.queue_Member_10, "禁用") + + +class GlobalConfig(QConfig): + """全局配置""" + + function_IfAllowSleep = ConfigItem( + "Function", "IfAllowSleep", False, BoolValidator() + ) + function_IfSilence = ConfigItem("Function", "IfSilence", False, BoolValidator()) + function_BossKey = ConfigItem("Function", "BossKey", "") + + start_IfSelfStart = ConfigItem("Start", "IfSelfStart", False, BoolValidator()) + start_IfRunDirectly = ConfigItem("Start", "IfRunDirectly", False, BoolValidator()) + + ui_IfShowTray = ConfigItem("UI", "IfShowTray", False, BoolValidator()) + ui_IfToTray = ConfigItem("UI", "IfToTray", False, BoolValidator()) + ui_size = ConfigItem("UI", "size", "1200x700") + ui_location = ConfigItem("UI", "location", "100x100") + ui_maximized = ConfigItem("UI", "maximized", False, BoolValidator()) + + notify_IfPushPlyer = ConfigItem("Notify", "IfPushPlyer", False, BoolValidator()) + notify_IfSendMail = ConfigItem("Notify", "IfSendMail", False, BoolValidator()) + notify_IfSendErrorOnly = ConfigItem( + "Notify", "IfSendErrorOnly", False, BoolValidator() + ) + notify_MailAddress = ConfigItem("Notify", "MailAddress", "") + + update_IfAutoUpdate = ConfigItem("Update", "IfAutoUpdate", False, BoolValidator()) + + +class QueueConfig(QConfig): + """队列配置""" + + queueSet_Name = ConfigItem("QueueSet", "Name", "") + queueSet_Enabled = ConfigItem("QueueSet", "Enabled", False, BoolValidator()) + + time_TimeEnabled_0 = ConfigItem("Time", "TimeEnabled_0", False, BoolValidator()) + time_TimeSet_0 = ConfigItem("Time", "TimeSet_0", "00:00") + + time_TimeEnabled_1 = ConfigItem("Time", "TimeEnabled_1", False, BoolValidator()) + time_TimeSet_1 = ConfigItem("Time", "TimeSet_1", "00:00") + + time_TimeEnabled_2 = ConfigItem("Time", "TimeEnabled_2", False, BoolValidator()) + time_TimeSet_2 = ConfigItem("Time", "TimeSet_2", "00:00") + + time_TimeEnabled_3 = ConfigItem("Time", "TimeEnabled_3", False, BoolValidator()) + time_TimeSet_3 = ConfigItem("Time", "TimeSet_3", "00:00") + + time_TimeEnabled_4 = ConfigItem("Time", "TimeEnabled_4", False, BoolValidator()) + time_TimeSet_4 = ConfigItem("Time", "TimeSet_4", "00:00") + + time_TimeEnabled_5 = ConfigItem("Time", "TimeEnabled_5", False, BoolValidator()) + time_TimeSet_5 = ConfigItem("Time", "TimeSet_5", "00:00") + + time_TimeEnabled_6 = ConfigItem("Time", "TimeEnabled_6", False, BoolValidator()) + time_TimeSet_6 = ConfigItem("Time", "TimeSet_6", "00:00") + + time_TimeEnabled_7 = ConfigItem("Time", "TimeEnabled_7", False, BoolValidator()) + time_TimeSet_7 = ConfigItem("Time", "TimeSet_7", "00:00") + + time_TimeEnabled_8 = ConfigItem("Time", "TimeEnabled_8", False, BoolValidator()) + time_TimeSet_8 = ConfigItem("Time", "TimeSet_8", "00:00") + + time_TimeEnabled_9 = ConfigItem("Time", "TimeEnabled_9", False, BoolValidator()) + time_TimeSet_9 = ConfigItem("Time", "TimeSet_9", "00:00") + + queue_Member_1 = OptionsConfigItem("Queue", "Member_1", "禁用") + queue_Member_2 = OptionsConfigItem("Queue", "Member_2", "禁用") + queue_Member_3 = OptionsConfigItem("Queue", "Member_3", "禁用") + queue_Member_4 = OptionsConfigItem("Queue", "Member_4", "禁用") + queue_Member_5 = OptionsConfigItem("Queue", "Member_5", "禁用") + queue_Member_6 = OptionsConfigItem("Queue", "Member_6", "禁用") + queue_Member_7 = OptionsConfigItem("Queue", "Member_7", "禁用") + queue_Member_8 = OptionsConfigItem("Queue", "Member_8", "禁用") + queue_Member_9 = OptionsConfigItem("Queue", "Member_9", "禁用") + queue_Member_10 = OptionsConfigItem("Queue", "Member_10", "禁用") + + +class MaaConfig(QConfig): + """MAA配置""" + + MaaSet_Name = ConfigItem("MaaSet", "Name", "") + MaaSet_Path = ConfigItem("MaaSet", "Path", ".", FolderValidator()) + + RunSet_AnnihilationTimeLimit = RangeConfigItem( + "RunSet", "AnnihilationTimeLimit", 40, RangeValidator(1, 1024) + ) + RunSet_RoutineTimeLimit = RangeConfigItem( + "RunSet", "RoutineTimeLimit", 10, RangeValidator(1, 1024) + ) + RunSet_RunTimesLimit = RangeConfigItem( + "RunSet", "RunTimesLimit", 3, RangeValidator(1, 1024) + ) + + +Config = AppConfig() diff --git a/app/core/main_info_bar.py b/app/core/main_info_bar.py new file mode 100644 index 0000000..349df7e --- /dev/null +++ b/app/core/main_info_bar.py @@ -0,0 +1,92 @@ +# +# Copyright © <2024> + +# This file is part of AUTO_MAA. + +# AUTO_MAA is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published +# by the Free Software Foundation, either version 3 of the License, +# or (at your option) any later version. + +# AUTO_MAA is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty +# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See +# the GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with AUTO_MAA. If not, see . + +# DLmaster_361@163.com + +""" +AUTO_MAA +AUTO_MAA信息通知栏 +v4.2 +作者:DLmaster_361 +""" + +from loguru import logger +from PySide6.QtCore import Qt +from qfluentwidgets import ( + InfoBar, + InfoBarPosition, +) + + +class _MainInfoBar: + """信息通知栏""" + + def __init__(self, parent=None): + + self.parent = parent + + def push_info_bar(self, mode: str, title: str, content: str, time: int): + """推送到信息通知栏""" + + if self.parent is None: + logger.error("信息通知栏未设置父窗口") + return None + + if mode == "success": + InfoBar.success( + title=title, + content=content, + orient=Qt.Horizontal, + isClosable=True, + position=InfoBarPosition.TOP_RIGHT, + duration=time, + parent=self.parent, + ) + elif mode == "warning": + InfoBar.warning( + title=title, + content=content, + orient=Qt.Horizontal, + isClosable=True, + position=InfoBarPosition.TOP_RIGHT, + duration=time, + parent=self.parent, + ) + elif mode == "error": + InfoBar.error( + title=title, + content=content, + orient=Qt.Horizontal, + isClosable=True, + position=InfoBarPosition.TOP_RIGHT, + duration=time, + parent=self.parent, + ) + elif mode == "info": + InfoBar.info( + title=title, + content=content, + orient=Qt.Horizontal, + isClosable=True, + position=InfoBarPosition.TOP_RIGHT, + duration=time, + parent=self.parent, + ) + + +MainInfoBar = _MainInfoBar() diff --git a/app/core/task_manager.py b/app/core/task_manager.py new file mode 100644 index 0000000..bcd4870 --- /dev/null +++ b/app/core/task_manager.py @@ -0,0 +1,278 @@ +# +# Copyright © <2024> + +# This file is part of AUTO_MAA. + +# AUTO_MAA is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published +# by the Free Software Foundation, either version 3 of the License, +# or (at your option) any later version. + +# AUTO_MAA is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty +# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See +# the GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with AUTO_MAA. If not, see . + +# DLmaster_361@163.com + +""" +AUTO_MAA +AUTO_MAA业务调度器 +v4.2 +作者:DLmaster_361 +""" + +from loguru import logger +from PySide6.QtCore import QThread, QObject, Signal +from qfluentwidgets import Dialog +from pathlib import Path +from typing import Dict, Union + +from .config import Config +from .main_info_bar import MainInfoBar +from app.models import MaaManager + + +class Task(QThread): + """业务线程""" + + push_info_bar = Signal(str, str, str, int) + question = Signal(str, str) + question_response = Signal(bool) + update_user_info = Signal(Path, list, list, list, list, list, list) + create_task_list = Signal(list) + create_user_list = Signal(list) + update_task_list = Signal(list) + update_user_list = Signal(list) + update_log_text = Signal(str) + accomplish = Signal(list) + + def __init__( + self, + mode: str, + name: str, + info: Dict[str, Dict[str, Union[str, int, bool]]], + ): + super(Task, self).__init__() + + self.mode = mode + self.name = name + self.info = info + + self.logs = [] + + self.question_response.connect(lambda: print("response")) + + def run(self): + + if "设置MAA" in self.mode: + + logger.info(f"任务开始:设置{self.name}") + self.push_info_bar.emit("info", "设置MAA", self.name, 3000) + + self.task = MaaManager( + self.mode, + Config.app_path / f"config/MaaConfig/{self.name}", + ( + None + if "全局" in self.mode + else Config.app_path + / f"config/MaaConfig/{self.name}/beta/{self.info["SetMaaInfo"]["UserId"]}/{self.info["SetMaaInfo"]["SetType"]}" + ), + ) + self.task.push_info_bar.connect(self.push_info_bar.emit) + self.task.accomplish.connect(lambda: self.accomplish.emit([])) + + self.task.run() + + else: + + self.member_dict = self.search_member() + self.task_list = [ + [value, "等待"] + for _, value in self.info["Queue"].items() + if value != "禁用" + ] + + self.create_task_list.emit(self.task_list) + + for i in range(len(self.task_list)): + + if self.isInterruptionRequested(): + break + + self.task_list[i][1] = "运行" + self.update_task_list.emit(self.task_list) + + if self.task_list[i][0] in Config.running_list: + + self.task_list[i][1] = "跳过" + self.update_task_list.emit(self.task_list) + logger.info(f"跳过任务:{self.task_list[i][0]}") + self.push_info_bar.emit( + "info", "跳过任务", self.task_list[i][0], 3000 + ) + continue + + Config.running_list.append(self.task_list[i][0]) + logger.info(f"任务开始:{self.task_list[i][0]}") + self.push_info_bar.emit("info", "任务开始", self.task_list[i][0], 3000) + + if self.member_dict[self.task_list[i][0]][0] == "Maa": + + self.task = MaaManager( + self.mode[0:4], + self.member_dict[self.task_list[i][0]][1], + ) + + self.task.question.connect(self.question.emit) + self.question_response.disconnect() + self.question_response.connect(self.task.question_response.emit) + self.task.push_info_bar.connect(self.push_info_bar.emit) + self.task.create_user_list.connect(self.create_user_list.emit) + self.task.update_user_list.connect(self.update_user_list.emit) + self.task.update_log_text.connect(self.update_log_text.emit) + self.task.update_user_info.connect( + lambda modes, uids, days, lasts, notes, numbs: self.update_user_info.emit( + self.member_dict[self.task_list[i][0]][1], + modes, + uids, + days, + lasts, + notes, + numbs, + ) + ) + self.task.accomplish.connect( + lambda log: self.save_log(self.task_list[i][0], log) + ) + + self.task.run() + + Config.running_list.remove(self.task_list[i][0]) + + self.task_list[i][1] = "完成" + logger.info(f"任务完成:{self.task_list[i][0]}") + self.push_info_bar.emit("info", "任务完成", self.task_list[i][0], 3000) + + self.accomplish.emit(self.logs) + + def search_member(self) -> dict: + """搜索所有脚本实例并固定相关配置信息""" + + member_dict = {} + + if (Config.app_path / "config/MaaConfig").exists(): + for subdir in (Config.app_path / "config/MaaConfig").iterdir(): + if subdir.is_dir(): + + member_dict[subdir.name] = ["Maa", subdir] + + return member_dict + + def save_log(self, name: str, log: dict): + """保存保存任务结果""" + + self.logs.append([name, log]) + + +class TaskManager(QObject): + """业务调度器""" + + create_gui = Signal(Task) + connect_gui = Signal(Task) + push_info_bar = Signal(str, str, str, int) + + def __init__(self): + super(TaskManager, self).__init__() + + self.task_list: Dict[str, Task] = {} + + def add_task( + self, mode: str, name: str, info: Dict[str, Dict[str, Union[str, int, bool]]] + ): + """添加任务""" + + if name in Config.running_list or name in self.task_list: + + logger.warning(f"任务已存在:{name}") + MainInfoBar.push_info_bar("warning", "任务已存在", name, 5000) + return None + + logger.info(f"任务开始:{name}") + MainInfoBar.push_info_bar("info", "任务开始", name, 3000) + + Config.running_list.append(name) + self.task_list[name] = Task(mode, name, info) + self.task_list[name].question.connect( + lambda title, content: self.push_dialog(name, title, content) + ) + self.task_list[name].push_info_bar.connect(MainInfoBar.push_info_bar) + self.task_list[name].update_user_info.connect(Config.change_user_info) + self.task_list[name].accomplish.connect( + lambda logs: self.remove_task(name, logs) + ) + + if "新窗口" in mode: + self.create_gui.emit(self.task_list[name]) + + elif "主窗口" in mode: + self.connect_gui.emit(self.task_list[name]) + + self.task_list[name].start() + + def stop_task(self, name: str): + """中止任务""" + + logger.info(f"中止任务:{name}") + MainInfoBar.push_info_bar("info", "中止任务", name, 3000) + + if name == "ALL": + + for name in self.task_list: + + self.task_list[name].task.requestInterruption() + self.task_list[name].requestInterruption() + self.task_list[name].quit() + self.task_list[name].wait() + + elif name in self.task_list: + + self.task_list[name].task.requestInterruption() + self.task_list[name].requestInterruption() + self.task_list[name].quit() + self.task_list[name].wait() + + def remove_task(self, name: str, logs: str): + """移除任务标记""" + + logger.info(f"任务结束:{name}") + MainInfoBar.push_info_bar("info", "任务结束", name, 3000) + + if len(logs) > 0: + time = logs[0][1]["Time"] + history = "" + for log in logs: + Config.save_history(log[0], log[1]) + history += ( + f"任务名称:{log[0]},{log[1]["History"].replace("\n","\n ")}\n" + ) + Config.save_history(name, {"Time": time, "History": history}) + + self.task_list.pop(name) + Config.running_list.remove(name) + + def push_dialog(self, name: str, title: str, content: str): + """推送对话框""" + + choice = Dialog(title, content, None) + choice.yesButton.setText("是") + choice.cancelButton.setText("否") + + self.task_list[name].question_response.emit(bool(choice.exec_())) + + +Task_manager = TaskManager() diff --git a/app/core/timer.py b/app/core/timer.py new file mode 100644 index 0000000..11b9008 --- /dev/null +++ b/app/core/timer.py @@ -0,0 +1,119 @@ +# +# Copyright © <2024> + +# This file is part of AUTO_MAA. + +# AUTO_MAA is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published +# by the Free Software Foundation, either version 3 of the License, +# or (at your option) any later version. + +# AUTO_MAA is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty +# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See +# the GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with AUTO_MAA. If not, see . + +# DLmaster_361@163.com + +""" +AUTO_MAA +AUTO_MAA主业务定时器 +v4.2 +作者:DLmaster_361 +""" + +from loguru import logger +from PySide6.QtWidgets import QWidget +from PySide6.QtCore import QTimer +import json +import datetime +import pyautogui + +from .config import Config +from .task_manager import Task_manager +from app.services import System + + +class MainTimer(QWidget): + + def __init__( + self, + parent=None, + ): + super().__init__(parent) + + self.Timer = QTimer() + self.Timer.timeout.connect(self.timed_start) + self.Timer.timeout.connect(self.set_silence) + self.Timer.start(1000) + + def timed_start(self): + """定时启动代理任务""" + + # 获取定时列表 + queue_list = self.search_queue() + + for i in queue_list: + + name, info = i + + if not info["QueueSet"]["Enabled"]: + continue + + history = Config.get_history(name) + + time_set = [ + info["Time"][f"TimeSet_{_}"] + for _ in range(10) + if info["Time"][f"TimeEnabled_{_}"] + ] + # 按时间调起代理任务 + curtime = datetime.datetime.now().strftime("%Y-%m-%d %H:%M") + if ( + curtime[11:16] in time_set + and curtime != history["Time"][:16] + and name not in Config.running_list + ): + + logger.info(f"定时任务:{name}") + Task_manager.add_task("自动代理_新窗口", name, info) + + def set_silence(self): + """设置静默模式""" + + windows = System.get_window_info() + if any( + str(emulator_path) in window + for window in windows + for emulator_path in Config.silence_list + ): + try: + pyautogui.hotkey( + *[ + _.strip().lower() + for _ in Config.global_config.get( + Config.global_config.function_BossKey + ).split("+") + ] + ) + except pyautogui.FailSafeException as e: + logger.warning(f"FailSafeException: {e}") + + def search_queue(self) -> list: + """搜索所有调度队列实例""" + + queue_list = [] + + if (Config.app_path / "config/QueueConfig").exists(): + for json_file in (Config.app_path / "config/QueueConfig").glob("*.json"): + with json_file.open("r", encoding="utf-8") as f: + info = json.load(f) + queue_list.append([json_file.stem, info]) + + return queue_list + + +Main_timer = MainTimer() diff --git a/app/models/MAA.py b/app/models/MAA.py index 791e866..22887ec 100644 --- a/app/models/MAA.py +++ b/app/models/MAA.py @@ -24,63 +24,70 @@ MAA功能组件 v4.2 作者:DLmaster_361 """ - -from PySide6 import QtCore +from loguru import logger +from PySide6.QtCore import QObject, Signal, QEventLoop import json +import sqlite3 import datetime -import os import subprocess import shutil import time from pathlib import Path +from typing import List -from app import AppConfig +from app.core import Config +from app.services import Notify -class MaaManager(QtCore.QThread): +class MaaManager(QObject): """MAA控制器""" - question = QtCore.Signal() - push_notification = QtCore.Signal(str, str, str, int) - send_mail = QtCore.Signal(str, str) - update_gui = QtCore.Signal(str, str, str, str, str) - update_user_info = QtCore.Signal(list, list, list, list, list, list) - set_silence = QtCore.Signal(str, str, list) - accomplish = QtCore.Signal() - get_json = QtCore.Signal(list) + question = Signal(str, str) + question_response = Signal(bool) + update_user_info = Signal(list, list, list, list, list, list) + push_info_bar = Signal(str, str, str, int) + create_user_list = Signal(list) + update_user_list = Signal(list) + update_log_text = Signal(str) + accomplish = Signal(dict) - def __init__(self, config: AppConfig): + isInterruptionRequested = False + + def __init__( + self, + mode: str, + config_path: Path, + user_config_path: Path = None, + ): super(MaaManager, self).__init__() - self.config = config - self.mode = None - self.data = None - self.get_json_path = [0, 0, 0] + self.mode = mode + self.config_path = config_path + self.user_config_path = user_config_path + + with (self.config_path / "config.json").open("r", encoding="utf-8") as f: + self.set = json.load(f) + + if "设置MAA" not in self.mode: + + db = sqlite3.connect(self.config_path / "user_data.db") + cur = db.cursor() + cur.execute("SELECT * FROM adminx WHERE True") + self.data = cur.fetchall() + self.data = [list(row) for row in self.data] + cur.close() + db.close() + + else: + self.data = [] def configure(self): """提取配置信息""" - self.maa_root_path = Path(self.config.content["Default"]["MaaSet.path"]) - self.set_path = self.maa_root_path / "config/gui.json" - self.log_path = self.maa_root_path / "debug/gui.log" - self.maa_path = self.maa_root_path / "MAA.exe" - self.json_path = self.config.app_path / "data/MAAconfig" - self.routine = self.config.content["Default"]["TimeLimit.routine"] - self.annihilation = self.config.content["Default"]["TimeLimit.annihilation"] - self.num = self.config.content["Default"]["TimesLimit.run"] - self.boss_key = [ - _.strip().lower() - for _ in self.config.content["Default"]["SelfSet.BossKey"].split("+") - ] - self.if_send_mail = bool( - self.config.content["Default"]["SelfSet.IfSendMail"] == "True" - ) - self.if_send_error_only = bool( - self.config.content["Default"]["SelfSet.IfSendMail.OnlyError"] == "True" - ) - self.if_silence = bool( - self.config.content["Default"]["SelfSet.IfSilence"] == "True" - ) + self.maa_root_path = Path(self.set["MaaSet"]["Path"]) + self.maa_set_path = self.maa_root_path / "config/gui.json" + self.maa_log_path = self.maa_root_path / "debug/gui.log" + self.maa_exe_path = self.maa_root_path / "MAA.exe" def run(self): """主进程,运行MAA代理进程""" @@ -89,86 +96,102 @@ class MaaManager(QtCore.QThread): begin_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") self.configure() + # 检查MAA路径是否可用 + if not self.maa_exe_path.exists() or not self.maa_set_path.exists(): + + logger.error("未正确配置MAA路径,MAA代理进程中止") + self.push_info_bar.emit( + "error", "启动MAA代理进程失败", "您还未正确配置MAA路径!", -1 + ) + self.accomplish.emit( + { + "Time": begin_time, + "History": "由于未正确配置MAA路径,MAA代理进程中止", + } + ) + return None # 整理用户数据,筛选需代理的用户 self.data = sorted(self.data, key=lambda x: (-len(x[15]), x[16])) - wait_index = [] - over_index = [] - error_index = [] - all_index = [ - _ - for _ in range(len(self.data)) - if (self.data[_][3] != 0 and self.data[_][4] == "y") + user_list: List[List[str, str, int]] = [ + [_[0], "等待", index] + for index, _ in enumerate(self.data) + if (_[3] != 0 and _[4] == "y") ] + self.create_user_list.emit(user_list) - # 日常代理模式 - if self.mode == "日常代理": + # 自动代理模式 + if self.mode == "自动代理": # 执行情况预处理 - for _ in all_index: - if self.data[_][5] != curdate: - self.data[_][5] = curdate - self.data[_][14] = 0 - self.data[_][0] += f"_第{str(self.data[_][14] + 1)}次代理" + for _ in user_list: + if self.data[_[2]][5] != curdate: + self.data[_[2]][5] = curdate + self.data[_[2]][14] = 0 + _[0] += f" - 第{self.data[_[2]][14] + 1}次代理" # 开始代理 - for index in all_index: + for user in user_list: - if self.isInterruptionRequested(): + if self.isInterruptionRequested: break + user[1] = "运行" + self.update_user_list.emit(user_list) + # 初始化代理情况记录和模式替换记录 run_book = [False for _ in range(2)] - mode_book = ["日常代理_剿灭", "日常代理_日常"] + mode_book = ["自动代理_剿灭", "自动代理_日常"] - # 简洁模式用户默认开启日常代理 - if self.data[index][15] == "simple": - self.data[index][9] = "y" + # 简洁模式用户默认开启日常选项 + if self.data[user[2]][15] == "simple": + self.data[user[2]][9] = "y" # 尝试次数循环 - for i in range(self.num): + for i in range(self.set["RunSet"]["RunTimesLimit"]): - if self.isInterruptionRequested(): + if self.isInterruptionRequested: break # 剿灭-日常模式循环 for j in range(2): - if self.isInterruptionRequested(): + if self.isInterruptionRequested: break - if self.data[index][10 - j] == "n": + if self.data[user[2]][10 - j] == "n": run_book[j] = True continue if run_book[j]: continue # 配置MAA - self.set_maa(mode_book[j], index) + self.set_maa(mode_book[j], user[2]) # 记录当前时间 start_time = datetime.datetime.now() # 创建MAA任务 maa = subprocess.Popen( - [self.maa_path], + [self.maa_exe_path], shell=True, creationflags=subprocess.CREATE_NO_WINDOW, ) - # 启动静默进程 - if self.if_silence: - self.set_silence.emit( - "启用", self.get_emulator_path(), self.boss_key + # 添加静默进程标记 + if Config.global_config.get( + Config.global_config.function_IfSilence + ): + with self.maa_set_path.open( + mode="r", encoding="utf-8" + ) as f: + set = json.load(f) + self.emulator_path = Path( + set["Configurations"]["Default"]["Start.EmulatorPath"] ) + Config.silence_list.append(self.emulator_path) # 记录是否超时的标记 self.if_time_out = False - # 更新运行信息 - wait_index = [ - _ - for _ in all_index - if (not _ in over_index + error_index + [index]) - ] # 监测MAA运行状态 - while not self.isInterruptionRequested(): + while not self.isInterruptionRequested: # 获取MAA日志 logs = self.get_maa_log(start_time) @@ -188,11 +211,17 @@ class MaaManager(QtCore.QThread): if ( j == 0 and now_time - latest_time - > datetime.timedelta(minutes=self.annihilation) + > datetime.timedelta( + minutes=self.set["RunSet"][ + "AnnihilationTimeLimit" + ] + ) ) or ( j == 1 and now_time - latest_time - > datetime.timedelta(minutes=self.routine) + > datetime.timedelta( + minutes=self.set["RunSet"]["RoutineTimeLimit"] + ) ): self.if_time_out = True @@ -201,38 +230,24 @@ class MaaManager(QtCore.QThread): # 更新MAA日志 if len(logs) > 100: - self.update_gui.emit( - f"{self.data[index][0]}_第{i + 1}次_{mode_book[j][5:7]}", - "\n".join([self.data[_][0] for _ in wait_index]), - "\n".join([self.data[_][0] for _ in over_index]), - "\n".join([self.data[_][0] for _ in error_index]), - "".join(logs[-100:]), - ) + self.update_log_text.emit("".join(logs[-100:])) else: - self.update_gui.emit( - f"{self.data[index][0]}_第{i + 1}次_{mode_book[j][5:7]}", - "\n".join([self.data[_][0] for _ in wait_index]), - "\n".join([self.data[_][0] for _ in over_index]), - "\n".join([self.data[_][0] for _ in error_index]), - "".join(logs), - ) + self.update_log_text.emit("".join(logs)) # 判断MAA程序运行状态 result = self.if_maa_success(log, mode_book[j]) if result == "Success!": run_book[j] = True - self.update_gui.emit( - f"{self.data[index][0]}_第{i + 1}次_{mode_book[j][5:7]}", - "\n".join([self.data[_][0] for _ in wait_index]), - "\n".join([self.data[_][0] for _ in over_index]), - "\n".join([self.data[_][0] for _ in error_index]), - "检测到MAA进程完成代理任务\n正在等待相关程序结束\n请等待10s", + self.update_log_text.emit( + "检测到MAA进程完成代理任务\n正在等待相关程序结束\n请等待10s" ) - # 关闭静默进程 - if self.if_silence: - self.set_silence.emit("禁用", "", []) + # 移除静默进程标记 + if Config.global_config.get( + Config.global_config.function_IfSilence + ): + Config.silence_list.remove(self.emulator_path) for _ in range(10): - if self.isInterruptionRequested(): + if self.isInterruptionRequested: break time.sleep(1) break @@ -242,13 +257,7 @@ class MaaManager(QtCore.QThread): else: # 打印中止信息 # 此时,log变量内存储的就是出现异常的日志信息,可以保存或发送用于问题排查 - self.update_gui.emit( - f"{self.data[index][0]}_第{i + 1}次_{mode_book[j][5:7]}", - "\n".join([self.data[_][0] for _ in wait_index]), - "\n".join([self.data[_][0] for _ in over_index]), - "\n".join([self.data[_][0] for _ in error_index]), - result, - ) + self.update_log_text.emit(result) # 无命令行中止MAA与其子程序 killprocess = subprocess.Popen( f"taskkill /F /T /PID {maa.pid}", @@ -256,39 +265,43 @@ class MaaManager(QtCore.QThread): creationflags=subprocess.CREATE_NO_WINDOW, ) killprocess.wait() - # 关闭静默进程 - if self.if_silence: - self.set_silence.emit("禁用", "", []) + # 移除静默进程标记 + if Config.global_config.get( + Config.global_config.function_IfSilence + ): + Config.silence_list.remove(self.emulator_path) # 推送异常通知 - self.push_notification.emit( - "用户日常代理出现异常!", - f"用户 {self.data[index][0].replace("_", " 今天的")}的{mode_book[j][5:7]}部分出现一次异常", - f"{self.data[index][0].replace("_", " ")}的{mode_book[j][5:7]}出现异常", + Notify.push_notification( + "用户自动代理出现异常!", + f"用户 {user[0].replace("_", " 今天的")}的{mode_book[j][5:7]}部分出现一次异常", + f"{user[0].replace("_", " ")}的{mode_book[j][5:7]}出现异常", 1, ) for _ in range(10): - if self.isInterruptionRequested(): + if self.isInterruptionRequested: break time.sleep(1) break # 成功完成代理的用户修改相关参数 if run_book[0] and run_book[1]: - if self.data[index][14] == 0 and self.data[index][3] != -1: - self.data[index][3] -= 1 - self.data[index][14] += 1 - over_index.append(index) - self.push_notification.emit( - "成功完成一个日常代理任务!", - f"已完成用户 {self.data[index][0].replace("_", " 今天的")}任务", - f"已完成 {self.data[index][0].replace("_", " 的")}", + if self.data[user[2]][14] == 0 and self.data[user[2]][3] != -1: + self.data[user[2]][3] -= 1 + self.data[user[2]][14] += 1 + user[1] = "完成" + Notify.push_notification( + "成功完成一个自动代理任务!", + f"已完成用户 {user[0].replace("_", " 今天的")}任务", + f"已完成 {user[0].replace("_", " 的")}", 3, ) break # 录入代理失败的用户 if not (run_book[0] and run_book[1]): - error_index.append(index) + user[1] = "异常" + + self.update_user_list.emit(user_list) # 人工排查模式 elif self.mode == "人工排查": @@ -296,47 +309,44 @@ class MaaManager(QtCore.QThread): # 标记是否需要启动模拟器 if_strat_app = True # 标识排查模式 - for _ in all_index: - self.data[_][0] += "_排查模式" + for _ in user_list: + _[0] += "_排查模式" # 开始排查 - for index in all_index: + for user in user_list: - if self.isInterruptionRequested(): + if self.isInterruptionRequested: break - if self.data[index][15] == "beta": + user[1] = "运行" + self.update_user_list.emit(user_list) + + if self.data[user[2]][15] == "beta": if_strat_app = True run_book = [False for _ in range(2)] # 启动重试循环 - while not self.isInterruptionRequested(): + while not self.isInterruptionRequested: # 配置MAA if if_strat_app: - self.set_maa("人工排查_启动模拟器", index) + self.set_maa("人工排查_启动模拟器", user[2]) if_strat_app = False else: - self.set_maa("人工排查_仅切换账号", index) + self.set_maa("人工排查_仅切换账号", user[2]) # 记录当前时间 start_time = datetime.datetime.now() # 创建MAA任务 maa = subprocess.Popen( - [self.maa_path], + [self.maa_exe_path], shell=True, creationflags=subprocess.CREATE_NO_WINDOW, ) - # 更新运行信息 - wait_index = [ - _ - for _ in all_index - if (not _ in over_index + error_index + [index]) - ] # 监测MAA运行状态 - while not self.isInterruptionRequested(): + while not self.isInterruptionRequested: # 获取MAA日志 logs = self.get_maa_log(start_time) @@ -345,45 +355,21 @@ class MaaManager(QtCore.QThread): # 更新MAA日志 if len(logs) > 100: - self.update_gui.emit( - self.data[index][0], - "\n".join([self.data[_][0] for _ in wait_index]), - "\n".join([self.data[_][0] for _ in over_index]), - "\n".join([self.data[_][0] for _ in error_index]), - "".join(logs[-100:]), - ) + self.update_log_text.emit("".join(logs[-100:])) else: - self.update_gui.emit( - self.data[index][0], - "\n".join([self.data[_][0] for _ in wait_index]), - "\n".join([self.data[_][0] for _ in over_index]), - "\n".join([self.data[_][0] for _ in error_index]), - "".join(logs), - ) + self.update_log_text.emit("".join(logs)) # 判断MAA程序运行状态 result = self.if_maa_success(log, "人工排查") if result == "Success!": run_book[0] = True - self.update_gui.emit( - self.data[index][0], - "\n".join([self.data[_][0] for _ in wait_index]), - "\n".join([self.data[_][0] for _ in over_index]), - "\n".join([self.data[_][0] for _ in error_index]), - "检测到MAA进程成功登录PRTS", - ) + self.update_log_text.emit("检测到MAA进程成功登录PRTS") break elif result == "Wait": # 检测时间间隔 time.sleep(1) else: - self.update_gui.emit( - self.data[index][0], - "\n".join([self.data[_][0] for _ in wait_index]), - "\n".join([self.data[_][0] for _ in over_index]), - "\n".join([self.data[_][0] for _ in error_index]), - result, - ) + self.update_log_text.emit(result) # 无命令行中止MAA与其子程序 killprocess = subprocess.Popen( f"taskkill /F /T /PID {maa.pid}", @@ -393,7 +379,7 @@ class MaaManager(QtCore.QThread): killprocess.wait() if_strat_app = True for _ in range(10): - if self.isInterruptionRequested(): + if self.isInterruptionRequested: break time.sleep(1) break @@ -402,38 +388,36 @@ class MaaManager(QtCore.QThread): if run_book[0]: break # 登录失败,询问是否结束循环 - elif not self.isInterruptionRequested(): - self.question_title = "操作提示" - self.question_info = "MAA未能正确登录到PRTS,是否重试?" - self.question_choice = "wait" - self.question.emit() - while self.question_choice == "wait": - time.sleep(1) - if self.question_choice == "No": + elif not self.isInterruptionRequested: + + if not self.push_question( + "操作提示", "MAA未能正确登录到PRTS,是否重试?" + ): break # 登录成功,录入人工排查情况 - if run_book[0] and not self.isInterruptionRequested(): - self.question_title = "操作提示" - self.question_info = "请检查用户代理情况,如无异常请按下确认键。" - self.question_choice = "wait" - self.question.emit() - while self.question_choice == "wait": - time.sleep(1) - if self.question_choice == "Yes": + if run_book[0] and not self.isInterruptionRequested: + + if self.push_question( + "操作提示", "请检查用户代理情况,是否将该用户标记为异常?" + ): run_book[1] = True # 结果录入用户备注栏 if run_book[0] and run_book[1]: - if "未通过人工排查" in self.data[index][13]: - self.data[index][13] = self.data[index][13].replace( + if "未通过人工排查" in self.data[user[2]][13]: + self.data[user[2]][13] = self.data[user[2]][13].replace( "未通过人工排查|", "" ) - over_index.append(index) + user[1] = "完成" elif not (run_book[0] and run_book[1]): - if not "未通过人工排查" in self.data[index][13]: - self.data[index][13] = f"未通过人工排查|{self.data[index][13]}" - error_index.append(index) + if not "未通过人工排查" in self.data[user[2]][13]: + self.data[user[2]][ + 13 + ] = f"未通过人工排查|{self.data[user[2]][13]}" + user[1] = "异常" + + self.update_user_list.emit(user_list) # 设置MAA模式 elif "设置MAA" in self.mode: @@ -442,7 +426,7 @@ class MaaManager(QtCore.QThread): self.set_maa(self.mode, "") # 创建MAA任务 maa = subprocess.Popen( - [self.maa_path], + [self.maa_exe_path], shell=True, creationflags=subprocess.CREATE_NO_WINDOW, ) @@ -450,7 +434,7 @@ class MaaManager(QtCore.QThread): start_time = datetime.datetime.now() # 监测MAA运行状态 - while not self.isInterruptionRequested(): + while not self.isInterruptionRequested: # 获取MAA日志 logs = self.get_maa_log(start_time) @@ -465,19 +449,22 @@ class MaaManager(QtCore.QThread): # 检测时间间隔 time.sleep(1) - # 保存MAA配置文件 if "全局" in self.mode: - self.get_json.emit(["Default"]) - elif "用户" in self.mode: - self.get_json.emit(self.get_json_path) + (self.config_path / "Default").mkdir(parents=True, exist_ok=True) + shutil.copy(self.maa_set_path, self.config_path / "Default") - self.accomplish.emit() + elif "用户" in self.mode: + self.user_config_path.mkdir(parents=True, exist_ok=True) + shutil.copy(self.maa_set_path, self.user_config_path) + logger.debug(self.user_config_path) + + end_log = "" # 导出结果 - if self.mode in ["日常代理", "人工排查"]: + if self.mode in ["自动代理", "人工排查"]: # 关闭可能未正常退出的MAA进程 - if self.isInterruptionRequested(): + if self.isInterruptionRequested: killprocess = subprocess.Popen( f"taskkill /F /T /PID {maa.pid}", shell=True, @@ -486,14 +473,18 @@ class MaaManager(QtCore.QThread): killprocess.wait() # 更新用户数据 - modes = [self.data[_][15] for _ in all_index] - uids = [self.data[_][16] for _ in all_index] - days = [self.data[_][3] for _ in all_index] - lasts = [self.data[_][5] for _ in all_index] - notes = [self.data[_][13] for _ in all_index] - numbs = [self.data[_][14] for _ in all_index] + modes = [self.data[_[2]][15] for _ in user_list] + uids = [self.data[_[2]][16] for _ in user_list] + days = [self.data[_[2]][3] for _ in user_list] + lasts = [self.data[_[2]][5] for _ in user_list] + notes = [self.data[_[2]][13] for _ in user_list] + numbs = [self.data[_[2]][14] for _ in user_list] self.update_user_info.emit(modes, uids, days, lasts, notes, numbs) + error_index = [_[2] for _ in user_list if _[1] == "异常"] + over_index = [_[2] for _ in user_list if _[1] == "完成"] + wait_index = [_[2] for _ in user_list if _[1] == "等待"] + # 保存运行日志 end_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") end_log = ( @@ -506,46 +497,55 @@ class MaaManager(QtCore.QThread): f"{self.mode[2:4]}未成功的用户:\n" f"{"\n".join([self.data[_][0] for _ in error_index])}\n" ) - wait_index = [_ for _ in all_index if (not _ in over_index + error_index)] if len(wait_index) != 0: end_log += ( f"\n未开始{self.mode[2:4]}的用户:\n" f"{"\n".join([self.data[_][0] for _ in wait_index])}\n" ) - (self.config.app_path / "log.txt").write_text( - end_log, - encoding="utf-8", - ) - - # 恢复GUI运行面板 - self.update_gui.emit("", "", "", "", end_log) - # 推送代理结果通知 - self.push_notification.emit( + Notify.push_notification( f"{self.mode[2:4]}任务已完成!", f"已完成用户数:{len(over_index)},未完成用户数:{len(error_index) + len(wait_index)}", f"已完成用户数:{len(over_index)},未完成用户数:{len(error_index) + len(wait_index)}", 10, ) - if self.if_send_mail and ( - not self.if_send_error_only - or (self.if_send_error_only and len(error_index) + len(wait_index) != 0) + if not Config.global_config.get( + Config.global_config.notify_IfSendErrorOnly + ) or ( + Config.global_config.get(Config.global_config.notify_IfSendErrorOnly) + and len(error_index) + len(wait_index) != 0 ): - self.send_mail.emit( + Notify.send_mail( f"{self.mode[:4]}任务报告", f"{end_log}\n\nAUTO_MAA 敬上\n\n我们根据您在 AUTO_MAA 中的设置发送了这封电子邮件,本邮件无需回复\n", ) - if not self.isInterruptionRequested(): - self.accomplish.emit() + self.accomplish.emit({"Time": begin_time, "History": end_log}) + + def requestInterruption(self) -> None: + + logger.info("申请中止本次任务") + self.isInterruptionRequested = True + + def push_question(self, title: str, message: str) -> bool: + + self.question.emit(title, message) + loop = QEventLoop() + self.question_response.connect(self._capture_response) + self.question_response.connect(loop.quit) + loop.exec() + return self.response + + def _capture_response(self, response: bool) -> None: + self.response = response def get_maa_log(self, start_time): """获取MAA日志""" logs = [] if_log_start = False - with self.log_path.open(mode="r", encoding="utf-8") as f: + with self.maa_log_path.open(mode="r", encoding="utf-8") as f: for entry in f: if not if_log_start: try: @@ -564,8 +564,8 @@ class MaaManager(QtCore.QThread): def if_maa_success(self, log, mode): """判断MAA程序运行状态""" - if "日常代理" in mode: - if mode == "日常代理_日常" and "任务出错: Fight" in log: + if "自动代理" in mode: + if mode == "自动代理_日常" and "任务出错: Fight" in log: return "检测到MAA未能实际执行任务\n正在中止相关程序\n请等待10s" if "任务出错: StartUp" in log: return "检测到MAA未能正确登录PRTS\n正在中止相关程序\n请等待10s" @@ -579,7 +579,7 @@ class MaaManager(QtCore.QThread): return "检测到MAA进程异常\n正在中止相关程序\n请等待10s" elif self.if_time_out: return "检测到MAA进程超时\n正在中止相关程序\n请等待10s" - elif self.isInterruptionRequested(): + elif self.isInterruptionRequested: return "您中止了本次任务\n正在中止相关程序\n请等待" else: return "Wait" @@ -593,7 +593,7 @@ class MaaManager(QtCore.QThread): or ("MaaAssistantArknights GUI exited" in log) ): return "检测到MAA进程异常\n正在中止相关程序\n请等待10s" - elif self.isInterruptionRequested(): + elif self.isInterruptionRequested: return "您中止了本次任务\n正在中止相关程序\n请等待" else: return "Wait" @@ -609,51 +609,43 @@ class MaaManager(QtCore.QThread): # 预导入MAA配置文件 if mode == "设置MAA_用户": - set_book = ["simple", "beta"] - if ( - self.json_path - / f"{set_book[self.get_json_path[0]]}/{self.get_json_path[1]}/{self.get_json_path[2]}/gui.json" - ).exists(): - shutil.copy( - self.json_path - / f"{set_book[self.get_json_path[0]]}/{self.get_json_path[1]}/{self.get_json_path[2]}/gui.json", - self.set_path, - ) + if self.user_config_path.exists(): + shutil.copy(self.user_config_path / "gui.json", self.maa_set_path) else: shutil.copy( - self.json_path / "Default/gui.json", - self.set_path, + self.config_path / "Default/gui.json", + self.maa_set_path, ) elif (mode == "设置MAA_全局") or ( - ("日常代理" in mode or "人工排查" in mode) + ("自动代理" in mode or "人工排查" in mode) and self.data[index][15] == "simple" ): shutil.copy( - self.json_path / "Default/gui.json", - self.set_path, + self.config_path / "Default/gui.json", + self.maa_set_path, ) - elif "日常代理" in mode and self.data[index][15] == "beta": - if mode == "日常代理_剿灭": + elif "自动代理" in mode and self.data[index][15] == "beta": + if mode == "自动代理_剿灭": shutil.copy( - self.json_path + self.config_path / f"beta/{self.data[index][16]}/annihilation/gui.json", - self.set_path, + self.maa_set_path, ) - elif mode == "日常代理_日常": + elif mode == "自动代理_日常": shutil.copy( - self.json_path / f"beta/{self.data[index][16]}/routine/gui.json", - self.set_path, + self.config_path / f"beta/{self.data[index][16]}/routine/gui.json", + self.maa_set_path, ) elif "人工排查" in mode and self.data[index][15] == "beta": shutil.copy( - self.json_path / f"beta/{self.data[index][16]}/routine/gui.json", - self.set_path, + self.config_path / f"beta/{self.data[index][16]}/routine/gui.json", + self.maa_set_path, ) - with self.set_path.open(mode="r", encoding="utf-8") as f: + with self.maa_set_path.open(mode="r", encoding="utf-8") as f: data = json.load(f) - # 日常代理配置 - if "日常代理" in mode: + # 自动代理配置 + if "自动代理" in mode: data["Current"] = "Default" # 切换配置 for i in range(1, 9): @@ -661,22 +653,14 @@ class MaaManager(QtCore.QThread): data["Configurations"]["Default"][ "MainFunction.PostActions" ] = "12" # 完成后退出MAA和模拟器 - - # v5.1.11版本对于以下字段处理 - # 启动MAA后直接运行 - data["Global"]["Start.RunDirectly"] = "True" - # 启动MAA后自动开启模拟器 - data["Global"][ + data["Configurations"]["Default"][ + "Start.RunDirectly" + ] = "True" # 启动MAA后直接运行 + data["Configurations"]["Default"][ "Start.OpenEmulatorAfterLaunch" - ] = "True" + ] = "True" # 启动MAA后自动开启模拟器 - # v5.1.12版本对以下字段处理 - # 启动MAA后直接运行 - data["Configurations"]["Default"]["Start.OpenEmulatorAfterLaunch"] = "True" - # 启动MAA后自动开启模拟器 - data["Configurations"]["Default"]["Start.RunDirectly"] = "True" - - if self.if_silence: + if Config.global_config.get(Config.global_config.function_IfSilence): data["Global"]["Start.MinimizeDirectly"] = "True" # 启动MAA后直接最小化 data["Global"]["GUI.UseTray"] = "True" # 显示托盘图标 data["Global"]["GUI.MinimizeToTray"] = "True" # 最小化时隐藏至托盘 @@ -867,7 +851,7 @@ class MaaManager(QtCore.QThread): ] = "False" # 自定义基建配置文件只读 data["Configurations"]["Default"][ "Infrast.CustomInfrastFile" - ] = f"{self.json_path}/simple/{self.data[index][16]}/infrastructure/infrastructure.json" # 自定义基建配置文件地址 + ] = f"{self.config_path}/simple/{self.data[index][16]}/infrastructure/infrastructure.json" # 自定义基建配置文件地址 # 人工排查配置 elif "人工排查" in mode: @@ -878,9 +862,9 @@ class MaaManager(QtCore.QThread): data["Configurations"]["Default"][ "MainFunction.PostActions" ] = "8" # 完成后退出MAA - - # v5.1.11版本对于以下字段处理 - data["Global"]["Start.RunDirectly"] = "True" # 启动MAA后直接运行 + data["Configurations"]["Default"][ + "Start.RunDirectly" + ] = "True" # 启动MAA后直接运行 data["Global"]["Start.MinimizeDirectly"] = "True" # 启动MAA后直接最小化 # v5.1.12版本对以下字段处理 # 启动MAA后直接运行 @@ -888,14 +872,17 @@ class MaaManager(QtCore.QThread): # 启动MAA后自动开启模拟器 data["Configurations"]["Default"]["Start.RunDirectly"] = "True" - data["Global"]["GUI.UseTray"] = "True" # 显示托盘图标 data["Global"]["GUI.MinimizeToTray"] = "True" # 最小化时隐藏至托盘 # 启动MAA后自动开启模拟器 if "启动模拟器" in mode: - data["Global"]["Start.OpenEmulatorAfterLaunch"] = "True" + data["Configurations"]["Default"][ + "Start.OpenEmulatorAfterLaunch" + ] = "True" elif "仅切换账号" in mode: - data["Global"]["Start.OpenEmulatorAfterLaunch"] = "False" + data["Configurations"]["Default"][ + "Start.OpenEmulatorAfterLaunch" + ] = "False" if self.data[index][15] == "simple": @@ -957,20 +944,14 @@ class MaaManager(QtCore.QThread): data["Configurations"]["Default"][ "MainFunction.PostActions" ] = "0" # 完成后无动作 - - # v5.11.1及以下版本的字段处理 - data["Global"]["Start.RunDirectly"] = "False" # 启动MAA后直接运行 - data["Global"][ + data["Configurations"]["Default"][ + "Start.RunDirectly" + ] = "False" # 启动MAA后直接运行 + data["Configurations"]["Default"][ "Start.OpenEmulatorAfterLaunch" ] = "False" # 启动MAA后自动开启模拟器 - # v5.1.12版本对以下字段处理 - # 启动MAA后直接运行 - data["Configurations"]["Default"]["Start.OpenEmulatorAfterLaunch"] = "False" - # 启动MAA后自动开启模拟器 - data["Configurations"]["Default"]["Start.RunDirectly"] = "False" - - if self.if_silence: + if Config.global_config.get(Config.global_config.function_IfSilence): data["Global"][ "Start.MinimizeDirectly" ] = "False" # 启动MAA后直接最小化 @@ -1012,8 +993,8 @@ class MaaManager(QtCore.QThread): ] = "False" # 生息演算 # 覆写配置文件 - with self.set_path.open(mode="w", encoding="utf-8") as f: - json.dump(data, f, indent=4) + with self.maa_set_path.open(mode="w", encoding="utf-8") as f: + json.dump(data, f, ensure_ascii=False, indent=4) return True @@ -1021,7 +1002,7 @@ class MaaManager(QtCore.QThread): """获取模拟器路径""" # 读取配置文件 - with self.set_path.open(mode="r", encoding="utf-8") as f: + with self.maa_set_path.open(mode="r", encoding="utf-8") as f: set = json.load(f) # 获取模拟器路径 return Path(set["Configurations"]["Default"]["Start.EmulatorPath"]) diff --git a/app/services/__init__.py b/app/services/__init__.py index cf23c93..7c0349d 100644 --- a/app/services/__init__.py +++ b/app/services/__init__.py @@ -29,7 +29,8 @@ __version__ = "4.2.0" __author__ = "DLmaster361 " __license__ = "GPL-3.0 license" -from .notification import Notification -from .security import CryptoHandler +from .notification import Notify +from .security import Crypto +from .system import System -__all__ = ["Notification", "CryptoHandler"] +__all__ = ["Notify", "Crypto", "System"] diff --git a/app/services/notification.py b/app/services/notification.py index 4afd7f5..07865ec 100644 --- a/app/services/notification.py +++ b/app/services/notification.py @@ -30,29 +30,26 @@ import smtplib from email.mime.text import MIMEText from email.header import Header from email.utils import formataddr -import os -from app import AppConfig +from app.core import Config class Notification: - def __init__(self, config: AppConfig): - - self.config = config - def push_notification(self, title, message, ticker, t): """推送系统通知""" - notification.notify( - title=title, - message=message, - app_name="AUTO_MAA", - app_icon=str(self.config.app_path / "resources/icons/AUTO_MAA.ico"), - timeout=t, - ticker=ticker, - toast=True, - ) + if Config.global_config.get(Config.global_config.notify_IfPushPlyer): + + notification.notify( + title=title, + message=message, + app_name="AUTO_MAA", + app_icon=str(Config.app_path / "resources/icons/AUTO_MAA.ico"), + timeout=t, + ticker=ticker, + toast=True, + ) return True @@ -62,34 +59,42 @@ class Notification: # 声明:此邮箱为AUTO_MAA项目组资产,未经授权不得私自使用 # 注意:此声明注释只有使用者更换发信邮箱时才能删除,本条规则优先级高于GPLv3 - # 第三方 SMTP 服务配置 - mail_host = "smtp.163.com" # 设置服务器 - mail_sender = "AUTO_MAA_server@163.com" # 用户名 - mail_key = "SYrq87nDLD4RNB5T" # 授权码 24/11/15 + if Config.global_config.get(Config.global_config.notify_IfSendMail): - # 定义邮件正文 - message = MIMEText(content, "plain", "utf-8") - message["From"] = formataddr( - (Header("AUTO_MAA通知服务", "utf-8").encode(), "AUTO_MAA_server@163.com") - ) # 发件人显示的名字 - message["To"] = formataddr( - ( - Header("AUTO_MAA用户", "utf-8").encode(), - self.config.content["Default"]["SelfSet.MailAddress"], - ) - ) # 收件人显示的名字 - message["Subject"] = Header(title, "utf-8") + # 第三方 SMTP 服务配置 + mail_host = "smtp.163.com" # 设置服务器 + mail_sender = "AUTO_MAA_server@163.com" # 用户名 + mail_key = "SYrq87nDLD4RNB5T" # 授权码 24/11/15 - try: - smtpObj = smtplib.SMTP_SSL(mail_host, 465) # 465为SMTP_SSL默认端口 - smtpObj.login(mail_sender, mail_key) - smtpObj.sendmail( - mail_sender, - self.config.content["Default"]["SelfSet.MailAddress"], - message.as_string(), - ) - return True - except smtplib.SMTPException as e: - return f"发送邮件时出错:\n{e}" - finally: - smtpObj.quit() + # 定义邮件正文 + message = MIMEText(content, "plain", "utf-8") + message["From"] = formataddr( + ( + Header("AUTO_MAA通知服务", "utf-8").encode(), + "AUTO_MAA_server@163.com", + ) + ) # 发件人显示的名字 + message["To"] = formataddr( + ( + Header("AUTO_MAA用户", "utf-8").encode(), + Config.global_config.get(Config.global_config.notify_MailAddress), + ) + ) # 收件人显示的名字 + message["Subject"] = Header(title, "utf-8") + + try: + smtpObj = smtplib.SMTP_SSL(mail_host, 465) # 465为SMTP_SSL默认端口 + smtpObj.login(mail_sender, mail_key) + smtpObj.sendmail( + mail_sender, + Config.global_config.get(Config.global_config.notify_MailAddress), + message.as_string(), + ) + return True + except smtplib.SMTPException as e: + return f"发送邮件时出错:\n{e}" + finally: + smtpObj.quit() + + +Notify = Notification() diff --git a/app/services/security.py b/app/services/security.py index b39dc00..21d9a80 100644 --- a/app/services/security.py +++ b/app/services/security.py @@ -20,12 +20,11 @@ """ AUTO_MAA -AUTO_MAA主程序 +AUTO_MAA安全服务 v4.2 作者:DLmaster_361 """ -import os import hashlib import random import secrets @@ -34,37 +33,33 @@ from Crypto.PublicKey import RSA from Crypto.Cipher import PKCS1_OAEP from Crypto.Util.Padding import pad, unpad -from app import AppConfig +from app.core import Config class CryptoHandler: - def __init__(self, config: AppConfig): - - self.config = config - def get_PASSWORD(self, PASSWORD: str) -> None: """配置管理密钥""" # 生成目录 - self.config.key_path.mkdir(parents=True, exist_ok=True) + Config.key_path.mkdir(parents=True, exist_ok=True) # 生成RSA密钥对 key = RSA.generate(2048) public_key_local = key.publickey() private_key = key # 保存RSA公钥 - (self.config.app_path / "data/key/public_key.pem").write_bytes( + (Config.app_path / "data/key/public_key.pem").write_bytes( public_key_local.exportKey() ) # 生成密钥转换与校验随机盐 PASSWORD_salt = secrets.token_hex(random.randint(32, 1024)) - (self.config.app_path / "data/key/PASSWORDsalt.txt").write_text( + (Config.app_path / "data/key/PASSWORDsalt.txt").write_text( PASSWORD_salt, encoding="utf-8", ) verify_salt = secrets.token_hex(random.randint(32, 1024)) - (self.config.app_path / "data/key/verifysalt.txt").write_text( + (Config.app_path / "data/key/verifysalt.txt").write_text( verify_salt, encoding="utf-8", ) @@ -76,22 +71,20 @@ class CryptoHandler: AES_password_verify = hashlib.sha256( AES_password + verify_salt.encode("utf-8") ).digest() - (self.config.app_path / "data/key/AES_password_verify.bin").write_bytes( + (Config.app_path / "data/key/AES_password_verify.bin").write_bytes( AES_password_verify ) # AES-256加密RSA私钥并保存密文 AES_key = AES.new(AES_password, AES.MODE_ECB) private_key_local = AES_key.encrypt(pad(private_key.exportKey(), 32)) - (self.config.app_path / "data/key/private_key.bin").write_bytes( - private_key_local - ) + (Config.app_path / "data/key/private_key.bin").write_bytes(private_key_local) def encryptx(self, note: str) -> bytes: """加密数据""" # 读取RSA公钥 public_key_local = RSA.import_key( - (self.config.app_path / "data/key/public_key.pem").read_bytes() + (Config.app_path / "data/key/public_key.pem").read_bytes() ) # 使用RSA公钥对数据进行加密 cipher = PKCS1_OAEP.new(public_key_local) @@ -103,22 +96,20 @@ class CryptoHandler: # 读入RSA私钥密文、盐与校验哈希值 private_key_local = ( - (self.config.app_path / "data/key/private_key.bin").read_bytes().strip() + (Config.app_path / "data/key/private_key.bin").read_bytes().strip() ) PASSWORD_salt = ( - (self.config.app_path / "data/key/PASSWORDsalt.txt") + (Config.app_path / "data/key/PASSWORDsalt.txt") .read_text(encoding="utf-8") .strip() ) verify_salt = ( - (self.config.app_path / "data/key/verifysalt.txt") + (Config.app_path / "data/key/verifysalt.txt") .read_text(encoding="utf-8") .strip() ) AES_password_verify = ( - (self.config.app_path / "data/key/AES_password_verify.bin") - .read_bytes() - .strip() + (Config.app_path / "data/key/AES_password_verify.bin").read_bytes().strip() ) # 将管理密钥转化为AES-256密钥并验证 AES_password = hashlib.sha256( @@ -149,7 +140,7 @@ class CryptoHandler: # 使用新管理密钥重新加密 self.get_PASSWORD(PASSWORD_new) for i in range(len(data)): - self.config.cur.execute( + Config.cur.execute( "UPDATE adminx SET password = ? WHERE mode = ? AND uid = ?", ( self.encryptx(new_data[i]), @@ -157,7 +148,7 @@ class CryptoHandler: data[i][16], ), ) - self.config.db.commit(), + Config.db.commit(), new_data[i] = None del new_data @@ -165,3 +156,6 @@ class CryptoHandler: """验证管理密钥""" return bool(self.decryptx(self.encryptx(""), PASSWORD) != "管理密钥错误") + + +Crypto = CryptoHandler() diff --git a/app/services/system.py b/app/services/system.py new file mode 100644 index 0000000..8099229 --- /dev/null +++ b/app/services/system.py @@ -0,0 +1,120 @@ +# +# Copyright © <2024> + +# This file is part of AUTO_MAA. + +# AUTO_MAA is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published +# by the Free Software Foundation, either version 3 of the License, +# or (at your option) any later version. + +# AUTO_MAA is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty +# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See +# the GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with AUTO_MAA. If not, see . + +# DLmaster_361@163.com + +""" +AUTO_MAA +AUTO_MAA系统服务 +v4.2 +作者:DLmaster_361 +""" + +import ctypes +import win32gui +import win32process +import winreg +import psutil + +from app.core import Config + + +class SystemHandler: + + ES_CONTINUOUS = 0x80000000 + ES_SYSTEM_REQUIRED = 0x00000001 + + def __init__(self): + + self.set_Sleep() + self.set_SelfStart() + + def set_Sleep(self): + """同步系统休眠状态""" + + if Config.global_config.get(Config.global_config.function_IfAllowSleep): + # 设置系统电源状态 + ctypes.windll.kernel32.SetThreadExecutionState( + self.ES_CONTINUOUS | self.ES_SYSTEM_REQUIRED + ) + else: + # 恢复系统电源状态 + ctypes.windll.kernel32.SetThreadExecutionState(self.ES_CONTINUOUS) + + def set_SelfStart(self): + """同步开机自启""" + + if ( + Config.global_config.get(Config.global_config.start_IfSelfStart) + and not self.is_startup() + ): + key = winreg.OpenKey( + winreg.HKEY_CURRENT_USER, + r"Software\Microsoft\Windows\CurrentVersion\Run", + winreg.KEY_SET_VALUE, + winreg.KEY_ALL_ACCESS | winreg.KEY_WRITE | winreg.KEY_CREATE_SUB_KEY, + ) + winreg.SetValueEx(key, "AUTO_MAA", 0, winreg.REG_SZ, Config.app_path_sys) + winreg.CloseKey(key) + elif ( + not Config.global_config.get(Config.global_config.start_IfSelfStart) + and self.is_startup() + ): + key = winreg.OpenKey( + winreg.HKEY_CURRENT_USER, + r"Software\Microsoft\Windows\CurrentVersion\Run", + winreg.KEY_SET_VALUE, + winreg.KEY_ALL_ACCESS | winreg.KEY_WRITE | winreg.KEY_CREATE_SUB_KEY, + ) + winreg.DeleteValue(key, "AUTO_MAA") + winreg.CloseKey(key) + + def is_startup(self): + """判断程序是否已经开机自启""" + + key = winreg.OpenKey( + winreg.HKEY_CURRENT_USER, + r"Software\Microsoft\Windows\CurrentVersion\Run", + 0, + winreg.KEY_READ, + ) + + try: + value, _ = winreg.QueryValueEx(key, "AUTO_MAA") + winreg.CloseKey(key) + return True + except FileNotFoundError: + winreg.CloseKey(key) + return False + + def get_window_info(self): + """获取当前窗口信息""" + + def callback(hwnd, window_info): + if win32gui.IsWindowVisible(hwnd) and win32gui.GetWindowText(hwnd): + _, pid = win32process.GetWindowThreadProcessId(hwnd) + process = psutil.Process(pid) + window_info.append((win32gui.GetWindowText(hwnd), process.exe())) + return True + + window_info = [] + win32gui.EnumWindows(callback, window_info) + return window_info + + +System = SystemHandler() diff --git a/app/ui/Widget.py b/app/ui/Widget.py new file mode 100644 index 0000000..1ec5a8f --- /dev/null +++ b/app/ui/Widget.py @@ -0,0 +1,282 @@ +# +# Copyright © <2024> + +# This file is part of AUTO_MAA. + +# AUTO_MAA is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published +# by the Free Software Foundation, either version 3 of the License, +# or (at your option) any later version. + +# AUTO_MAA is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty +# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See +# the GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with AUTO_MAA. If not, see . + +# DLmaster_361@163.com + +""" +AUTO_MAA +AUTO_MAA组件 +v4.2 +作者:DLmaster_361 +""" + +from PySide6.QtCore import Qt, QTime +from PySide6.QtGui import QIcon +from PySide6.QtWidgets import QWidget, QHBoxLayout +from qfluentwidgets import ( + LineEdit, + PasswordLineEdit, + MessageBoxBase, + SubtitleLabel, + SettingCard, + SpinBox, + FluentIconBase, + Signal, + ComboBox, + CheckBox, + qconfig, + ConfigItem, + TimeEdit, + OptionsConfigItem, +) + +from typing import Union, List + + +class InputMessageBox(MessageBoxBase): + """输入对话框""" + + def __init__(self, parent, title: str, content: str, mode: str, list: list = None): + super().__init__(parent) + self.title = SubtitleLabel(title) + + if mode == "明文": + self.input = LineEdit() + self.input.setClearButtonEnabled(True) + elif mode == "密码": + self.input = PasswordLineEdit() + elif mode == "选择": + self.input = ComboBox() + self.input.addItems(list) + self.input.setCurrentIndex(-1) + + self.input.setPlaceholderText(content) + + # 将组件添加到布局中 + self.viewLayout.addWidget(self.title) + self.viewLayout.addWidget(self.input) + + +class SetMessageBox(MessageBoxBase): + """输入对话框""" + + def __init__(self, parent, title: str, content: List[str], list: List[List[str]]): + super().__init__(parent) + self.title = SubtitleLabel(title) + + Widget = QWidget() + Layout = QHBoxLayout(Widget) + + self.input: List[ComboBox] = [] + + for i in range(len(content)): + + self.input.append(ComboBox()) + self.input[i].addItems(list[i]) + self.input[i].setCurrentIndex(-1) + self.input[i].setPlaceholderText(content[i]) + Layout.addWidget(self.input[i]) + + # 将组件添加到布局中 + self.viewLayout.addWidget(self.title) + self.viewLayout.addWidget(Widget) + + +class LineEditSettingCard(SettingCard): + """Setting card with switch button""" + + textChanged = Signal(str) + + def __init__( + self, + text, + icon: Union[str, QIcon, FluentIconBase], + title, + content=None, + configItem: ConfigItem = None, + parent=None, + ): + + super().__init__(icon, title, content, parent) + self.configItem = configItem + self.LineEdit = LineEdit(self) + self.LineEdit.setMinimumWidth(250) + self.LineEdit.setPlaceholderText(text) + + if configItem: + self.setValue(qconfig.get(configItem)) + configItem.valueChanged.connect(self.setValue) + + self.hBoxLayout.addWidget(self.LineEdit, 0, Qt.AlignRight) + self.hBoxLayout.addSpacing(16) + + self.LineEdit.textChanged.connect(self.__textChanged) + + def __textChanged(self, content: str): + self.setValue(content) + self.textChanged.emit(content) + + def setValue(self, content: str): + if self.configItem: + qconfig.set(self.configItem, content) + + self.LineEdit.setText(content) + + +class SpinBoxSettingCard(SettingCard): + + textChanged = Signal(int) + + def __init__( + self, + range: tuple[int, int], + icon: Union[str, QIcon, FluentIconBase], + title, + content=None, + configItem: ConfigItem = None, + parent=None, + ): + + super().__init__(icon, title, content, parent) + self.configItem = configItem + self.SpinBox = SpinBox(self) + self.SpinBox.setRange(range[0], range[1]) + self.SpinBox.setMinimumWidth(150) + + if configItem: + self.setValue(qconfig.get(configItem)) + configItem.valueChanged.connect(self.setValue) + + self.hBoxLayout.addWidget(self.SpinBox, 0, Qt.AlignRight) + self.hBoxLayout.addSpacing(16) + + self.SpinBox.valueChanged.connect(self.__valueChanged) + + def __valueChanged(self, value: int): + self.setValue(value) + self.textChanged.emit(value) + + def setValue(self, value: int): + if self.configItem: + qconfig.set(self.configItem, value) + + self.SpinBox.setValue(value) + + +class NoOptionComboBoxSettingCard(SettingCard): + + def __init__( + self, + configItem: OptionsConfigItem, + icon: Union[str, QIcon, FluentIconBase], + title, + content=None, + value=None, + texts=None, + parent=None, + ): + + super().__init__(icon, title, content, parent) + self.configItem = configItem + self.comboBox = ComboBox(self) + self.comboBox.setMinimumWidth(250) + self.hBoxLayout.addWidget(self.comboBox, 0, Qt.AlignRight) + self.hBoxLayout.addSpacing(16) + + self.optionToText = {o: t for o, t in zip(value, texts)} + for text, option in zip(texts, value): + self.comboBox.addItem(text, userData=option) + + self.comboBox.setCurrentText(self.optionToText[qconfig.get(configItem)]) + self.comboBox.currentIndexChanged.connect(self._onCurrentIndexChanged) + configItem.valueChanged.connect(self.setValue) + + def _onCurrentIndexChanged(self, index: int): + + qconfig.set(self.configItem, self.comboBox.itemData(index)) + + def setValue(self, value): + if value not in self.optionToText: + return + + self.comboBox.setCurrentText(self.optionToText[value]) + qconfig.set(self.configItem, value) + + +class TimeEditSettingCard(SettingCard): + + enabledChanged = Signal(bool) + timeChanged = Signal(str) + + def __init__( + self, + icon: Union[str, QIcon, FluentIconBase], + title, + content=None, + configItem_bool: ConfigItem = None, + configItem_time: ConfigItem = None, + parent=None, + ): + + super().__init__(icon, title, content, parent) + self.configItem_bool = configItem_bool + self.configItem_time = configItem_time + self.CheckBox = CheckBox(self) + self.CheckBox.setTristate(False) + self.TimeEdit = TimeEdit(self) + self.TimeEdit.setDisplayFormat("HH:mm") + self.TimeEdit.setMinimumWidth(150) + + if configItem_bool: + self.setValue_bool(qconfig.get(configItem_bool)) + configItem_bool.valueChanged.connect(self.setValue_bool) + + if configItem_time: + self.setValue_time(qconfig.get(configItem_time)) + configItem_time.valueChanged.connect(self.setValue_time) + + self.hBoxLayout.addWidget(self.CheckBox, 0, Qt.AlignRight) + self.hBoxLayout.addWidget(self.TimeEdit, 0, Qt.AlignRight) + self.hBoxLayout.addSpacing(16) + + self.CheckBox.stateChanged.connect(self.__enableChanged) + self.TimeEdit.timeChanged.connect(self.__timeChanged) + + def __timeChanged(self, value: QTime): + self.setValue_time(value.toString("HH:mm")) + self.timeChanged.emit(value.toString("HH:mm")) + + def __enableChanged(self, value: int): + if value == 0: + self.setValue_bool(False) + self.enabledChanged.emit(False) + else: + self.setValue_bool(True) + self.enabledChanged.emit(True) + + def setValue_bool(self, value: bool): + if self.configItem_bool: + qconfig.set(self.configItem_bool, value) + + self.CheckBox.setChecked(value) + + def setValue_time(self, value: str): + if self.configItem_time: + qconfig.set(self.configItem_time, value) + + self.TimeEdit.setTime(QTime.fromString(value, "HH:mm")) diff --git a/app/ui/__init__.py b/app/ui/__init__.py index 7156ee7..3764f04 100644 --- a/app/ui/__init__.py +++ b/app/ui/__init__.py @@ -29,6 +29,6 @@ __version__ = "4.2.0" __author__ = "DLmaster361 " __license__ = "GPL-3.0 license" -from .gui import AUTO_MAA +from .main_window import AUTO_MAA __all__ = ["AUTO_MAA"] diff --git a/app/ui/dispatch_center.py b/app/ui/dispatch_center.py new file mode 100644 index 0000000..4c0abc6 --- /dev/null +++ b/app/ui/dispatch_center.py @@ -0,0 +1,435 @@ +# +# Copyright © <2024> + +# This file is part of AUTO_MAA. + +# AUTO_MAA is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published +# by the Free Software Foundation, either version 3 of the License, +# or (at your option) any later version. + +# AUTO_MAA is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty +# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See +# the GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with AUTO_MAA. If not, see . + +# DLmaster_361@163.com + +""" +AUTO_MAA +AUTO_MAA调度中枢界面 +v4.2 +作者:DLmaster_361 +""" + +from loguru import logger +from PySide6.QtWidgets import ( + QWidget, + QVBoxLayout, + QStackedWidget, + QHBoxLayout, +) +from qfluentwidgets import ( + CardWidget, + IconWidget, + BodyLabel, + Pivot, + ScrollArea, + FluentIcon, + HeaderCardWidget, + FluentIcon, + TextBrowser, + ComboBox, + SubtitleLabel, + PushButton, +) +from PySide6.QtCore import Qt +from PySide6.QtGui import QTextCursor +from typing import List, Dict +import json + + +from app.core import Config, Task_manager, Task, MainInfoBar + + +class DispatchCenter(QWidget): + + def __init__(self, parent=None): + super().__init__(parent) + + self.setObjectName("调度中枢") + + self.pivot = Pivot(self) + self.stackedWidget = QStackedWidget(self) + self.Layout = QVBoxLayout(self) + + self.script_list: Dict[str, DispatchBox] = {} + + dispatch_box = DispatchBox("主调度台", self) + self.script_list["主调度台"] = dispatch_box + self.stackedWidget.addWidget(self.script_list["主调度台"]) + self.pivot.addItem( + routeKey="主调度台", + text="主调度台", + onClick=self.update_top_bar, + icon=FluentIcon.CAFE, + ) + self.update_top_bar() + + self.Layout.addWidget(self.pivot, 0, Qt.AlignHCenter) + self.Layout.addWidget(self.stackedWidget) + self.Layout.setContentsMargins(0, 0, 0, 0) + + self.pivot.currentItemChanged.connect( + lambda index: self.stackedWidget.setCurrentWidget(self.script_list[index]) + ) + + def add_board(self, task: Task) -> None: + """添加一个调度台界面""" + + dispatch_box = DispatchBox(task.name, self) + + dispatch_box.top_bar.button.clicked.connect( + lambda: Task_manager.stop_task(task.name) + ) + + task.create_task_list.connect(dispatch_box.info.task.create_task) + task.create_user_list.connect(dispatch_box.info.user.create_user) + task.update_task_list.connect(dispatch_box.info.task.update_task) + task.update_user_list.connect(dispatch_box.info.user.update_user) + task.update_log_text.connect(dispatch_box.info.log_text.text.setText) + task.accomplish.connect(lambda: self.del_board(f"调度台_{task.name}")) + + self.script_list[f"调度台_{task.name}"] = dispatch_box + + self.stackedWidget.addWidget(self.script_list[f"调度台_{task.name}"]) + + self.pivot.addItem(routeKey=f"调度台_{task.name}", text=f"调度台 {task.name}") + + def del_board(self, name: str) -> None: + """删除指定子界面""" + + self.pivot.setCurrentItem("主调度台") + self.stackedWidget.removeWidget(self.script_list[name]) + self.script_list[name].deleteLater() + self.pivot.removeWidget(name) + + def connect_main_board(self, task: Task) -> None: + """连接主调度台""" + + self.script_list["主调度台"].top_bar.button.clicked.disconnect() + self.script_list["主调度台"].top_bar.button.setText("中止任务") + self.script_list["主调度台"].top_bar.button.clicked.connect( + lambda: Task_manager.stop_task(task.name) + ) + task.create_task_list.connect( + self.script_list["主调度台"].info.task.create_task + ) + task.create_user_list.connect( + self.script_list["主调度台"].info.user.create_user + ) + task.update_task_list.connect( + self.script_list["主调度台"].info.task.update_task + ) + task.update_user_list.connect( + self.script_list["主调度台"].info.user.update_user + ) + task.update_log_text.connect( + self.script_list["主调度台"].info.log_text.text.setText + ) + task.accomplish.connect(lambda: self.disconnect_main_board(task.name)) + + def disconnect_main_board(self, name: str) -> None: + """断开主调度台""" + + self.script_list["主调度台"].top_bar.button.clicked.disconnect() + self.script_list["主调度台"].top_bar.button.setText("开始任务") + self.script_list["主调度台"].top_bar.button.clicked.connect( + self.script_list["主调度台"].top_bar.start_task + ) + self.script_list["主调度台"].info.log_text.text.setText( + Config.get_history(name)["History"] + ) + + def update_top_bar(self): + """更新顶栏""" + + list = [] + + if (Config.app_path / "config/QueueConfig").exists(): + for json_file in (Config.app_path / "config/QueueConfig").glob("*.json"): + list.append(f"队列 - {json_file.stem}") + + if (Config.app_path / "config/MaaConfig").exists(): + for subdir in (Config.app_path / "config/MaaConfig").iterdir(): + if subdir.is_dir(): + list.append(f"实例 - Maa - {subdir.name}") + + self.script_list["主调度台"].top_bar.object.clear() + self.script_list["主调度台"].top_bar.object.addItems(list) + + +class DispatchBox(QWidget): + + def __init__(self, name: str, parent=None): + super().__init__(parent) + + self.setObjectName(name) + + layout = QVBoxLayout() + + scrollArea = ScrollArea() + scrollArea.setWidgetResizable(True) + + 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.addWidget(scrollArea) + + self.setLayout(layout) + + class DispatchTopBar(CardWidget): + + def __init__(self, parent=None, name: str = None): + super().__init__(parent) + + Layout = QHBoxLayout(self) + + if name == "主调度台": + + self.object = ComboBox() + self.object.setCurrentIndex(-1) + self.object.setPlaceholderText("请选择调度对象") + self.mode = ComboBox() + self.mode.addItems(["自动代理", "人工排查"]) + self.mode.setCurrentIndex(-1) + self.mode.setPlaceholderText("请选择调度模式") + + self.button = PushButton("开始任务") + self.button.clicked.connect(self.start_task) + + Layout.addWidget(self.object) + Layout.addWidget(self.mode) + Layout.addStretch(1) + Layout.addWidget(self.button) + + else: + + self.Lable = SubtitleLabel(name, self) + self.button = PushButton("中止任务") + + Layout.addWidget(self.Lable) + Layout.addStretch(1) + Layout.addWidget(self.button) + + def start_task(self): + """开始任务""" + + if self.object.currentIndex() == -1: + logger.warning("未选择调度对象") + MainInfoBar.push_info_bar( + "warning", "未选择调度对象", "请选择后再开始任务", 5000 + ) + return None + + if self.mode.currentIndex() == -1: + logger.warning("未选择调度模式") + MainInfoBar.push_info_bar( + "warning", "未选择调度模式", "请选择后再开始任务", 5000 + ) + return None + + name = self.object.currentText().split(" - ")[-1] + + if name in Config.running_list: + logger.warning(f"任务已存在:{name}") + MainInfoBar.push_info_bar("warning", "任务已存在", name, 5000) + return None + + if self.object.currentText().split(" - ")[0] == "队列": + + with (Config.app_path / f"config/QueueConfig/{name}.json").open( + mode="r", encoding="utf-8" + ) as f: + info = json.load(f) + + logger.info(f"用户添加任务:{name}") + Task_manager.add_task(f"{self.mode.currentText()}_主窗口", name, info) + + elif self.object.currentText().split(" - ")[0] == "实例": + + if self.object.currentText().split(" - ")[1] == "Maa": + + info = {"Queue": {"Member_1": name}} + + logger.info(f"用户添加任务:{name}") + Task_manager.add_task( + f"{self.mode.currentText()}_主窗口", "用户自定义队列", info + ) + + class DispatchInfoCard(HeaderCardWidget): + + def __init__(self, parent=None): + super().__init__(parent) + + self.setTitle("调度信息") + + self.task = self.TaskInfoCard(self) + self.user = self.UserInfoCard(self) + self.log_text = self.LogCard(self) + + self.viewLayout.addWidget(self.task) + self.viewLayout.addWidget(self.user) + self.viewLayout.addWidget(self.log_text) + + self.viewLayout.setStretch(0, 1) + self.viewLayout.setStretch(1, 1) + self.viewLayout.setStretch(2, 5) + + def update_board(self, task_list: list, user_list: list, log: str): + """更新调度信息""" + + self.task.update_task(task_list) + self.user.update_user(user_list) + self.log_text.text.setText(log) + + class TaskInfoCard(HeaderCardWidget): + + def __init__(self, parent=None): + super().__init__(parent) + self.setTitle("任务队列") + + self.Layout = QVBoxLayout() + self.viewLayout.addLayout(self.Layout) + self.viewLayout.setContentsMargins(3, 0, 3, 3) + + self.task_cards: List[ItemCard] = [] + + def create_task(self, task_list: list): + """创建任务队列""" + + while self.Layout.count() > 0: + item = self.Layout.takeAt(0) + if item.spacerItem(): + self.Layout.removeItem(item.spacerItem()) + elif item.widget(): + item.widget().deleteLater() + + self.task_cards = [] + + for task in task_list: + + self.task_cards.append(ItemCard(task)) + self.Layout.addWidget(self.task_cards[-1]) + + self.Layout.addStretch(1) + + def update_task(self, task_list: list): + """更新任务队列""" + + for i in range(len(task_list)): + + self.task_cards[i].update_status(task_list[i][1]) + + class UserInfoCard(HeaderCardWidget): + + def __init__(self, parent=None): + super().__init__(parent) + self.setTitle("用户队列") + + self.Layout = QVBoxLayout() + self.viewLayout.addLayout(self.Layout) + self.viewLayout.setContentsMargins(3, 0, 3, 3) + + self.user_cards: List[ItemCard] = [] + + def create_user(self, user_list: list): + """创建用户队列""" + + while self.Layout.count() > 0: + item = self.Layout.takeAt(0) + if item.spacerItem(): + self.Layout.removeItem(item.spacerItem()) + elif item.widget(): + item.widget().deleteLater() + + self.user_cards = [] + + for user in user_list: + + self.user_cards.append(ItemCard(user)) + self.Layout.addWidget(self.user_cards[-1]) + + self.Layout.addStretch(1) + + def update_user(self, user_list: list): + """更新用户队列""" + + for i in range(len(user_list)): + + self.user_cards[i].Label.setText(user_list[i][0]) + self.user_cards[i].update_status(user_list[i][1]) + + class LogCard(HeaderCardWidget): + + def __init__(self, parent=None): + super().__init__(parent) + self.setTitle("日志") + + self.text = TextBrowser() + self.viewLayout.setContentsMargins(3, 0, 3, 3) + self.viewLayout.addWidget(self.text) + + self.text.textChanged.connect(self.to_end) + + def to_end(self): + """滚动到底部""" + + self.text.moveCursor(QTextCursor.End) + self.text.ensureCursorVisible() + + +class ItemCard(CardWidget): + + def __init__(self, task_item: list, parent=None): + super().__init__(parent) + + self.Layout = QHBoxLayout(self) + + self.Label = BodyLabel(task_item[0], self) + self.icon = IconWidget(FluentIcon.MORE, self) + self.icon.setFixedSize(16, 16) + self.update_status(task_item[1]) + + self.Layout.addWidget(self.icon) + self.Layout.addWidget(self.Label) + self.Layout.addStretch(1) + + def update_status(self, status: str): + + if status == "完成": + self.icon.setIcon(FluentIcon.ACCEPT) + self.Label.setTextColor("#0eb840", "#0eb840") + elif status == "等待": + self.icon.setIcon(FluentIcon.MORE) + self.Label.setTextColor("#7397ab", "#7397ab") + elif status == "运行": + self.icon.setIcon(FluentIcon.PLAY) + self.Label.setTextColor("#2e4e7e", "#2e4e7e") + elif status == "跳过": + self.icon.setIcon(FluentIcon.REMOVE) + self.Label.setTextColor("#606060", "#d2d2d2") + elif status == "异常": + self.icon.setIcon(FluentIcon.CLOSE) + self.Label.setTextColor("#ff2121", "#ff2121") diff --git a/app/ui/gui.py b/app/ui/gui.py deleted file mode 100644 index d19fd28..0000000 --- a/app/ui/gui.py +++ /dev/null @@ -1,1823 +0,0 @@ -# -# Copyright © <2024> - -# This file is part of AUTO_MAA. - -# AUTO_MAA is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published -# by the Free Software Foundation, either version 3 of the License, -# or (at your option) any later version. - -# AUTO_MAA is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty -# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See -# the GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with AUTO_MAA. If not, see . - -# DLmaster_361@163.com - -""" -AUTO_MAA -AUTO_MAA主界面 -v4.2 -作者:DLmaster_361 -""" - -from PySide6.QtWidgets import ( - QWidget, # - QMainWindow, # - QApplication, # - QSystemTrayIcon, # - QFileDialog, # - QTabWidget, # - QToolBox, # - QComboBox, # - QTableWidgetItem, # - QHeaderView, # -) -from qfluentwidgets import ( - Action, - PushButton, - LineEdit, - PasswordLineEdit, - TextBrowser, - TableWidget, - TimePicker, - ComboBox, - CheckBox, - SpinBox, - FluentIcon, - RoundMenu, - MessageBox, - MessageBoxBase, - HeaderCardWidget, - BodyLabel, - SubtitleLabel, -) -from PySide6.QtUiTools import QUiLoader -from PySide6.QtGui import QIcon, QCloseEvent -from PySide6 import QtCore -from functools import partial -from typing import List, Tuple -from pathlib import Path -import json -import datetime -import ctypes -import subprocess -import shutil -import win32gui -import win32process -import psutil -import pyautogui -import time -import winreg -import requests - -uiLoader = QUiLoader() - -from app import AppConfig -from app.models import MaaManager -from app.services import Notification, CryptoHandler -from app.utils import Updater, version_text - - -class Main(QWidget): - - ES_CONTINUOUS = 0x80000000 - ES_SYSTEM_REQUIRED = 0x00000001 - - def __init__(self, config: AppConfig, notify: Notification, crypto: CryptoHandler): - super().__init__() - - self.config = config - self.notify = notify - self.crypto = crypto - - self.PASSWORD = "" - self.if_user_list_editable = True - self.if_update_database = True - self.if_update_config = True - self.user_mode_list = ["simple", "beta"] - self.user_column = [ - "admin", - "id", - "server", - "day", - "status", - "last", - "game", - "game_1", - "game_2", - "routine", - "annihilation", - "infrastructure", - "password", - "notes", - "numb", - "mode", - "uid", - ] - self.userlist_simple_index = [ - 0, - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - "-", - 9, - 10, - 11, - 12, - "-", - "-", - "-", - ] - self.userlist_beta_index = [ - 0, - "-", - "-", - 1, - 2, - 3, - "-", - "-", - "-", - 4, - 5, - "-", - 6, - 7, - "-", - "-", - "-", - ] - - uiLoader.registerCustomWidget(PushButton) - uiLoader.registerCustomWidget(LineEdit) - uiLoader.registerCustomWidget(TextBrowser) - uiLoader.registerCustomWidget(TableWidget) - uiLoader.registerCustomWidget(TimePicker) - uiLoader.registerCustomWidget(SpinBox) - uiLoader.registerCustomWidget(CheckBox) - uiLoader.registerCustomWidget(HeaderCardWidget) - uiLoader.registerCustomWidget(BodyLabel) - - # 导入ui配置 - self.ui = uiLoader.load(self.config.app_path / "resources/gui/main.ui") - self.ui.setWindowIcon( - QIcon(str(self.config.app_path / "resources/icons/AUTO_MAA.ico")) - ) - - # 初始化控件 - self.main_tab: QTabWidget = self.ui.findChild(QTabWidget, "tabWidget_main") - self.main_tab.currentChanged.connect(self.change_config) - - self.user_set: QToolBox = self.ui.findChild(QToolBox, "toolBox_userset") - self.user_set.currentChanged.connect(lambda: self.update_user_info("normal")) - - self.user_list_simple: TableWidget = self.ui.findChild( - TableWidget, "tableWidget_userlist_simple" - ) - self.user_list_simple.itemChanged.connect( - lambda item: self.change_user_Item(item, "simple") - ) - - self.user_list_beta: TableWidget = self.ui.findChild( - TableWidget, "tableWidget_userlist_beta" - ) - self.user_list_beta.itemChanged.connect( - lambda item: self.change_user_Item(item, "beta") - ) - - self.user_add: PushButton = self.ui.findChild(PushButton, "pushButton_new") - self.user_add.setIcon(FluentIcon.ADD_TO) - self.user_add.clicked.connect(self.add_user) - - self.user_del: PushButton = self.ui.findChild(PushButton, "pushButton_del") - self.user_del.setIcon(FluentIcon.REMOVE_FROM) - self.user_del.clicked.connect(self.del_user) - - self.user_switch: PushButton = self.ui.findChild( - PushButton, "pushButton_switch" - ) - self.user_switch.setIcon(FluentIcon.MOVE) - self.user_switch.clicked.connect(self.switch_user) - - self.read_PASSWORD: PushButton = self.ui.findChild( - PushButton, "pushButton_password" - ) - self.read_PASSWORD.setIcon(FluentIcon.HIDE) - self.read_PASSWORD.clicked.connect(lambda: self.read("key")) - - self.refresh: PushButton = self.ui.findChild(PushButton, "pushButton_refresh") - self.refresh.setIcon(FluentIcon.SYNC) - self.refresh.clicked.connect(lambda: self.update_user_info("clear")) - - self.run_now: PushButton = self.ui.findChild(PushButton, "pushButton_runnow") - self.run_now.setIcon(FluentIcon.PLAY) - self.run_now.clicked.connect(lambda: self.maa_starter("日常代理")) - - self.check_start: PushButton = self.ui.findChild( - PushButton, "pushButton_checkstart" - ) - self.check_start.setIcon(FluentIcon.PLAY) - self.check_start.clicked.connect(lambda: self.maa_starter("人工排查")) - - self.maa_path: LineEdit = self.ui.findChild(LineEdit, "lineEdit_MAApath") - self.maa_path.textChanged.connect(self.change_config) - self.maa_path.setReadOnly(True) - - self.get_maa_path: PushButton = self.ui.findChild( - PushButton, "pushButton_getMAApath" - ) - self.get_maa_path.setIcon(FluentIcon.FOLDER) - self.get_maa_path.clicked.connect(lambda: self.read("file_path_maa")) - - self.set_maa: PushButton = self.ui.findChild(PushButton, "pushButton_setMAA") - self.set_maa.setIcon(FluentIcon.SETTING) - self.set_maa.clicked.connect(lambda: self.maa_starter("设置MAA_全局")) - - self.routine: SpinBox = self.ui.findChild(SpinBox, "spinBox_routine") - self.routine.valueChanged.connect(self.change_config) - - self.annihilation: SpinBox = self.ui.findChild(SpinBox, "spinBox_annihilation") - self.annihilation.valueChanged.connect(self.change_config) - - self.num: SpinBox = self.ui.findChild(SpinBox, "spinBox_numt") - self.num.valueChanged.connect(self.change_config) - - self.if_self_start: CheckBox = self.ui.findChild( - CheckBox, "checkBox_ifselfstart" - ) - self.if_self_start.stateChanged.connect(self.change_config) - - self.if_sleep: CheckBox = self.ui.findChild(CheckBox, "checkBox_ifsleep") - self.if_sleep.stateChanged.connect(self.change_config) - - self.if_proxy_directly: CheckBox = self.ui.findChild( - CheckBox, "checkBox_ifproxydirectly" - ) - self.if_proxy_directly.stateChanged.connect(self.change_config) - - self.if_send_mail: CheckBox = self.ui.findChild(CheckBox, "checkBox_ifsendmail") - self.if_send_mail.stateChanged.connect(self.change_config) - - self.mail_address: LineEdit = self.ui.findChild( - LineEdit, "lineEdit_mailaddress" - ) - self.mail_address.textChanged.connect(self.change_config) - - self.if_send_error_only: CheckBox = self.ui.findChild( - CheckBox, "checkBox_ifonlyerror" - ) - self.if_send_error_only.stateChanged.connect(self.change_config) - - self.if_silence: CheckBox = self.ui.findChild(CheckBox, "checkBox_silence") - self.if_silence.stateChanged.connect(self.change_config) - - self.boss_key: LineEdit = self.ui.findChild(LineEdit, "lineEdit_boss") - self.boss_key.textChanged.connect(self.change_config) - - self.if_to_tray: CheckBox = self.ui.findChild(CheckBox, "checkBox_iftotray") - self.if_to_tray.stateChanged.connect(self.change_config) - - self.check_update: PushButton = self.ui.findChild( - PushButton, "pushButton_check_update" - ) - self.check_update.setIcon(FluentIcon.UPDATE) - self.check_update.clicked.connect(self.check_version) - - self.tips: TextBrowser = self.ui.findChild(TextBrowser, "textBrowser_tips") - self.tips.setOpenExternalLinks(True) - - self.run_text: TextBrowser = self.ui.findChild(TextBrowser, "textBrowser_run") - self.wait_text: TextBrowser = self.ui.findChild(TextBrowser, "textBrowser_wait") - self.over_text: TextBrowser = self.ui.findChild(TextBrowser, "textBrowser_over") - self.error_text: TextBrowser = self.ui.findChild( - TextBrowser, "textBrowser_error" - ) - self.log_text: TextBrowser = self.ui.findChild(TextBrowser, "textBrowser_log") - - self.start_time: List[Tuple[CheckBox, TimePicker]] = [] - for i in range(10): - self.start_time.append( - [ - self.ui.findChild(CheckBox, f"checkBox_t{i + 1}"), - self.ui.findChild(TimePicker, f"timeEdit_{i + 1}"), - ] - ) - self.start_time[i][0].stateChanged.connect(self.change_config) - self.start_time[i][1].timeChanged.connect(self.change_config) - - self.change_password: PushButton = self.ui.findChild( - PushButton, "pushButton_changePASSWORD" - ) - self.change_password.setIcon(FluentIcon.VPN) - self.change_password.clicked.connect(self.change_PASSWORD) - - # 初始化线程 - self.MaaManager = MaaManager(self.config) - self.MaaManager.question.connect(lambda: self.read("question_runner")) - self.MaaManager.update_gui.connect(self.update_board) - self.MaaManager.update_user_info.connect(self.change_user_info) - self.MaaManager.push_notification.connect(self.notify.push_notification) - self.MaaManager.send_mail.connect(self.notify.send_mail) - self.MaaManager.accomplish.connect(lambda: self.maa_ender("日常代理_结束")) - self.MaaManager.get_json.connect(self.get_maa_config) - self.MaaManager.set_silence.connect(self.switch_silence) - - self.last_time = "0000-00-00 00:00" - self.Timer = QtCore.QTimer() - self.Timer.timeout.connect(self.set_theme) - self.Timer.timeout.connect(self.set_system) - self.Timer.timeout.connect(self.timed_start) - self.Timer.start(1000) - - # 载入GUI数据 - self.update_user_info("normal") - self.update_config() - - # 启动后直接开始代理 - if self.config.content["Default"]["SelfSet.IfProxyDirectly"] == "True": - self.maa_starter("日常代理") - - def check_PASSWORD(self) -> None: - """检查并配置管理密钥""" - - if self.config.key_path.exists(): - return None - - while True: - - if self.read("setkey"): - self.crypto.get_PASSWORD(self.PASSWORD) - break - else: - choice = MessageBox( - "确认", "您没有输入管理密钥,确定要暂时跳过这一步吗?", self.ui - ) - if choice.exec(): - break - - def change_PASSWORD(self) -> None: - """修改管理密钥""" - - # 获取用户信息 - self.config.cur.execute("SELECT * FROM adminx WHERE True") - data = self.config.cur.fetchall() - - if len(data) == 0: - choice = MessageBox("验证通过", "当前无用户,验证自动通过", self.ui) - choice.cancelButton.hide() - choice.buttonLayout.insertStretch(1) - # 获取新的管理密钥 - if choice.exec(): - while True: - PASSWORD_new = self.read("newkey") - if PASSWORD_new == None: - choice = MessageBox( - "确认", - "您没有输入新的管理密钥,是否取消修改管理密钥?", - self.ui, - ) - if choice.exec(): - break - else: - # 修改管理密钥 - self.PASSWORD = PASSWORD_new - self.crypto.get_PASSWORD(self.PASSWORD) - choice = MessageBox("操作成功", "管理密钥修改成功", self.ui) - choice.cancelButton.hide() - choice.buttonLayout.insertStretch(1) - if choice.exec(): - break - else: - # 验证管理密钥 - if_change = True - while if_change: - if self.read("oldkey"): - # 验证旧管理密钥 - if not self.crypto.check_PASSWORD(self.PASSWORD): - choice = MessageBox("错误", "管理密钥错误", self.ui) - choice.cancelButton.hide() - choice.buttonLayout.insertStretch(1) - if choice.exec(): - pass - else: - # 获取新的管理密钥 - while True: - PASSWORD_new = self.read("newkey") - if PASSWORD_new == None: - choice = MessageBox( - "确认", - "您没有输入新的管理密钥,是否取消修改管理密钥?", - self.ui, - ) - if choice.exec(): - if_change = False - break - # 修改管理密钥 - else: - self.crypto.change_PASSWORD( - data, self.PASSWORD, PASSWORD_new - ) - self.PASSWORD = PASSWORD_new - choice = MessageBox( - "操作成功", "管理密钥修改成功", self.ui - ) - choice.cancelButton.hide() - choice.buttonLayout.insertStretch(1) - if choice.exec(): - if_change = False - break - else: - choice = MessageBox( - "确认", - "您没有输入管理密钥,是否取消修改管理密钥?", - self.ui, - ) - if choice.exec(): - break - - def update_user_info(self, operation: str) -> None: - """将本地数据库中的用户配置同步至GUI的用户管理界面""" - - # 读入本地数据库 - self.config.cur.execute("SELECT * FROM adminx WHERE True") - data = self.config.cur.fetchall() - - # 处理部分模式调整 - if operation == "clear": - self.PASSWORD = "" - elif operation == "read_only": - self.if_user_list_editable = False - elif operation == "editable": - self.if_user_list_editable = True - - # 阻止GUI用户数据被立即写入数据库形成死循环 - self.if_update_database = False - - user_switch_list = ["转为高级", "转为简洁"] - self.user_switch.setText(user_switch_list[self.user_set.currentIndex()]) - - # 同步简洁用户配置列表 - data_simple = [_ for _ in data if _[15] == "simple"] - self.user_list_simple.setRowCount(len(data_simple)) - - for i, row in enumerate(data_simple): - - for j, value in enumerate(row): - - if self.userlist_simple_index[j] == "-": - continue - - # 生成表格组件 - if j == 2: - item = ComboBox() - item.addItems(["官服", "B服"]) - if value == "Official": - item.setCurrentIndex(0) - elif value == "Bilibili": - item.setCurrentIndex(1) - item.currentIndexChanged.connect( - partial( - self.change_user_CellWidget, - data_simple[i][16], - self.user_column[j], - ) - ) - elif j in [4, 10, 11]: - item = QComboBox() - if j in [4, 10]: - item.addItems(["启用", "禁用"]) - elif j == 11: - item.addItems(["启用", "禁用", "更改配置文件"]) - if value == "y": - item.setCurrentIndex(0) - elif value == "n": - item.setCurrentIndex(1) - item.currentIndexChanged.connect( - partial( - self.change_user_CellWidget, - data_simple[i][16], - self.user_column[j], - ) - ) - elif j == 3 and value == -1: - item = QTableWidgetItem("无限") - elif j == 5: - curdate = self.server_date() - if curdate != value: - item = QTableWidgetItem("今日未代理") - else: - item = QTableWidgetItem(f"今日已代理{data_simple[i][14]}次") - item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) - elif j == 12: - if self.PASSWORD == "": - item = QTableWidgetItem("******") - item.setFlags( - QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled - ) - else: - result = self.crypto.decryptx(value, self.PASSWORD) - item = QTableWidgetItem(result) - if result == "管理密钥错误": - item.setFlags( - QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled - ) - else: - item = QTableWidgetItem(str(value)) - - # 组件录入表格 - if j in [2, 4, 10, 11]: - if not self.if_user_list_editable: - item.setEnabled(False) - self.user_list_simple.setCellWidget( - data_simple[i][16], self.userlist_simple_index[j], item - ) - else: - self.user_list_simple.setItem( - data_simple[i][16], self.userlist_simple_index[j], item - ) - - # 同步高级用户配置列表 - data_beta = [_ for _ in data if _[15] == "beta"] - self.user_list_beta.setRowCount(len(data_beta)) - - for i, row in enumerate(data_beta): - - for j, value in enumerate(row): - - if self.userlist_beta_index[j] == "-": - continue - - # 生成表格组件 - if j in [4, 9, 10]: - item = ComboBox() - if j == 4: - item.addItems(["启用", "禁用"]) - elif j in [9, 10]: - item.addItems(["启用", "禁用", "修改MAA配置"]) - if value == "y": - item.setCurrentIndex(0) - elif value == "n": - item.setCurrentIndex(1) - item.currentIndexChanged.connect( - partial( - self.change_user_CellWidget, - data_beta[i][16], - self.user_column[j], - ) - ) - elif j == 3 and value == -1: - item = QTableWidgetItem("无限") - elif j == 5: - curdate = self.server_date() - if curdate != value: - item = QTableWidgetItem("今日未代理") - else: - item = QTableWidgetItem(f"今日已代理{data_beta[i][14]}次") - item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) - elif j == 12: - if self.PASSWORD == "": - item = QTableWidgetItem("******") - item.setFlags( - QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled - ) - else: - result = self.crypto.decryptx(value, self.PASSWORD) - item = QTableWidgetItem(result) - if result == "管理密钥错误": - item.setFlags( - QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled - ) - else: - item = QTableWidgetItem(str(value)) - - # 组件录入表格 - if j in [4, 9, 10]: - if not self.if_user_list_editable: - item.setEnabled(False) - self.user_list_beta.setCellWidget( - data_beta[i][16], self.userlist_beta_index[j], item - ) - else: - self.user_list_beta.setItem( - data_beta[i][16], self.userlist_beta_index[j], item - ) - - # 设置列表可编辑状态 - if self.if_user_list_editable: - self.user_list_simple.setEditTriggers(TableWidget.AllEditTriggers) - self.user_list_beta.setEditTriggers(TableWidget.AllEditTriggers) - else: - self.user_list_simple.setEditTriggers(TableWidget.NoEditTriggers) - self.user_list_beta.setEditTriggers(TableWidget.NoEditTriggers) - - # 允许GUI改变被同步到本地数据库 - self.if_update_database = True - - # 设置用户配置列表的标题栏宽度 - self.user_list_simple.horizontalHeader().setSectionResizeMode( - QHeaderView.Stretch - ) - self.user_list_beta.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) - - def update_config(self): - """将self.config中的程序配置同步至GUI界面""" - - # 阻止GUI程序配置被立即读入程序形成死循环 - self.if_update_config = False - - self.main_tab.setCurrentIndex( - self.config.content["Default"]["SelfSet.MainIndex"] - ) - - self.maa_path.setText(str(Path(self.config.content["Default"]["MaaSet.path"]))) - self.routine.setValue(self.config.content["Default"]["TimeLimit.routine"]) - self.annihilation.setValue( - self.config.content["Default"]["TimeLimit.annihilation"] - ) - self.num.setValue(self.config.content["Default"]["TimesLimit.run"]) - self.mail_address.setText(self.config.content["Default"]["SelfSet.MailAddress"]) - self.boss_key.setText(self.config.content["Default"]["SelfSet.BossKey"]) - - self.if_self_start.setChecked( - bool(self.config.content["Default"]["SelfSet.IfSelfStart"] == "True") - ) - - self.if_sleep.setChecked( - bool(self.config.content["Default"]["SelfSet.IfSleep"] == "True") - ) - - self.if_proxy_directly.setChecked( - bool(self.config.content["Default"]["SelfSet.IfProxyDirectly"] == "True") - ) - - self.if_send_mail.setChecked( - bool(self.config.content["Default"]["SelfSet.IfSendMail"] == "True") - ) - - self.mail_address.setVisible( - bool(self.config.content["Default"]["SelfSet.IfSendMail"] == "True") - ) - - self.if_send_error_only.setChecked( - bool( - self.config.content["Default"]["SelfSet.IfSendMail.OnlyError"] == "True" - ) - ) - - self.if_send_error_only.setVisible( - bool(self.config.content["Default"]["SelfSet.IfSendMail"] == "True") - ) - - self.if_silence.setChecked( - bool(self.config.content["Default"]["SelfSet.IfSilence"] == "True") - ) - - self.boss_key.setVisible( - bool(self.config.content["Default"]["SelfSet.IfSilence"] == "True") - ) - - self.if_to_tray.setChecked( - bool(self.config.content["Default"]["SelfSet.IfToTray"] == "True") - ) - - for i in range(10): - self.start_time[i][0].setChecked( - bool(self.config.content["Default"][f"TimeSet.set{i + 1}"] == "True") - ) - time = QtCore.QTime( - int(self.config.content["Default"][f"TimeSet.run{i + 1}"][:2]), - int(self.config.content["Default"][f"TimeSet.run{i + 1}"][3:]), - ) - self.start_time[i][1].setTime(time) - self.if_update_config = True - - def update_board(self, run_text, wait_text, over_text, error_text, log_text): - """写入数据至GUI执行界面的调度台面板""" - - self.run_text.setPlainText(run_text) - self.wait_text.setPlainText(wait_text) - self.over_text.setPlainText(over_text) - self.error_text.setPlainText(error_text) - self.log_text.setPlainText(log_text) - self.log_text.verticalScrollBar().setValue( - self.log_text.verticalScrollBar().maximum() - ) - - def add_user(self): - """添加一位新用户""" - - # 判断是否已设置管理密钥 - if not self.config.key_path.exists(): - choice = MessageBox( - "错误", - "请先设置管理密钥再执行添加用户操作", - self.ui, - ) - choice.cancelButton.hide() - choice.buttonLayout.insertStretch(1) - if choice.exec(): - return None - - # 插入预设用户数据 - set_book = [ - ["simple", self.user_list_simple.rowCount()], - ["beta", self.user_list_beta.rowCount()], - ] - self.config.cur.execute( - "INSERT INTO adminx VALUES('新用户','手机号码(官服)/B站ID(B服)','Official',-1,'y','2000-01-01','1-7','-','-','n','n','n',?,'无',0,?,?)", - ( - self.crypto.encryptx("未设置"), - set_book[self.user_set.currentIndex()][0], - set_book[self.user_set.currentIndex()][1], - ), - ) - self.config.db.commit(), - - # 同步新用户至GUI - self.update_user_info("normal") - - def del_user(self): - """删除选中的首位用户""" - - # 获取对应的行索引 - if self.user_set.currentIndex() == 0: - row = self.user_list_simple.currentRow() - elif self.user_set.currentIndex() == 1: - row = self.user_list_beta.currentRow() - - # 判断选择合理性 - if row == -1: - choice = MessageBox("错误", "请选中一个用户后再执行删除操作", self.ui) - choice.cancelButton.hide() - choice.buttonLayout.insertStretch(1) - if choice.exec(): - return None - - # 确认待删除用户信息 - self.config.cur.execute( - "SELECT * FROM adminx WHERE mode = ? AND uid = ?", - ( - self.user_mode_list[self.user_set.currentIndex()], - row, - ), - ) - data = self.config.cur.fetchall() - choice = MessageBox("确认", f"确定要删除用户 {data[0][0]} 吗?", self.ui) - - # 删除用户 - if choice.exec(): - # 删除所选用户 - self.config.cur.execute( - "DELETE FROM adminx WHERE mode = ? AND uid = ?", - ( - self.user_mode_list[self.user_set.currentIndex()], - row, - ), - ) - self.config.db.commit() - if ( - self.config.app_path - / f"data/MAAconfig/{self.user_mode_list[self.user_set.currentIndex()]}/{row}" - ).exists(): - shutil.rmtree( - self.config.app_path - / f"data/MAAconfig/{self.user_mode_list[self.user_set.currentIndex()]}/{row}" - ) - # 后续用户补位 - if self.user_set.currentIndex() == 0: - current_numb = self.user_list_simple.rowCount() - elif self.user_set.currentIndex() == 1: - current_numb = self.user_list_beta.rowCount() - for i in range(row + 1, current_numb): - self.config.cur.execute( - "UPDATE adminx SET uid = ? WHERE mode = ? AND uid = ?", - (i - 1, self.user_mode_list[self.user_set.currentIndex()], i), - ) - self.config.db.commit() - if ( - self.config.app_path - / f"data/MAAconfig/{self.user_mode_list[self.user_set.currentIndex()]}/{i}" - ).exists(): - ( - self.config.app_path - / f"data/MAAconfig/{self.user_mode_list[self.user_set.currentIndex()]}/{i}" - ).rename( - self.config.app_path - / f"data/MAAconfig/{self.user_mode_list[self.user_set.currentIndex()]}/{i - 1}", - ) - - # 同步最终结果至GUI - self.update_user_info("normal") - - def switch_user(self): - """切换用户配置模式""" - - # 获取当前用户配置模式信息 - if self.user_set.currentIndex() == 0: - row = self.user_list_simple.currentRow() - elif self.user_set.currentIndex() == 1: - row = self.user_list_beta.currentRow() - - # 判断选择合理性 - if row == -1: - choice = MessageBox("错误", "请选中一个用户后再执行切换操作", self.ui) - choice.cancelButton.hide() - choice.buttonLayout.insertStretch(1) - if choice.exec(): - return None - - # 确认待切换用户信息 - self.config.cur.execute( - "SELECT * FROM adminx WHERE mode = ? AND uid = ?", - ( - self.user_mode_list[self.user_set.currentIndex()], - row, - ), - ) - data = self.config.cur.fetchall() - - mode_list = ["简洁", "高级"] - choice = MessageBox( - "确认", - f"确定要将用户 {data[0][0]} 转为{mode_list[1 - self.user_set.currentIndex()]}配置模式吗?", - self.ui, - ) - - # 切换用户 - if choice.exec(): - self.config.cur.execute("SELECT * FROM adminx WHERE True") - data = self.config.cur.fetchall() - if self.user_set.currentIndex() == 0: - current_numb = self.user_list_simple.rowCount() - elif self.user_set.currentIndex() == 1: - current_numb = self.user_list_beta.rowCount() - # 切换所选用户 - other_numb = len(data) - current_numb - self.config.cur.execute( - "UPDATE adminx SET mode = ?, uid = ? WHERE mode = ? AND uid = ?", - ( - self.user_mode_list[1 - self.user_set.currentIndex()], - other_numb, - self.user_mode_list[self.user_set.currentIndex()], - row, - ), - ) - self.config.db.commit() - if ( - self.config.app_path - / f"data/MAAconfig/{self.user_mode_list[self.user_set.currentIndex()]}/{row}" - ).exists(): - shutil.move( - self.config.app_path - / f"data/MAAconfig/{self.user_mode_list[self.user_set.currentIndex()]}/{row}", - self.config.app_path - / f"data/MAAconfig/{self.user_mode_list[1 - self.user_set.currentIndex()]}/{other_numb}", - ) - # 后续用户补位 - for i in range(row + 1, current_numb): - self.config.cur.execute( - "UPDATE adminx SET uid = ? WHERE mode = ? AND uid = ?", - (i - 1, self.user_mode_list[self.user_set.currentIndex()], i), - ) - self.config.db.commit(), - if ( - self.config.app_path - / f"data/MAAconfig/{self.user_mode_list[self.user_set.currentIndex()]}/{i}" - ).exists(): - ( - self.config.app_path - / f"data/MAAconfig/{self.user_mode_list[self.user_set.currentIndex()]}/{i}" - ).rename( - self.config.app_path - / f"data/MAAconfig/{self.user_mode_list[self.user_set.currentIndex()]}/{i - 1}" - ) - - self.update_user_info("normal") - - def get_maa_config(self, info): - """获取MAA配置文件""" - - # 获取全局MAA配置文件 - if info == ["Default"]: - shutil.copy( - Path(self.config.content["Default"]["MaaSet.path"]) / "config/gui.json", - self.config.app_path / "data/MAAconfig/Default", - ) - # 获取基建配置文件 - elif info[2] == "infrastructure": - infrastructure_path = self.read("file_path_infrastructure") - if infrastructure_path: - ( - self.config.app_path - / f"data/MAAconfig/{self.user_mode_list[info[0]]}/{info[1]}/infrastructure" - ).mkdir(parents=True, exist_ok=True) - shutil.copy( - infrastructure_path, - self.config.app_path - / f"data/MAAconfig/{self.user_mode_list[info[0]]}/{info[1]}/infrastructure/infrastructure.json", - ) - return True - else: - choice = MessageBox( - "错误", - "未选择自定义基建文件", - self.ui, - ) - choice.cancelButton.hide() - choice.buttonLayout.insertStretch(1) - if choice.exec(): - return False - # 获取高级用户MAA配置文件 - elif info[2] in ["routine", "annihilation"]: - ( - self.config.app_path - / f"data/MAAconfig/{self.user_mode_list[info[0]]}/{info[1]}/{info[2]}" - ).mkdir(parents=True, exist_ok=True) - shutil.copy( - Path(self.config.content["Default"]["MaaSet.path"]) / "config/gui.json", - self.config.app_path - / f"data/MAAconfig/{self.user_mode_list[info[0]]}/{info[1]}/{info[2]}", - ) - - def change_user_Item(self, item: TableWidget, mode): - """将GUI中发生修改的用户配置表中的一般信息同步至本地数据库""" - - # 验证能否写入本地数据库 - if not self.if_update_database: - return None - - text = item.text() - # 简洁用户配置列表 - if mode == "simple": - # 待写入信息预处理 - if item.column() == 3: # 代理天数 - try: - text = max(int(text), -1) - except ValueError: - self.update_user_info("normal") - return None - if item.column() in [6, 7, 8]: # 关卡号 - # 导入与应用特殊关卡规则 - games = {} - with self.config.gameid_path.open(mode="r", encoding="utf-8") as f: - gameids = f.readlines() - for line in gameids: - if ":" in line: - game_in, game_out = line.split(":", 1) - games[game_in.strip()] = game_out.strip() - text = games.get(text, text) - if item.column() == 11: # 密码 - text = self.crypto.encryptx(text) - - # 保存至本地数据库 - if text != "": - self.config.cur.execute( - f"UPDATE adminx SET {self.user_column[self.userlist_simple_index.index(item.column())]} = ? WHERE mode = 'simple' AND uid = ?", - (text, item.row()), - ) - # 高级用户配置列表 - elif mode == "beta": - # 待写入信息预处理 - if item.column() == 1: # 代理天数 - try: - text = max(int(text), -1) - except ValueError: - self.update_user_info("normal") - return None - if item.column() == 6: # 密码 - text = self.crypto.encryptx(text) - - # 保存至本地数据库 - if text != "": - self.config.cur.execute( - f"UPDATE adminx SET {self.user_column[self.userlist_beta_index.index(item.column())]} = ? WHERE mode = 'beta' AND uid = ?", - (text, item.row()), - ) - self.config.db.commit() - - # 同步一般用户信息更改到GUI - self.update_user_info("normal") - - def change_user_CellWidget(self, row, column, index): - """将GUI中发生修改的用户配置表中的CellWidget类信息同步至本地数据库""" - - # 验证能否写入本地数据库 - if not self.if_update_database: - return None - - # 初次开启自定义基建或选择修改配置文件时选择配置文件 - if ( - self.user_set.currentIndex() == 0 - and column == "infrastructure" - and ( - index == 2 - or ( - index == 0 - and not ( - self.config.app_path - / f"data/MAAconfig/{self.user_mode_list[self.user_set.currentIndex()]}/{row}/infrastructure/infrastructure.json" - ).exists() - ) - ) - ): - result = self.get_maa_config([0, row, "infrastructure"]) - if index == 0 and not result: - index = 1 - - # 初次开启自定义MAA配置或选择修改MAA配置时调起MAA配置任务 - if ( - self.user_set.currentIndex() == 1 - and column in ["routine", "annihilation"] - and ( - index == 2 - or ( - index == 0 - and not ( - self.config.app_path - / f"data/MAAconfig/{self.user_mode_list[self.user_set.currentIndex()]}/{row}/{column}/gui.json" - ).exists() - ) - ) - ): - self.MaaManager.get_json_path = [ - self.user_set.currentIndex(), - row, - column, - ] - self.maa_starter("设置MAA_用户") - - # 服务器 - if self.user_set.currentIndex() == 0 and column == "server": - server_list = ["Official", "Bilibili"] - self.config.cur.execute( - f"UPDATE adminx SET server = ? WHERE mode = 'simple' AND uid = ?", - (server_list[index], row), - ) - # 其它(启用/禁用) - elif index in [0, 1]: - index_list = ["y", "n"] - self.config.cur.execute( - f"UPDATE adminx SET {column} = ? WHERE mode = ? AND uid = ?", - ( - index_list[index], - self.user_mode_list[self.user_set.currentIndex()], - row, - ), - ) - self.config.db.commit() - - # 同步用户组件信息修改到GUI - self.update_user_info("normal") - - def change_user_info(self, modes, uids, days, lasts, notes, numbs): - """将代理完成后发生改动的用户信息同步至本地数据库""" - - for index in range(len(uids)): - self.config.cur.execute( - "UPDATE adminx SET day = ? WHERE mode = ? AND uid = ?", - (days[index], modes[index], uids[index]), - ) - self.config.cur.execute( - "UPDATE adminx SET last = ? WHERE mode = ? AND uid = ?", - (lasts[index], modes[index], uids[index]), - ) - self.config.cur.execute( - "UPDATE adminx SET notes = ? WHERE mode = ? AND uid = ?", - (notes[index], modes[index], uids[index]), - ) - self.config.cur.execute( - "UPDATE adminx SET numb = ? WHERE mode = ? AND uid = ?", - (numbs[index], modes[index], uids[index]), - ) - self.config.db.commit() - - # 同步用户信息更改至GUI - self.update_user_info("normal") - - def change_config(self): - """将GUI中发生修改的程序配置同步至self.config变量""" - - # 验证能否写入self.config变量 - if not self.if_update_config: - return None - - # 验证MAA路径 - if Path(self.config.content["Default"]["MaaSet.path"]) != Path( - self.maa_path.text() - ): - if (Path(self.maa_path.text()) / "MAA.exe").exists() and ( - Path(self.maa_path.text()) / "config/gui.json" - ).exists(): - self.config.content["Default"]["MaaSet.path"] = str( - Path(self.maa_path.text()) - ) - self.get_maa_config(["Default"]) - else: - choice = MessageBox( - "错误", - "该路径下未找到MAA.exe或MAA配置文件,请重新设置MAA路径!", - self.ui, - ) - if choice.exec(): - pass - - self.config.content["Default"][ - "SelfSet.MainIndex" - ] = self.main_tab.currentIndex() - self.config.content["Default"]["TimeLimit.routine"] = self.routine.value() - self.config.content["Default"][ - "TimeLimit.annihilation" - ] = self.annihilation.value() - self.config.content["Default"]["TimesLimit.run"] = self.num.value() - self.config.content["Default"]["SelfSet.MailAddress"] = self.mail_address.text() - self.config.content["Default"]["SelfSet.BossKey"] = self.boss_key.text() - - if self.if_sleep.isChecked(): - self.config.content["Default"]["SelfSet.IfSleep"] = "True" - else: - self.config.content["Default"]["SelfSet.IfSleep"] = "False" - - if self.if_self_start.isChecked(): - self.config.content["Default"]["SelfSet.IfSelfStart"] = "True" - else: - self.config.content["Default"]["SelfSet.IfSelfStart"] = "False" - - if self.if_proxy_directly.isChecked(): - self.config.content["Default"]["SelfSet.IfProxyDirectly"] = "True" - else: - self.config.content["Default"]["SelfSet.IfProxyDirectly"] = "False" - - if self.if_send_mail.isChecked(): - self.config.content["Default"]["SelfSet.IfSendMail"] = "True" - else: - self.config.content["Default"]["SelfSet.IfSendMail"] = "False" - - if self.if_send_error_only.isChecked(): - self.config.content["Default"]["SelfSet.IfSendMail.OnlyError"] = "True" - else: - self.config.content["Default"]["SelfSet.IfSendMail.OnlyError"] = "False" - - if self.if_silence.isChecked(): - self.config.content["Default"]["SelfSet.IfSilence"] = "True" - else: - self.config.content["Default"]["SelfSet.IfSilence"] = "False" - - if self.if_to_tray.isChecked(): - self.config.content["Default"]["SelfSet.IfToTray"] = "True" - else: - self.config.content["Default"]["SelfSet.IfToTray"] = "False" - - for i in range(10): - if self.start_time[i][0].isChecked(): - self.config.content["Default"][f"TimeSet.set{i + 1}"] = "True" - else: - self.config.content["Default"][f"TimeSet.set{i + 1}"] = "False" - time = self.start_time[i][1].getTime().toString("HH:mm") - self.config.content["Default"][f"TimeSet.run{i + 1}"] = time - - # 将配置信息同步至本地JSON文件 - self.config.save_config() - - # 同步程序配置至GUI - self.update_config() - - def set_theme(self): - """手动更新主题色到组件""" - - self.user_list_simple.setStyleSheet("QTableWidget::item {}") - self.user_list_beta.setStyleSheet("QTableWidget::item {}") - - def read(self, operation): - """弹出对话框组件进行读入""" - - class InputMessageBox(MessageBoxBase): - """输入对话框""" - - def __init__(self, parent, title: str, content: str, mode: str): - super().__init__(parent) - self.title = SubtitleLabel(title) - - if mode == "明文": - self.input = LineEdit() - elif mode == "密码": - self.input = PasswordLineEdit() - - self.input.setPlaceholderText(content) - self.input.setClearButtonEnabled(True) - - # 将组件添加到布局中 - self.viewLayout.addWidget(self.title) - self.viewLayout.addWidget(self.input) - - # 读入PASSWORD - if operation == "key": - - choice = InputMessageBox(self.ui, "请输入管理密钥", "管理密钥", "密码") - if choice.exec() and choice.input.text() != "": - self.PASSWORD = choice.input.text() - self.update_user_info("normal") - - elif operation == "oldkey": - - choice = InputMessageBox( - self.ui, "请输入旧的管理密钥", "旧管理密钥", "密码" - ) - if choice.exec() and choice.input.text() != "": - self.PASSWORD = choice.input.text() - return True - else: - return False - - elif operation == "newkey": - - choice = InputMessageBox( - self.ui, "请输入新的管理密钥", "新管理密钥", "密码" - ) - if choice.exec() and choice.input.text() != "": - return choice.input.text() - else: - return None - - elif operation == "setkey": - - choice = InputMessageBox( - self.ui, - "未检测到管理密钥,请设置您的管理密钥", - "管理密钥", - "密码", - ) - if choice.exec() and choice.input.text() != "": - self.PASSWORD = choice.input.text() - return True - else: - return False - - # 读入选择 - elif operation == "question_runner": - choice = MessageBox( - self.MaaManager.question_title, - self.MaaManager.question_info, - None, - ) - if choice.exec(): - self.MaaManager.question_choice = "Yes" - else: - self.MaaManager.question_choice = "No" - - # 读入MAA文件目录 - elif operation == "file_path_maa": - file_path = QFileDialog.getExistingDirectory(self.ui, "选择MAA文件夹") - if file_path: - self.maa_path.setText(file_path) - - # 读入自定义基建文件目录 - elif operation == "file_path_infrastructure": - file_path, _ = QFileDialog.getOpenFileName( - self.ui, "选择自定义基建文件", "", "JSON 文件 (*.json)" - ) - return file_path - - def set_system(self): - """设置系统相关配置""" - - # 同步系统休眠状态 - if self.config.content["Default"]["SelfSet.IfSleep"] == "True": - # 设置系统电源状态 - ctypes.windll.kernel32.SetThreadExecutionState( - self.ES_CONTINUOUS | self.ES_SYSTEM_REQUIRED - ) - elif self.config.content["Default"]["SelfSet.IfSleep"] == "False": - # 恢复系统电源状态 - ctypes.windll.kernel32.SetThreadExecutionState(self.ES_CONTINUOUS) - - # 同步开机自启 - if ( - self.config.content["Default"]["SelfSet.IfSelfStart"] == "True" - and not self.is_startup() - ): - key = winreg.OpenKey( - winreg.HKEY_CURRENT_USER, - r"Software\Microsoft\Windows\CurrentVersion\Run", - winreg.KEY_SET_VALUE, - winreg.KEY_ALL_ACCESS | winreg.KEY_WRITE | winreg.KEY_CREATE_SUB_KEY, - ) - winreg.SetValueEx( - key, self.config.app_name, 0, winreg.REG_SZ, self.config.app_path_sys - ) - winreg.CloseKey(key) - elif ( - self.config.content["Default"]["SelfSet.IfSelfStart"] == "False" - and self.is_startup() - ): - key = winreg.OpenKey( - winreg.HKEY_CURRENT_USER, - r"Software\Microsoft\Windows\CurrentVersion\Run", - winreg.KEY_SET_VALUE, - winreg.KEY_ALL_ACCESS | winreg.KEY_WRITE | winreg.KEY_CREATE_SUB_KEY, - ) - winreg.DeleteValue(key, self.config.app_name) - winreg.CloseKey(key) - - def is_startup(self): - """判断程序是否已经开机自启""" - - key = winreg.OpenKey( - winreg.HKEY_CURRENT_USER, - r"Software\Microsoft\Windows\CurrentVersion\Run", - 0, - winreg.KEY_READ, - ) - - try: - value, _ = winreg.QueryValueEx(key, self.config.app_name) - winreg.CloseKey(key) - return True - except FileNotFoundError: - winreg.CloseKey(key) - return False - - def timed_start(self): - """定时启动代理任务""" - - # 获取定时列表 - time_set = [ - self.config.content["Default"][f"TimeSet.run{_ + 1}"] - for _ in range(10) - if self.config.content["Default"][f"TimeSet.set{_ + 1}"] == "True" - ] - # 按时间调起代理任务 - curtime = datetime.datetime.now().strftime("%Y-%m-%d %H:%M") - if ( - curtime[11:16] in time_set - and curtime != self.last_time - and not self.MaaManager.isRunning() - ): - self.last_time = curtime - self.maa_starter("日常代理") - - def switch_silence(self, mode, emulator_path, boss_key): - """切换静默模式""" - - if mode == "启用": - self.Timer.timeout.disconnect() - self.Timer.timeout.connect(self.set_theme) - self.Timer.timeout.connect(self.set_system) - self.Timer.timeout.connect(self.timed_start) - self.Timer.timeout.connect( - lambda: self.set_silence(emulator_path, boss_key) - ) - elif mode == "禁用": - self.Timer.timeout.disconnect() - self.Timer.timeout.connect(self.set_theme) - self.Timer.timeout.connect(self.set_system) - self.Timer.timeout.connect(self.timed_start) - - def set_silence(self, emulator_path, boss_key): - """设置静默模式""" - - windows = self.get_window_info() - if any(emulator_path in _ for _ in windows): - try: - pyautogui.hotkey(*boss_key) - except pyautogui.FailSafeException as e: - # 执行日志记录,暂时缺省 - pass - - def get_window_info(self): - """获取当前窗口信息""" - - def callback(hwnd, window_info): - if win32gui.IsWindowVisible(hwnd) and win32gui.GetWindowText(hwnd): - _, pid = win32process.GetWindowThreadProcessId(hwnd) - process = psutil.Process(pid) - window_info.append((win32gui.GetWindowText(hwnd), process.exe())) - return True - - window_info = [] - win32gui.EnumWindows(callback, window_info) - return window_info - - def maa_starter(self, mode): - """启动MaaManager线程运行任务""" - - # 检查MAA路径是否可用 - if ( - not ( - Path(self.config.content["Default"]["MaaSet.path"]) / "MAA.exe" - ).exists() - and ( - Path(self.config.content["Default"]["MaaSet.path"]) / "config/gui.json" - ).exists() - ): - choice = MessageBox("错误", "您还未正确配置MAA路径!", self.ui) - choice.cancelButton.hide() - choice.buttonLayout.insertStretch(1) - if choice.exec(): - return None - - self.maa_running_set(f"{mode}_开始") - - # 配置参数 - self.MaaManager.mode = mode - self.config.cur.execute("SELECT * FROM adminx WHERE True") - data = self.config.cur.fetchall() - self.MaaManager.data = [list(row) for row in data] - - # 启动执行线程 - self.MaaManager.start() - - def maa_ender(self, mode): - """中止MAA线程""" - - self.switch_silence("禁用", "", []) - - self.MaaManager.requestInterruption() - self.MaaManager.wait() - - self.maa_running_set(mode) - - def maa_running_set(self, mode): - """处理MAA运行过程中的GUI组件变化""" - - if "开始" in mode: - - self.MaaManager.accomplish.disconnect() - self.user_add.setEnabled(False) - self.user_del.setEnabled(False) - self.user_switch.setEnabled(False) - self.set_maa.setEnabled(False) - - self.update_user_info("read_only") - - if mode == "日常代理_开始": - self.MaaManager.accomplish.connect( - lambda: self.maa_ender("日常代理_结束") - ) - self.check_start.setEnabled(False) - self.run_now.clicked.disconnect() - self.run_now.setText("结束运行") - self.run_now.clicked.connect(lambda: self.maa_ender("日常代理_结束")) - - elif mode == "人工排查_开始": - self.MaaManager.accomplish.connect( - lambda: self.maa_ender("人工排查_结束") - ) - self.run_now.setEnabled(False) - self.check_start.clicked.disconnect() - self.check_start.setText("中止排查") - self.check_start.clicked.connect( - lambda: self.maa_ender("人工排查_结束") - ) - - elif mode == "设置MAA_全局_开始" or mode == "设置MAA_用户_开始": - self.MaaManager.accomplish.connect( - lambda: self.maa_ender("设置MAA_结束") - ) - self.run_now.setEnabled(False) - self.check_start.setEnabled(False) - - elif "结束" in mode: - - shutil.copy( - self.config.app_path / "data/MAAconfig/Default/gui.json", - Path(self.config.content["Default"]["MaaSet.path"]) / "config", - ) - self.user_add.setEnabled(True) - self.user_del.setEnabled(True) - self.user_switch.setEnabled(True) - self.set_maa.setEnabled(True) - - self.update_user_info("editable") - - if mode == "日常代理_结束": - - self.check_start.setEnabled(True) - self.run_now.clicked.disconnect() - self.run_now.setText("立即执行") - self.run_now.clicked.connect(lambda: self.maa_starter("日常代理")) - - elif mode == "人工排查_结束": - - self.run_now.setEnabled(True) - self.check_start.clicked.disconnect() - self.check_start.setText("开始排查") - self.check_start.clicked.connect(lambda: self.maa_starter("人工排查")) - - elif mode == "设置MAA_结束": - - self.run_now.setEnabled(True) - self.check_start.setEnabled(True) - - def check_version(self): - """检查版本更新,调起文件下载进程""" - - # 从本地版本信息文件获取当前版本信息 - with self.config.version_path.open(mode="r", encoding="utf-8") as f: - version_current = json.load(f) - main_version_current = list( - map(int, version_current["main_version"].split(".")) - ) - updater_version_current = list( - map(int, version_current["updater_version"].split(".")) - ) - # 检查更新器是否存在 - if not (self.config.app_path / "Updater.exe").exists(): - updater_version_current = [0, 0, 0, 0] - - # 从远程服务器获取最新版本信息 - for _ in range(3): - try: - response = requests.get( - "https://gitee.com/DLmaster_361/AUTO_MAA/raw/main/resources/version.json" - ) - version_remote = response.json() - break - except Exception as e: - err = e - time.sleep(0.1) - else: - choice = MessageBox( - "错误", - f"获取版本信息时出错:\n{err}", - self.ui, - ) - choice.cancelButton.hide() - choice.buttonLayout.insertStretch(1) - if choice.exec(): - return None - - main_version_remote = list(map(int, version_remote["main_version"].split("."))) - updater_version_remote = list( - map(int, version_remote["updater_version"].split(".")) - ) - - # 有版本更新 - if (main_version_remote > main_version_current) or ( - updater_version_remote > updater_version_current - ): - - # 生成版本更新信息 - if main_version_remote > main_version_current: - main_version_info = f" 主程序:{version_text(main_version_current)} --> {version_text(main_version_remote)}\n" - else: - main_version_info = ( - f" 主程序:{version_text(main_version_current)}\n" - ) - if updater_version_remote > updater_version_current: - updater_version_info = f" 更新器:{version_text(updater_version_current)} --> {version_text(updater_version_remote)}\n" - else: - updater_version_info = ( - f" 更新器:{version_text(updater_version_current)}\n" - ) - - # 询问是否开始版本更新 - choice = MessageBox( - "版本更新", - f"发现新版本:\n{main_version_info}{updater_version_info} 更新说明:\n{version_remote['announcement'].replace("\n# ","\n !").replace("\n## ","\n - ").replace("\n- ","\n · ")}\n\n是否开始更新?\n\n 注意:主程序更新时AUTO_MAA将自动关闭", - self.ui, - ) - if not choice.exec(): - return None - - # 更新更新器 - if updater_version_remote > updater_version_current: - # 创建更新进程 - self.updater = Updater( - self.config.app_path, - "AUTO_MAA更新器", - main_version_remote, - updater_version_remote, - ) - # 完成更新器的更新后更新主程序 - if main_version_remote > main_version_current: - self.updater.update_process.accomplish.connect(self.update_main) - # 显示更新页面 - self.updater.ui.show() - - # 更新主程序 - elif main_version_remote > main_version_current: - self.update_main() - - # 无版本更新 - else: - self.notify.push_notification("已是最新版本~", " ", " ", 3) - - def update_main(self): - """更新主程序""" - - subprocess.Popen( - str(self.config.app_path / "Updater.exe"), - shell=True, - creationflags=subprocess.CREATE_NO_WINDOW, - ) - self.close() - QApplication.quit() - - def server_date(self): - """获取当前的服务器日期""" - - dt = datetime.datetime.now() - if dt.time() < datetime.datetime.min.time().replace(hour=4): - dt = dt - datetime.timedelta(days=1) - return dt.strftime("%Y-%m-%d") - - -class AUTO_MAA(QMainWindow): - - if_save = True - - def __init__(self, config: AppConfig, notify: Notification, crypto: CryptoHandler): - super(AUTO_MAA, self).__init__() - - self.config = config - self.notify = notify - - self.config.open_database() - - # 创建主窗口 - self.main = Main(config=config, notify=notify, crypto=crypto) - self.setCentralWidget(self.main.ui) - self.setWindowIcon( - QIcon(str(self.config.app_path / "resources/icons/AUTO_MAA.ico")) - ) - self.setWindowTitle("AUTO_MAA") - - # 创建系统托盘及其菜单 - self.tray = QSystemTrayIcon( - QIcon(str(self.config.app_path / "resources/icons/AUTO_MAA.ico")), - self, - ) - self.tray.setToolTip("AUTO_MAA") - self.tray_menu = RoundMenu() - - # 显示主界面菜单项 - self.tray_menu.addAction( - Action(FluentIcon.CAFE, "显示主界面", triggered=self.show_main) - ) - self.tray_menu.addSeparator() - - # 开始任务菜单项 - self.tray_menu.addActions( - [ - Action( - FluentIcon.PLAY, - "运行日常代理", - triggered=lambda: self.start_task("日常代理"), - ), - # Action( - # FluentIcon.PLAY, - # "运行人工排查", - # triggered=lambda: self.start_task("人工排查"), - # ), - Action(FluentIcon.PAUSE, "中止当前任务", triggered=self.stop_task), - ] - ) - self.tray_menu.addSeparator() - - # 退出主程序菜单项 - self.tray_menu.addAction( - Action(FluentIcon.POWER_BUTTON, "退出主程序", triggered=self.kill_main) - ) - - # 设置托盘菜单 - self.tray.setContextMenu(self.tray_menu) - self.tray.activated.connect(self.on_tray_activated) - - self.show_main() - - def show_tray(self): - """最小化到托盘""" - if self.if_save: - self.set_ui("保存") - self.hide() - self.tray.show() - - def show_main(self): - """显示主界面""" - self.set_ui("配置") - self.tray.hide() - - def on_tray_activated(self, reason): - """双击返回主界面""" - if reason == QSystemTrayIcon.DoubleClick: - self.show_main() - - def start_task(self, mode): - """调起对应任务""" - if self.main.MaaManager.isRunning(): - self.notify.push_notification( - f"无法运行{mode}!", - "当前已有任务正在运行,请在该任务结束后重试", - "当前已有任务正在运行,请在该任务结束后重试", - 3, - ) - else: - self.main.maa_starter(mode) - - def stop_task(self): - """中止当前任务""" - if self.main.MaaManager.isRunning(): - if ( - self.main.MaaManager.mode == "日常代理" - or self.main.MaaManager.mode == "人工排查" - ): - self.main.maa_ender(f"{self.main.MaaManager.mode}_结束") - elif "设置MAA" in self.main.MaaManager.mode: - self.notify.push_notification( - "正在设置MAA!", - "正在运行设置MAA任务,无法中止", - "正在运行设置MAA任务,无法中止", - 3, - ) - else: - self.notify.push_notification( - "无任务运行!", - "当前无任务正在运行,无需中止", - "当前无任务正在运行,无需中止", - 3, - ) - - def kill_main(self): - """退出主程序""" - self.close() - QApplication.quit() - - def set_ui(self, mode): - """设置窗口相关属性""" - - # 保存窗口相关属性 - if mode == "保存": - - self.config.content["Default"][ - "SelfSet.UIsize" - ] = f"{self.geometry().width()}x{self.geometry().height()}" - self.config.content["Default"][ - "SelfSet.UIlocation" - ] = f"{self.geometry().x()}x{self.geometry().y()}" - if self.isMaximized(): - self.config.content["Default"]["SelfSet.UImaximized"] = "True" - else: - self.config.content["Default"]["SelfSet.UImaximized"] = "False" - self.config.save_config() - - # 配置窗口相关属性 - elif mode == "配置": - - self.if_save = False - - size = list( - map(int, self.config.content["Default"]["SelfSet.UIsize"].split("x")) - ) - location = list( - map( - int, self.config.content["Default"]["SelfSet.UIlocation"].split("x") - ) - ) - self.setGeometry(location[0], location[1], size[0], size[1]) - if self.config.content["Default"]["SelfSet.UImaximized"] == "True": - self.showMinimized() - self.showMaximized() - else: - self.showMinimized() - self.showNormal() - - self.if_save = True - - def changeEvent(self, event: QtCore.QEvent): - """重写后的 changeEvent""" - - # 最小化到托盘功能实现 - if event.type() == QtCore.QEvent.WindowStateChange: - if self.windowState() & QtCore.Qt.WindowMinimized: - if self.config.content["Default"]["SelfSet.IfToTray"] == "True": - self.show_tray() - - # 保留其它 changeEvent 方法 - return super().changeEvent(event) - - def closeEvent(self, event: QCloseEvent): - """清理残余进程""" - - self.set_ui("保存") - - # 清理各功能线程 - self.main.Timer.stop() - self.main.Timer.deleteLater() - self.main.MaaManager.requestInterruption() - self.main.MaaManager.quit() - self.main.MaaManager.wait() - - # 关闭数据库连接 - self.config.close_database() - - event.accept() diff --git a/app/ui/main_window.py b/app/ui/main_window.py new file mode 100644 index 0000000..05378e9 --- /dev/null +++ b/app/ui/main_window.py @@ -0,0 +1,343 @@ +# +# Copyright © <2024> + +# This file is part of AUTO_MAA. + +# AUTO_MAA is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published +# by the Free Software Foundation, either version 3 of the License, +# or (at your option) any later version. + +# AUTO_MAA is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty +# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See +# the GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with AUTO_MAA. If not, see . + +# DLmaster_361@163.com + +""" +AUTO_MAA +AUTO_MAA主界面 +v4.2 +作者:DLmaster_361 +""" + +from loguru import logger +from PySide6.QtWidgets import ( + QApplication, + QSystemTrayIcon, +) +from qfluentwidgets import ( + Action, + PushButton, + SystemTrayMenu, + SplashScreen, + FluentIcon, + InfoBar, + InfoBarPosition, + setTheme, + Theme, + MSFluentWindow, + NavigationItemPosition, + qconfig, +) +from PySide6.QtGui import QIcon, QCloseEvent +from PySide6.QtCore import Qt + +from app.core import Config, Task_manager, Main_timer, MainInfoBar +from app.services import Notify, Crypto, System +from .setting import Setting +from .member_manager import MemberManager +from .queue_manager import QueueManager +from .dispatch_center import DispatchCenter + + +class AUTO_MAA(MSFluentWindow): + + def __init__(self): + super().__init__() + + self.setWindowIcon(QIcon(str(Config.app_path / "resources/icons/AUTO_MAA.ico"))) + self.setWindowTitle("AUTO_MAA") + + setTheme(Theme.AUTO) + + self.splashScreen = SplashScreen(self.windowIcon(), self) + self.show_ui("显示主窗口", if_quick=True) + + MainInfoBar.parent = self + + # 创建主窗口 + self.setting = Setting(self) + self.member_manager = MemberManager(self) + self.queue_manager = QueueManager(self) + self.dispatch_center = DispatchCenter(self) + + self.addSubInterface( + self.setting, + FluentIcon.SETTING, + "设置", + FluentIcon.SETTING, + NavigationItemPosition.BOTTOM, + ) + self.addSubInterface( + self.member_manager, + FluentIcon.ROBOT, + "脚本管理", + FluentIcon.ROBOT, + NavigationItemPosition.TOP, + ) + self.addSubInterface( + self.queue_manager, + FluentIcon.BOOK_SHELF, + "调度队列", + FluentIcon.BOOK_SHELF, + NavigationItemPosition.TOP, + ) + self.addSubInterface( + self.dispatch_center, + FluentIcon.IOT, + "调度中心", + FluentIcon.IOT, + NavigationItemPosition.TOP, + ) + self.stackedWidget.currentChanged.connect( + lambda index: (self.member_manager.refresh() if index == 1 else None) + ) + self.stackedWidget.currentChanged.connect( + lambda index: self.queue_manager.refresh() if index == 2 else None + ) + 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.tray = QSystemTrayIcon( + QIcon(str(Config.app_path / "resources/icons/AUTO_MAA.ico")), + self, + ) + self.tray.setToolTip("AUTO_MAA") + self.tray_menu = SystemTrayMenu("AUTO_MAA", self) + + # 显示主界面菜单项 + self.tray_menu.addAction( + Action( + FluentIcon.CAFE, + "显示主界面", + triggered=lambda: self.show_ui("显示主窗口"), + ) + ) + self.tray_menu.addSeparator() + + # 开始任务菜单项 + # self.tray_menu.addActions( + # [ + # Action( + # FluentIcon.PLAY, + # "运行自动代理", + # triggered=lambda: self.start_task("自动代理"), + # ), + # Action( + # FluentIcon.PLAY, + # "运行人工排查", + # triggered=lambda: self.start_task("人工排查"), + # ), + # Action(FluentIcon.PAUSE, "中止当前任务", triggered=self.stop_task), + # ] + # ) + # self.tray_menu.addSeparator() + + # 退出主程序菜单项 + self.tray_menu.addAction( + Action(FluentIcon.POWER_BUTTON, "退出主程序", triggered=self.kill_main) + ) + + # 设置托盘菜单 + self.tray.setContextMenu(self.tray_menu) + self.tray.activated.connect(self.on_tray_activated) + + Task_manager.create_gui.connect(self.dispatch_center.add_board) + Task_manager.connect_gui.connect(self.dispatch_center.connect_main_board) + self.setting.ui.card_IfShowTray.checkedChanged.connect( + lambda: self.show_ui("配置托盘") + ) + self.setting.ui.card_IfToTray.checkedChanged.connect(self.set_min_method) + + self.splashScreen.finish() + + def start_up_task(self) -> None: + """启动时任务""" + + # 加载配置 + qconfig.load(Config.config_path, Config.global_config) + + # 检查密码 + self.setting.check_PASSWORD() + + # 检查更新 + if Config.global_config.get(Config.global_config.update_IfAutoUpdate): + result = self.setting.get_update_info() + if result == "已是最新版本~": + MainInfoBar.push_info_bar("success", "更新检查", result, 3000) + else: + info = InfoBar.info( + title="更新检查", + content=result, + orient=Qt.Horizontal, + isClosable=True, + position=InfoBarPosition.BOTTOM_LEFT, + duration=-1, + parent=self, + ) + Up = PushButton("更新") + Up.clicked.connect(lambda: self.setting.get_update(if_question=False)) + Up.clicked.connect(info.close) + info.addWidget(Up) + info.show() + + def set_min_method(self) -> None: + """设置最小化方法""" + + if Config.global_config.get(Config.global_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.showMinimized) + + def on_tray_activated(self, reason): + """双击返回主界面""" + if reason == QSystemTrayIcon.DoubleClick: + self.show_ui("显示主窗口") + + # def start_task(self, mode): + # """调起对应任务""" + # if self.main.MaaManager.isRunning(): + # Notify.push_notification( + # f"无法运行{mode}!", + # "当前已有任务正在运行,请在该任务结束后重试", + # "当前已有任务正在运行,请在该任务结束后重试", + # 3, + # ) + # else: + # self.main.maa_starter(mode) + + # def stop_task(self): + # """中止当前任务""" + # if self.main.MaaManager.isRunning(): + # if ( + # self.main.MaaManager.mode == "自动代理" + # or self.main.MaaManager.mode == "人工排查" + # ): + # self.main.maa_ender(f"{self.main.MaaManager.mode}_结束") + # elif "设置MAA" in self.main.MaaManager.mode: + # Notify.push_notification( + # "正在设置MAA!", + # "正在运行设置MAA任务,无法中止", + # "正在运行设置MAA任务,无法中止", + # 3, + # ) + # else: + # Notify.push_notification( + # "无任务运行!", + # "当前无任务正在运行,无需中止", + # "当前无任务正在运行,无需中止", + # 3, + # ) + + def kill_main(self) -> None: + """退出主程序""" + self.close() + QApplication.quit() + + def show_ui(self, mode: str, if_quick: bool = False) -> None: + """配置窗口状态""" + + if mode == "显示主窗口": + + # 配置主窗口 + size = list( + map( + int, + Config.global_config.get(Config.global_config.ui_size).split("x"), + ) + ) + location = list( + map( + int, + Config.global_config.get(Config.global_config.ui_location).split( + "x" + ), + ) + ) + self.setGeometry(location[0], location[1], size[0], size[1]) + self.show() + if not if_quick: + if Config.global_config.get(Config.global_config.ui_maximized): + self.showMaximized() + self.set_min_method() + self.show_ui("配置托盘") + + elif mode == "配置托盘": + + if Config.global_config.get(Config.global_config.ui_IfShowTray): + self.tray.show() + else: + self.tray.hide() + + elif mode == "隐藏到托盘": + + # 保存窗口相关属性 + if not self.isMaximized(): + + Config.global_config.set( + Config.global_config.ui_size, + f"{self.geometry().width()}x{self.geometry().height()}", + ) + Config.global_config.set( + Config.global_config.ui_location, + f"{self.geometry().x()}x{self.geometry().y()}", + ) + Config.global_config.set( + Config.global_config.ui_maximized, self.isMaximized() + ) + Config.global_config.save() + + # 隐藏主窗口 + if not if_quick: + + self.hide() + self.tray.show() + + def closeEvent(self, event: QCloseEvent): + """清理残余进程""" + + self.show_ui("隐藏到托盘", if_quick=True) + + # 清理各功能线程 + Main_timer.Timer.stop() + Main_timer.Timer.deleteLater() + Task_manager.stop_task("ALL") + + # 关闭数据库连接 + Config.close_database() + + logger.info("AUTO_MAA主程序关闭") + logger.info("===================================") + + event.accept() diff --git a/app/ui/member_manager.py b/app/ui/member_manager.py new file mode 100644 index 0000000..7b17504 --- /dev/null +++ b/app/ui/member_manager.py @@ -0,0 +1,1628 @@ +# +# Copyright © <2024> + +# This file is part of AUTO_MAA. + +# AUTO_MAA is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published +# by the Free Software Foundation, either version 3 of the License, +# or (at your option) any later version. + +# AUTO_MAA is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty +# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See +# the GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with AUTO_MAA. If not, see . + +# DLmaster_361@163.com + +""" +AUTO_MAA +AUTO_MAA脚本管理界面 +v4.2 +作者:DLmaster_361 +""" +from loguru import logger +from PySide6.QtWidgets import ( + QWidget, + QFileDialog, + QTableWidgetItem, + QHeaderView, + QVBoxLayout, + QStackedWidget, +) +from qfluentwidgets import ( + Action, + qconfig, + TableWidget, + Pivot, + ComboBox, + ScrollArea, + FluentIcon, + MessageBox, + HeaderCardWidget, + CommandBar, + ExpandGroupSettingCard, + PushSettingCard, +) +from PySide6.QtCore import Qt +from functools import partial +from pathlib import Path +from typing import List +import datetime +import json +import shutil + +from app.core import Config, MainInfoBar, Task_manager +from app.services import Crypto +from .Widget import ( + InputMessageBox, + LineEditSettingCard, + SpinBoxSettingCard, + SetMessageBox, +) + + +class MemberManager(QWidget): + + def __init__( + self, + parent=None, + ): + super().__init__(parent) + + self.setObjectName("脚本管理") + + layout = QVBoxLayout(self) + + self.tools = CommandBar() + + self.member_manager = 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.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 = InputMessageBox( + self, + "选择一个脚本类型并添加相应脚本实例", + "选择脚本类型", + "选择", + ["MAA"], + ) + if choice.exec() and choice.input.currentIndex() != -1: + + if choice.input.currentText() == "MAA": + + index = len(self.member_manager.search_member()) + 1 + + qconfig.load( + Config.app_path / f"config/MaaConfig/脚本_{index}/config.json", + Config.maa_config, + ) + Config.clear_maa_config() + Config.maa_config.save() + + Config.open_database("Maa", f"脚本_{index}") + Config.init_database("Maa") + self.member_manager.add_MaaSettingBox(index) + self.member_manager.switch_SettingBox(index) + + def del_setting_box(self): + """删除一个脚本实例""" + + name = self.member_manager.pivot.currentRouteKey() + + if name == None: + logger.warning("删除脚本实例时未选择脚本实例") + MainInfoBar.push_info_bar( + "warning", "未选择脚本实例", "请选择一个脚本实例", 5000 + ) + return None + + if len(Config.running_list) > 0: + logger.warning("删除脚本实例时调度队列未停止运行") + MainInfoBar.push_info_bar( + "warning", "调度中心正在执行任务", "请等待或手动中止任务", 5000 + ) + return None + + choice = MessageBox( + "确认", + f"确定要删除 {name} 实例吗?", + self, + ) + if choice.exec(): + + member_list = self.member_manager.search_member() + move_list = [_ for _ in member_list if int(_[0][3:]) > int(name[3:])] + + type = [_[1] for _ in member_list if _[0] == name] + index = max(int(name[3:]) - 1, 1) + + self.member_manager.clear_SettingBox() + + shutil.rmtree(Config.app_path / f"config/{type[0]}Config/{name}") + self.change_queue(name, "禁用") + for member in move_list: + if (Config.app_path / f"config/{member[1]}Config/{member[0]}").exists(): + (Config.app_path / f"config/{member[1]}Config/{member[0]}").rename( + Config.app_path + / f"config/{member[1]}Config/脚本_{int(member[0][3:])-1}", + ) + self.change_queue(member[0], f"脚本_{int(member[0][3:])-1}") + + self.member_manager.show_SettingBox(index) + + def left_setting_box(self): + """向左移动脚本实例""" + + name = self.member_manager.pivot.currentRouteKey() + + if name == None: + logger.warning("向左移动脚本实例时未选择脚本实例") + MainInfoBar.push_info_bar( + "warning", "未选择脚本实例", "请选择一个脚本实例", 5000 + ) + return None + + member_list = self.member_manager.search_member() + 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 + + type_right = [_[1] for _ in member_list if _[0] == name] + type_left = [_[1] for _ in member_list if _[0] == f"脚本_{index-1}"] + + self.member_manager.clear_SettingBox() + + (Config.app_path / f"config/{type_right[0]}Config/脚本_{index}").rename( + Config.app_path / f"config/{type_right[0]}Config/脚本_0" + ) + self.change_queue(f"脚本_{index}", "脚本_0") + (Config.app_path / f"config/{type_left[0]}Config/脚本_{index-1}").rename( + Config.app_path / f"config/{type_left[0]}Config/脚本_{index}" + ) + self.change_queue(f"脚本_{index-1}", f"脚本_{index}") + (Config.app_path / f"config/{type_right[0]}Config/脚本_0").rename( + Config.app_path / f"config/{type_right[0]}Config/脚本_{index-1}" + ) + self.change_queue("脚本_0", f"脚本_{index-1}") + + self.member_manager.show_SettingBox(index - 1) + + def right_setting_box(self): + """向右移动脚本实例""" + + name = self.member_manager.pivot.currentRouteKey() + + if name == None: + logger.warning("向右移动脚本实例时未选择脚本实例") + MainInfoBar.push_info_bar( + "warning", "未选择脚本实例", "请选择一个脚本实例", 5000 + ) + return None + + member_list = self.member_manager.search_member() + index = int(name[3:]) + + if index == len(member_list): + 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 + + type_left = [_[1] for _ in member_list if _[0] == name] + type_right = [_[1] for _ in member_list if _[0] == f"脚本_{index+1}"] + + self.member_manager.clear_SettingBox() + + (Config.app_path / f"config/{type_left[0]}Config/脚本_{index}").rename( + Config.app_path / f"config/{type_left[0]}Config/脚本_0", + ) + self.change_queue(f"脚本_{index}", "脚本_0") + (Config.app_path / f"config/{type_right[0]}Config/脚本_{index+1}").rename( + Config.app_path / f"config/{type_right[0]}Config/脚本_{index}", + ) + self.change_queue(f"脚本_{index+1}", f"脚本_{index}") + (Config.app_path / f"config/{type_left[0]}Config/脚本_0").rename( + Config.app_path / f"config/{type_left[0]}Config/脚本_{index+1}", + ) + self.change_queue("脚本_0", f"脚本_{index+1}") + + self.member_manager.show_SettingBox(index + 1) + + def show_password(self): + + if Config.PASSWORD == "": + choice = InputMessageBox( + self, + "请输入管理密钥", + "管理密钥", + "密码", + ) + if choice.exec() and choice.input.text() != "": + Config.PASSWORD = choice.input.text() + self.member_manager.script_list[ + int(self.member_manager.pivot.currentRouteKey()[3:]) - 1 + ].user_setting.user_list.update_user_info("normal") + self.key.setIcon(FluentIcon.VIEW) + self.key.setChecked(True) + else: + Config.PASSWORD = "" + self.member_manager.script_list[ + int(self.member_manager.pivot.currentRouteKey()[3:]) - 1 + ].user_setting.user_list.update_user_info("normal") + self.key.setIcon(FluentIcon.HIDE) + self.key.setChecked(False) + else: + Config.PASSWORD = "" + self.member_manager.script_list[ + int(self.member_manager.pivot.currentRouteKey()[3:]) - 1 + ].user_setting.user_list.update_user_info("normal") + self.key.setIcon(FluentIcon.HIDE) + self.key.setChecked(False) + + def change_queue(self, old: str, new: str) -> None: + """修改调度队列配置文件的队列参数""" + + if (Config.app_path / "config/QueueConfig").exists(): + for json_file in (Config.app_path / "config/QueueConfig").glob("*.json"): + with json_file.open("r", encoding="utf-8") as f: + data = json.load(f) + + for i in range(10): + if data["Queue"][f"Member_{i+1}"] == old: + data["Queue"][f"Member_{i+1}"] = new + + with json_file.open("w", encoding="utf-8") as f: + json.dump(data, f, ensure_ascii=False, indent=4) + + def refresh(self): + """刷新脚本实例界面""" + + if len(self.member_manager.search_member()) == 0: + index = 0 + else: + index = int(self.member_manager.pivot.currentRouteKey()[3:]) + self.member_manager.switch_SettingBox(index) + + +class MemberSettingBox(QWidget): + + def __init__(self, parent=None): + super().__init__(parent) + + self.setObjectName("脚本管理") + + self.pivot = Pivot(self) + self.stackedWidget = QStackedWidget(self) + self.Layout = QVBoxLayout(self) + + self.script_list: List[MaaSettingBox] = [] + + self.Layout.addWidget(self.pivot, 0, Qt.AlignHCenter) + self.Layout.addWidget(self.stackedWidget) + self.Layout.setContentsMargins(0, 0, 0, 0) + + self.pivot.currentItemChanged.connect( + lambda index: self.switch_SettingBox(int(index[3:]), if_chang_pivot=False) + ) + + self.show_SettingBox(1) + + def show_SettingBox(self, index) -> None: + """加载所有子界面""" + + member_list = self.search_member() + + qconfig.load( + Config.app_path / "config/临时.json", + Config.maa_config, + ) + Config.clear_maa_config() + for member in member_list: + if member[1] == "Maa": + Config.open_database(member[1], member[0]) + self.add_MaaSettingBox(int(member[0][3:])) + if (Config.app_path / "config/临时.json").exists(): + (Config.app_path / "config/临时.json").unlink() + + self.switch_SettingBox(index) + + def switch_SettingBox(self, index: int, if_chang_pivot: bool = True) -> None: + """切换到指定的子界面""" + + member_list = self.search_member() + + if len(member_list) == 0: + return None + + if index > len(member_list): + return None + + type = [_[1] for _ in member_list if _[0] == f"脚本_{index}"] + + qconfig.load( + Config.app_path + / f"config/{type[0]}Config/{self.script_list[index-1].objectName()}/config.json", + Config.maa_config, + ) + Config.open_database(type[0], self.script_list[index - 1].objectName()) + self.script_list[index - 1].user_setting.user_list.update_user_info("normal") + + if if_chang_pivot: + self.pivot.setCurrentItem(self.script_list[index - 1].objectName()) + self.stackedWidget.setCurrentWidget(self.script_list[index - 1]) + + def clear_SettingBox(self) -> None: + """清空所有子界面""" + + for sub_interface in self.script_list: + self.stackedWidget.removeWidget(sub_interface) + sub_interface.deleteLater() + self.script_list.clear() + self.pivot.clear() + qconfig.load( + Config.app_path / "config/临时.json", + Config.maa_config, + ) + Config.clear_maa_config() + if (Config.app_path / "config/临时.json").exists(): + (Config.app_path / "config/临时.json").unlink() + Config.close_database() + + def add_MaaSettingBox(self, uid: int) -> None: + """添加一个MAA设置界面""" + + maa_setting_box = MaaSettingBox(uid, self) + + self.script_list.append(maa_setting_box) + + self.stackedWidget.addWidget(self.script_list[-1]) + + self.pivot.addItem(routeKey=f"脚本_{uid}", text=f"脚本 {uid}") + + def search_member(self) -> list: + """搜索所有脚本实例""" + + member_list = [] + + if (Config.app_path / "config/MaaConfig").exists(): + for subdir in (Config.app_path / "config/MaaConfig").iterdir(): + if subdir.is_dir(): + member_list.append([subdir.name, "Maa"]) + + return member_list + + +class MaaSettingBox(QWidget): + + def __init__(self, uid: int, parent=None): + super().__init__(parent) + + self.setObjectName(f"脚本_{uid}") + + layout = QVBoxLayout() + + scrollArea = ScrollArea() + scrollArea.setWidgetResizable(True) + + content_widget = QWidget() + content_layout = QVBoxLayout(content_widget) + + self.app_setting = self.AppSettingCard(self, self.objectName()) + self.user_setting = self.UserSettingCard(self, self.objectName()) + + content_layout.addWidget(self.app_setting) + content_layout.addWidget(self.user_setting) + content_layout.addStretch(1) + + scrollArea.setWidget(content_widget) + + layout.addWidget(scrollArea) + + self.setLayout(layout) + + class AppSettingCard(HeaderCardWidget): + + def __init__(self, parent=None, name: str = None): + super().__init__(parent) + + self.setTitle("MAA实例") + + self.name = name + + Layout = QVBoxLayout() + + self.card_Name = LineEditSettingCard( + "请输入实例名称", + FluentIcon.EDIT, + "实例名称", + "用于标识MAA实例的名称", + Config.maa_config.MaaSet_Name, + ) + self.card_Path = PushSettingCard( + "选择文件夹", + FluentIcon.FOLDER, + "MAA目录", + Config.maa_config.get(Config.maa_config.MaaSet_Path), + ) + self.card_Set = PushSettingCard( + "设置", + FluentIcon.HOME, + "MAA全局配置", + "简洁模式下MAA将继承全局配置", + ) + self.RunSet = self.RunSetSettingCard(self) + + self.card_Path.clicked.connect(self.PathClicked) + Config.maa_config.MaaSet_Path.valueChanged.connect( + lambda: self.card_Path.setContent( + Config.maa_config.get(Config.maa_config.MaaSet_Path) + ) + ) + self.card_Set.clicked.connect( + lambda: Task_manager.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目录", "./") + if ( + not folder + or Config.maa_config.get(Config.maa_config.MaaSet_Path) == folder + ): + logger.warning("选择MAA目录时未选择文件夹或未更改文件夹") + MainInfoBar.push_info_bar( + "warning", "警告", "未选择文件夹或未更改文件夹", 5000 + ) + return None + elif ( + not (Path(folder) / "config/gui.json").exists() + or not (Path(folder) / "MAA.exe").exists() + ): + logger.warning("选择MAA目录时未找到MAA程序或配置文件") + MainInfoBar.push_info_bar( + "warning", "警告", "未找到MAA程序或配置文件", 5000 + ) + return None + + (Config.app_path / f"config/MaaConfig/{self.name}/Default").mkdir( + parents=True, exist_ok=True + ) + shutil.copy( + Path(folder) / "config/gui.json", + Config.app_path / f"config/MaaConfig/{self.name}/Default/gui.json", + ) + Config.maa_config.set(Config.maa_config.MaaSet_Path, folder) + self.card_Path.setContent(folder) + + class RunSetSettingCard(ExpandGroupSettingCard): + + def __init__(self, parent=None): + super().__init__( + FluentIcon.SETTING, + "运行", + "MAA运行调控选项", + parent, + ) + + widget = QWidget() + Layout = QVBoxLayout(widget) + + self.AnnihilationTimeLimit = SpinBoxSettingCard( + (1, 1024), + FluentIcon.PAGE_RIGHT, + "剿灭代理超时限制", + "MAA日志无变化时间超过该阈值视为超时,单位为分钟", + Config.maa_config.RunSet_AnnihilationTimeLimit, + ) + + self.RoutineTimeLimit = SpinBoxSettingCard( + (1, 1024), + FluentIcon.PAGE_RIGHT, + "自动代理超时限制", + "MAA日志无变化时间超过该阈值视为超时,单位为分钟", + Config.maa_config.RunSet_RoutineTimeLimit, + ) + + self.RunTimesLimit = SpinBoxSettingCard( + (1, 1024), + FluentIcon.PAGE_RIGHT, + "代理重试次数限制", + "若超过该次数限制仍未完成代理,视为代理失败", + Config.maa_config.RunSet_RunTimesLimit, + ) + + Layout.addWidget(self.AnnihilationTimeLimit) + Layout.addWidget(self.RoutineTimeLimit) + Layout.addWidget(self.RunTimesLimit) + + self.viewLayout.setContentsMargins(0, 0, 0, 0) + self.viewLayout.setSpacing(0) + + self.addGroupWidget(widget) + + class UserSettingCard(HeaderCardWidget): + + def __init__(self, parent=None, name: str = None): + super().__init__(parent) + + self.setTitle("用户列表") + + self.name = name + + Layout = QVBoxLayout() + + self.user_list = self.UserListBox(self.name, self) + + self.tools = CommandBar() + self.tools.addActions( + [ + Action( + FluentIcon.ADD, "新建用户", triggered=self.user_list.add_user + ), + Action( + FluentIcon.REMOVE, "删除用户", triggered=self.user_list.del_user + ), + ] + ) + self.tools.addSeparator() + self.tools.addActions( + [ + Action(FluentIcon.UP, "向上移动", triggered=self.user_list.up_user), + Action( + FluentIcon.DOWN, "向下移动", triggered=self.user_list.down_user + ), + ] + ) + self.tools.addSeparator() + self.tools.addAction( + Action( + FluentIcon.SCROLL, "模式转换", triggered=self.user_list.switch_user + ) + ) + self.tools.addSeparator() + self.tools.addAction( + Action( + FluentIcon.DEVELOPER_TOOLS, "用户选项配置", triggered=self.set_more + ) + ) + + Layout.addWidget(self.tools) + Layout.addWidget(self.user_list) + + self.viewLayout.addLayout(Layout) + + def set_more(self): + """用户选项配置""" + + if len(Config.running_list) > 0: + logger.warning("配置用户选项时调度队列未停止运行") + MainInfoBar.push_info_bar( + "warning", "调度中心正在执行任务", "请等待或手动中止任务", 5000 + ) + return None + + Config.cur.execute("SELECT * FROM adminx WHERE True") + data = Config.cur.fetchall() + + if self.user_list.pivot.currentRouteKey() == f"{self.name}_简洁用户列表": + + user_list = [_[0] for _ in data if _[15] == "simple"] + set_list = ["自定义基建"] + + choice = SetMessageBox( + self.parent().parent().parent().parent().parent().parent().parent(), + "用户选项配置", + ["选择要配置的用户", "选择要配置的选项"], + [user_list, set_list], + ) + if ( + choice.exec() + and choice.input[0].currentIndex() != -1 + and choice.input[1].currentIndex() != -1 + ): + + if choice.input[1].currentIndex() == 0: + file_path, _ = QFileDialog.getOpenFileName( + self, + "选择自定义基建文件", + ".", + "JSON 文件 (*.json)", + ) + if file_path != "": + ( + Config.app_path + / f"config/MaaConfig/{self.name}/simple/{choice.input[0].currentIndex()}/infrastructure" + ).mkdir(parents=True, exist_ok=True) + shutil.copy( + file_path, + Config.app_path + / f"config/MaaConfig/{self.name}/simple/{choice.input[0].currentIndex()}/infrastructure", + ) + else: + logger.warning("未选择自定义基建文件") + MainInfoBar.push_info_bar( + "warning", "警告", "未选择自定义基建文件", 5000 + ) + + elif self.user_list.pivot.currentRouteKey() == f"{self.name}_高级用户列表": + + user_list = [_[0] for _ in data if _[15] == "beta"] + set_list = ["MAA日常配置", "MAA剿灭配置"] + + choice = SetMessageBox( + self.parent().parent().parent().parent().parent().parent().parent(), + "用户选项配置", + ["选择要配置的用户", "选择要配置的选项"], + [user_list, set_list], + ) + if ( + choice.exec() + and choice.input[0].currentIndex() != -1 + and choice.input[1].currentIndex() != -1 + ): + + set_book = ["routine", "annihilation"] + Task_manager.add_task( + "设置MAA_用户", + self.name, + { + "SetMaaInfo": { + "UserId": choice.input[0].currentIndex(), + "SetType": set_book[choice.input[1].currentIndex()], + } + }, + ) + + class UserListBox(QWidget): + + def __init__(self, name: str, parent=None): + super().__init__(parent) + self.setObjectName(f"{name}_用户列表") + + self.name = name + + self.if_user_list_editable = True + self.if_update_database = True + self.if_update_config = True + + self.user_mode_list = ["simple", "beta"] + self.user_column = [ + "admin", + "id", + "server", + "day", + "status", + "last", + "game", + "game_1", + "game_2", + "routine", + "annihilation", + "infrastructure", + "password", + "notes", + "numb", + "mode", + "uid", + ] + self.userlist_simple_index = [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + "-", + 9, + 10, + 11, + 12, + "-", + "-", + "-", + ] + self.userlist_beta_index = [ + 0, + "-", + "-", + 1, + 2, + 3, + "-", + "-", + "-", + 4, + 5, + "-", + 6, + 7, + "-", + "-", + "-", + ] + + self.pivot = Pivot(self) + self.stackedWidget = QStackedWidget(self) + self.Layout = QVBoxLayout(self) + + self.user_list_simple = TableWidget() + self.user_list_simple.setObjectName(f"{self.name}_简洁用户列表") + self.user_list_simple.setColumnCount(13) + self.user_list_simple.setBorderVisible(True) + self.user_list_simple.setBorderRadius(10) + self.user_list_simple.setWordWrap(False) + self.user_list_simple.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + self.user_list_simple.setHorizontalHeaderLabels( + [ + "用户名", + "账号ID", + "服务器", + "代理天数", + "状态", + "执行情况", + "关卡", + "备选关卡-1", + "备选关卡-2", + "剿灭", + "自定义基建", + "密码", + "备注", + ] + ) + + self.user_list_beta = TableWidget() + self.user_list_beta.setObjectName(f"{name}_高级用户列表") + self.user_list_beta.setColumnCount(8) + self.user_list_beta.setBorderVisible(True) + self.user_list_beta.setBorderRadius(10) + self.user_list_beta.setWordWrap(False) + self.user_list_beta.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + self.user_list_beta.setHorizontalHeaderLabels( + [ + "用户名", + "代理天数", + "状态", + "执行情况", + "日常", + "剿灭", + "密码", + "备注", + ] + ) + + self.user_list_simple.itemChanged.connect( + lambda item: self.change_user_Item(item, "simple") + ) + + self.stackedWidget.addWidget(self.user_list_simple) + self.pivot.addItem( + routeKey=f"{name}_简洁用户列表", text=f"简洁用户列表" + ) + self.stackedWidget.addWidget(self.user_list_beta) + self.pivot.addItem( + routeKey=f"{name}_高级用户列表", text=f"高级用户列表" + ) + + self.Layout.addWidget(self.pivot, 0, Qt.AlignHCenter) + self.Layout.addWidget(self.stackedWidget) + self.Layout.setContentsMargins(0, 0, 0, 0) + + self.update_user_info("normal") + self.switch_SettingBox(f"{name}_简洁用户列表") + self.pivot.currentItemChanged.connect( + lambda index: self.switch_SettingBox(index) + ) + + def switch_SettingBox(self, index: str) -> None: + """切换到指定的子界面""" + + self.pivot.setCurrentItem(index) + if "简洁用户列表" in index: + self.stackedWidget.setCurrentWidget(self.user_list_simple) + elif "高级用户列表" in index: + self.stackedWidget.setCurrentWidget(self.user_list_beta) + + def update_user_info(self, operation: str) -> None: + """将本地数据库中的用户配置同步至GUI的用户管理界面""" + + # 读入本地数据库 + Config.cur.execute("SELECT * FROM adminx WHERE True") + data = Config.cur.fetchall() + + # 处理部分模式调整 + if operation == "read_only": + self.if_user_list_editable = False + elif operation == "editable": + self.if_user_list_editable = True + + # 阻止GUI用户数据被立即写入数据库形成死循环 + self.if_update_database = False + + # user_switch_list = ["转为高级", "转为简洁"] + # self.user_switch.setText(user_switch_list[index]) + + # 同步简洁用户配置列表 + data_simple = [_ for _ in data if _[15] == "simple"] + self.user_list_simple.setRowCount(len(data_simple)) + height = self.user_list_simple.horizontalHeader().height() + + for i, row in enumerate(data_simple): + + height += self.user_list_simple.rowHeight(i) + + for j, value in enumerate(row): + + if self.userlist_simple_index[j] == "-": + continue + + # 生成表格组件 + if j == 2: + item = ComboBox() + item.addItems(["官服", "B服"]) + if value == "Official": + item.setCurrentIndex(0) + elif value == "Bilibili": + item.setCurrentIndex(1) + item.currentIndexChanged.connect( + partial( + self.change_user_CellWidget, + data_simple[i][16], + self.user_column[j], + ) + ) + elif j in [4, 10, 11]: + item = ComboBox() + item.addItems(["启用", "禁用"]) + if value == "y": + item.setCurrentIndex(0) + elif value == "n": + item.setCurrentIndex(1) + item.currentIndexChanged.connect( + partial( + self.change_user_CellWidget, + data_simple[i][16], + self.user_column[j], + ) + ) + elif j == 3 and value == -1: + item = QTableWidgetItem("无限") + elif j == 5: + curdate = server_date() + if curdate != value: + item = QTableWidgetItem("今日未代理") + else: + item = QTableWidgetItem( + f"今日已代理{data_simple[i][14]}次" + ) + item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) + elif j == 12: + if Config.PASSWORD == "": + item = QTableWidgetItem("******") + item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) + else: + result = Crypto.decryptx(value, Config.PASSWORD) + item = QTableWidgetItem(result) + if result == "管理密钥错误": + item.setFlags( + Qt.ItemIsSelectable | Qt.ItemIsEnabled + ) + else: + item = QTableWidgetItem(str(value)) + + # 组件录入表格 + if j in [2, 4, 10, 11]: + if not self.if_user_list_editable: + item.setEnabled(False) + self.user_list_simple.setCellWidget( + data_simple[i][16], self.userlist_simple_index[j], item + ) + else: + self.user_list_simple.setItem( + data_simple[i][16], self.userlist_simple_index[j], item + ) + self.user_list_simple.setFixedHeight( + height + self.user_list_simple.frameWidth() * 2 + 10 + ) + + # 同步高级用户配置列表 + data_beta = [_ for _ in data if _[15] == "beta"] + self.user_list_beta.setRowCount(len(data_beta)) + height = self.user_list_beta.horizontalHeader().height() + + for i, row in enumerate(data_beta): + + height += self.user_list_beta.rowHeight(i) + + for j, value in enumerate(row): + + if self.userlist_beta_index[j] == "-": + continue + + # 生成表格组件 + if j in [4, 9, 10]: + item = ComboBox() + item.addItems(["启用", "禁用"]) + if value == "y": + item.setCurrentIndex(0) + elif value == "n": + item.setCurrentIndex(1) + item.currentIndexChanged.connect( + partial( + self.change_user_CellWidget, + data_beta[i][16], + self.user_column[j], + ) + ) + elif j == 3 and value == -1: + item = QTableWidgetItem("无限") + elif j == 5: + curdate = server_date() + if curdate != value: + item = QTableWidgetItem("今日未代理") + else: + item = QTableWidgetItem( + f"今日已代理{data_beta[i][14]}次" + ) + item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) + elif j == 12: + if Config.PASSWORD == "": + item = QTableWidgetItem("******") + item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) + else: + result = Crypto.decryptx(value, Config.PASSWORD) + item = QTableWidgetItem(result) + if result == "管理密钥错误": + item.setFlags( + Qt.ItemIsSelectable | Qt.ItemIsEnabled + ) + else: + item = QTableWidgetItem(str(value)) + + # 组件录入表格 + if j in [4, 9, 10]: + if not self.if_user_list_editable: + item.setEnabled(False) + self.user_list_beta.setCellWidget( + data_beta[i][16], self.userlist_beta_index[j], item + ) + else: + self.user_list_beta.setItem( + data_beta[i][16], self.userlist_beta_index[j], item + ) + self.user_list_beta.setFixedHeight( + height + self.user_list_beta.frameWidth() * 2 + 10 + ) + + # 设置列表可编辑状态 + if self.if_user_list_editable: + self.user_list_simple.setEditTriggers(TableWidget.AllEditTriggers) + self.user_list_beta.setEditTriggers(TableWidget.AllEditTriggers) + else: + self.user_list_simple.setEditTriggers(TableWidget.NoEditTriggers) + self.user_list_beta.setEditTriggers(TableWidget.NoEditTriggers) + + # 允许GUI改变被同步到本地数据库 + self.if_update_database = True + + # 设置用户配置列表的标题栏宽度 + self.user_list_simple.horizontalHeader().setSectionResizeMode( + QHeaderView.Stretch + ) + self.user_list_beta.horizontalHeader().setSectionResizeMode( + QHeaderView.Stretch + ) + + def change_user_Item(self, item: QTableWidgetItem, mode): + """将GUI中发生修改的用户配置表中的一般信息同步至本地数据库""" + + # 验证能否写入本地数据库 + if not self.if_update_database: + return None + + text = item.text() + # 简洁用户配置列表 + if mode == "simple": + # 待写入信息预处理 + if item.column() == 3: # 代理天数 + try: + text = max(int(text), -1) + except ValueError: + self.update_user_info("normal") + return None + if item.column() in [6, 7, 8]: # 关卡号 + # 导入与应用特殊关卡规则 + games = {} + with Config.gameid_path.open(mode="r", encoding="utf-8") as f: + gameids = f.readlines() + for line in gameids: + if ":" in line: + game_in, game_out = line.split(":", 1) + games[game_in.strip()] = game_out.strip() + text = games.get(text, text) + if item.column() == 11: # 密码 + text = Crypto.encryptx(text) + + # 保存至本地数据库 + if text != "": + Config.cur.execute( + f"UPDATE adminx SET {self.user_column[self.userlist_simple_index.index(item.column())]} = ? WHERE mode = 'simple' AND uid = ?", + (text, item.row()), + ) + # 高级用户配置列表 + elif mode == "beta": + # 待写入信息预处理 + if item.column() == 1: # 代理天数 + try: + text = max(int(text), -1) + except ValueError: + self.update_user_info("normal") + return None + if item.column() == 6: # 密码 + text = Crypto.encryptx(text) + + # 保存至本地数据库 + if text != "": + Config.cur.execute( + f"UPDATE adminx SET {self.user_column[self.userlist_beta_index.index(item.column())]} = ? WHERE mode = 'beta' AND uid = ?", + (text, item.row()), + ) + Config.db.commit() + + # 同步一般用户信息更改到GUI + self.update_user_info("normal") + + def change_user_CellWidget(self, row, column, index): + """将GUI中发生修改的用户配置表中的CellWidget类信息同步至本地数据库""" + + # 验证能否写入本地数据库 + if not self.if_update_database: + return None + + if "简洁用户列表" in self.pivot.currentRouteKey(): + mode = 0 + elif "高级用户列表" in self.pivot.currentRouteKey(): + mode = 1 + + # 初次开启自定义MAA配置或选择修改MAA配置时调起MAA配置任务 + # if ( + # mode == 1 + # and column in ["routine", "annihilation"] + # and ( + # index == 2 + # or ( + # index == 0 + # and not ( + # Config.app_path + # / f"data/MAAconfig/{self.user_mode_list[index]}/{row}/{column}/gui.json" + # ).exists() + # ) + # ) + # ): + # pass + # self.MaaManager.get_json_path = [ + # index, + # row, + # column, + # ] + # self.maa_starter("设置MAA_用户") + + # 服务器 + if mode == 0 and column == "server": + server_list = ["Official", "Bilibili"] + Config.cur.execute( + f"UPDATE adminx SET server = ? WHERE mode = 'simple' AND uid = ?", + (server_list[index], row), + ) + # 其它(启用/禁用) + elif index in [0, 1]: + index_list = ["y", "n"] + Config.cur.execute( + f"UPDATE adminx SET {column} = ? WHERE mode = ? AND uid = ?", + ( + index_list[index], + self.user_mode_list[mode], + row, + ), + ) + Config.db.commit() + + # 同步用户组件信息修改到GUI + self.update_user_info("normal") + + def add_user(self): + """添加一位新用户""" + + # 插入预设用户数据 + if "简洁用户列表" in self.pivot.currentRouteKey(): + set_book = ["simple", self.user_list_simple.rowCount()] + elif "高级用户列表" in self.pivot.currentRouteKey(): + set_book = ["beta", self.user_list_beta.rowCount()] + Config.cur.execute( + "INSERT INTO adminx VALUES('新用户','手机号码(官服)/B站ID(B服)','Official',-1,'y','2000-01-01','1-7','-','-','n','n','n',?,'无',0,?,?)", + ( + Crypto.encryptx("未设置"), + set_book[0], + set_book[1], + ), + ) + Config.db.commit(), + + # 同步新用户至GUI + self.update_user_info("normal") + + def del_user(self) -> None: + """删除选中的首位用户""" + + if len(Config.running_list) > 0: + logger.warning("删除用户时调度队列未停止运行") + MainInfoBar.push_info_bar( + "warning", "调度中心正在执行任务", "请等待或手动中止任务", 5000 + ) + return None + + # 获取对应的行索引 + if "简洁用户列表" in self.pivot.currentRouteKey(): + row = self.user_list_simple.currentRow() + current_numb = self.user_list_simple.rowCount() + mode = 0 + elif "高级用户列表" in self.pivot.currentRouteKey(): + row = self.user_list_beta.currentRow() + current_numb = self.user_list_beta.rowCount() + mode = 1 + + # 判断选择合理性 + if row == -1: + logger.warning("删除用户时未选中用户") + MainInfoBar.push_info_bar( + "warning", "未选择用户", "请先选择一个用户", 5000 + ) + return None + + # 确认待删除用户信息 + Config.cur.execute( + "SELECT * FROM adminx WHERE mode = ? AND uid = ?", + ( + self.user_mode_list[mode], + row, + ), + ) + data = Config.cur.fetchall() + choice = MessageBox( + "确认", + f"确定要删除用户 {data[0][0]} 吗?", + self.parent() + .parent() + .parent() + .parent() + .parent() + .parent() + .parent() + .parent() + .parent(), + ) + + # 删除用户 + if choice.exec(): + # 删除所选用户 + Config.cur.execute( + "DELETE FROM adminx WHERE mode = ? AND uid = ?", + ( + self.user_mode_list[mode], + row, + ), + ) + Config.db.commit() + + if ( + Config.app_path + / f"config/MaaConfig/{self.name}/{self.user_mode_list[mode]}/{row}" + ).exists(): + shutil.rmtree( + Config.app_path + / f"config/MaaConfig/{self.name}/{self.user_mode_list[mode]}/{row}" + ) + # 后续用户补位 + for i in range(row + 1, current_numb): + Config.cur.execute( + "UPDATE adminx SET uid = ? WHERE mode = ? AND uid = ?", + ( + i - 1, + self.user_mode_list[mode], + i, + ), + ) + Config.db.commit() + if ( + Config.app_path + / f"config/MaaConfig/{self.name}/{self.user_mode_list[mode]}/{i}" + ).exists(): + ( + Config.app_path + / f"config/MaaConfig/{self.name}/{self.user_mode_list[mode]}/{i}" + ).rename( + Config.app_path + / f"config/MaaConfig/{self.name}/{self.user_mode_list[mode]}/{i}" + ) + + # 同步最终结果至GUI + self.update_user_info("normal") + + def up_user(self): + """向上移动用户""" + + if len(Config.running_list) > 0: + logger.warning("向上移动用户时调度队列未停止运行") + MainInfoBar.push_info_bar( + "warning", "调度中心正在执行任务", "请等待或手动中止任务", 5000 + ) + return None + + # 获取对应的行索引 + if "简洁用户列表" in self.pivot.currentRouteKey(): + row = self.user_list_simple.currentRow() + mode = 0 + elif "高级用户列表" in self.pivot.currentRouteKey(): + row = self.user_list_beta.currentRow() + mode = 1 + + # 判断选择合理性 + if row == -1: + logger.warning("向上移动用户时未选中用户") + MainInfoBar.push_info_bar( + "warning", "未选中用户", "请先选择一个用户", 5000 + ) + return None + + if row == 0: + return None + + Config.cur.execute( + "UPDATE adminx SET uid = ? WHERE mode = ? AND uid = ?", + ( + -1, + self.user_mode_list[mode], + row, + ), + ) + Config.cur.execute( + "UPDATE adminx SET uid = ? WHERE mode = ? AND uid = ?", + ( + row, + self.user_mode_list[mode], + row - 1, + ), + ) + Config.cur.execute( + "UPDATE adminx SET uid = ? WHERE mode = ? AND uid = ?", + ( + row - 1, + self.user_mode_list[mode], + -1, + ), + ) + Config.db.commit() + + if ( + Config.app_path + / f"config/MaaConfig/{self.name}/{self.user_mode_list[mode]}/{row}" + ).exists(): + ( + Config.app_path + / f"config/MaaConfig/{self.name}/{self.user_mode_list[mode]}/{row}" + ).rename( + Config.app_path + / f"config/MaaConfig/{self.name}/{self.user_mode_list[mode]}/{-1}" + ) + if ( + Config.app_path + / f"config/MaaConfig/{self.name}/{self.user_mode_list[mode]}/{row - 1}" + ).exists(): + ( + Config.app_path + / f"config/MaaConfig/{self.name}/{self.user_mode_list[mode]}/{row - 1}" + ).rename( + Config.app_path + / f"config/MaaConfig/{self.name}/{self.user_mode_list[mode]}/{row}" + ) + if ( + Config.app_path + / f"config/MaaConfig/{self.name}/{self.user_mode_list[mode]}/{-1}" + ).exists(): + ( + Config.app_path + / f"config/MaaConfig/{self.name}/{self.user_mode_list[mode]}/{-1}" + ).rename( + Config.app_path + / f"config/MaaConfig/{self.name}/{self.user_mode_list[mode]}/{row - 1}" + ) + + self.update_user_info("normal") + if "简洁用户列表" in self.pivot.currentRouteKey(): + self.user_list_simple.selectRow(row - 1) + elif "高级用户列表" in self.pivot.currentRouteKey(): + self.user_list_beta.selectRow(row - 1) + + def down_user(self): + """向下移动用户""" + + if len(Config.running_list) > 0: + logger.warning("向下移动用户时调度队列未停止运行") + MainInfoBar.push_info_bar( + "warning", "调度中心正在执行任务", "请等待或手动中止任务", 5000 + ) + return None + + # 获取对应的行索引 + if "简洁用户列表" in self.pivot.currentRouteKey(): + row = self.user_list_simple.currentRow() + current_numb = self.user_list_simple.rowCount() + mode = 0 + elif "高级用户列表" in self.pivot.currentRouteKey(): + row = self.user_list_beta.currentRow() + current_numb = self.user_list_beta.rowCount() + mode = 1 + + # 判断选择合理性 + if row == -1: + logger.warning("向下移动用户时未选中用户") + MainInfoBar.push_info_bar( + "warning", "未选中用户", "请先选择一个用户", 5000 + ) + return None + + if row == current_numb - 1: + return None + + Config.cur.execute( + "UPDATE adminx SET uid = ? WHERE mode = ? AND uid = ?", + ( + -1, + self.user_mode_list[mode], + row, + ), + ) + Config.cur.execute( + "UPDATE adminx SET uid = ? WHERE mode = ? AND uid = ?", + ( + row, + self.user_mode_list[mode], + row + 1, + ), + ) + Config.cur.execute( + "UPDATE adminx SET uid = ? WHERE mode = ? AND uid = ?", + ( + row + 1, + self.user_mode_list[mode], + -1, + ), + ) + Config.db.commit() + + if ( + Config.app_path + / f"config/MaaConfig/{self.name}/{self.user_mode_list[mode]}/{row}" + ).exists(): + ( + Config.app_path + / f"config/MaaConfig/{self.name}/{self.user_mode_list[mode]}/{row}" + ).rename( + Config.app_path + / f"config/MaaConfig/{self.name}/{self.user_mode_list[mode]}/{-1}" + ) + if ( + Config.app_path + / f"config/MaaConfig/{self.name}/{self.user_mode_list[mode]}/{row + 1}" + ).exists(): + ( + Config.app_path + / f"config/MaaConfig/{self.name}/{self.user_mode_list[mode]}/{row + 1}" + ).rename( + Config.app_path + / f"config/MaaConfig/{self.name}/{self.user_mode_list[mode]}/{row}" + ) + if ( + Config.app_path + / f"config/MaaConfig/{self.name}/{self.user_mode_list[mode]}/{-1}" + ).exists(): + ( + Config.app_path + / f"config/MaaConfig/{self.name}/{self.user_mode_list[mode]}/{-1}" + ).rename( + Config.app_path + / f"config/MaaConfig/{self.name}/{self.user_mode_list[mode]}/{row + 1}" + ) + + self.update_user_info("normal") + if "简洁用户列表" in self.pivot.currentRouteKey(): + self.user_list_simple.selectRow(row + 1) + elif "高级用户列表" in self.pivot.currentRouteKey(): + self.user_list_beta.selectRow(row + 1) + + def switch_user(self) -> None: + """切换用户配置模式""" + + if len(Config.running_list) > 0: + logger.warning("切换用户配置模式时调度队列未停止运行") + MainInfoBar.push_info_bar( + "warning", "调度中心正在执行任务", "请等待或手动中止任务", 5000 + ) + return None + + # 获取当前用户配置模式信息 + if "简洁用户列表" in self.pivot.currentRouteKey(): + row = self.user_list_simple.currentRow() + mode = 0 + elif "高级用户列表" in self.pivot.currentRouteKey(): + row = self.user_list_beta.currentRow() + mode = 1 + + # 判断选择合理性 + if row == -1: + logger.warning("切换用户配置模式时未选中用户") + MainInfoBar.push_info_bar( + "warning", "未选中用户", "请先选择一个用户", 5000 + ) + return None + + # 确认待切换用户信息 + Config.cur.execute( + "SELECT * FROM adminx WHERE mode = ? AND uid = ?", + ( + self.user_mode_list[mode], + row, + ), + ) + data = Config.cur.fetchall() + + mode_list = ["简洁", "高级"] + choice = MessageBox( + "确认", + f"确定要将用户 {data[0][0]} 转为{mode_list[1 - mode]}配置模式吗?", + self.parent() + .parent() + .parent() + .parent() + .parent() + .parent() + .parent() + .parent() + .parent(), + ) + + # 切换用户 + if choice.exec(): + Config.cur.execute("SELECT * FROM adminx WHERE True") + data = Config.cur.fetchall() + if mode == 0: + current_numb = self.user_list_simple.rowCount() + elif mode == 1: + current_numb = self.user_list_beta.rowCount() + # 切换所选用户 + other_numb = len(data) - current_numb + Config.cur.execute( + "UPDATE adminx SET mode = ?, uid = ? WHERE mode = ? AND uid = ?", + ( + self.user_mode_list[1 - mode], + other_numb, + self.user_mode_list[mode], + row, + ), + ) + Config.db.commit() + if ( + Config.app_path + / f"config/MaaConfig/{self.name}/{self.user_mode_list[mode]}/{row}" + ).exists(): + shutil.move( + Config.app_path + / f"config/MaaConfig/{self.name}/{self.user_mode_list[mode]}/{row}", + Config.app_path + / f"config/MaaConfig/{self.name}/{self.user_mode_list[1 - mode]}/{other_numb}", + ) + # 后续用户补位 + for i in range(row + 1, current_numb): + Config.cur.execute( + "UPDATE adminx SET uid = ? WHERE mode = ? AND uid = ?", + ( + i - 1, + self.user_mode_list[mode], + i, + ), + ) + Config.db.commit(), + if ( + Config.app_path + / f"config/MaaConfig/{self.name}/{self.user_mode_list[mode]}/{i}" + ).exists(): + ( + Config.app_path + / f"config/MaaConfig/{self.name}/{self.user_mode_list[mode]}/{i}" + ).rename( + Config.app_path + / f"config/MaaConfig/{self.name}/{self.user_mode_list[mode]}/{i - 1}" + ) + + self.update_user_info("normal") + + +def server_date() -> str: + """获取当前的服务器日期""" + + dt = datetime.datetime.now() + if dt.time() < datetime.datetime.min.time().replace(hour=4): + dt = dt - datetime.timedelta(days=1) + return dt.strftime("%Y-%m-%d") diff --git a/app/ui/queue_manager.py b/app/ui/queue_manager.py new file mode 100644 index 0000000..2661baf --- /dev/null +++ b/app/ui/queue_manager.py @@ -0,0 +1,656 @@ +# +# Copyright © <2024> + +# This file is part of AUTO_MAA. + +# AUTO_MAA is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published +# by the Free Software Foundation, either version 3 of the License, +# or (at your option) any later version. + +# AUTO_MAA is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty +# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See +# the GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with AUTO_MAA. If not, see . + +# DLmaster_361@163.com + +""" +AUTO_MAA +AUTO_MAA调度队列界面 +v4.2 +作者:DLmaster_361 +""" +from loguru import logger +from PySide6.QtWidgets import ( + QWidget, + QVBoxLayout, + QStackedWidget, + QHBoxLayout, +) +from qfluentwidgets import ( + Action, + qconfig, + Pivot, + ScrollArea, + FluentIcon, + MessageBox, + HeaderCardWidget, + TextBrowser, + CommandBar, + SwitchSettingCard, +) +from PySide6.QtCore import Qt +from typing import List +import json +import shutil + +from app.core import Config, MainInfoBar +from .Widget import ( + LineEditSettingCard, + TimeEditSettingCard, + NoOptionComboBoxSettingCard, +) + + +class QueueManager(QWidget): + + def __init__( + self, + parent=None, + ): + super().__init__(parent) + + self.setObjectName("调度队列") + + layout = QVBoxLayout(self) + + self.tools = CommandBar() + + self.queue_manager = QueueSettingBox(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 + ), + ] + ) + + layout.addWidget(self.tools) + layout.addWidget(self.queue_manager) + + def add_setting_box(self): + """添加一个调度队列""" + + index = len(self.queue_manager.search_queue()) + 1 + + qconfig.load( + Config.app_path / f"config/QueueConfig/调度队列_{index}.json", + Config.queue_config, + ) + Config.clear_queue_config() + Config.queue_config.save() + + self.queue_manager.add_QueueSettingBox(index) + self.queue_manager.switch_SettingBox(index) + + def del_setting_box(self): + """删除一个调度队列实例""" + + name = self.queue_manager.pivot.currentRouteKey() + + if name == None: + logger.warning("未选择调度队列") + MainInfoBar.push_info_bar( + "warning", "未选择调度队列", "请先选择一个调度队列", 5000 + ) + return None + + if name in Config.running_list: + logger.warning("调度队列正在运行") + MainInfoBar.push_info_bar( + "warning", "调度队列正在运行", "请先停止调度队列", 5000 + ) + return None + + choice = MessageBox( + "确认", + f"确定要删除 {name} 吗?", + self, + ) + if choice.exec(): + + queue_list = self.queue_manager.search_queue() + move_list = [_ for _ in queue_list if int(_[0][5:]) > int(name[5:])] + + index = max(int(name[5:]) - 1, 1) + + self.queue_manager.clear_SettingBox() + + (Config.app_path / f"config/QueueConfig/{name}.json").unlink() + for queue in move_list: + if (Config.app_path / f"config/QueueConfig/{queue[0]}.json").exists(): + (Config.app_path / f"config/QueueConfig/{queue[0]}.json").rename( + Config.app_path + / f"config/QueueConfig/调度队列_{int(queue[0][5:])-1}.json", + ) + + self.queue_manager.show_SettingBox(index) + + def left_setting_box(self): + """向左移动调度队列实例""" + + name = self.queue_manager.pivot.currentRouteKey() + + if name == None: + logger.warning("未选择调度队列") + MainInfoBar.push_info_bar( + "warning", "未选择调度队列", "请先选择一个调度队列", 5000 + ) + return None + + index = int(name[5:]) + + if index == 1: + logger.warning("向左移动调度队列时已到达最左端") + MainInfoBar.push_info_bar( + "warning", "已经是第一个调度队列", "无法向左移动", 5000 + ) + return None + + if name in Config.running_list or f"调度队列_{index-1}" in Config.running_list: + logger.warning("相关调度队列正在运行") + MainInfoBar.push_info_bar( + "warning", "相关调度队列正在运行", "请先停止调度队列", 5000 + ) + return None + + self.queue_manager.clear_SettingBox() + + (Config.app_path / f"config/QueueConfig/调度队列_{index}.json").rename( + Config.app_path / f"config/QueueConfig/调度队列_0.json", + ) + shutil.move( + str(Config.app_path / f"config/QueueConfig/调度队列_{index-1}.json"), + str(Config.app_path / f"config/QueueConfig/调度队列_{index}.json"), + ) + (Config.app_path / f"config/QueueConfig/调度队列_0.json").rename( + Config.app_path / f"config/QueueConfig/调度队列_{index-1}.json", + ) + + self.queue_manager.show_SettingBox(index - 1) + + def right_setting_box(self): + """向右移动调度队列实例""" + + name = self.queue_manager.pivot.currentRouteKey() + + if name == None: + logger.warning("未选择调度队列") + MainInfoBar.push_info_bar( + "warning", "未选择调度队列", "请先选择一个调度队列", 5000 + ) + return None + + queue_list = self.queue_manager.search_queue() + index = int(name[5:]) + + if index == len(queue_list): + logger.warning("向右移动调度队列时已到达最右端") + MainInfoBar.push_info_bar( + "warning", "已经是最后一个调度队列", "无法向右移动", 5000 + ) + return None + + if name in Config.running_list or f"调度队列_{index+1}" in Config.running_list: + logger.warning("相关调度队列正在运行") + MainInfoBar.push_info_bar( + "warning", "相关调度队列正在运行", "请先停止调度队列", 5000 + ) + return None + + self.queue_manager.clear_SettingBox() + + (Config.app_path / f"config/QueueConfig/调度队列_{index}.json").rename( + Config.app_path / f"config/QueueConfig/调度队列_0.json", + ) + (Config.app_path / f"config/QueueConfig/调度队列_{index+1}.json").rename( + Config.app_path / f"config/QueueConfig/调度队列_{index}.json", + ) + (Config.app_path / f"config/QueueConfig/调度队列_0.json").rename( + Config.app_path / f"config/QueueConfig/调度队列_{index+1}.json", + ) + + self.queue_manager.show_SettingBox(index + 1) + + def refresh(self): + """刷新调度队列界面""" + + if len(self.queue_manager.search_queue()) == 0: + index = 0 + else: + index = int(self.queue_manager.pivot.currentRouteKey()[5:]) + self.queue_manager.clear_SettingBox() + self.queue_manager.show_SettingBox(index) + + +class QueueSettingBox(QWidget): + + def __init__(self, parent=None): + super().__init__(parent) + + self.setObjectName("调度队列管理") + + self.pivot = Pivot(self) + self.stackedWidget = QStackedWidget(self) + self.Layout = QVBoxLayout(self) + + self.script_list: List[QueueMemberSettingBox] = [] + + self.Layout.addWidget(self.pivot, 0, Qt.AlignHCenter) + self.Layout.addWidget(self.stackedWidget) + self.Layout.setContentsMargins(0, 0, 0, 0) + + self.pivot.currentItemChanged.connect( + lambda index: self.switch_SettingBox(int(index[5:]), if_change_pivot=False) + ) + + self.show_SettingBox(1) + + def show_SettingBox(self, index) -> None: + """加载所有子界面""" + + queue_list = self.search_queue() + + qconfig.load( + Config.app_path / "config/临时.json", + Config.queue_config, + ) + Config.clear_queue_config() + for queue in queue_list: + self.add_QueueSettingBox(int(queue[0][5:])) + if (Config.app_path / "config/临时.json").exists(): + (Config.app_path / "config/临时.json").unlink() + + self.switch_SettingBox(index) + + def switch_SettingBox(self, index: int, if_change_pivot: bool = True) -> None: + """切换到指定的子界面""" + + queue_list = self.search_queue() + + if len(queue_list) == 0: + return None + + if index > len(queue_list): + return None + + qconfig.load( + Config.app_path + / f"config/QueueConfig/{self.script_list[index-1].objectName()}.json", + Config.queue_config, + ) + + if if_change_pivot: + self.pivot.setCurrentItem(self.script_list[index - 1].objectName()) + self.stackedWidget.setCurrentWidget(self.script_list[index - 1]) + + def clear_SettingBox(self) -> None: + """清空所有子界面""" + + for sub_interface in self.script_list: + self.stackedWidget.removeWidget(sub_interface) + sub_interface.deleteLater() + self.script_list.clear() + self.pivot.clear() + qconfig.load( + Config.app_path / "config/临时.json", + Config.queue_config, + ) + Config.clear_queue_config() + if (Config.app_path / "config/临时.json").exists(): + (Config.app_path / "config/临时.json").unlink() + + def add_QueueSettingBox(self, uid: int) -> None: + """添加一个调度队列设置界面""" + + maa_setting_box = QueueMemberSettingBox(uid, self) + + self.script_list.append(maa_setting_box) + + self.stackedWidget.addWidget(self.script_list[-1]) + + self.pivot.addItem(routeKey=f"调度队列_{uid}", text=f"调度队列 {uid}") + + def search_queue(self) -> list: + """搜索所有调度队列实例""" + + queue_list = [] + + if (Config.app_path / "config/QueueConfig").exists(): + for json_file in (Config.app_path / "config/QueueConfig").glob("*.json"): + with json_file.open("r", encoding="utf-8") as f: + info = json.load(f) + queue_list.append([json_file.stem, info["QueueSet"]["Name"]]) + + return queue_list + + +class QueueMemberSettingBox(QWidget): + + def __init__(self, uid: int, parent=None): + super().__init__(parent) + + self.setObjectName(f"调度队列_{uid}") + + layout = QVBoxLayout() + + scrollArea = ScrollArea() + scrollArea.setWidgetResizable(True) + + content_widget = QWidget() + content_layout = QVBoxLayout(content_widget) + + self.queue_set = self.QueueSetSettingCard(self) + self.time = self.TimeSettingCard(self) + self.task = self.TaskSettingCard(self) + self.history = self.HistoryCard(self, f"调度队列_{uid}") + + content_layout.addWidget(self.queue_set) + content_layout.addWidget(self.time) + content_layout.addWidget(self.task) + content_layout.addWidget(self.history) + content_layout.addStretch(1) + + scrollArea.setWidget(content_widget) + + layout.addWidget(scrollArea) + + self.setLayout(layout) + + class QueueSetSettingCard(HeaderCardWidget): + + def __init__(self, parent=None): + super().__init__(parent) + + self.setTitle("队列设置") + + Layout = QVBoxLayout() + + self.card_Name = LineEditSettingCard( + "请输入调度队列名称", + FluentIcon.EDIT, + "调度队列名称", + "用于标识调度队列的名称", + Config.queue_config.queueSet_Name, + ) + self.card_Enable = SwitchSettingCard( + FluentIcon.HOME, + "状态", + "调度队列状态", + Config.queue_config.queueSet_Enabled, + ) + + Layout.addWidget(self.card_Name) + Layout.addWidget(self.card_Enable) + + self.viewLayout.addLayout(Layout) + + class TimeSettingCard(HeaderCardWidget): + + def __init__(self, parent=None): + super().__init__(parent) + + self.setTitle("定时设置") + + widget_1 = QWidget() + Layout_1 = QVBoxLayout(widget_1) + widget_2 = QWidget() + Layout_2 = QVBoxLayout(widget_2) + Layout = QHBoxLayout() + + self.card_Time_0 = TimeEditSettingCard( + FluentIcon.STOP_WATCH, + "定时 1", + "", + Config.queue_config.time_TimeEnabled_0, + Config.queue_config.time_TimeSet_0, + ) + self.card_Time_1 = TimeEditSettingCard( + FluentIcon.STOP_WATCH, + "定时 2", + "", + Config.queue_config.time_TimeEnabled_1, + Config.queue_config.time_TimeSet_1, + ) + self.card_Time_2 = TimeEditSettingCard( + FluentIcon.STOP_WATCH, + "定时 3", + "", + Config.queue_config.time_TimeEnabled_2, + Config.queue_config.time_TimeSet_2, + ) + self.card_Time_3 = TimeEditSettingCard( + FluentIcon.STOP_WATCH, + "定时 4", + "", + Config.queue_config.time_TimeEnabled_3, + Config.queue_config.time_TimeSet_3, + ) + self.card_Time_4 = TimeEditSettingCard( + FluentIcon.STOP_WATCH, + "定时 5", + "", + Config.queue_config.time_TimeEnabled_4, + Config.queue_config.time_TimeSet_4, + ) + self.card_Time_5 = TimeEditSettingCard( + FluentIcon.STOP_WATCH, + "定时 6", + "", + Config.queue_config.time_TimeEnabled_5, + Config.queue_config.time_TimeSet_5, + ) + self.card_Time_6 = TimeEditSettingCard( + FluentIcon.STOP_WATCH, + "定时 7", + "", + Config.queue_config.time_TimeEnabled_6, + Config.queue_config.time_TimeSet_6, + ) + self.card_Time_7 = TimeEditSettingCard( + FluentIcon.STOP_WATCH, + "定时 8", + "", + Config.queue_config.time_TimeEnabled_7, + Config.queue_config.time_TimeSet_7, + ) + self.card_Time_8 = TimeEditSettingCard( + FluentIcon.STOP_WATCH, + "定时 9", + "", + Config.queue_config.time_TimeEnabled_8, + Config.queue_config.time_TimeSet_8, + ) + self.card_Time_9 = TimeEditSettingCard( + FluentIcon.STOP_WATCH, + "定时 10", + "", + Config.queue_config.time_TimeEnabled_9, + Config.queue_config.time_TimeSet_9, + ) + + Layout_1.addWidget(self.card_Time_0) + Layout_1.addWidget(self.card_Time_1) + Layout_1.addWidget(self.card_Time_2) + Layout_1.addWidget(self.card_Time_3) + Layout_1.addWidget(self.card_Time_4) + Layout_2.addWidget(self.card_Time_5) + Layout_2.addWidget(self.card_Time_6) + Layout_2.addWidget(self.card_Time_7) + Layout_2.addWidget(self.card_Time_8) + Layout_2.addWidget(self.card_Time_9) + Layout.addWidget(widget_1) + Layout.addWidget(widget_2) + + self.viewLayout.addLayout(Layout) + + class TaskSettingCard(HeaderCardWidget): + + def __init__(self, parent=None): + super().__init__(parent) + + self.setTitle("任务队列") + + Layout = QVBoxLayout() + + member_list = self.search_member() + + self.card_Member_1 = NoOptionComboBoxSettingCard( + Config.queue_config.queue_Member_1, + FluentIcon.APPLICATION, + "任务实例 1", + "第一个调起的脚本任务实例", + member_list[0], + member_list[1], + ) + self.card_Member_2 = NoOptionComboBoxSettingCard( + Config.queue_config.queue_Member_2, + FluentIcon.APPLICATION, + "任务实例 2", + "第二个调起的脚本任务实例", + member_list[0], + member_list[1], + ) + self.card_Member_3 = NoOptionComboBoxSettingCard( + Config.queue_config.queue_Member_3, + FluentIcon.APPLICATION, + "任务实例 3", + "第三个调起的脚本任务实例", + member_list[0], + member_list[1], + ) + self.card_Member_4 = NoOptionComboBoxSettingCard( + Config.queue_config.queue_Member_4, + FluentIcon.APPLICATION, + "任务实例 4", + "第四个调起的脚本任务实例", + member_list[0], + member_list[1], + ) + self.card_Member_5 = NoOptionComboBoxSettingCard( + Config.queue_config.queue_Member_5, + FluentIcon.APPLICATION, + "任务实例 5", + "第五个调起的脚本任务实例", + member_list[0], + member_list[1], + ) + self.card_Member_6 = NoOptionComboBoxSettingCard( + Config.queue_config.queue_Member_6, + FluentIcon.APPLICATION, + "任务实例 6", + "第六个调起的脚本任务实例", + member_list[0], + member_list[1], + ) + self.card_Member_7 = NoOptionComboBoxSettingCard( + Config.queue_config.queue_Member_7, + FluentIcon.APPLICATION, + "任务实例 7", + "第七个调起的脚本任务实例", + member_list[0], + member_list[1], + ) + self.card_Member_8 = NoOptionComboBoxSettingCard( + Config.queue_config.queue_Member_8, + FluentIcon.APPLICATION, + "任务实例 8", + "第八个调起的脚本任务实例", + member_list[0], + member_list[1], + ) + self.card_Member_9 = NoOptionComboBoxSettingCard( + Config.queue_config.queue_Member_9, + FluentIcon.APPLICATION, + "任务实例 9", + "第九个调起的脚本任务实例", + member_list[0], + member_list[1], + ) + self.card_Member_10 = NoOptionComboBoxSettingCard( + Config.queue_config.queue_Member_10, + FluentIcon.APPLICATION, + "任务实例 10", + "第十个调起的脚本任务实例", + member_list[0], + member_list[1], + ) + + Layout.addWidget(self.card_Member_1) + Layout.addWidget(self.card_Member_2) + Layout.addWidget(self.card_Member_3) + Layout.addWidget(self.card_Member_4) + Layout.addWidget(self.card_Member_5) + Layout.addWidget(self.card_Member_6) + Layout.addWidget(self.card_Member_7) + Layout.addWidget(self.card_Member_8) + Layout.addWidget(self.card_Member_9) + Layout.addWidget(self.card_Member_10) + + self.viewLayout.addLayout(Layout) + + def search_member(self) -> list: + """搜索所有脚本实例""" + + member_list_name = ["禁用"] + member_list_text = ["未启用"] + + if (Config.app_path / "config/MaaConfig").exists(): + for subdir in (Config.app_path / "config/MaaConfig").iterdir(): + if subdir.is_dir(): + member_list_name.append(subdir.name) + with (subdir / "config.json").open("r", encoding="utf-8") as f: + info = json.load(f) + if info["MaaSet"]["Name"] != "": + member_list_text.append( + f"{subdir.name} - {info["MaaSet"]["Name"]}" + ) + else: + member_list_text.append(subdir.name) + + return [member_list_name, member_list_text] + + class HistoryCard(HeaderCardWidget): + + def __init__(self, parent=None, name: str = None): + super().__init__(parent) + self.setTitle("历史运行记录") + + self.text = TextBrowser() + self.text.setMinimumHeight(300) + history = Config.get_history(name) + self.text.setPlainText(history["History"]) + + self.viewLayout.addWidget(self.text) diff --git a/app/ui/setting.py b/app/ui/setting.py new file mode 100644 index 0000000..1562ff8 --- /dev/null +++ b/app/ui/setting.py @@ -0,0 +1,731 @@ +# +# Copyright © <2024> + +# This file is part of AUTO_MAA. + +# AUTO_MAA is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published +# by the Free Software Foundation, either version 3 of the License, +# or (at your option) any later version. + +# AUTO_MAA is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty +# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See +# the GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with AUTO_MAA. If not, see . + +# DLmaster_361@163.com + +""" +AUTO_MAA +AUTO_MAA设置界面 +v4.2 +作者:DLmaster_361 +""" + +from loguru import logger +from PySide6.QtWidgets import ( + QWidget, + QApplication, + QVBoxLayout, + QVBoxLayout, +) +from qfluentwidgets import ( + ScrollArea, + FluentIcon, + MessageBox, + Dialog, + HyperlinkCard, + HeaderCardWidget, + SwitchSettingCard, + ExpandGroupSettingCard, + PushSettingCard, +) +import json +import subprocess +import time +import requests + +from app.core import Config, MainInfoBar +from app.services import Crypto, System +from app.utils import Updater +from .Widget import InputMessageBox, LineEditSettingCard + + +class Setting(QWidget): + + def __init__( + self, + parent=None, + ): + super().__init__(parent) + + self.setObjectName("设置") + + layout = QVBoxLayout() + + scrollArea = ScrollArea() + scrollArea.setWidgetResizable(True) + + content_widget = QWidget() + content_layout = QVBoxLayout(content_widget) + + self.function = FunctionSettingCard(self) + self.start = StartSettingCard(self) + self.ui = UiSettingCard(self) + self.notification = NotifySettingCard(self) + self.security = SecuritySettingCard(self) + self.updater = UpdaterSettingCard(self) + self.other = OtherSettingCard(self) + + self.function.card_IfAllowSleep.checkedChanged.connect(System.set_Sleep) + self.start.card_IfSelfStart.checkedChanged.connect(System.set_SelfStart) + self.security.card_changePASSWORD.clicked.connect(self.change_PASSWORD) + self.updater.card_CheckUpdate.clicked.connect(self.get_update) + self.other.card_Notice.clicked.connect(self.show_notice) + + content_layout.addWidget(self.function) + content_layout.addWidget(self.start) + content_layout.addWidget(self.ui) + content_layout.addWidget(self.notification) + content_layout.addWidget(self.security) + content_layout.addWidget(self.updater) + content_layout.addWidget(self.other) + + scrollArea.setWidget(content_widget) + + layout.addWidget(scrollArea) + + self.setLayout(layout) + + def check_PASSWORD(self) -> None: + """检查并配置管理密钥""" + + if Config.key_path.exists(): + return None + + while True: + + choice = InputMessageBox( + self.parent().parent().parent(), + "未检测到管理密钥,请设置您的管理密钥", + "管理密钥", + "密码", + ) + if choice.exec() and choice.input.text() != "": + Crypto.get_PASSWORD(choice.input.text()) + break + else: + choice = MessageBox( + "警告", + "您没有设置管理密钥,无法使用本软件,请先设置管理密钥", + self.parent().parent().parent(), + ) + choice.cancelButton.hide() + choice.buttonLayout.insertStretch(1) + if choice.exec(): + pass + + def change_PASSWORD(self) -> None: + """修改管理密钥""" + + # 获取用户信息 + Config.cur.execute("SELECT * FROM adminx WHERE True") + data = Config.cur.fetchall() + + if len(data) == 0: + + choice = MessageBox("验证通过", "当前无用户,验证自动通过", self) + choice.cancelButton.hide() + choice.buttonLayout.insertStretch(1) + + # 获取新的管理密钥 + if choice.exec(): + + while True: + + choice = InputMessageBox( + self, + "请输入新的管理密钥", + "新管理密钥", + "密码", + ) + if choice.exec() and choice.input.text() != "": + # 修改管理密钥 + Crypto.get_PASSWORD(choice.input.text()) + choice = MessageBox( + "操作成功", + "管理密钥修改成功", + self, + ) + choice.cancelButton.hide() + choice.buttonLayout.insertStretch(1) + if choice.exec(): + break + else: + choice = MessageBox( + "确认", + "您没有输入新的管理密钥,是否取消修改管理密钥?", + self, + ) + if choice.exec(): + break + + else: + # 验证管理密钥 + if_change = True + + while if_change: + + choice = InputMessageBox( + self, + "请输入旧的管理密钥", + "旧管理密钥", + "密码", + ) + if choice.exec() and choice.input.text() != "": + + # 验证旧管理密钥 + if Crypto.check_PASSWORD(choice.input.text()): + + PASSWORD_old = choice.input.text() + # 获取新的管理密钥 + while True: + + choice = InputMessageBox( + self, + "请输入新的管理密钥", + "新管理密钥", + "密码", + ) + if choice.exec() and choice.input.text() != "": + + # 修改管理密钥 + Crypto.change_PASSWORD( + data, PASSWORD_old, choice.input.text() + ) + choice = MessageBox( + "操作成功", + "管理密钥修改成功", + self, + ) + choice.cancelButton.hide() + choice.buttonLayout.insertStretch(1) + if choice.exec(): + if_change = False + break + + else: + + choice = MessageBox( + "确认", + "您没有输入新的管理密钥,是否取消修改管理密钥?", + self, + ) + if choice.exec(): + if_change = False + break + + else: + choice = MessageBox("错误", "管理密钥错误", self) + choice.cancelButton.hide() + choice.buttonLayout.insertStretch(1) + if choice.exec(): + pass + else: + choice = MessageBox( + "确认", + "您没有输入管理密钥,是否取消修改管理密钥?", + self, + ) + if choice.exec(): + break + + def get_update_info(self) -> str: + """检查主程序版本更新,返回更新信息""" + + # 从本地版本信息文件获取当前版本信息 + with Config.version_path.open(mode="r", encoding="utf-8") as f: + version_current = json.load(f) + main_version_current = list( + map(int, version_current["main_version"].split(".")) + ) + + # 从远程服务器获取最新版本信息 + for _ in range(3): + try: + response = requests.get( + "https://gitee.com/DLmaster_361/AUTO_MAA/raw/main/resources/version.json" + ) + version_remote = response.json() + break + except Exception as e: + err = e + time.sleep(0.1) + else: + return f"获取版本信息时出错:\n{err}" + + main_version_remote = list(map(int, version_remote["main_version"].split("."))) + + # 有版本更新 + if main_version_remote > main_version_current: + + main_version_info = f" 主程序:{version_text(main_version_current)} --> {version_text(main_version_remote)}\n" + + return f"发现新版本:\n{main_version_info} 更新说明:\n{version_remote['announcement'].replace("\n# ","\n !").replace("\n## ","\n - ").replace("\n- ","\n · ")}\n\n是否开始更新?\n\n 注意:主程序更新时AUTO_MAA将自动关闭" + + else: + return "已是最新版本~" + + def get_update(self, if_question: bool = True) -> None: + """检查版本更新,调起文件下载进程""" + + # 从本地版本信息文件获取当前版本信息 + with Config.version_path.open(mode="r", encoding="utf-8") as f: + version_current = json.load(f) + main_version_current = list( + map(int, version_current["main_version"].split(".")) + ) + updater_version_current = list( + map(int, version_current["updater_version"].split(".")) + ) + # 检查更新器是否存在 + if not (Config.app_path / "Updater.exe").exists(): + updater_version_current = [0, 0, 0, 0] + + # 从远程服务器获取最新版本信息 + for _ in range(3): + try: + response = requests.get( + "https://gitee.com/DLmaster_361/AUTO_MAA/raw/main/resources/version.json" + ) + version_remote = response.json() + break + except Exception as e: + err = e + time.sleep(0.1) + else: + choice = MessageBox( + "错误", + f"获取版本信息时出错:\n{err}", + self, + ) + choice.cancelButton.hide() + choice.buttonLayout.insertStretch(1) + if choice.exec(): + return None + + main_version_remote = list(map(int, version_remote["main_version"].split("."))) + updater_version_remote = list( + map(int, version_remote["updater_version"].split(".")) + ) + + # 有版本更新 + if (main_version_remote > main_version_current) or ( + updater_version_remote > updater_version_current + ): + + # 生成版本更新信息 + if main_version_remote > main_version_current: + main_version_info = f" 主程序:{version_text(main_version_current)} --> {version_text(main_version_remote)}\n" + else: + main_version_info = ( + f" 主程序:{version_text(main_version_current)}\n" + ) + if updater_version_remote > updater_version_current: + updater_version_info = f" 更新器:{version_text(updater_version_current)} --> {version_text(updater_version_remote)}\n" + else: + updater_version_info = ( + f" 更新器:{version_text(updater_version_current)}\n" + ) + + # 询问是否开始版本更新 + if if_question: + choice = MessageBox( + "版本更新", + f"发现新版本:\n{main_version_info}{updater_version_info} 更新说明:\n{version_remote['announcement'].replace("\n# ","\n !").replace("\n## ","\n - ").replace("\n- ","\n · ")}\n\n是否开始更新?\n\n 注意:主程序更新时AUTO_MAA将自动关闭", + self, + ) + if not choice.exec(): + return None + + # 更新更新器 + if updater_version_remote > updater_version_current: + # 创建更新进程 + self.updater = Updater( + Config.app_path, + "AUTO_MAA更新器", + main_version_remote, + updater_version_remote, + ) + # 完成更新器的更新后更新主程序 + if main_version_remote > main_version_current: + self.updater.update_process.accomplish.connect(self.update_main) + # 显示更新页面 + self.updater.ui.show() + + # 更新主程序 + elif main_version_remote > main_version_current: + self.update_main() + + # 无版本更新 + else: + MainInfoBar.push_info_bar("success", "更新检查", "已是最新版本~", 3000) + + def update_main(self) -> None: + """更新主程序""" + + subprocess.Popen( + str(Config.app_path / "Updater.exe"), + shell=True, + creationflags=subprocess.CREATE_NO_WINDOW, + ) + self.close() + QApplication.quit() + + def show_notice(self): + """显示公告""" + + # 从远程服务器获取最新版本信息 + for _ in range(3): + try: + response = requests.get( + "https://gitee.com/DLmaster_361/AUTO_MAA/raw/main/resources/version.json" + ) + version_remote = response.json() + break + except Exception as e: + err = e + time.sleep(0.1) + else: + logger.warning(f"获取最新公告时出错:\n{err}") + choice = Dialog( + "网络错误", + f"获取最新公告时出错:\n{err}", + self, + ) + choice.cancelButton.hide() + choice.buttonLayout.insertStretch(1) + if choice.exec(): + return None + + if "notice" in version_remote: + notice = version_remote["notice"] + else: + notice = "暂无公告~" + + choice = Dialog("公告", notice, self) + choice.cancelButton.hide() + choice.buttonLayout.insertStretch(1) + choice.exec() + + +class FunctionSettingCard(HeaderCardWidget): + + def __init__(self, parent=None): + super().__init__(parent) + + self.setTitle("功能") + + Layout = QVBoxLayout() + + self.card_IfAllowSleep = SwitchSettingCard( + icon=FluentIcon.PAGE_RIGHT, + title="启动时阻止系统休眠", + content="仅阻止电脑自动休眠,不会影响屏幕是否熄灭", + configItem=Config.global_config.function_IfAllowSleep, + ) + + self.card_IfSilence = self.SilenceSettingCard(self) + + # 添加各组到设置卡中 + Layout.addWidget(self.card_IfAllowSleep) + Layout.addWidget(self.card_IfSilence) + + self.viewLayout.addLayout(Layout) + + class SilenceSettingCard(ExpandGroupSettingCard): + + def __init__(self, parent=None): + super().__init__( + FluentIcon.SETTING, + "静默模式", + "将各代理窗口置于后台运行,减少对前台的干扰", + parent, + ) + + widget = QWidget() + Layout = QVBoxLayout(widget) + + self.card_IfSilence = SwitchSettingCard( + icon=FluentIcon.PAGE_RIGHT, + title="静默模式", + content="是否启用静默模式", + configItem=Config.global_config.function_IfSilence, + ) + + self.card_BossKey = LineEditSettingCard( + text="请输入安卓模拟器老版键", + icon=FluentIcon.PAGE_RIGHT, + title="模拟器老版键", + content="输入模拟器老版快捷键,以“+”分隔", + configItem=Config.global_config.function_BossKey, + ) + + Layout.addWidget(self.card_IfSilence) + Layout.addWidget(self.card_BossKey) + + # 调整内部布局 + self.viewLayout.setContentsMargins(0, 0, 0, 0) + self.viewLayout.setSpacing(0) + + self.addGroupWidget(widget) + + +class StartSettingCard(HeaderCardWidget): + + def __init__(self, parent=None): + super().__init__(parent) + + self.setTitle("启动") + + Layout = QVBoxLayout() + + self.card_IfSelfStart = SwitchSettingCard( + icon=FluentIcon.PAGE_RIGHT, + title="开机时自动启动", + content="将AUTO_MAA添加到开机启动项", + configItem=Config.global_config.start_IfSelfStart, + ) + + self.card_IfRunDirectly = SwitchSettingCard( + icon=FluentIcon.PAGE_RIGHT, + title="启动后直接运行", + content="启动AUTO_MAA后自动运行任务(暂不可用)", + configItem=Config.global_config.start_IfRunDirectly, + ) + + # 添加各组到设置卡中 + Layout.addWidget( + self.card_IfSelfStart, + ) + Layout.addWidget(self.card_IfRunDirectly) + + self.viewLayout.addLayout(Layout) + + +class UiSettingCard(HeaderCardWidget): + + def __init__(self, parent=None): + super().__init__(parent) + + self.setTitle("界面") + + Layout = QVBoxLayout() + + self.card_IfShowTray = SwitchSettingCard( + icon=FluentIcon.PAGE_RIGHT, + title="显示托盘图标", + content="常态显示托盘图标", + configItem=Config.global_config.ui_IfShowTray, + ) + + self.card_IfToTray = SwitchSettingCard( + icon=FluentIcon.PAGE_RIGHT, + title="最小化到托盘", + content="最小化时隐藏到托盘", + configItem=Config.global_config.ui_IfToTray, + ) + + # 添加各组到设置卡中 + Layout.addWidget(self.card_IfShowTray) + Layout.addWidget(self.card_IfToTray) + + self.viewLayout.addLayout(Layout) + + +class NotifySettingCard(HeaderCardWidget): + + def __init__(self, parent=None): + super().__init__(parent) + + self.setTitle("通知") + + Layout = QVBoxLayout() + + self.card_IfPushPlyer = SwitchSettingCard( + icon=FluentIcon.PAGE_RIGHT, + title="推送系统通知", + content="推送系统级通知,不会在通知中心停留", + configItem=Config.global_config.notify_IfPushPlyer, + ) + + self.card_SendMail = self.SendMailSettingCard(self) + + Layout.addWidget(self.card_IfPushPlyer) + Layout.addWidget(self.card_SendMail) + + self.viewLayout.addLayout(Layout) + + class SendMailSettingCard(ExpandGroupSettingCard): + + def __init__(self, parent=None): + super().__init__( + FluentIcon.SETTING, + "推送邮件通知", + "通过AUTO_MAA官方通知服务邮箱推送任务结果", + parent, + ) + + widget = QWidget() + Layout = QVBoxLayout(widget) + + self.card_IfSendMail = SwitchSettingCard( + icon=FluentIcon.PAGE_RIGHT, + title="推送邮件通知", + content="是否启用邮件通知功能", + configItem=Config.global_config.notify_IfSendMail, + ) + + self.MailAddress = LineEditSettingCard( + text="请输入邮箱地址", + icon=FluentIcon.PAGE_RIGHT, + title="邮箱地址", + content="接收通知的邮箱地址", + configItem=Config.global_config.notify_MailAddress, + ) + + self.card_IfSendErrorOnly = SwitchSettingCard( + icon=FluentIcon.PAGE_RIGHT, + title="仅推送异常信息", + content="仅在任务出现异常时推送通知", + configItem=Config.global_config.notify_IfSendErrorOnly, + ) + + Layout.addWidget(self.card_IfSendMail) + Layout.addWidget(self.MailAddress) + Layout.addWidget(self.card_IfSendErrorOnly) + + # 调整内部布局 + self.viewLayout.setContentsMargins(0, 0, 0, 0) + self.viewLayout.setSpacing(0) + + self.addGroupWidget(widget) + + +class SecuritySettingCard(HeaderCardWidget): + + def __init__(self, parent=None): + super().__init__(parent) + + self.setTitle("安全") + + Layout = QVBoxLayout() + + self.card_changePASSWORD = PushSettingCard( + text="修改", + icon=FluentIcon.VPN, + title="修改管理密钥", + content="修改用于解密用户密码的管理密钥", + ) + + Layout.addWidget(self.card_changePASSWORD) + + self.viewLayout.addLayout(Layout) + + +class UpdaterSettingCard(HeaderCardWidget): + + def __init__(self, parent=None): + super().__init__(parent) + + self.setTitle("更新") + + Layout = QVBoxLayout() + + self.card_IfAutoUpdate = SwitchSettingCard( + icon=FluentIcon.PAGE_RIGHT, + title="自动检查更新", + content="将在启动时自动检查AUTO_MAA是否有新版本", + configItem=Config.global_config.update_IfAutoUpdate, + ) + + self.card_CheckUpdate = PushSettingCard( + text="检查更新", + icon=FluentIcon.UPDATE, + title="获取最新版本", + content="检查AUTO_MAA是否有新版本", + ) + + Layout.addWidget(self.card_IfAutoUpdate) + Layout.addWidget(self.card_CheckUpdate) + + self.viewLayout.addLayout(Layout) + + +class OtherSettingCard(HeaderCardWidget): + + def __init__(self, parent=None): + super().__init__(parent) + + self.setTitle("其他") + + self.card_Notice = PushSettingCard( + text="查看", + icon=FluentIcon.PAGE_RIGHT, + title="公告", + content="查看AUTO_MAA的最新公告", + ) + self.card_Association = self.AssociationSettingCard() + + Layout = QVBoxLayout() + Layout.addWidget(self.card_Notice) + Layout.addWidget(self.card_Association) + self.viewLayout.addLayout(Layout) + + class AssociationSettingCard(ExpandGroupSettingCard): + + def __init__(self, parent=None): + super().__init__( + FluentIcon.SETTING, + "AUTO_MAA官方社群", + "加入AUTO_MAA官方社群,获取更多帮助", + parent, + ) + + self.card_GitHubRepository = HyperlinkCard( + url="https://github.com/DLmaster361/AUTO_MAA", + text="访问GitHub仓库", + icon=FluentIcon.GITHUB, + title="GitHub", + content="查看AUTO_MAA的源代码,提交问题和建议,欢迎参与开发", + ) + self.card_QQGroup = HyperlinkCard( + url="https://qm.qq.com/q/bd9fISNoME", + text="加入官方QQ交流群", + icon=FluentIcon.CHAT, + title="QQ群", + content="与AUTO_MAA开发者和用户交流", + ) + + widget = QWidget() + Layout = QVBoxLayout(widget) + Layout.addWidget(self.card_GitHubRepository) + Layout.addWidget(self.card_QQGroup) + self.viewLayout.setContentsMargins(0, 0, 0, 0) + self.viewLayout.setSpacing(0) + self.addGroupWidget(widget) + + +def version_text(version_numb: list) -> str: + """将版本号列表转为可读的文本信息""" + + if version_numb[3] == 0: + version = f"v{'.'.join(str(_) for _ in version_numb[0:3])}" + else: + version = ( + f"v{'.'.join(str(_) for _ in version_numb[0:3])}-beta.{version_numb[3]}" + ) + return version diff --git a/app/utils/Updater.py b/app/utils/Updater.py index 86a335f..364288f 100644 --- a/app/utils/Updater.py +++ b/app/utils/Updater.py @@ -25,7 +25,6 @@ v1.1 作者:DLmaster_361 """ -import os import sys import json import zipfile @@ -39,11 +38,21 @@ from PySide6.QtWidgets import ( QDialog, QVBoxLayout, ) -from qfluentwidgets import ProgressBar, BodyLabel +from qfluentwidgets import ProgressBar, IndeterminateProgressBar, BodyLabel from PySide6.QtGui import QIcon from PySide6.QtCore import QObject, QThread, Signal -from .version import version_text + +def version_text(version_numb: list) -> str: + """将版本号列表转为可读的文本信息""" + + if version_numb[3] == 0: + version = f"v{'.'.join(str(_) for _ in version_numb[0:3])}" + else: + version = ( + f"v{'.'.join(str(_) for _ in version_numb[0:3])}-beta.{version_numb[3]}" + ) + return version class UpdateProcess(QThread): @@ -67,10 +76,8 @@ class UpdateProcess(QThread): def run(self) -> None: # 清理可能存在的临时文件 - try: - os.remove(self.download_path) - except FileNotFoundError: - pass + if self.download_path.exists(): + self.download_path.unlink() self.info.emit("正在获取下载链接") url_list = self.get_download_url() @@ -158,7 +165,7 @@ class UpdateProcess(QThread): self.info.emit("正在删除临时文件") self.progress.emit(0, 0, 0) - os.remove(self.download_path) + self.download_path.unlink() self.info.emit(f"{self.name}更新成功!") self.progress.emit(0, 100, 100) @@ -178,7 +185,7 @@ class UpdateProcess(QThread): elif self.name == "AUTO_MAA主程序": version_info["main_version"] = ".".join(map(str, self.main_version)) with open(self.version_path, "w", encoding="utf-8") as f: - json.dump(version_info, f, indent=4) + json.dump(version_info, f, ensure_ascii=False, indent=4) # 主程序更新完成后打开AUTO_MAA if self.name == "AUTO_MAA主程序": @@ -262,14 +269,19 @@ class Updater(QObject): ) # 创建垂直布局 - self.Layout_v = QVBoxLayout(self.ui) + self.Layout = QVBoxLayout(self.ui) self.info = BodyLabel("正在初始化", self.ui) - self.Layout_v.addWidget(self.info) + self.progress_1 = IndeterminateProgressBar(self.ui) + self.progress_2 = ProgressBar(self.ui) - self.progress = ProgressBar(self.ui) - self.progress.setRange(0, 0) - self.Layout_v.addWidget(self.progress) + self.update_progress(0, 0, 0) + + self.Layout.addWidget(self.info) + self.Layout.addStretch(1) + self.Layout.addWidget(self.progress_1) + self.Layout.addWidget(self.progress_2) + self.Layout.addStretch(1) self.update_process = UpdateProcess( app_path, name, main_version, updater_version @@ -284,8 +296,14 @@ class Updater(QObject): self.info.setText(text) def update_progress(self, begin: int, end: int, current: int) -> None: - self.progress.setRange(begin, end) - self.progress.setValue(current) + if begin == 0 and end == 0: + self.progress_2.setVisible(False) + self.progress_1.setVisible(True) + else: + self.progress_1.setVisible(False) + self.progress_2.setVisible(True) + self.progress_2.setRange(begin, end) + self.progress_2.setValue(current) class AUTO_MAA_Updater(QApplication): diff --git a/app/utils/__init__.py b/app/utils/__init__.py index 2f1ba20..3cbc288 100644 --- a/app/utils/__init__.py +++ b/app/utils/__init__.py @@ -30,6 +30,5 @@ __author__ = "DLmaster361 " __license__ = "GPL-3.0 license" from .Updater import Updater -from .version import version_text -__all__ = ["Updater", "version_text"] +__all__ = ["Updater"] diff --git a/app/utils/package.py b/app/utils/package.py index 5120980..7c17754 100644 --- a/app/utils/package.py +++ b/app/utils/package.py @@ -26,17 +26,27 @@ v4.2 """ import os +import sys import json import shutil -import subprocess from pathlib import Path -from app import version_text + +def version_text(version_numb: list) -> str: + """将版本号列表转为可读的文本信息""" + + if version_numb[3] == 0: + version = f"v{'.'.join(str(_) for _ in version_numb[0:3])}" + else: + version = ( + f"v{'.'.join(str(_) for _ in version_numb[0:3])}-beta.{version_numb[3]}" + ) + return version if __name__ == "__main__": - root_path = Path.cwd() + root_path = Path(sys.argv[0]).resolve().parent with (root_path / "resources/version.json").open(mode="r", encoding="utf-8") as f: version = json.load(f) @@ -46,24 +56,20 @@ if __name__ == "__main__": print("Packaging AUTO_MAA main program ...") - result = subprocess.run( - f"powershell -Command nuitka --standalone --onefile --mingw64" - f" --enable-plugins=pyside6 --windows-console-mode=disable" - f" --windows-icon-from-ico=resources\\icons\\AUTO_MAA.ico" - f" --company-name='AUTO_MAA Team' --product-name=AUTO_MAA" + os.system( + "powershell -Command python -m nuitka --standalone --onefile --mingw64" + " --enable-plugins=pyside6 --windows-console-mode=disable" + " --onefile-tempdir-spec='{TEMP}\\AUTO_MAA'" + " --windows-icon-from-ico=resources\\icons\\AUTO_MAA.ico" + " --company-name='AUTO_MAA Team' --product-name=AUTO_MAA" f" --file-version={version["main_version"]}" f" --product-version={version["main_version"]}" - f" --file-description='AUTO_MAA Component'" - f" --copyright='Copyright © 2024 DLmaster361'" - f" --assume-yes-for-downloads --output-filename=AUTO_MAA" - f" --remove-output main.py", - shell=True, - capture_output=True, - text=True, + " --file-description='AUTO_MAA Component'" + " --copyright='Copyright © 2024 DLmaster361'" + " --assume-yes-for-downloads --output-filename=AUTO_MAA" + " --remove-output main.py" ) - print(result.stdout) - print(result.stderr) print("AUTO_MAA main program packaging completed !") shutil.copy(root_path / "app/utils/Updater.py", root_path) @@ -79,27 +85,23 @@ if __name__ == "__main__": print("Packaging AUTO_MAA update program ...") - result = subprocess.run( - f"powershell -Command nuitka --standalone --onefile --mingw64" - f" --enable-plugins=pyside6 --windows-console-mode=disable" - f" --windows-icon-from-ico=resources\\icons\\AUTO_MAA_Updater.ico" - f" --company-name='AUTO_MAA Team' --product-name=AUTO_MAA" + os.system( + "powershell -Command python -m nuitka --standalone --onefile --mingw64" + " --enable-plugins=pyside6 --windows-console-mode=disable" + " --onefile-tempdir-spec='{TEMP}\\AUTO_MAA_Updater'" + " --windows-icon-from-ico=resources\\icons\\AUTO_MAA_Updater.ico" + " --company-name='AUTO_MAA Team' --product-name=AUTO_MAA" f" --file-version={version["updater_version"]}" f" --product-version={version["main_version"]}" - f" --file-description='AUTO_MAA Component'" - f" --copyright='Copyright © 2024 DLmaster361'" - f" --assume-yes-for-downloads --output-filename=Updater" - f" --remove-output Updater.py", - shell=True, - capture_output=True, - text=True, + " --file-description='AUTO_MAA Component'" + " --copyright='Copyright © 2024 DLmaster361'" + " --assume-yes-for-downloads --output-filename=Updater" + " --remove-output Updater.py" ) - print(result.stdout) - print(result.stderr) print("AUTO_MAA update program packaging completed !") - os.remove(root_path / "Updater.py") + (root_path / "Updater.py").unlink() (root_path / "version_info.txt").write_text( f"{version_text(main_version_numb)}\n{version_text(updater_version_numb)}{version["announcement"]}", diff --git a/main.py b/main.py index f185b0d..6e557e4 100644 --- a/main.py +++ b/main.py @@ -25,18 +25,30 @@ v4.2 作者:DLmaster_361 """ +from loguru import logger from PySide6.QtWidgets import QApplication +from PySide6.QtCore import Qt +from qfluentwidgets import FluentTranslator import sys -from app import AppConfig, Notification, CryptoHandler, AUTO_MAA + +@logger.catch +def main(): + + application = QApplication(sys.argv) + QApplication.setAttribute(Qt.AA_DontCreateNativeWidgetSiblings) + + translator = FluentTranslator() + application.installTranslator(translator) + + from app.ui.main_window import AUTO_MAA + + window = AUTO_MAA() + window.show_ui("显示主窗口") + window.start_up_task() + sys.exit(application.exec()) + if __name__ == "__main__": - config = AppConfig() - notify = Notification(config) - crypto = CryptoHandler(config) - - application = QApplication(sys.argv) - window = AUTO_MAA(config=config, notify=notify, crypto=crypto) - window.main.check_PASSWORD() - sys.exit(application.exec()) + main() diff --git a/requirements.txt b/requirements.txt index b66d50a..7d543dd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ +loguru plyer PySide6 PySide6-Fluent-Widgets[full] diff --git a/resources/gui/main.ui b/resources/gui/main.ui deleted file mode 100644 index eb11ac9..0000000 --- a/resources/gui/main.ui +++ /dev/null @@ -1,1545 +0,0 @@ - - - AUTO_MAA - - - - 0 - 0 - 1218 - 718 - - - - AUTO_MAA - - - - - - 0 - - - - true - - - 用户管理 - - - - - - QFrame::Shape::StyledPanel - - - QFrame::Shadow::Raised - - - - - - - - 新建 - - - - - - - 删除 - - - - - - - 转为 - - - - - - - Qt::Orientation::Horizontal - - - - 40 - 20 - - - - - - - - 显示密码 - - - - - - - 刷新 - - - - - - - - - 0 - - - - - 0 - 0 - 1156 - 542 - - - - 简洁 - - - - - - - 用户名 - - - - - 账号ID - - - - - 服务器 - - - - - 代理天数 - - - - - 状态 - - - - - 执行情况 - - - - - 关卡 - - - - - 备选关卡-1 - - - - - 备选关卡-2 - - - - - 剿灭 - - - - - 自定义基建 - - - - - 密码 - - - - - 备注 - - - - - - - - - - 0 - 0 - 98 - 74 - - - - 高级 - - - - - - - 用户名 - - - - - 代理天数 - - - - - 状态 - - - - - 执行情况 - - - - - 日常 - - - - - 剿灭 - - - - - 密码 - - - - - 备注 - - - - - - - - - - - - - - - - 执行 - - - - - - 定时执行 - - - - - - - - QFrame::Shape::StyledPanel - - - QFrame::Shadow::Raised - - - - - - true - - - - 20 - 20 - - - - - 20 - 20 - - - - - - - - - - - - - - - - - QFrame::Shape::StyledPanel - - - QFrame::Shadow::Raised - - - - - - true - - - - 20 - 20 - - - - - 20 - 20 - - - - - - - - - - - - - - - - - QFrame::Shape::StyledPanel - - - QFrame::Shadow::Raised - - - - - - true - - - - 20 - 20 - - - - - 20 - 20 - - - - - - - - - - - - - - - - - QFrame::Shape::StyledPanel - - - QFrame::Shadow::Raised - - - - - - true - - - - 20 - 20 - - - - - 20 - 20 - - - - - - - - - - - - - - - - - QFrame::Shape::StyledPanel - - - QFrame::Shadow::Raised - - - - - - true - - - - 20 - 20 - - - - - 20 - 20 - - - - - - - - - - - - - - - - - QFrame::Shape::StyledPanel - - - QFrame::Shadow::Raised - - - - - - true - - - - 20 - 20 - - - - - 20 - 20 - - - - - - - - - - - - - - - - - QFrame::Shape::StyledPanel - - - QFrame::Shadow::Raised - - - - - - true - - - - 20 - 20 - - - - - 20 - 20 - - - - - - - - - - - - - - - - - QFrame::Shape::StyledPanel - - - QFrame::Shadow::Raised - - - - - - true - - - - 20 - 20 - - - - - 20 - 20 - - - - - - - - - - - - - - - - - QFrame::Shape::StyledPanel - - - QFrame::Shadow::Raised - - - - - - true - - - - 20 - 20 - - - - - 20 - 20 - - - - - - - - - - - - - - - - - QFrame::Shape::StyledPanel - - - QFrame::Shadow::Raised - - - - - - true - - - - 20 - 20 - - - - - 20 - 20 - - - - - - - - - - - - - - - - - - - - - - 调度台 - - - - - - - - QFrame::Shape::StyledPanel - - - QFrame::Shadow::Raised - - - - - - - - 调度器 - - - - - - - Qt::Orientation::Horizontal - - - - 40 - 20 - - - - - - - - 开始排查 - - - - - - - 立即执行 - - - - - - - - - - - - QFrame::Shape::StyledPanel - - - QFrame::Shadow::Raised - - - - - - 代理中 - - - - - - - - - - - - - QFrame::Shape::StyledPanel - - - QFrame::Shadow::Raised - - - - - - 等待中 - - - - - - - - - - - - - QFrame::Shape::StyledPanel - - - QFrame::Shadow::Raised - - - - - - 已完成 - - - - - - - - - - - - - QFrame::Shape::StyledPanel - - - QFrame::Shadow::Raised - - - - - - 异常 - - - - - - - - - - - - - - - QFrame::Shape::StyledPanel - - - QFrame::Shadow::Raised - - - - - - 日志 - - - - - - - - - - - - - - - - - 设置 - - - - - - MAA设置 - - - - - - QFrame::Shape::StyledPanel - - - QFrame::Shadow::Raised - - - - - - MAA路径 - - - - - - - - - - 浏览 - - - - - - - Qt::Orientation::Horizontal - - - - 40 - 20 - - - - - - - - 设置MAA - - - - - - - - - - - - - 执行限制 - - - - - - QFrame::Shape::StyledPanel - - - QFrame::Shadow::Raised - - - - - - 日常限制 - - - - - - - Qt::Orientation::Horizontal - - - - 40 - 20 - - - - - - - - - 140 - 30 - - - - 1 - - - 1024 - - - - - - - 分钟 - - - - - - - - - - QFrame::Shape::StyledPanel - - - QFrame::Shadow::Raised - - - - - - 剿灭限制 - - - - - - - Qt::Orientation::Horizontal - - - - 40 - 20 - - - - - - - - - 140 - 30 - - - - 1 - - - 1024 - - - - - - - 分钟 - - - - - - - - - - QFrame::Shape::StyledPanel - - - QFrame::Shadow::Raised - - - - - - 运行失败重试次数上限 - - - - - - - Qt::Orientation::Horizontal - - - - 40 - 20 - - - - - - - - - 140 - 30 - - - - 1 - - - 1024 - - - - - - - - - - - - - - - - - - - - AUTO_MAA设置 - - - - - - Qt::Orientation::Horizontal - - - - 40 - 20 - - - - - - - - QFrame::Shape::StyledPanel - - - QFrame::Shadow::Raised - - - - - - 后台静默代理 - - - - - - - Qt::Orientation::Horizontal - - - - 40 - 20 - - - - - - - - 安卓模拟器老板键 - - - - - - - - - - Qt::Orientation::Horizontal - - - - 231 - 20 - - - - - - - - QFrame::Shape::StyledPanel - - - QFrame::Shadow::Raised - - - - - - 通过邮件通知结果 - - - - - - - Qt::Orientation::Horizontal - - - - 40 - 20 - - - - - - - - 收信邮箱地址 - - - - - - - 仅推送异常信息 - - - - - - - - - - QFrame::Shape::StyledPanel - - - QFrame::Shadow::Raised - - - - - - 最小化到托盘 - - - - - - - Qt::Orientation::Horizontal - - - - 149 - 20 - - - - - - - - - - - QFrame::Shape::StyledPanel - - - QFrame::Shadow::Raised - - - - - - 启动AUTO_MAA后直接代理 - - - - - - - Qt::Orientation::Horizontal - - - - 40 - 20 - - - - - - - - - - - Qt::Orientation::Horizontal - - - - 32 - 20 - - - - - - - - QFrame::Shape::StyledPanel - - - QFrame::Shadow::Raised - - - - - - 开机自动启动AUTO_MAA - - - - - - - Qt::Orientation::Horizontal - - - - 40 - 20 - - - - - - - - - - - QFrame::Shape::StyledPanel - - - QFrame::Shadow::Raised - - - - - - AUTO_MAA启动时禁止电脑休眠 - - - - - - - Qt::Orientation::Horizontal - - - - 40 - 20 - - - - - - - - - - - Qt::Orientation::Horizontal - - - - 311 - 20 - - - - - - - - Qt::Orientation::Horizontal - - - - 40 - 20 - - - - - - - - QFrame::Shape::StyledPanel - - - QFrame::Shadow::Raised - - - - - - Qt::Orientation::Horizontal - - - - 40 - 20 - - - - - - - - 修改管理密钥 - - - - - - - Qt::Orientation::Horizontal - - - - 40 - 20 - - - - - - - - - - - Qt::Orientation::Horizontal - - - - 40 - 20 - - - - - - - - QFrame::Shape::StyledPanel - - - QFrame::Shadow::Raised - - - - - - Qt::Orientation::Horizontal - - - - 40 - 20 - - - - - - - - 检查版本更新 - - - - - - - Qt::Orientation::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - - tips - - - - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> -p, li { white-space: pre-wrap; } -hr { height: 1px; border-width: 0; } -li.unchecked::marker { content: "\2610"; } -li.checked::marker { content: "\2612"; } -</style></head><body style=" font-family:'Microsoft YaHei UI'; font-size:9pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">致用户:</p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> 这是AUTO_MAA_v4.2.0-beta.1,项目初步进行界面美化。希望大家喜欢这个新年礼物!</p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> 使用本程序前,请先仔细阅读README.md。</p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> 对于B服用户,由于我们无权替您同意用户协议,若出现用户协议弹窗,您需要自行完成确认,否则可能造成MAA卡死,代理任务失败。</p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> 由于用户与项目贡献者的稀缺,我们无法确保正式版足够完善,还望谅解。遇到问题时,请先更新到最新版本试试。若仍无法解决,请在官方仓库发布Issues求助。</p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> 官方仓库:<a href="https://github.com/DLmaster361/AUTO_MAA/"><span style=" text-decoration: underline; color:#004194;">DLmaster361/AUTO_MAA</span></a></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> 官方QQ交流群:<a href="https://qm.qq.com/cgi-bin/qm/qr?k=EET-OL_o52KPlDLEmbzaNkKUXuyQ4WZY&amp;jump_from=webapi&amp;authKey=6NxGwEu9JAOLHqfdEmNfrZy4tUvC/3ar2j5+Go7Hgf3j+ntAK1VS6SUOLOjYVKTt"><span style=" text-decoration: underline; color:#004194;">957750551</span></a></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> 友情推荐项目:<a href="https://git.zhaozuohong.vip/mower-ng"><span style=" text-decoration: underline; color:#004194;">zhaozuohong/mower-ng</span></a> 、 <a href="https://github.com/AegirTech/ArkLights"><span style=" text-decoration: underline; color:#004194;">AegirTech/ArkLights</span></a></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> -<p align="right" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">DLmaster</p></body></html> - - - - - - - 注意:在设置页执行的所有更改,不会对正在执行的任务生效;正在执行任务时,用户管理页无法编辑。 - - - - - - - - - - Qt::Orientation::Vertical - - - - 20 - 40 - - - - - - - - - - - - - PushButton - QPushButton -
qfluentwidgets
-
- - TimePicker - QTimeEdit -
qfluentwidgets
-
- - TableWidget - QTableWidget -
qfluentwidgets
-
- - LineEdit - QLineEdit -
qfluentwidgets
-
- - SpinBox - QSpinBox -
qfluentwidgets
-
- - CheckBox - QCheckBox -
qfluentwidgets
-
- - TextBrowser - QTextBrowser -
qfluentwidgets
-
- - BodyLabel - QLabel -
qfluentwidgets
-
-
- - -
diff --git a/resources/version.json b/resources/version.json index 6d64931..9acd203 100644 --- a/resources/version.json +++ b/resources/version.json @@ -1,8 +1,9 @@ { - "main_version": "4.2.0.0", - "updater_version": "1.1.0.0", - "announcement": "\n## 新增功能\n- 提供完整打包代码\n## 修复BUG\n- 同步MAA`v5.11.1`的字段修改\n- 清除自动化中无效的整合流程\n## 程序优化\n- 调整项目结构,模块化各功能组件\n- 改用`nuitka`编译,压缩软件体积,提升运行速度", - "proxy_list":[ + "main_version": "4.2.1.1", + "updater_version": "1.1.0.1", + "announcement": "\n## 新增功能\n- 调度队列上线,支持MAA多开\n## 修复BUG\n- 添加了一堆BUG(确信)\n## 程序优化\n- 界面重构,引入`QFluentWidgets`美化界面", + "notice": "公告系统测试中~", + "proxy_list": [ "", "https://gitproxy.click/", "https://cdn.moran233.xyz/",