Compare commits
29 Commits
v4.3.10
...
v4.4.1-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4cbd921ab6 | ||
|
|
2c915161d5 | ||
|
|
75b06ca770 | ||
|
|
c3468a3387 | ||
|
|
a2f4adb647 | ||
|
|
403f69df8b | ||
|
|
12cf10f97a | ||
|
|
6084befe2c | ||
|
|
1aa99ea613 | ||
|
|
d539c0f808 | ||
|
|
bc509806fb | ||
|
|
c52820550f | ||
|
|
98b30f90a1 | ||
|
|
4efbafc174 | ||
|
|
6d3fda50d3 | ||
|
|
70b936012f | ||
|
|
54917fbe6d | ||
|
|
abeb9f054d | ||
|
|
c6d6c5fb2a | ||
|
|
5b0d7f0012 | ||
|
|
d9043aab0a | ||
|
|
5c6a20be4e | ||
|
|
1c0a65957d | ||
|
|
7c315624b1 | ||
|
|
0572caa528 | ||
|
|
4233040585 | ||
|
|
c27dc8e380 | ||
|
|
e746756e56 | ||
|
|
1829d1cd0b |
1
.github/workflows/build-app.yml
vendored
1
.github/workflows/build-app.yml
vendored
@@ -84,6 +84,7 @@ jobs:
|
||||
onefile-tempdir-spec: "{TEMP}/AUTO_MAA"
|
||||
windows-console-mode: attach
|
||||
windows-icon-from-ico: resources/icons/AUTO_MAA.ico
|
||||
windows-uac-admin: true
|
||||
company-name: AUTO_MAA Team
|
||||
product-name: AUTO_MAA
|
||||
file-version: ${{ steps.get_version.outputs.main_version }}
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -3,6 +3,7 @@ config/
|
||||
data/
|
||||
debug/
|
||||
history/
|
||||
script/
|
||||
resources/notice.json
|
||||
resources/theme_image.json
|
||||
resources/images/Home/BannerTheme.jpg
|
||||
@@ -21,7 +21,7 @@
|
||||
"""
|
||||
AUTO_MAA
|
||||
AUTO_MAA主程序包
|
||||
v4.3
|
||||
v4.4
|
||||
作者:DLmaster_361
|
||||
"""
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"""
|
||||
AUTO_MAA
|
||||
AUTO_MAA核心组件包
|
||||
v4.3
|
||||
v4.4
|
||||
作者:DLmaster_361
|
||||
"""
|
||||
|
||||
@@ -29,7 +29,15 @@ __version__ = "4.2.0"
|
||||
__author__ = "DLmaster361 <DLmaster_361@163.com>"
|
||||
__license__ = "GPL-3.0 license"
|
||||
|
||||
from .config import QueueConfig, MaaConfig, MaaUserConfig, MaaPlanConfig, Config
|
||||
from .config import (
|
||||
QueueConfig,
|
||||
MaaConfig,
|
||||
MaaUserConfig,
|
||||
MaaPlanConfig,
|
||||
GeneralConfig,
|
||||
GeneralSubConfig,
|
||||
Config,
|
||||
)
|
||||
from .main_info_bar import MainInfoBar
|
||||
from .network import Network
|
||||
from .sound_player import SoundPlayer
|
||||
@@ -42,6 +50,8 @@ __all__ = [
|
||||
"MaaConfig",
|
||||
"MaaUserConfig",
|
||||
"MaaPlanConfig",
|
||||
"GeneralConfig",
|
||||
"GeneralSubConfig",
|
||||
"MainInfoBar",
|
||||
"Network",
|
||||
"SoundPlayer",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -21,7 +21,7 @@
|
||||
"""
|
||||
AUTO_MAA
|
||||
AUTO_MAA信息通知栏
|
||||
v4.3
|
||||
v4.4
|
||||
作者:DLmaster_361
|
||||
"""
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"""
|
||||
AUTO_MAA
|
||||
AUTO_MAA网络请求线程
|
||||
v4.3
|
||||
v4.4
|
||||
作者:DLmaster_361
|
||||
"""
|
||||
|
||||
@@ -30,6 +30,7 @@ from PySide6.QtCore import QObject, QThread, QEventLoop
|
||||
import re
|
||||
import time
|
||||
import requests
|
||||
import truststore
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
@@ -51,12 +52,21 @@ class NetworkThread(QThread):
|
||||
self.url = url
|
||||
self.path = path
|
||||
|
||||
from .config import Config
|
||||
|
||||
self.proxies = {
|
||||
"http": Config.get(Config.update_ProxyAddress),
|
||||
"https": Config.get(Config.update_ProxyAddress),
|
||||
}
|
||||
|
||||
self.status_code = None
|
||||
self.response_json = None
|
||||
self.error_message = None
|
||||
|
||||
self.loop = QEventLoop()
|
||||
|
||||
truststore.inject_into_ssl()
|
||||
|
||||
@logger.catch
|
||||
def run(self) -> None:
|
||||
"""运行网络请求线程"""
|
||||
@@ -73,7 +83,7 @@ class NetworkThread(QThread):
|
||||
|
||||
for _ in range(self.max_retries):
|
||||
try:
|
||||
response = requests.get(url, timeout=self.timeout)
|
||||
response = requests.get(url, timeout=self.timeout, proxies=self.proxies)
|
||||
self.status_code = response.status_code
|
||||
self.response_json = response.json()
|
||||
self.error_message = None
|
||||
@@ -92,7 +102,7 @@ class NetworkThread(QThread):
|
||||
response = None
|
||||
|
||||
try:
|
||||
response = requests.get(url, timeout=10)
|
||||
response = requests.get(url, timeout=10, proxies=self.proxies)
|
||||
if response.status_code == 200:
|
||||
with open(path, "wb") as file:
|
||||
file.write(response.content)
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"""
|
||||
AUTO_MAA
|
||||
AUTO_MAA音效播放器
|
||||
v4.3
|
||||
v4.4
|
||||
作者:DLmaster_361
|
||||
"""
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"""
|
||||
AUTO_MAA
|
||||
AUTO_MAA业务调度器
|
||||
v4.3
|
||||
v4.4
|
||||
作者:DLmaster_361
|
||||
"""
|
||||
|
||||
@@ -36,7 +36,7 @@ from .config import Config
|
||||
from .main_info_bar import MainInfoBar
|
||||
from .network import Network
|
||||
from .sound_player import SoundPlayer
|
||||
from app.models import MaaManager
|
||||
from app.models import MaaManager, GeneralManager
|
||||
from app.services import System
|
||||
|
||||
|
||||
@@ -48,7 +48,8 @@ class Task(QThread):
|
||||
play_sound = Signal(str)
|
||||
question = Signal(str, str)
|
||||
question_response = Signal(bool)
|
||||
update_user_info = Signal(str, dict)
|
||||
update_maa_user_info = Signal(str, dict)
|
||||
update_general_sub_info = Signal(str, dict)
|
||||
create_task_list = Signal(list)
|
||||
create_user_list = Signal(list)
|
||||
update_task_list = Signal(list)
|
||||
@@ -89,7 +90,31 @@ class Task(QThread):
|
||||
self.task.play_sound.connect(self.play_sound.emit)
|
||||
self.task.accomplish.connect(lambda: self.accomplish.emit([]))
|
||||
|
||||
self.task.run()
|
||||
try:
|
||||
self.task.run()
|
||||
except Exception as e:
|
||||
logger.exception(f"任务异常:{self.name},错误信息:{e}")
|
||||
self.push_info_bar.emit("error", "任务异常", self.name, -1)
|
||||
|
||||
elif self.mode == "设置通用脚本":
|
||||
|
||||
logger.info(f"任务开始:设置{self.name}")
|
||||
self.push_info_bar.emit("info", "设置通用脚本", self.name, 3000)
|
||||
|
||||
self.task = GeneralManager(
|
||||
self.mode,
|
||||
Config.member_dict[self.name],
|
||||
self.info["SetSubInfo"]["Path"],
|
||||
)
|
||||
self.task.push_info_bar.connect(self.push_info_bar.emit)
|
||||
self.task.play_sound.connect(self.play_sound.emit)
|
||||
self.task.accomplish.connect(lambda: self.accomplish.emit([]))
|
||||
|
||||
try:
|
||||
self.task.run()
|
||||
except Exception as e:
|
||||
logger.exception(f"任务异常:{self.name},错误信息:{e}")
|
||||
self.push_info_bar.emit("error", "任务异常", self.name, -1)
|
||||
|
||||
else:
|
||||
|
||||
@@ -97,11 +122,8 @@ class Task(QThread):
|
||||
[
|
||||
(
|
||||
value
|
||||
if Config.member_dict[value]["Config"].get(
|
||||
Config.member_dict[value]["Config"].MaaSet_Name
|
||||
)
|
||||
== ""
|
||||
else f"{value} - {Config.member_dict[value]["Config"].get(Config.member_dict[value]["Config"].MaaSet_Name)}"
|
||||
if Config.member_dict[value]["Config"].get_name() == ""
|
||||
else f"{value} - {Config.member_dict[value]["Config"].get_name()}"
|
||||
),
|
||||
"等待",
|
||||
value,
|
||||
@@ -150,24 +172,60 @@ class Task(QThread):
|
||||
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(self.update_user_info.emit)
|
||||
self.task.update_user_info.connect(self.update_maa_user_info.emit)
|
||||
self.task.accomplish.connect(
|
||||
lambda log: self.task_accomplish(task[2], log)
|
||||
)
|
||||
|
||||
elif Config.member_dict[task[2]]["Type"] == "General":
|
||||
|
||||
self.task = GeneralManager(
|
||||
self.mode[0:4],
|
||||
Config.member_dict[task[2]],
|
||||
)
|
||||
|
||||
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.play_sound.connect(self.play_sound.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_sub_info.connect(self.update_general_sub_info.emit)
|
||||
self.task.accomplish.connect(
|
||||
lambda log: self.task_accomplish(task[2], log)
|
||||
)
|
||||
|
||||
try:
|
||||
self.task.run()
|
||||
|
||||
Config.running_list.remove(task[2])
|
||||
task[1] = "完成"
|
||||
self.update_task_list.emit(self.task_list)
|
||||
logger.info(f"任务完成:{task[0]}")
|
||||
self.push_info_bar.emit("info", "任务完成", task[0], 3000)
|
||||
|
||||
task[1] = "完成"
|
||||
self.update_task_list.emit(self.task_list)
|
||||
logger.info(f"任务完成:{task[0]}")
|
||||
self.push_info_bar.emit("info", "任务完成", task[0], 3000)
|
||||
except Exception as e:
|
||||
|
||||
self.task_accomplish(
|
||||
task[2],
|
||||
{
|
||||
"Time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||||
"History": f"任务异常,异常简报:{e}",
|
||||
},
|
||||
)
|
||||
|
||||
task[1] = "异常"
|
||||
self.update_task_list.emit(self.task_list)
|
||||
logger.exception(f"任务异常:{task[0]},错误信息:{e}")
|
||||
self.push_info_bar.emit("error", "任务异常", task[0], -1)
|
||||
|
||||
Config.running_list.remove(task[2])
|
||||
|
||||
self.accomplish.emit(self.logs)
|
||||
|
||||
def task_accomplish(self, name: str, log: dict):
|
||||
"""保存保存任务结果"""
|
||||
"""保存任务结果"""
|
||||
|
||||
self.logs.append([name, log])
|
||||
self.task.deleteLater()
|
||||
@@ -207,7 +265,10 @@ class _TaskManager(QObject):
|
||||
)
|
||||
self.task_dict[name].push_info_bar.connect(MainInfoBar.push_info_bar)
|
||||
self.task_dict[name].play_sound.connect(SoundPlayer.play)
|
||||
self.task_dict[name].update_user_info.connect(Config.change_user_info)
|
||||
self.task_dict[name].update_maa_user_info.connect(Config.change_maa_user_info)
|
||||
self.task_dict[name].update_general_sub_info.connect(
|
||||
Config.change_general_sub_info
|
||||
)
|
||||
self.task_dict[name].accomplish.connect(
|
||||
lambda logs: self.remove_task(mode, name, logs)
|
||||
)
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"""
|
||||
AUTO_MAA
|
||||
AUTO_MAA主业务定时器
|
||||
v4.3
|
||||
v4.4
|
||||
作者:DLmaster_361
|
||||
"""
|
||||
|
||||
@@ -53,7 +53,7 @@ class _MainTimer(QObject):
|
||||
def long_timed_task(self):
|
||||
"""长时间定期检定任务"""
|
||||
|
||||
Config.get_gameid()
|
||||
Config.get_stage()
|
||||
Config.main_window.setting.show_notice()
|
||||
if Config.get(Config.update_IfAutoUpdate):
|
||||
Config.main_window.setting.check_update()
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"""
|
||||
AUTO_MAA
|
||||
MAA功能组件
|
||||
v4.3
|
||||
v4.4
|
||||
作者:DLmaster_361
|
||||
"""
|
||||
|
||||
@@ -40,7 +40,7 @@ from typing import Union, List, Dict
|
||||
|
||||
from app.core import Config, MaaConfig, MaaUserConfig
|
||||
from app.services import Notify, Crypto, System, skland_sign_in
|
||||
from app.utils.ImageUtils import ImageUtils
|
||||
from app.utils import ProcessManager
|
||||
|
||||
|
||||
class MaaManager(QObject):
|
||||
@@ -58,8 +58,6 @@ class MaaManager(QObject):
|
||||
interrupt = Signal()
|
||||
accomplish = Signal(dict)
|
||||
|
||||
isInterruptionRequested = False
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
mode: str,
|
||||
@@ -81,17 +79,25 @@ class MaaManager(QObject):
|
||||
self.config_path = config["Path"]
|
||||
self.user_config_path = user_config_path
|
||||
|
||||
self.emulator_process_manager = ProcessManager()
|
||||
self.maa_process_manager = ProcessManager()
|
||||
|
||||
self.log_monitor = QFileSystemWatcher()
|
||||
self.log_monitor_timer = QTimer()
|
||||
self.log_monitor_timer.timeout.connect(self.refresh_maa_log)
|
||||
self.monitor_loop = QEventLoop()
|
||||
|
||||
self.maa_process_manager.processClosed.connect(
|
||||
lambda: self.log_monitor.fileChanged.emit("进程结束检查")
|
||||
)
|
||||
|
||||
self.question_loop = QEventLoop()
|
||||
self.question_response.connect(self.__capture_response)
|
||||
self.question_response.connect(self.question_loop.quit)
|
||||
|
||||
self.wait_loop = QEventLoop()
|
||||
|
||||
self.isInterruptionRequested = False
|
||||
self.interrupt.connect(self.quit_monitor)
|
||||
|
||||
self.maa_version = None
|
||||
@@ -505,9 +511,8 @@ class MaaManager(QObject):
|
||||
logger.info(
|
||||
f"{self.name} | 启动模拟器:{self.emulator_path},参数:{self.emulator_arguments}"
|
||||
)
|
||||
self.emulator_process = subprocess.Popen(
|
||||
[self.emulator_path, *self.emulator_arguments],
|
||||
creationflags=subprocess.CREATE_NO_WINDOW,
|
||||
self.emulator_process_manager.open_process(
|
||||
self.emulator_path, self.emulator_arguments, 0
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"{self.name} | 启动模拟器时出现异常:{e}")
|
||||
@@ -526,10 +531,7 @@ class MaaManager(QObject):
|
||||
self.search_ADB_address()
|
||||
|
||||
# 创建MAA任务
|
||||
maa = subprocess.Popen(
|
||||
[self.maa_exe_path],
|
||||
creationflags=subprocess.CREATE_NO_WINDOW,
|
||||
)
|
||||
self.maa_process_manager.open_process(self.maa_exe_path, [], 10)
|
||||
# 监测MAA运行状态
|
||||
self.start_monitor(start_time, mode_book[mode])
|
||||
|
||||
@@ -579,11 +581,11 @@ class MaaManager(QObject):
|
||||
f"{self.maa_result}\n正在中止相关程序\n请等待10s"
|
||||
)
|
||||
# 无命令行中止MAA与其子程序
|
||||
self.maa_process_manager.kill(if_force=True)
|
||||
System.kill_process(self.maa_exe_path)
|
||||
|
||||
# 中止模拟器进程
|
||||
self.emulator_process.terminate()
|
||||
self.emulator_process.wait()
|
||||
self.emulator_process_manager.kill()
|
||||
|
||||
self.if_open_emulator = True
|
||||
|
||||
@@ -644,8 +646,7 @@ class MaaManager(QObject):
|
||||
)
|
||||
# 任务结束后再次手动中止模拟器进程,防止退出不彻底
|
||||
if self.if_kill_emulator:
|
||||
self.emulator_process.terminate()
|
||||
self.emulator_process.wait()
|
||||
self.emulator_process_manager.kill()
|
||||
self.if_open_emulator = True
|
||||
|
||||
# 记录剿灭情况
|
||||
@@ -700,7 +701,7 @@ class MaaManager(QObject):
|
||||
logger.info(f"{self.name} | 更新动作结束")
|
||||
|
||||
# 发送统计信息
|
||||
statistics = Config.merge_maa_logs("指定项", user_logs_list)
|
||||
statistics = Config.merge_statistic_info(user_logs_list)
|
||||
statistics["user_index"] = user[2]
|
||||
statistics["user_info"] = user[0]
|
||||
statistics["start_time"] = user_start_time.strftime("%Y-%m-%d %H:%M:%S")
|
||||
@@ -777,10 +778,7 @@ class MaaManager(QObject):
|
||||
# 记录当前时间
|
||||
start_time = datetime.now()
|
||||
# 创建MAA任务
|
||||
maa = subprocess.Popen(
|
||||
[self.maa_exe_path],
|
||||
creationflags=subprocess.CREATE_NO_WINDOW,
|
||||
)
|
||||
self.maa_process_manager.open_process(self.maa_exe_path, [], 10)
|
||||
|
||||
# 监测MAA运行状态
|
||||
self.start_monitor(start_time, "人工排查")
|
||||
@@ -799,6 +797,7 @@ class MaaManager(QObject):
|
||||
f"{self.maa_result}\n正在中止相关程序\n请等待10s"
|
||||
)
|
||||
# 无命令行中止MAA与其子程序
|
||||
self.maa_process_manager.kill(if_force=True)
|
||||
System.kill_process(self.maa_exe_path)
|
||||
self.if_open_emulator = True
|
||||
self.sleep(10)
|
||||
@@ -845,10 +844,7 @@ class MaaManager(QObject):
|
||||
# 配置MAA
|
||||
self.set_maa(self.mode, "")
|
||||
# 创建MAA任务
|
||||
maa = subprocess.Popen(
|
||||
[self.maa_exe_path],
|
||||
creationflags=subprocess.CREATE_NO_WINDOW,
|
||||
)
|
||||
self.maa_process_manager.open_process(self.maa_exe_path, [], 10)
|
||||
# 记录当前时间
|
||||
start_time = datetime.now()
|
||||
|
||||
@@ -870,6 +866,7 @@ class MaaManager(QObject):
|
||||
|
||||
# 关闭可能未正常退出的MAA进程
|
||||
if self.isInterruptionRequested:
|
||||
self.maa_process_manager.kill(if_force=True)
|
||||
System.kill_process(self.maa_exe_path)
|
||||
|
||||
# 复原MAA配置文件
|
||||
@@ -1024,6 +1021,7 @@ class MaaManager(QObject):
|
||||
self.ADB_address = ADB_address
|
||||
|
||||
# 覆写当前ADB地址
|
||||
self.maa_process_manager.kill(if_force=True)
|
||||
System.kill_process(self.maa_exe_path)
|
||||
with self.maa_set_path.open(mode="r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
@@ -1053,7 +1051,7 @@ class MaaManager(QObject):
|
||||
|
||||
# 一分钟内未执行日志变化检查,强制检查一次
|
||||
if datetime.now() - self.last_check_time > timedelta(minutes=1):
|
||||
self.log_monitor.fileChanged.emit(self.log_monitor.files()[0])
|
||||
self.log_monitor.fileChanged.emit("1分钟超时检查")
|
||||
|
||||
def check_maa_log(self, start_time: datetime, mode: str) -> list:
|
||||
"""获取MAA日志并检查以判断MAA程序运行状态"""
|
||||
@@ -1124,21 +1122,25 @@ class MaaManager(QObject):
|
||||
|
||||
elif "任务已全部完成!" in log:
|
||||
|
||||
if "完成任务: StartUp" in log:
|
||||
if "完成任务: StartUp" in log or "完成任务: 开始唤醒" in log:
|
||||
self.task_dict["WakeUp"] = "False"
|
||||
if "完成任务: Recruit" in log:
|
||||
if "完成任务: Recruit" in log or "完成任务: 自动公招" in log:
|
||||
self.task_dict["Recruiting"] = "False"
|
||||
if "完成任务: Infrast" in log:
|
||||
if "完成任务: Infrast" in log or "完成任务: 基建换班" in log:
|
||||
self.task_dict["Base"] = "False"
|
||||
if "完成任务: Fight" in log or "剿灭任务失败" in log:
|
||||
if (
|
||||
"完成任务: Fight" in log
|
||||
or "完成任务: 刷理智" in log
|
||||
or "剿灭任务失败" in log
|
||||
):
|
||||
self.task_dict["Combat"] = "False"
|
||||
if "完成任务: Mall" in log:
|
||||
if "完成任务: Mall" in log or "完成任务: 获取信用及购物" in log:
|
||||
self.task_dict["Mall"] = "False"
|
||||
if "完成任务: Award" in log:
|
||||
if "完成任务: Award" in log or "完成任务: 领取奖励" in log:
|
||||
self.task_dict["Mission"] = "False"
|
||||
if "完成任务: Roguelike" in log:
|
||||
if "完成任务: Roguelike" in log or "完成任务: 自动肉鸽" in log:
|
||||
self.task_dict["AutoRoguelike"] = "False"
|
||||
if "完成任务: Reclamation" in log:
|
||||
if "完成任务: Reclamation" in log or "完成任务: 生息演算" in log:
|
||||
self.task_dict["Reclamation"] = "False"
|
||||
|
||||
if all(v == "False" for v in self.task_dict.values()):
|
||||
@@ -1155,7 +1157,10 @@ class MaaManager(QObject):
|
||||
elif "已停止" in log:
|
||||
self.maa_result = "MAA在完成任务前中止"
|
||||
|
||||
elif "MaaAssistantArknights GUI exited" in log:
|
||||
elif (
|
||||
"MaaAssistantArknights GUI exited" in log
|
||||
or not self.maa_process_manager.is_running()
|
||||
):
|
||||
self.maa_result = "MAA在完成任务前退出"
|
||||
|
||||
elif datetime.now() - latest_time > timedelta(
|
||||
@@ -1170,7 +1175,7 @@ class MaaManager(QObject):
|
||||
self.maa_result = "Wait"
|
||||
|
||||
elif mode == "人工排查":
|
||||
if "完成任务: StartUp" in log:
|
||||
if "完成任务: StartUp" in log or "完成任务: 开始唤醒" in log:
|
||||
self.maa_result = "Success!"
|
||||
elif "请 「检查连接设置」 → 「尝试重启模拟器与 ADB」 → 「重启电脑」" in log:
|
||||
self.maa_result = "MAA的ADB连接异常"
|
||||
@@ -1178,7 +1183,10 @@ class MaaManager(QObject):
|
||||
self.maa_result = "MAA未检测到任何模拟器"
|
||||
elif "已停止" in log:
|
||||
self.maa_result = "MAA在完成任务前中止"
|
||||
elif "MaaAssistantArknights GUI exited" in log:
|
||||
elif (
|
||||
"MaaAssistantArknights GUI exited" in log
|
||||
or not self.maa_process_manager.is_running()
|
||||
):
|
||||
self.maa_result = "MAA在完成任务前退出"
|
||||
elif self.isInterruptionRequested:
|
||||
self.maa_result = "任务被手动中止"
|
||||
@@ -1186,7 +1194,10 @@ class MaaManager(QObject):
|
||||
self.maa_result = "Wait"
|
||||
|
||||
elif mode == "设置MAA":
|
||||
if "MaaAssistantArknights GUI exited" in log:
|
||||
if (
|
||||
"MaaAssistantArknights GUI exited" in log
|
||||
or not self.maa_process_manager.is_running()
|
||||
):
|
||||
self.maa_result = "Success!"
|
||||
else:
|
||||
self.maa_result = "Wait"
|
||||
@@ -1229,6 +1240,7 @@ class MaaManager(QObject):
|
||||
user_data = self.data[index]["Config"]
|
||||
|
||||
# 配置MAA前关闭可能未正常退出的MAA进程
|
||||
self.maa_process_manager.kill(if_force=True)
|
||||
System.kill_process(self.maa_exe_path)
|
||||
|
||||
# 预导入MAA配置文件
|
||||
@@ -1442,23 +1454,28 @@ class MaaManager(QObject):
|
||||
user_data["Info"]["MedicineNumb"]
|
||||
) # 吃理智药数量
|
||||
data["Configurations"]["Default"]["MainFunction.Stage1"] = (
|
||||
user_data["Info"]["GameId"]
|
||||
if user_data["Info"]["GameId"] != "-"
|
||||
user_data["Info"]["Stage"]
|
||||
if user_data["Info"]["Stage"] != "-"
|
||||
else ""
|
||||
) # 主关卡
|
||||
data["Configurations"]["Default"]["MainFunction.Stage2"] = (
|
||||
user_data["Info"]["GameId_1"]
|
||||
if user_data["Info"]["GameId_1"] != "-"
|
||||
user_data["Info"]["Stage_1"]
|
||||
if user_data["Info"]["Stage_1"] != "-"
|
||||
else ""
|
||||
) # 备选关卡1
|
||||
data["Configurations"]["Default"]["MainFunction.Stage3"] = (
|
||||
user_data["Info"]["GameId_2"]
|
||||
if user_data["Info"]["GameId_2"] != "-"
|
||||
user_data["Info"]["Stage_2"]
|
||||
if user_data["Info"]["Stage_2"] != "-"
|
||||
else ""
|
||||
) # 备选关卡2
|
||||
data["Configurations"]["Default"]["MainFunction.Stage4"] = (
|
||||
user_data["Info"]["Stage_3"]
|
||||
if user_data["Info"]["Stage_3"] != "-"
|
||||
else ""
|
||||
) # 备选关卡3
|
||||
data["Configurations"]["Default"]["Fight.RemainingSanityStage"] = (
|
||||
user_data["Info"]["GameId_Remain"]
|
||||
if user_data["Info"]["GameId_Remain"] != "-"
|
||||
user_data["Info"]["Stage_Remain"]
|
||||
if user_data["Info"]["Stage_Remain"] != "-"
|
||||
else ""
|
||||
) # 剩余理智关卡
|
||||
data["Configurations"]["Default"][
|
||||
@@ -1478,7 +1495,7 @@ class MaaManager(QObject):
|
||||
data["Configurations"]["Default"][
|
||||
"Fight.UseRemainingSanityStage"
|
||||
] = (
|
||||
"True" if user_data["Info"]["GameId_Remain"] != "-" else "False"
|
||||
"True" if user_data["Info"]["Stage_Remain"] != "-" else "False"
|
||||
) # 使用剩余理智
|
||||
data["Configurations"]["Default"][
|
||||
"Fight.UseExpiringMedicine"
|
||||
@@ -1548,23 +1565,28 @@ class MaaManager(QObject):
|
||||
user_data["Info"]["MedicineNumb"]
|
||||
) # 吃理智药数量
|
||||
data["Configurations"]["Default"]["MainFunction.Stage1"] = (
|
||||
user_data["Info"]["GameId"]
|
||||
if user_data["Info"]["GameId"] != "-"
|
||||
user_data["Info"]["Stage"]
|
||||
if user_data["Info"]["Stage"] != "-"
|
||||
else ""
|
||||
) # 主关卡
|
||||
data["Configurations"]["Default"]["MainFunction.Stage2"] = (
|
||||
user_data["Info"]["GameId_1"]
|
||||
if user_data["Info"]["GameId_1"] != "-"
|
||||
user_data["Info"]["Stage_1"]
|
||||
if user_data["Info"]["Stage_1"] != "-"
|
||||
else ""
|
||||
) # 备选关卡1
|
||||
data["Configurations"]["Default"]["MainFunction.Stage3"] = (
|
||||
user_data["Info"]["GameId_2"]
|
||||
if user_data["Info"]["GameId_2"] != "-"
|
||||
user_data["Info"]["Stage_2"]
|
||||
if user_data["Info"]["Stage_2"] != "-"
|
||||
else ""
|
||||
) # 备选关卡2
|
||||
data["Configurations"]["Default"]["MainFunction.Stage4"] = (
|
||||
user_data["Info"]["Stage_3"]
|
||||
if user_data["Info"]["Stage_3"] != "-"
|
||||
else ""
|
||||
) # 备选关卡3
|
||||
data["Configurations"]["Default"]["Fight.RemainingSanityStage"] = (
|
||||
user_data["Info"]["GameId_Remain"]
|
||||
if user_data["Info"]["GameId_Remain"] != "-"
|
||||
user_data["Info"]["Stage_Remain"]
|
||||
if user_data["Info"]["Stage_Remain"] != "-"
|
||||
else ""
|
||||
) # 剩余理智关卡
|
||||
data["Configurations"]["Default"][
|
||||
@@ -1578,7 +1600,7 @@ class MaaManager(QObject):
|
||||
data["Configurations"]["Default"][
|
||||
"Fight.UseRemainingSanityStage"
|
||||
] = (
|
||||
"True" if user_data["Info"]["GameId_Remain"] != "-" else "False"
|
||||
"True" if user_data["Info"]["Stage_Remain"] != "-" else "False"
|
||||
) # 使用剩余理智
|
||||
|
||||
# 基建模式
|
||||
@@ -1881,15 +1903,17 @@ class MaaManager(QObject):
|
||||
|
||||
# 生成文本通知内容
|
||||
formatted = []
|
||||
for stage, items in message["drop_statistics"].items():
|
||||
formatted.append(f"掉落统计({stage}):")
|
||||
for item, quantity in items.items():
|
||||
formatted.append(f" {item}: {quantity}")
|
||||
if "drop_statistics" in message:
|
||||
for stage, items in message["drop_statistics"].items():
|
||||
formatted.append(f"掉落统计({stage}):")
|
||||
for item, quantity in items.items():
|
||||
formatted.append(f" {item}: {quantity}")
|
||||
drop_text = "\n".join(formatted)
|
||||
|
||||
formatted = ["招募统计:"]
|
||||
for star, count in message["recruit_statistics"].items():
|
||||
formatted.append(f" {star}: {count}")
|
||||
if "recruit_statistics" in message:
|
||||
for star, count in message["recruit_statistics"].items():
|
||||
formatted.append(f" {star}: {count}")
|
||||
recruit_text = "\n".join(formatted)
|
||||
|
||||
message_text = (
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"""
|
||||
AUTO_MAA
|
||||
AUTO_MAA模组包
|
||||
v4.3
|
||||
v4.4
|
||||
作者:DLmaster_361
|
||||
"""
|
||||
|
||||
@@ -29,6 +29,7 @@ __version__ = "4.2.0"
|
||||
__author__ = "DLmaster361 <DLmaster_361@163.com>"
|
||||
__license__ = "GPL-3.0 license"
|
||||
|
||||
from .general import GeneralManager
|
||||
from .MAA import MaaManager
|
||||
|
||||
__all__ = ["MaaManager"]
|
||||
__all__ = ["GeneralManager", "MaaManager"]
|
||||
|
||||
937
app/models/general.py
Normal file
937
app/models/general.py
Normal file
@@ -0,0 +1,937 @@
|
||||
# AUTO_MAA:A MAA Multi Account Management and Automation Tool
|
||||
# Copyright © 2024-2025 DLmaster361
|
||||
|
||||
# This file is part of AUTO_MAA.
|
||||
|
||||
# AUTO_MAA is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License,
|
||||
# or (at your option) any later version.
|
||||
|
||||
# AUTO_MAA is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
|
||||
# the GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# Contact: DLmaster_361@163.com
|
||||
|
||||
"""
|
||||
AUTO_MAA
|
||||
通用功能组件
|
||||
v4.4
|
||||
作者:DLmaster_361
|
||||
"""
|
||||
|
||||
from loguru import logger
|
||||
from PySide6.QtCore import QObject, Signal, QEventLoop, QFileSystemWatcher, QTimer
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
import subprocess
|
||||
from functools import partial
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
from typing import Union, List, Dict
|
||||
|
||||
from app.core import Config, GeneralConfig, GeneralSubConfig
|
||||
from app.services import Notify, System
|
||||
from app.utils import ProcessManager
|
||||
|
||||
|
||||
class GeneralManager(QObject):
|
||||
"""通用脚本通用控制器"""
|
||||
|
||||
question = Signal(str, str)
|
||||
question_response = Signal(bool)
|
||||
update_sub_info = Signal(str, dict)
|
||||
push_info_bar = Signal(str, str, str, int)
|
||||
play_sound = Signal(str)
|
||||
create_user_list = Signal(list)
|
||||
update_user_list = Signal(list)
|
||||
update_log_text = Signal(str)
|
||||
interrupt = Signal()
|
||||
accomplish = Signal(dict)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
mode: str,
|
||||
config: Dict[
|
||||
str,
|
||||
Union[
|
||||
str,
|
||||
Path,
|
||||
GeneralConfig,
|
||||
Dict[str, Dict[str, Union[Path, GeneralSubConfig]]],
|
||||
],
|
||||
],
|
||||
sub_config_path: Path = None,
|
||||
):
|
||||
super(GeneralManager, self).__init__()
|
||||
|
||||
self.sub_list = []
|
||||
self.mode = mode
|
||||
self.config_path = config["Path"]
|
||||
self.sub_config_path = sub_config_path
|
||||
|
||||
self.game_process_manager = ProcessManager()
|
||||
self.script_process_manager = ProcessManager()
|
||||
|
||||
self.log_monitor = QFileSystemWatcher()
|
||||
self.log_monitor_timer = QTimer()
|
||||
self.log_monitor_timer.timeout.connect(self.refresh_log)
|
||||
self.monitor_loop = QEventLoop()
|
||||
|
||||
self.script_process_manager.processClosed.connect(
|
||||
lambda: self.log_monitor.fileChanged.emit("进程结束检查")
|
||||
)
|
||||
|
||||
self.question_loop = QEventLoop()
|
||||
self.question_response.connect(self.__capture_response)
|
||||
self.question_response.connect(self.question_loop.quit)
|
||||
|
||||
self.wait_loop = QEventLoop()
|
||||
|
||||
self.isInterruptionRequested = False
|
||||
self.interrupt.connect(self.quit_monitor)
|
||||
|
||||
self.task_dict = {}
|
||||
self.set = config["Config"].toDict()
|
||||
|
||||
self.data: Dict[str, Dict[str, Union[Path, dict]]] = {}
|
||||
if self.mode != "设置通用脚本":
|
||||
for name, info in config["SubData"].items():
|
||||
self.data[name] = {
|
||||
"Path": info["Path"],
|
||||
"Config": info["Config"].toDict(),
|
||||
}
|
||||
|
||||
self.data = dict(sorted(self.data.items(), key=lambda x: int(x[0][3:])))
|
||||
|
||||
def check_config_info(self) -> bool:
|
||||
"""检查配置完整性"""
|
||||
|
||||
if not (
|
||||
Path(self.set["Script"]["RootPath"]).exists()
|
||||
and Path(self.set["Script"]["ScriptPath"]).exists()
|
||||
and Path(self.set["Script"]["ConfigPath"]).exists()
|
||||
and Path(self.set["Script"]["LogPath"]).parent.exists()
|
||||
and self.set["Script"]["LogTimeFormat"]
|
||||
and self.set["Script"]["ErrorLog"]
|
||||
) or (
|
||||
self.set["Game"]["Enabled"] and not Path(self.set["Game"]["Path"]).exists()
|
||||
):
|
||||
logger.error("脚本配置缺失")
|
||||
self.push_info_bar.emit("error", "脚本配置缺失", "请检查脚本配置!", -1)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def configure(self):
|
||||
"""提取配置信息"""
|
||||
|
||||
self.name = self.set["Script"]["Name"]
|
||||
self.script_root_path = Path(self.set["Script"]["RootPath"])
|
||||
self.script_exe_path = Path(self.set["Script"]["ScriptPath"])
|
||||
self.script_config_path = Path(self.set["Script"]["ConfigPath"])
|
||||
self.script_log_path = (
|
||||
Path(self.set["Script"]["LogPath"]).with_stem(
|
||||
datetime.now().strftime(self.set["Script"]["LogPathFormat"])
|
||||
)
|
||||
if self.set["Script"]["LogPathFormat"]
|
||||
else Path(self.set["Script"]["LogPath"])
|
||||
)
|
||||
if not self.script_log_path.exists():
|
||||
self.script_log_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
self.script_log_path.touch(exist_ok=True)
|
||||
self.game_path = Path(self.set["Game"]["Path"])
|
||||
self.log_time_range = [
|
||||
self.set["Script"]["LogTimeStart"] - 1,
|
||||
self.set["Script"]["LogTimeEnd"],
|
||||
]
|
||||
self.success_log = (
|
||||
[_.strip() for _ in self.set["Script"]["SuccessLog"].split("|")]
|
||||
if self.set["Script"]["SuccessLog"]
|
||||
else []
|
||||
)
|
||||
self.error_log = [_.strip() for _ in self.set["Script"]["ErrorLog"].split("|")]
|
||||
|
||||
def run(self):
|
||||
"""主进程,运行通用脚本代理进程"""
|
||||
|
||||
current_date = datetime.now().strftime("%m-%d")
|
||||
curdate = Config.server_date().strftime("%Y-%m-%d")
|
||||
begin_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
# 检查配置完整性
|
||||
if not self.check_config_info():
|
||||
|
||||
self.accomplish.emit(
|
||||
{"Time": begin_time, "History": "由于配置不完整,通用代理进程中止"}
|
||||
)
|
||||
return None
|
||||
|
||||
self.configure()
|
||||
|
||||
# 整理用户数据,筛选需代理的用户
|
||||
if self.mode != "设置通用脚本":
|
||||
|
||||
self.data = dict(sorted(self.data.items(), key=lambda x: int(x[0][3:])))
|
||||
self.sub_list: List[List[str, str, str]] = [
|
||||
[_["Config"]["Info"]["Name"], "等待", index]
|
||||
for index, _ in self.data.items()
|
||||
if (
|
||||
_["Config"]["Info"]["RemainedDay"] != 0
|
||||
and _["Config"]["Info"]["Status"]
|
||||
)
|
||||
]
|
||||
self.create_user_list.emit(self.sub_list)
|
||||
|
||||
# 自动代理模式
|
||||
if self.mode == "自动代理":
|
||||
|
||||
# 执行情况预处理
|
||||
for _ in self.sub_list:
|
||||
if self.data[_[2]]["Config"]["Data"]["LastProxyDate"] != curdate:
|
||||
self.data[_[2]]["Config"]["Data"]["LastProxyDate"] = curdate
|
||||
self.data[_[2]]["Config"]["Data"]["ProxyTimes"] = 0
|
||||
_[
|
||||
0
|
||||
] += f" - 第{self.data[_[2]]['Config']['Data']['ProxyTimes'] + 1}次代理"
|
||||
|
||||
# 开始代理
|
||||
for sub in self.sub_list:
|
||||
|
||||
sub_data = self.data[sub[2]]["Config"]
|
||||
|
||||
if self.isInterruptionRequested:
|
||||
break
|
||||
|
||||
if (
|
||||
self.set["Run"]["ProxyTimesLimit"] == 0
|
||||
or sub_data["Data"]["ProxyTimes"]
|
||||
< self.set["Run"]["ProxyTimesLimit"]
|
||||
):
|
||||
sub[1] = "运行"
|
||||
self.update_user_list.emit(self.sub_list)
|
||||
else:
|
||||
sub[1] = "跳过"
|
||||
self.update_user_list.emit(self.sub_list)
|
||||
continue
|
||||
|
||||
logger.info(f"{self.name} | 开始代理配置: {sub[0]}")
|
||||
|
||||
sub_start_time = datetime.now()
|
||||
|
||||
run_book = False
|
||||
|
||||
if not (self.data[sub[2]]["Path"] / "ConfigFiles").exists():
|
||||
logger.error(f"{self.name} | 配置: {sub[0]} - 未找到配置文件")
|
||||
self.push_info_bar.emit(
|
||||
"error",
|
||||
"启动通用代理进程失败",
|
||||
f"未找到{sub[0]}的配置文件!",
|
||||
-1,
|
||||
)
|
||||
run_book = False
|
||||
continue
|
||||
|
||||
# 尝试次数循环
|
||||
for i in range(self.set["Run"]["RunTimesLimit"]):
|
||||
|
||||
if self.isInterruptionRequested or run_book:
|
||||
break
|
||||
|
||||
logger.info(
|
||||
f"{self.name} | 用户: {sub[0]} - 尝试次数: {i + 1}/{self.set['Run']['RunTimesLimit']}"
|
||||
)
|
||||
|
||||
# 记录当前时间
|
||||
start_time = datetime.now()
|
||||
# 配置脚本
|
||||
self.set_sub(sub[2])
|
||||
# 执行任务前脚本
|
||||
if (
|
||||
sub_data["Info"]["IfScriptBeforeTask"]
|
||||
and Path(sub_data["Info"]["ScriptBeforeTask"]).exists()
|
||||
):
|
||||
self.execute_script_task(
|
||||
Path(sub_data["Info"]["ScriptBeforeTask"]), "脚本前任务"
|
||||
)
|
||||
|
||||
# 启动游戏/模拟器
|
||||
if self.set["Game"]["Enabled"]:
|
||||
|
||||
try:
|
||||
logger.info(
|
||||
f"{self.name} | 启动游戏/模拟器:{self.game_path},参数:{self.set['Game']['Arguments']}"
|
||||
)
|
||||
self.game_process_manager.open_process(
|
||||
self.game_path,
|
||||
str(self.set["Game"]["Arguments"]).split(" "),
|
||||
0,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"{self.name} | 启动游戏/模拟器时出现异常:{e}"
|
||||
)
|
||||
self.push_info_bar.emit(
|
||||
"error",
|
||||
"启动游戏/模拟器时出现异常",
|
||||
"请检查游戏/模拟器路径设置",
|
||||
-1,
|
||||
)
|
||||
self.script_result = "游戏/模拟器启动失败"
|
||||
break
|
||||
|
||||
# 添加静默进程标记
|
||||
if self.set["Game"]["Style"] == "Emulator":
|
||||
Config.silence_list.append(self.game_path)
|
||||
|
||||
self.update_log_text.emit(
|
||||
f"正在等待游戏/模拟器完成启动\n请等待{self.set['Game']['WaitTime']}s"
|
||||
)
|
||||
|
||||
self.sleep(self.set["Game"]["WaitTime"])
|
||||
|
||||
# 10s后移除静默进程标记
|
||||
if self.set["Game"]["Style"] == "Emulator":
|
||||
QTimer.singleShot(
|
||||
10000,
|
||||
partial(Config.silence_list.remove, self.game_path),
|
||||
)
|
||||
|
||||
# 运行脚本任务
|
||||
logger.info(
|
||||
f"{self.name} | 运行脚本任务:{self.script_exe_path},参数:{self.set['Script']['Arguments']}"
|
||||
)
|
||||
self.script_process_manager.open_process(
|
||||
self.script_exe_path,
|
||||
str(self.set["Script"]["Arguments"]).split(" "),
|
||||
tracking_time=60 if self.set["Script"]["IfTrackProcess"] else 0,
|
||||
)
|
||||
|
||||
# 监测运行状态
|
||||
self.start_monitor(start_time)
|
||||
|
||||
if self.script_result == "Success!":
|
||||
|
||||
# 标记任务完成
|
||||
run_book = True
|
||||
|
||||
# 中止相关程序
|
||||
self.script_process_manager.kill()
|
||||
System.kill_process(self.script_exe_path)
|
||||
if self.set["Game"]["Enabled"]:
|
||||
self.game_process_manager.kill()
|
||||
if self.set["Game"]["IfForceClose"]:
|
||||
System.kill_process(self.game_path)
|
||||
|
||||
logger.info(
|
||||
f"{self.name} | 配置: {sub[0]} - 通用脚本进程完成代理任务"
|
||||
)
|
||||
self.update_log_text.emit(
|
||||
"检测到通用脚本进程完成代理任务\n正在等待相关程序结束\n请等待10s"
|
||||
)
|
||||
|
||||
self.sleep(10)
|
||||
else:
|
||||
logger.error(
|
||||
f"{self.name} | 配置: {sub[0]} - 代理任务异常: {self.script_result}"
|
||||
)
|
||||
# 打印中止信息
|
||||
# 此时,log变量内存储的就是出现异常的日志信息,可以保存或发送用于问题排查
|
||||
self.update_log_text.emit(
|
||||
f"{self.script_result}\n正在中止相关程序\n请等待10s"
|
||||
)
|
||||
|
||||
# 中止相关程序
|
||||
self.script_process_manager.kill()
|
||||
if self.set["Game"]["Enabled"]:
|
||||
self.game_process_manager.kill()
|
||||
if self.set["Game"]["IfForceClose"]:
|
||||
System.kill_process(self.game_path)
|
||||
|
||||
# 推送异常通知
|
||||
Notify.push_plyer(
|
||||
"用户自动代理出现异常!",
|
||||
f"用户 {sub[0].replace("_", " 今天的")}出现一次异常",
|
||||
f"{sub[0].replace("_", " ")}出现异常",
|
||||
1,
|
||||
)
|
||||
if i == self.set["Run"]["RunTimesLimit"] - 1:
|
||||
self.play_sound.emit("子任务失败")
|
||||
else:
|
||||
self.play_sound.emit(self.script_result)
|
||||
self.sleep(10)
|
||||
|
||||
# 执行任务后脚本
|
||||
if (
|
||||
sub_data["Info"]["IfScriptAfterTask"]
|
||||
and Path(sub_data["Info"]["ScriptAfterTask"]).exists()
|
||||
):
|
||||
self.execute_script_task(
|
||||
Path(sub_data["Info"]["ScriptAfterTask"]), "脚本后任务"
|
||||
)
|
||||
|
||||
# 保存运行日志以及统计信息
|
||||
Config.save_general_log(
|
||||
Config.app_path
|
||||
/ f"history/{curdate}/{sub_data['Info']['Name']}/{start_time.strftime("%H-%M-%S")}.log",
|
||||
self.check_script_log(start_time),
|
||||
self.script_result,
|
||||
)
|
||||
|
||||
# 发送统计信息
|
||||
statistics = {
|
||||
"sub_index": sub[2],
|
||||
"sub_info": sub[0],
|
||||
"start_time": sub_start_time.strftime("%Y-%m-%d %H:%M:%S"),
|
||||
"end_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||||
"sub_result": "代理成功" if run_book else self.script_result,
|
||||
}
|
||||
self.push_notification(
|
||||
"统计信息",
|
||||
f"{current_date} | 配置 {sub[0]} 的自动代理统计报告",
|
||||
statistics,
|
||||
sub_data,
|
||||
)
|
||||
|
||||
if run_book:
|
||||
# 成功完成代理的用户修改相关参数
|
||||
if (
|
||||
sub_data["Data"]["ProxyTimes"] == 0
|
||||
and sub_data["Info"]["RemainedDay"] != -1
|
||||
):
|
||||
sub_data["Info"]["RemainedDay"] -= 1
|
||||
sub_data["Data"]["ProxyTimes"] += 1
|
||||
sub[1] = "完成"
|
||||
Notify.push_plyer(
|
||||
"成功完成一个自动代理任务!",
|
||||
f"已完成配置 {sub[0].replace("_", " 今天的")}任务",
|
||||
f"已完成 {sub[0].replace("_", " 的")}",
|
||||
3,
|
||||
)
|
||||
else:
|
||||
# 录入代理失败的用户
|
||||
sub[1] = "异常"
|
||||
|
||||
self.update_user_list.emit(self.sub_list)
|
||||
|
||||
# 设置通用脚本模式
|
||||
elif self.mode == "设置通用脚本":
|
||||
|
||||
# 配置通用脚本
|
||||
self.set_sub()
|
||||
|
||||
try:
|
||||
# 创建通用脚本任务
|
||||
logger.info(f"{self.name} | 无参数启动通用脚本:{self.script_exe_path}")
|
||||
self.script_process_manager.open_process(
|
||||
self.script_exe_path,
|
||||
tracking_time=60 if self.set["Script"]["IfTrackProcess"] else 0,
|
||||
)
|
||||
|
||||
# 记录当前时间
|
||||
start_time = datetime.now()
|
||||
|
||||
# 监测通用脚本运行状态
|
||||
self.start_monitor(start_time)
|
||||
|
||||
self.sub_config_path.mkdir(parents=True, exist_ok=True)
|
||||
if self.set["Script"]["ConfigPathMode"] == "文件夹":
|
||||
shutil.copytree(
|
||||
self.script_config_path,
|
||||
self.sub_config_path,
|
||||
dirs_exist_ok=True,
|
||||
)
|
||||
else:
|
||||
shutil.copy(self.script_config_path, self.sub_config_path)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"{self.name} | 启动通用脚本时出现异常:{e}")
|
||||
self.push_info_bar.emit(
|
||||
"error",
|
||||
"启动通用脚本时出现异常",
|
||||
"请检查相关设置",
|
||||
-1,
|
||||
)
|
||||
|
||||
result_text = ""
|
||||
|
||||
# 导出结果
|
||||
if self.mode in ["自动代理"]:
|
||||
|
||||
# 关闭可能未正常退出的通用脚本进程
|
||||
if self.isInterruptionRequested:
|
||||
self.script_process_manager.kill(if_force=True)
|
||||
System.kill_process(self.script_exe_path)
|
||||
if self.set["Game"]["Enabled"]:
|
||||
self.game_process_manager.kill(if_force=True)
|
||||
if self.set["Game"]["IfForceClose"]:
|
||||
System.kill_process(self.game_path)
|
||||
|
||||
# 更新用户数据
|
||||
updated_info = {_[2]: self.data[_[2]] for _ in self.sub_list}
|
||||
self.update_sub_info.emit(self.config_path.name, updated_info)
|
||||
|
||||
error_index = [_[2] for _ in self.sub_list if _[1] == "异常"]
|
||||
over_index = [_[2] for _ in self.sub_list if _[1] == "完成"]
|
||||
wait_index = [_[2] for _ in self.sub_list if _[1] == "等待"]
|
||||
|
||||
# 保存运行日志
|
||||
title = (
|
||||
f"{current_date} | {self.name}的{self.mode[:4]}任务报告"
|
||||
if self.name != ""
|
||||
else f"{current_date} | {self.mode[:4]}任务报告"
|
||||
)
|
||||
result = {
|
||||
"title": f"{self.mode[:4]}任务报告",
|
||||
"script_name": (self.name if self.name != "" else "空白"),
|
||||
"start_time": begin_time,
|
||||
"end_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||||
"completed_count": len(over_index),
|
||||
"uncompleted_count": len(error_index) + len(wait_index),
|
||||
"failed_sub": [
|
||||
self.data[_]["Config"]["Info"]["Name"] for _ in error_index
|
||||
],
|
||||
"waiting_sub": [
|
||||
self.data[_]["Config"]["Info"]["Name"] for _ in wait_index
|
||||
],
|
||||
}
|
||||
|
||||
# 生成结果文本
|
||||
result_text = (
|
||||
f"任务开始时间:{result['start_time']},结束时间:{result['end_time']}\n"
|
||||
f"已完成数:{result['completed_count']},未完成数:{result['uncompleted_count']}\n\n"
|
||||
)
|
||||
if len(result["failed_sub"]) > 0:
|
||||
result_text += f"{self.mode[2:4]}未成功的配置:\n{"\n".join(result['failed_sub'])}\n"
|
||||
if len(result["waiting_sub"]) > 0:
|
||||
result_text += f"\n未开始{self.mode[2:4]}的配置:\n{"\n".join(result['waiting_sub'])}\n"
|
||||
|
||||
# 推送代理结果通知
|
||||
Notify.push_plyer(
|
||||
title.replace("报告", "已完成!"),
|
||||
f"已完成配置数:{len(over_index)},未完成配置数:{len(error_index) + len(wait_index)}",
|
||||
f"已完成配置数:{len(over_index)},未完成配置数:{len(error_index) + len(wait_index)}",
|
||||
10,
|
||||
)
|
||||
self.push_notification("代理结果", title, result)
|
||||
|
||||
self.log_monitor.deleteLater()
|
||||
self.log_monitor_timer.deleteLater()
|
||||
self.accomplish.emit({"Time": begin_time, "History": result_text})
|
||||
|
||||
def requestInterruption(self) -> None:
|
||||
logger.info(f"{self.name} | 收到任务中止申请")
|
||||
|
||||
if len(self.log_monitor.files()) != 0:
|
||||
self.interrupt.emit()
|
||||
|
||||
self.script_result = "任务被手动中止"
|
||||
self.isInterruptionRequested = True
|
||||
self.wait_loop.quit()
|
||||
|
||||
def push_question(self, title: str, message: str) -> bool:
|
||||
|
||||
self.question.emit(title, message)
|
||||
self.question_loop.exec()
|
||||
return self.response
|
||||
|
||||
def __capture_response(self, response: bool) -> None:
|
||||
self.response = response
|
||||
|
||||
def sleep(self, time: int) -> None:
|
||||
"""非阻塞型等待"""
|
||||
|
||||
QTimer.singleShot(time * 1000, self.wait_loop.quit)
|
||||
self.wait_loop.exec()
|
||||
|
||||
def refresh_log(self) -> None:
|
||||
"""刷新脚本日志"""
|
||||
|
||||
with self.script_log_path.open(mode="r", encoding="utf-8") as f:
|
||||
pass
|
||||
|
||||
# 一分钟内未执行日志变化检查,强制检查一次
|
||||
if (datetime.now() - self.last_check_time).total_seconds() > 60:
|
||||
self.log_monitor.fileChanged.emit("1分钟超时检查")
|
||||
|
||||
def strptime(
|
||||
self, date_string: str, format: str, default_date: datetime
|
||||
) -> datetime:
|
||||
"""根据指定格式解析日期字符串"""
|
||||
|
||||
# 时间字段映射表
|
||||
time_fields = {
|
||||
"%Y": "year",
|
||||
"%m": "month",
|
||||
"%d": "day",
|
||||
"%H": "hour",
|
||||
"%M": "minute",
|
||||
"%S": "second",
|
||||
"%f": "microsecond",
|
||||
}
|
||||
|
||||
date = datetime.strptime(date_string, format)
|
||||
|
||||
# 构建参数字典
|
||||
datetime_kwargs = {}
|
||||
for format_code, field_name in time_fields.items():
|
||||
if format_code in format:
|
||||
datetime_kwargs[field_name] = getattr(date, field_name)
|
||||
else:
|
||||
datetime_kwargs[field_name] = getattr(default_date, field_name)
|
||||
|
||||
return datetime(**datetime_kwargs)
|
||||
|
||||
def check_script_log(self, start_time: datetime) -> list:
|
||||
"""获取脚本日志并检查以判断脚本程序运行状态"""
|
||||
|
||||
self.last_check_time = datetime.now()
|
||||
|
||||
# 获取日志
|
||||
logs = []
|
||||
if_log_start = False
|
||||
with self.script_log_path.open(mode="r", encoding="utf-8") as f:
|
||||
for entry in f:
|
||||
if not if_log_start:
|
||||
try:
|
||||
entry_time = self.strptime(
|
||||
entry[self.log_time_range[0] : self.log_time_range[1]],
|
||||
self.set["Script"]["LogTimeFormat"],
|
||||
self.last_check_time,
|
||||
)
|
||||
|
||||
if entry_time > start_time:
|
||||
if_log_start = True
|
||||
logs.append(entry)
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
logs.append(entry)
|
||||
log = "".join(logs)
|
||||
|
||||
# 更新日志
|
||||
if len(logs) > 100:
|
||||
self.update_log_text.emit("".join(logs[-100:]))
|
||||
else:
|
||||
self.update_log_text.emit("".join(logs))
|
||||
|
||||
if "自动代理" in self.mode:
|
||||
|
||||
# 获取最近一条日志的时间
|
||||
latest_time = start_time
|
||||
for _ in logs[::-1]:
|
||||
try:
|
||||
latest_time = self.strptime(
|
||||
_[self.log_time_range[0] : self.log_time_range[1]],
|
||||
self.set["Script"]["LogTimeFormat"],
|
||||
self.last_check_time,
|
||||
)
|
||||
break
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
for success_sign in self.success_log:
|
||||
if success_sign in log:
|
||||
self.script_result = "Success!"
|
||||
break
|
||||
else:
|
||||
|
||||
if self.isInterruptionRequested:
|
||||
self.script_result = "任务被手动中止"
|
||||
elif datetime.now() - latest_time > timedelta(
|
||||
minutes=self.set["Run"]["RunTimeLimit"]
|
||||
):
|
||||
self.script_result = "脚本进程超时"
|
||||
else:
|
||||
for error_sign in self.error_log:
|
||||
if error_sign in log:
|
||||
self.script_result = f"异常日志:{error_sign}"
|
||||
break
|
||||
else:
|
||||
if self.script_process_manager.is_running():
|
||||
self.script_result = "Wait"
|
||||
elif self.success_log:
|
||||
self.script_result = "脚本在完成任务前退出"
|
||||
else:
|
||||
self.script_result = "Success!"
|
||||
|
||||
elif self.mode == "设置通用脚本":
|
||||
if self.script_process_manager.is_running():
|
||||
self.script_result = "Wait"
|
||||
else:
|
||||
self.script_result = "Success!"
|
||||
|
||||
if self.script_result != "Wait":
|
||||
|
||||
self.quit_monitor()
|
||||
|
||||
return logs
|
||||
|
||||
def start_monitor(self, start_time: datetime) -> None:
|
||||
"""开始监视通用脚本日志"""
|
||||
|
||||
logger.info(f"{self.name} | 开始监视通用脚本日志")
|
||||
self.log_monitor.addPath(str(self.script_log_path))
|
||||
self.log_monitor.fileChanged.connect(lambda: self.check_script_log(start_time))
|
||||
self.log_monitor_timer.start(1000)
|
||||
self.last_check_time = datetime.now()
|
||||
self.monitor_loop.exec()
|
||||
|
||||
def quit_monitor(self) -> None:
|
||||
"""退出通用脚本日志监视进程"""
|
||||
|
||||
if len(self.log_monitor.files()) != 0:
|
||||
|
||||
logger.info(f"{self.name} | 退出通用脚本日志监视")
|
||||
self.log_monitor.removePath(str(self.script_log_path))
|
||||
self.log_monitor.fileChanged.disconnect()
|
||||
self.log_monitor_timer.stop()
|
||||
self.last_check_time = None
|
||||
self.monitor_loop.quit()
|
||||
|
||||
def set_sub(self, index: str = "") -> dict:
|
||||
"""配置通用脚本运行参数"""
|
||||
logger.info(f"{self.name} | 配置脚本运行参数: {index}")
|
||||
|
||||
# 配置前关闭可能未正常退出的脚本进程
|
||||
System.kill_process(self.script_exe_path)
|
||||
|
||||
# 预导入配置文件
|
||||
if self.mode == "设置通用脚本":
|
||||
if self.sub_config_path.exists():
|
||||
if self.set["Script"]["ConfigPathMode"] == "文件夹":
|
||||
shutil.copytree(
|
||||
self.sub_config_path,
|
||||
self.script_config_path,
|
||||
dirs_exist_ok=True,
|
||||
)
|
||||
elif (self.sub_config_path / self.script_config_path.name).exists():
|
||||
shutil.copy(
|
||||
self.sub_config_path / self.script_config_path.name,
|
||||
self.script_config_path,
|
||||
)
|
||||
else:
|
||||
if self.set["Script"]["ConfigPathMode"] == "文件夹":
|
||||
shutil.copytree(
|
||||
self.data[index]["Path"] / "ConfigFiles",
|
||||
self.script_config_path,
|
||||
dirs_exist_ok=True,
|
||||
)
|
||||
else:
|
||||
shutil.copy(
|
||||
self.data[index]["Path"]
|
||||
/ "ConfigFiles"
|
||||
/ self.script_config_path.name,
|
||||
self.script_config_path,
|
||||
)
|
||||
|
||||
def execute_script_task(self, script_path: Path, task_name: str) -> bool:
|
||||
"""执行脚本任务并等待结束"""
|
||||
|
||||
try:
|
||||
logger.info(f"{self.name} | 开始执行{task_name}: {script_path}")
|
||||
|
||||
# 根据文件类型选择执行方式
|
||||
if script_path.suffix.lower() == ".py":
|
||||
cmd = [sys.executable, script_path]
|
||||
elif script_path.suffix.lower() in [".bat", ".cmd", ".exe"]:
|
||||
cmd = [str(script_path)]
|
||||
elif script_path.suffix.lower() == "":
|
||||
logger.warning(f"{self.name} | {task_name}脚本没有指定后缀名,无法执行")
|
||||
return False
|
||||
else:
|
||||
# 使用系统默认程序打开
|
||||
os.startfile(str(script_path))
|
||||
return True
|
||||
|
||||
# 执行脚本并等待结束
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
cwd=script_path.parent,
|
||||
stdin=subprocess.DEVNULL,
|
||||
creationflags=(
|
||||
subprocess.CREATE_NO_WINDOW
|
||||
if Config.get(Config.function_IfSilence)
|
||||
else 0
|
||||
),
|
||||
timeout=600,
|
||||
capture_output=True,
|
||||
errors="ignore",
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
logger.info(f"{self.name} | {task_name}执行成功")
|
||||
if result.stdout.strip():
|
||||
logger.info(f"{self.name} | {task_name}输出: {result.stdout}")
|
||||
return True
|
||||
else:
|
||||
logger.error(
|
||||
f"{self.name} | {task_name}执行失败,返回码: {result.returncode}"
|
||||
)
|
||||
if result.stderr.strip():
|
||||
logger.error(f"{self.name} | {task_name}错误输出: {result.stderr}")
|
||||
return False
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
logger.error(f"{self.name} | {task_name}执行超时")
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.exception(f"{self.name} | 执行{task_name}时出现异常: {e}")
|
||||
return False
|
||||
|
||||
def push_notification(
|
||||
self,
|
||||
mode: str,
|
||||
title: str,
|
||||
message: Union[str, dict],
|
||||
sub_data: Dict[str, Dict[str, Union[str, int, bool]]] = None,
|
||||
) -> None:
|
||||
"""通过所有渠道推送通知"""
|
||||
|
||||
env = Environment(
|
||||
loader=FileSystemLoader(str(Config.app_path / "resources/html"))
|
||||
)
|
||||
|
||||
if mode == "代理结果" and (
|
||||
Config.get(Config.notify_SendTaskResultTime) == "任何时刻"
|
||||
or (
|
||||
Config.get(Config.notify_SendTaskResultTime) == "仅失败时"
|
||||
and message["uncompleted_count"] != 0
|
||||
)
|
||||
):
|
||||
# 生成文本通知内容
|
||||
message_text = (
|
||||
f"任务开始时间:{message['start_time']},结束时间:{message['end_time']}\n"
|
||||
f"已完成数:{message['completed_count']},未完成数:{message['uncompleted_count']}\n\n"
|
||||
)
|
||||
|
||||
if len(message["failed_sub"]) > 0:
|
||||
message_text += f"{self.mode[2:4]}未成功的配置:\n{"\n".join(message['failed_sub'])}\n"
|
||||
if len(message["waiting_sub"]) > 0:
|
||||
message_text += f"\n未开始{self.mode[2:4]}的配置:\n{"\n".join(message['waiting_sub'])}\n"
|
||||
|
||||
# 生成HTML通知内容
|
||||
message["failed_sub"] = "、".join(message["failed_sub"])
|
||||
message["waiting_sub"] = "、".join(message["waiting_sub"])
|
||||
|
||||
template = env.get_template("general_result.html")
|
||||
message_html = template.render(message)
|
||||
|
||||
# ServerChan的换行是两个换行符。故而将\n替换为\n\n
|
||||
serverchan_message = message_text.replace("\n", "\n\n")
|
||||
|
||||
# 发送全局通知
|
||||
|
||||
if Config.get(Config.notify_IfSendMail):
|
||||
Notify.send_mail(
|
||||
"网页", title, message_html, Config.get(Config.notify_ToAddress)
|
||||
)
|
||||
|
||||
if Config.get(Config.notify_IfServerChan):
|
||||
Notify.ServerChanPush(
|
||||
title,
|
||||
f"{serverchan_message}\n\nAUTO_MAA 敬上",
|
||||
Config.get(Config.notify_ServerChanKey),
|
||||
Config.get(Config.notify_ServerChanTag),
|
||||
Config.get(Config.notify_ServerChanChannel),
|
||||
)
|
||||
|
||||
if Config.get(Config.notify_IfCompanyWebHookBot):
|
||||
Notify.CompanyWebHookBotPush(
|
||||
title,
|
||||
f"{message_text}\n\nAUTO_MAA 敬上",
|
||||
Config.get(Config.notify_CompanyWebHookBotUrl),
|
||||
)
|
||||
|
||||
elif mode == "统计信息":
|
||||
|
||||
message_text = (
|
||||
f"开始时间: {message['start_time']}\n"
|
||||
f"结束时间: {message['end_time']}\n"
|
||||
f"通用脚本执行结果: {message['sub_result']}\n\n"
|
||||
)
|
||||
|
||||
# 生成HTML通知内容
|
||||
template = env.get_template("general_statistics.html")
|
||||
message_html = template.render(message)
|
||||
|
||||
# ServerChan的换行是两个换行符。故而将\n替换为\n\n
|
||||
serverchan_message = message_text.replace("\n", "\n\n")
|
||||
|
||||
# 发送全局通知
|
||||
if Config.get(Config.notify_IfSendStatistic):
|
||||
|
||||
if Config.get(Config.notify_IfSendMail):
|
||||
Notify.send_mail(
|
||||
"网页", title, message_html, Config.get(Config.notify_ToAddress)
|
||||
)
|
||||
|
||||
if Config.get(Config.notify_IfServerChan):
|
||||
Notify.ServerChanPush(
|
||||
title,
|
||||
f"{serverchan_message}\n\nAUTO_MAA 敬上",
|
||||
Config.get(Config.notify_ServerChanKey),
|
||||
Config.get(Config.notify_ServerChanTag),
|
||||
Config.get(Config.notify_ServerChanChannel),
|
||||
)
|
||||
|
||||
if Config.get(Config.notify_IfCompanyWebHookBot):
|
||||
Notify.CompanyWebHookBotPush(
|
||||
title,
|
||||
f"{message_text}\n\nAUTO_MAA 敬上",
|
||||
Config.get(Config.notify_CompanyWebHookBotUrl),
|
||||
)
|
||||
|
||||
# 发送用户单独通知
|
||||
if sub_data["Notify"]["Enabled"] and sub_data["Notify"]["IfSendStatistic"]:
|
||||
|
||||
# 发送邮件通知
|
||||
if sub_data["Notify"]["IfSendMail"]:
|
||||
if sub_data["Notify"]["ToAddress"]:
|
||||
Notify.send_mail(
|
||||
"网页",
|
||||
title,
|
||||
message_html,
|
||||
sub_data["Notify"]["ToAddress"],
|
||||
)
|
||||
else:
|
||||
logger.error(
|
||||
f"{self.name} | 用户邮箱地址为空,无法发送用户单独的邮件通知"
|
||||
)
|
||||
|
||||
# 发送ServerChan通知
|
||||
if sub_data["Notify"]["IfServerChan"]:
|
||||
if sub_data["Notify"]["ServerChanKey"]:
|
||||
Notify.ServerChanPush(
|
||||
title,
|
||||
f"{serverchan_message}\n\nAUTO_MAA 敬上",
|
||||
sub_data["Notify"]["ServerChanKey"],
|
||||
sub_data["Notify"]["ServerChanTag"],
|
||||
sub_data["Notify"]["ServerChanChannel"],
|
||||
)
|
||||
else:
|
||||
logger.error(
|
||||
f"{self.name} |用户ServerChan密钥为空,无法发送用户单独的ServerChan通知"
|
||||
)
|
||||
|
||||
# 推送CompanyWebHookBot通知
|
||||
if sub_data["Notify"]["IfCompanyWebHookBot"]:
|
||||
if sub_data["Notify"]["CompanyWebHookBotUrl"]:
|
||||
Notify.CompanyWebHookBotPush(
|
||||
title,
|
||||
f"{message_text}\n\nAUTO_MAA 敬上",
|
||||
sub_data["Notify"]["CompanyWebHookBotUrl"],
|
||||
)
|
||||
else:
|
||||
logger.error(
|
||||
f"{self.name} |用户CompanyWebHookBot密钥为空,无法发送用户单独的CompanyWebHookBot通知"
|
||||
)
|
||||
|
||||
return None
|
||||
@@ -21,7 +21,7 @@
|
||||
"""
|
||||
AUTO_MAA
|
||||
AUTO_MAA服务包
|
||||
v4.3
|
||||
v4.4
|
||||
作者:DLmaster_361
|
||||
"""
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"""
|
||||
AUTO_MAA
|
||||
AUTO_MAA通知服务
|
||||
v4.3
|
||||
v4.4
|
||||
作者:DLmaster_361
|
||||
"""
|
||||
|
||||
@@ -199,7 +199,16 @@ class Notification(QObject):
|
||||
params = {"title": title, "desp": content, **options}
|
||||
headers = {"Content-Type": "application/json;charset=utf-8"}
|
||||
|
||||
response = requests.post(url, json=params, headers=headers, timeout=10)
|
||||
response = requests.post(
|
||||
url,
|
||||
json=params,
|
||||
headers=headers,
|
||||
timeout=10,
|
||||
proxies={
|
||||
"http": Config.get(Config.update_ProxyAddress),
|
||||
"https": Config.get(Config.update_ProxyAddress),
|
||||
},
|
||||
)
|
||||
result = response.json()
|
||||
|
||||
if result.get("code") == 0:
|
||||
@@ -244,6 +253,10 @@ class Notification(QObject):
|
||||
url=webhook_url,
|
||||
json=data,
|
||||
timeout=10,
|
||||
proxies={
|
||||
"http": Config.get(Config.update_ProxyAddress),
|
||||
"https": Config.get(Config.update_ProxyAddress),
|
||||
},
|
||||
)
|
||||
info = response.json()
|
||||
break
|
||||
@@ -307,7 +320,7 @@ class Notification(QObject):
|
||||
image_base64 = ImageUtils.get_base64_from_file(str(image_path))
|
||||
image_md5 = ImageUtils.calculate_md5_from_file(str(image_path))
|
||||
except Exception as e:
|
||||
logger.error(f"图片编码或MD5计算失败:{e}")
|
||||
logger.exception(f"图片编码或MD5计算失败:{e}")
|
||||
self.push_info_bar.emit(
|
||||
"error",
|
||||
"企业微信群机器人通知推送异常",
|
||||
@@ -327,6 +340,10 @@ class Notification(QObject):
|
||||
url=webhook_url,
|
||||
json=data,
|
||||
timeout=10,
|
||||
proxies={
|
||||
"http": Config.get(Config.update_ProxyAddress),
|
||||
"https": Config.get(Config.update_ProxyAddress),
|
||||
},
|
||||
)
|
||||
info = response.json()
|
||||
break
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"""
|
||||
AUTO_MAA
|
||||
AUTO_MAA安全服务
|
||||
v4.3
|
||||
v4.4
|
||||
作者:DLmaster_361
|
||||
"""
|
||||
|
||||
@@ -147,22 +147,36 @@ class CryptoHandler:
|
||||
for member in Config.member_dict.values():
|
||||
|
||||
# 使用旧管理密钥解密
|
||||
for user in member["UserData"].values():
|
||||
user["Password"] = self.AUTO_decryptor(
|
||||
user["Config"].get(user["Config"].Info_Password), PASSWORD_old
|
||||
)
|
||||
if member["Type"] == "Maa":
|
||||
for user in member["UserData"].values():
|
||||
user["Password"] = self.AUTO_decryptor(
|
||||
user["Config"].get(user["Config"].Info_Password), PASSWORD_old
|
||||
)
|
||||
|
||||
self.get_PASSWORD(PASSWORD_new)
|
||||
|
||||
for member in Config.member_dict.values():
|
||||
|
||||
# 使用新管理密钥重新加密
|
||||
for user in member["UserData"].values():
|
||||
user["Config"].set(
|
||||
user["Config"].Info_Password, self.AUTO_encryptor(user["Password"])
|
||||
)
|
||||
user["Password"] = None
|
||||
del user["Password"]
|
||||
if member["Type"] == "Maa":
|
||||
for user in member["UserData"].values():
|
||||
user["Config"].set(
|
||||
user["Config"].Info_Password,
|
||||
self.AUTO_encryptor(user["Password"]),
|
||||
)
|
||||
user["Password"] = None
|
||||
del user["Password"]
|
||||
|
||||
def reset_PASSWORD(self, PASSWORD_new: str) -> None:
|
||||
"""重置管理密钥"""
|
||||
|
||||
self.get_PASSWORD(PASSWORD_new)
|
||||
|
||||
for member in Config.member_dict.values():
|
||||
|
||||
if member["Type"] == "Maa":
|
||||
for user in member["UserData"].values():
|
||||
user["Config"].set(user["Config"].Info_Password, "")
|
||||
|
||||
def win_encryptor(
|
||||
self, note: str, description: str = None, entropy: bytes = None
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
"""
|
||||
AUTO_MAA
|
||||
AUTO_MAA森空岛服务
|
||||
v4.3
|
||||
v4.4
|
||||
作者:DLmaster_361、ClozyA
|
||||
"""
|
||||
|
||||
@@ -40,6 +40,8 @@ import hashlib
|
||||
import requests
|
||||
from urllib import parse
|
||||
|
||||
from app.core import Config
|
||||
|
||||
|
||||
def skland_sign_in(token) -> dict:
|
||||
"""森空岛签到"""
|
||||
@@ -137,7 +139,13 @@ def skland_sign_in(token) -> dict:
|
||||
# 通过grant code换cred和sign_token
|
||||
def get_cred(grant):
|
||||
rsp = requests.post(
|
||||
cred_code_url, json={"code": grant, "kind": 1}, headers=header_login
|
||||
cred_code_url,
|
||||
json={"code": grant, "kind": 1},
|
||||
headers=header_login,
|
||||
proxies={
|
||||
"http": Config.get(Config.update_ProxyAddress),
|
||||
"https": Config.get(Config.update_ProxyAddress),
|
||||
},
|
||||
).json()
|
||||
if rsp["code"] != 0:
|
||||
raise Exception(f'获得cred失败:{rsp.get("messgae")}')
|
||||
@@ -151,6 +159,10 @@ def skland_sign_in(token) -> dict:
|
||||
grant_code_url,
|
||||
json={"appCode": app_code, "token": token, "type": 0},
|
||||
headers=header_login,
|
||||
proxies={
|
||||
"http": Config.get(Config.update_ProxyAddress),
|
||||
"https": Config.get(Config.update_ProxyAddress),
|
||||
},
|
||||
).json()
|
||||
if rsp["status"] != 0:
|
||||
raise Exception(
|
||||
@@ -172,6 +184,10 @@ def skland_sign_in(token) -> dict:
|
||||
headers=get_sign_header(
|
||||
binding_url, "get", None, copy_header(cred), sign_token
|
||||
),
|
||||
proxies={
|
||||
"http": Config.get(Config.update_ProxyAddress),
|
||||
"https": Config.get(Config.update_ProxyAddress),
|
||||
},
|
||||
).json()
|
||||
if rsp["code"] != 0:
|
||||
logger.error(f"森空岛服务 | 请求角色列表出现问题:{rsp['message']}")
|
||||
@@ -209,6 +225,10 @@ def skland_sign_in(token) -> dict:
|
||||
sign_url, "post", body, copy_header(cred), sign_token
|
||||
),
|
||||
json=body,
|
||||
proxies={
|
||||
"http": Config.get(Config.update_ProxyAddress),
|
||||
"https": Config.get(Config.update_ProxyAddress),
|
||||
},
|
||||
).json()
|
||||
|
||||
if rsp["code"] != 0:
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"""
|
||||
AUTO_MAA
|
||||
AUTO_MAA系统服务
|
||||
v4.3
|
||||
v4.4
|
||||
作者:DLmaster_361
|
||||
"""
|
||||
|
||||
@@ -31,9 +31,11 @@ import sys
|
||||
import ctypes
|
||||
import win32gui
|
||||
import win32process
|
||||
import winreg
|
||||
import psutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
import getpass
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
from app.core import Config
|
||||
@@ -65,23 +67,119 @@ class _SystemHandler:
|
||||
"""同步开机自启"""
|
||||
|
||||
if Config.get(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)
|
||||
|
||||
# 创建任务计划
|
||||
try:
|
||||
|
||||
# 获取当前用户和时间
|
||||
current_user = getpass.getuser()
|
||||
current_time = datetime.now().strftime("%Y-%m-%dT%H:%M:%S")
|
||||
|
||||
# XML 模板
|
||||
xml_content = f"""<?xml version="1.0" encoding="UTF-16"?>
|
||||
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
|
||||
<RegistrationInfo>
|
||||
<Date>{current_time}</Date>
|
||||
<Author>{current_user}</Author>
|
||||
<Description>AUTO_MAA自启动服务</Description>
|
||||
<URI>\\AUTO_MAA_AutoStart</URI>
|
||||
</RegistrationInfo>
|
||||
<Triggers>
|
||||
<LogonTrigger>
|
||||
<StartBoundary>{current_time}</StartBoundary>
|
||||
<Enabled>true</Enabled>
|
||||
</LogonTrigger>
|
||||
</Triggers>
|
||||
<Principals>
|
||||
<Principal id="Author">
|
||||
<LogonType>InteractiveToken</LogonType>
|
||||
<RunLevel>HighestAvailable</RunLevel>
|
||||
</Principal>
|
||||
</Principals>
|
||||
<Settings>
|
||||
<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
|
||||
<DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
|
||||
<StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
|
||||
<AllowHardTerminate>false</AllowHardTerminate>
|
||||
<StartWhenAvailable>true</StartWhenAvailable>
|
||||
<RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
|
||||
<IdleSettings>
|
||||
<StopOnIdleEnd>false</StopOnIdleEnd>
|
||||
<RestartOnIdle>false</RestartOnIdle>
|
||||
</IdleSettings>
|
||||
<AllowStartOnDemand>true</AllowStartOnDemand>
|
||||
<Enabled>true</Enabled>
|
||||
<Hidden>false</Hidden>
|
||||
<RunOnlyIfIdle>false</RunOnlyIfIdle>
|
||||
<WakeToRun>false</WakeToRun>
|
||||
<ExecutionTimeLimit>PT0S</ExecutionTimeLimit>
|
||||
<Priority>7</Priority>
|
||||
</Settings>
|
||||
<Actions Context="Author">
|
||||
<Exec>
|
||||
<Command>"{Config.app_path_sys}"</Command>
|
||||
</Exec>
|
||||
</Actions>
|
||||
</Task>"""
|
||||
|
||||
# 创建临时 XML 文件并执行
|
||||
with tempfile.NamedTemporaryFile(
|
||||
mode="w", suffix=".xml", delete=False, encoding="utf-16"
|
||||
) as f:
|
||||
f.write(xml_content)
|
||||
xml_file = f.name
|
||||
|
||||
try:
|
||||
result = subprocess.run(
|
||||
[
|
||||
"schtasks",
|
||||
"/create",
|
||||
"/tn",
|
||||
"AUTO_MAA_AutoStart",
|
||||
"/xml",
|
||||
xml_file,
|
||||
"/f",
|
||||
],
|
||||
creationflags=subprocess.CREATE_NO_WINDOW,
|
||||
stdin=subprocess.DEVNULL,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
logger.info(f"任务计划程序自启动已创建: {Config.app_path_sys}")
|
||||
else:
|
||||
logger.error(f"创建任务计划失败: {result.stderr}")
|
||||
|
||||
finally:
|
||||
# 删除临时文件
|
||||
try:
|
||||
Path(xml_file).unlink()
|
||||
except:
|
||||
pass
|
||||
|
||||
except Exception as e:
|
||||
logger.exception(f"设置任务计划程序自启动失败: {e}")
|
||||
|
||||
elif not Config.get(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)
|
||||
|
||||
try:
|
||||
|
||||
result = subprocess.run(
|
||||
["schtasks", "/delete", "/tn", "AUTO_MAA_AutoStart", "/f"],
|
||||
creationflags=subprocess.CREATE_NO_WINDOW,
|
||||
stdin=subprocess.DEVNULL,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
logger.info("任务计划程序自启动已删除")
|
||||
else:
|
||||
logger.error(f"删除任务计划失败: {result.stderr}")
|
||||
|
||||
except Exception as e:
|
||||
logger.exception(f"删除任务计划程序自启动失败: {e}")
|
||||
|
||||
def set_power(self, mode) -> None:
|
||||
|
||||
@@ -144,19 +242,17 @@ class _SystemHandler:
|
||||
def is_startup(self) -> bool:
|
||||
"""判断程序是否已经开机自启"""
|
||||
|
||||
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)
|
||||
result = subprocess.run(
|
||||
["schtasks", "/query", "/tn", "AUTO_MAA_AutoStart"],
|
||||
creationflags=subprocess.CREATE_NO_WINDOW,
|
||||
stdin=subprocess.DEVNULL,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
return result.returncode == 0
|
||||
except Exception as e:
|
||||
logger.error(f"检查任务计划程序失败: {e}")
|
||||
return False
|
||||
|
||||
def get_window_info(self) -> list:
|
||||
|
||||
150
app/ui/Widget.py
150
app/ui/Widget.py
@@ -27,12 +27,14 @@
|
||||
"""
|
||||
AUTO_MAA
|
||||
AUTO_MAA组件
|
||||
v4.3
|
||||
v4.4
|
||||
作者:DLmaster_361
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import win32com.client
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
from functools import partial
|
||||
from typing import Optional, Union, List, Dict
|
||||
@@ -48,6 +50,7 @@ from PySide6.QtWidgets import (
|
||||
QHBoxLayout,
|
||||
QVBoxLayout,
|
||||
QSizePolicy,
|
||||
QFileDialog,
|
||||
)
|
||||
from qfluentwidgets import (
|
||||
LineEdit,
|
||||
@@ -566,6 +569,100 @@ class PasswordLineEditSettingCard(SettingCard):
|
||||
self.LineEdit.textChanged.connect(self.__textChanged)
|
||||
|
||||
|
||||
class PathSettingCard(PushSettingCard):
|
||||
|
||||
pathChanged = Signal(Path, Path)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
icon: Union[str, QIcon, FluentIconBase],
|
||||
title: str,
|
||||
mode: Union[str, OptionsConfigItem],
|
||||
text: str,
|
||||
qconfig: QConfig,
|
||||
configItem: ConfigItem,
|
||||
parent=None,
|
||||
):
|
||||
super().__init__(text, icon, title, "未设置", parent)
|
||||
|
||||
self.title = title
|
||||
self.mode = mode
|
||||
self.qconfig = qconfig
|
||||
self.configItem = configItem
|
||||
|
||||
if isinstance(mode, OptionsConfigItem):
|
||||
|
||||
self.ComboBox = ComboBox(self)
|
||||
self.hBoxLayout.insertWidget(5, self.ComboBox, 0, Qt.AlignRight)
|
||||
|
||||
for option in mode.options:
|
||||
self.ComboBox.addItem(option, userData=option)
|
||||
|
||||
self.ComboBox.setCurrentText(self.qconfig.get(mode))
|
||||
self.ComboBox.currentIndexChanged.connect(self._onCurrentIndexChanged)
|
||||
mode.valueChanged.connect(self.setValue)
|
||||
|
||||
self.setContent(self.qconfig.get(self.configItem))
|
||||
|
||||
self.clicked.connect(self.ChoosePath)
|
||||
self.configItem.valueChanged.connect(
|
||||
lambda: self.setContent(self.qconfig.get(self.configItem))
|
||||
)
|
||||
|
||||
def ChoosePath(self):
|
||||
"""选择文件或文件夹路径"""
|
||||
|
||||
old_path = Path(self.qconfig.get(self.configItem))
|
||||
|
||||
if self.get_mode() == "文件夹":
|
||||
|
||||
folder = QFileDialog.getExistingDirectory(
|
||||
self, "选择文件夹", self.qconfig.get(self.configItem)
|
||||
)
|
||||
if folder:
|
||||
self.qconfig.set(self.configItem, folder)
|
||||
self.pathChanged.emit(old_path, Path(folder))
|
||||
|
||||
else:
|
||||
|
||||
file_path, _ = QFileDialog.getOpenFileName(
|
||||
self, "打开文件", self.qconfig.get(self.configItem), self.get_mode()
|
||||
)
|
||||
if file_path:
|
||||
file_path = self.analysis_lnk(file_path)
|
||||
self.qconfig.set(self.configItem, str(file_path))
|
||||
self.pathChanged.emit(old_path, file_path)
|
||||
|
||||
def analysis_lnk(self, path: str) -> Path:
|
||||
"""快捷方式解析"""
|
||||
|
||||
lnk_path = Path(path)
|
||||
if lnk_path.suffix == ".lnk":
|
||||
try:
|
||||
shell = win32com.client.Dispatch("WScript.Shell")
|
||||
shortcut = shell.CreateShortcut(str(lnk_path))
|
||||
return Path(shortcut.TargetPath)
|
||||
except Exception as e:
|
||||
return lnk_path
|
||||
else:
|
||||
return lnk_path
|
||||
|
||||
def get_mode(self) -> str:
|
||||
"""获取当前模式"""
|
||||
if isinstance(self.mode, OptionsConfigItem):
|
||||
return self.qconfig.get(self.mode)
|
||||
return self.mode
|
||||
|
||||
def _onCurrentIndexChanged(self, index: int):
|
||||
|
||||
self.qconfig.set(self.mode, self.ComboBox.itemData(index))
|
||||
|
||||
def setValue(self, value):
|
||||
|
||||
self.ComboBox.setCurrentText(value)
|
||||
self.qconfig.set(self.mode, value)
|
||||
|
||||
|
||||
class PushAndSwitchButtonSettingCard(SettingCard):
|
||||
"""Setting card with push & switch button"""
|
||||
|
||||
@@ -1163,6 +1260,48 @@ class TimeEditSettingCard(SettingCard):
|
||||
self.TimeEdit.setTime(QTime.fromString(value, "HH:mm"))
|
||||
|
||||
|
||||
class SubLableSettingCard(SettingCard):
|
||||
"""Setting card with Sub's Lable"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
icon: Union[str, QIcon, FluentIconBase],
|
||||
title: str,
|
||||
content: Union[str, None],
|
||||
qconfig: QConfig,
|
||||
configItems: Dict[str, ConfigItem],
|
||||
parent=None,
|
||||
):
|
||||
|
||||
super().__init__(icon, title, content, parent)
|
||||
self.qconfig = qconfig
|
||||
self.configItems = configItems
|
||||
self.Lable = SubtitleLabel(self)
|
||||
|
||||
if configItems:
|
||||
for configItem in configItems.values():
|
||||
configItem.valueChanged.connect(self.setValue)
|
||||
self.setValue()
|
||||
|
||||
self.hBoxLayout.addWidget(self.Lable, 0, Qt.AlignRight)
|
||||
self.hBoxLayout.addSpacing(16)
|
||||
|
||||
def setValue(self):
|
||||
|
||||
text_list = []
|
||||
|
||||
if self.configItems:
|
||||
|
||||
text_list.append(
|
||||
f"今日已代理{self.qconfig.get(self.configItems["ProxyTimes"])}次"
|
||||
if Config.server_date().strftime("%Y-%m-%d")
|
||||
== self.qconfig.get(self.configItems["LastProxyDate"])
|
||||
else "今日未进行代理"
|
||||
)
|
||||
|
||||
self.Lable.setText(" | ".join(text_list))
|
||||
|
||||
|
||||
class UserLableSettingCard(SettingCard):
|
||||
"""Setting card with User's Lable"""
|
||||
|
||||
@@ -1341,13 +1480,18 @@ class UserNoticeSettingCard(PushAndSwitchButtonSettingCard):
|
||||
|
||||
if not (
|
||||
self.qconfig.get(self.configItems["IfSendStatistic"])
|
||||
or self.qconfig.get(self.configItems["IfSendSixStar"])
|
||||
or (
|
||||
"IfSendSixStar" in self.configItems
|
||||
and self.qconfig.get(self.configItems["IfSendSixStar"])
|
||||
)
|
||||
):
|
||||
text_list.append("未启用任何通知项")
|
||||
|
||||
if self.qconfig.get(self.configItems["IfSendStatistic"]):
|
||||
text_list.append("统计信息已启用")
|
||||
if self.qconfig.get(self.configItems["IfSendSixStar"]):
|
||||
if "IfSendSixStar" in self.configItems and self.qconfig.get(
|
||||
self.configItems["IfSendSixStar"]
|
||||
):
|
||||
text_list.append("六星喜报已启用")
|
||||
|
||||
if self.qconfig.get(self.configItems["IfSendMail"]):
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"""
|
||||
AUTO_MAA
|
||||
AUTO_MAA图形化界面包
|
||||
v4.3
|
||||
v4.4
|
||||
作者:DLmaster_361
|
||||
"""
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"""
|
||||
AUTO_MAA
|
||||
AUTO_MAA调度中枢界面
|
||||
v4.3
|
||||
v4.4
|
||||
作者:DLmaster_361
|
||||
"""
|
||||
|
||||
@@ -210,8 +210,8 @@ class DispatchCenter(QWidget):
|
||||
self.script_list["主调度台"].top_bar.object.addItem(
|
||||
(
|
||||
f"实例 - {info['Type']}"
|
||||
if info["Config"].get(info["Config"].MaaSet_Name) == ""
|
||||
else f"实例 - {info['Type']} - {info["Config"].get(info["Config"].MaaSet_Name)}"
|
||||
if info["Config"].get_name() == ""
|
||||
else f"实例 - {info['Type']} - {info['Config'].get_name()}"
|
||||
),
|
||||
userData=name,
|
||||
)
|
||||
@@ -284,8 +284,8 @@ class DispatchCenter(QWidget):
|
||||
continue
|
||||
text_list.append(
|
||||
f"实例 - {info['Type']}"
|
||||
if info["Config"].get(info["Config"].MaaSet_Name) == ""
|
||||
else f"实例 - {info['Type']} - {info["Config"].get(info["Config"].MaaSet_Name)}"
|
||||
if info["Config"].get_name() == ""
|
||||
else f"实例 - {info['Type']} - {info['Config'].get_name()}"
|
||||
)
|
||||
data_list.append(name)
|
||||
|
||||
@@ -317,14 +317,12 @@ class DispatchCenter(QWidget):
|
||||
|
||||
elif "脚本" in choice.input[0].currentData():
|
||||
|
||||
if Config.member_dict[choice.input[0].currentData()]["Type"] == "Maa":
|
||||
|
||||
logger.info(f"用户添加任务:{choice.input[0].currentData()}")
|
||||
TaskManager.add_task(
|
||||
"自动代理_新调度台",
|
||||
f"自定义队列 - {choice.input[0].currentData()}",
|
||||
{"Queue": {"Member_1": choice.input[0].currentData()}},
|
||||
)
|
||||
logger.info(f"用户添加任务:{choice.input[0].currentData()}")
|
||||
TaskManager.add_task(
|
||||
"自动代理_新调度台",
|
||||
f"自定义队列 - {choice.input[0].currentData()}",
|
||||
{"Queue": {"Member_1": choice.input[0].currentData()}},
|
||||
)
|
||||
|
||||
class DispatchBox(QWidget):
|
||||
|
||||
@@ -409,6 +407,18 @@ class DispatchCenter(QWidget):
|
||||
)
|
||||
return None
|
||||
|
||||
if (
|
||||
"脚本" in self.object.currentData()
|
||||
and Config.member_dict[self.object.currentData()]["Type"]
|
||||
== "General"
|
||||
and self.mode.currentData() == "人工排查"
|
||||
):
|
||||
logger.warning("通用脚本类型不存在人工排查功能")
|
||||
MainInfoBar.push_info_bar(
|
||||
"warning", "不支持的任务", "通用脚本无人工排查功能", 5000
|
||||
)
|
||||
return None
|
||||
|
||||
if "调度队列" in self.object.currentData():
|
||||
|
||||
logger.info(f"用户添加任务:{self.object.currentData()}")
|
||||
@@ -420,14 +430,12 @@ class DispatchCenter(QWidget):
|
||||
|
||||
elif "脚本" in self.object.currentData():
|
||||
|
||||
if Config.member_dict[self.object.currentData()]["Type"] == "Maa":
|
||||
|
||||
logger.info(f"用户添加任务:{self.object.currentData()}")
|
||||
TaskManager.add_task(
|
||||
f"{self.mode.currentText()}_主调度台",
|
||||
"自定义队列",
|
||||
{"Queue": {"Member_1": self.object.currentData()}},
|
||||
)
|
||||
logger.info(f"用户添加任务:{self.object.currentData()}")
|
||||
TaskManager.add_task(
|
||||
f"{self.mode.currentText()}_主调度台",
|
||||
"自定义队列",
|
||||
{"Queue": {"Member_1": self.object.currentData()}},
|
||||
)
|
||||
|
||||
class DispatchInfoCard(HeaderCardWidget):
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"""
|
||||
AUTO_MAA
|
||||
AUTO_MAA更新器
|
||||
v4.3
|
||||
v4.4
|
||||
作者:DLmaster_361
|
||||
"""
|
||||
|
||||
@@ -30,7 +30,6 @@ import zipfile
|
||||
import requests
|
||||
import subprocess
|
||||
import time
|
||||
import psutil
|
||||
from functools import partial
|
||||
from pathlib import Path
|
||||
|
||||
@@ -47,6 +46,9 @@ from PySide6.QtCore import QThread, Signal, QTimer, QEventLoop
|
||||
|
||||
from typing import List, Dict, Union
|
||||
|
||||
from app.core import Config
|
||||
from app.services import System
|
||||
|
||||
|
||||
def version_text(version_numb: list) -> str:
|
||||
"""将版本号列表转为可读的文本信息"""
|
||||
@@ -94,7 +96,15 @@ class DownloadProcess(QThread):
|
||||
if self.download_path.exists():
|
||||
self.download_path.unlink()
|
||||
|
||||
headers = {"Range": f"bytes={self.start_byte}-{self.end_byte}"}
|
||||
logger.info(
|
||||
f"开始下载:{self.url},范围:{self.start_byte}-{self.end_byte},存储地址:{self.download_path}"
|
||||
)
|
||||
|
||||
headers = (
|
||||
{"Range": f"bytes={self.start_byte}-{self.end_byte}"}
|
||||
if not (self.start_byte == -1 or self.end_byte == -1)
|
||||
else None
|
||||
)
|
||||
|
||||
while not self.isInterruptionRequested() and self.check_times != 0:
|
||||
|
||||
@@ -103,17 +113,30 @@ class DownloadProcess(QThread):
|
||||
start_time = time.time()
|
||||
|
||||
response = requests.get(
|
||||
self.url, headers=headers, timeout=10, stream=True
|
||||
self.url,
|
||||
headers=headers,
|
||||
timeout=10,
|
||||
stream=True,
|
||||
proxies={
|
||||
"http": Config.get(Config.update_ProxyAddress),
|
||||
"https": Config.get(Config.update_ProxyAddress),
|
||||
},
|
||||
)
|
||||
|
||||
if response.status_code != 206:
|
||||
if response.status_code not in [200, 206]:
|
||||
|
||||
if self.check_times != -1:
|
||||
self.check_times -= 1
|
||||
|
||||
logger.error(
|
||||
f"连接失败:{self.url},状态码:{response.status_code},剩余重试次数:{self.check_times}"
|
||||
)
|
||||
|
||||
time.sleep(1)
|
||||
continue
|
||||
|
||||
logger.info(f"连接成功:{self.url},状态码:{response.status_code}")
|
||||
|
||||
downloaded_size = 0
|
||||
with self.download_path.open(mode="wb") as f:
|
||||
|
||||
@@ -132,10 +155,14 @@ class DownloadProcess(QThread):
|
||||
if self.download_path.exists():
|
||||
self.download_path.unlink()
|
||||
self.accomplish.emit(0)
|
||||
logger.info(f"下载中止:{self.url}")
|
||||
|
||||
else:
|
||||
|
||||
self.accomplish.emit(time.time() - start_time)
|
||||
logger.success(
|
||||
f"下载完成:{self.url},实际下载大小:{downloaded_size} 字节,耗时:{time.time() - start_time:.2f} 秒"
|
||||
)
|
||||
|
||||
break
|
||||
|
||||
@@ -143,6 +170,10 @@ class DownloadProcess(QThread):
|
||||
|
||||
if self.check_times != -1:
|
||||
self.check_times -= 1
|
||||
|
||||
logger.exception(
|
||||
f"下载出错:{self.url},错误信息:{e},剩余重试次数:{self.check_times}"
|
||||
)
|
||||
time.sleep(1)
|
||||
|
||||
else:
|
||||
@@ -150,6 +181,7 @@ class DownloadProcess(QThread):
|
||||
if self.download_path.exists():
|
||||
self.download_path.unlink()
|
||||
self.accomplish.emit(0)
|
||||
logger.error(f"下载失败:{self.url}")
|
||||
|
||||
|
||||
class ZipExtractProcess(QThread):
|
||||
@@ -172,6 +204,8 @@ class ZipExtractProcess(QThread):
|
||||
|
||||
try:
|
||||
|
||||
logger.info(f"开始解压:{self.download_path} 到 {self.app_path}")
|
||||
|
||||
while True:
|
||||
|
||||
if self.isInterruptionRequested():
|
||||
@@ -181,13 +215,15 @@ class ZipExtractProcess(QThread):
|
||||
with zipfile.ZipFile(self.download_path, "r") as zip_ref:
|
||||
zip_ref.extractall(self.app_path)
|
||||
self.accomplish.emit()
|
||||
logger.success(f"解压完成:{self.download_path} 到 {self.app_path}")
|
||||
break
|
||||
except PermissionError:
|
||||
if self.name == "AUTO_MAA":
|
||||
self.info.emit(f"解压出错:AUTO_MAA正在运行,正在尝试将其关闭")
|
||||
self.kill_process(self.app_path / "AUTO_MAA.exe")
|
||||
System.kill_process(self.app_path / "AUTO_MAA.exe")
|
||||
else:
|
||||
self.info.emit(f"解压出错:{self.name}正在运行,正在等待其关闭")
|
||||
logger.warning(f"解压出错:{self.name}正在运行,正在等待其关闭")
|
||||
time.sleep(1)
|
||||
|
||||
except Exception as e:
|
||||
@@ -195,32 +231,9 @@ class ZipExtractProcess(QThread):
|
||||
e = str(e)
|
||||
e = "\n".join([e[_ : _ + 75] for _ in range(0, len(e), 75)])
|
||||
self.info.emit(f"解压更新时出错:\n{e}")
|
||||
logger.exception(f"解压更新时出错:{e}")
|
||||
return None
|
||||
|
||||
def kill_process(self, path: Path) -> None:
|
||||
"""根据路径中止进程"""
|
||||
|
||||
for pid in self.search_pids(path):
|
||||
killprocess = subprocess.Popen(
|
||||
f"taskkill /F /PID {pid}",
|
||||
shell=True,
|
||||
creationflags=subprocess.CREATE_NO_WINDOW,
|
||||
)
|
||||
killprocess.wait()
|
||||
|
||||
def search_pids(self, path: Path) -> list:
|
||||
"""根据路径查找进程PID"""
|
||||
|
||||
pids = []
|
||||
for proc in psutil.process_iter(["pid", "exe"]):
|
||||
try:
|
||||
if proc.info["exe"] and proc.info["exe"].lower() == str(path).lower():
|
||||
pids.append(proc.info["pid"])
|
||||
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
|
||||
# 进程可能在此期间已结束或无法访问,忽略这些异常
|
||||
pass
|
||||
return pids
|
||||
|
||||
|
||||
class DownloadManager(QDialog):
|
||||
"""下载管理器"""
|
||||
@@ -326,6 +339,10 @@ class DownloadManager(QDialog):
|
||||
allow_redirects=True,
|
||||
timeout=10,
|
||||
stream=True,
|
||||
proxies={
|
||||
"http": Config.get(Config.update_ProxyAddress),
|
||||
"https": Config.get(Config.update_ProxyAddress),
|
||||
},
|
||||
) as response:
|
||||
if response.status_code == 200:
|
||||
return response.url
|
||||
@@ -333,7 +350,14 @@ class DownloadManager(QDialog):
|
||||
elif self.config["mode"] == "MirrorChyan":
|
||||
|
||||
with requests.get(
|
||||
self.config["url"], allow_redirects=True, timeout=10, stream=True
|
||||
self.config["url"],
|
||||
allow_redirects=True,
|
||||
timeout=10,
|
||||
stream=True,
|
||||
proxies={
|
||||
"http": Config.get(Config.update_ProxyAddress),
|
||||
"https": Config.get(Config.update_ProxyAddress),
|
||||
},
|
||||
) as response:
|
||||
if response.status_code == 200:
|
||||
return response.url
|
||||
@@ -346,6 +370,8 @@ class DownloadManager(QDialog):
|
||||
url_dict = self.get_download_url("测速")
|
||||
self.test_speed_result: Dict[str, float] = {}
|
||||
|
||||
logger.info(f"测速链接:{url_dict}")
|
||||
|
||||
for name, url in url_dict.items():
|
||||
|
||||
if self.isInterruptionRequested:
|
||||
@@ -427,6 +453,7 @@ class DownloadManager(QDialog):
|
||||
|
||||
# 保存测速结果
|
||||
self.config["speed_result"] = self.test_speed_result
|
||||
logger.info(f"测速结果:{self.test_speed_result}")
|
||||
|
||||
self.update_info("测速完成!")
|
||||
self.speed_test_accomplish.emit()
|
||||
@@ -439,7 +466,14 @@ class DownloadManager(QDialog):
|
||||
url = self.get_download_url("下载")
|
||||
self.downloaded_size_list: List[List[int, bool]] = []
|
||||
|
||||
response = requests.head(url, timeout=10)
|
||||
response = requests.head(
|
||||
url,
|
||||
timeout=10,
|
||||
proxies={
|
||||
"http": Config.get(Config.update_ProxyAddress),
|
||||
"https": Config.get(Config.update_ProxyAddress),
|
||||
},
|
||||
)
|
||||
|
||||
self.file_size = int(response.headers.get("content-length", 0))
|
||||
part_size = self.file_size // self.config["thread_numb"]
|
||||
@@ -465,8 +499,8 @@ class DownloadManager(QDialog):
|
||||
# 创建下载子线程
|
||||
self.download_process_dict[f"part{i}"] = DownloadProcess(
|
||||
url,
|
||||
start_byte,
|
||||
end_byte,
|
||||
-1 if self.config["mode"] == "MirrorChyan" else start_byte,
|
||||
-1 if self.config["mode"] == "MirrorChyan" else end_byte,
|
||||
self.download_path.with_suffix(f".part{i}"),
|
||||
1 if self.config["mode"] == "MirrorChyan" else -1,
|
||||
)
|
||||
@@ -525,6 +559,9 @@ class DownloadManager(QDialog):
|
||||
return None
|
||||
|
||||
# 合并下载的分段文件
|
||||
logger.info(
|
||||
f"所有分段下载完成:{self.name},开始合并分段文件到 {self.download_path}"
|
||||
)
|
||||
with self.download_path.open(mode="wb") as outfile:
|
||||
for i in range(self.config["thread_numb"]):
|
||||
with self.download_path.with_suffix(f".part{i}").open(
|
||||
@@ -533,6 +570,10 @@ class DownloadManager(QDialog):
|
||||
outfile.write(infile.read())
|
||||
self.download_path.with_suffix(f".part{i}").unlink()
|
||||
|
||||
logger.success(
|
||||
f"合并完成:{self.name},下载文件大小:{self.download_path.stat().st_size} 字节"
|
||||
)
|
||||
|
||||
self.update_info("正在解压更新文件")
|
||||
self.update_progress(0, 0, 0)
|
||||
|
||||
@@ -583,6 +624,9 @@ class DownloadManager(QDialog):
|
||||
self.progress_2.setValue(current)
|
||||
|
||||
def requestInterruption(self) -> None:
|
||||
"""请求中断下载任务"""
|
||||
|
||||
logger.info("收到下载任务中止请求")
|
||||
|
||||
self.isInterruptionRequested = True
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"""
|
||||
AUTO_MAA
|
||||
AUTO_MAA历史记录界面
|
||||
v4.3
|
||||
v4.4
|
||||
作者:DLmaster_361
|
||||
"""
|
||||
|
||||
@@ -48,7 +48,7 @@ import subprocess
|
||||
from datetime import datetime, timedelta
|
||||
from functools import partial
|
||||
from pathlib import Path
|
||||
from typing import Union, List, Dict
|
||||
from typing import List, Dict
|
||||
|
||||
|
||||
from app.core import Config, SoundPlayer
|
||||
@@ -100,9 +100,9 @@ class History(QWidget):
|
||||
datetime(end_date.year(), end_date.month(), end_date.day()),
|
||||
)
|
||||
|
||||
for date, user in history_dict.items():
|
||||
for date, user_dict in history_dict.items():
|
||||
|
||||
self.history_card_list.append(self.HistoryCard(mode, date, user, self))
|
||||
self.history_card_list.append(self.HistoryCard(date, user_dict, self))
|
||||
self.content_layout.addWidget(self.history_card_list[-1])
|
||||
|
||||
self.content_layout.addStretch(1)
|
||||
@@ -172,14 +172,9 @@ class History(QWidget):
|
||||
self.search.clicked.emit()
|
||||
|
||||
class HistoryCard(QuickExpandGroupCard):
|
||||
"""历史记录卡片"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
mode: str,
|
||||
date: str,
|
||||
user: Union[List[Path], Dict[str, List[Path]]],
|
||||
parent=None,
|
||||
):
|
||||
def __init__(self, date: str, user_dict: Dict[str, List[Path]], parent=None):
|
||||
super().__init__(
|
||||
FluentIcon.HISTORY, date, f"{date}的历史运行记录与统计信息", parent
|
||||
)
|
||||
@@ -192,64 +187,28 @@ class History(QWidget):
|
||||
|
||||
self.user_history_card_list = []
|
||||
|
||||
if mode == "按日合并":
|
||||
|
||||
for user_path in user:
|
||||
self.user_history_card_list.append(
|
||||
self.UserHistoryCard(mode, user_path.stem, user_path, self)
|
||||
)
|
||||
Layout.addWidget(self.user_history_card_list[-1])
|
||||
|
||||
elif mode in ["按周合并", "按月合并"]:
|
||||
|
||||
for user, info in user.items():
|
||||
self.user_history_card_list.append(
|
||||
self.UserHistoryCard(mode, user, info, self)
|
||||
)
|
||||
Layout.addWidget(self.user_history_card_list[-1])
|
||||
for user, info in user_dict.items():
|
||||
self.user_history_card_list.append(
|
||||
self.UserHistoryCard(user, info, self)
|
||||
)
|
||||
Layout.addWidget(self.user_history_card_list[-1])
|
||||
|
||||
class UserHistoryCard(HeaderCardWidget):
|
||||
"""用户历史记录卡片"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
mode: str,
|
||||
name: str,
|
||||
user_history: Union[Path, List[Path]],
|
||||
parent=None,
|
||||
):
|
||||
def __init__(self, name: str, user_history: List[Path], parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
self.setTitle(name)
|
||||
|
||||
if mode == "按日合并":
|
||||
self.user_history = user_history
|
||||
|
||||
self.user_history_path = user_history
|
||||
self.main_history = Config.load_maa_logs("总览", user_history)
|
||||
|
||||
self.index_card = self.IndexCard(
|
||||
self.main_history["条目索引"], self
|
||||
)
|
||||
self.index_card.index_changed.connect(self.update_info)
|
||||
self.viewLayout.addWidget(self.index_card)
|
||||
|
||||
elif mode in ["按周合并", "按月合并"]:
|
||||
|
||||
history = Config.merge_maa_logs("指定项", user_history)
|
||||
|
||||
self.main_history = {}
|
||||
self.main_history["统计数据"] = {
|
||||
"公招统计": list(history["recruit_statistics"].items())
|
||||
}
|
||||
|
||||
for game_id, drops in history["drop_statistics"].items():
|
||||
self.main_history["统计数据"][f"掉落统计:{game_id}"] = list(
|
||||
drops.items()
|
||||
)
|
||||
self.index_card = self.IndexCard(self.user_history, self)
|
||||
self.index_card.index_changed.connect(self.update_info)
|
||||
|
||||
self.statistics_card = QHBoxLayout()
|
||||
self.log_card = self.LogCard(self)
|
||||
|
||||
self.viewLayout.addWidget(self.index_card)
|
||||
self.viewLayout.addLayout(self.statistics_card)
|
||||
self.viewLayout.addWidget(self.log_card)
|
||||
self.viewLayout.setContentsMargins(0, 0, 0, 0)
|
||||
@@ -259,19 +218,45 @@ class History(QWidget):
|
||||
|
||||
self.update_info("数据总览")
|
||||
|
||||
def get_statistics(self, mode: str) -> dict:
|
||||
"""生成GUI相应结构化统计数据"""
|
||||
|
||||
history_info = Config.merge_statistic_info(
|
||||
self.user_history if mode == "数据总览" else [Path(mode)]
|
||||
)
|
||||
|
||||
statistics_info = {}
|
||||
|
||||
if "recruit_statistics" in history_info:
|
||||
statistics_info["公招统计"] = list(
|
||||
history_info["recruit_statistics"].items()
|
||||
)
|
||||
|
||||
if "drop_statistics" in history_info:
|
||||
for game_id, drops in history_info["drop_statistics"].items():
|
||||
statistics_info[f"掉落统计:{game_id}"] = list(drops.items())
|
||||
|
||||
if mode == "数据总览" and "error_info" in history_info:
|
||||
statistics_info["报错汇总"] = list(
|
||||
history_info["error_info"].items()
|
||||
)
|
||||
|
||||
return statistics_info
|
||||
|
||||
def update_info(self, index: str) -> None:
|
||||
"""更新信息"""
|
||||
|
||||
# 移除已有统计信息UI组件
|
||||
while self.statistics_card.count() > 0:
|
||||
item = self.statistics_card.takeAt(0)
|
||||
if item.spacerItem():
|
||||
self.statistics_card.removeItem(item.spacerItem())
|
||||
elif item.widget():
|
||||
item.widget().deleteLater()
|
||||
|
||||
if index == "数据总览":
|
||||
|
||||
while self.statistics_card.count() > 0:
|
||||
item = self.statistics_card.takeAt(0)
|
||||
if item.spacerItem():
|
||||
self.statistics_card.removeItem(item.spacerItem())
|
||||
elif item.widget():
|
||||
item.widget().deleteLater()
|
||||
|
||||
for name, item_list in self.main_history["统计数据"].items():
|
||||
for name, item_list in self.get_statistics("数据总览").items():
|
||||
|
||||
statistics_card = self.StatisticsCard(name, item_list, self)
|
||||
self.statistics_card.addWidget(statistics_card)
|
||||
@@ -280,44 +265,24 @@ class History(QWidget):
|
||||
|
||||
else:
|
||||
|
||||
single_history = Config.load_maa_logs(
|
||||
"单项",
|
||||
self.user_history_path.with_suffix("")
|
||||
/ f"{index.replace(":","-")}.json",
|
||||
)
|
||||
|
||||
while self.statistics_card.count() > 0:
|
||||
item = self.statistics_card.takeAt(0)
|
||||
if item.spacerItem():
|
||||
self.statistics_card.removeItem(item.spacerItem())
|
||||
elif item.widget():
|
||||
item.widget().deleteLater()
|
||||
|
||||
for name, item_list in single_history["统计数据"].items():
|
||||
single_history = self.get_statistics(index)
|
||||
log_path = Path(index).with_suffix(".log")
|
||||
|
||||
for name, item_list in single_history.items():
|
||||
statistics_card = self.StatisticsCard(name, item_list, self)
|
||||
self.statistics_card.addWidget(statistics_card)
|
||||
|
||||
self.log_card.text.setText(single_history["日志信息"])
|
||||
with log_path.open("r", encoding="utf-8") as f:
|
||||
log = f.read()
|
||||
|
||||
self.log_card.text.setText(log)
|
||||
self.log_card.open_file.clicked.disconnect()
|
||||
self.log_card.open_file.clicked.connect(
|
||||
lambda: os.startfile(
|
||||
self.user_history_path.with_suffix("")
|
||||
/ f"{index.replace(":","-")}.log"
|
||||
)
|
||||
lambda: os.startfile(log_path)
|
||||
)
|
||||
self.log_card.open_dir.clicked.disconnect()
|
||||
self.log_card.open_dir.clicked.connect(
|
||||
lambda: subprocess.Popen(
|
||||
[
|
||||
"explorer",
|
||||
"/select,",
|
||||
str(
|
||||
self.user_history_path.with_suffix("")
|
||||
/ f"{index.replace(":","-")}.log"
|
||||
),
|
||||
]
|
||||
)
|
||||
lambda: subprocess.Popen(["explorer", "/select,", log_path])
|
||||
)
|
||||
self.log_card.show()
|
||||
|
||||
@@ -329,7 +294,7 @@ class History(QWidget):
|
||||
|
||||
index_changed = Signal(str)
|
||||
|
||||
def __init__(self, index_list: list, parent=None):
|
||||
def __init__(self, history_list: List[Path], parent=None):
|
||||
super().__init__(parent)
|
||||
self.setTitle("记录条目")
|
||||
|
||||
@@ -339,11 +304,14 @@ class History(QWidget):
|
||||
|
||||
self.index_cards: List[StatefulItemCard] = []
|
||||
|
||||
index_list = Config.merge_statistic_info(history_list)["index"]
|
||||
index_list.insert(0, ["数据总览", "运行", "数据总览"])
|
||||
|
||||
for index in index_list:
|
||||
|
||||
self.index_cards.append(StatefulItemCard(index))
|
||||
self.index_cards.append(StatefulItemCard(index[:2]))
|
||||
self.index_cards[-1].clicked.connect(
|
||||
partial(self.index_changed.emit, index[0])
|
||||
partial(self.index_changed.emit, str(index[2]))
|
||||
)
|
||||
self.Layout.addWidget(self.index_cards[-1])
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"""
|
||||
AUTO_MAA
|
||||
AUTO_MAA主界面
|
||||
v4.3
|
||||
v4.4
|
||||
作者:DLmaster_361
|
||||
"""
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"""
|
||||
AUTO_MAA
|
||||
AUTO_MAA主界面
|
||||
v4.3
|
||||
v4.4
|
||||
作者:DLmaster_361
|
||||
"""
|
||||
|
||||
@@ -184,7 +184,7 @@ class AUTO_MAA(MSFluentWindow):
|
||||
|
||||
self.set_min_method()
|
||||
|
||||
Config.user_info_changed.connect(self.member_manager.refresh_dashboard)
|
||||
Config.sub_info_changed.connect(self.member_manager.refresh_dashboard)
|
||||
Config.power_sign_changed.connect(self.dispatch_center.update_power_sign)
|
||||
TaskManager.create_gui.connect(self.dispatch_center.add_board)
|
||||
TaskManager.connect_gui.connect(self.dispatch_center.connect_main_board)
|
||||
@@ -361,6 +361,9 @@ class AUTO_MAA(MSFluentWindow):
|
||||
# 检查密码
|
||||
self.setting.check_PASSWORD()
|
||||
|
||||
# 获取关卡号信息
|
||||
Config.get_stage()
|
||||
|
||||
# 获取主题图像
|
||||
if Config.get(Config.function_HomeImageMode) == "主题图像":
|
||||
self.home.get_home_image()
|
||||
@@ -469,7 +472,7 @@ class AUTO_MAA(MSFluentWindow):
|
||||
|
||||
logger.warning("启动主任务失败:未找到有效的主任务配置文件")
|
||||
MainInfoBar.push_info_bar(
|
||||
"warning", "启动主任务失败", "“调度队列_1”与“脚本_1”均不存在", -1
|
||||
"warning", "启动主任务失败", "「调度队列_1」与「脚本_1」均不存在", -1
|
||||
)
|
||||
|
||||
def __currentChanged(self, index: int) -> None:
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -21,7 +21,7 @@
|
||||
"""
|
||||
AUTO_MAA
|
||||
AUTO_MAA计划管理界面
|
||||
v4.3
|
||||
v4.4
|
||||
作者:DLmaster_361
|
||||
"""
|
||||
|
||||
@@ -156,7 +156,7 @@ class PlanManager(QWidget):
|
||||
self.plan_manager.clear_SettingBox()
|
||||
|
||||
shutil.rmtree(Config.plan_dict[name]["Path"])
|
||||
Config.change_plan(name, "禁用")
|
||||
Config.change_plan(name, "固定")
|
||||
for i in range(int(name[3:]) + 1, len(Config.plan_dict) + 1):
|
||||
if Config.plan_dict[f"计划_{i}"]["Path"].exists():
|
||||
Config.plan_dict[f"计划_{i}"]["Path"].rename(
|
||||
@@ -324,7 +324,7 @@ class PlanManager(QWidget):
|
||||
"""清空所有子界面"""
|
||||
|
||||
for sub_interface in self.script_list:
|
||||
Config.gameid_refreshed.disconnect(sub_interface.refresh_gameid)
|
||||
Config.stage_refreshed.disconnect(sub_interface.refresh_stage)
|
||||
self.stackedWidget.removeWidget(sub_interface)
|
||||
sub_interface.deleteLater()
|
||||
self.script_list.clear()
|
||||
@@ -372,7 +372,7 @@ class PlanManager(QWidget):
|
||||
|
||||
self.table = TableWidget(self)
|
||||
self.table.setColumnCount(8)
|
||||
self.table.setRowCount(6)
|
||||
self.table.setRowCount(7)
|
||||
self.table.setHorizontalHeaderLabels(
|
||||
["全局", "周一", "周二", "周三", "周四", "周五", "周六", "周日"]
|
||||
)
|
||||
@@ -383,6 +383,7 @@ class PlanManager(QWidget):
|
||||
"关卡选择",
|
||||
"备选 - 1",
|
||||
"备选 - 2",
|
||||
"备选 - 3",
|
||||
"剩余理智",
|
||||
]
|
||||
)
|
||||
@@ -392,7 +393,7 @@ class PlanManager(QWidget):
|
||||
self.table.horizontalHeader().setSectionResizeMode(
|
||||
col, QHeaderView.ResizeMode.Stretch
|
||||
)
|
||||
for row in range(6):
|
||||
for row in range(7):
|
||||
self.table.verticalHeader().setSectionResizeMode(
|
||||
row, QHeaderView.ResizeMode.ResizeToContents
|
||||
)
|
||||
@@ -427,21 +428,21 @@ class PlanManager(QWidget):
|
||||
configItem=configItem,
|
||||
parent=self,
|
||||
)
|
||||
elif name == "GameId_Remain":
|
||||
elif name == "Stage_Remain":
|
||||
self.item_dict[group][name] = EditableComboBoxSetting(
|
||||
value=Config.gameid_dict[group]["value"],
|
||||
value=Config.stage_dict[group]["value"],
|
||||
texts=[
|
||||
"不使用" if _ == "当前/上次" else _
|
||||
for _ in Config.gameid_dict[group]["text"]
|
||||
for _ in Config.stage_dict[group]["text"]
|
||||
],
|
||||
qconfig=self.config,
|
||||
configItem=configItem,
|
||||
parent=self,
|
||||
)
|
||||
elif "GameId" in name:
|
||||
elif "Stage" in name:
|
||||
self.item_dict[group][name] = EditableComboBoxSetting(
|
||||
value=Config.gameid_dict[group]["value"],
|
||||
texts=Config.gameid_dict[group]["text"],
|
||||
value=Config.stage_dict[group]["value"],
|
||||
texts=Config.stage_dict[group]["text"],
|
||||
qconfig=self.config,
|
||||
configItem=configItem,
|
||||
parent=self,
|
||||
@@ -459,7 +460,7 @@ class PlanManager(QWidget):
|
||||
self.viewLayout.setContentsMargins(3, 0, 3, 3)
|
||||
|
||||
self.card_Mode.comboBox.currentIndexChanged.connect(self.switch_mode)
|
||||
Config.gameid_refreshed.connect(self.refresh_gameid)
|
||||
Config.stage_refreshed.connect(self.refresh_stage)
|
||||
|
||||
self.switch_mode()
|
||||
|
||||
@@ -473,25 +474,25 @@ class PlanManager(QWidget):
|
||||
== (self.config.get(self.config.Info_Mode) == "ALL")
|
||||
)
|
||||
|
||||
def refresh_gameid(self):
|
||||
def refresh_stage(self):
|
||||
|
||||
for group, name_dict in self.item_dict.items():
|
||||
|
||||
for name, setting_item in name_dict.items():
|
||||
|
||||
if name == "GameId_Remain":
|
||||
if name == "Stage_Remain":
|
||||
|
||||
setting_item.reLoadOptions(
|
||||
Config.gameid_dict[group]["value"],
|
||||
Config.stage_dict[group]["value"],
|
||||
[
|
||||
"不使用" if _ == "当前/上次" else _
|
||||
for _ in Config.gameid_dict[group]["text"]
|
||||
for _ in Config.stage_dict[group]["text"]
|
||||
],
|
||||
)
|
||||
|
||||
elif "GameId" in name:
|
||||
elif "Stage" in name:
|
||||
|
||||
setting_item.reLoadOptions(
|
||||
Config.gameid_dict[group]["value"],
|
||||
Config.gameid_dict[group]["text"],
|
||||
Config.stage_dict[group]["value"],
|
||||
Config.stage_dict[group]["text"],
|
||||
)
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"""
|
||||
AUTO_MAA
|
||||
AUTO_MAA调度队列界面
|
||||
v4.3
|
||||
v4.4
|
||||
作者:DLmaster_361
|
||||
"""
|
||||
|
||||
@@ -111,7 +111,7 @@ class QueueManager(QWidget):
|
||||
"Config": queue_config,
|
||||
}
|
||||
|
||||
self.queue_manager.add_QueueSettingBox(index)
|
||||
self.queue_manager.add_SettingBox(index)
|
||||
self.queue_manager.switch_SettingBox(index)
|
||||
|
||||
logger.success(f"调度队列_{index} 添加成功")
|
||||
@@ -256,8 +256,8 @@ class QueueManager(QWidget):
|
||||
+ [
|
||||
(
|
||||
k
|
||||
if v["Config"].get(v["Config"].MaaSet_Name) == ""
|
||||
else f"{k} - {v["Config"].get(v["Config"].MaaSet_Name)}"
|
||||
if v["Config"].get_name() == ""
|
||||
else f"{k} - {v["Config"].get_name()}"
|
||||
)
|
||||
for k, v in Config.member_dict.items()
|
||||
],
|
||||
@@ -332,7 +332,7 @@ class QueueManager(QWidget):
|
||||
Config.search_queue()
|
||||
|
||||
for name in Config.queue_dict.keys():
|
||||
self.add_QueueSettingBox(int(name[5:]))
|
||||
self.add_SettingBox(int(name[5:]))
|
||||
|
||||
self.switch_SettingBox(index)
|
||||
|
||||
@@ -358,12 +358,12 @@ class QueueManager(QWidget):
|
||||
self.script_list.clear()
|
||||
self.pivot.clear()
|
||||
|
||||
def add_QueueSettingBox(self, uid: int) -> None:
|
||||
def add_SettingBox(self, uid: int) -> None:
|
||||
"""添加一个调度队列设置界面"""
|
||||
|
||||
maa_setting_box = self.QueueMemberSettingBox(uid, self)
|
||||
setting_box = self.QueueMemberSettingBox(uid, self)
|
||||
|
||||
self.script_list.append(maa_setting_box)
|
||||
self.script_list.append(setting_box)
|
||||
|
||||
self.stackedWidget.addWidget(self.script_list[-1])
|
||||
|
||||
@@ -586,8 +586,8 @@ class QueueManager(QWidget):
|
||||
+ [
|
||||
(
|
||||
k
|
||||
if v["Config"].get(v["Config"].MaaSet_Name) == ""
|
||||
else f"{k} - {v["Config"].get(v["Config"].MaaSet_Name)}"
|
||||
if v["Config"].get_name() == ""
|
||||
else f"{k} - {v["Config"].get_name()}"
|
||||
)
|
||||
for k, v in Config.member_dict.items()
|
||||
],
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"""
|
||||
AUTO_MAA
|
||||
AUTO_MAA设置界面
|
||||
v4.3
|
||||
v4.4
|
||||
作者:DLmaster_361
|
||||
"""
|
||||
|
||||
@@ -86,6 +86,7 @@ class Setting(QWidget):
|
||||
)
|
||||
self.start.card_IfSelfStart.checkedChanged.connect(System.set_SelfStart)
|
||||
self.security.card_changePASSWORD.clicked.connect(self.change_PASSWORD)
|
||||
self.security.card_resetPASSWORD.clicked.connect(self.reset_PASSWORD)
|
||||
self.updater.card_CheckUpdate.clicked.connect(
|
||||
lambda: self.check_update(if_show=True)
|
||||
)
|
||||
@@ -119,7 +120,7 @@ class Setting(QWidget):
|
||||
|
||||
choice = MessageBox(
|
||||
"授权声明",
|
||||
"开启“托管bilibili游戏隐私政策”功能,即代表您已完整阅读并同意《哔哩哔哩弹幕网用户使用协议》、《哔哩哔哩隐私政策》和《哔哩哔哩游戏中心用户协议》,并授权AUTO_MAA在其认定需要时以其认定合适的方法替您处理相关弹窗\n\n是否同意授权?",
|
||||
"开启「托管bilibili游戏隐私政策」功能,即代表您已完整阅读并同意《哔哩哔哩弹幕网用户使用协议》、《哔哩哔哩隐私政策》和《哔哩哔哩游戏中心用户协议》,并授权AUTO_MAA在其认定需要时以其认定合适的方法替您处理相关弹窗\n\n是否同意授权?",
|
||||
self.window(),
|
||||
)
|
||||
if choice.exec():
|
||||
@@ -147,7 +148,7 @@ class Setting(QWidget):
|
||||
|
||||
choice = MessageBox(
|
||||
"风险声明",
|
||||
"开启“跳过MuMu启动广告”功能,即代表您已安装MuMu模拟器-12且允许AUTO_MAA以其认定合适的方法屏蔽MuMu启动广告,并接受此操作带来的风险\n\n此功能即时生效,是否仍要开启此功能?",
|
||||
"开启「跳过MuMu启动广告」功能,即代表您已安装MuMu模拟器-12且允许AUTO_MAA以其认定合适的方法屏蔽MuMu启动广告,并接受此操作带来的风险\n\n此功能即时生效,是否仍要开启此功能?",
|
||||
self.window(),
|
||||
)
|
||||
if choice.exec():
|
||||
@@ -259,6 +260,61 @@ class Setting(QWidget):
|
||||
if choice.exec():
|
||||
break
|
||||
|
||||
def reset_PASSWORD(self) -> None:
|
||||
"""重置管理密钥"""
|
||||
|
||||
choice = MessageBox(
|
||||
"确认",
|
||||
"重置管理密钥将清空所有使用管理密钥加密的数据,您确认要重置管理密钥吗?",
|
||||
self.window(),
|
||||
)
|
||||
if choice.exec():
|
||||
choice = LineEditMessageBox(
|
||||
self.window(),
|
||||
"请输入文本提示框内的验证信息",
|
||||
"AUTO_MAA绝赞DeBug中!",
|
||||
"明文",
|
||||
)
|
||||
|
||||
if choice.exec() and choice.input.text() in [
|
||||
"AUTO_MAA绝赞DeBug中!",
|
||||
"AUTO_MAA绝赞DeBug中!",
|
||||
]:
|
||||
|
||||
# 获取新的管理密钥
|
||||
while True:
|
||||
|
||||
choice = LineEditMessageBox(
|
||||
self.window(), "请输入新的管理密钥", "新管理密钥", "密码"
|
||||
)
|
||||
if choice.exec() and choice.input.text() != "":
|
||||
|
||||
# 重置管理密钥
|
||||
Crypto.reset_PASSWORD(choice.input.text())
|
||||
MainInfoBar.push_info_bar(
|
||||
"success", "操作成功", "管理密钥重置成功", 3000
|
||||
)
|
||||
break
|
||||
|
||||
else:
|
||||
|
||||
choice = MessageBox(
|
||||
"确认",
|
||||
"您没有输入新的管理密钥,是否取消修改管理密钥?",
|
||||
self.window(),
|
||||
)
|
||||
if choice.exec():
|
||||
break
|
||||
|
||||
else:
|
||||
|
||||
MainInfoBar.push_info_bar(
|
||||
"info",
|
||||
"验证未通过",
|
||||
"请输入「AUTO_MAA绝赞DeBug中!」后单击确认键",
|
||||
3000,
|
||||
)
|
||||
|
||||
def check_update(self, if_show: bool = False, if_first: bool = False) -> None:
|
||||
"""检查版本更新,调起文件下载进程"""
|
||||
|
||||
@@ -670,7 +726,7 @@ class FunctionSettingCard(HeaderCardWidget):
|
||||
self.card_BossKey = LineEditSettingCard(
|
||||
icon=FluentIcon.PAGE_RIGHT,
|
||||
title="模拟器老板键",
|
||||
content="请输入对应的模拟器老板键,请直接输入文字,多个键位之间请用“+”隔开。如:“Alt+Q”",
|
||||
content="请输入对应的模拟器老板键,请直接输入文字,多个键位之间请用「+」隔开。如:「Alt+Q」",
|
||||
text="请以文字形式输入模拟器老板快捷键",
|
||||
qconfig=Config,
|
||||
configItem=Config.function_BossKey,
|
||||
@@ -981,7 +1037,7 @@ class NotifySettingCard(HeaderCardWidget):
|
||||
self.card_ServerChanChannel = LineEditSettingCard(
|
||||
icon=FluentIcon.PAGE_RIGHT,
|
||||
title="ServerChanChannel代码",
|
||||
content="可以留空,留空则默认。可以多个,请使用“|”隔开",
|
||||
content="可以留空,留空则默认。可以多个,请使用「|」隔开",
|
||||
text="请输入需要推送的Channel代码(SCT生效)",
|
||||
qconfig=Config,
|
||||
configItem=Config.notify_ServerChanChannel,
|
||||
@@ -990,7 +1046,7 @@ class NotifySettingCard(HeaderCardWidget):
|
||||
self.card_ServerChanTag = LineEditSettingCard(
|
||||
icon=FluentIcon.PAGE_RIGHT,
|
||||
title="Tag内容",
|
||||
content="可以留空,留空则默认。可以多个,请使用“|”隔开",
|
||||
content="可以留空,留空则默认。可以多个,请使用「|」隔开",
|
||||
text="请输入加入推送的Tag(SC3生效)",
|
||||
qconfig=Config,
|
||||
configItem=Config.notify_ServerChanTag,
|
||||
@@ -1056,9 +1112,17 @@ class SecuritySettingCard(HeaderCardWidget):
|
||||
content="修改用于解密用户密码的管理密钥",
|
||||
parent=self,
|
||||
)
|
||||
self.card_resetPASSWORD = PushSettingCard(
|
||||
text="重置",
|
||||
icon=FluentIcon.VPN,
|
||||
title="重置管理密钥",
|
||||
content="重置用于解密用户密码的管理密钥",
|
||||
parent=self,
|
||||
)
|
||||
|
||||
Layout = QVBoxLayout()
|
||||
Layout.addWidget(self.card_changePASSWORD)
|
||||
Layout.addWidget(self.card_resetPASSWORD)
|
||||
self.viewLayout.addLayout(Layout)
|
||||
|
||||
|
||||
@@ -1100,6 +1164,15 @@ class UpdaterSettingCard(HeaderCardWidget):
|
||||
configItem=Config.update_ThreadNumb,
|
||||
parent=self,
|
||||
)
|
||||
self.card_ProxyAddress = LineEditSettingCard(
|
||||
icon=FluentIcon.PAGE_RIGHT,
|
||||
title="网络代理地址",
|
||||
content="使用网络代理软件时,若出现网络连接问题,请尝试设置代理地址,此设置全局生效",
|
||||
text="请输入代理地址",
|
||||
qconfig=Config,
|
||||
configItem=Config.update_ProxyAddress,
|
||||
parent=self,
|
||||
)
|
||||
self.card_ProxyUrlList = UrlListSettingCard(
|
||||
icon=FluentIcon.SETTING,
|
||||
title="代理地址列表",
|
||||
@@ -1132,6 +1205,7 @@ class UpdaterSettingCard(HeaderCardWidget):
|
||||
Layout.addWidget(self.card_IfAutoUpdate)
|
||||
Layout.addWidget(self.card_UpdateType)
|
||||
Layout.addWidget(self.card_ThreadNumb)
|
||||
Layout.addWidget(self.card_ProxyAddress)
|
||||
Layout.addWidget(self.card_ProxyUrlList)
|
||||
Layout.addWidget(self.card_MirrorChyanCDK)
|
||||
self.viewLayout.addLayout(Layout)
|
||||
|
||||
@@ -1,9 +1,37 @@
|
||||
# AUTO_MAA:A MAA Multi Account Management and Automation Tool
|
||||
# Copyright © 2025 ClozyA
|
||||
|
||||
# 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
# Contact: DLmaster_361@163.com
|
||||
|
||||
"""
|
||||
AUTO_MAA
|
||||
AUTO_MAA图像组件
|
||||
v4.4
|
||||
作者:ClozyA
|
||||
"""
|
||||
|
||||
import base64
|
||||
import hashlib
|
||||
from pathlib import Path
|
||||
|
||||
from PIL import Image
|
||||
|
||||
|
||||
class ImageUtils:
|
||||
@staticmethod
|
||||
def get_base64_from_file(image_path):
|
||||
|
||||
169
app/utils/ProcessManager.py
Normal file
169
app/utils/ProcessManager.py
Normal file
@@ -0,0 +1,169 @@
|
||||
# AUTO_MAA:A MAA Multi Account Management and Automation Tool
|
||||
# Copyright © 2024-2025 DLmaster361
|
||||
|
||||
# This file is part of AUTO_MAA.
|
||||
|
||||
# AUTO_MAA is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License,
|
||||
# or (at your option) any later version.
|
||||
|
||||
# AUTO_MAA is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
|
||||
# the GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# Contact: DLmaster_361@163.com
|
||||
|
||||
"""
|
||||
AUTO_MAA
|
||||
AUTO_MAA进程管理组件
|
||||
v4.4
|
||||
作者:DLmaster_361
|
||||
"""
|
||||
|
||||
|
||||
import psutil
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
|
||||
from PySide6.QtCore import QTimer, QObject, Signal
|
||||
|
||||
|
||||
class ProcessManager(QObject):
|
||||
"""进程监视器类,用于跟踪主进程及其所有子进程的状态"""
|
||||
|
||||
processClosed = Signal()
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self.main_pid = None
|
||||
self.tracked_pids = set()
|
||||
|
||||
self.check_timer = QTimer()
|
||||
self.check_timer.timeout.connect(self.check_processes)
|
||||
|
||||
def open_process(
|
||||
self,
|
||||
path: Path,
|
||||
args: list = [],
|
||||
tracking_time: int = 60,
|
||||
) -> int:
|
||||
"""
|
||||
启动一个新进程并返回其pid,并开始监视该进程
|
||||
|
||||
:param path: 可执行文件的路径
|
||||
:param args: 启动参数列表
|
||||
:param tracking_time: 子进程追踪持续时间(秒)
|
||||
:return: 新进程的PID
|
||||
"""
|
||||
|
||||
process = subprocess.Popen(
|
||||
[path, *args],
|
||||
cwd=path.parent,
|
||||
creationflags=subprocess.CREATE_NO_WINDOW,
|
||||
stdin=subprocess.DEVNULL,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
)
|
||||
|
||||
self.start_monitoring(process.pid, tracking_time)
|
||||
|
||||
def start_monitoring(self, pid: int, tracking_time: int = 60) -> None:
|
||||
"""
|
||||
启动进程监视器,跟踪指定的主进程及其子进程
|
||||
|
||||
:param pid: 被监视进程的PID
|
||||
:param tracking_time: 子进程追踪持续时间(秒)
|
||||
"""
|
||||
|
||||
self.clear()
|
||||
|
||||
self.main_pid = pid
|
||||
self.tracking_time = tracking_time
|
||||
|
||||
# 扫描并记录所有相关进程
|
||||
try:
|
||||
# 获取主进程及其子进程
|
||||
main_proc = psutil.Process(self.main_pid)
|
||||
self.tracked_pids.add(self.main_pid)
|
||||
|
||||
# 递归获取所有子进程
|
||||
if tracking_time:
|
||||
for child in main_proc.children(recursive=True):
|
||||
self.tracked_pids.add(child.pid)
|
||||
|
||||
except psutil.NoSuchProcess:
|
||||
pass
|
||||
|
||||
# 启动持续追踪机制
|
||||
self.start_time = datetime.now()
|
||||
self.check_timer.start(100)
|
||||
|
||||
def check_processes(self) -> None:
|
||||
"""检查跟踪的进程是否仍在运行,并更新子进程列表"""
|
||||
|
||||
# 仅在时限内持续更新跟踪的进程列表,发现新的子进程
|
||||
if (datetime.now() - self.start_time).total_seconds() < self.tracking_time:
|
||||
|
||||
current_pids = set(self.tracked_pids)
|
||||
for pid in current_pids:
|
||||
try:
|
||||
proc = psutil.Process(pid)
|
||||
for child in proc.children():
|
||||
if child.pid not in self.tracked_pids:
|
||||
# 新发现的子进程
|
||||
self.tracked_pids.add(child.pid)
|
||||
except psutil.NoSuchProcess:
|
||||
continue
|
||||
|
||||
if not self.is_running():
|
||||
self.clear()
|
||||
self.processClosed.emit()
|
||||
|
||||
def is_running(self) -> bool:
|
||||
"""检查所有跟踪的进程是否还在运行"""
|
||||
|
||||
for pid in self.tracked_pids:
|
||||
try:
|
||||
proc = psutil.Process(pid)
|
||||
if proc.is_running():
|
||||
return True
|
||||
except psutil.NoSuchProcess:
|
||||
continue
|
||||
|
||||
return False
|
||||
|
||||
def kill(self, if_force: bool = False) -> None:
|
||||
"""停止监视器并中止所有跟踪的进程"""
|
||||
|
||||
self.check_timer.stop()
|
||||
|
||||
for pid in self.tracked_pids:
|
||||
try:
|
||||
proc = psutil.Process(pid)
|
||||
if if_force:
|
||||
kill_process = subprocess.Popen(
|
||||
["taskkill", "/F", "/T", "/PID", str(pid)],
|
||||
creationflags=subprocess.CREATE_NO_WINDOW,
|
||||
)
|
||||
kill_process.wait()
|
||||
proc.terminate()
|
||||
except psutil.NoSuchProcess:
|
||||
continue
|
||||
|
||||
if self.main_pid:
|
||||
self.processClosed.emit()
|
||||
self.clear()
|
||||
|
||||
def clear(self) -> None:
|
||||
"""清空跟踪的进程列表"""
|
||||
|
||||
self.main_pid = None
|
||||
self.check_timer.stop()
|
||||
self.tracked_pids.clear()
|
||||
@@ -21,7 +21,7 @@
|
||||
"""
|
||||
AUTO_MAA
|
||||
AUTO_MAA工具包
|
||||
v4.3
|
||||
v4.4
|
||||
作者:DLmaster_361
|
||||
"""
|
||||
|
||||
@@ -29,4 +29,7 @@ __version__ = "4.2.0"
|
||||
__author__ = "DLmaster361 <DLmaster_361@163.com>"
|
||||
__license__ = "GPL-3.0 license"
|
||||
|
||||
__all__ = []
|
||||
from .ImageUtils import ImageUtils
|
||||
from .ProcessManager import ProcessManager
|
||||
|
||||
__all__ = ["ImageUtils", "ProcessManager"]
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"""
|
||||
AUTO_MAA
|
||||
AUTO_MAA打包程序
|
||||
v4.3
|
||||
v4.4
|
||||
作者:DLmaster_361
|
||||
"""
|
||||
|
||||
@@ -70,7 +70,7 @@ if __name__ == "__main__":
|
||||
print("Packaging AUTO_MAA main program ...")
|
||||
|
||||
os.system(
|
||||
"powershell -Command python -m nuitka --standalone --onefile --mingw64"
|
||||
"powershell -Command python -m nuitka --standalone --onefile --mingw64 --windows-uac-admin"
|
||||
" --enable-plugins=pyside6 --windows-console-mode=attach"
|
||||
" --onefile-tempdir-spec='{TEMP}\\AUTO_MAA'"
|
||||
" --windows-icon-from-ico=resources\\icons\\AUTO_MAA.ico"
|
||||
|
||||
22
main.py
22
main.py
@@ -21,7 +21,7 @@
|
||||
"""
|
||||
AUTO_MAA
|
||||
AUTO_MAA主程序
|
||||
v4.3
|
||||
v4.4
|
||||
作者:DLmaster_361
|
||||
"""
|
||||
|
||||
@@ -45,9 +45,19 @@ builtins.print = no_print
|
||||
|
||||
|
||||
from loguru import logger
|
||||
import os
|
||||
import sys
|
||||
import ctypes
|
||||
from PySide6.QtWidgets import QApplication
|
||||
from qfluentwidgets import FluentTranslator
|
||||
import sys
|
||||
|
||||
|
||||
def is_admin() -> bool:
|
||||
"""检查当前程序是否以管理员身份运行"""
|
||||
try:
|
||||
return ctypes.windll.shell32.IsUserAnAdmin()
|
||||
except:
|
||||
return False
|
||||
|
||||
|
||||
@logger.catch
|
||||
@@ -68,4 +78,10 @@ def main():
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
main()
|
||||
if is_admin():
|
||||
main()
|
||||
else:
|
||||
ctypes.windll.shell32.ShellExecuteW(
|
||||
None, "runas", sys.executable, os.path.realpath(sys.argv[0]), None, 1
|
||||
)
|
||||
sys.exit(0)
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
loguru
|
||||
plyer
|
||||
PySide6
|
||||
loguru==0.7.3
|
||||
plyer==2.1.0
|
||||
PySide6==6.9.1
|
||||
PySide6-Fluent-Widgets[full]
|
||||
psutil
|
||||
pywin32
|
||||
keyboard
|
||||
pycryptodome
|
||||
requests
|
||||
markdown
|
||||
Jinja2
|
||||
nuitka
|
||||
pillow
|
||||
psutil==7.0.0
|
||||
pywin32==310
|
||||
keyboard==0.13.5
|
||||
pycryptodome==3.23.0
|
||||
certifi==2025.4.26
|
||||
truststore==0.10.1
|
||||
requests==2.32.4
|
||||
markdown==3.8.2
|
||||
Jinja2==3.1.6
|
||||
nuitka==2.7.12
|
||||
pillow==11.3.0
|
||||
@@ -24,6 +24,7 @@
|
||||
"MainFunction.Stage1": "" #主关卡
|
||||
"MainFunction.Stage2": "" #备选关卡1
|
||||
"MainFunction.Stage3": "" #备选关卡2
|
||||
"MainFunction.Stage4": "" #备选关卡3
|
||||
"Fight.RemainingSanityStage": "Annihilation" #剩余理智关卡
|
||||
"MainFunction.Series.Quantity": "1" #连战次数
|
||||
"Penguin.IsDrGrandet": "True" #博朗台模式
|
||||
|
||||
160
resources/html/general_result.html
Normal file
160
resources/html/general_result.html
Normal file
File diff suppressed because one or more lines are too long
200
resources/html/general_statistics.html
Normal file
200
resources/html/general_statistics.html
Normal file
File diff suppressed because one or more lines are too long
@@ -1,36 +1,60 @@
|
||||
{
|
||||
"main_version": "4.3.10.0",
|
||||
"main_version": "4.4.0.0",
|
||||
"version_info": {
|
||||
"4.3.10.0": {
|
||||
"4.4.0.0": {
|
||||
"新增功能": [
|
||||
"更换全新默认主页图",
|
||||
"适配 MAA 无`Default`配置情况 #52"
|
||||
],
|
||||
"程序优化": [
|
||||
"静默模式控制时段延长至模拟器完成启动的10s后"
|
||||
]
|
||||
},
|
||||
"4.3.10.3": {
|
||||
"程序优化": [
|
||||
"使用 keyboard 模块替代 pyautogui 模块"
|
||||
]
|
||||
},
|
||||
"4.3.10.2": {
|
||||
"新增功能": [
|
||||
"公招喜报模板优化",
|
||||
"支持使用命令行调用"
|
||||
"通用配置模式接入日志系统"
|
||||
],
|
||||
"修复BUG": [
|
||||
"修复更新动作重复执行问题"
|
||||
"信任系统证书,并添加网络代理地址配置项 #50",
|
||||
"适配 MAA 任务及基建设施日志翻译"
|
||||
],
|
||||
"程序优化": [
|
||||
"Mirror 酱链接添加`source`字段,用于标识来源",
|
||||
"优化下载器测速中止条件"
|
||||
"重构历史记录保存与载入逻辑"
|
||||
]
|
||||
},
|
||||
"4.3.10.1": {
|
||||
"4.4.0.5": {
|
||||
"新增功能": [
|
||||
"森空岛签到功能上线"
|
||||
"添加导入导出通用配置功能"
|
||||
],
|
||||
"修复BUG": [
|
||||
"修复开机自启相关功能"
|
||||
]
|
||||
},
|
||||
"4.4.0.4": {
|
||||
"新增功能": [
|
||||
"添加重置管理密钥功能"
|
||||
],
|
||||
"修复BUG": [
|
||||
"修复无计划表时数据系统无法正常升级到v1.7的问题"
|
||||
]
|
||||
},
|
||||
"4.4.0.3": {
|
||||
"修复BUG": [
|
||||
"适配 MAA 备选关卡字段修改",
|
||||
"修复无成功日志时的脚本判定逻辑"
|
||||
],
|
||||
"程序优化": [
|
||||
"`GameId`字段改为 `Stage`,与 MAA 保持一致"
|
||||
]
|
||||
},
|
||||
"4.4.0.2": {
|
||||
"新增功能": [
|
||||
"进一步适配三月七相关配置项"
|
||||
],
|
||||
"修复BUG": [
|
||||
"适配 Mirror 酱 平台下载策略调整"
|
||||
]
|
||||
},
|
||||
"4.4.0.1": {
|
||||
"新增功能": [
|
||||
"初步完成通用调度模块"
|
||||
],
|
||||
"修复BUG": [
|
||||
"修复了程序BUG较少的BUG"
|
||||
],
|
||||
"程序优化": [
|
||||
"子线程卡死不再阻塞调度任务"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user