Compare commits

..

76 Commits

Author SHA1 Message Date
DLmaster361
21e7df7c3e Merge branch 'dev' 2025-06-01 20:04:16 +08:00
DLmaster361
2d72ca66a4 fix(core): 修复网络模块子线程未及时销毁导致的程序崩溃 2025-06-01 19:52:22 +08:00
DLmaster361
4725a30165 fix(ui): 修复语音包禁忌二重奏 2025-06-01 04:31:29 +08:00
DLmaster361
f3c977f1b3 Merge branch 'main' into dev 2025-06-01 03:18:39 +08:00
DLmaster361
9a0e7265c6 feat(core): 语音功能上线 2025-06-01 03:16:56 +08:00
DLmaster361
3f8e2fbe6b Merge branch 'dev' 2025-05-31 13:37:06 +08:00
DLmaster361
590b13e916 ci: 移除测试打包流程 2025-05-31 13:36:09 +08:00
DLmaster361
0f6aee56e5 ci: 修正测试工作流名称 2025-05-31 11:21:13 +08:00
DLmaster361
daf18e7295 Merge branch 'dev' 2025-05-31 11:20:04 +08:00
DLmaster361
9bcc87f663 ci: 添加引入打包action的测试工作流 2025-05-31 11:19:36 +08:00
DLmaster361
e7205ce0aa fix(services): 非UI组件转为QObject类 2025-05-30 21:06:39 +08:00
DLmaster361
e3c4b2edc8 fix(core): 网络模块支持并发请求 2025-05-30 20:30:23 +08:00
DLmaster361
222a3b35a2 fix(maa): 修复ADB与模拟器相关日志信息报错时不显示 2025-05-29 21:06:09 +08:00
DLmaster361
cd5dfd56b2 Merge branch 'dev' 2025-05-28 23:13:15 +08:00
DLmaster361
7d5c6b8222 docs: 添加DeepWiki徽章 2025-05-28 23:12:56 +08:00
DLmaster361
4dbf4736e4 Merge branch 'dev' 2025-05-28 21:18:24 +08:00
DLmaster361
d50504181e feat(ui): 吐司通知在主窗口隐藏时不再弹出 2025-05-28 21:17:14 +08:00
DLmaster361
c7e94dfcd1 Merge branch 'DLMS_dev' into dev 2025-05-27 18:17:29 +08:00
DLmaster361
a752b67ca1 feat(ui): UI界面添加自动日常代理任务序列设置项 2025-05-26 16:05:46 +08:00
DLmaster361
078736337d fix: 修复雷电模拟器静默模式无法正常识别模拟器是否隐藏相关问题 2025-05-25 23:31:37 +08:00
DLmaster361
de1058a28c feat(core): 计划表功能上线 2025-05-25 21:12:22 +08:00
DLmaster361
740797a689 Merge branch 'dev' into DLMS_dev 2025-05-24 19:56:16 +08:00
DLmaster361
26328920a2 chore(ui): 输入文本框适配文本插入操作 2025-05-24 19:55:49 +08:00
DLmaster361
9c447bbdf9 Merge branch 'ClozyA_dev' into dev 2025-05-24 18:34:03 +08:00
DLmaster361
fac85a889f fix(ui): 简单优化用户通知显示效果 2025-05-24 18:33:46 +08:00
DLmaster361
f5d898c89e fix(maa): 将通知判定过程全部移入push_notification 2025-05-23 22:59:09 +08:00
974a4b634a feat(ui): 优化敏感信息显示逻辑 2025-05-23 15:19:35 +08:00
3127c83603 feat(notification): 增加用户单独通知功能 2025-05-23 15:01:26 +08:00
DLmaster361
8d69e43f72 feat(ui): 初步添加周计划表 2025-05-22 23:20:38 +08:00
DLmaster361
86df9e7a50 chore(ui): 优化二级菜单显示效果 2025-05-21 01:00:38 +08:00
59ff9bf818 refactor(notification): 重构通知模块 2025-05-20 02:11:54 +08:00
DLmaster361
1641e32e3d feat(ui): 完成完整用户通知子菜单设计 2025-05-19 17:31:50 +08:00
DLmaster361
a482087abd feat(ui): 初步完成用户通知相关界面 2025-05-19 16:54:14 +08:00
bc5b15cec2 feat(notification): 优化用户通知服务 2025-05-19 01:10:20 +08:00
3787c25a77 Merge branch 'dev' into ClozyA_dev 2025-05-19 00:18:37 +08:00
DLmaster361
0b06b499e4 fix(models): 修复雷电ADB端口号相关问题 2025-05-18 23:35:35 +08:00
DLmaster361
04079dd57b Merge branch 'dev' of github.com:DLmaster361/AUTO_MAA into dev 2025-05-18 13:54:19 +08:00
DLmaster361
34ac0c5ab3 fix: 主动关闭剩余理智选项 2025-05-18 13:54:14 +08:00
0d904b229e feat(core): 初步完成新增用户单独通知功能 2025-05-18 01:57:48 +08:00
c0f887ff9d refactor(notification): 更新任务报告标题格式,增加日期前缀 2025-05-18 00:17:06 +08:00
DLmaster361
cf95075d01 fix(ui): 修复窗口最大化功能异常 2025-05-17 22:26:41 +08:00
DLmaster361
d78a764d87 fix: 修正搜索ADB时的等待方法 2025-05-17 00:07:34 +08:00
DLmaster
a368f4b722 feat(ui): 用户仪表盘支持直接控制用户状态
优化仪表盘排版
2025-05-16 23:27:53 +08:00
DLmaster361
803fe4568f chore(ui): 优化实现方法 2025-05-16 23:23:02 +08:00
DLmaster361
1162d5dcc1 feat(ui): 添加对剩余天数为0的展示 2025-05-16 22:50:54 +08:00
Zrief
aa83058e39 Update member_manager.py 2025-05-16 21:56:20 +08:00
Zrief
061f205224 Merge branch 'DLmaster361:dev' into dev 2025-05-16 21:55:16 +08:00
Zrief
5d966f98df 修改逻辑不完备的地方 2025-05-16 21:53:03 +08:00
DLmaster361
0037914db8 Merge branch 'DLMS_dev' into dev 2025-05-16 20:41:04 +08:00
DLmaster361
13d0115475 feat(core): 添加ADB端口号宽幅适配能力 2025-05-16 20:09:29 +08:00
Zrief
5bdb5ad2bf 优化脚本仪表盘排版
可以直接在仪表盘里面开关用户状态了
2025-05-15 21:20:30 +08:00
a5d733c8df feat(notification): 为自动代理统计报告添加日期前缀 2025-05-15 16:39:56 +08:00
DLmaster361
0b038e2997 Merge branch 'main' into dev 2025-05-12 14:54:17 +08:00
DLmaster361
5e46040db6 feat: 日志分析忽略MAA超时提示 2025-05-12 14:53:57 +08:00
DLmaster361
f2b04dd0f6 Merge branch 'dev' 2025-05-11 23:28:18 +08:00
DLmaster361
2177c1b40e fix(models): 适配MAAv5.16.3的ADB报错信息更改 2025-05-11 23:26:42 +08:00
DLmaster361
d1f4cffe8f feat(ui): 主调度台添加仅一次电源任务,UI样式优化 2025-05-10 23:15:54 +08:00
DLmaster361
74ce441b90 feat(core): 电源相关选项改为所有任务完成后生效 2025-05-09 21:52:18 +08:00
DLmaster361
5893aa2426 feat(ui): 自定义基建显示配置名称 2025-05-09 19:54:07 +08:00
DLmaster361
cb7e7bf9d4 Merge branch 'dev' 2025-05-09 14:54:29 +08:00
DLmaster361
fbfdc6aa12 feat: 优化更新方式 2025-05-09 14:54:09 +08:00
DLmaster361
e7b6743e10 test: 移除测试ci 2025-05-06 17:43:23 +08:00
DLmaster361
ff4283e917 Merge branch 'dev' 2025-05-06 17:42:42 +08:00
DLmaster361
890886d62d fix(ci): 适配新下载站模式 2025-05-06 17:42:24 +08:00
DLmaster361
fd75dda2b1 feat(ui): 适配MAA连战次数AUTO模式 2025-05-06 13:57:49 +08:00
DLmaster361
f22c1aeae3 test(ci): 补充步骤 2025-05-06 13:07:56 +08:00
DLmaster361
d68d49a469 test(ci): 更正环境 2025-05-06 13:00:04 +08:00
DLmaster361
1900d4eaf5 test(ci): 测试ssh部分 2025-05-06 12:57:55 +08:00
DLmaster361
02833209d5 Merge branch 'dev' 2025-05-06 00:18:44 +08:00
2058c0218c ci: 更新下载服务器配置 2025-05-06 00:09:14 +08:00
DLmaster361
8896e723eb fix: 补充版本号 2025-05-05 20:14:25 +08:00
DLmaster361
edcc614833 Merge commit '23fe1ff0beb234eb7ebbc07b189782b7fb84d5e5' 2025-05-05 20:13:22 +08:00
DLmaster361
23fe1ff0be feat: release中添加安装程序 2025-05-05 20:12:59 +08:00
DLmaster361
19d1dc9f28 fix: 修复详细模式下非定时自定义基建无法正常换班的问题 2025-05-05 15:59:07 +08:00
DLmaster361
24b93cfcad feat: 下载器支持调用Mirror酱 2025-05-04 23:43:04 +08:00
DLmaster361
d3298fac8a fix: 移除serverchan_sdk 2025-05-04 17:01:36 +08:00
73 changed files with 4377 additions and 1837 deletions

View File

@@ -55,6 +55,8 @@ jobs:
python -m pip install --upgrade pip
pip install flake8 pytest
pip install -r requirements.txt
choco install innosetup
echo "C:\Program Files (x86)\Inno Setup 6" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
@@ -71,14 +73,11 @@ jobs:
run: |
$MAIN_VERSION=(Get-Content -Path "version_info.txt" -TotalCount 1).Trim()
"AUTO_MAA_version=$MAIN_VERSION" | Out-File -FilePath $env:GITHUB_ENV -Append
$UPDATER_VERSION=(Get-Content -Path "version_info.txt" -TotalCount 2 | Select-Object -Index 1).Trim()
"updater_version=$UPDATER_VERSION" | Out-File -FilePath $env:GITHUB_ENV -Append
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: AUTO_MAA_${{ env.AUTO_MAA_version }}
path: |
AUTO_MAA_${{ env.AUTO_MAA_version }}.zip
path: AUTO_MAA_${{ env.AUTO_MAA_version }}.zip
- name: Upload Version_Info Artifact
uses: actions/upload-artifact@v4
with:
@@ -110,8 +109,11 @@ jobs:
NAME="$(sed 's/\r$//g' <(head -n 1 version_info.txt))"
TAGNAME="$(sed 's/\r$//g' <(head -n 1 version_info.txt))"
NOTES_MAIN="$(sed 's/\r$//g' <(tail -n +3 version_info.txt))"
NOTES_TAIL="\`\`\`本release通过GitHub Actions自动构建\`\`\`"
NOTES="$NOTES_MAIN<br><br>$NOTES_TAIL"
NOTES="$NOTES_MAIN
[已有 Mirror酱 CDK ?前往 Mirror酱 高速下载](https://mirrorchyan.com/zh/projects?rid=AUTO_MAA)
\`\`\`本release通过GitHub Actions自动构建\`\`\`"
if [ "${{ github.ref_name }}" == "main" ]; then
PRERELEASE_FLAG=""
else
@@ -126,12 +128,3 @@ jobs:
gh workflow run --repo $GITHUB_REPOSITORY mirrorchyan_release_note
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Setup SSH Key
run: |
mkdir -p ~/.ssh
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan -H ${{ secrets.SERVER_IP }} >> ~/.ssh/known_hosts
- name: Upload Release to Server
run: |
scp -r artifacts/* ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_IP }}:/home/user/files/AUTO_MAA/

View File

@@ -13,6 +13,7 @@
<a href="https://github.com/DLmaster361/AUTO_MAA/issues"><img alt="GitHub Issues" src="https://img.shields.io/github/issues/DLmaster361/AUTO_MAA?style=flat-square"></a>
<a href="https://github.com/DLmaster361/AUTO_MAA/graphs/contributors"><img alt="GitHub Contributors" src="https://img.shields.io/github/contributors/DLmaster361/AUTO_MAA?style=flat-square"></a>
<a href="https://github.com/DLmaster361/AUTO_MAA/blob/main/LICENSE"><img alt="GitHub License" src="https://img.shields.io/github/license/DLmaster361/AUTO_MAA?style=flat-square"></a>
<a href="https://deepwiki.com/DLmaster361/AUTO_MAA"><img alt="DeepWiki" src="https://deepwiki.com/badge.svg"></a>
<a href="https://mirrorchyan.com/zh/projects?rid=AUTO_MAA"><img alt="mirrorc" src="https://img.shields.io/badge/Mirror%E9%85%B1-%239af3f6?logo=countingworkspro&logoColor=4f46e5"></a>
</p>
@@ -22,6 +23,11 @@
本软件是明日方舟第三方软件`MAA`的第三方工具即第3<sup>3</sup>方软件。旨在优化MAA多账号功能体验并通过一些方法解决MAA项目未能解决的部分问题提高代理的稳定性。
- **集中管理**一站式管理多个MAA脚本与多个用户配置和凌乱的散装脚本窗口说再见
- **无人值守**自动处理MAA相关报错再也不用为代理任务卡死时自己不在电脑旁烦恼啦
- **配置灵活**:通过调度队列与脚本的组合,自由实现您能想到的所有调度需求!
- **信息统计**:自动统计用户的公招与关卡掉落物,看看这个月您的收益是多少!
### 原理
本软件可以存储多个明日方舟账号数据,并通过以下流程实现代理功能:
@@ -32,11 +38,9 @@
### 优势
- **节省运行开销:** 只需要一份MAA软件与一个模拟器无需多开就能完成多账号代理羸弱的电脑也能代理日常
- **自定义空间大:** 依靠高级用户配置模式支持MAA几乎所有设置选项自定义支持模拟器多开
- **调度方法自由:** 通过调度队列功能自由实现MAA多开等多种调度方式
- **一键代理无忧:** 无须中途手动修改MAA配置将繁琐交给AUTO_MAA把游戏留给自己。
- **代理结果复核:** 通过人工排查功能核实各用户代理情况,堵住自动代理的最后一丝风险。
- **高效稳定**:通过日志监测、异常处理等机制,保障代理任务顺利完成
- **简洁易用**:无需手动修改配置文件,实现自动化调度与多开管理
- **兼容扩展**:支持 MAA 几乎所有的配置选项,满足不同用户需求
## 重要声明

View File

@@ -33,7 +33,6 @@ from .core import QueueConfig, MaaConfig, MaaUserConfig, Task, TaskManager, Main
from .models import MaaManager
from .services import Notify, Crypto, System
from .ui import AUTO_MAA
from .utils import DownloadManager
__all__ = [
"QueueConfig",
@@ -47,5 +46,4 @@ __all__ = [
"Crypto",
"System",
"AUTO_MAA",
"DownloadManager",
]

View File

@@ -29,9 +29,10 @@ __version__ = "4.2.0"
__author__ = "DLmaster361 <DLmaster_361@163.com>"
__license__ = "GPL-3.0 license"
from .config import QueueConfig, MaaConfig, MaaUserConfig, Config
from .config import QueueConfig, MaaConfig, MaaUserConfig, MaaPlanConfig, Config
from .main_info_bar import MainInfoBar
from .network import Network
from .sound_player import SoundPlayer
from .task_manager import Task, TaskManager
from .timer import MainTimer
@@ -40,8 +41,10 @@ __all__ = [
"QueueConfig",
"MaaConfig",
"MaaUserConfig",
"MaaPlanConfig",
"MainInfoBar",
"Network",
"SoundPlayer",
"Task",
"TaskManager",
"MainTimer",

View File

@@ -33,6 +33,7 @@ import sys
import shutil
import re
import base64
import calendar
from datetime import datetime, timedelta, date
from collections import defaultdict
from pathlib import Path
@@ -78,110 +79,12 @@ class UrlListValidator(ConfigValidator):
return list(set([_ for _ in urls if self.validate(_)]))
class GlobalConfig(QConfig):
"""局配置"""
class LQConfig(QConfig):
"""配置"""
def __init__(self) -> None:
super().__init__()
self.function_HomeImageMode = OptionsConfigItem(
"Function",
"HomeImageMode",
"默认",
OptionsValidator(["默认", "自定义", "主题图像"]),
)
self.function_HistoryRetentionTime = OptionsConfigItem(
"Function",
"HistoryRetentionTime",
0,
OptionsValidator([7, 15, 30, 60, 90, 180, 365, 0]),
)
self.function_IfAllowSleep = ConfigItem(
"Function", "IfAllowSleep", False, BoolValidator()
)
self.function_IfSilence = ConfigItem(
"Function", "IfSilence", False, BoolValidator()
)
self.function_BossKey = ConfigItem("Function", "BossKey", "")
self.function_UnattendedMode = ConfigItem(
"Function", "UnattendedMode", False, BoolValidator()
)
self.function_IfAgreeBilibili = ConfigItem(
"Function", "IfAgreeBilibili", False, BoolValidator()
)
self.function_IfSkipMumuSplashAds = ConfigItem(
"Function", "IfSkipMumuSplashAds", False, BoolValidator()
)
self.start_IfSelfStart = ConfigItem(
"Start", "IfSelfStart", False, BoolValidator()
)
self.start_IfRunDirectly = ConfigItem(
"Start", "IfRunDirectly", False, BoolValidator()
)
self.start_IfMinimizeDirectly = ConfigItem(
"Start", "IfMinimizeDirectly", False, BoolValidator()
)
self.ui_IfShowTray = ConfigItem("UI", "IfShowTray", False, BoolValidator())
self.ui_IfToTray = ConfigItem("UI", "IfToTray", False, BoolValidator())
self.ui_size = ConfigItem("UI", "size", "1200x700")
self.ui_location = ConfigItem("UI", "location", "100x100")
self.ui_maximized = ConfigItem("UI", "maximized", False, BoolValidator())
self.notify_SendTaskResultTime = OptionsConfigItem(
"Notify",
"SendTaskResultTime",
"不推送",
OptionsValidator(["不推送", "任何时刻", "仅失败时"]),
)
self.notify_IfSendStatistic = ConfigItem(
"Notify", "IfSendStatistic", False, BoolValidator()
)
self.notify_IfSendSixStar = ConfigItem(
"Notify", "IfSendSixStar", False, BoolValidator()
)
self.notify_IfPushPlyer = ConfigItem(
"Notify", "IfPushPlyer", False, BoolValidator()
)
self.notify_IfSendMail = ConfigItem(
"Notify", "IfSendMail", False, BoolValidator()
)
self.notify_SMTPServerAddress = ConfigItem("Notify", "SMTPServerAddress", "")
self.notify_AuthorizationCode = ConfigItem("Notify", "AuthorizationCode", "")
self.notify_FromAddress = ConfigItem("Notify", "FromAddress", "")
self.notify_ToAddress = ConfigItem("Notify", "ToAddress", "")
self.notify_IfServerChan = ConfigItem(
"Notify", "IfServerChan", False, BoolValidator()
)
self.notify_ServerChanKey = ConfigItem("Notify", "ServerChanKey", "")
self.notify_ServerChanChannel = ConfigItem("Notify", "ServerChanChannel", "")
self.notify_ServerChanTag = ConfigItem("Notify", "ServerChanTag", "")
self.notify_IfCompanyWebHookBot = ConfigItem(
"Notify", "IfCompanyWebHookBot", False, BoolValidator()
)
self.notify_CompanyWebHookBotUrl = ConfigItem(
"Notify", "CompanyWebHookBotUrl", ""
)
self.notify_IfPushDeer = ConfigItem(
"Notify", "IfPushDeer", False, BoolValidator()
)
self.notify_IfPushDeerKey = ConfigItem("Notify", "PushDeerKey", "")
self.update_IfAutoUpdate = ConfigItem(
"Update", "IfAutoUpdate", False, BoolValidator()
)
self.update_UpdateType = OptionsConfigItem(
"Update", "UpdateType", "stable", OptionsValidator(["stable", "beta"])
)
self.update_ThreadNumb = RangeConfigItem(
"Update", "ThreadNumb", 8, RangeValidator(1, 32)
)
self.update_ProxyUrlList = ConfigItem(
"Update", "ProxyUrlList", [], UrlListValidator()
)
self.update_MirrorChyanCDK = ConfigItem("Update", "MirrorChyanCDK", "")
def toDict(self, serialize=True):
"""convert config items to `dict`"""
items = {}
@@ -247,7 +150,113 @@ class GlobalConfig(QConfig):
self.theme = self.get(self._cfg.themeMode)
class QueueConfig(QConfig):
class GlobalConfig(LQConfig):
"""全局配置"""
def __init__(self) -> None:
super().__init__()
self.function_HomeImageMode = OptionsConfigItem(
"Function",
"HomeImageMode",
"默认",
OptionsValidator(["默认", "自定义", "主题图像"]),
)
self.function_HistoryRetentionTime = OptionsConfigItem(
"Function",
"HistoryRetentionTime",
0,
OptionsValidator([7, 15, 30, 60, 90, 180, 365, 0]),
)
self.function_IfAllowSleep = ConfigItem(
"Function", "IfAllowSleep", False, BoolValidator()
)
self.function_IfSilence = ConfigItem(
"Function", "IfSilence", False, BoolValidator()
)
self.function_BossKey = ConfigItem("Function", "BossKey", "")
self.function_UnattendedMode = ConfigItem(
"Function", "UnattendedMode", False, BoolValidator()
)
self.function_IfAgreeBilibili = ConfigItem(
"Function", "IfAgreeBilibili", False, BoolValidator()
)
self.function_IfSkipMumuSplashAds = ConfigItem(
"Function", "IfSkipMumuSplashAds", False, BoolValidator()
)
self.voice_Enabled = ConfigItem("Voice", "Enabled", False, BoolValidator())
self.voice_Type = OptionsConfigItem(
"Voice", "Type", "simple", OptionsValidator(["simple", "noisy"])
)
self.start_IfSelfStart = ConfigItem(
"Start", "IfSelfStart", False, BoolValidator()
)
self.start_IfRunDirectly = ConfigItem(
"Start", "IfRunDirectly", False, BoolValidator()
)
self.start_IfMinimizeDirectly = ConfigItem(
"Start", "IfMinimizeDirectly", False, BoolValidator()
)
self.ui_IfShowTray = ConfigItem("UI", "IfShowTray", False, BoolValidator())
self.ui_IfToTray = ConfigItem("UI", "IfToTray", False, BoolValidator())
self.ui_size = ConfigItem("UI", "size", "1200x700")
self.ui_location = ConfigItem("UI", "location", "100x100")
self.ui_maximized = ConfigItem("UI", "maximized", False, BoolValidator())
self.notify_SendTaskResultTime = OptionsConfigItem(
"Notify",
"SendTaskResultTime",
"不推送",
OptionsValidator(["不推送", "任何时刻", "仅失败时"]),
)
self.notify_IfSendStatistic = ConfigItem(
"Notify", "IfSendStatistic", False, BoolValidator()
)
self.notify_IfSendSixStar = ConfigItem(
"Notify", "IfSendSixStar", False, BoolValidator()
)
self.notify_IfPushPlyer = ConfigItem(
"Notify", "IfPushPlyer", False, BoolValidator()
)
self.notify_IfSendMail = ConfigItem(
"Notify", "IfSendMail", False, BoolValidator()
)
self.notify_SMTPServerAddress = ConfigItem("Notify", "SMTPServerAddress", "")
self.notify_AuthorizationCode = ConfigItem("Notify", "AuthorizationCode", "")
self.notify_FromAddress = ConfigItem("Notify", "FromAddress", "")
self.notify_ToAddress = ConfigItem("Notify", "ToAddress", "")
self.notify_IfServerChan = ConfigItem(
"Notify", "IfServerChan", False, BoolValidator()
)
self.notify_ServerChanKey = ConfigItem("Notify", "ServerChanKey", "")
self.notify_ServerChanChannel = ConfigItem("Notify", "ServerChanChannel", "")
self.notify_ServerChanTag = ConfigItem("Notify", "ServerChanTag", "")
self.notify_IfCompanyWebHookBot = ConfigItem(
"Notify", "IfCompanyWebHookBot", False, BoolValidator()
)
self.notify_CompanyWebHookBotUrl = ConfigItem(
"Notify", "CompanyWebHookBotUrl", ""
)
self.update_IfAutoUpdate = ConfigItem(
"Update", "IfAutoUpdate", False, BoolValidator()
)
self.update_UpdateType = OptionsConfigItem(
"Update", "UpdateType", "stable", OptionsValidator(["stable", "beta"])
)
self.update_ThreadNumb = RangeConfigItem(
"Update", "ThreadNumb", 8, RangeValidator(1, 32)
)
self.update_ProxyUrlList = ConfigItem(
"Update", "ProxyUrlList", [], UrlListValidator()
)
self.update_MirrorChyanCDK = ConfigItem("Update", "MirrorChyanCDK", "")
class QueueConfig(LQConfig):
"""队列配置"""
def __init__(self) -> None:
@@ -260,8 +269,10 @@ class QueueConfig(QConfig):
self.queueSet_AfterAccomplish = OptionsConfigItem(
"QueueSet",
"AfterAccomplish",
"None",
OptionsValidator(["None", "KillSelf", "Sleep", "Hibernate", "Shutdown"]),
"NoAction",
OptionsValidator(
["NoAction", "KillSelf", "Sleep", "Hibernate", "Shutdown"]
),
)
self.time_TimeEnabled_0 = ConfigItem(
@@ -332,72 +343,8 @@ class QueueConfig(QConfig):
"Data", "LastProxyHistory", "暂无历史运行记录"
)
def toDict(self, serialize=True):
"""convert config items to `dict`"""
items = {}
for name in dir(self._cfg):
item = getattr(self._cfg, name)
if not isinstance(item, ConfigItem):
continue
value = item.serialize() if serialize else item.value
if not items.get(item.group):
if not item.name:
items[item.group] = value
else:
items[item.group] = {}
if item.name:
items[item.group][item.name] = value
return items
@exceptionHandler()
def load(self, file=None, config=None):
"""load config
Parameters
----------
file: str or Path
the path of json config file
config: Config
config object to be initialized
"""
if isinstance(config, QConfig):
self._cfg = config
self._cfg.themeChanged.connect(self.themeChanged)
if isinstance(file, (str, Path)):
self._cfg.file = Path(file)
try:
with open(self._cfg.file, encoding="utf-8") as f:
cfg = json.load(f)
except:
cfg = {}
# map config items'key to item
items = {}
for name in dir(self._cfg):
item = getattr(self._cfg, name)
if isinstance(item, ConfigItem):
items[item.key] = item
# update the value of config item
for k, v in cfg.items():
if not isinstance(v, dict) and items.get(k) is not None:
items[k].deserializeFrom(v)
elif isinstance(v, dict):
for key, value in v.items():
key = k + "." + key
if items.get(key) is not None:
items[key].deserializeFrom(value)
self.theme = self.get(self._cfg.themeMode)
class MaaConfig(QConfig):
class MaaConfig(LQConfig):
"""MAA配置"""
def __init__(self) -> None:
@@ -415,6 +362,9 @@ class MaaConfig(QConfig):
self.RunSet_ProxyTimesLimit = RangeConfigItem(
"RunSet", "ProxyTimesLimit", 0, RangeValidator(0, 1024)
)
self.RunSet_ADBSearchRange = RangeConfigItem(
"RunSet", "ADBSearchRange", 0, RangeValidator(0, 3)
)
self.RunSet_RunTimesLimit = RangeConfigItem(
"RunSet", "RunTimesLimit", 3, RangeValidator(1, 1024)
)
@@ -431,72 +381,8 @@ class MaaConfig(QConfig):
"RunSet", "AutoUpdateMaa", False, BoolValidator()
)
def toDict(self, serialize=True):
"""convert config items to `dict`"""
items = {}
for name in dir(self._cfg):
item = getattr(self._cfg, name)
if not isinstance(item, ConfigItem):
continue
value = item.serialize() if serialize else item.value
if not items.get(item.group):
if not item.name:
items[item.group] = value
else:
items[item.group] = {}
if item.name:
items[item.group][item.name] = value
return items
@exceptionHandler()
def load(self, file=None, config=None):
"""load config
Parameters
----------
file: str or Path
the path of json config file
config: Config
config object to be initialized
"""
if isinstance(config, QConfig):
self._cfg = config
self._cfg.themeChanged.connect(self.themeChanged)
if isinstance(file, (str, Path)):
self._cfg.file = Path(file)
try:
with open(self._cfg.file, encoding="utf-8") as f:
cfg = json.load(f)
except:
cfg = {}
# map config items'key to item
items = {}
for name in dir(self._cfg):
item = getattr(self._cfg, name)
if isinstance(item, ConfigItem):
items[item.key] = item
# update the value of config item
for k, v in cfg.items():
if not isinstance(v, dict) and items.get(k) is not None:
items[k].deserializeFrom(v)
elif isinstance(v, dict):
for key, value in v.items():
key = k + "." + key
if items.get(key) is not None:
items[key].deserializeFrom(value)
self.theme = self.get(self._cfg.themeMode)
class MaaUserConfig(QConfig):
class MaaUserConfig(LQConfig):
"""MAA用户配置"""
def __init__(self) -> None:
@@ -507,9 +393,7 @@ class MaaUserConfig(QConfig):
self.Info_Mode = OptionsConfigItem(
"Info", "Mode", "简洁", OptionsValidator(["简洁", "详细"])
)
self.Info_GameIdMode = OptionsConfigItem(
"Info", "GameIdMode", "固定", OptionsValidator(["固定"])
)
self.Info_GameIdMode = ConfigItem("Info", "GameIdMode", "固定")
self.Info_Server = OptionsConfigItem(
"Info", "Server", "Official", OptionsValidator(["Official", "Bilibili"])
)
@@ -532,7 +416,12 @@ class MaaUserConfig(QConfig):
self.Info_MedicineNumb = ConfigItem(
"Info", "MedicineNumb", 0, RangeValidator(0, 1024)
)
self.Info_SeriesNumb = ConfigItem("Info", "SeriesNumb", 1, RangeValidator(1, 6))
self.Info_SeriesNumb = OptionsConfigItem(
"Info",
"SeriesNumb",
"0",
OptionsValidator(["0", "6", "5", "4", "3", "2", "1", "-1"]),
)
self.Info_GameId = ConfigItem("Info", "GameId", "-")
self.Info_GameId_1 = ConfigItem("Info", "GameId_1", "-")
self.Info_GameId_2 = ConfigItem("Info", "GameId_2", "-")
@@ -550,78 +439,145 @@ class MaaUserConfig(QConfig):
"Data", "CustomInfrastPlanIndex", "0"
)
def toDict(self, serialize=True):
"""convert config items to `dict`"""
items = {}
for name in dir(self._cfg):
item = getattr(self._cfg, name)
if not isinstance(item, ConfigItem):
continue
self.Task_IfWakeUp = ConfigItem("Task", "IfWakeUp", True, BoolValidator())
self.Task_IfRecruiting = ConfigItem(
"Task", "IfRecruiting", True, BoolValidator()
)
self.Task_IfBase = ConfigItem("Task", "IfBase", True, BoolValidator())
self.Task_IfCombat = ConfigItem("Task", "IfCombat", True, BoolValidator())
self.Task_IfMall = ConfigItem("Task", "IfMall", True, BoolValidator())
self.Task_IfMission = ConfigItem("Task", "IfMission", True, BoolValidator())
self.Task_IfAutoRoguelike = ConfigItem(
"Task", "IfAutoRoguelike", False, BoolValidator()
)
self.Task_IfReclamation = ConfigItem(
"Task", "IfReclamation", False, BoolValidator()
)
value = item.serialize() if serialize else item.value
if not items.get(item.group):
if not item.name:
items[item.group] = value
else:
items[item.group] = {}
self.Notify_Enabled = ConfigItem("Notify", "Enabled", False, BoolValidator())
self.Notify_IfSendStatistic = ConfigItem(
"Notify", "IfSendStatistic", False, BoolValidator()
)
self.Notify_IfSendSixStar = ConfigItem(
"Notify", "IfSendSixStar", False, BoolValidator()
)
self.Notify_IfSendMail = ConfigItem(
"Notify", "IfSendMail", False, BoolValidator()
)
self.Notify_ToAddress = ConfigItem("Notify", "ToAddress", "")
self.Notify_IfServerChan = ConfigItem(
"Notify", "IfServerChan", False, BoolValidator()
)
self.Notify_ServerChanKey = ConfigItem("Notify", "ServerChanKey", "")
self.Notify_ServerChanChannel = ConfigItem("Notify", "ServerChanChannel", "")
self.Notify_ServerChanTag = ConfigItem("Notify", "ServerChanTag", "")
self.Notify_IfCompanyWebHookBot = ConfigItem(
"Notify", "IfCompanyWebHookBot", False, BoolValidator()
)
self.Notify_CompanyWebHookBotUrl = ConfigItem(
"Notify", "CompanyWebHookBotUrl", ""
)
if item.name:
items[item.group][item.name] = value
def get_plan_info(self) -> Dict[str, Union[str, int]]:
"""获取当前的计划下信息"""
return items
if self.get(self.Info_GameIdMode) == "固定":
return {
"MedicineNumb": self.get(self.Info_MedicineNumb),
"SeriesNumb": self.get(self.Info_SeriesNumb),
"GameId": self.get(self.Info_GameId),
"GameId_1": self.get(self.Info_GameId_1),
"GameId_2": self.get(self.Info_GameId_2),
"GameId_Remain": self.get(self.Info_GameId_Remain),
}
elif "计划" in self.get(self.Info_GameIdMode):
plan = Config.plan_dict[self.get(self.Info_GameIdMode)]["Config"]
return {
"MedicineNumb": plan.get(plan.get_current_info("MedicineNumb")),
"SeriesNumb": plan.get(plan.get_current_info("SeriesNumb")),
"GameId": plan.get(plan.get_current_info("GameId")),
"GameId_1": plan.get(plan.get_current_info("GameId_1")),
"GameId_2": plan.get(plan.get_current_info("GameId_2")),
"GameId_Remain": plan.get(plan.get_current_info("GameId_Remain")),
}
@exceptionHandler()
def load(self, file=None, config=None):
"""load config
Parameters
----------
file: str or Path
the path of json config file
class MaaPlanConfig(LQConfig):
"""MAA计划表配置"""
config: Config
config object to be initialized
"""
if isinstance(config, QConfig):
self._cfg = config
self._cfg.themeChanged.connect(self.themeChanged)
def __init__(self) -> None:
super().__init__()
if isinstance(file, (str, Path)):
self._cfg.file = Path(file)
self.Info_Name = ConfigItem("Info", "Name", "")
self.Info_Mode = OptionsConfigItem(
"Info", "Mode", "ALL", OptionsValidator(["ALL", "Weekly"])
)
try:
with open(self._cfg.file, encoding="utf-8") as f:
cfg = json.load(f)
except:
cfg = {}
self.config_item_dict: dict[str, Dict[str, ConfigItem]] = {}
# map config items'key to item
items = {}
for name in dir(self._cfg):
item = getattr(self._cfg, name)
if isinstance(item, ConfigItem):
items[item.key] = item
for group in [
"ALL",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
"Sunday",
]:
self.config_item_dict[group] = {}
# update the value of config item
for k, v in cfg.items():
if not isinstance(v, dict) and items.get(k) is not None:
items[k].deserializeFrom(v)
elif isinstance(v, dict):
for key, value in v.items():
key = k + "." + key
if items.get(key) is not None:
items[key].deserializeFrom(value)
self.config_item_dict[group]["MedicineNumb"] = ConfigItem(
group, "MedicineNumb", 0, RangeValidator(0, 1024)
)
self.config_item_dict[group]["SeriesNumb"] = OptionsConfigItem(
group,
"SeriesNumb",
"0",
OptionsValidator(["0", "6", "5", "4", "3", "2", "1", "-1"]),
)
self.config_item_dict[group]["GameId"] = ConfigItem(group, "GameId", "-")
self.config_item_dict[group]["GameId_1"] = ConfigItem(
group, "GameId_1", "-"
)
self.config_item_dict[group]["GameId_2"] = ConfigItem(
group, "GameId_2", "-"
)
self.config_item_dict[group]["GameId_Remain"] = ConfigItem(
group, "GameId_Remain", "-"
)
self.theme = self.get(self._cfg.themeMode)
for name in [
"MedicineNumb",
"SeriesNumb",
"GameId",
"GameId_1",
"GameId_2",
"GameId_Remain",
]:
setattr(self, f"{group}_{name}", self.config_item_dict[group][name])
def get_current_info(self, name: str) -> ConfigItem:
"""获取当前的计划表配置项"""
if self.get(self.Info_Mode) == "ALL":
return self.config_item_dict["ALL"][name]
elif self.get(self.Info_Mode) == "Weekly":
today = datetime.now().strftime("%A")
if today in self.config_item_dict:
return self.config_item_dict[today][name]
else:
return self.config_item_dict["ALL"][name]
class AppConfig(GlobalConfig):
VERSION = "4.3.6.2"
VERSION = "4.3.9.0"
gameid_refreshed = Signal()
PASSWORD_refreshed = Signal()
user_info_changed = Signal()
power_sign_changed = Signal()
def __init__(self) -> None:
super().__init__()
@@ -640,10 +596,18 @@ class AppConfig(GlobalConfig):
self.PASSWORD = ""
self.running_list = []
self.silence_list = []
self.info_bar_list = []
self.gameid_dict = {
"ALL": {"value": [], "text": []},
"Today": {"value": [], "text": []},
"Monday": {"value": [], "text": []},
"Tuesday": {"value": [], "text": []},
"Wednesday": {"value": [], "text": []},
"Thursday": {"value": [], "text": []},
"Friday": {"value": [], "text": []},
"Saturday": {"value": [], "text": []},
"Sunday": {"value": [], "text": []},
}
self.power_sign = "NoAction"
self.if_ignore_silence = False
self.if_database_opened = False
@@ -693,21 +657,23 @@ class AppConfig(GlobalConfig):
def get_gameid(self) -> None:
# 从MAA服务器获取活动关卡信息
Network.set_info(
network = Network.add_task(
mode="get",
url="https://ota.maa.plus/MaaAssistantArknights/api/gui/StageActivity.json",
url="https://api.maa.plus/MaaAssistantArknights/api/gui/StageActivity.json",
)
Network.start()
Network.loop.exec()
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 = []
gameid_dict = {"value": [], "text": []}
ss_gameid_dict = {"value": [], "text": []}
for gameid_info in gameid_infos:
@@ -720,53 +686,11 @@ class AppConfig(GlobalConfig):
gameid_info["Activity"]["UtcExpireTime"], "%Y/%m/%d %H:%M:%S"
)
):
gameid_dict["value"].append(gameid_info["Value"])
gameid_dict["text"].append(gameid_info["Value"])
ss_gameid_dict["value"].append(gameid_info["Value"])
ss_gameid_dict["text"].append(gameid_info["Value"])
# 生成全部关卡信息
self.gameid_dict["ALL"]["value"] = gameid_dict["value"] + [
"-",
"1-7",
"R8-11",
"12-17-HARD",
"CE-6",
"AP-5",
"CA-5",
"LS-6",
"SK-5",
"PR-A-1",
"PR-A-2",
"PR-B-1",
"PR-B-2",
"PR-C-1",
"PR-C-2",
"PR-D-1",
"PR-D-2",
]
self.gameid_dict["ALL"]["text"] = gameid_dict["text"] + [
"当前/上次",
"1-7",
"R8-11",
"12-17-HARD",
"龙门币-6/5",
"红票-5",
"技能-5",
"经验-6/5",
"碳-5",
"奶/盾芯片",
"奶/盾芯片组",
"术/狙芯片",
"术/狙芯片组",
"先/辅芯片",
"先/辅芯片组",
"近/特芯片",
"近/特芯片组",
]
# 生成本日关卡信息
days = self.server_date().isoweekday()
gameid_list = [
# 生成每日关卡信息
gameid_daily_info = [
{"value": "-", "text": "当前/上次", "days": [1, 2, 3, 4, 5, 6, 7]},
{"value": "1-7", "text": "1-7", "days": [1, 2, 3, 4, 5, 6, 7]},
{"value": "R8-11", "text": "R8-11", "days": [1, 2, 3, 4, 5, 6, 7]},
@@ -790,12 +714,20 @@ class AppConfig(GlobalConfig):
{"value": "PR-D-2", "text": "近/特芯片组", "days": [2, 3, 6, 7]},
]
for gameid_info in gameid_list:
if days in gameid_info["days"]:
gameid_dict["value"].append(gameid_info["value"])
gameid_dict["text"].append(gameid_info["text"])
for day in range(0, 8):
self.gameid_dict["Today"] = gameid_dict
today_gameid_dict = {"value": [], "text": []}
for gameid_info in gameid_daily_info:
if day in gameid_info["days"] or day == 0:
today_gameid_dict["value"].append(gameid_info["value"])
today_gameid_dict["text"].append(gameid_info["text"])
self.gameid_dict[calendar.day_name[day - 1] if day > 0 else "ALL"] = {
"value": today_gameid_dict["value"] + ss_gameid_dict["value"],
"text": today_gameid_dict["text"] + ss_gameid_dict["text"],
}
self.gameid_refreshed.emit()
@@ -1226,6 +1158,28 @@ class AppConfig(GlobalConfig):
sorted(user_dict.items(), key=lambda x: int(x[0][3:]))
)
def search_plan(self) -> None:
"""搜索所有计划表"""
self.plan_dict: Dict[str, Dict[str, Union[str, Path, MaaPlanConfig]]] = {}
if (self.app_path / "config/MaaPlanConfig").exists():
for maa_plan_dir in (self.app_path / "config/MaaPlanConfig").iterdir():
if maa_plan_dir.is_dir():
maa_plan_config = MaaPlanConfig()
maa_plan_config.load(maa_plan_dir / "config.json", maa_plan_config)
maa_plan_config.save()
self.plan_dict[maa_plan_dir.name] = {
"Type": "Maa",
"Path": maa_plan_dir,
"Config": maa_plan_config,
}
self.plan_dict = dict(
sorted(self.plan_dict.items(), key=lambda x: int(x[0][3:]))
)
def search_queue(self):
"""搜索所有调度队列实例"""
@@ -1273,6 +1227,16 @@ class AppConfig(GlobalConfig):
if queue["Config"].get(queue["Config"].queue_Member_10) == old:
queue["Config"].set(queue["Config"].queue_Member_10, new)
def change_plan(self, old: str, new: str) -> None:
"""修改脚本管理所有下属用户的计划表配置参数"""
for member in self.member_dict.values():
for user in member["UserData"].values():
if user["Config"].get(user["Config"].Info_GameIdMode) == old:
user["Config"].set(user["Config"].Info_GameIdMode, new)
def change_user_info(
self, name: str, user_data: Dict[str, Dict[str, Union[str, Path, dict]]]
) -> None:
@@ -1305,6 +1269,12 @@ class AppConfig(GlobalConfig):
self.user_info_changed.emit()
def set_power_sign(self, sign: str) -> None:
"""设置当前电源状态"""
self.power_sign = sign
self.power_sign_changed.emit()
def save_history(self, key: str, content: dict) -> None:
"""保存历史记录"""

View File

@@ -30,28 +30,36 @@ from PySide6.QtCore import Qt
from qfluentwidgets import InfoBar, InfoBarPosition
from .config import Config
from .sound_player import SoundPlayer
class _MainInfoBar:
"""信息通知栏"""
def push_info_bar(self, mode: str, title: str, content: str, time: int):
# 模式到 InfoBar 方法的映射
mode_mapping = {
"success": InfoBar.success,
"warning": InfoBar.warning,
"error": InfoBar.error,
"info": InfoBar.info,
}
def push_info_bar(
self, mode: str, title: str, content: str, time: int, if_force: bool = False
):
"""推送到信息通知栏"""
if Config.main_window is None:
logger.error("信息通知栏未设置父窗口")
return None
# 定义模式到 InfoBar 方法的映射
mode_mapping = {
"success": InfoBar.success,
"warning": InfoBar.warning,
"error": InfoBar.error,
"info": InfoBar.info,
}
# 根据 mode 获取对应的 InfoBar 方法
info_bar_method = mode_mapping.get(mode)
if info_bar_method:
info_bar_method = self.mode_mapping.get(mode)
if not info_bar_method:
logger.error(f"未知的通知栏模式: {mode}")
return None
if Config.main_window.isVisible():
info_bar_method(
title=title,
content=content,
@@ -61,8 +69,21 @@ class _MainInfoBar:
duration=time,
parent=Config.main_window,
)
else:
logger.error(f"未知的通知栏模式: {mode}")
elif if_force:
# 如果主窗口不可见且强制推送,则录入消息队列等待窗口显示后推送
info_bar_item = {
"mode": mode,
"title": title,
"content": content,
"time": time,
}
if info_bar_item not in Config.info_bar_list:
Config.info_bar_list.append(info_bar_item)
if mode == "warning":
SoundPlayer.play("发生异常")
if mode == "error":
SoundPlayer.play("发生错误")
MainInfoBar = _MainInfoBar()

View File

@@ -26,55 +26,46 @@ v4.3
"""
from loguru import logger
from PySide6.QtCore import QThread, QEventLoop, QTimer
from PySide6.QtCore import QObject, QThread, QEventLoop
import re
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.setObjectName(
f"NetworkThread-{mode}-{re.sub(r'(&cdk=)[^&]+(&)', r'\1******\2', url)}"
)
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 +74,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 +96,56 @@ 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:
"""获取网络请求结果"""
result = {
"status_code": network_thread.status_code,
"response_json": network_thread.response_json,
"error_message": (
re.sub(r"(&cdk=)[^&]+(&)", r"\1******\2", network_thread.error_message)
if network_thread.error_message
else None
),
}
network_thread.quit()
network_thread.wait()
self.task_queue.remove(network_thread)
network_thread.deleteLater()
return result
Network = _Network()

69
app/core/sound_player.py Normal file
View File

@@ -0,0 +1,69 @@
# 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.3
作者DLmaster_361
"""
from loguru import logger
from PySide6.QtCore import QObject, QUrl
from PySide6.QtMultimedia import QSoundEffect
from pathlib import Path
from .config import Config
class _SoundPlayer(QObject):
def __init__(self):
super().__init__()
self.sounds_path = Config.app_path / "resources/sounds"
def play(self, sound_name: str):
if not Config.get(Config.voice_Enabled):
return
if (self.sounds_path / f"both/{sound_name}.wav").exists():
self.play_voice(self.sounds_path / f"both/{sound_name}.wav")
elif (
self.sounds_path / Config.get(Config.voice_Type) / f"{sound_name}.wav"
).exists():
self.play_voice(
self.sounds_path / Config.get(Config.voice_Type) / f"{sound_name}.wav"
)
def play_voice(self, sound_path: Path):
effect = QSoundEffect(self)
effect.setVolume(1)
effect.setSource(QUrl.fromLocalFile(sound_path))
effect.play()
SoundPlayer = _SoundPlayer()

View File

@@ -35,6 +35,7 @@ from typing import Dict, Union
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.services import System
@@ -44,6 +45,7 @@ class Task(QThread):
check_maa_version = Signal(str)
push_info_bar = Signal(str, str, str, int)
play_sound = Signal(str)
question = Signal(str, str)
question_response = Signal(bool)
update_user_info = Signal(str, dict)
@@ -59,6 +61,8 @@ class Task(QThread):
):
super(Task, self).__init__()
self.setObjectName(f"Task-{mode}-{name}")
self.mode = mode
self.name = name
self.info = info
@@ -82,6 +86,7 @@ class Task(QThread):
)
self.task.check_maa_version.connect(self.check_maa_version.emit)
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([]))
self.task.run()
@@ -141,6 +146,7 @@ class Task(QThread):
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)
@@ -191,6 +197,7 @@ class _TaskManager(QObject):
logger.info(f"任务开始:{name}")
MainInfoBar.push_info_bar("info", "任务开始", name, 3000)
SoundPlayer.play("任务开始")
Config.running_list.append(name)
self.task_dict[name] = Task(mode, name, info)
@@ -199,6 +206,7 @@ class _TaskManager(QObject):
lambda title, content: self.push_dialog(name, title, content)
)
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].accomplish.connect(
lambda logs: self.remove_task(mode, name, logs)
@@ -239,6 +247,7 @@ class _TaskManager(QObject):
logger.info(f"任务结束:{name}")
MainInfoBar.push_info_bar("info", "任务结束", name, 3000)
SoundPlayer.play("任务结束")
self.task_dict[name].deleteLater()
self.task_dict.pop(name)
@@ -265,46 +274,32 @@ class _TaskManager(QObject):
Config.queue_dict[name]["Config"].get(
Config.queue_dict[name]["Config"].queueSet_AfterAccomplish
)
!= "None"
!= "NoAction"
and Config.power_sign == "NoAction"
):
from app.ui import ProgressRingMessageBox
mode_book = {
"Shutdown": "关机",
"Hibernate": "休眠",
"Sleep": "睡眠",
"KillSelf": "关闭AUTO_MAA",
}
choice = ProgressRingMessageBox(
Config.main_window,
f"{mode_book[Config.queue_dict[name]["Config"].get(Config.queue_dict[name]["Config"].queueSet_AfterAccomplish)]}倒计时",
)
if choice.exec():
System.set_power(
Config.queue_dict[name]["Config"].get(
Config.queue_dict[name]["Config"].queueSet_AfterAccomplish
)
Config.set_power_sign(
Config.queue_dict[name]["Config"].get(
Config.queue_dict[name]["Config"].queueSet_AfterAccomplish
)
)
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

View File

@@ -26,9 +26,9 @@ 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
from .config import Config
@@ -36,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)
@@ -46,6 +46,7 @@ class _MainTimer(QWidget):
self.Timer = QTimer()
self.Timer.timeout.connect(self.timed_start)
self.Timer.timeout.connect(self.set_silence)
self.Timer.timeout.connect(self.check_power)
self.Timer.start(1000)
self.LongTimer = QTimer()
self.LongTimer.timeout.connect(self.long_timed_task)
@@ -96,6 +97,16 @@ class _MainTimer(QWidget):
):
windows = System.get_window_info()
# 排除雷电名为新通知的窗口
windows = [
window
for window in windows
if not (
window[0] == "新通知" and Path(window[1]) in Config.silence_list
)
]
if any(
str(emulator_path) in window
for window in windows
@@ -113,5 +124,27 @@ class _MainTimer(QWidget):
logger.warning(f"FailSafeException: {e}")
self.if_FailSafeException = True
def check_power(self):
if Config.power_sign != "NoAction" and not Config.running_list:
from app.ui import ProgressRingMessageBox
mode_book = {
"KillSelf": "退出软件",
"Sleep": "睡眠",
"Hibernate": "休眠",
"Shutdown": "关机",
}
choice = ProgressRingMessageBox(
Config.main_window, f"{mode_book[Config.power_sign]}倒计时"
)
if choice.exec():
System.set_power(Config.power_sign)
Config.set_power_sign("NoAction")
else:
Config.set_power_sign("NoAction")
MainTimer = _MainTimer()

View File

@@ -50,6 +50,7 @@ class MaaManager(QObject):
question_response = Signal(bool)
update_user_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)
@@ -88,6 +89,8 @@ class MaaManager(QObject):
self.question_response.connect(self.__capture_response)
self.question_response.connect(self.question_loop.quit)
self.wait_loop = QEventLoop()
self.interrupt.connect(self.quit_monitor)
self.maa_version = None
@@ -102,6 +105,9 @@ class MaaManager(QObject):
"Path": info["Path"],
"Config": info["Config"].toDict(),
}
planed_info = info["Config"].get_plan_info()
for key, value in planed_info.items():
self.data[name]["Config"]["Info"][key] = value
self.data = dict(sorted(self.data.items(), key=lambda x: int(x[0][3:])))
@@ -114,10 +120,15 @@ class MaaManager(QObject):
self.maa_log_path = self.maa_root_path / "debug/gui.log"
self.maa_exe_path = self.maa_root_path / "MAA.exe"
self.maa_tasks_path = self.maa_root_path / "resource/tasks/tasks.json"
self.port_range = [0] + [
(i // 2 + 1) * (-1 if i % 2 else 1)
for i in range(0, 2 * self.set["RunSet"]["ADBSearchRange"])
]
def run(self):
"""主进程运行MAA代理进程"""
current_date = datetime.now().strftime("%m-%d")
curdate = Config.server_date().strftime("%Y-%m-%d")
begin_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
@@ -266,9 +277,23 @@ class MaaManager(QObject):
)
# 解析任务构成
if user_data["Info"]["Mode"] == "简洁":
if mode == "Routine":
self.task_dict = {
"WakeUp": str(user_data["Task"]["IfWakeUp"]),
"Recruiting": str(user_data["Task"]["IfRecruiting"]),
"Base": str(user_data["Task"]["IfBase"]),
"Combat": str(user_data["Task"]["IfCombat"]),
"Mission": str(user_data["Task"]["IfMission"]),
"Mall": str(user_data["Task"]["IfMall"]),
"AutoRoguelike": str(user_data["Task"]["IfAutoRoguelike"]),
"Reclamation": str(user_data["Task"]["IfReclamation"]),
}
elif mode == "Annihilation":
if user_data["Info"]["Mode"] == "简洁":
if mode == "Annihilation":
self.task_dict = {
"WakeUp": "True",
"Recruiting": "False",
@@ -280,52 +305,40 @@ class MaaManager(QObject):
"Reclamation": "False",
}
elif mode == "Routine":
elif user_data["Info"]["Mode"] == "详细":
with (self.data[user[2]]["Path"] / f"{mode}/gui.json").open(
mode="r", encoding="utf-8"
) as f:
data = json.load(f)
self.task_dict = {
"WakeUp": "True",
"Recruiting": "True",
"Base": "True",
"Combat": "True",
"Mission": "True",
"Mall": "True",
"AutoRoguelike": "False",
"Reclamation": "False",
"WakeUp": data["Configurations"]["Default"][
"TaskQueue.WakeUp.IsChecked"
],
"Recruiting": data["Configurations"]["Default"][
"TaskQueue.Recruiting.IsChecked"
],
"Base": data["Configurations"]["Default"][
"TaskQueue.Base.IsChecked"
],
"Combat": data["Configurations"]["Default"][
"TaskQueue.Combat.IsChecked"
],
"Mission": data["Configurations"]["Default"][
"TaskQueue.Mission.IsChecked"
],
"Mall": data["Configurations"]["Default"][
"TaskQueue.Mall.IsChecked"
],
"AutoRoguelike": data["Configurations"]["Default"][
"TaskQueue.AutoRoguelike.IsChecked"
],
"Reclamation": data["Configurations"]["Default"][
"TaskQueue.Reclamation.IsChecked"
],
}
elif user_data["Info"]["Mode"] == "详细":
with (self.data[user[2]]["Path"] / f"{mode}/gui.json").open(
mode="r", encoding="utf-8"
) as f:
data = json.load(f)
self.task_dict = {
"WakeUp": data["Configurations"]["Default"][
"TaskQueue.WakeUp.IsChecked"
],
"Recruiting": data["Configurations"]["Default"][
"TaskQueue.Recruiting.IsChecked"
],
"Base": data["Configurations"]["Default"][
"TaskQueue.Base.IsChecked"
],
"Combat": data["Configurations"]["Default"][
"TaskQueue.Combat.IsChecked"
],
"Mission": data["Configurations"]["Default"][
"TaskQueue.Mission.IsChecked"
],
"Mall": data["Configurations"]["Default"][
"TaskQueue.Mall.IsChecked"
],
"AutoRoguelike": data["Configurations"]["Default"][
"TaskQueue.AutoRoguelike.IsChecked"
],
"Reclamation": data["Configurations"]["Default"][
"TaskQueue.Reclamation.IsChecked"
],
}
# 尝试次数循环
for i in range(self.set["RunSet"]["RunTimesLimit"]):
@@ -386,6 +399,12 @@ class MaaManager(QObject):
self.if_open_emulator = True
break
self.wait_time = int(
set["Configurations"]["Default"][
"Start.EmulatorWaitSeconds"
]
)
self.ADB_path = Path(
set["Configurations"]["Default"]["Connect.AdbPath"]
)
@@ -410,6 +429,7 @@ 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,
@@ -428,6 +448,9 @@ 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,
@@ -446,6 +469,8 @@ class MaaManager(QObject):
# 添加静默进程标记
Config.silence_list.append(self.emulator_path)
self.search_ADB_address()
# 创建MAA任务
maa = subprocess.Popen(
[self.maa_exe_path],
@@ -489,10 +514,7 @@ class MaaManager(QObject):
"检测到MAA进程完成代理任务\n正在等待相关程序结束\n请等待10s"
)
for _ in range(10):
if self.isInterruptionRequested:
break
time.sleep(1)
self.sleep(10)
else:
logger.error(
f"{self.name} | 用户: {user[0]} - 代理任务异常: {self.maa_result}"
@@ -517,6 +539,12 @@ class MaaManager(QObject):
) as f:
data = json.load(f)
# 记录自定义基建索引
if self.task_dict["Base"] == "False":
user_data["Data"]["CustomInfrastPlanIndex"] = data[
"Configurations"
]["Default"]["Infrast.CustomInfrastPlanIndex"]
# 记录更新包路径
if (
data["Global"]["VersionUpdate.package"]
@@ -536,16 +564,15 @@ class MaaManager(QObject):
f"{user[0].replace("_", " ")}{mode_book[mode][5:7]}出现异常",
1,
)
for _ in range(10):
if self.isInterruptionRequested:
break
time.sleep(1)
# 移除静默进程标记
Config.silence_list.remove(self.emulator_path)
if i == self.set["RunSet"]["RunTimesLimit"] - 1:
self.play_sound.emit("子任务失败")
else:
self.play_sound.emit(self.maa_result)
self.sleep(10)
# 任务结束后释放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,
@@ -567,29 +594,6 @@ class MaaManager(QObject):
self.emulator_process.wait()
self.if_open_emulator = True
# 执行MAA解压更新动作
if self.maa_update_package:
logger.info(
f"{self.name} | 检测到MAA更新正在执行更新动作"
)
self.update_log_text.emit(
f"检测到MAA存在更新\nMAA正在执行更新动作\n请等待10s"
)
self.set_maa("更新MAA", None)
subprocess.Popen(
[self.maa_exe_path],
creationflags=subprocess.CREATE_NO_WINDOW,
)
for _ in range(10):
if self.isInterruptionRequested:
break
time.sleep(1)
System.kill_process(self.maa_exe_path)
logger.info(f"{self.name} | 更新动作结束")
# 记录剿灭情况
if (
mode == "Annihilation"
@@ -607,33 +611,55 @@ class MaaManager(QObject):
Config.app_path
/ f"history/{curdate}/{user_data["Info"]["Name"]}/{start_time.strftime("%H-%M-%S")}.json",
)
if Config.get(Config.notify_IfSendSixStar) and if_six_star:
if if_six_star:
self.push_notification(
"公招六星",
f"喜报:用户 {user[0]} 公招出六星啦!",
{"user_name": user_data["Info"]["Name"]},
{
"user_name": user_data["Info"]["Name"],
},
user_data,
)
self.play_sound.emit("六星喜报")
# 执行MAA解压更新动作
if self.maa_update_package:
logger.info(
f"{self.name} | 检测到MAA更新正在执行更新动作"
)
if Config.get(Config.notify_IfSendStatistic):
self.update_log_text.emit(
f"检测到MAA存在更新\nMAA正在执行更新动作\n请等待10s"
)
self.play_sound.emit("MAA更新")
self.set_maa("更新MAA", None)
subprocess.Popen(
[self.maa_exe_path],
creationflags=subprocess.CREATE_NO_WINDOW,
)
self.sleep(10)
System.kill_process(self.maa_exe_path)
statistics = Config.merge_maa_logs("指定项", user_logs_list)
statistics["user_info"] = user[0]
statistics["start_time"] = user_start_time.strftime(
"%Y-%m-%d %H:%M:%S"
)
statistics["end_time"] = datetime.now().strftime(
"%Y-%m-%d %H:%M:%S"
)
statistics["maa_result"] = (
"代理任务全部完成"
if (run_book["Annihilation"] and run_book["Routine"])
else "代理任务未全部完成"
)
self.push_notification(
"统计信息", f"用户 {user[0]} 的自动代理统计报告", statistics
)
logger.info(f"{self.name} | 更新动作结束")
# 发送统计信息
statistics = Config.merge_maa_logs("指定项", user_logs_list)
statistics["user_index"] = user[2]
statistics["user_info"] = user[0]
statistics["start_time"] = user_start_time.strftime("%Y-%m-%d %H:%M:%S")
statistics["end_time"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
statistics["maa_result"] = (
"代理任务全部完成"
if (run_book["Annihilation"] and run_book["Routine"])
else "代理任务未全部完成"
)
self.push_notification(
"统计信息",
f"{current_date} | 用户 {user[0]} 的自动代理统计报告",
statistics,
user_data,
)
if run_book["Annihilation"] and run_book["Routine"]:
# 成功完成代理的用户修改相关参数
@@ -719,10 +745,7 @@ class MaaManager(QObject):
# 无命令行中止MAA与其子程序
System.kill_process(self.maa_exe_path)
self.if_open_emulator = True
for _ in range(10):
if self.isInterruptionRequested:
break
time.sleep(1)
self.sleep(10)
# 登录成功,结束循环
if run_book[0]:
@@ -730,6 +753,7 @@ class MaaManager(QObject):
# 登录失败,询问是否结束循环
elif not self.isInterruptionRequested:
self.play_sound.emit("排查重试")
if not self.push_question(
"操作提示", "MAA未能正确登录到PRTS是否重试"
):
@@ -738,6 +762,7 @@ class MaaManager(QObject):
# 登录成功,录入人工排查情况
if run_book[0] and not self.isInterruptionRequested:
self.play_sound.emit("排查录入")
if self.push_question(
"操作提示", "请检查用户代理情况,该用户是否正确完成代理任务?"
):
@@ -804,9 +829,9 @@ class MaaManager(QObject):
# 保存运行日志
title = (
f"{self.set["MaaSet"]["Name"]}{self.mode[:4]}任务报告"
f"{current_date} | {self.set["MaaSet"]["Name"]}{self.mode[:4]}任务报告"
if self.set["MaaSet"]["Name"] != ""
else f"{self.mode[:4]}任务报告"
else f"{current_date} | {self.mode[:4]}任务报告"
)
result = {
"title": f"{self.mode[:4]}任务报告",
@@ -826,6 +851,17 @@ class MaaManager(QObject):
self.data[_]["Config"]["Info"]["Name"] for _ in wait_index
],
}
# 生成结果文本
result_text = (
f"任务开始时间:{result["start_time"]},结束时间:{result["end_time"]}\n"
f"已完成数:{result["completed_count"]},未完成数:{result["uncompleted_count"]}\n\n"
)
if len(result["failed_user"]) > 0:
result_text += f"{self.mode[2:4]}未成功的用户:\n{"\n".join(result["failed_user"])}\n"
if len(result["waiting_user"]) > 0:
result_text += f"\n未开始{self.mode[2:4]}的用户:\n{"\n".join(result["waiting_user"])}\n"
# 推送代理结果通知
Notify.push_plyer(
title.replace("报告", "已完成!"),
@@ -833,15 +869,7 @@ class MaaManager(QObject):
f"已完成用户数:{len(over_index)},未完成用户数:{len(error_index) + len(wait_index)}",
10,
)
if Config.get(Config.notify_SendTaskResultTime) == "任何时刻" or (
Config.get(Config.notify_SendTaskResultTime) == "仅失败时"
and len(error_index) + len(wait_index) != 0
):
result_text = self.push_notification("代理结果", title, result)
else:
result_text = self.push_notification(
"代理结果", title, result, if_get_text_only=True
)
self.push_notification("代理结果", title, result)
self.agree_bilibili(False)
self.log_monitor.deleteLater()
@@ -856,6 +884,7 @@ class MaaManager(QObject):
self.maa_result = "任务被手动中止"
self.isInterruptionRequested = True
self.wait_loop.quit()
def push_question(self, title: str, message: str) -> bool:
@@ -866,6 +895,92 @@ class MaaManager(QObject):
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 search_ADB_address(self) -> None:
"""搜索ADB实际地址"""
self.update_log_text.emit(
f"即将搜索ADB实际地址\n正在等待模拟器完成启动\n请等待{self.wait_time}s"
)
self.sleep(self.wait_time)
# 移除静默进程标记
Config.silence_list.remove(self.emulator_path)
if "-" in self.ADB_address:
ADB_ip = f"{self.ADB_address.split("-")[0]}-"
ADB_port = int(self.ADB_address.split("-")[1])
elif ":" in self.ADB_address:
ADB_ip = f"{self.ADB_address.split(':')[0]}:"
ADB_port = int(self.ADB_address.split(":")[1])
logger.info(
f"{self.name} | 正在搜索ADB实际地址ADB前缀{ADB_ip},初始端口:{ADB_port},搜索范围:{self.port_range}"
)
for port in self.port_range:
ADB_address = f"{ADB_ip}{ADB_port + port}"
# 尝试通过ADB连接到指定地址
connect_result = subprocess.run(
[self.ADB_path, "connect", ADB_address],
creationflags=subprocess.CREATE_NO_WINDOW,
capture_output=True,
text=True,
encoding="utf-8",
)
if "connected" in connect_result.stdout:
# 检查连接状态
devices_result = subprocess.run(
[self.ADB_path, "devices"],
creationflags=subprocess.CREATE_NO_WINDOW,
capture_output=True,
text=True,
encoding="utf-8",
)
if ADB_address in devices_result.stdout:
logger.info(f"{self.name} | ADB实际地址{ADB_address}")
# 断开连接
subprocess.run(
[self.ADB_path, "disconnect", ADB_address],
creationflags=subprocess.CREATE_NO_WINDOW,
)
self.ADB_address = ADB_address
# 覆写当前ADB地址
System.kill_process(self.maa_exe_path)
with self.maa_set_path.open(mode="r", encoding="utf-8") as f:
data = json.load(f)
data["Configurations"]["Default"][
"Connect.Address"
] = self.ADB_address
data["Configurations"]["Default"]["Start.EmulatorWaitSeconds"] = "0"
with self.maa_set_path.open(mode="w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=4)
self.play_sound.emit("ADB成功")
return None
else:
logger.info(f"{self.name} | 无法连接到ADB地址{ADB_address}")
else:
logger.info(f"{self.name} | 无法连接到ADB地址{ADB_address}")
self.play_sound.emit("ADB失败")
def refresh_maa_log(self) -> None:
"""刷新MAA日志"""
@@ -923,6 +1038,8 @@ class MaaManager(QObject):
latest_time = start_time
for _ in logs[::-1]:
try:
if "如果长时间无进一步日志更新,可能需要手动干预。" in _:
continue
latest_time = datetime.strptime(_[1:20], "%Y-%m-%d %H:%M:%S")
break
except ValueError:
@@ -965,7 +1082,7 @@ class MaaManager(QObject):
else:
self.maa_result = "MAA部分任务执行失败"
elif "检查连接设置」或「尝试重启模拟器与 ADB」或「重启电脑" in log:
elif "检查连接设置」 → 「尝试重启模拟器与 ADB」 → 「重启电脑" in log:
self.maa_result = "MAA的ADB连接异常"
elif "未检测到任何模拟器" in log:
@@ -991,7 +1108,7 @@ class MaaManager(QObject):
elif mode == "人工排查":
if "完成任务: StartUp" in log:
self.maa_result = "Success!"
elif "检查连接设置」或「尝试重启模拟器与 ADB」或「重启电脑" in log:
elif "检查连接设置」 → 「尝试重启模拟器与 ADB」 → 「重启电脑" in log:
self.maa_result = "MAA的ADB连接异常"
elif "未检测到任何模拟器" in log:
self.maa_result = "MAA未检测到任何模拟器"
@@ -1166,6 +1283,9 @@ class MaaManager(QObject):
]["Id"]
# 按预设设定任务
data["Configurations"]["Default"][
"TaskQueue.WakeUp.IsChecked"
] = "True" # 开始唤醒
data["Configurations"]["Default"]["TaskQueue.Recruiting.IsChecked"] = (
self.task_dict["Recruiting"]
) # 自动公招
@@ -1190,10 +1310,6 @@ class MaaManager(QObject):
if user_data["Info"]["Mode"] == "简洁":
data["Configurations"]["Default"][
"TaskQueue.WakeUp.IsChecked"
] = "True" # 开始唤醒
data["Configurations"]["Default"]["Start.ClientType"] = user_data[
"Info"
][
@@ -1278,9 +1394,9 @@ class MaaManager(QObject):
) # 剩余理智关卡
data["Configurations"]["Default"][
"MainFunction.Series.Quantity"
] = str(
user_data["Info"]["SeriesNumb"]
) # 连战次数
] = user_data["Info"][
"SeriesNumb"
] # 连战次数
data["Configurations"]["Default"][
"Penguin.IsDrGrandet"
] = "False" # 博朗台模式
@@ -1292,7 +1408,9 @@ class MaaManager(QObject):
] = "True" # 备选关卡
data["Configurations"]["Default"][
"Fight.UseRemainingSanityStage"
] = "True" # 使用剩余理智
] = (
"True" if user_data["Info"]["GameId_Remain"] != "-" else "False"
) # 使用剩余理智
data["Configurations"]["Default"][
"Fight.UseExpiringMedicine"
] = "True" # 无限吃48小时内过期的理智药
@@ -1382,15 +1500,28 @@ class MaaManager(QObject):
) # 剩余理智关卡
data["Configurations"]["Default"][
"MainFunction.Series.Quantity"
] = str(
user_data["Info"]["SeriesNumb"]
) # 连战次数
] = user_data["Info"][
"SeriesNumb"
] # 连战次数
data["Configurations"]["Default"][
"GUI.UseAlternateStage"
] = "True" # 备选关卡
data["Configurations"]["Default"][
"Fight.UseRemainingSanityStage"
] = "True" # 使用剩余理智
] = (
"True" if user_data["Info"]["GameId_Remain"] != "-" else "False"
) # 使用剩余理智
# 基建模式
if (
data["Configurations"]["Default"]["Infrast.InfrastMode"]
== "Custom"
):
data["Configurations"]["Default"][
"Infrast.CustomInfrastPlanIndex"
] = user_data["Data"][
"CustomInfrastPlanIndex"
] # 自定义基建配置索引
# 人工排查配置
elif "人工排查" in mode:
@@ -1405,12 +1536,20 @@ class MaaManager(QObject):
"Start.RunDirectly"
] = "True" # 启动MAA后直接运行
data["Global"]["Start.MinimizeDirectly"] = "True" # 启动MAA后直接最小化
data["Global"]["GUI.UseTray"] = "True" # 显示托盘图标
data["Global"]["GUI.MinimizeToTray"] = "True" # 最小化时隐藏至托盘
data["Configurations"]["Default"]["Start.OpenEmulatorAfterLaunch"] = str(
self.if_open_emulator
) # 启动MAA后自动开启模拟器
data["Global"][
"VersionUpdate.ScheduledUpdateCheck"
] = "False" # 定时检查更新
data["Global"][
"VersionUpdate.AutoDownloadUpdatePackage"
] = "False" # 自动下载更新包
data["Global"][
"VersionUpdate.AutoInstallUpdatePackage"
] = "False" # 自动安装更新包
# 账号切换
if user_data["Info"]["Server"] == "Official":
@@ -1426,15 +1565,6 @@ class MaaManager(QObject):
if user_data["Info"]["Mode"] == "简洁":
data["Global"][
"VersionUpdate.ScheduledUpdateCheck"
] = "False" # 定时检查更新
data["Global"][
"VersionUpdate.AutoDownloadUpdatePackage"
] = "False" # 自动下载更新包
data["Global"][
"VersionUpdate.AutoInstallUpdatePackage"
] = "False" # 自动安装更新包
data["Configurations"]["Default"]["Start.ClientType"] = user_data[
"Info"
][
@@ -1481,6 +1611,15 @@ class MaaManager(QObject):
data["Configurations"]["Default"][
"Start.OpenEmulatorAfterLaunch"
] = "False" # 启动MAA后自动开启模拟器
data["Global"][
"VersionUpdate.ScheduledUpdateCheck"
] = "False" # 定时检查更新
data["Global"][
"VersionUpdate.AutoDownloadUpdatePackage"
] = "False" # 自动下载更新包
data["Global"][
"VersionUpdate.AutoInstallUpdatePackage"
] = "False" # 自动安装更新包
if Config.get(Config.function_IfSilence):
data["Global"][
@@ -1489,15 +1628,6 @@ class MaaManager(QObject):
if "全局" in mode:
data["Global"][
"VersionUpdate.ScheduledUpdateCheck"
] = "False" # 定时检查更新
data["Global"][
"VersionUpdate.AutoDownloadUpdatePackage"
] = "False" # 自动下载更新包
data["Global"][
"VersionUpdate.AutoInstallUpdatePackage"
] = "False" # 自动安装更新包
data["Configurations"]["Default"][
"TaskQueue.WakeUp.IsChecked"
] = "False" # 开始唤醒
@@ -1622,16 +1752,21 @@ class MaaManager(QObject):
mode: str,
title: str,
message: Union[str, dict],
if_get_text_only: bool = False,
) -> str:
user_data: Dict[str, Dict[str, Union[str, int, bool]]] = None,
) -> None:
"""通过所有渠道推送通知"""
env = Environment(
loader=FileSystemLoader(str(Config.app_path / "resources/html"))
)
if mode == "代理结果":
if mode == "代理结果" and (
Config.get(Config.notify_SendTaskResultTime) == "任何时刻"
or (
Config.get(Config.notify_SendTaskResultTime) == "仅失败时"
and message["uncompleted_count"] != 0
)
):
# 生成文本通知内容
message_text = (
f"任务开始时间:{message["start_time"]},结束时间:{message["end_time"]}\n"
@@ -1643,9 +1778,6 @@ class MaaManager(QObject):
if len(message["waiting_user"]) > 0:
message_text += f"\n未开始{self.mode[2:4]}的用户:\n{"\n".join(message["waiting_user"])}\n"
if if_get_text_only:
return message_text
# 生成HTML通知内容
message["failed_user"] = "".join(message["failed_user"])
message["waiting_user"] = "".join(message["waiting_user"])
@@ -1653,11 +1785,31 @@ class MaaManager(QObject):
template = env.get_template("MAA_result.html")
message_html = template.render(message)
Notify.send_mail("网页", title, message_html)
Notify.ServerChanPush(title, f"{message_text}\n\nAUTO_MAA 敬上")
Notify.CompanyWebHookBotPush(title, f"{message_text}\n\nAUTO_MAA 敬上")
# ServerChan的换行是两个换行符。故而将\n替换为\n\n
serverchan_message = message_text.replace("\n", "\n\n")
return message_text
# 发送全局通知
if Config.get(Config.notify_IfSendMail):
Notify.send_mail(
"网页", title, message_html, Config.get(Config.notify_ToAddress)
)
if Config.get(Config.notify_IfServerChan):
Notify.ServerChanPush(
title,
f"{serverchan_message}\n\nAUTO_MAA 敬上",
Config.get(Config.notify_ServerChanKey),
Config.get(Config.notify_ServerChanTag),
Config.get(Config.notify_ServerChanChannel),
)
if Config.get(Config.notify_IfCompanyWebHookBot):
Notify.CompanyWebHookBotPush(
title,
f"{message_text}\n\nAUTO_MAA 敬上",
Config.get(Config.notify_CompanyWebHookBotUrl),
)
elif mode == "统计信息":
@@ -1686,18 +1838,155 @@ class MaaManager(QObject):
template = env.get_template("MAA_statistics.html")
message_html = template.render(message)
Notify.send_mail("网页", title, message_html)
# ServerChan的换行是两个换行符。故而将\n替换为\n\n
serverchan_message = message_text.replace("\n", "\n\n")
Notify.ServerChanPush(title, f"{serverchan_message}\n\nAUTO_MAA 敬上")
Notify.CompanyWebHookBotPush(title, f"{message_text}\n\nAUTO_MAA 敬上")
# 发送全局通知
if Config.get(Config.notify_IfSendStatistic):
if Config.get(Config.notify_IfSendMail):
Notify.send_mail(
"网页", title, message_html, Config.get(Config.notify_ToAddress)
)
if Config.get(Config.notify_IfServerChan):
Notify.ServerChanPush(
title,
f"{serverchan_message}\n\nAUTO_MAA 敬上",
Config.get(Config.notify_ServerChanKey),
Config.get(Config.notify_ServerChanTag),
Config.get(Config.notify_ServerChanChannel),
)
if Config.get(Config.notify_IfCompanyWebHookBot):
Notify.CompanyWebHookBotPush(
title,
f"{message_text}\n\nAUTO_MAA 敬上",
Config.get(Config.notify_CompanyWebHookBotUrl),
)
# 发送用户单独通知
if (
user_data["Notify"]["Enabled"]
and user_data["Notify"]["IfSendStatistic"]
):
# 发送邮件通知
if user_data["Notify"]["IfSendMail"]:
if user_data["Notify"]["ToAddress"]:
Notify.send_mail(
"网页",
title,
message_html,
user_data["Notify"]["ToAddress"],
)
else:
logger.error(
f"{self.name} | 用户邮箱地址为空,无法发送用户单独的邮件通知"
)
# 发送ServerChan通知
if user_data["Notify"]["IfServerChan"]:
if user_data["Notify"]["ServerChanKey"]:
Notify.ServerChanPush(
title,
f"{serverchan_message}\n\nAUTO_MAA 敬上",
user_data["Notify"]["ServerChanKey"],
user_data["Notify"]["ServerChanTag"],
user_data["Notify"]["ServerChanChannel"],
)
else:
logger.error(
f"{self.name} |用户ServerChan密钥为空无法发送用户单独的ServerChan通知"
)
# 推送CompanyWebHookBot通知
if user_data["Notify"]["IfCompanyWebHookBot"]:
if user_data["Notify"]["CompanyWebHookBotUrl"]:
Notify.CompanyWebHookBotPush(
title,
f"{message_text}\n\nAUTO_MAA 敬上",
user_data["Notify"]["CompanyWebHookBotUrl"],
)
else:
logger.error(
f"{self.name} |用户CompanyWebHookBot密钥为空无法发送用户单独的CompanyWebHookBot通知"
)
elif mode == "公招六星":
# 生成HTML通知内容
template = env.get_template("MAA_six_star.html")
message_html = template.render(message)
Notify.send_mail("网页", title, message_html)
Notify.ServerChanPush(title, "好羡慕~\n\nAUTO_MAA 敬上")
Notify.CompanyWebHookBotPush(title, "好羡慕~\n\nAUTO_MAA 敬上")
# 发送全局通知
if Config.get(Config.notify_IfSendSixStar):
if Config.get(Config.notify_IfSendMail):
Notify.send_mail(
"网页", title, message_html, Config.get(Config.notify_ToAddress)
)
if Config.get(Config.notify_IfServerChan):
Notify.ServerChanPush(
title,
"好羡慕~\n\nAUTO_MAA 敬上",
Config.get(Config.notify_ServerChanKey),
Config.get(Config.notify_ServerChanTag),
Config.get(Config.notify_ServerChanChannel),
)
if Config.get(Config.notify_IfCompanyWebHookBot):
Notify.CompanyWebHookBotPush(
title,
"好羡慕~\n\nAUTO_MAA 敬上",
Config.get(Config.notify_CompanyWebHookBotUrl),
)
# 发送用户单独通知
if user_data["Notify"]["Enabled"] and user_data["Notify"]["IfSendSixStar"]:
# 发送邮件通知
if user_data["Notify"]["IfSendMail"]:
if user_data["Notify"]["ToAddress"]:
Notify.send_mail(
"网页",
title,
message_html,
user_data["Notify"]["ToAddress"],
)
else:
logger.error(
f"{self.name} | 用户邮箱地址为空,无法发送用户单独的邮件通知"
)
# 发送ServerChan通知
if user_data["Notify"]["IfServerChan"]:
if user_data["Notify"]["ServerChanKey"]:
Notify.ServerChanPush(
title,
"好羡慕~\n\nAUTO_MAA 敬上",
user_data["Notify"]["ServerChanKey"],
user_data["Notify"]["ServerChanTag"],
user_data["Notify"]["ServerChanChannel"],
)
else:
logger.error(
f"{self.name} |用户ServerChan密钥为空无法发送用户单独的ServerChan通知"
)
# 推送CompanyWebHookBot通知
if user_data["Notify"]["IfCompanyWebHookBot"]:
if user_data["Notify"]["CompanyWebHookBotUrl"]:
Notify.CompanyWebHookBotPush(
title,
"好羡慕~\n\nAUTO_MAA 敬上",
user_data["Notify"]["CompanyWebHookBotUrl"],
)
else:
logger.error(
f"{self.name} |用户CompanyWebHookBot密钥为空无法发送用户单独的CompanyWebHookBot通知"
)
return None

View File

@@ -25,23 +25,24 @@ v4.3
作者DLmaster_361
"""
from PySide6.QtWidgets import QWidget
from PySide6.QtCore import Signal
import requests
import time
from loguru import logger
from plyer import notification
import re
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
import time
from email.header import Header
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.utils import formataddr
import requests
from PySide6.QtCore import QObject, Signal
from loguru import logger
from plyer import notification
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)
@@ -65,218 +66,210 @@ class Notification(QWidget):
return True
def send_mail(self, mode, title, content) -> None:
def send_mail(self, mode, title, content, to_address) -> None:
"""推送邮件通知"""
if Config.get(Config.notify_IfSendMail):
if (
Config.get(Config.notify_SMTPServerAddress) == ""
or Config.get(Config.notify_AuthorizationCode) == ""
or not bool(
re.match(
r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$",
Config.get(Config.notify_FromAddress),
)
)
or not bool(
re.match(
r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$",
Config.get(Config.notify_ToAddress),
)
)
):
logger.error(
"请正确设置邮件通知的SMTP服务器地址、授权码、发件人地址和收件人地址"
)
self.push_info_bar.emit(
"error",
"邮件通知推送异常",
"请正确设置邮件通知的SMTP服务器地址、授权码、发件人地址和收件人地址",
-1,
)
return None
try:
# 定义邮件正文
if mode == "文本":
message = MIMEText(content, "plain", "utf-8")
elif mode == "网页":
message = MIMEMultipart("alternative")
message["From"] = formataddr(
(
Header("AUTO_MAA通知服务", "utf-8").encode(),
Config.get(Config.notify_FromAddress),
)
) # 发件人显示的名字
message["To"] = formataddr(
(
Header("AUTO_MAA用户", "utf-8").encode(),
Config.get(Config.notify_ToAddress),
)
) # 收件人显示的名字
message["Subject"] = Header(title, "utf-8")
if mode == "网页":
message.attach(MIMEText(content, "html", "utf-8"))
smtpObj = smtplib.SMTP_SSL(
Config.get(Config.notify_SMTPServerAddress),
465,
)
smtpObj.login(
if (
Config.get(Config.notify_SMTPServerAddress) == ""
or Config.get(Config.notify_AuthorizationCode) == ""
or not bool(
re.match(
r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$",
Config.get(Config.notify_FromAddress),
Crypto.win_decryptor(Config.get(Config.notify_AuthorizationCode)),
)
smtpObj.sendmail(
)
or not bool(
re.match(
r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$",
to_address,
)
)
):
logger.error(
"请正确设置邮件通知的SMTP服务器地址、授权码、发件人地址和收件人地址"
)
self.push_info_bar.emit(
"error",
"邮件通知推送异常",
"请正确设置邮件通知的SMTP服务器地址、授权码、发件人地址和收件人地址",
-1,
)
return None
try:
# 定义邮件正文
if mode == "文本":
message = MIMEText(content, "plain", "utf-8")
elif mode == "网页":
message = MIMEMultipart("alternative")
message["From"] = formataddr(
(
Header("AUTO_MAA通知服务", "utf-8").encode(),
Config.get(Config.notify_FromAddress),
Config.get(Config.notify_ToAddress),
message.as_string(),
)
smtpObj.quit()
logger.success("邮件发送成功")
except Exception as e:
logger.error(f"发送邮件时出错:\n{e}")
self.push_info_bar.emit("error", "发送邮件时出错", f"{e}", -1)
def ServerChanPush(self, title, content):
"""使用Server酱推送通知支持 tag 和 channel避免使用SDK"""
if Config.get(Config.notify_IfServerChan):
send_key = Config.get(Config.notify_ServerChanKey)
if not send_key:
logger.error("请正确设置Server酱的SendKey")
self.push_info_bar.emit(
"error", "Server酱通知推送异常", "请正确设置Server酱的SendKey", -1
) # 发件人显示的名字
message["To"] = formataddr(
(
Header("AUTO_MAA用户", "utf-8").encode(),
to_address,
)
return None
) # 收件人显示的名字
message["Subject"] = Header(title, "utf-8")
try:
# 构造 URL
if send_key.startswith("sctp"):
match = re.match(r"^sctp(\d+)t", send_key)
if match:
url = f"https://{match.group(1)}.push.ft07.com/send/{send_key}.send"
else:
raise ValueError("SendKey 格式错误sctp")
if mode == "网页":
message.attach(MIMEText(content, "html", "utf-8"))
smtpObj = smtplib.SMTP_SSL(
Config.get(Config.notify_SMTPServerAddress),
465,
)
smtpObj.login(
Config.get(Config.notify_FromAddress),
Crypto.win_decryptor(Config.get(Config.notify_AuthorizationCode)),
)
smtpObj.sendmail(
Config.get(Config.notify_FromAddress),
to_address,
message.as_string(),
)
smtpObj.quit()
logger.success("邮件发送成功")
return None
except Exception as e:
logger.error(f"发送邮件时出错:\n{e}")
self.push_info_bar.emit("error", "发送邮件时出错", f"{e}", -1)
return None
return None
def ServerChanPush(self, title, content, send_key, tag, channel):
"""使用Server酱推送通知"""
if not send_key:
logger.error("请正确设置Server酱的SendKey")
self.push_info_bar.emit(
"error", "Server酱通知推送异常", "请正确设置Server酱的SendKey", -1
)
return None
try:
# 构造 URL
if send_key.startswith("sctp"):
match = re.match(r"^sctp(\d+)t", send_key)
if match:
url = f"https://{match.group(1)}.push.ft07.com/send/{send_key}.send"
else:
url = f"https://sctapi.ftqq.com/{send_key}.send"
# 构建 tags 和 channel
def is_valid(s):
return s == "" or (
s == "|".join(s.split("|"))
and (s.count("|") == 0 or all(s.split("|")))
)
tags = "|".join(
_.strip()
for _ in Config.get(Config.notify_ServerChanTag).split("|")
)
channels = "|".join(
_.strip()
for _ in Config.get(Config.notify_ServerChanChannel).split("|")
)
options = {}
if is_valid(tags):
options["tags"] = tags
else:
logger.warning("Server酱 Tag 配置不正确,将被忽略")
self.push_info_bar.emit(
"warning",
"Server酱通知推送异常",
"请正确设置 ServerChan 的 Tag",
-1,
)
if is_valid(channels):
options["channel"] = channels
else:
logger.warning("Server酱 Channel 配置不正确,将被忽略")
self.push_info_bar.emit(
"warning",
"Server酱通知推送异常",
"请正确设置 ServerChan 的 Channel",
-1,
)
# 请求发送
params = {"title": title, "desp": content, **options}
headers = {"Content-Type": "application/json;charset=utf-8"}
response = requests.post(url, json=params, headers=headers, timeout=10)
result = response.json()
if result.get("code") == 0:
logger.info("Server酱推送通知成功")
return True
else:
error_code = result.get("code", "-1")
logger.error(f"Server酱通知推送失败响应码{error_code}")
self.push_info_bar.emit(
"error", "Server酱通知推送失败", f"响应码:{error_code}", -1
)
return f"Server酱通知推送失败{error_code}"
except Exception as e:
logger.exception("Server酱通知推送异常")
self.push_info_bar.emit(
"error", "Server酱通知推送异常", f"请检查相关设置,如还有问题可联系开发者", -1
)
return f"Server酱通知推送异常{str(e)}"
def CompanyWebHookBotPush(self, title, content):
"""使用企业微信群机器人推送通知"""
if Config.get(Config.notify_IfCompanyWebHookBot):
if Config.get(Config.notify_CompanyWebHookBotUrl) == "":
logger.error("请正确设置企业微信群机器人的WebHook地址")
self.push_info_bar.emit(
"error",
"企业微信群机器人通知推送异常",
"请正确设置企业微信群机器人的WebHook地址",
-1,
)
return None
content = f"{title}\n{content}"
data = {"msgtype": "text", "text": {"content": content}}
# 从远程服务器获取最新主题图像
for _ in range(3):
try:
response = requests.post(
url=Config.get(Config.notify_CompanyWebHookBotUrl),
json=data,
timeout=10,
)
info = response.json()
break
except Exception as e:
err = e
time.sleep(0.1)
raise ValueError("SendKey 格式错误sctp")
else:
logger.error(f"推送企业微信群机器人时出错:{err}")
url = f"https://sctapi.ftqq.com/{send_key}.send"
# 构建 tags 和 channel
def is_valid(s):
return s == "" or (
s == "|".join(s.split("|"))
and (s.count("|") == 0 or all(s.split("|")))
)
tags = "|".join(_.strip() for _ in tag.split("|"))
channels = "|".join(_.strip() for _ in channel.split("|"))
options = {}
if is_valid(tags):
options["tags"] = tags
else:
logger.warning("Server酱 Tag 配置不正确,将被忽略")
self.push_info_bar.emit(
"error",
"企业微信群机器人通知推送失败",
f'使用企业微信群机器人推送通知时出错:{info["errmsg"]}',
"warning",
"Server酱通知推送异常",
"请正确设置 ServerChan 的 Tag",
-1,
)
return None
if info["errcode"] == 0:
logger.info("企业微信群机器人推送通知成功")
if is_valid(channels):
options["channel"] = channels
else:
logger.warning("Server酱 Channel 配置不正确,将被忽略")
self.push_info_bar.emit(
"warning",
"Server酱通知推送异常",
"请正确设置 ServerChan 的 Channel",
-1,
)
# 请求发送
params = {"title": title, "desp": content, **options}
headers = {"Content-Type": "application/json;charset=utf-8"}
response = requests.post(url, json=params, headers=headers, timeout=10)
result = response.json()
if result.get("code") == 0:
logger.info("Server酱推送通知成功")
return True
else:
logger.error(f"企业微信群机器人推送通知失败:{info}")
error_code = result.get("code", "-1")
logger.error(f"Server酱通知推送失败响应码{error_code}")
self.push_info_bar.emit(
"error",
"企业微信群机器人通知推送失败",
f'使用企业微信群机器人推送通知时出错:{info["errmsg"]}',
-1,
"error", "Server酱通知推送失败", f"响应码:{error_code}", -1
)
return f'使用企业微信群机器人推送通知时出错:{info["errmsg"]}'
return f"Server酱通知推送失败{error_code}"
except Exception as e:
logger.exception("Server酱通知推送异常")
self.push_info_bar.emit(
"error",
"Server酱通知推送异常",
"请检查相关设置和网络连接。如全部配置正确,请稍后再试。",
-1,
)
return f"Server酱通知推送异常{str(e)}"
def CompanyWebHookBotPush(self, title, content, webhook_url):
"""使用企业微信群机器人推送通知"""
if webhook_url == "":
logger.error("请正确设置企业微信群机器人的WebHook地址")
self.push_info_bar.emit(
"error",
"企业微信群机器人通知推送异常",
"请正确设置企业微信群机器人的WebHook地址",
-1,
)
return None
content = f"{title}\n{content}"
data = {"msgtype": "text", "text": {"content": content}}
for _ in range(3):
try:
response = requests.post(
url=webhook_url,
json=data,
timeout=10,
)
info = response.json()
break
except Exception as e:
err = e
time.sleep(0.1)
else:
logger.error(f"推送企业微信群机器人时出错:{err}")
self.push_info_bar.emit(
"error",
"企业微信群机器人通知推送失败",
f"使用企业微信群机器人推送通知时出错:{err}",
-1,
)
return None
if info["errcode"] == 0:
logger.info("企业微信群机器人推送通知成功")
return True
else:
logger.error(f"企业微信群机器人推送通知失败:{info}")
self.push_info_bar.emit(
"error",
"企业微信群机器人通知推送失败",
f"使用企业微信群机器人推送通知时出错:{err}",
-1,
)
return f"使用企业微信群机器人推送通知时出错:{err}"
def send_test_notification(self):
"""发送测试通知到所有已启用的通知渠道"""
@@ -294,6 +287,7 @@ class Notification(QWidget):
"文本",
"AUTO_MAA测试通知",
"这是 AUTO_MAA 外部通知测试信息。如果你看到了这段内容,说明 AUTO_MAA 的通知功能已经正确配置且可以正常工作!",
Config.get(Config.notify_ToAddress),
)
# 发送Server酱通知
@@ -301,6 +295,9 @@ class Notification(QWidget):
self.ServerChanPush(
"AUTO_MAA测试通知",
"这是 AUTO_MAA 外部通知测试信息。如果你看到了这段内容,说明 AUTO_MAA 的通知功能已经正确配置且可以正常工作!",
Config.get(Config.notify_ServerChanKey),
Config.get(Config.notify_ServerChanTag),
Config.get(Config.notify_ServerChanChannel),
)
# 发送企业微信机器人通知
@@ -308,6 +305,7 @@ class Notification(QWidget):
self.CompanyWebHookBotPush(
"AUTO_MAA测试通知",
"这是 AUTO_MAA 外部通知测试信息。如果你看到了这段内容,说明 AUTO_MAA 的通知功能已经正确配置且可以正常工作!",
Config.get(Config.notify_CompanyWebHookBotUrl),
)
return True

View File

@@ -26,7 +26,7 @@ v4.3
"""
from loguru import logger
from PySide6.QtWidgets import QApplication, QWidget
from PySide6.QtWidgets import QApplication
import sys
import ctypes
import win32gui
@@ -87,7 +87,7 @@ class _SystemHandler:
if sys.platform.startswith("win"):
if mode == "None":
if mode == "NoAction":
logger.info("不执行系统电源操作")
@@ -115,7 +115,7 @@ class _SystemHandler:
elif sys.platform.startswith("linux"):
if mode == "None":
if mode == "NoAction":
logger.info("不执行系统电源操作")

View File

@@ -25,17 +25,24 @@ v4.3
作者DLmaster_361
"""
import os
import re
from datetime import datetime
from functools import partial
from typing import Optional, Union, List, Dict
from urllib.parse import urlparse
import markdown
from PySide6.QtCore import Qt, QTime, QTimer, QEvent, QSize
from PySide6.QtGui import QIcon, QPixmap, QPainter, QPainterPath
from PySide6.QtWidgets import (
QApplication,
QWidget,
QWidget,
QLabel,
QHBoxLayout,
QVBoxLayout,
QSizePolicy,
)
from PySide6.QtCore import Qt, QTime, QTimer, QEvent, QSize
from PySide6.QtGui import QIcon, QPixmap, QPainter, QPainterPath
from qfluentwidgets import (
LineEdit,
PasswordLineEdit,
@@ -71,15 +78,13 @@ from qfluentwidgets import (
SwitchButton,
IndicatorPosition,
Slider,
ScrollArea,
Pivot,
PivotItem,
FlyoutViewBase,
PushSettingCard,
)
from qfluentwidgets.common.overload import singledispatchmethod
import os
import re
import markdown
from datetime import datetime
from urllib.parse import urlparse
from functools import partial
from typing import Optional, Union, List, Dict
from app.core import Config
from app.services import Crypto
@@ -105,6 +110,8 @@ class LineEditMessageBox(MessageBoxBase):
self.viewLayout.addWidget(self.title)
self.viewLayout.addWidget(self.input)
self.input.setFocus()
class ComboBoxMessageBox(MessageBoxBase):
"""选择对话框"""
@@ -268,6 +275,41 @@ class NoticeMessageBox(MessageBoxBase):
self.Layout.addStretch(1)
class SettingFlyoutView(FlyoutViewBase):
"""设置卡二级菜单弹出组件"""
def __init__(
self,
parent,
title: str,
setting_cards: List[Union[SettingCard, HeaderCardWidget]],
):
super().__init__(parent)
self.title = SubtitleLabel(title)
content_widget = QWidget()
content_layout = QVBoxLayout(content_widget)
content_layout.setSpacing(0)
content_layout.setContentsMargins(0, 0, 11, 0)
for setting_card in setting_cards:
content_layout.addWidget(setting_card)
scrollArea = ScrollArea()
scrollArea.setWidgetResizable(True)
scrollArea.setContentsMargins(0, 0, 0, 0)
scrollArea.setStyleSheet("background: transparent; border: none;")
scrollArea.setWidget(content_widget)
self.viewLayout = QVBoxLayout(self)
self.viewLayout.setSpacing(12)
self.viewLayout.setContentsMargins(20, 16, 9, 16)
self.viewLayout.addWidget(self.title)
self.viewLayout.addWidget(scrollArea)
self.setVisible(False)
class SwitchSettingCard(SettingCard):
"""Setting card with switch button"""
@@ -428,24 +470,27 @@ class LineEditSettingCard(SettingCard):
self.LineEdit.setMinimumWidth(250)
self.LineEdit.setPlaceholderText(text)
if configItem:
self.setValue(self.qconfig.get(configItem))
configItem.valueChanged.connect(self.setValue)
self.hBoxLayout.addWidget(self.LineEdit, 0, Qt.AlignRight)
self.hBoxLayout.addSpacing(16)
self.configItem.valueChanged.connect(self.setValue)
self.LineEdit.textChanged.connect(self.__textChanged)
self.setValue(self.qconfig.get(configItem))
def __textChanged(self, content: str):
self.setValue(content.strip())
self.configItem.valueChanged.disconnect(self.setValue)
self.qconfig.set(self.configItem, content.strip())
self.configItem.valueChanged.connect(self.setValue)
self.textChanged.emit(content.strip())
def setValue(self, content: str):
if self.configItem:
self.qconfig.set(self.configItem, content.strip())
self.LineEdit.textChanged.disconnect(self.__textChanged)
self.LineEdit.setText(content.strip())
self.LineEdit.textChanged.connect(self.__textChanged)
class PasswordLineEditSettingCard(SettingCard):
@@ -474,35 +519,29 @@ class PasswordLineEditSettingCard(SettingCard):
self.LineEdit.setPlaceholderText(text)
if algorithm == "AUTO":
self.LineEdit.setViewPasswordButtonVisible(False)
self.if_setValue = False
if configItem:
self.setValue(self.qconfig.get(configItem))
configItem.valueChanged.connect(self.setValue)
self.hBoxLayout.addWidget(self.LineEdit, 0, Qt.AlignRight)
self.hBoxLayout.addSpacing(16)
self.configItem.valueChanged.connect(self.setValue)
self.LineEdit.textChanged.connect(self.__textChanged)
self.setValue(self.qconfig.get(configItem))
def __textChanged(self, content: str):
if self.if_setValue:
return None
self.configItem.valueChanged.disconnect(self.setValue)
if self.algorithm == "DPAPI":
self.setValue(Crypto.win_encryptor(content))
self.qconfig.set(self.configItem, Crypto.win_encryptor(content))
elif self.algorithm == "AUTO":
self.setValue(Crypto.AUTO_encryptor(content))
self.qconfig.set(self.configItem, Crypto.AUTO_encryptor(content))
self.configItem.valueChanged.connect(self.setValue)
self.textChanged.emit()
def setValue(self, content: str):
self.if_setValue = True
if self.configItem:
self.qconfig.set(self.configItem, content)
self.LineEdit.textChanged.disconnect(self.__textChanged)
if self.algorithm == "DPAPI":
self.LineEdit.setText(Crypto.win_decryptor(content))
elif self.algorithm == "AUTO":
@@ -518,59 +557,7 @@ class PasswordLineEditSettingCard(SettingCard):
self.LineEdit.setText("************")
self.LineEdit.setPasswordVisible(False)
self.LineEdit.setReadOnly(True)
self.if_setValue = False
class UserLableSettingCard(SettingCard):
"""Setting card with User's Lable"""
def __init__(
self,
icon: Union[str, QIcon, FluentIconBase],
title: str,
content: Union[str, None],
qconfig: QConfig,
configItems: Dict[str, ConfigItem],
parent=None,
):
super().__init__(icon, title, content, parent)
self.qconfig = qconfig
self.configItems = configItems
self.Lable = SubtitleLabel(self)
if configItems:
for configItem in configItems.values():
configItem.valueChanged.connect(self.setValue)
self.setValue()
self.hBoxLayout.addWidget(self.Lable, 0, Qt.AlignRight)
self.hBoxLayout.addSpacing(16)
def setValue(self):
if self.configItems:
text_list = []
if not self.qconfig.get(self.configItems["IfPassCheck"]):
text_list.append("未通过人工排查")
text_list.append(
f"今日已代理{self.qconfig.get(self.configItems["ProxyTimes"])}"
if Config.server_date().strftime("%Y-%m-%d")
== self.qconfig.get(self.configItems["LastProxyDate"])
else "今日未进行代理"
)
text_list.append(
"本周剿灭已完成"
if datetime.strptime(
self.qconfig.get(self.configItems["LastAnnihilationDate"]),
"%Y-%m-%d",
).isocalendar()[:2]
== Config.server_date().isocalendar()[:2]
else "本周剿灭未完成"
)
self.Lable.setText(" | ".join(text_list))
self.LineEdit.textChanged.connect(self.__textChanged)
class PushAndSwitchButtonSettingCard(SettingCard):
@@ -721,7 +708,7 @@ class NoOptionComboBoxSettingCard(SettingCard):
value: List[str],
texts: List[str],
qconfig: QConfig,
configItem: OptionsConfigItem,
configItem: ConfigItem,
parent=None,
):
@@ -754,7 +741,7 @@ class NoOptionComboBoxSettingCard(SettingCard):
def reLoadOptions(self, value: List[str], texts: List[str]):
self.comboBox.currentIndexChanged.disconnect()
self.comboBox.currentIndexChanged.disconnect(self._onCurrentIndexChanged)
self.comboBox.clear()
self.optionToText = {o: t for o, t in zip(value, texts)}
for text, option in zip(texts, value):
@@ -776,7 +763,7 @@ class EditableComboBoxSettingCard(SettingCard):
value: List[str],
texts: List[str],
qconfig: QConfig,
configItem: OptionsConfigItem,
configItem: ConfigItem,
parent=None,
):
@@ -826,7 +813,7 @@ class EditableComboBoxSettingCard(SettingCard):
def reLoadOptions(self, value: List[str], texts: List[str]):
self.comboBox.currentIndexChanged.disconnect()
self.comboBox.currentIndexChanged.disconnect(self._onCurrentIndexChanged)
self.comboBox.clear()
self.optionToText = {o: t for o, t in zip(value, texts)}
for text, option in zip(texts, value):
@@ -864,6 +851,169 @@ class EditableComboBoxSettingCard(SettingCard):
self.currentIndexChanged.emit(self.count() - 1)
class SpinBoxWithPlanSettingCard(SpinBoxSettingCard):
textChanged = Signal(int)
def __init__(
self,
icon: Union[str, QIcon, FluentIconBase],
title: str,
content: Union[str, None],
range: tuple[int, int],
qconfig: QConfig,
configItem: ConfigItem,
parent=None,
):
super().__init__(icon, title, content, range, qconfig, configItem, parent)
self.configItem_plan = None
self.LineEdit = LineEdit(self)
self.LineEdit.setMinimumWidth(150)
self.LineEdit.setReadOnly(True)
self.LineEdit.setVisible(False)
self.hBoxLayout.insertWidget(5, self.LineEdit, 0, Qt.AlignRight)
def setText(self, value: int) -> None:
self.LineEdit.setText(str(value))
def switch_mode(self, mode: str) -> None:
"""切换模式"""
if mode == "固定":
self.LineEdit.setVisible(False)
self.SpinBox.setVisible(True)
elif mode == "计划":
self.SpinBox.setVisible(False)
self.LineEdit.setVisible(True)
def change_plan(self, configItem_plan: ConfigItem) -> None:
"""切换计划"""
if self.configItem_plan is not None:
self.configItem_plan.valueChanged.disconnect(self.setText)
self.configItem_plan = configItem_plan
self.configItem_plan.valueChanged.connect(self.setText)
self.setText(self.qconfig.get(self.configItem_plan))
class ComboBoxWithPlanSettingCard(ComboBoxSettingCard):
def __init__(
self,
icon: Union[str, QIcon, FluentIconBase],
title: str,
content: Union[str, None],
texts: List[str],
qconfig: QConfig,
configItem: OptionsConfigItem,
parent=None,
):
super().__init__(icon, title, content, texts, qconfig, configItem, parent)
self.configItem_plan = None
self.LineEdit = LineEdit(self)
self.LineEdit.setMinimumWidth(150)
self.LineEdit.setReadOnly(True)
self.LineEdit.setVisible(False)
self.hBoxLayout.insertWidget(5, self.LineEdit, 0, Qt.AlignRight)
def setText(self, value: str) -> None:
if value not in self.optionToText:
self.optionToText[value] = value
self.LineEdit.setText(self.optionToText[value])
def switch_mode(self, mode: str) -> None:
"""切换模式"""
if mode == "固定":
self.LineEdit.setVisible(False)
self.comboBox.setVisible(True)
elif mode == "计划":
self.comboBox.setVisible(False)
self.LineEdit.setVisible(True)
def change_plan(self, configItem_plan: ConfigItem) -> None:
"""切换计划"""
if self.configItem_plan is not None:
self.configItem_plan.valueChanged.disconnect(self.setText)
self.configItem_plan = configItem_plan
self.configItem_plan.valueChanged.connect(self.setText)
self.setText(self.qconfig.get(self.configItem_plan))
class EditableComboBoxWithPlanSettingCard(EditableComboBoxSettingCard):
def __init__(
self,
icon: Union[str, QIcon, FluentIconBase],
title: str,
content: Union[str, None],
value: List[str],
texts: List[str],
qconfig: QConfig,
configItem: ConfigItem,
parent=None,
):
super().__init__(
icon, title, content, value, texts, qconfig, configItem, parent
)
self.configItem_plan = None
self.LineEdit = LineEdit(self)
self.LineEdit.setMinimumWidth(150)
self.LineEdit.setReadOnly(True)
self.LineEdit.setVisible(False)
self.hBoxLayout.insertWidget(5, self.LineEdit, 0, Qt.AlignRight)
def setText(self, value: str) -> None:
if value not in self.optionToText:
self.optionToText[value] = value
self.LineEdit.setText(self.optionToText[value])
def switch_mode(self, mode: str) -> None:
"""切换模式"""
if mode == "固定":
self.LineEdit.setVisible(False)
self.comboBox.setVisible(True)
elif mode == "计划":
self.comboBox.setVisible(False)
self.LineEdit.setVisible(True)
def change_plan(self, configItem_plan: ConfigItem) -> None:
"""切换计划"""
if self.configItem_plan is not None:
self.configItem_plan.valueChanged.disconnect(self.setText)
self.configItem_plan = configItem_plan
self.configItem_plan.valueChanged.connect(self.setText)
self.setText(self.qconfig.get(self.configItem_plan))
class TimeEditSettingCard(SettingCard):
enabledChanged = Signal(bool)
@@ -930,6 +1080,418 @@ class TimeEditSettingCard(SettingCard):
self.TimeEdit.setTime(QTime.fromString(value, "HH:mm"))
class UserLableSettingCard(SettingCard):
"""Setting card with User's Lable"""
def __init__(
self,
icon: Union[str, QIcon, FluentIconBase],
title: str,
content: Union[str, None],
qconfig: QConfig,
configItems: Dict[str, ConfigItem],
parent=None,
):
super().__init__(icon, title, content, parent)
self.qconfig = qconfig
self.configItems = configItems
self.Lable = SubtitleLabel(self)
if configItems:
for configItem in configItems.values():
configItem.valueChanged.connect(self.setValue)
self.setValue()
self.hBoxLayout.addWidget(self.Lable, 0, Qt.AlignRight)
self.hBoxLayout.addSpacing(16)
def setValue(self):
text_list = []
if self.configItems:
if not self.qconfig.get(self.configItems["IfPassCheck"]):
text_list.append("未通过人工排查")
text_list.append(
f"今日已代理{self.qconfig.get(self.configItems["ProxyTimes"])}"
if Config.server_date().strftime("%Y-%m-%d")
== self.qconfig.get(self.configItems["LastProxyDate"])
else "今日未进行代理"
)
text_list.append(
"本周剿灭已完成"
if datetime.strptime(
self.qconfig.get(self.configItems["LastAnnihilationDate"]),
"%Y-%m-%d",
).isocalendar()[:2]
== Config.server_date().isocalendar()[:2]
else "本周剿灭未完成"
)
self.Lable.setText(" | ".join(text_list))
class UserTaskSettingCard(PushSettingCard):
"""Setting card with User's Task"""
def __init__(
self,
icon: Union[str, QIcon, FluentIconBase],
title: str,
content: Union[str, None],
text: str,
qconfig: QConfig,
configItems: Dict[str, ConfigItem],
parent=None,
):
super().__init__(text, icon, title, content, parent)
self.qconfig = qconfig
self.configItems = configItems
self.Lable = SubtitleLabel(self)
if configItems:
for config_item in configItems.values():
config_item.valueChanged.connect(self.setValues)
self.setValues()
self.hBoxLayout.addWidget(self.Lable, 0, Qt.AlignRight)
self.hBoxLayout.addSpacing(16)
def setValues(self):
text_list = []
if self.configItems:
if self.qconfig.get(self.configItems["IfWakeUp"]):
text_list.append("开始唤醒")
if self.qconfig.get(self.configItems["IfRecruiting"]):
text_list.append("自动公招")
if self.qconfig.get(self.configItems["IfBase"]):
text_list.append("基建换班")
if self.qconfig.get(self.configItems["IfCombat"]):
text_list.append("刷理智")
if self.qconfig.get(self.configItems["IfMall"]):
text_list.append("获取信用及购物")
if self.qconfig.get(self.configItems["IfMission"]):
text_list.append("领取奖励")
if self.qconfig.get(self.configItems["IfAutoRoguelike"]):
text_list.append("自动肉鸽")
if self.qconfig.get(self.configItems["IfReclamation"]):
text_list.append("生息演算")
if text_list:
self.setContent(f"任务序列:{" - ".join(text_list)}")
else:
self.setContent("未启用任何任务项")
class UserNoticeSettingCard(PushAndSwitchButtonSettingCard):
"""Setting card with User's Notice"""
def __init__(
self,
icon: Union[str, QIcon, FluentIconBase],
title: str,
content: Union[str, None],
text: str,
qconfig: QConfig,
configItem: ConfigItem,
configItems: Dict[str, ConfigItem],
parent=None,
):
super().__init__(icon, title, content, text, qconfig, configItem, parent)
self.qconfig = qconfig
self.configItems = configItems
self.Lable = SubtitleLabel(self)
if configItems:
for config_item in configItems.values():
config_item.valueChanged.connect(self.setValues)
self.setValues()
self.hBoxLayout.addWidget(self.Lable, 0, Qt.AlignRight)
self.hBoxLayout.addSpacing(16)
def setValues(self):
def short_str(s: str) -> str:
if s.startswith(("SC", "sc")):
# SendKey首4 + 末4
return f"{s[:4]}***{s[-4:]}" if len(s) > 8 else s
elif s.startswith(("http://", "https://")):
# Webhook URL域名 + 路径尾3
parsed_url = urlparse(s)
domain = parsed_url.netloc
path_tail = (
parsed_url.path[-3:]
if len(parsed_url.path) > 3
else parsed_url.path
)
return f"{domain}***{path_tail}"
elif "@" in s:
# 邮箱:@前3/6 + 域名
username, domain = s.split("@", 1)
displayed_name = f"{username[:3]}***" if len(username) > 6 else username
return f"{displayed_name}@{domain}"
else:
# 普通字符串末尾3字符
return f"***{s[-3:]}" if len(s) > 3 else s
text_list = []
if self.configItems:
if not (
self.qconfig.get(self.configItems["IfSendStatistic"])
or self.qconfig.get(self.configItems["IfSendSixStar"])
):
text_list.append("未启用任何通知项")
if self.qconfig.get(self.configItems["IfSendStatistic"]):
text_list.append("统计信息已启用")
if self.qconfig.get(self.configItems["IfSendSixStar"]):
text_list.append("六星喜报已启用")
if self.qconfig.get(self.configItems["IfSendMail"]):
text_list.append(
f"邮箱通知:{short_str(self.qconfig.get(self.configItems["ToAddress"]))}"
)
if self.qconfig.get(self.configItems["IfServerChan"]):
text_list.append(
f"Server酱通知{short_str(self.qconfig.get(self.configItems["ServerChanKey"]))}"
)
if self.qconfig.get(self.configItems["IfCompanyWebHookBot"]):
text_list.append(
f"企业微信通知:{short_str(self.qconfig.get(self.configItems["CompanyWebHookBotUrl"]))}"
)
self.setContent(" | ".join(text_list))
class StatusSwitchSetting(SwitchButton):
def __init__(
self,
qconfig: QConfig,
configItem_check: ConfigItem,
configItem_enable: ConfigItem,
parent=None,
):
super().__init__(parent)
self.qconfig = qconfig
self.configItem_check = configItem_check
self.configItem_enable = configItem_enable
self.setOffText("")
self.setOnText("")
if configItem_check:
self.setValue(self.qconfig.get(configItem_check))
configItem_check.valueChanged.connect(self.setValue)
if configItem_enable:
self.setEnabled(self.qconfig.get(configItem_enable))
configItem_enable.valueChanged.connect(self.setEnabled)
self.checkedChanged.connect(self.setValue)
def setValue(self, isChecked: bool):
if self.configItem_check:
self.qconfig.set(self.configItem_check, isChecked)
self.setChecked(isChecked)
class ComboBoxSetting(ComboBox):
def __init__(
self,
texts: List[str],
qconfig: QConfig,
configItem: OptionsConfigItem,
parent=None,
):
super().__init__(parent)
self.qconfig = qconfig
self.configItem = configItem
self.optionToText = {o: t for o, t in zip(configItem.options, texts)}
for text, option in zip(texts, configItem.options):
self.addItem(text, userData=option)
self.setCurrentText(self.optionToText[self.qconfig.get(configItem)])
self.currentIndexChanged.connect(self._onCurrentIndexChanged)
configItem.valueChanged.connect(self.setValue)
def _onCurrentIndexChanged(self, index: int):
self.qconfig.set(self.configItem, self.itemData(index))
def setValue(self, value):
if value not in self.optionToText:
return
self.setCurrentText(self.optionToText[value])
self.qconfig.set(self.configItem, value)
class NoOptionComboBoxSetting(ComboBox):
def __init__(
self,
value: List[str],
texts: List[str],
qconfig: QConfig,
configItem: OptionsConfigItem,
parent=None,
):
super().__init__(parent)
self.qconfig = qconfig
self.configItem = configItem
self.optionToText = {o: t for o, t in zip(value, texts)}
for text, option in zip(texts, value):
self.addItem(text, userData=option)
self.setCurrentText(self.optionToText[self.qconfig.get(configItem)])
self.currentIndexChanged.connect(self._onCurrentIndexChanged)
configItem.valueChanged.connect(self.setValue)
def _onCurrentIndexChanged(self, index: int):
self.qconfig.set(self.configItem, self.itemData(index))
def setValue(self, value):
if value not in self.optionToText:
return
self.setCurrentText(self.optionToText[value])
self.qconfig.set(self.configItem, value)
def reLoadOptions(self, value: List[str], texts: List[str]):
self.currentIndexChanged.disconnect(self._onCurrentIndexChanged)
self.clear()
self.optionToText = {o: t for o, t in zip(value, texts)}
for text, option in zip(texts, value):
self.addItem(text, userData=option)
self.setCurrentText(self.optionToText[self.qconfig.get(self.configItem)])
self.currentIndexChanged.connect(self._onCurrentIndexChanged)
class EditableComboBoxSetting(EditableComboBox):
def __init__(
self,
value: List[str],
texts: List[str],
qconfig: QConfig,
configItem: OptionsConfigItem,
parent=None,
):
super().__init__(parent)
self.qconfig = qconfig
self.configItem = configItem
self.optionToText = {o: t for o, t in zip(value, texts)}
for text, option in zip(texts, value):
self.addItem(text, userData=option)
if qconfig.get(configItem) not in self.optionToText:
self.optionToText[qconfig.get(configItem)] = qconfig.get(configItem)
self.addItem(qconfig.get(configItem), userData=qconfig.get(configItem))
self.setCurrentText(self.optionToText[qconfig.get(configItem)])
self.currentIndexChanged.connect(self._onCurrentIndexChanged)
configItem.valueChanged.connect(self.setValue)
def _onCurrentIndexChanged(self, index: int):
self.qconfig.set(
self.configItem,
(self.itemData(index) if self.itemData(index) else self.itemText(index)),
)
def setValue(self, value):
if value not in self.optionToText:
self.optionToText[value] = value
if self.findText(value) == -1:
self.addItem(value, userData=value)
else:
self.setItemData(self.findText(value), value)
self.setCurrentText(self.optionToText[value])
self.qconfig.set(self.configItem, value)
def reLoadOptions(self, value: List[str], texts: List[str]):
self.currentIndexChanged.disconnect(self._onCurrentIndexChanged)
self.clear()
self.optionToText = {o: t for o, t in zip(value, texts)}
for text, option in zip(texts, value):
self.addItem(text, userData=option)
if self.qconfig.get(self.configItem) not in self.optionToText:
self.optionToText[self.qconfig.get(self.configItem)] = self.qconfig.get(
self.configItem
)
self.addItem(
self.qconfig.get(self.configItem),
userData=self.qconfig.get(self.configItem),
)
self.setCurrentText(self.optionToText[self.qconfig.get(self.configItem)])
self.currentIndexChanged.connect(self._onCurrentIndexChanged)
def _onReturnPressed(self):
if not self.text():
return
index = self.findText(self.text())
if index >= 0 and index != self.currentIndex():
self._currentIndex = index
self.currentIndexChanged.emit(index)
elif index == -1:
self.addItem(self.text())
self.setCurrentIndex(self.count() - 1)
self.currentIndexChanged.emit(self.count() - 1)
class SpinBoxSetting(SpinBox):
def __init__(
self,
range: tuple[int, int],
qconfig: QConfig,
configItem: ConfigItem,
parent=None,
):
super().__init__(parent)
self.qconfig = qconfig
self.configItem = configItem
self.setRange(range[0], range[1])
if configItem:
self.set_value(qconfig.get(configItem))
configItem.valueChanged.connect(self.set_value)
self.valueChanged.connect(self.set_value)
def set_value(self, value: int):
if self.configItem:
self.qconfig.set(self.configItem, value)
self.setValue(value)
class HistoryCard(HeaderCardWidget):
def __init__(self, qconfig: QConfig, configItem: ConfigItem, parent=None):
@@ -1049,9 +1611,7 @@ class UrlListSettingCard(ExpandSettingCard):
"""show confirm dialog"""
choice = MessageBox(
"确认",
f"确定要删除 {item.url} 代理网址吗?",
self.window(),
"确认", f"确定要删除 {item.url} 代理网址吗?", self.window()
)
if choice.exec():
self.__removeUrl(item)
@@ -1128,6 +1688,84 @@ class QuantifiedItemCard(CardWidget):
self.Layout.addWidget(self.Numb)
class PivotArea(ScrollArea):
def __init__(self, parent=None):
super().__init__(parent)
# 创建中间容器并设置布局
self.center_container = QWidget()
self.center_layout = QHBoxLayout(self.center_container)
self.center_layout.setContentsMargins(0, 0, 0, 0)
self.center_layout.setSpacing(0)
self.center_container.setStyleSheet("background: transparent; border: none;")
self.center_container.setFixedHeight(45)
self.pivot = self._Pivot(self)
self.pivot.ItemNumbChanged.connect(
lambda: QTimer.singleShot(
100,
lambda: (
self.center_container.setFixedWidth(
max(self.width() - 2, self.pivot.width())
)
),
)
)
self.center_layout.addStretch(1)
self.center_layout.addWidget(self.pivot)
self.center_layout.addStretch(1)
self.setWidgetResizable(False)
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.viewport().setCursor(Qt.ArrowCursor)
self.setStyleSheet("background: transparent; border: none;")
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
self.setWidget(self.center_container)
def wheelEvent(self, event):
scroll_bar = self.horizontalScrollBar()
if scroll_bar.maximum() > 0:
delta = event.angleDelta().y()
scroll_bar.setValue(scroll_bar.value() - delta // 15)
event.ignore()
def resizeEvent(self, event):
super().resizeEvent(event)
self.center_container.setFixedWidth(max(self.width() - 2, self.pivot.width()))
QTimer.singleShot(
100,
lambda: (
self.center_container.setFixedWidth(
max(self.width() - 2, self.pivot.width())
)
),
)
class _Pivot(Pivot):
ItemNumbChanged = Signal()
def __init__(self, parent=None):
super().__init__(parent)
def insertWidget(
self, index: int, routeKey: str, widget: PivotItem, onClick=None
):
super().insertWidget(index, routeKey, widget, onClick)
self.ItemNumbChanged.emit()
def removeWidget(self, routeKey: str):
super().removeWidget(routeKey)
self.ItemNumbChanged.emit()
def clear(self):
super().clear()
self.ItemNumbChanged.emit()
class QuickExpandGroupCard(ExpandGroupSettingCard):
"""全局配置"""

View File

@@ -33,8 +33,8 @@ from PySide6.QtWidgets import (
QHBoxLayout,
)
from qfluentwidgets import (
BodyLabel,
CardWidget,
Pivot,
ScrollArea,
FluentIcon,
HeaderCardWidget,
@@ -44,13 +44,12 @@ from qfluentwidgets import (
SubtitleLabel,
PushButton,
)
from PySide6.QtCore import Qt
from PySide6.QtGui import QTextCursor
from typing import List, Dict
from app.core import Config, TaskManager, Task, MainInfoBar
from .Widget import StatefulItemCard, ComboBoxMessageBox
from app.core import Config, TaskManager, Task, MainInfoBar, SoundPlayer
from .Widget import StatefulItemCard, ComboBoxMessageBox, PivotArea
class DispatchCenter(QWidget):
@@ -60,13 +59,29 @@ class DispatchCenter(QWidget):
self.setObjectName("调度中枢")
self.pivot = Pivot(self)
self.multi_button = PushButton(FluentIcon.ADD, "添加任务", self)
self.multi_button.setToolTip("添加任务")
self.multi_button.clicked.connect(self.start_multi_task)
self.power_combox = ComboBox()
self.power_combox.addItem("无动作", userData="NoAction")
self.power_combox.addItem("退出软件", userData="KillSelf")
self.power_combox.addItem("睡眠", userData="Sleep")
self.power_combox.addItem("休眠", userData="Hibernate")
self.power_combox.addItem("关机", userData="Shutdown")
self.power_combox.setCurrentText("无动作")
self.power_combox.currentIndexChanged.connect(self.set_power_sign)
self.pivotArea = PivotArea(self)
self.pivot = self.pivotArea.pivot
self.stackedWidget = QStackedWidget(self)
self.Layout = QVBoxLayout(self)
self.stackedWidget.setContentsMargins(0, 0, 0, 0)
self.stackedWidget.setStyleSheet("background: transparent; border: none;")
self.script_list: Dict[str, DispatchBox] = {}
self.script_list: Dict[str, DispatchCenter.DispatchBox] = {}
dispatch_box = DispatchBox("主调度台", self)
dispatch_box = self.DispatchBox("主调度台", self)
self.script_list["主调度台"] = dispatch_box
self.stackedWidget.addWidget(self.script_list["主调度台"])
self.pivot.addItem(
@@ -76,7 +91,15 @@ class DispatchCenter(QWidget):
icon=FluentIcon.CAFE,
)
self.Layout.addWidget(self.pivot, 0, Qt.AlignHCenter)
h_layout = QHBoxLayout()
h_layout.addWidget(self.multi_button)
h_layout.addWidget(self.pivotArea)
h_layout.addWidget(BodyLabel("全部完成后", self))
h_layout.addWidget(self.power_combox)
h_layout.setContentsMargins(11, 5, 11, 0)
self.Layout = QVBoxLayout(self)
self.Layout.addLayout(h_layout)
self.Layout.addWidget(self.stackedWidget)
self.Layout.setContentsMargins(0, 0, 0, 0)
@@ -87,7 +110,7 @@ class DispatchCenter(QWidget):
def add_board(self, task: Task) -> None:
"""添加一个调度台界面"""
dispatch_box = DispatchBox(task.name, self)
dispatch_box = self.DispatchBox(task.name, self)
dispatch_box.top_bar.main_button.clicked.connect(
lambda: TaskManager.stop_task(task.name)
@@ -123,7 +146,6 @@ class DispatchCenter(QWidget):
self.script_list["主调度台"].top_bar.Lable.show()
self.script_list["主调度台"].top_bar.object.hide()
self.script_list["主调度台"].top_bar.mode.hide()
self.script_list["主调度台"].top_bar.multi_button.show()
self.script_list["主调度台"].top_bar.main_button.clicked.disconnect()
self.script_list["主调度台"].top_bar.main_button.setText("中止任务")
self.script_list["主调度台"].top_bar.main_button.clicked.connect(
@@ -154,7 +176,6 @@ class DispatchCenter(QWidget):
self.script_list["主调度台"].top_bar.Lable.hide()
self.script_list["主调度台"].top_bar.object.show()
self.script_list["主调度台"].top_bar.mode.show()
self.script_list["主调度台"].top_bar.multi_button.hide()
self.script_list["主调度台"].top_bar.main_button.clicked.disconnect()
self.script_list["主调度台"].top_bar.main_button.setText("开始任务")
self.script_list["主调度台"].top_bar.main_button.clicked.connect(
@@ -208,301 +229,323 @@ class DispatchCenter(QWidget):
self.script_list["主调度台"].top_bar.mode.addItems(["自动代理", "人工排查"])
self.script_list["主调度台"].top_bar.mode.setCurrentIndex(0)
def update_power_sign(self) -> None:
"""更新电源设置"""
class DispatchBox(QWidget):
mode_book = {
"NoAction": "无动作",
"KillSelf": "退出软件",
"Sleep": "睡眠",
"Hibernate": "休眠",
"Shutdown": "关机",
}
self.power_combox.currentIndexChanged.disconnect()
self.power_combox.setCurrentText(mode_book[Config.power_sign])
self.power_combox.currentIndexChanged.connect(self.set_power_sign)
def __init__(self, name: str, parent=None):
super().__init__(parent)
def set_power_sign(self) -> None:
"""设置所有任务完成后动作"""
self.setObjectName(name)
if not Config.running_list:
layout = QVBoxLayout()
scrollArea = ScrollArea()
scrollArea.setWidgetResizable(True)
content_widget = QWidget()
content_layout = QVBoxLayout(content_widget)
self.top_bar = self.DispatchTopBar(self, name)
self.info = self.DispatchInfoCard(self)
content_layout.addWidget(self.top_bar)
content_layout.addWidget(self.info)
scrollArea.setWidget(content_widget)
layout.addWidget(scrollArea)
self.setLayout(layout)
class DispatchTopBar(CardWidget):
def __init__(self, parent=None, name: str = None):
super().__init__(parent)
Layout = QHBoxLayout(self)
if name == "主调度台":
self.Lable = SubtitleLabel("", self)
self.Lable.hide()
self.object = ComboBox()
self.object.setPlaceholderText("请选择调度对象")
self.mode = ComboBox()
self.mode.setPlaceholderText("请选择调度模式")
self.multi_button = PushButton("添加任务")
self.multi_button.clicked.connect(self.start_multi_task)
self.main_button = PushButton("开始任务")
self.main_button.clicked.connect(self.start_main_task)
self.multi_button.hide()
Layout.addWidget(self.Lable)
Layout.addWidget(self.object)
Layout.addWidget(self.mode)
Layout.addStretch(1)
Layout.addWidget(self.multi_button)
Layout.addWidget(self.main_button)
else:
self.Lable = SubtitleLabel(name, self)
self.main_button = PushButton("中止任务")
Layout.addWidget(self.Lable)
Layout.addStretch(1)
Layout.addWidget(self.main_button)
def start_main_task(self):
"""开始任务"""
if self.object.currentIndex() == -1:
logger.warning("未选择调度对象")
MainInfoBar.push_info_bar(
"warning", "未选择调度对象", "请选择后再开始任务", 5000
)
return None
if self.mode.currentIndex() == -1:
logger.warning("未选择调度模式")
MainInfoBar.push_info_bar(
"warning", "未选择调度模式", "请选择后再开始任务", 5000
)
return None
if self.object.currentData() in Config.running_list:
logger.warning(f"任务已存在:{self.object.currentData()}")
MainInfoBar.push_info_bar(
"warning", "任务已存在", self.object.currentData(), 5000
)
return None
if "调度队列" in self.object.currentData():
logger.info(f"用户添加任务:{self.object.currentData()}")
TaskManager.add_task(
f"{self.mode.currentText()}_主调度台",
self.object.currentData(),
Config.queue_dict[self.object.currentData()]["Config"].toDict(),
)
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()}},
)
def start_multi_task(self):
"""开始任务"""
# 获取所有可用的队列和实例
text_list = []
data_list = []
for name, info in Config.queue_dict.items():
if name in Config.running_list:
continue
text_list.append(
"队列"
if info["Config"].get(info["Config"].queueSet_Name) == ""
else f"队列 - {info["Config"].get(info["Config"].queueSet_Name)}"
)
data_list.append(name)
for name, info in Config.member_dict.items():
if name in Config.running_list:
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)}"
)
data_list.append(name)
choice = ComboBoxMessageBox(
self.window(),
"选择一个对象以添加相应多开任务",
["选择调度对象"],
[text_list],
[data_list],
self.power_combox.currentIndexChanged.disconnect()
self.power_combox.setCurrentText("无动作")
self.power_combox.currentIndexChanged.connect(self.set_power_sign)
logger.warning("没有正在运行的任务,无法设置任务完成后动作")
MainInfoBar.push_info_bar(
"warning",
"没有正在运行的任务",
"无法设置任务完成后动作",
5000,
)
if choice.exec() and choice.input[0].currentIndex() != -1:
else:
if choice.input[0].currentData() in Config.running_list:
logger.warning(f"任务已存在:{choice.input[0].currentData()}")
MainInfoBar.push_info_bar(
"warning", "任务已存在", choice.input[0].currentData(), 5000
)
return None
Config.set_power_sign(self.power_combox.currentData())
if "调度队列" in choice.input[0].currentData():
def start_multi_task(self) -> None:
"""开始任务"""
# 获取所有可用的队列和实例
text_list = []
data_list = []
for name, info in Config.queue_dict.items():
if name in Config.running_list:
continue
text_list.append(
"队列"
if info["Config"].get(info["Config"].queueSet_Name) == ""
else f"队列 - {info["Config"].get(info["Config"].queueSet_Name)}"
)
data_list.append(name)
for name, info in Config.member_dict.items():
if name in Config.running_list:
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)}"
)
data_list.append(name)
choice = ComboBoxMessageBox(
self.window(),
"选择一个对象以添加相应多开任务",
["选择调度对象"],
[text_list],
[data_list],
)
if choice.exec() and choice.input[0].currentIndex() != -1:
if choice.input[0].currentData() in Config.running_list:
logger.warning(f"任务已存在:{choice.input[0].currentData()}")
MainInfoBar.push_info_bar(
"warning", "任务已存在", choice.input[0].currentData(), 5000
)
return None
if "调度队列" in choice.input[0].currentData():
logger.info(f"用户添加任务:{choice.input[0].currentData()}")
TaskManager.add_task(
"自动代理_新调度台",
choice.input[0].currentData(),
Config.queue_dict[choice.input[0].currentData()]["Config"].toDict(),
)
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(
"自动代理_新调度台",
choice.input[0].currentData(),
Config.queue_dict[choice.input[0].currentData()][
"Config"
].toDict(),
f"自定义队列 - {choice.input[0].currentData()}",
{"Queue": {"Member_1": choice.input[0].currentData()}},
)
elif "脚本" in choice.input[0].currentData():
class DispatchBox(QWidget):
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()}},
)
class DispatchInfoCard(HeaderCardWidget):
def __init__(self, parent=None):
def __init__(self, name: str, parent=None):
super().__init__(parent)
self.setTitle("调度信息")
self.setObjectName(name)
self.task = self.TaskInfoCard(self)
self.user = self.UserInfoCard(self)
self.log_text = self.LogCard(self)
self.top_bar = self.DispatchTopBar(self, name)
self.info = self.DispatchInfoCard(self)
self.viewLayout.addWidget(self.task)
self.viewLayout.addWidget(self.user)
self.viewLayout.addWidget(self.log_text)
content_widget = QWidget()
content_layout = QVBoxLayout(content_widget)
content_layout.setContentsMargins(0, 0, 0, 0)
content_layout.addWidget(self.top_bar)
content_layout.addWidget(self.info)
self.viewLayout.setStretch(0, 1)
self.viewLayout.setStretch(1, 1)
self.viewLayout.setStretch(2, 5)
scrollArea = ScrollArea()
scrollArea.setWidgetResizable(True)
scrollArea.setContentsMargins(0, 0, 0, 0)
scrollArea.setStyleSheet("background: transparent; border: none;")
scrollArea.setWidget(content_widget)
def update_board(self, task_list: list, user_list: list, log: str):
"""更新调度信息"""
layout = QVBoxLayout(self)
layout.addWidget(scrollArea)
self.task.update_task(task_list)
self.user.update_user(user_list)
self.log_text.text.setText(log)
class DispatchTopBar(CardWidget):
class TaskInfoCard(HeaderCardWidget):
def __init__(self, parent=None, name: str = None):
super().__init__(parent)
Layout = QHBoxLayout(self)
if name == "主调度台":
self.Lable = SubtitleLabel("", self)
self.Lable.hide()
self.object = ComboBox()
self.object.setPlaceholderText("请选择调度对象")
self.mode = ComboBox()
self.mode.setPlaceholderText("请选择调度模式")
self.main_button = PushButton("开始任务")
self.main_button.clicked.connect(self.start_main_task)
Layout.addWidget(self.Lable)
Layout.addWidget(self.object)
Layout.addWidget(self.mode)
Layout.addStretch(1)
Layout.addWidget(self.main_button)
else:
self.Lable = SubtitleLabel(name, self)
self.main_button = PushButton("中止任务")
Layout.addWidget(self.Lable)
Layout.addStretch(1)
Layout.addWidget(self.main_button)
def start_main_task(self):
"""开始任务"""
if self.object.currentIndex() == -1:
logger.warning("未选择调度对象")
MainInfoBar.push_info_bar(
"warning", "未选择调度对象", "请选择后再开始任务", 5000
)
return None
if self.mode.currentIndex() == -1:
logger.warning("未选择调度模式")
MainInfoBar.push_info_bar(
"warning", "未选择调度模式", "请选择后再开始任务", 5000
)
return None
if self.object.currentData() in Config.running_list:
logger.warning(f"任务已存在:{self.object.currentData()}")
MainInfoBar.push_info_bar(
"warning", "任务已存在", self.object.currentData(), 5000
)
return None
if "调度队列" in self.object.currentData():
logger.info(f"用户添加任务:{self.object.currentData()}")
TaskManager.add_task(
f"{self.mode.currentText()}_主调度台",
self.object.currentData(),
Config.queue_dict[self.object.currentData()]["Config"].toDict(),
)
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()}},
)
class DispatchInfoCard(HeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setTitle("任务队列")
self.Layout = QVBoxLayout()
self.viewLayout.addLayout(self.Layout)
self.viewLayout.setContentsMargins(3, 0, 3, 3)
self.setTitle("调度信息")
self.task_cards: List[StatefulItemCard] = []
self.task = self.TaskInfoCard(self)
self.user = self.UserInfoCard(self)
self.log_text = self.LogCard(self)
def create_task(self, task_list: list):
"""创建任务队列"""
self.viewLayout.addWidget(self.task)
self.viewLayout.addWidget(self.user)
self.viewLayout.addWidget(self.log_text)
while self.Layout.count() > 0:
item = self.Layout.takeAt(0)
if item.spacerItem():
self.Layout.removeItem(item.spacerItem())
elif item.widget():
item.widget().deleteLater()
self.viewLayout.setStretch(0, 1)
self.viewLayout.setStretch(1, 1)
self.viewLayout.setStretch(2, 5)
self.task_cards = []
def update_board(self, task_list: list, user_list: list, log: str):
"""更新调度信息"""
for task in task_list:
self.task.update_task(task_list)
self.user.update_user(user_list)
self.log_text.text.setText(log)
self.task_cards.append(StatefulItemCard(task))
self.Layout.addWidget(self.task_cards[-1])
class TaskInfoCard(HeaderCardWidget):
self.Layout.addStretch(1)
def __init__(self, parent=None):
super().__init__(parent)
self.setTitle("任务队列")
def update_task(self, task_list: list):
"""更新任务队列"""
self.Layout = QVBoxLayout()
self.viewLayout.addLayout(self.Layout)
self.viewLayout.setContentsMargins(3, 0, 3, 3)
for i in range(len(task_list)):
self.task_cards: List[StatefulItemCard] = []
self.task_cards[i].update_status(task_list[i][1])
def create_task(self, task_list: list):
"""创建任务队列"""
class UserInfoCard(HeaderCardWidget):
while self.Layout.count() > 0:
item = self.Layout.takeAt(0)
if item.spacerItem():
self.Layout.removeItem(item.spacerItem())
elif item.widget():
item.widget().deleteLater()
def __init__(self, parent=None):
super().__init__(parent)
self.setTitle("用户队列")
self.task_cards = []
self.Layout = QVBoxLayout()
self.viewLayout.addLayout(self.Layout)
self.viewLayout.setContentsMargins(3, 0, 3, 3)
for task in task_list:
self.user_cards: List[StatefulItemCard] = []
self.task_cards.append(StatefulItemCard(task))
self.Layout.addWidget(self.task_cards[-1])
def create_user(self, user_list: list):
"""创建用户队列"""
self.Layout.addStretch(1)
while self.Layout.count() > 0:
item = self.Layout.takeAt(0)
if item.spacerItem():
self.Layout.removeItem(item.spacerItem())
elif item.widget():
item.widget().deleteLater()
def update_task(self, task_list: list):
"""更新任务队列"""
self.user_cards = []
for i in range(len(task_list)):
for user in user_list:
self.task_cards[i].update_status(task_list[i][1])
self.user_cards.append(StatefulItemCard(user))
self.Layout.addWidget(self.user_cards[-1])
class UserInfoCard(HeaderCardWidget):
self.Layout.addStretch(1)
def __init__(self, parent=None):
super().__init__(parent)
self.setTitle("用户队列")
def update_user(self, user_list: list):
"""更新用户队列"""
self.Layout = QVBoxLayout()
self.viewLayout.addLayout(self.Layout)
self.viewLayout.setContentsMargins(3, 0, 3, 3)
for i in range(len(user_list)):
self.user_cards: List[StatefulItemCard] = []
self.user_cards[i].Label.setText(user_list[i][0])
self.user_cards[i].update_status(user_list[i][1])
def create_user(self, user_list: list):
"""创建用户队列"""
class LogCard(HeaderCardWidget):
while self.Layout.count() > 0:
item = self.Layout.takeAt(0)
if item.spacerItem():
self.Layout.removeItem(item.spacerItem())
elif item.widget():
item.widget().deleteLater()
def __init__(self, parent=None):
super().__init__(parent)
self.setTitle("日志")
self.user_cards = []
self.text = TextBrowser()
self.viewLayout.setContentsMargins(3, 0, 3, 3)
self.viewLayout.addWidget(self.text)
for user in user_list:
self.text.textChanged.connect(self.to_end)
self.user_cards.append(StatefulItemCard(user))
self.Layout.addWidget(self.user_cards[-1])
def to_end(self):
"""滚动到底部"""
self.Layout.addStretch(1)
self.text.moveCursor(QTextCursor.End)
self.text.ensureCursorVisible()
def update_user(self, user_list: list):
"""更新用户队列"""
for i in range(len(user_list)):
self.user_cards[i].Label.setText(user_list[i][0])
self.user_cards[i].update_status(user_list[i][1])
class LogCard(HeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setTitle("日志")
self.text = TextBrowser()
self.viewLayout.setContentsMargins(3, 0, 3, 3)
self.viewLayout.addWidget(self.text)
self.text.textChanged.connect(self.to_end)
def to_end(self):
"""滚动到底部"""
self.text.moveCursor(QTextCursor.End)
self.text.ensureCursorVisible()

View File

@@ -21,24 +21,19 @@
"""
AUTO_MAA
AUTO_MAA更新器
v1.2
v4.3
作者DLmaster_361
"""
import sys
import json
import zipfile
import requests
import subprocess
import time
import psutil
import win32crypt
import base64
from packaging import version
from functools import partial
from pathlib import Path
from PySide6.QtWidgets import QApplication, QDialog, QVBoxLayout
from PySide6.QtWidgets import QDialog, QVBoxLayout
from qfluentwidgets import (
ProgressBar,
IndeterminateProgressBar,
@@ -46,7 +41,7 @@ from qfluentwidgets import (
setTheme,
Theme,
)
from PySide6.QtGui import QIcon, QCloseEvent
from PySide6.QtGui import QCloseEvent
from PySide6.QtCore import QThread, Signal, QTimer, QEventLoop
from typing import List, Dict, Union
@@ -83,6 +78,8 @@ class DownloadProcess(QThread):
) -> None:
super(DownloadProcess, self).__init__()
self.setObjectName(f"DownloadProcess-{url}-{start_byte}-{end_byte}")
self.url = url
self.start_byte = start_byte
self.end_byte = end_byte
@@ -162,6 +159,8 @@ class ZipExtractProcess(QThread):
def __init__(self, name: str, app_path: Path, download_path: Path) -> None:
super(ZipExtractProcess, self).__init__()
self.setObjectName(f"ZipExtractProcess-{name}")
self.name = name
self.app_path = app_path
self.download_path = download_path
@@ -237,14 +236,9 @@ class DownloadManager(QDialog):
self.version = version
self.config = config
self.download_path = app_path / "DOWNLOAD_TEMP.zip" # 临时下载文件的路径
self.version_path = app_path / "resources/version.json"
self.download_process_dict: Dict[str, DownloadProcess] = {}
self.timer_dict: Dict[str, QTimer] = {}
self.setWindowTitle("AUTO_MAA更新器")
self.setWindowIcon(
QIcon(str(app_path / "resources/icons/AUTO_MAA_Updater.ico"))
)
self.resize(700, 70)
setTheme(Theme.AUTO, lazy=True)
@@ -266,14 +260,14 @@ class DownloadManager(QDialog):
def run(self) -> None:
if self.name == "MAA":
self.download_task1()
elif self.name == "AUTO_MAA":
if self.name == "AUTO_MAA":
if self.config["mode"] == "Proxy":
self.test_speed_task1()
self.speed_test_accomplish.connect(self.download_task1)
elif self.config["mode"] == "MirrorChyan":
self.download_task1()
elif self.config["mode"] == "MirrorChyan":
self.download_task1()
def get_download_url(self, mode: str) -> Union[str, Dict[str, str]]:
"""获取下载链接"""
@@ -300,9 +294,6 @@ class DownloadManager(QDialog):
elif mode == "下载":
if self.name == "MAA":
return f"https://jp-download.fearr.xyz/MAA/MAA-{version_text(self.version)}-win-x64.zip"
if self.name == "AUTO_MAA":
if self.config["mode"] == "Proxy":
@@ -325,6 +316,7 @@ class DownloadManager(QDialog):
return f"{selected_url}https://github.com/DLmaster361/AUTO_MAA/releases/download/{version_text(self.version)}/AUTO_MAA_{version_text(self.version)}.zip"
elif self.config["mode"] == "MirrorChyan":
with requests.get(
self.config["url"],
allow_redirects=True,
@@ -334,6 +326,14 @@ class DownloadManager(QDialog):
if response.status_code == 200:
return response.url
elif self.config["mode"] == "MirrorChyan":
with requests.get(
self.config["url"], allow_redirects=True, timeout=10, stream=True
) as response:
if response.status_code == 200:
return response.url
def test_speed_task1(self) -> None:
if self.isInterruptionRequested:
@@ -533,41 +533,25 @@ class DownloadManager(QDialog):
self.zip_extract.start()
self.zip_loop.exec()
self.update_info("正在删除已弃用的文件")
if (self.app_path / "changes.json").exists():
with (self.app_path / "changes.json").open(mode="r", encoding="utf-8") as f:
info: Dict[str, List[str]] = json.load(f)
if "deleted" in info:
for file_path in info["deleted"]:
if (self.app_path / file_path).exists():
(self.app_path / file_path).unlink()
(self.app_path / "changes.json").unlink()
self.update_info("正在删除临时文件")
self.update_progress(0, 0, 0)
if (self.app_path / "changes.json").exists():
(self.app_path / "changes.json").unlink()
if self.download_path.exists():
self.download_path.unlink()
# 主程序更新完成后打开对应程序
if not self.isInterruptionRequested and self.name == "AUTO_MAA":
subprocess.Popen(
[self.app_path / "AUTO_MAA.exe"],
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP
| subprocess.DETACHED_PROCESS
| subprocess.CREATE_NO_WINDOW,
)
elif not self.isInterruptionRequested and self.name == "MAA":
# 下载完成后打开对应程序
if not self.isInterruptionRequested and self.name == "MAA":
subprocess.Popen(
[self.app_path / "MAA.exe"],
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP
| subprocess.DETACHED_PROCESS
| subprocess.CREATE_NO_WINDOW,
)
self.update_info(f"{self.name}更新成功!")
if self.name == "AUTO_MAA":
self.update_info(f"即将安装{self.name}")
else:
self.update_info(f"{self.name}下载成功!")
self.update_progress(0, 100, 100)
self.download_accomplish.emit()
@@ -609,147 +593,3 @@ class DownloadManager(QDialog):
self.requestInterruption()
event.accept()
class AUTO_MAA_Downloader(QApplication):
def __init__(
self, app_path: Path, name: str, main_version: list, config: dict
) -> None:
super().__init__()
self.main = DownloadManager(app_path, name, main_version, config)
self.main.show()
self.main.run()
if __name__ == "__main__":
# 获取软件自身的路径
app_path = Path(sys.argv[0]).resolve().parent
# 从本地版本信息文件获取当前版本信息
if (app_path / "resources/version.json").exists():
with (app_path / "resources/version.json").open(
mode="r", encoding="utf-8"
) as f:
current_version_info = json.load(f)
current_version = list(
map(int, current_version_info["main_version"].split("."))
)
else:
current_version = [0, 0, 0, 0]
# 从本地配置文件获取更新信息
if (app_path / "config/config.json").exists():
with (app_path / "config/config.json").open(mode="r", encoding="utf-8") as f:
config = json.load(f)
if "Update" in config:
if "UpdateType" in config["Update"]:
update_type = config["Update"]["UpdateType"]
else:
update_type = "stable"
if "ProxyUrlList" in config["Update"]:
proxy_list = config["Update"]["ProxyUrlList"]
else:
proxy_list = []
if "ThreadNumb" in config["Update"]:
thread_numb = config["Update"]["ThreadNumb"]
else:
thread_numb = 8
if "MirrorChyanCDK" in config["Update"]:
mirrorchyan_CDK = (
win32crypt.CryptUnprotectData(
base64.b64decode(config["Update"]["MirrorChyanCDK"]),
None,
None,
None,
0,
)[1].decode("utf-8")
if config["Update"]["MirrorChyanCDK"]
else ""
)
else:
mirrorchyan_CDK = ""
else:
update_type = "stable"
proxy_list = []
thread_numb = 8
mirrorchyan_CDK = ""
else:
update_type = "stable"
proxy_list = []
thread_numb = 8
mirrorchyan_CDK = ""
# 从远程服务器获取最新版本信息
for _ in range(3):
try:
response = requests.get(
f"https://mirrorchyan.com/api/resources/AUTO_MAA/latest?user_agent=AutoMaaDownloader&current_version={version_text(current_version)}&cdk={mirrorchyan_CDK}&channel={update_type}",
timeout=10,
)
version_info: Dict[str, Union[int, str, Dict[str, str]]] = response.json()
break
except Exception as e:
err = e
time.sleep(0.1)
else:
sys.exit(f"获取版本信息时出错:\n{err}")
if version_info["code"] == 0:
if "url" in version_info["data"]:
download_config = {
"mode": "MirrorChyan",
"thread_numb": 1,
"url": version_info["data"]["url"],
}
else:
download_config = {"mode": "Proxy", "thread_numb": thread_numb}
else:
sys.exit(f"获取版本信息时出错:{version_info["msg"]}")
remote_version = list(
map(
int,
version_info["data"]["version_name"][1:].replace("-beta", "").split("."),
)
)
if download_config["mode"] == "Proxy":
for _ in range(3):
try:
response = requests.get(
"https://gitee.com/DLmaster_361/AUTO_MAA/raw/server/download_info.json",
timeout=10,
)
download_info = response.json()
download_config["proxy_list"] = list(
set(proxy_list + download_info["proxy_list"])
)
download_config["download_dict"] = download_info["download_dict"]
break
except Exception as e:
err = e
time.sleep(0.1)
else:
sys.exit(f"获取代理信息时出错:{err}")
if (app_path / "changes.json").exists():
(app_path / "changes.json").unlink()
# 启动更新线程
if version.parse(version_text(remote_version)) > version.parse(
version_text(current_version)
):
app = AUTO_MAA_Downloader(
app_path,
"AUTO_MAA",
remote_version,
download_config,
)
sys.exit(app.exec())

View File

@@ -51,7 +51,7 @@ from pathlib import Path
from typing import Union, List, Dict
from app.core import Config
from app.core import Config, SoundPlayer
from .Widget import StatefulItemCard, QuantifiedItemCard, QuickExpandGroupCard
@@ -61,25 +61,30 @@ class History(QWidget):
super().__init__(parent)
self.setObjectName("历史记录")
self.history_top_bar = self.HistoryTopBar(self)
self.history_top_bar.search_history.connect(self.reload_history)
content_widget = QWidget()
self.content_layout = QVBoxLayout(content_widget)
self.history_top_bar = self.HistoryTopBar(self)
self.history_top_bar.search_history.connect(self.reload_history)
self.content_layout.setContentsMargins(0, 0, 11, 0)
scrollArea = ScrollArea()
scrollArea.setWidgetResizable(True)
scrollArea.setContentsMargins(0, 0, 0, 0)
scrollArea.setStyleSheet("background: transparent; border: none;")
scrollArea.setWidget(content_widget)
layout = QVBoxLayout()
layout = QVBoxLayout(self)
layout.addWidget(self.history_top_bar)
layout.addWidget(scrollArea)
self.setLayout(layout)
self.history_card_list = []
def reload_history(self, mode: str, start_date: QDate, end_date: QDate) -> None:
"""加载历史记录界面"""
SoundPlayer.play("历史记录查询")
while self.content_layout.count() > 0:
item = self.content_layout.takeAt(0)
if item.spacerItem():

View File

@@ -62,14 +62,6 @@ class Home(QWidget):
self.banner = Banner()
self.banner_text = TextBrowser()
widget = QWidget()
Layout = QVBoxLayout(widget)
Layout.addWidget(self.banner)
Layout.addWidget(self.banner_text)
Layout.setStretch(0, 2)
Layout.setStretch(1, 3)
v_layout = QVBoxLayout(self.banner)
v_layout.setContentsMargins(0, 0, 0, 15)
v_layout.setSpacing(5)
@@ -146,12 +138,22 @@ class Home(QWidget):
# 将底部水平布局添加到垂直布局
v_layout.addLayout(h2_layout)
layout = QVBoxLayout()
content_widget = QWidget()
content_layout = QVBoxLayout(content_widget)
content_layout.setContentsMargins(0, 0, 0, 0)
content_layout.addWidget(self.banner)
content_layout.addWidget(self.banner_text)
content_layout.setStretch(0, 2)
content_layout.setStretch(1, 3)
scrollArea = ScrollArea()
scrollArea.setWidgetResizable(True)
scrollArea.setWidget(widget)
scrollArea.setContentsMargins(0, 0, 0, 0)
scrollArea.setStyleSheet("background: transparent; border: none;")
scrollArea.setWidget(content_widget)
layout = QVBoxLayout(self)
layout.addWidget(scrollArea)
self.setLayout(layout)
self.set_banner()
@@ -197,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
@@ -234,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"
@@ -259,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,
)

View File

@@ -28,7 +28,6 @@ v4.3
from loguru import logger
from PySide6.QtWidgets import QApplication, QSystemTrayIcon
from qfluentwidgets import (
qconfig,
Action,
SystemTrayMenu,
SplashScreen,
@@ -46,10 +45,11 @@ from datetime import datetime, timedelta
import shutil
import darkdetect
from app.core import Config, TaskManager, MainTimer, MainInfoBar
from app.core import Config, TaskManager, MainTimer, MainInfoBar, SoundPlayer
from app.services import Notify, Crypto, System
from .home import Home
from .member_manager import MemberManager
from .plan_manager import PlanManager
from .queue_manager import QueueManager
from .dispatch_center import DispatchCenter
from .history import History
@@ -81,6 +81,7 @@ class AUTO_MAA(MSFluentWindow):
# 创建主窗口
self.home = Home(self)
self.plan_manager = PlanManager(self)
self.member_manager = MemberManager(self)
self.queue_manager = QueueManager(self)
self.dispatch_center = DispatchCenter(self)
@@ -101,6 +102,13 @@ class AUTO_MAA(MSFluentWindow):
FluentIcon.ROBOT,
NavigationItemPosition.TOP,
)
self.addSubInterface(
self.plan_manager,
FluentIcon.CALENDAR,
"计划管理",
FluentIcon.CALENDAR,
NavigationItemPosition.TOP,
)
self.addSubInterface(
self.queue_manager,
FluentIcon.BOOK_SHELF,
@@ -129,23 +137,7 @@ class AUTO_MAA(MSFluentWindow):
FluentIcon.SETTING,
NavigationItemPosition.BOTTOM,
)
self.stackedWidget.currentChanged.connect(
lambda index: (
self.queue_manager.reload_member_name() if index == 2 else None
)
)
self.stackedWidget.currentChanged.connect(
lambda index: (
self.dispatch_center.pivot.setCurrentItem("主调度台")
if index == 3
else None
)
)
self.stackedWidget.currentChanged.connect(
lambda index: (
self.dispatch_center.update_top_bar() if index == 3 else None
)
)
self.stackedWidget.currentChanged.connect(self.__currentChanged)
# 创建系统托盘及其菜单
self.tray = QSystemTrayIcon(
@@ -182,7 +174,7 @@ class AUTO_MAA(MSFluentWindow):
Action(
FluentIcon.POWER_BUTTON,
"退出主程序",
triggered=lambda: (self.window().close(), QApplication.quit()),
triggered=lambda: System.set_power("KillSelf"),
)
)
@@ -193,6 +185,7 @@ class AUTO_MAA(MSFluentWindow):
self.set_min_method()
Config.user_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)
Notify.push_info_bar.connect(MainInfoBar.push_info_bar)
@@ -241,16 +234,124 @@ class AUTO_MAA(MSFluentWindow):
else:
self.setStyleSheet("background-color: #ffffff;")
def set_min_method(self) -> None:
"""设置最小化方法"""
if Config.get(Config.ui_IfToTray):
self.titleBar.minBtn.clicked.disconnect()
self.titleBar.minBtn.clicked.connect(lambda: self.show_ui("隐藏到托盘"))
else:
self.titleBar.minBtn.clicked.disconnect()
self.titleBar.minBtn.clicked.connect(self.window().showMinimized)
def on_tray_activated(self, reason):
"""双击返回主界面"""
if reason == QSystemTrayIcon.DoubleClick:
self.show_ui("显示主窗口")
def show_ui(
self, mode: str, if_quick: bool = False, if_start: bool = False
) -> None:
"""配置窗口状态"""
self.switch_theme()
if mode == "显示主窗口":
# 配置主窗口
if not self.window().isVisible():
size = list(
map(
int,
Config.get(Config.ui_size).split("x"),
)
)
location = list(
map(
int,
Config.get(Config.ui_location).split("x"),
)
)
if self.window().isMaximized():
self.window().showNormal()
self.window().setGeometry(location[0], location[1], size[0], size[1])
self.window().show()
if not if_quick:
if (
Config.get(Config.ui_maximized)
and not self.window().isMaximized()
):
self.titleBar.maxBtn.click()
SoundPlayer.play("欢迎回来")
self.show_ui("配置托盘")
elif if_start:
if Config.get(Config.ui_maximized) and not self.window().isMaximized():
self.titleBar.maxBtn.click()
self.show_ui("配置托盘")
# 如果窗口不在屏幕内,则重置窗口位置
if not any(
self.window().geometry().intersects(screen.availableGeometry())
for screen in QApplication.screens()
):
self.window().showNormal()
self.window().setGeometry(100, 100, 1200, 700)
self.window().raise_()
self.window().activateWindow()
while Config.info_bar_list:
info_bar_item = Config.info_bar_list.pop(0)
MainInfoBar.push_info_bar(
info_bar_item["mode"],
info_bar_item["title"],
info_bar_item["content"],
info_bar_item["time"],
)
elif mode == "配置托盘":
if Config.get(Config.ui_IfShowTray):
self.tray.show()
else:
self.tray.hide()
elif mode == "隐藏到托盘":
# 保存窗口相关属性
if not self.window().isMaximized():
Config.set(
Config.ui_size,
f"{self.geometry().width()}x{self.geometry().height()}",
)
Config.set(
Config.ui_location,
f"{self.geometry().x()}x{self.geometry().y()}",
)
Config.set(Config.ui_maximized, self.window().isMaximized())
Config.save()
# 隐藏主窗口
if not if_quick:
self.window().hide()
self.tray.show()
def start_up_task(self) -> None:
"""启动时任务"""
# 清理旧日志
self.clean_old_logs()
# 清理临时更新器
if (Config.app_path / "AUTO_Updater.active.exe").exists():
# 清理安装包
if (Config.app_path / "AUTO_MAA-Setup.exe").exists():
try:
(Config.app_path / "AUTO_Updater.active.exe").unlink()
(Config.app_path / "AUTO_MAA-Setup.exe").unlink()
except Exception:
pass
@@ -278,24 +379,6 @@ class AUTO_MAA(MSFluentWindow):
self.titleBar.minBtn.click()
def set_min_method(self) -> None:
"""设置最小化方法"""
if Config.get(Config.ui_IfToTray):
self.titleBar.minBtn.clicked.disconnect()
self.titleBar.minBtn.clicked.connect(lambda: self.show_ui("隐藏到托盘"))
else:
self.titleBar.minBtn.clicked.disconnect()
self.titleBar.minBtn.clicked.connect(self.window().showMinimized)
def on_tray_activated(self, reason):
"""双击返回主界面"""
if reason == QSystemTrayIcon.DoubleClick:
self.show_ui("显示主窗口")
def clean_old_logs(self):
"""
删除超过用户设定天数的日志文件(基于目录日期)
@@ -351,75 +434,16 @@ class AUTO_MAA(MSFluentWindow):
"warning", "启动主任务失败", "“调度队列_1”与“脚本_1”均不存在", -1
)
def show_ui(self, mode: str, if_quick: bool = False) -> None:
"""配置窗口状态"""
def __currentChanged(self, index: int) -> None:
"""切换界面时任务"""
self.switch_theme()
if mode == "显示主窗口":
# 配置主窗口
if not self.window().isVisible():
size = list(
map(
int,
Config.get(Config.ui_size).split("x"),
)
)
location = list(
map(
int,
Config.get(Config.ui_location).split("x"),
)
)
if self.window().isMaximized():
self.window().showNormal()
self.window().setGeometry(location[0], location[1], size[0], size[1])
self.window().show()
if not if_quick:
if Config.get(Config.ui_maximized):
self.titleBar.maxBtn.click()
self.show_ui("配置托盘")
if not any(
self.window().geometry().intersects(screen.availableGeometry())
for screen in QApplication.screens()
):
self.window().showNormal()
self.window().setGeometry(100, 100, 1200, 700)
self.window().raise_()
self.window().activateWindow()
elif mode == "配置托盘":
if Config.get(Config.ui_IfShowTray):
self.tray.show()
else:
self.tray.hide()
elif mode == "隐藏到托盘":
# 保存窗口相关属性
if not self.window().isMaximized():
Config.set(
Config.ui_size,
f"{self.geometry().width()}x{self.geometry().height()}",
)
Config.set(
Config.ui_location,
f"{self.geometry().x()}x{self.geometry().y()}",
)
Config.set(Config.ui_maximized, self.window().isMaximized())
Config.save()
# 隐藏主窗口
if not if_quick:
self.window().hide()
self.tray.show()
if index == 1:
self.member_manager.reload_plan_name()
elif index == 3:
self.queue_manager.reload_member_name()
elif index == 4:
self.dispatch_center.pivot.setCurrentItem("主调度台")
self.dispatch_center.update_top_bar()
def closeEvent(self, event: QCloseEvent):
"""清理残余进程"""

File diff suppressed because it is too large Load Diff

497
app/ui/plan_manager.py Normal file
View File

@@ -0,0 +1,497 @@
# 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.3
作者DLmaster_361
"""
from loguru import logger
from PySide6.QtWidgets import (
QWidget,
QVBoxLayout,
QStackedWidget,
QHeaderView,
)
from qfluentwidgets import (
Action,
FluentIcon,
MessageBox,
HeaderCardWidget,
CommandBar,
TableWidget,
)
from typing import List, Dict, Union
import shutil
from app.core import Config, MainInfoBar, MaaPlanConfig, SoundPlayer
from .Widget import (
ComboBoxMessageBox,
LineEditSettingCard,
ComboBoxSettingCard,
SpinBoxSetting,
EditableComboBoxSetting,
ComboBoxSetting,
PivotArea,
)
class PlanManager(QWidget):
"""计划管理父界面"""
def __init__(self, parent=None):
super().__init__(parent)
self.setObjectName("计划管理")
layout = QVBoxLayout(self)
self.tools = CommandBar()
self.plan_manager = self.PlanSettingBox(self)
# 逐个添加动作
self.tools.addActions(
[
Action(FluentIcon.ADD_TO, "新建计划表", triggered=self.add_setting_box),
Action(
FluentIcon.REMOVE_FROM, "删除计划表", triggered=self.del_setting_box
),
]
)
self.tools.addSeparator()
self.tools.addActions(
[
Action(
FluentIcon.LEFT_ARROW, "向左移动", triggered=self.left_setting_box
),
Action(
FluentIcon.RIGHT_ARROW, "向右移动", triggered=self.right_setting_box
),
]
)
self.tools.addSeparator()
layout.addWidget(self.tools)
layout.addWidget(self.plan_manager)
def add_setting_box(self):
"""添加一个计划表"""
choice = ComboBoxMessageBox(
self.window(),
"选择一个计划类型以添加相应计划表",
["选择计划类型"],
[["MAA"]],
)
if choice.exec() and choice.input[0].currentIndex() != -1:
if choice.input[0].currentText() == "MAA":
index = len(Config.plan_dict) + 1
maa_plan_config = MaaPlanConfig()
maa_plan_config.load(
Config.app_path / f"config/MaaPlanConfig/计划_{index}/config.json",
maa_plan_config,
)
maa_plan_config.save()
Config.plan_dict[f"计划_{index}"] = {
"Type": "Maa",
"Path": Config.app_path / f"config/MaaPlanConfig/计划_{index}",
"Config": maa_plan_config,
}
self.plan_manager.add_MaaPlanSettingBox(index)
self.plan_manager.switch_SettingBox(index)
logger.success(f"计划管理 计划_{index} 添加成功")
MainInfoBar.push_info_bar(
"success", "操作成功", f"添加计划表 计划_{index}", 3000
)
SoundPlayer.play("添加计划表")
def del_setting_box(self):
"""删除一个计划表"""
name = self.plan_manager.pivot.currentRouteKey()
if name is None:
logger.warning("删除计划表时未选择计划表")
MainInfoBar.push_info_bar(
"warning", "未选择计划表", "请选择一个计划表", 5000
)
return None
if len(Config.running_list) > 0:
logger.warning("删除计划表时调度队列未停止运行")
MainInfoBar.push_info_bar(
"warning", "调度中心正在执行任务", "请等待或手动中止任务", 5000
)
return None
choice = MessageBox("确认", f"确定要删除 {name} 吗?", self.window())
if choice.exec():
self.plan_manager.clear_SettingBox()
shutil.rmtree(Config.plan_dict[name]["Path"])
Config.change_plan(name, "禁用")
for i in range(int(name[3:]) + 1, len(Config.plan_dict) + 1):
if Config.plan_dict[f"计划_{i}"]["Path"].exists():
Config.plan_dict[f"计划_{i}"]["Path"].rename(
Config.plan_dict[f"计划_{i}"]["Path"].with_name(f"计划_{i-1}")
)
Config.change_plan(f"计划_{i}", f"计划_{i-1}")
self.plan_manager.show_SettingBox(max(int(name[3:]) - 1, 1))
logger.success(f"计划表 {name} 删除成功")
MainInfoBar.push_info_bar("success", "操作成功", f"删除计划表 {name}", 3000)
SoundPlayer.play("删除计划表")
def left_setting_box(self):
"""向左移动计划表"""
name = self.plan_manager.pivot.currentRouteKey()
if name is None:
logger.warning("向左移动计划表时未选择计划表")
MainInfoBar.push_info_bar(
"warning", "未选择计划表", "请选择一个计划表", 5000
)
return None
index = int(name[3:])
if index == 1:
logger.warning("向左移动计划表时已到达最左端")
MainInfoBar.push_info_bar(
"warning", "已经是第一个计划表", "无法向左移动", 5000
)
return None
if len(Config.running_list) > 0:
logger.warning("向左移动计划表时调度队列未停止运行")
MainInfoBar.push_info_bar(
"warning", "调度中心正在执行任务", "请等待或手动中止任务", 5000
)
return None
self.plan_manager.clear_SettingBox()
Config.plan_dict[name]["Path"].rename(
Config.plan_dict[name]["Path"].with_name("计划_0")
)
Config.change_plan(name, "计划_0")
Config.plan_dict[f"计划_{index-1}"]["Path"].rename(
Config.plan_dict[name]["Path"]
)
Config.change_plan(f"计划_{index-1}", name)
Config.plan_dict[name]["Path"].with_name("计划_0").rename(
Config.plan_dict[f"计划_{index-1}"]["Path"]
)
Config.change_plan("计划_0", f"计划_{index-1}")
self.plan_manager.show_SettingBox(index - 1)
logger.success(f"计划表 {name} 左移成功")
MainInfoBar.push_info_bar("success", "操作成功", f"左移计划表 {name}", 3000)
def right_setting_box(self):
"""向右移动计划表"""
name = self.plan_manager.pivot.currentRouteKey()
if name is None:
logger.warning("向右移动计划表时未选择计划表")
MainInfoBar.push_info_bar(
"warning", "未选择计划表", "请选择一个计划表", 5000
)
return None
index = int(name[3:])
if index == len(Config.plan_dict):
logger.warning("向右移动计划表时已到达最右端")
MainInfoBar.push_info_bar(
"warning", "已经是最后一个计划表", "无法向右移动", 5000
)
return None
if len(Config.running_list) > 0:
logger.warning("向右移动计划表时调度队列未停止运行")
MainInfoBar.push_info_bar(
"warning", "调度中心正在执行任务", "请等待或手动中止任务", 5000
)
return None
self.plan_manager.clear_SettingBox()
Config.plan_dict[name]["Path"].rename(
Config.plan_dict[name]["Path"].with_name("计划_0")
)
Config.change_plan(name, "计划_0")
Config.plan_dict[f"计划_{index+1}"]["Path"].rename(
Config.plan_dict[name]["Path"]
)
Config.change_plan(f"计划_{index+1}", name)
Config.plan_dict[name]["Path"].with_name("计划_0").rename(
Config.plan_dict[f"计划_{index+1}"]["Path"]
)
Config.change_plan("计划_0", f"计划_{index+1}")
self.plan_manager.show_SettingBox(index + 1)
logger.success(f"计划表 {name} 右移成功")
MainInfoBar.push_info_bar("success", "操作成功", f"右移计划表 {name}", 3000)
class PlanSettingBox(QWidget):
"""计划管理子页面组"""
def __init__(self, parent=None):
super().__init__(parent)
self.setObjectName("计划管理页面组")
self.pivotArea = PivotArea(self)
self.pivot = self.pivotArea.pivot
self.stackedWidget = QStackedWidget(self)
self.stackedWidget.setContentsMargins(0, 0, 0, 0)
self.stackedWidget.setStyleSheet("background: transparent; border: none;")
self.script_list: List[PlanManager.PlanSettingBox.MaaPlanSettingBox] = []
self.Layout = QVBoxLayout(self)
self.Layout.addWidget(self.pivotArea)
self.Layout.addWidget(self.stackedWidget)
self.Layout.setContentsMargins(0, 0, 0, 0)
self.pivot.currentItemChanged.connect(
lambda index: self.switch_SettingBox(
int(index[3:]), if_chang_pivot=False
)
)
self.show_SettingBox(1)
def show_SettingBox(self, index) -> None:
"""加载所有子界面"""
Config.search_plan()
for name, info in Config.plan_dict.items():
if info["Type"] == "Maa":
self.add_MaaPlanSettingBox(int(name[3:]))
self.switch_SettingBox(index)
def switch_SettingBox(self, index: int, if_chang_pivot: bool = True) -> None:
"""切换到指定的子界面"""
if len(Config.plan_dict) == 0:
return None
if index > len(Config.plan_dict):
return None
if if_chang_pivot:
self.pivot.setCurrentItem(self.script_list[index - 1].objectName())
self.stackedWidget.setCurrentWidget(self.script_list[index - 1])
def clear_SettingBox(self) -> None:
"""清空所有子界面"""
for sub_interface in self.script_list:
Config.gameid_refreshed.disconnect(sub_interface.refresh_gameid)
self.stackedWidget.removeWidget(sub_interface)
sub_interface.deleteLater()
self.script_list.clear()
self.pivot.clear()
def add_MaaPlanSettingBox(self, uid: int) -> None:
"""添加一个MAA设置界面"""
maa_plan_setting_box = self.MaaPlanSettingBox(uid, self)
self.script_list.append(maa_plan_setting_box)
self.stackedWidget.addWidget(self.script_list[-1])
self.pivot.addItem(routeKey=f"计划_{uid}", text=f"计划 {uid}")
class MaaPlanSettingBox(HeaderCardWidget):
"""MAA类计划设置界面"""
def __init__(self, uid: int, parent=None):
super().__init__(parent)
self.setObjectName(f"计划_{uid}")
self.setTitle("MAA计划表")
self.config = Config.plan_dict[f"计划_{uid}"]["Config"]
self.card_Name = LineEditSettingCard(
icon=FluentIcon.EDIT,
title="计划表名称",
content="用于标识计划表的名称",
text="请输入计划表名称",
qconfig=self.config,
configItem=self.config.Info_Name,
parent=self,
)
self.card_Mode = ComboBoxSettingCard(
icon=FluentIcon.DICTIONARY,
title="计划模式",
content="全局模式下计划内容固定,周计划模式下计划按周一到周日切换",
texts=["全局", "周计划"],
qconfig=self.config,
configItem=self.config.Info_Mode,
parent=self,
)
self.table = TableWidget(self)
self.table.setColumnCount(8)
self.table.setRowCount(6)
self.table.setHorizontalHeaderLabels(
["全局", "周一", "周二", "周三", "周四", "周五", "周六", "周日"]
)
self.table.setVerticalHeaderLabels(
[
"吃理智药",
"连战次数",
"关卡选择",
"备选 - 1",
"备选 - 2",
"剩余理智",
]
)
self.table.setAlternatingRowColors(False)
self.table.setEditTriggers(TableWidget.NoEditTriggers)
for col in range(8):
self.table.horizontalHeader().setSectionResizeMode(
col, QHeaderView.ResizeMode.Stretch
)
for row in range(6):
self.table.verticalHeader().setSectionResizeMode(
row, QHeaderView.ResizeMode.ResizeToContents
)
self.item_dict: Dict[
str,
Dict[
str,
Union[SpinBoxSetting, ComboBoxSetting, EditableComboBoxSetting],
],
] = {}
for col, (group, name_dict) in enumerate(
self.config.config_item_dict.items()
):
self.item_dict[group] = {}
for row, (name, configItem) in enumerate(name_dict.items()):
if name == "MedicineNumb":
self.item_dict[group][name] = SpinBoxSetting(
range=(0, 1024),
qconfig=self.config,
configItem=configItem,
parent=self,
)
elif name == "SeriesNumb":
self.item_dict[group][name] = ComboBoxSetting(
texts=["AUTO", "6", "5", "4", "3", "2", "1", "不选择"],
qconfig=self.config,
configItem=configItem,
parent=self,
)
elif name == "GameId_Remain":
self.item_dict[group][name] = EditableComboBoxSetting(
value=Config.gameid_dict[group]["value"],
texts=[
"不使用" if _ == "当前/上次" else _
for _ in Config.gameid_dict[group]["text"]
],
qconfig=self.config,
configItem=configItem,
parent=self,
)
elif "GameId" in name:
self.item_dict[group][name] = EditableComboBoxSetting(
value=Config.gameid_dict[group]["value"],
texts=Config.gameid_dict[group]["text"],
qconfig=self.config,
configItem=configItem,
parent=self,
)
self.table.setCellWidget(row, col, self.item_dict[group][name])
Layout = QVBoxLayout()
Layout.addWidget(self.card_Name)
Layout.addWidget(self.card_Mode)
Layout.addWidget(self.table)
self.viewLayout.addLayout(Layout)
self.viewLayout.setSpacing(3)
self.viewLayout.setContentsMargins(3, 0, 3, 3)
self.card_Mode.comboBox.currentIndexChanged.connect(self.switch_mode)
Config.gameid_refreshed.connect(self.refresh_gameid)
self.switch_mode()
def switch_mode(self) -> None:
"""切换计划模式"""
for group, name_dict in self.item_dict.items():
for name, setting_item in name_dict.items():
setting_item.setEnabled(
(group == "ALL")
== (self.config.get(self.config.Info_Mode) == "ALL")
)
def refresh_gameid(self):
for group, name_dict in self.item_dict.items():
for name, setting_item in name_dict.items():
if name == "GameId_Remain":
setting_item.reLoadOptions(
Config.gameid_dict[group]["value"],
[
"不使用" if _ == "当前/上次" else _
for _ in Config.gameid_dict[group]["text"]
],
)
elif "GameId" in name:
setting_item.reLoadOptions(
Config.gameid_dict[group]["value"],
Config.gameid_dict[group]["text"],
)

View File

@@ -34,17 +34,15 @@ from PySide6.QtWidgets import (
)
from qfluentwidgets import (
Action,
Pivot,
ScrollArea,
FluentIcon,
MessageBox,
HeaderCardWidget,
CommandBar,
)
from PySide6.QtCore import Qt
from typing import List
from app.core import QueueConfig, Config, MainInfoBar
from app.core import QueueConfig, Config, MainInfoBar, SoundPlayer
from .Widget import (
SwitchSettingCard,
ComboBoxSettingCard,
@@ -52,6 +50,7 @@ from .Widget import (
TimeEditSettingCard,
NoOptionComboBoxSettingCard,
HistoryCard,
PivotArea,
)
@@ -117,13 +116,14 @@ class QueueManager(QWidget):
logger.success(f"调度队列_{index} 添加成功")
MainInfoBar.push_info_bar("success", "操作成功", f"添加 调度队列_{index}", 3000)
SoundPlayer.play("添加调度队列")
def del_setting_box(self):
"""删除一个调度队列实例"""
name = self.queue_manager.pivot.currentRouteKey()
if name == None:
if name is None:
logger.warning("未选择调度队列")
MainInfoBar.push_info_bar(
"warning", "未选择调度队列", "请先选择一个调度队列", 5000
@@ -137,11 +137,7 @@ class QueueManager(QWidget):
)
return None
choice = MessageBox(
"确认",
f"确定要删除 {name} 吗?",
self.window(),
)
choice = MessageBox("确认", f"确定要删除 {name} 吗?", self.window())
if choice.exec():
self.queue_manager.clear_SettingBox()
@@ -159,13 +155,14 @@ class QueueManager(QWidget):
logger.success(f"{name} 删除成功")
MainInfoBar.push_info_bar("success", "操作成功", f"删除 {name}", 3000)
SoundPlayer.play("删除调度队列")
def left_setting_box(self):
"""向左移动调度队列实例"""
name = self.queue_manager.pivot.currentRouteKey()
if name == None:
if name is None:
logger.warning("未选择调度队列")
MainInfoBar.push_info_bar(
"warning", "未选择调度队列", "请先选择一个调度队列", 5000
@@ -210,7 +207,7 @@ class QueueManager(QWidget):
name = self.queue_manager.pivot.currentRouteKey()
if name == None:
if name is None:
logger.warning("未选择调度队列")
MainInfoBar.push_info_bar(
"warning", "未选择调度队列", "请先选择一个调度队列", 5000
@@ -305,15 +302,19 @@ class QueueManager(QWidget):
self.setObjectName("调度队列管理")
self.pivot = Pivot(self)
self.pivotArea = PivotArea()
self.pivot = self.pivotArea.pivot
self.stackedWidget = QStackedWidget(self)
self.Layout = QVBoxLayout(self)
self.stackedWidget.setContentsMargins(0, 0, 0, 0)
self.stackedWidget.setStyleSheet("background: transparent; border: none;")
self.script_list: List[
QueueManager.QueueSettingBox.QueueMemberSettingBox
] = []
self.Layout.addWidget(self.pivot, 0, Qt.AlignHCenter)
self.Layout = QVBoxLayout(self)
self.Layout.addWidget(self.pivotArea)
self.Layout.addWidget(self.stackedWidget)
self.Layout.setContentsMargins(0, 0, 0, 0)
@@ -376,14 +377,6 @@ class QueueManager(QWidget):
self.setObjectName(f"调度队列_{uid}")
self.config = Config.queue_dict[f"调度队列_{uid}"]["Config"]
layout = QVBoxLayout()
scrollArea = ScrollArea()
scrollArea.setWidgetResizable(True)
content_widget = QWidget()
content_layout = QVBoxLayout(content_widget)
self.queue_set = self.QueueSetSettingCard(self.config, self)
self.time = self.TimeSettingCard(self.config, self)
self.task = self.TaskSettingCard(self.config, self)
@@ -393,18 +386,24 @@ class QueueManager(QWidget):
parent=self,
)
content_widget = QWidget()
content_layout = QVBoxLayout(content_widget)
content_layout.setContentsMargins(0, 0, 11, 0)
content_layout.addWidget(self.queue_set)
content_layout.addWidget(self.time)
content_layout.addWidget(self.task)
content_layout.addWidget(self.history)
content_layout.addStretch(1)
scrollArea = ScrollArea()
scrollArea.setWidgetResizable(True)
scrollArea.setContentsMargins(0, 0, 0, 0)
scrollArea.setStyleSheet("background: transparent; border: none;")
scrollArea.setWidget(content_widget)
layout = QVBoxLayout(self)
layout.addWidget(scrollArea)
self.setLayout(layout)
class QueueSetSettingCard(HeaderCardWidget):
def __init__(self, config: QueueConfig, parent=None):

View File

@@ -26,7 +26,8 @@ v4.3
"""
from loguru import logger
from PySide6.QtWidgets import QApplication, QWidget, QVBoxLayout
from PySide6.QtWidgets import QWidget, QVBoxLayout
from PySide6.QtGui import QIcon
from PySide6.QtCore import Qt
from qfluentwidgets import (
ScrollArea,
@@ -48,8 +49,9 @@ from packaging import version
from pathlib import Path
from typing import Dict, Union
from app.core import Config, MainInfoBar, Network
from app.core import Config, MainInfoBar, Network, SoundPlayer
from app.services import Crypto, System, Notify
from .downloader import DownloadManager
from .Widget import (
SwitchSettingCard,
RangeSettingCard,
@@ -68,10 +70,8 @@ class Setting(QWidget):
super().__init__(parent)
self.setObjectName("设置")
content_widget = QWidget()
content_layout = QVBoxLayout(content_widget)
self.function = FunctionSettingCard(self)
self.voice = VoiceSettingCard(self)
self.start = StartSettingCard(self)
self.ui = UiSettingCard(self)
self.notification = NotifySettingCard(self)
@@ -91,7 +91,11 @@ class Setting(QWidget):
)
self.other.card_Notice.clicked.connect(lambda: self.show_notice(if_show=True))
content_widget = QWidget()
content_layout = QVBoxLayout(content_widget)
content_layout.setContentsMargins(0, 0, 11, 0)
content_layout.addWidget(self.function)
content_layout.addWidget(self.voice)
content_layout.addWidget(self.start)
content_layout.addWidget(self.ui)
content_layout.addWidget(self.notification)
@@ -101,10 +105,12 @@ class Setting(QWidget):
scrollArea = ScrollArea()
scrollArea.setWidgetResizable(True)
scrollArea.setContentsMargins(0, 0, 0, 0)
scrollArea.setStyleSheet("background: transparent; border: none;")
scrollArea.setWidget(content_widget)
layout = QVBoxLayout()
layout = QVBoxLayout(self)
layout.addWidget(scrollArea)
self.setLayout(layout)
def agree_bilibili(self) -> None:
"""授权bilibili游戏隐私政策"""
@@ -248,9 +254,7 @@ class Setting(QWidget):
choice.exec()
else:
choice = MessageBox(
"确认",
"您没有输入管理密钥,是否取消修改管理密钥?",
self.window(),
"确认", "您没有输入管理密钥,是否取消修改管理密钥?", self.window()
)
if choice.exec():
break
@@ -260,67 +264,67 @@ 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&current_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:
logger.warning(f"获取版本信息时出错:{Network.error_message}")
if network_result["response_json"]:
version_info = network_result["response_json"]
if version_info["code"] != 0:
logger.error(f"获取版本信息时出错:{version_info['msg']}")
error_remark_dict = {
1001: "获取版本信息的URL参数不正确",
7001: "填入的 CDK 已过期",
7002: "填入的 CDK 错误",
7003: "填入的 CDK 今日下载次数已达上限",
7004: "填入的 CDK 类型和待下载的资源不匹配",
7005: "填入的 CDK 已被封禁",
8001: "对应架构和系统下的资源不存在",
8002: "错误的系统参数",
8003: "错误的架构参数",
8004: "错误的更新通道参数",
1: version_info["msg"],
}
if version_info["code"] in error_remark_dict:
MainInfoBar.push_info_bar(
"error",
"获取版本信息时出错",
error_remark_dict[version_info["code"]],
-1,
)
else:
MainInfoBar.push_info_bar(
"error",
"获取版本信息时出错",
"意料之外的错误,请及时联系项目组以获取来自 Mirror 酱的技术支持",
-1,
)
return None
logger.warning(f"获取版本信息时出错:{network_result['error_message']}")
MainInfoBar.push_info_bar(
"warning",
"获取版本信息时出错",
f"网络错误:{Network.stutus_code}",
f"网络错误:{network_result['status_code']}",
5000,
)
return None
if version_info["code"] != 0:
logger.error(f"获取版本信息时出错:{version_info["msg"]}")
error_remark_dict = {
1001: "获取版本信息的URL参数不正确",
7001: "填入的 CDK 已过期",
7002: "填入的 CDK 错误",
7003: "填入的 CDK 今日下载次数已达上限",
7004: "填入的 CDK 类型和待下载的资源不匹配",
7005: "填入的 CDK 已被封禁",
8001: "对应架构和系统下的资源不存在",
8002: "错误的系统参数",
8003: "错误的架构参数",
8004: "错误的更新通道参数",
1: version_info["msg"],
}
if version_info["code"] in error_remark_dict:
MainInfoBar.push_info_bar(
"error",
"获取版本信息时出错",
error_remark_dict[version_info["code"]],
-1,
)
else:
MainInfoBar.push_info_bar(
"error",
"获取版本信息时出错",
"意料之外的错误,请及时联系项目组以获取来自 Mirror 酱的技术支持",
-1,
)
return None
remote_version = list(
map(
int,
@@ -373,45 +377,75 @@ class Setting(QWidget):
else:
all_version_info[key] = value.copy()
version_info = {
"更新总览": f"{main_version_info}\n\n{version_info_markdown(update_version_info)}",
"ALL~版本信息": version_info_markdown(all_version_info),
**{
version_text(list(map(int, k.split(".")))): version_info_markdown(v)
for k, v in version_info_json.items()
},
}
# 询问是否开始版本更新
choice = NoticeMessageBox(self.window(), "版本更新", version_info)
SoundPlayer.play("有新版本")
choice = NoticeMessageBox(
self.window(),
"版本更新",
{
"更新总览": f"{main_version_info}\n\n{version_info_markdown(update_version_info)}",
"ALL~版本信息": version_info_markdown(all_version_info),
**{
version_text(
list(map(int, k.split(".")))
): version_info_markdown(v)
for k, v in version_info_json.items()
},
},
)
if choice.exec():
with Config.version_path.open(mode="r", encoding="utf-8") as f:
version_info = json.load(f)
version_info["main_version"] = Config.VERSION
with Config.version_path.open(mode="w", encoding="utf-8") as f:
json.dump(version_info, f, ensure_ascii=False, indent=4)
if (Config.app_path / "AUTO_Updater.exe").exists():
shutil.copy(
Config.app_path / "AUTO_Updater.exe",
Config.app_path / "AUTO_Updater.active.exe",
)
if "url" in version_info["data"]:
download_config = {
"mode": "MirrorChyan",
"thread_numb": 1,
"url": version_info["data"]["url"],
}
else:
logger.error("更新器文件不存在")
MainInfoBar.push_info_bar(
"error", "更新器不存在", "请手动前往 GitHub 获取最新版本", -1
)
return None
subprocess.Popen(
[Config.app_path / "AUTO_Updater.active.exe"],
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP
| subprocess.DETACHED_PROCESS
| subprocess.CREATE_NO_WINDOW,
# 从远程服务器获取代理信息
network = Network.add_task(
mode="get",
url="https://gitee.com/DLmaster_361/AUTO_MAA/raw/server/download_info.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_result['error_message']}"
)
MainInfoBar.push_info_bar(
"warning",
"获取应用列表时出错",
f"网络错误:{network_result['status_code']}",
5000,
)
return None
download_config = {
"mode": "Proxy",
"thread_numb": Config.get(Config.update_ThreadNumb),
"proxy_list": list(
set(
Config.get(Config.update_ProxyUrlList)
+ download_info["proxy_list"]
)
),
"download_dict": download_info["download_dict"],
}
self.downloader = DownloadManager(
Config.app_path, "AUTO_MAA", remote_version, download_config
)
self.window().close()
QApplication.quit()
self.downloader.setWindowTitle("AUTO_MAA更新器")
self.downloader.setWindowIcon(
QIcon(str(Config.app_path / "resources/icons/AUTO_MAA_Updater.ico"))
)
self.downloader.download_accomplish.connect(self.start_setup)
self.downloader.show()
self.downloader.run()
elif (
if_show
@@ -428,28 +462,48 @@ class Setting(QWidget):
"发现新版本",
f"{version_text(current_version)} --> {version_text(remote_version)}",
3600000,
if_force=True,
)
SoundPlayer.play("有新版本")
else:
MainInfoBar.push_info_bar("success", "更新检查", "已是最新版本~", 3000)
SoundPlayer.play("无新版本")
def start_setup(self) -> None:
subprocess.Popen(
[
Config.app_path / "AUTO_MAA-Setup.exe",
"/SP-",
"/SILENT",
"/NOCANCEL",
"/FORCECLOSEAPPLICATIONS",
"/LANG=Chinese",
f"/DIR={Config.app_path}",
],
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP
| subprocess.DETACHED_PROCESS
| subprocess.CREATE_NO_WINDOW,
)
System.set_power("KillSelf")
def show_notice(self, if_show: bool = False, if_first: bool = False) -> None:
"""显示公告"""
# 从远程服务器获取最新公告
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
@@ -481,6 +535,7 @@ class Setting(QWidget):
choice = NoticeMessageBox(self.window(), "公告", notice["notice_dict"])
choice.button_cancel.hide()
choice.button_layout.insertStretch(0, 1)
SoundPlayer.play("公告展示")
if choice.exec():
with (Config.app_path / "resources/notice.json").open(
mode="w", encoding="utf-8"
@@ -494,8 +549,9 @@ class Setting(QWidget):
):
MainInfoBar.push_info_bar(
"info", "有新公告", "请前往设置界面查看公告", 3600000
"info", "有新公告", "请前往设置界面查看公告", 3600000, if_force=True
)
SoundPlayer.play("公告通知")
return None
@@ -604,6 +660,36 @@ class FunctionSettingCard(HeaderCardWidget):
self.addGroupWidget(widget)
class VoiceSettingCard(HeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setTitle("音效")
self.card_Enabled = SwitchSettingCard(
icon=FluentIcon.PAGE_RIGHT,
title="音效开关",
content="是否启用音效",
qconfig=Config,
configItem=Config.voice_Enabled,
parent=self,
)
self.card_Type = ComboBoxSettingCard(
icon=FluentIcon.PAGE_RIGHT,
title="音效模式",
content="选择音效的播放模式",
texts=["简洁", "聒噪"],
qconfig=Config,
configItem=Config.voice_Type,
parent=self,
)
Layout = QVBoxLayout()
Layout.addWidget(self.card_Enabled)
Layout.addWidget(self.card_Type)
self.viewLayout.addLayout(Layout)
class StartSettingCard(HeaderCardWidget):
def __init__(self, parent=None):

88
app/utils/AUTO_MAA.iss Normal file
View File

@@ -0,0 +1,88 @@
; Script generated by the Inno Setup Script Wizard.
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
#define MyAppName "AUTO_MAA"
#define MyAppVersion ""
#define MyAppPublisher "AUTO_MAA Team"
#define MyAppURL "https://doc.automaa.xyz/"
#define MyAppExeName "AUTO_MAA.exe"
#define MyAppPath ""
#define OutputDir ""
[Setup]
; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications.
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
AppId={{D116A92A-E174-4699-B777-61C5FD837B19}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
AppVerName={#MyAppName}
AppPublisher={#MyAppPublisher}
AppPublisherURL={#MyAppURL}
AppSupportURL={#MyAppURL}
AppUpdatesURL={#MyAppURL}
DefaultDirName=D:\{#MyAppName}
UninstallDisplayIcon={app}\{#MyAppExeName}
; "ArchitecturesAllowed=x64compatible" specifies that Setup cannot run
; on anything but x64 and Windows 11 on Arm.
ArchitecturesAllowed=x64compatible
; "ArchitecturesInstallIn64BitMode=x64compatible" requests that the
; install be done in "64-bit mode" on x64 or Windows 11 on Arm,
; meaning it should use the native 64-bit Program Files directory and
; the 64-bit view of the registry.
ArchitecturesInstallIn64BitMode=x64compatible
DisableProgramGroupPage=yes
LicenseFile={#MyAppPath}\LICENSE
; Remove the following line to run in administrative install mode (install for all users).
PrivilegesRequired=lowest
OutputDir={#OutputDir}
OutputBaseFilename=AUTO_MAA-Setup
SetupIconFile={#MyAppPath}\resources\icons\AUTO_MAA.ico
SolidCompression=yes
WizardStyle=modern
[Languages]
Name: "Chinese"; MessagesFile: "{#MyAppPath}\resources\docs\ChineseSimplified.isl"
Name: "English"; MessagesFile: "compiler:Default.isl"
[Tasks]
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
[Files]
Source: "{#MyAppPath}\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
Source: "{#MyAppPath}\app\*"; DestDir: "{app}\app"; Flags: ignoreversion recursesubdirs createallsubdirs
Source: "{#MyAppPath}\resources\*"; DestDir: "{app}\resources"; Flags: ignoreversion recursesubdirs createallsubdirs
Source: "{#MyAppPath}\main.py"; DestDir: "{app}"; Flags: ignoreversion
Source: "{#MyAppPath}\requirements.txt"; DestDir: "{app}"; Flags: ignoreversion
Source: "{#MyAppPath}\README.md"; DestDir: "{app}"; Flags: ignoreversion
Source: "{#MyAppPath}\LICENSE"; DestDir: "{app}"; Flags: ignoreversion
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
[Icons]
Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
[Run]
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall
[Code]
var
DeleteDataQuestion: Boolean;
function InitializeUninstall: Boolean;
begin
DeleteDataQuestion := MsgBox('您确认要完全移除 AUTO_MAA 的所有用户数据文件与子组件吗?', mbConfirmation, MB_YESNO) = IDYES;
Result := True;
end;
procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
begin
if CurUninstallStep = usPostUninstall then
begin
DelTree(ExpandConstant('{app}\app'), True, True, True);
DelTree(ExpandConstant('{app}\resources'), True, True, True);
if DeleteDataQuestion then
begin
DelTree(ExpandConstant('{app}'), True, True, True);
end;
end;
end;

View File

@@ -29,6 +29,4 @@ __version__ = "4.2.0"
__author__ = "DLmaster361 <DLmaster_361@163.com>"
__license__ = "GPL-3.0 license"
from .downloader import DownloadManager
__all__ = ["DownloadManager"]
__all__ = []

View File

@@ -66,7 +66,6 @@ if __name__ == "__main__":
version = json.load(f)
main_version_numb = list(map(int, version["main_version"].split(".")))
updater_version_numb = list(map(int, version["updater_version"].split(".")))
print("Packaging AUTO_MAA main program ...")
@@ -86,35 +85,10 @@ if __name__ == "__main__":
print("AUTO_MAA main program packaging completed !")
print("Packaging AUTO_MAA update program ...")
shutil.copy(root_path / "app/utils/downloader.py", root_path)
os.system(
"powershell -Command python -m nuitka --standalone --onefile --mingw64"
" --enable-plugins=pyside6 --windows-console-mode=disable"
" --onefile-tempdir-spec='{TEMP}\\AUTO_MAA_Updater'"
" --windows-icon-from-ico=resources\\icons\\AUTO_MAA_Updater.ico"
" --company-name='AUTO_MAA Team' --product-name=AUTO_MAA"
f" --file-version={version["updater_version"]}"
f" --product-version={version["main_version"]}"
" --file-description='AUTO_MAA Component'"
" --copyright='Copyright © 2024-2025 DLmaster361'"
" --assume-yes-for-downloads --output-filename=AUTO_Updater"
" --remove-output downloader.py"
)
(root_path / "downloader.py").unlink()
print("AUTO_MAA update program packaging completed !")
print("start to create setup program ...")
(root_path / "AUTO_MAA").mkdir(parents=True, exist_ok=True)
print("Start to move AUTO_MAA program ...")
shutil.move(root_path / "AUTO_MAA.exe", root_path / "AUTO_MAA/")
shutil.move(root_path / "AUTO_Updater.exe", root_path / "AUTO_MAA/")
print("Start to copy rescourses ...")
shutil.copytree(root_path / "app", root_path / "AUTO_MAA/app")
shutil.copytree(root_path / "resources", root_path / "AUTO_MAA/resources")
shutil.copy(root_path / "main.py", root_path / "AUTO_MAA/")
@@ -122,17 +96,38 @@ if __name__ == "__main__":
shutil.copy(root_path / "README.md", root_path / "AUTO_MAA/")
shutil.copy(root_path / "LICENSE", root_path / "AUTO_MAA/")
print("Start to compress ...")
with (root_path / "app/utils/AUTO_MAA.iss").open(mode="r", encoding="utf-8") as f:
iss = f.read()
iss = (
iss.replace(
'#define MyAppVersion ""',
f'#define MyAppVersion "{version["main_version"]}"',
)
.replace(
'#define MyAppPath ""', f'#define MyAppPath "{root_path / "AUTO_MAA"}"'
)
.replace('#define OutputDir ""', f'#define OutputDir "{root_path}"')
)
with (root_path / "AUTO_MAA.iss").open(mode="w", encoding="utf-8") as f:
f.write(iss)
os.system(f'ISCC "{root_path / "AUTO_MAA.iss"}"')
(root_path / "AUTO_MAA_Setup").mkdir(parents=True, exist_ok=True)
shutil.move(root_path / "AUTO_MAA-Setup.exe", root_path / "AUTO_MAA_Setup")
shutil.make_archive(
base_name=root_path / f"AUTO_MAA_{version_text(main_version_numb)}",
format="zip",
root_dir=root_path / "AUTO_MAA",
root_dir=root_path / "AUTO_MAA_Setup",
base_dir=".",
)
shutil.rmtree(root_path / "AUTO_MAA")
print("compress completed !")
print("setup program created !")
(root_path / "AUTO_MAA.iss").unlink(missing_ok=True)
shutil.rmtree(root_path / "AUTO_MAA")
shutil.rmtree(root_path / "AUTO_MAA_Setup")
all_version_info = {}
for v_i in version["version_info"].values():
@@ -143,6 +138,6 @@ if __name__ == "__main__":
all_version_info[key] = value.copy()
(root_path / "version_info.txt").write_text(
f"{version_text(main_version_numb)}\n{version_text(updater_version_numb)}\n<!--{json.dumps(version["version_info"], ensure_ascii=False)}-->\n{version_info_markdown(all_version_info)}",
f"{version_text(main_version_numb)}\n\n<!--{json.dumps(version["version_info"], ensure_ascii=False)}-->\n{version_info_markdown(all_version_info)}",
encoding="utf-8",
)

View File

@@ -42,7 +42,7 @@ def main():
from app.ui.main_window import AUTO_MAA
window = AUTO_MAA()
window.show_ui("显示主窗口")
window.show_ui("显示主窗口", if_start=True)
window.start_up_task()
sys.exit(application.exec())

View File

@@ -10,5 +10,4 @@ pycryptodome
requests
markdown
Jinja2
serverchan_sdk
nuitka

View File

@@ -0,0 +1,403 @@
; *** Inno Setup version 6.4.0+ Chinese Simplified messages ***
;
; To download user-contributed translations of this file, go to:
; https://jrsoftware.org/files/istrans/
;
; Note: When translating this text, do not add periods (.) to the end of
; messages that didn't have them already, because on those messages Inno
; Setup adds the periods automatically (appending a period would result in
; two periods being displayed).
;
; Maintained by Zhenghan Yang
; Email: 847320916@QQ.com
; Translation based on network resource
; The latest Translation is on https://github.com/kira-96/Inno-Setup-Chinese-Simplified-Translation
;
[LangOptions]
; The following three entries are very important. Be sure to read and
; understand the '[LangOptions] section' topic in the help file.
LanguageName=简体中文
; If Language Name display incorrect, uncomment next line
; LanguageName=<7B80><4F53><4E2D><6587>
; About LanguageID, to reference link:
; https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-lcid/a9eac961-e77d-41a6-90a5-ce1a8b0cdb9c
LanguageID=$0804
; About CodePage, to reference link:
; https://docs.microsoft.com/en-us/windows/win32/intl/code-page-identifiers
LanguageCodePage=936
; If the language you are translating to requires special font faces or
; sizes, uncomment any of the following entries and change them accordingly.
;DialogFontName=
;DialogFontSize=8
;WelcomeFontName=Verdana
;WelcomeFontSize=12
;TitleFontName=Arial
;TitleFontSize=29
;CopyrightFontName=Arial
;CopyrightFontSize=8
[Messages]
; *** 应用程序标题
SetupAppTitle=安装
SetupWindowTitle=安装 - %1
UninstallAppTitle=卸载
UninstallAppFullTitle=%1 卸载
; *** Misc. common
InformationTitle=信息
ConfirmTitle=确认
ErrorTitle=错误
; *** SetupLdr messages
SetupLdrStartupMessage=现在将安装 %1。您想要继续吗
LdrCannotCreateTemp=无法创建临时文件。安装程序已中止
LdrCannotExecTemp=无法执行临时目录中的文件。安装程序已中止
HelpTextNote=
; *** 启动错误消息
LastErrorMessage=%1。%n%n错误 %2: %3
SetupFileMissing=安装目录中缺少文件 %1。请修正这个问题或者获取程序的新副本。
SetupFileCorrupt=安装文件已损坏。请获取程序的新副本。
SetupFileCorruptOrWrongVer=安装文件已损坏,或是与这个安装程序的版本不兼容。请修正这个问题或获取新的程序副本。
InvalidParameter=无效的命令行参数:%n%n%1
SetupAlreadyRunning=安装程序正在运行。
WindowsVersionNotSupported=此程序不支持当前计算机运行的 Windows 版本。
WindowsServicePackRequired=此程序需要 %1 服务包 %2 或更高版本。
NotOnThisPlatform=此程序不能在 %1 上运行。
OnlyOnThisPlatform=此程序只能在 %1 上运行。
OnlyOnTheseArchitectures=此程序只能安装到为下列处理器架构设计的 Windows 版本中:%n%n%1
WinVersionTooLowError=此程序需要 %1 版本 %2 或更高。
WinVersionTooHighError=此程序不能安装于 %1 版本 %2 或更高。
AdminPrivilegesRequired=在安装此程序时您必须以管理员身份登录。
PowerUserPrivilegesRequired=在安装此程序时您必须以管理员身份或有权限的用户组身份登录。
SetupAppRunningError=安装程序发现 %1 当前正在运行。%n%n请先关闭正在运行的程序然后点击“确定”继续或点击“取消”退出。
UninstallAppRunningError=卸载程序发现 %1 当前正在运行。%n%n请先关闭正在运行的程序然后点击“确定”继续或点击“取消”退出。
; *** 启动问题
PrivilegesRequiredOverrideTitle=选择安装程序模式
PrivilegesRequiredOverrideInstruction=选择安装模式
PrivilegesRequiredOverrideText1=%1 可以为所有用户安装(需要管理员权限),或仅为您安装。
PrivilegesRequiredOverrideText2=%1 只能为您安装,或为所有用户安装(需要管理员权限)。
PrivilegesRequiredOverrideAllUsers=为所有用户安装(&A)
PrivilegesRequiredOverrideAllUsersRecommended=为所有用户安装(&A) (建议选项)
PrivilegesRequiredOverrideCurrentUser=只为我安装(&M)
PrivilegesRequiredOverrideCurrentUserRecommended=只为我安装(&M) (建议选项)
; *** 其他错误
ErrorCreatingDir=安装程序无法创建目录“%1”
ErrorTooManyFilesInDir=无法在目录“%1”中创建文件因为里面包含太多文件
; *** 安装程序公共消息
ExitSetupTitle=退出安装程序
ExitSetupMessage=安装程序尚未完成。如果现在退出,将不会安装该程序。%n%n您之后可以再次运行安装程序完成安装。%n%n现在退出安装程序吗
AboutSetupMenuItem=关于安装程序(&A)...
AboutSetupTitle=关于安装程序
AboutSetupMessage=%1 版本 %2%n%3%n%n%1 主页:%n%4
AboutSetupNote=
TranslatorNote=简体中文翻译由Kira(847320916@qq.com)维护。项目地址https://github.com/kira-96/Inno-Setup-Chinese-Simplified-Translation
; *** 按钮
ButtonBack=< 上一步(&B)
ButtonNext=下一步(&N) >
ButtonInstall=安装(&I)
ButtonOK=确定
ButtonCancel=取消
ButtonYes=是(&Y)
ButtonYesToAll=全是(&A)
ButtonNo=否(&N)
ButtonNoToAll=全否(&O)
ButtonFinish=完成(&F)
ButtonBrowse=浏览(&B)...
ButtonWizardBrowse=浏览(&R)...
ButtonNewFolder=新建文件夹(&M)
; *** “选择语言”对话框消息
SelectLanguageTitle=选择安装语言
SelectLanguageLabel=选择安装时使用的语言。
; *** 公共向导文字
ClickNext=点击“下一步”继续,或点击“取消”退出安装程序。
BeveledLabel=
BrowseDialogTitle=浏览文件夹
BrowseDialogLabel=在下面的列表中选择一个文件夹,然后点击“确定”。
NewFolderName=新建文件夹
; *** “欢迎”向导页
WelcomeLabel1=欢迎使用 [name] 安装向导
WelcomeLabel2=现在将安装 [name/ver] 到您的电脑中。%n%n建议您在继续安装前关闭所有其他应用程序。
; *** “密码”向导页
WizardPassword=密码
PasswordLabel1=这个安装程序有密码保护。
PasswordLabel3=请输入密码,然后点击“下一步”继续。密码区分大小写。
PasswordEditLabel=密码(&P)
IncorrectPassword=您输入的密码不正确,请重新输入。
; *** “许可协议”向导页
WizardLicense=许可协议
LicenseLabel=请在继续安装前阅读以下重要信息。
LicenseLabel3=请仔细阅读下列许可协议。在继续安装前您必须同意这些协议条款。
LicenseAccepted=我同意此协议(&A)
LicenseNotAccepted=我不同意此协议(&D)
; *** “信息”向导页
WizardInfoBefore=信息
InfoBeforeLabel=请在继续安装前阅读以下重要信息。
InfoBeforeClickLabel=准备好继续安装后,点击“下一步”。
WizardInfoAfter=信息
InfoAfterLabel=请在继续安装前阅读以下重要信息。
InfoAfterClickLabel=准备好继续安装后,点击“下一步”。
; *** “用户信息”向导页
WizardUserInfo=用户信息
UserInfoDesc=请输入您的信息。
UserInfoName=用户名(&U)
UserInfoOrg=组织(&O)
UserInfoSerial=序列号(&S)
UserInfoNameRequired=您必须输入用户名。
; *** “选择目标目录”向导页
WizardSelectDir=选择目标位置
SelectDirDesc=您想将 [name] 安装在哪里?
SelectDirLabel3=安装程序将安装 [name] 到下面的文件夹中。
SelectDirBrowseLabel=点击“下一步”继续。如果您想选择其他文件夹,点击“浏览”。
DiskSpaceGBLabel=至少需要有 [gb] GB 的可用磁盘空间。
DiskSpaceMBLabel=至少需要有 [mb] MB 的可用磁盘空间。
CannotInstallToNetworkDrive=安装程序无法安装到一个网络驱动器。
CannotInstallToUNCPath=安装程序无法安装到一个 UNC 路径。
InvalidPath=您必须输入一个带驱动器卷标的完整路径,例如:%n%nC:\APP%n%n或UNC路径%n%n\\server\share
InvalidDrive=您选定的驱动器或 UNC 共享不存在或不能访问。请选择其他位置。
DiskSpaceWarningTitle=磁盘空间不足
DiskSpaceWarning=安装程序至少需要 %1 KB 的可用空间才能安装,但选定驱动器只有 %2 KB 的可用空间。%n%n您一定要继续吗
DirNameTooLong=文件夹名称或路径太长。
InvalidDirName=文件夹名称无效。
BadDirName32=文件夹名称不能包含下列任何字符:%n%n%1
DirExistsTitle=文件夹已存在
DirExists=文件夹:%n%n%1%n%n已经存在。您一定要安装到这个文件夹中吗
DirDoesntExistTitle=文件夹不存在
DirDoesntExist=文件夹:%n%n%1%n%n不存在。您想要创建此文件夹吗
; *** “选择组件”向导页
WizardSelectComponents=选择组件
SelectComponentsDesc=您想安装哪些程序组件?
SelectComponentsLabel2=选中您想安装的组件;取消您不想安装的组件。然后点击“下一步”继续。
FullInstallation=完全安装
; if possible don't translate 'Compact' as 'Minimal' (I mean 'Minimal' in your language)
CompactInstallation=简洁安装
CustomInstallation=自定义安装
NoUninstallWarningTitle=组件已存在
NoUninstallWarning=安装程序检测到下列组件已安装在您的电脑中:%n%n%1%n%n取消选中这些组件不会卸载它们。%n%n确定要继续吗
ComponentSize1=%1 KB
ComponentSize2=%1 MB
ComponentsDiskSpaceGBLabel=当前选择的组件需要至少 [gb] GB 的磁盘空间。
ComponentsDiskSpaceMBLabel=当前选择的组件需要至少 [mb] MB 的磁盘空间。
; *** “选择附加任务”向导页
WizardSelectTasks=选择附加任务
SelectTasksDesc=您想要安装程序执行哪些附加任务?
SelectTasksLabel2=选择您想要安装程序在安装 [name] 时执行的附加任务,然后点击“下一步”。
; *** “选择开始菜单文件夹”向导页
WizardSelectProgramGroup=选择开始菜单文件夹
SelectStartMenuFolderDesc=安装程序应该在哪里放置程序的快捷方式?
SelectStartMenuFolderLabel3=安装程序将在下列“开始”菜单文件夹中创建程序的快捷方式。
SelectStartMenuFolderBrowseLabel=点击“下一步”继续。如果您想选择其他文件夹,点击“浏览”。
MustEnterGroupName=您必须输入一个文件夹名。
GroupNameTooLong=文件夹名或路径太长。
InvalidGroupName=无效的文件夹名字。
BadGroupName=文件夹名不能包含下列任何字符:%n%n%1
NoProgramGroupCheck2=不创建开始菜单文件夹(&D)
; *** “准备安装”向导页
WizardReady=准备安装
ReadyLabel1=安装程序准备就绪,现在可以开始安装 [name] 到您的电脑。
ReadyLabel2a=点击“安装”继续此安装程序。如果您想重新考虑或修改任何设置,点击“上一步”。
ReadyLabel2b=点击“安装”继续此安装程序。
ReadyMemoUserInfo=用户信息:
ReadyMemoDir=目标位置:
ReadyMemoType=安装类型:
ReadyMemoComponents=已选择组件:
ReadyMemoGroup=开始菜单文件夹:
ReadyMemoTasks=附加任务:
; *** TExtractionWizardPage wizard page and Extract7ZipArchive
ExtractionLabel=正在提取附加文件...
ButtonStopExtraction=停止提取(&S)
StopExtraction=您确定要停止提取吗?
ErrorExtractionAborted=提取已中止
ErrorExtractionFailed=提取失败:%1
; *** TDownloadWizardPage wizard page and DownloadTemporaryFile
DownloadingLabel=正在下载附加文件...
ButtonStopDownload=停止下载(&S)
StopDownload=您确定要停止下载吗?
ErrorDownloadAborted=下载已中止
ErrorDownloadFailed=下载失败:%1 %2
ErrorDownloadSizeFailed=获取下载大小失败:%1 %2
ErrorFileHash1=校验文件哈希失败:%1
ErrorFileHash2=无效的文件哈希:预期 %1实际 %2
ErrorProgress=无效的进度:%1 / %2
ErrorFileSize=文件大小错误:预期 %1实际 %2
; *** “正在准备安装”向导页
WizardPreparing=正在准备安装
PreparingDesc=安装程序正在准备安装 [name] 到您的电脑。
PreviousInstallNotCompleted=先前的程序安装或卸载未完成,您需要重启您的电脑以完成。%n%n在重启电脑后再次运行安装程序以完成 [name] 的安装。
CannotContinue=安装程序不能继续。请点击“取消”退出。
ApplicationsFound=以下应用程序正在使用将由安装程序更新的文件。建议您允许安装程序自动关闭这些应用程序。
ApplicationsFound2=以下应用程序正在使用将由安装程序更新的文件。建议您允许安装程序自动关闭这些应用程序。安装完成后,安装程序将尝试重新启动这些应用程序。
CloseApplications=自动关闭应用程序(&A)
DontCloseApplications=不要关闭应用程序(&D)
ErrorCloseApplications=安装程序无法自动关闭所有应用程序。建议您在继续之前,关闭所有在使用需要由安装程序更新的文件的应用程序。
PrepareToInstallNeedsRestart=安装程序必须重启您的计算机。计算机重启后,请再次运行安装程序以完成 [name] 的安装。%n%n是否立即重新启动
; *** “正在安装”向导页
WizardInstalling=正在安装
InstallingLabel=安装程序正在安装 [name] 到您的电脑,请稍候。
; *** “安装完成”向导页
FinishedHeadingLabel=[name] 安装完成
FinishedLabelNoIcons=安装程序已在您的电脑中安装了 [name]。
FinishedLabel=安装程序已在您的电脑中安装了 [name]。您可以通过已安装的快捷方式运行此应用程序。
ClickFinish=点击“完成”退出安装程序。
FinishedRestartLabel=为完成 [name] 的安装,安装程序必须重新启动您的电脑。要立即重启吗?
FinishedRestartMessage=为完成 [name] 的安装,安装程序必须重新启动您的电脑。%n%n要立即重启吗
ShowReadmeCheck=是,我想查阅自述文件
YesRadio=是,立即重启电脑(&Y)
NoRadio=否,稍后重启电脑(&N)
; used for example as 'Run MyProg.exe'
RunEntryExec=运行 %1
; used for example as 'View Readme.txt'
RunEntryShellExec=查阅 %1
; *** “安装程序需要下一张磁盘”提示
ChangeDiskTitle=安装程序需要下一张磁盘
SelectDiskLabel2=请插入磁盘 %1 并点击“确定”。%n%n如果这个磁盘中的文件可以在下列文件夹之外的文件夹中找到请输入正确的路径或点击“浏览”。
PathLabel=路径(&P)
FileNotInDir2=“%2”中找不到文件“%1”。请插入正确的磁盘或选择其他文件夹。
SelectDirectoryLabel=请指定下一张磁盘的位置。
; *** 安装状态消息
SetupAborted=安装程序未完成安装。%n%n请修正这个问题并重新运行安装程序。
AbortRetryIgnoreSelectAction=选择操作
AbortRetryIgnoreRetry=重试(&T)
AbortRetryIgnoreIgnore=忽略错误并继续(&I)
AbortRetryIgnoreCancel=关闭安装程序
; *** 安装状态消息
StatusClosingApplications=正在关闭应用程序...
StatusCreateDirs=正在创建目录...
StatusExtractFiles=正在解压缩文件...
StatusCreateIcons=正在创建快捷方式...
StatusCreateIniEntries=正在创建 INI 条目...
StatusCreateRegistryEntries=正在创建注册表条目...
StatusRegisterFiles=正在注册文件...
StatusSavingUninstall=正在保存卸载信息...
StatusRunProgram=正在完成安装...
StatusRestartingApplications=正在重启应用程序...
StatusRollback=正在撤销更改...
; *** 其他错误
ErrorInternal2=内部错误:%1
ErrorFunctionFailedNoCode=%1 失败
ErrorFunctionFailed=%1 失败;错误代码 %2
ErrorFunctionFailedWithMessage=%1 失败;错误代码 %2.%n%3
ErrorExecutingProgram=无法执行文件:%n%1
; *** 注册表错误
ErrorRegOpenKey=打开注册表项时出错:%n%1\%2
ErrorRegCreateKey=创建注册表项时出错:%n%1\%2
ErrorRegWriteKey=写入注册表项时出错:%n%1\%2
; *** INI 错误
ErrorIniEntry=在文件“%1”中创建 INI 条目时出错。
; *** 文件复制错误
FileAbortRetryIgnoreSkipNotRecommended=跳过此文件(&S) (不推荐)
FileAbortRetryIgnoreIgnoreNotRecommended=忽略错误并继续(&I) (不推荐)
SourceIsCorrupted=源文件已损坏
SourceDoesntExist=源文件“%1”不存在
ExistingFileReadOnly2=无法替换现有文件,它是只读的。
ExistingFileReadOnlyRetry=移除只读属性并重试(&R)
ExistingFileReadOnlyKeepExisting=保留现有文件(&K)
ErrorReadingExistingDest=尝试读取现有文件时出错:
FileExistsSelectAction=选择操作
FileExists2=文件已经存在。
FileExistsOverwriteExisting=覆盖已存在的文件(&O)
FileExistsKeepExisting=保留现有的文件(&K)
FileExistsOverwriteOrKeepAll=为所有冲突文件执行此操作(&D)
ExistingFileNewerSelectAction=选择操作
ExistingFileNewer2=现有的文件比安装程序将要安装的文件还要新。
ExistingFileNewerOverwriteExisting=覆盖已存在的文件(&O)
ExistingFileNewerKeepExisting=保留现有的文件(&K) (推荐)
ExistingFileNewerOverwriteOrKeepAll=为所有冲突文件执行此操作(&D)
ErrorChangingAttr=尝试更改下列现有文件的属性时出错:
ErrorCreatingTemp=尝试在目标目录创建文件时出错:
ErrorReadingSource=尝试读取下列源文件时出错:
ErrorCopying=尝试复制下列文件时出错:
ErrorReplacingExistingFile=尝试替换现有文件时出错:
ErrorRestartReplace=重启并替换失败:
ErrorRenamingTemp=尝试重命名下列目标目录中的一个文件时出错:
ErrorRegisterServer=无法注册 DLL/OCX%1
ErrorRegSvr32Failed=RegSvr32 失败;退出代码 %1
ErrorRegisterTypeLib=无法注册类库:%1
; *** 卸载显示名字标记
; used for example as 'My Program (32-bit)'
UninstallDisplayNameMark=%1 (%2)
; used for example as 'My Program (32-bit, All users)'
UninstallDisplayNameMarks=%1 (%2, %3)
UninstallDisplayNameMark32Bit=32 位
UninstallDisplayNameMark64Bit=64 位
UninstallDisplayNameMarkAllUsers=所有用户
UninstallDisplayNameMarkCurrentUser=当前用户
; *** 安装后错误
ErrorOpeningReadme=尝试打开自述文件时出错。
ErrorRestartingComputer=安装程序无法重启电脑,请手动重启。
; *** 卸载消息
UninstallNotFound=文件“%1”不存在。无法卸载。
UninstallOpenError=文件“%1”不能被打开。无法卸载。
UninstallUnsupportedVer=此版本的卸载程序无法识别卸载日志文件“%1”的格式。无法卸载
UninstallUnknownEntry=卸载日志中遇到一个未知条目 (%1)
ConfirmUninstall=您确认要完全移除 %1 及其所有组件吗?
UninstallOnlyOnWin64=仅允许在 64 位 Windows 中卸载此程序。
OnlyAdminCanUninstall=仅使用管理员权限的用户能完成此卸载。
UninstallStatusLabel=正在从您的电脑中移除 %1请稍候。
UninstalledAll=已顺利从您的电脑中移除 %1。
UninstalledMost=%1 卸载完成。%n%n有部分内容未能被删除但您可以手动删除它们。
UninstalledAndNeedsRestart=为完成 %1 的卸载,需要重启您的电脑。%n%n立即重启电脑吗
UninstallDataCorrupted=文件“%1”已损坏。无法卸载
; *** 卸载状态消息
ConfirmDeleteSharedFileTitle=删除共享的文件吗?
ConfirmDeleteSharedFile2=系统表示下列共享的文件已不有其他程序使用。您希望卸载程序删除这些共享的文件吗?%n%n如果删除这些文件但仍有程序在使用这些文件则这些程序可能出现异常。如果您不能确定请选择“否”在系统中保留这些文件以免引发问题。
SharedFileNameLabel=文件名:
SharedFileLocationLabel=位置:
WizardUninstalling=卸载状态
StatusUninstalling=正在卸载 %1...
; *** Shutdown block reasons
ShutdownBlockReasonInstallingApp=正在安装 %1。
ShutdownBlockReasonUninstallingApp=正在卸载 %1。
; The custom messages below aren't used by Setup itself, but if you make
; use of them in your scripts, you'll want to translate them.
[CustomMessages]
NameAndVersion=%1 版本 %2
AdditionalIcons=附加快捷方式:
CreateDesktopIcon=创建桌面快捷方式(&D)
CreateQuickLaunchIcon=创建快速启动栏快捷方式(&Q)
ProgramOnTheWeb=%1 网站
UninstallProgram=卸载 %1
LaunchProgram=运行 %1
AssocFileExtension=将 %2 文件扩展名与 %1 建立关联(&A)
AssocingFileExtension=正在将 %2 文件扩展名与 %1 建立关联...
AutoStartProgramGroupDescription=启动:
AutoStartProgram=自动启动 %1
AddonHostProgramNotFound=您选择的文件夹中无法找到 %1。%n%n您要继续吗

View File

@@ -58,4 +58,5 @@ G"GUI.UseTray": "True" #显示托盘图标
G"GUI.MinimizeToTray": "False" #最小化时隐藏至托盘
"Start.EmulatorPath" #模拟器路径
"Start.EmulatorAddCommand": "-v 2" #附加命令
"Start.EmulatorWaitSeconds": "10" #等待模拟器启动时间
G"VersionUpdate.package": "MirrorChyanAppv5.15.6.zip" #更新包标识

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,61 +1,27 @@
{
"main_version": "4.3.6.2",
"updater_version": "1.0.0.0",
"announcement": "\n## 新增功能\n- 屏蔽MuMu模拟器开屏广告功能上线\n- 更新器支持多线程下载\n- 添加强制关闭ADB与模拟器等增强任务项\n## 修复BUG\n- 修复统计信息HTML模板公招匹配错误\n- 修复密码显示按钮动画异常\n- 修复`检测到MAA未能实际执行任务`报错被异常屏蔽\n- 修复MAA超时判定异常失效\n## 程序优化\n- 关机等电源操作添加100s倒计时\n- 人工排查弹窗方法优化\n- 人工排查时自动屏蔽静默操作\n- 公告样式优化",
"main_version": "4.3.9.0",
"version_info": {
"4.3.6.2": {
"4.3.9.0": {
"修复bug": [
"修复网络模块子线程未及时销毁导致的程序崩溃"
]
},
"4.3.9.2": {
"修复bug": [
"修复语音包禁忌二重奏"
]
},
"4.3.9.1": {
"新增功能": [
"新增`无人值守模式`"
"语音功能上线"
],
"修复BUG": [
"修复软件窗口最大化异常问题",
"修复异常操作导致窗口离开屏幕后难以复原的问题",
"修正剩余理智关卡文案",
"修复隐藏到托盘时,托盘无法退出主程序的问题",
"修复Server酱网络异常导致的卡死问题"
"修复bug": [
"网络模块支持并发请求",
"修复中止任务时程序异常卡顿"
],
"程序优化": [
"主窗口显示版本号"
]
},
"4.3.6.1": {
"新增功能": [
"单次自动代理任务中,已完成的子任务在重复执行时不再启用"
]
},
"4.3.5.0": {
"新增功能": [
"用户设置中新增连战次数与剩余理智关卡两项配置项",
"支持自动代理时更新MAA"
],
"修复BUG": [
"适配MAAv5.16.0基建模式",
"适配自定义基建自动轮换功能"
],
"程序优化": [
"移除增效任务"
]
},
"4.3.5.2": {
"修复BUG": [
"修复无法建立网络连接时软件卡死问题"
]
},
"4.3.5.1": {
"程序优化": [
"模拟器路径适配快捷方式"
"非UI组件转为QObject类"
]
}
},
"proxy_list": [
"https://gitproxy.click/",
"https://cdn.moran233.xyz/",
"https://gh.llkk.cc/",
"https://github.akams.cn/",
"https://www.ghproxy.cn/",
"https://ghfast.top/"
],
"download_dict": {
"官方下载站-jp": "https://jp-download.fearr.xyz/AUTO_MAA/"
}
}