diff --git a/.github/workflows/build-app-test.yml b/.github/workflows/build-app-test.yml new file mode 100644 index 0000000..21290ce --- /dev/null +++ b/.github/workflows/build-app-test.yml @@ -0,0 +1,173 @@ +# AUTO_MAA:A MAA Multi Account Management and Automation Tool +# Copyright © 2024-2025 DLmaster361 + +# This file is part of AUTO_MAA. + +# AUTO_MAA is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published +# by the Free Software Foundation, either version 3 of the License, +# or (at your option) any later version. + +# AUTO_MAA is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty +# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See +# the GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with AUTO_MAA. If not, see . + +# Contact: DLmaster_361@163.com + +name: Build AUTO_MAA + +on: + workflow_dispatch: + +permissions: + contents: write + actions: write + +jobs: + pre_check: + name: Pre Checks + runs-on: ubuntu-latest + steps: + - name: Repo Check + id: repo_check + run: | + if [[ "$GITHUB_REPOSITORY" != "DLmaster361/AUTO_MAA" ]]; then + echo "When forking this repository to make your own builds, you have to adjust this check." + exit 1 + fi + exit 0 + build_AUTO_MAA: + runs-on: windows-latest + needs: pre_check + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install flake8 pytest + pip install -r requirements.txt + + - name: Lint with flake8 + run: | + # stop the build if there are Python syntax errors or undefined names + flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + + - name: Read version + id: get_version + run: | + $version = (Get-Content resources/version.json | ConvertFrom-Json).main_version + echo "main_version=$version" >> $env:GITHUB_OUTPUT + + - name: Nuitka build main program + uses: Nuitka/Nuitka-Action@main + with: + script-name: main.py + mode: app + enable-plugins: pyside6 + onefile-tempdir-spec: "{TEMP}/AUTO_MAA" + windows-console-mode: disable + windows-icon-from-ico: resources/icons/AUTO_MAA.ico + company-name: AUTO_MAA Team + product-name: AUTO_MAA + file-version: ${{ steps.get_version.outputs.main_version }} + product-version: ${{ steps.get_version.outputs.main_version }} + file-description: AUTO_MAA Component + copyright: Copyright © 2024-2025 DLmaster361 + assume-yes-for-downloads: true + output-file: AUTO_MAA + output-dir: AUTO_MAA + + - name: Prepare setup files + shell: pwsh + run: | + $root = "${{ github.workspace }}" + $ver = "${{ steps.get_version.outputs.main_version }}" + Copy-Item "$root/app" "$root/AUTO_MAA/app" -Recurse + Copy-Item "$root/resources" "$root/AUTO_MAA/resources" -Recurse + Copy-Item "$root/main.py" "$root/AUTO_MAA/" + Copy-Item "$root/requirements.txt" "$root/AUTO_MAA/" + Copy-Item "$root/README.md" "$root/AUTO_MAA/" + Copy-Item "$root/LICENSE" "$root/AUTO_MAA/" + + - name: Prepare Inno Setup script + shell: pwsh + run: | + $root = "${{ github.workspace }}" + $ver = "${{ steps.get_version.outputs.main_version }}" + $iss = Get-Content "$root/app/utils/AUTO_MAA.iss" -Raw + $iss = $iss -replace '#define MyAppVersion ""', "#define MyAppVersion `"$ver`"" + $iss = $iss -replace '#define MyAppPath ""', "#define MyAppPath `"$root/AUTO_MAA`"" + $iss = $iss -replace '#define OutputDir ""', "#define OutputDir `"$root`"" + Set-Content -Path "$root/AUTO_MAA.iss" -Value $iss + + - name: Build setup program + uses: Minionguyjpro/Inno-Setup-Action@v1.2.2 + with: + path: ${{ github.workspace }}/AUTO_MAA.iss + + - name: Zip setup exe + shell: pwsh + run: | + $root = "${{ github.workspace }}" + New-Item -ItemType Directory -Force -Path "$root/AUTO_MAA_Setup" + Move-Item "$root/AUTO_MAA-Setup.exe" "$root/AUTO_MAA_Setup/" + Compress-Archive -Path AUTO_MAA_Setup/* -DestinationPath AUTO_MAA_${{ steps.get_version.outputs.main_version }}.zip + + - name: Generate version info + shell: python + run: | + import json + from pathlib import Path + def version_text(version_numb): + while len(version_numb) < 4: + version_numb.append(0) + if version_numb[3] == 0: + return f"v{'.'.join(str(_) for _ in version_numb[0:3])}" + else: + return f"v{'.'.join(str(_) for _ in version_numb[0:3])}-beta.{version_numb[3]}" + def version_info_markdown(info): + version_info = "" + for key, value in info.items(): + version_info += f"## {key}\n" + for v in value: + version_info += f"- {v}\n" + return version_info + root_path = Path(".") + version = json.loads((root_path / "resources/version.json").read_text(encoding="utf-8")) + main_version_numb = list(map(int, version["main_version"].split("."))) + all_version_info = {} + for v_i in version["version_info"].values(): + for key, value in v_i.items(): + if key in all_version_info: + all_version_info[key] += value.copy() + else: + all_version_info[key] = value.copy() + (root_path / "version_info.txt").write_text( + f"{version_text(main_version_numb)}\n\n\n{version_info_markdown(all_version_info)}", + encoding="utf-8", + ) + + - name: Upload Artifact + uses: actions/upload-artifact@v4 + with: + name: AUTO_MAA_${{ steps.get_version.outputs.main_version }} + path: AUTO_MAA_${{ steps.get_version.outputs.main_version }}.zip + - name: Upload Version_Info Artifact + uses: actions/upload-artifact@v4 + with: + name: version_info + path: version_info.txt \ No newline at end of file diff --git a/app/core/config.py b/app/core/config.py index b99afff..1789131 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -567,7 +567,7 @@ class MaaPlanConfig(LQConfig): class AppConfig(GlobalConfig): - VERSION = "4.3.8.0" + VERSION = "4.3.9.1" gameid_refreshed = Signal() PASSWORD_refreshed = Signal() @@ -652,18 +652,20 @@ class AppConfig(GlobalConfig): def get_gameid(self) -> None: # 从MAA服务器获取活动关卡信息 - Network.set_info( + network = Network.add_task( mode="get", url="https://api.maa.plus/MaaAssistantArknights/api/gui/StageActivity.json", ) - Network.start() - Network.loop.exec() - if Network.stutus_code == 200: + network.loop.exec() + network_result = Network.get_result(network) + if network_result["status_code"] == 200: gameid_infos: List[Dict[str, Union[str, Dict[str, Union[str, int]]]]] = ( - Network.response_json["Official"]["sideStoryStage"] + network_result["response_json"]["Official"]["sideStoryStage"] ) else: - logger.warning(f"无法从MAA服务器获取活动关卡信息:{Network.error_message}") + logger.warning( + f"无法从MAA服务器获取活动关卡信息:{network_result['error_message']}" + ) gameid_infos = [] ss_gameid_dict = {"value": [], "text": []} diff --git a/app/core/network.py b/app/core/network.py index 5c91d8b..628b6cf 100644 --- a/app/core/network.py +++ b/app/core/network.py @@ -26,55 +26,41 @@ v4.3 """ from loguru import logger -from PySide6.QtCore import QThread, QEventLoop, QTimer +from PySide6.QtCore import QObject, QThread, QEventLoop import time import requests from pathlib import Path -class _Network(QThread): +class NetworkThread(QThread): + """网络请求线程类""" max_retries = 3 timeout = 10 backoff_factor = 0.1 - def __init__(self) -> None: + def __init__(self, mode: str, url: str, path: Path = None) -> None: super().__init__() - self.if_running = False - self.mode = None - self.url = None - self.loop = QEventLoop() - self.wait_loop = QEventLoop() - - @logger.catch - def run(self) -> None: - """运行网络请求线程""" - - self.if_running = True - - if self.mode == "get": - self.get_json(self.url) - elif self.mode == "get_file": - self.get_file(self.url, self.path) - - self.if_running = False - - def set_info(self, mode: str, url: str, path: Path = None) -> None: - """设置网络请求信息""" - - while self.if_running: - QTimer.singleShot(self.backoff_factor * 1000, self.wait_loop.quit) - self.wait_loop.exec() - self.mode = mode self.url = url self.path = path - self.stutus_code = None + self.status_code = None self.response_json = None self.error_message = None + self.loop = QEventLoop() + + @logger.catch + def run(self) -> None: + """运行网络请求线程""" + + if self.mode == "get": + self.get_json(self.url) + elif self.mode == "get_file": + self.get_file(self.url, self.path) + def get_json(self, url: str) -> None: """通过get方法获取json数据""" @@ -83,12 +69,12 @@ class _Network(QThread): for _ in range(self.max_retries): try: response = requests.get(url, timeout=self.timeout) - self.stutus_code = response.status_code + self.status_code = response.status_code self.response_json = response.json() self.error_message = None break except Exception as e: - self.stutus_code = response.status_code if response else None + self.status_code = response.status_code if response else None self.response_json = None self.error_message = str(e) time.sleep(self.backoff_factor) @@ -105,16 +91,47 @@ class _Network(QThread): if response.status_code == 200: with open(path, "wb") as file: file.write(response.content) - self.stutus_code = response.status_code + self.status_code = response.status_code else: - self.stutus_code = response.status_code + self.status_code = response.status_code self.error_message = "下载失败" except Exception as e: - self.stutus_code = response.status_code if response else None + self.status_code = response.status_code if response else None self.error_message = str(e) self.loop.quit() +class _Network(QObject): + """网络请求线程类""" + + def __init__(self) -> None: + super().__init__() + + self.task_queue = [] + + def add_task(self, mode: str, url: str, path: Path = None) -> NetworkThread: + """添加网络请求任务""" + + network_thread = NetworkThread(mode, url, path) + + self.task_queue.append(network_thread) + + network_thread.start() + + return network_thread + + def get_result(self, network_thread: NetworkThread) -> dict: + """获取网络请求结果""" + + self.task_queue.remove(network_thread) + + return { + "status_code": network_thread.status_code, + "response_json": network_thread.response_json, + "error_message": network_thread.error_message, + } + + Network = _Network() diff --git a/app/core/task_manager.py b/app/core/task_manager.py index ce97f21..eefbc77 100644 --- a/app/core/task_manager.py +++ b/app/core/task_manager.py @@ -277,20 +277,20 @@ class _TaskManager(QObject): def check_maa_version(self, v: str): """检查MAA版本""" - Network.set_info( + network = Network.add_task( mode="get", url="https://mirrorchyan.com/api/resources/MAA/latest?user_agent=AutoMaaGui&os=win&arch=x64&channel=stable", ) - Network.start() - Network.loop.exec() - if Network.stutus_code == 200: - maa_info = Network.response_json + network.loop.exec() + network_result = Network.get_result(network) + if network_result["status_code"] == 200: + maa_info = network_result["response_json"] else: - logger.warning(f"获取MAA版本信息时出错:{Network.error_message}") + logger.warning(f"获取MAA版本信息时出错:{network_result['error_message']}") MainInfoBar.push_info_bar( "warning", "获取MAA版本信息时出错", - f"网络错误:{Network.stutus_code}", + f"网络错误:{network_result['status_code']}", 5000, ) return None diff --git a/app/core/timer.py b/app/core/timer.py index c09bd7f..43c0da3 100644 --- a/app/core/timer.py +++ b/app/core/timer.py @@ -26,8 +26,7 @@ v4.3 """ from loguru import logger -from PySide6.QtWidgets import QWidget -from PySide6.QtCore import QTimer +from PySide6.QtCore import QObject, QTimer from datetime import datetime from pathlib import Path import pyautogui @@ -37,7 +36,7 @@ from .task_manager import TaskManager from app.services import System -class _MainTimer(QWidget): +class _MainTimer(QObject): def __init__(self, parent=None): super().__init__(parent) diff --git a/app/models/MAA.py b/app/models/MAA.py index d43de4e..1f39c54 100644 --- a/app/models/MAA.py +++ b/app/models/MAA.py @@ -426,11 +426,11 @@ class MaaManager(QObject): # 任务开始前释放ADB try: + logger.info(f"{self.name} | 释放ADB:{self.ADB_address}") subprocess.run( [self.ADB_path, "disconnect", self.ADB_address], creationflags=subprocess.CREATE_NO_WINDOW, ) - logger.info(f"{self.name} | 释放ADB:{self.ADB_address}") except subprocess.CalledProcessError as e: # 忽略错误,因为可能本来就没有连接 logger.warning(f"{self.name} | 释放ADB时出现异常:{e}") @@ -445,13 +445,13 @@ class MaaManager(QObject): if self.if_open_emulator_process: try: + 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, ) - logger.info( - f"{self.name} | 启动模拟器:{self.emulator_path},参数:{self.emulator_arguments}" - ) except Exception as e: logger.error(f"{self.name} | 启动模拟器时出现异常:{e}") self.push_info_bar.emit( @@ -571,11 +571,11 @@ class MaaManager(QObject): # 任务结束后释放ADB try: + logger.info(f"{self.name} | 释放ADB:{self.ADB_address}") subprocess.run( [self.ADB_path, "disconnect", self.ADB_address], creationflags=subprocess.CREATE_NO_WINDOW, ) - logger.info(f"{self.name} | 释放ADB:{self.ADB_address}") except subprocess.CalledProcessError as e: # 忽略错误,因为可能本来就没有连接 logger.warning(f"{self.name} | 释放ADB时出现异常:{e}") diff --git a/app/services/notification.py b/app/services/notification.py index 7d17545..21486d5 100644 --- a/app/services/notification.py +++ b/app/services/notification.py @@ -34,8 +34,7 @@ from email.mime.text import MIMEText from email.utils import formataddr import requests -from PySide6.QtCore import Signal -from PySide6.QtWidgets import QWidget +from PySide6.QtCore import QObject, Signal from loguru import logger from plyer import notification @@ -43,7 +42,7 @@ from app.core import Config from app.services.security import Crypto -class Notification(QWidget): +class Notification(QObject): push_info_bar = Signal(str, str, str, int) diff --git a/app/ui/home.py b/app/ui/home.py index 9a60344..475230a 100644 --- a/app/ui/home.py +++ b/app/ui/home.py @@ -199,20 +199,22 @@ class Home(QWidget): elif Config.get(Config.function_HomeImageMode) == "主题图像": # 从远程服务器获取最新主题图像 - Network.set_info( + network = Network.add_task( mode="get", url="https://gitee.com/DLmaster_361/AUTO_MAA/raw/server/theme_image.json", ) - Network.start() - Network.loop.exec() - if Network.stutus_code == 200: - theme_image = Network.response_json + network.loop.exec() + network_result = Network.get_result(network) + if network_result["status_code"] == 200: + theme_image = network_result["response_json"] else: - logger.warning(f"获取最新主题图像时出错:{Network.error_message}") + logger.warning( + f"获取最新主题图像时出错:{network_result['error_message']}" + ) MainInfoBar.push_info_bar( "warning", "获取最新主题图像时出错", - f"网络错误:{Network.stutus_code}", + f"网络错误:{network_result['status_code']}", 5000, ) return None @@ -236,15 +238,15 @@ class Home(QWidget): > time_local ): - Network.set_info( + network = Network.add_task( mode="get_file", url=theme_image["url"], path=Config.app_path / "resources/images/Home/BannerTheme.jpg", ) - Network.start() - Network.loop.exec() + network.loop.exec() + network_result = Network.get_result(network) - if Network.stutus_code == 200: + if network_result["status_code"] == 200: with (Config.app_path / "resources/theme_image.json").open( mode="w", encoding="utf-8" @@ -261,11 +263,13 @@ class Home(QWidget): else: - logger.warning(f"下载最新主题图像时出错:{Network.error_message}") + logger.warning( + f"下载最新主题图像时出错:{network_result['error_message']}" + ) MainInfoBar.push_info_bar( "warning", "下载最新主题图像时出错", - f"网络错误:{Network.stutus_code}", + f"网络错误:{network_result['status_code']}", 5000, ) diff --git a/app/ui/member_manager.py b/app/ui/member_manager.py index 82865af..4952690 100644 --- a/app/ui/member_manager.py +++ b/app/ui/member_manager.py @@ -333,20 +333,20 @@ class MemberManager(QWidget): return None # 从远程服务器获取应用列表 - Network.set_info( + network = Network.add_task( mode="get", url="https://gitee.com/DLmaster_361/AUTO_MAA/raw/server/apps_info.json", ) - Network.start() - Network.loop.exec() - if Network.stutus_code == 200: - apps_info = Network.response_json + network.loop.exec() + network_result = Network.get_result(network) + if network_result["status_code"] == 200: + apps_info = network_result["response_json"] else: - logger.warning(f"获取应用列表时出错:{Network.error_message}") + logger.warning(f"获取应用列表时出错:{network_result['error_message']}") MainInfoBar.push_info_bar( "warning", "获取应用列表时出错", - f"网络错误:{Network.stutus_code}", + f"网络错误:{network_result['status_code']}", 5000, ) return None @@ -376,19 +376,19 @@ class MemberManager(QWidget): return None # 从mirrorc服务器获取最新版本信息 - Network.set_info( + network = Network.add_task( mode="get", url=f"https://mirrorchyan.com/api/resources/{app_rid}/latest?user_agent=AutoMaaGui&cdk={Crypto.win_decryptor(Config.get(Config.update_MirrorChyanCDK))}&os={apps_info[app_name]["os"]}&arch={apps_info[app_name]["arch"]}&channel=stable", ) - Network.start() - Network.loop.exec() - if Network.stutus_code == 200: - app_info = Network.response_json + network.loop.exec() + network_result = Network.get_result(network) + if network_result["status_code"] == 200: + app_info = network_result["response_json"] else: - if Network.response_json: + if network_result["response_json"]: - app_info = Network.response_json + app_info = network_result["response_json"] if app_info["code"] != 0: @@ -425,11 +425,11 @@ class MemberManager(QWidget): return None - logger.warning(f"获取版本信息时出错:{Network.error_message}") + logger.warning(f"获取版本信息时出错:{network_result['error_message']}") MainInfoBar.push_info_bar( "warning", "获取版本信息时出错", - f"网络错误:{Network.stutus_code}", + f"网络错误:{network_result['status_code']}", 5000, ) return None diff --git a/app/ui/setting.py b/app/ui/setting.py index 84fda41..23cd5f4 100644 --- a/app/ui/setting.py +++ b/app/ui/setting.py @@ -262,31 +262,26 @@ class Setting(QWidget): current_version = list(map(int, Config.VERSION.split("."))) - if Network.if_running and if_show: - MainInfoBar.push_info_bar( - "warning", "请求速度过快", "上个网络请求还未结束,请稍等片刻", 5000 - ) - return None # 从远程服务器获取最新版本信息 - Network.set_info( + network = Network.add_task( mode="get", url=f"https://mirrorchyan.com/api/resources/AUTO_MAA/latest?user_agent=AutoMaaGui¤t_version={version_text(current_version)}&cdk={Crypto.win_decryptor(Config.get(Config.update_MirrorChyanCDK))}&channel={Config.get(Config.update_UpdateType)}", ) - Network.start() - Network.loop.exec() - if Network.stutus_code == 200: - version_info: Dict[str, Union[int, str, Dict[str, str]]] = ( - Network.response_json - ) + network.loop.exec() + network_result = Network.get_result(network) + if network_result["status_code"] == 200: + version_info: Dict[str, Union[int, str, Dict[str, str]]] = network_result[ + "response_json" + ] else: - if Network.response_json: + if network_result["response_json"]: - version_info = Network.response_json + version_info = network_result["response_json"] if version_info["code"] != 0: - logger.error(f"获取版本信息时出错:{version_info["msg"]}") + logger.error(f"获取版本信息时出错:{version_info['msg']}") error_remark_dict = { 1001: "获取版本信息的URL参数不正确", @@ -319,11 +314,11 @@ class Setting(QWidget): return None - logger.warning(f"获取版本信息时出错:{Network.error_message}") + logger.warning(f"获取版本信息时出错:{network_result['error_message']}") MainInfoBar.push_info_bar( "warning", "获取版本信息时出错", - f"网络错误:{Network.stutus_code}", + f"网络错误:{network_result['status_code']}", 5000, ) return None @@ -406,20 +401,22 @@ class Setting(QWidget): else: # 从远程服务器获取代理信息 - Network.set_info( + network = Network.add_task( mode="get", url="https://gitee.com/DLmaster_361/AUTO_MAA/raw/server/download_info.json", ) - Network.start() - Network.loop.exec() - if Network.stutus_code == 200: - download_info = Network.response_json + network.loop.exec() + network_result = Network.get_result(network) + if network_result["status_code"] == 200: + download_info = network_result["response_json"] else: - logger.warning(f"获取应用列表时出错:{Network.error_message}") + logger.warning( + f"获取应用列表时出错:{network_result['error_message']}" + ) MainInfoBar.push_info_bar( "warning", "获取应用列表时出错", - f"网络错误:{Network.stutus_code}", + f"网络错误:{network_result['status_code']}", 5000, ) return None @@ -488,20 +485,20 @@ class Setting(QWidget): """显示公告""" # 从远程服务器获取最新公告 - Network.set_info( + network = Network.add_task( mode="get", url="https://gitee.com/DLmaster_361/AUTO_MAA/raw/server/notice.json", ) - Network.start() - Network.loop.exec() - if Network.stutus_code == 200: - notice = Network.response_json + network.loop.exec() + network_result = Network.get_result(network) + if network_result["status_code"] == 200: + notice = network_result["response_json"] else: - logger.warning(f"获取最新公告时出错:{Network.error_message}") + logger.warning(f"获取最新公告时出错:{network_result['error_message']}") MainInfoBar.push_info_bar( "warning", "获取最新公告时出错", - f"网络错误:{Network.stutus_code}", + f"网络错误:{network_result['status_code']}", 5000, ) return None diff --git a/resources/version.json b/resources/version.json index c0e4e71..fb2309d 100644 --- a/resources/version.json +++ b/resources/version.json @@ -1,53 +1,12 @@ { - "main_version": "4.3.8.0", + "main_version": "4.3.9.1", "version_info": { - "4.3.8.0": { - "新增功能": [ - "吐司通知在主窗口隐藏时不再弹出" - ] - }, - "4.3.8.4": { - "新增功能": [ - "支持为每一个用户执行独立通知", - "输入文本框适配文本插入操作", - "计划表功能上线", - "静默控制时长从全任务内缩短至搜索ADB时段内", - "UI界面添加自动日常代理任务序列设置项" - ], + "4.3.9.1": { "修复bug": [ - "修复雷电模拟器静默模式无法正常识别模拟器是否隐藏相关问题" - ] - }, - "4.3.8.3": { - "新增功能": [ - "用户仪表盘支持直接控制用户状态" - ], - "修复bug": [ - "修复雷电ADB端口号相关问题" - ] - }, - "4.3.8.2": { - "新增功能": [ - "添加ADB端口号宽幅适配能力" - ], - "修复bug": [ - "日志分析忽略MAA超时提示" + "网络模块支持并发请求" ], "程序优化": [ - "配置类定义方法优化" - ] - }, - "4.3.8.1": { - "新增功能": [ - "自定义基建显示配置名称 #46", - "主调度台添加仅一次电源任务" - ], - "修复bug": [ - "电源相关选项改为所有任务完成后生效", - "适配MAAv5.16.3的ADB报错信息更改" - ], - "程序优化": [ - "UI样式优化,进一步适配win10主题" + "非UI组件转为QObject类" ] } }