Compare commits

...

107 Commits

Author SHA1 Message Date
DLmaster
54b697f2ee Merge branch 'dev' 2025-03-07 11:56:54 +08:00
DLmaster
70df428825 fix(rescourse): 修复统计信息HTML模板公招匹配错误 2025-03-07 11:56:28 +08:00
DLmaster
8993d66056 Merge branch 'dev' 2025-03-07 01:19:18 +08:00
DLmaster
863e6fb25e Merge branch 'notice_dev' into dev 2025-03-06 23:43:10 +08:00
DLmaster
181856173e feat(core): 调整获取主题图像的时机 2025-03-06 23:42:59 +08:00
DLmaster
576fe59bbc fix(models): 修复任务被手动中止项目无法被正常录入的问题 2025-03-06 23:37:51 +08:00
DLmaster
c73aca71f7 feat(core): 优化通知服务
- 优化相关设置项排布
- 邮件添加HTML模板
2025-03-06 23:02:23 +08:00
DLmaster
ce264de963 feat(ui): 6星公招通知添加独立设置项 2025-03-05 16:00:48 +08:00
DLmaster
1feb0cf83f fix(serices): 修复通知服务使用InfoBar报错时程序崩溃问题 2025-03-05 15:11:57 +08:00
DLmaster
6292624d41 fix(services): 添加邮箱通知校验过程 2025-03-05 14:34:15 +08:00
4271a07f03 fix: 修复企业微信群机器人推送通知消息未正确换行 2025-03-05 14:07:03 +08:00
DLmaster
254fb6916f feat(core): 初步完成六星公招通知 2025-03-03 21:59:37 +08:00
DLmaster
21857325a2 Merge branch 'notice_dev' into dev 2025-03-03 20:41:17 +08:00
DLmaster
175d6860a3 feat(core): 添加统计信息通知功能 2025-03-03 20:40:28 +08:00
DLmaster
d1c8f98408 feat(ui): 主页添加主题活动信息 2025-03-02 17:14:09 +08:00
DLmaster
3499fa9067 feat(utils): 更新器拥有多网址测速功能 2025-03-02 15:32:56 +08:00
DLmaster
cca2cd774c chore(release): 发版 v4.2.4-beta.6 2025-03-01 22:53:08 +08:00
DLmaster
6d60f8adb8 Merge branch 'gui_dev' into dev 2025-03-01 22:51:30 +08:00
DLmaster
3b406a7974 feat(gui): 主页功能完善 2025-03-01 22:49:51 +08:00
a116b3359c fix: 修复掉落统计正则表达式错误统计时间的问题 2025-02-27 16:04:45 +08:00
928019390b chore(release): 发版 v4.2.4-beta.5 2025-02-26 21:58:29 +08:00
022b698f54 fix: 添加剿灭模式特殊适配 2025-02-26 16:13:46 +08:00
0228ac8393 fix: 修复 RMA70-12 匹配正则表达式 2025-02-26 16:12:42 +08:00
DLmaster
a99f381f7f fix(ui): 换个正常的主页 2025-02-22 00:22:57 +08:00
DLmaster
7c0af24bf5 fix(ui): 修正部分主页网址 2025-02-21 21:39:16 +08:00
DLmaster
d3aa45cfb9 Merge branch 'DLMS_dev' into dev 2025-02-21 18:36:32 +08:00
DLmaster
f5461deb81 feat(ui): 软件主页初步完成 2025-02-21 18:35:55 +08:00
DLmaster
c19068128f feat(core): 添加启动时直接最小化功能 2025-02-21 16:15:55 +08:00
DLmaster
1367daf1b7 feat(ui): 添加软件临时主页 2025-02-21 15:54:28 +08:00
DLmaster
5fc6e74cd6 Merge branch 'log_dev' into dev 2025-02-21 12:05:08 +08:00
DLmaster
5d7227c009 feat(ui): 初步完成历史记录前端适配 2025-02-21 12:04:52 +08:00
3a9c670172 feat(core): 新增日志管理功能
- 在配置文件中添加日志保存和保留天数设置项
- 实现日志保存功能,每次运行后保存日志到指定目录
- 添加日志分析功能,掉落信息并保存为 JSON 文件
- 在设置界面新增日志管理相关配置选项

todo: 日志清理可能有问题、多账号日志可能会保存为上一个账号的日志(加了time.sleep还没测)
2025-02-18 17:29:13 +08:00
DLmaster
2768faed53 fix(core): 修正更新器方法;取消MAA运行中自动更新;补充MAA监测字段 2025-02-18 16:17:03 +08:00
DLmaster
85f3d6f09f fix(core): 修改下载器默认下载目录为/script 2025-02-17 22:54:45 +08:00
DLmaster
c99707ecb4 Merge branch 'dev' 2025-02-17 18:09:42 +08:00
DLmaster
2b8e648fe6 Merge branch 'DLMS_dev' into dev 2025-02-17 18:08:45 +08:00
DLmaster
fcf61fd39a feat(core): 接入镜像源 2025-02-17 18:06:05 +08:00
DLmaster
8e3026f91e ci(build): 激活测试流程 2025-02-17 14:27:06 +08:00
DLmaster
8e00676faf ci(build): 测试服务器上传功能 2025-02-17 14:20:23 +08:00
DLmaster
ae293c4c20 Merge pull request #28 from ClozyA:dev
ci(build): 增加预发布版本服务器上传
2025-02-17 13:28:53 +08:00
df4a5f3318 ci(build): 增加预发布版本服务器上传 2025-02-17 12:02:03 +08:00
DLmaster
1da96c4d1d fix(ui): 矫正老板键文案 2025-02-17 11:06:07 +08:00
DLmaster
144c7f5db7 feat(modles): 配置MAA前关闭可能未正常退出的MAA进程 2025-02-16 18:44:46 +08:00
DLmaster
b3a3ccfea3 feat(core): 恢复启动后直接运行主任务功能以及相关托盘菜单 2025-02-16 14:56:25 +08:00
DLmaster
c3212f0ca1 Merge branch 'DLMS_dev' into dev 2025-02-15 17:32:47 +08:00
DLmaster
a946999782 fix: 修正版本号 2025-02-15 17:32:33 +08:00
DLmaster
284c41feb7 fix(ui): 适配深色模式 2025-02-15 17:28:54 +08:00
DLmaster
ddf905cb13 docs: 文档站试运行 2025-02-15 15:55:25 +08:00
DLmaster
d5082d18ef fix: 更正版本描述 2025-02-14 23:21:38 +08:00
DLmaster
af51831062 Merge branch 'DLMS_dev' into dev 2025-02-14 16:51:04 +08:00
DLmaster
fe4707df84 feat(ui): 优化主调度台默认选项 2025-02-14 16:50:45 +08:00
DLmaster
292e7f51e0 chore(core): 升级日志监看方法 2025-02-14 16:27:18 +08:00
DLmaster
8936b1c41d fix(core): 修复部分异常;添加高级代理文件校验过程 2025-02-13 13:03:44 +08:00
DLmaster
d45da439bd chore(models): 优化MAA关闭方法 2025-02-12 22:15:17 +08:00
DLmaster
7dc057e30f feat(models): 简洁用户列表下相邻两个任务间的切换方式 2025-02-12 22:06:04 +08:00
DLmaster
eb2f9d2cea fix(models): 修复静默代理标记移除异常情况 2025-02-11 13:47:12 +08:00
DLmaster
fb1895c4eb Merge branch 'dev' 2025-02-10 19:26:25 +08:00
DLmaster
90d3dad8c8 fix(services): 修复邮箱发信人信息错误 2025-02-10 11:29:27 +08:00
DLmaster
de12339c3e Merge branch 'DLMS_dev' into dev 2025-02-09 21:36:57 +08:00
DLmaster
f07cd2b44a feat(core): 邮箱推送功能调整,改由用户提供发信邮箱 2025-02-09 21:36:33 +08:00
DLmaster
c7fbbf6f50 Merge branch 'DLMS_dev' into dev 2025-02-08 16:58:23 +08:00
DLmaster
de262ee6bd fix(models): 修复设置MAA时异常调用B服任务设置 2025-02-08 16:58:11 +08:00
DLmaster
0c123e9389 feat(services): 通知标题添加脚本实例信息 2025-02-08 12:25:48 +08:00
DLmaster
d13fbb063d feat(core): 初步完成托管bilibili游戏隐私政策功能 2025-02-08 12:05:28 +08:00
DLmaster
5c24eb7d56 docs: 改用腾讯文档展示使用方法 2025-02-07 20:15:12 +08:00
DLmaster
6c2f19a884 Revert "新增用户字段today_stauts"
This reverts commit 4ff632ed2a.
2025-02-07 19:56:41 +08:00
heziziziscool
4ff632ed2a 新增用户字段today_stauts
新增用户字段`today_status`(位于user_db),用于记录用户的代理是否代理成功,便于后续的衍生项目与二次开发。
2025-02-07 18:50:32 +08:00
DLmaster
d445c0054f Merge branch 'DLMS_dev' into dev 2025-02-07 18:27:29 +08:00
DLmaster
748fa7a004 fix(gui): 修复窗口记忆功能失效问题 2025-02-07 18:27:01 +08:00
DLmaster
c3e710b5cf chore(gui): 调整MAA设置目录时打开当前已配置的目录位置 2025-02-07 15:53:37 +08:00
DLmaster
a93a60d125 chore(core): 优化静默判定逻辑 2025-02-07 15:37:07 +08:00
DLmaster
07f24c6168 Merge branch 'DLMS_dev' into dev 2025-02-06 23:33:16 +08:00
DLmaster
7f5478b098 feat(core): 添加调度队列完成任务后行为选项 2025-02-06 23:33:00 +08:00
DLmaster
fb7a429ff2 Merge pull request #22 from ClozyA:auto_shutdown
feat(core): 添加运行完成后自动关机功能
2025-02-06 18:33:12 +08:00
3307793a3d feat: 添加运行完成后自动关机功能 2025-02-06 15:55:55 +08:00
DLmaster
0da9f4b7ab Merge branch 'main' into dev 2025-02-04 22:48:09 +08:00
DLmaster
f45dc3a34c chore(utils): 修改代理优先级 2025-02-04 22:47:26 +08:00
DLmaster
1c17f3d878 Merge branch 'dev' 2025-02-04 22:36:34 +08:00
DLmaster
75e4d2b290 fix(utils): 修复更新器异常并覆盖版本 2025-02-04 22:34:17 +08:00
DLmaster
32fe941735 Merge branch 'dev' 2025-02-04 21:52:20 +08:00
DLmaster
27633b1017 Merge branch 'DLMS_dev' into dev 2025-02-04 18:56:27 +08:00
DLmaster
c34ca0dea9 fix(utils): 更新代理镜像 2025-02-04 18:56:09 +08:00
DLmaster
0574e9c6cb fix(gui): 调整部分选项文案 2025-02-04 18:48:58 +08:00
DLmaster
b7f09141f1 feat(core): 添加更新类别可选项 2025-02-04 18:27:19 +08:00
DLmaster
022e59e65c fix(gha): pr时不再自动发版 2025-02-04 15:56:21 +08:00
DLmaster
a0731331a8 fix(gui): 修复高级MAA配置序号错位;修复高级用户无法配置问题 2025-02-04 15:17:15 +08:00
DLmaster
4b01222648 Merge branch 'dev' into DLMS_dev 2025-02-04 14:16:07 +08:00
DLmaster
cae4b26c89 Merge branch 'dev' of https://github.com/DLmaster361/AUTO_MAA into dev 2025-02-04 14:08:27 +08:00
DLmaster
427c2332f5 chore: 补充版本信息 2025-02-04 14:08:13 +08:00
heziziziscool
6f0aec329b 新增代理成功消息推送渠道Server酱与企业微信群机器人推送 2025-02-04 14:07:45 +08:00
DLmaster
4e4d1d068f chore: 补充版本信息 2025-02-03 20:12:19 +08:00
DLmaster
074f4f2ca9 Merge branch 'hz_dev' into dev 2025-02-03 20:05:34 +08:00
heziziziscool
c51f9ad901 新增代理成功消息推送渠道Server酱与企业微信群机器人推送 2025-02-03 19:22:36 +08:00
heziziziscool
792452c048 Revert "revert 提交到main"
This reverts commit 662eb0bc7f.
2025-02-03 19:00:01 +08:00
heziziziscool
662eb0bc7f revert 提交到main 2025-02-03 18:57:40 +08:00
heziziziscool
94a9bdbb93 Revert "- 新增Server酱与企业微信群机器人推送代理成功渠道。"
This reverts commit df96183f42.
2025-02-03 18:54:09 +08:00
heziziziscool
df96183f42 - 新增Server酱与企业微信群机器人推送代理成功渠道。 2025-02-03 18:49:46 +08:00
DLmaster
a5b4f6f59f fix(gui): 修复主调度台运行时选项变动问题 2025-02-03 16:20:26 +08:00
DLmaster
6f7497cbe9 fix(utils): 修复更新器文件夹定位问题 2025-02-03 09:47:10 +08:00
DLmaster
dbdc2144b7 Merge branch 'dev' 2025-02-02 09:26:20 +08:00
DLmaster
e34106f857 Merge branch 'fix_inf_dev' into dev 2025-02-02 01:20:01 +08:00
DLmaster
c3c07804cd fix(core): 修复自定义基建无法正常使用的问题
feat(core): 添加用户每日代理次数上限功能
2025-02-02 01:19:46 +08:00
DLmaster
0b5ac6bb6e Merge branch 'dev' 2025-01-31 22:03:03 +08:00
DLmaster
ea87eefb9b doc: 修复部分图片位置 2025-01-31 22:02:51 +08:00
DLmaster
20dc4656dc Merge branch 'dev' 2025-01-31 21:52:57 +08:00
DLmaster
3f0f1612e3 doc: README同步至v4.2.2版本 2025-01-31 21:52:31 +08:00
DLmaster
e92b6ecfe6 fix(maa): 修正人工排查文案 2025-01-29 10:24:03 +08:00
31 changed files with 3564 additions and 954 deletions

View File

@@ -26,11 +26,6 @@ on:
paths-ignore: paths-ignore:
- '**.md' - '**.md'
- 'LICENSE' - 'LICENSE'
pull_request:
branches: [ "main" ]
paths-ignore:
- '**.md'
- 'LICENSE'
permissions: permissions:
contents: read contents: read
@@ -155,4 +150,13 @@ jobs:
gh release delete "$TAGNAME" --yes gh release delete "$TAGNAME" --yes
gh release create "$TAGNAME" --target "main" --title "$NAME" --notes "$NOTES" artifacts/* gh release create "$TAGNAME" --target "main" --title "$NAME" --notes "$NOTES" artifacts/*
env: env:
GITHUB_TOKEN: ${{ secrets.WORKFLOW_TOKEN }} GITHUB_TOKEN: ${{ secrets.WORKFLOW_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

@@ -26,11 +26,6 @@ on:
paths-ignore: paths-ignore:
- '**.md' - '**.md'
- 'LICENSE' - 'LICENSE'
pull_request:
branches: [ "dev" ]
paths-ignore:
- '**.md'
- 'LICENSE'
permissions: permissions:
contents: read contents: read
@@ -155,4 +150,13 @@ jobs:
gh release delete "$TAGNAME" --yes gh release delete "$TAGNAME" --yes
gh release create "$TAGNAME" --target "main" --title "$NAME" --notes "$NOTES" --prerelease artifacts/* gh release create "$TAGNAME" --target "main" --title "$NAME" --notes "$NOTES" --prerelease artifacts/*
env: env:
GITHUB_TOKEN: ${{ secrets.WORKFLOW_TOKEN }} GITHUB_TOKEN: ${{ secrets.WORKFLOW_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/

152
README.md
View File

@@ -32,7 +32,8 @@ MAA多账号管理与自动化软件
### 优势 ### 优势
- **节省运行开销:** 只需要一份MAA软件与一个模拟器无需多开就能完成多账号代理羸弱的电脑也能代理日常。 - **节省运行开销:** 只需要一份MAA软件与一个模拟器无需多开就能完成多账号代理羸弱的电脑也能代理日常。
- **自定义空间大:** 依靠高级用户配置模式支持MAA几乎所有设置选项自定义同时保留对模拟器多开的支持 - **自定义空间大:** 依靠高级用户配置模式支持MAA几乎所有设置选项自定义支持模拟器多开。
- **调度方法自由:** 通过调度队列功能自由实现MAA多开等多种调度方式。
- **一键代理无忧:** 无须中途手动修改MAA配置将繁琐交给AUTO_MAA把游戏留给自己。 - **一键代理无忧:** 无须中途手动修改MAA配置将繁琐交给AUTO_MAA把游戏留给自己。
- **代理结果复核:** 通过人工排查功能核实各用户代理情况,堵住自动代理的最后一丝风险。 - **代理结果复核:** 通过人工排查功能核实各用户代理情况,堵住自动代理的最后一丝风险。
@@ -44,8 +45,7 @@ MAA多账号管理与自动化软件
- **使用:** AUTO_MAA使用者可以按自己的意愿自由使用本软件。依据GPL对于由此可能产生的损失AUTO_MAA项目组不负任何责任。 - **使用:** AUTO_MAA使用者可以按自己的意愿自由使用本软件。依据GPL对于由此可能产生的损失AUTO_MAA项目组不负任何责任。
- **分发:** AUTO_MAA允许任何人自由分发本软件包括进行商业活动牟利。若为直接分发本软件必须遵循GPL向接收者提供本软件项目地址、完整的软件源码与GPL协议原文若为修改软件后进行分发必须遵循GPL向接收者提供本软件项目地址、修改前的完整软件源码副本与GPL协议原文违反者可能会被追究法律责任。 - **分发:** AUTO_MAA允许任何人自由分发本软件包括进行商业活动牟利。若为直接分发本软件必须遵循GPL向接收者提供本软件项目地址、完整的软件源码与GPL协议原文若为修改软件后进行分发必须遵循GPL向接收者提供本软件项目地址、修改前的完整软件源码副本与GPL协议原文违反者可能会被追究法律责任。
- **传播:** AUTO_MAA原则上允许传播者自由传播本软件但无论在何种传播过程中不得删除项目作者与开发者所留版权声明不得隐瞒项目作者与相关开发者的存在。由于软件性质项目组不希望发现任何人在明日方舟官方媒体包括官方媒体账号与森空岛社区等或明日方舟游戏相关内容包括同好群、线下活动与游戏内容讨论等下提及AUTO_MAA或MAA希望各位理解。 - **传播:** AUTO_MAA原则上允许传播者自由传播本软件但无论在何种传播过程中不得删除项目作者与开发者所留版权声明不得隐瞒项目作者与相关开发者的存在。由于软件性质项目组不希望发现任何人在明日方舟官方媒体包括官方媒体账号与森空岛社区等或明日方舟游戏相关内容包括同好群、线下活动与游戏内容讨论等下提及AUTO_MAA或MAA希望各位理解。
- **衍生:** AUTO_MAA允许任何人对软件本体或软件部分代码进行二次开发或利用。但依据GPL相关成果也必须使用GPL开源。 - **衍生:** AUTO_MAA允许任何人对软件本体或软件部分代码进行二次开发或利用。但依据GPL相关成果再次分发时也必须使用GPL或兼容的协议开源。
- **授权:** 如果希望在使用AUTO_MAA的相关成果后仍保持自己的项目闭源请在Issues中说明来意。得到项目组认可后我们可以提供另一份使用不同协议的代码此协议主要内容如下被授权者可以自由使用该代码并维持闭源被授权者必须定期为AUTO_MAA作出贡献。
- **贡献:** 不论是直接参与软件的维护编写或是撰写文档、测试、反馈BUG、给出建议、参与讨论都为AUTO_MAA项目的发展完善做出了不可忽视的贡献。项目组提倡各位贡献者遵照GitHub开源社区惯例发布Issues参与项目。避免私信或私发邮件安全性漏洞或敏感问题除外以帮助更多用户。 - **贡献:** 不论是直接参与软件的维护编写或是撰写文档、测试、反馈BUG、给出建议、参与讨论都为AUTO_MAA项目的发展完善做出了不可忽视的贡献。项目组提倡各位贡献者遵照GitHub开源社区惯例发布Issues参与项目。避免私信或私发邮件安全性漏洞或敏感问题除外以帮助更多用户。
以上细则是本项目对GPL的相关补充与强调。未提及的以GPL为准发生冲突的以本细则为准。如有不清楚的部分请发Issues询问。若发生纠纷相关内容也没有在Issues上提及的项目组拥有最终解释权。 以上细则是本项目对GPL的相关补充与强调。未提及的以GPL为准发生冲突的以本细则为准。如有不清楚的部分请发Issues询问。若发生纠纷相关内容也没有在Issues上提及的项目组拥有最终解释权。
@@ -58,151 +58,17 @@ MAA多账号管理与自动化软件
# 使用方法 # 使用方法
## 安装软件 访问AUTO_MAA官方文档站以获取使用指南和项目相关信息
``` - [AUTO_MAA官方文档站](https://clozya.github.io/AUTOMAA_docs)
本软件是MAA的外部工具需要安装MAA后才能使用。
```
### 下载MAA
- 什么是MAA [官网](https://maa.plus/)/[GitHub](https://github.com/MaaAssistantArknights/MaaAssistantArknights)
- MAA下载地址 [GitHub下载](https://github.com/MaaAssistantArknights/MaaAssistantArknights/releases)
### 安装MAA
- 将MAA压缩包解压至任意普通文件夹即可。
- 若为首次安装MAA请双击`MAA.exe`启动MAA程序以生成MAA配置文件。
### 下载AUTO_MAA [![](https://img.shields.io/github/downloads/DLmaster361/AUTO_MAA/total?color=66ccff)](https://github.com/DLmaster361/AUTO_MAA/releases)
- GitHub下载地址 [GitHub下载](https://github.com/DLmaster361/AUTO_MAA/releases)
### 安装AUTO_MAA
- 将AUTO_MAA压缩包解压至任意普通文件夹即可。
## 配置AUTO_MAA
### 启动AUTO_MAA
- 双击`AUTO_MAA.exe`以启动软件。
```
注意:
首次启动时会要求设置管理密钥。
管理密钥是解密用户密码的唯一凭证,与数据库绑定。
密钥丢失或data/key/目录下任一文件损坏都将导致解密无法正常进行。
本项目采用自主开发的混合加密模式项目组也无法找回您的管理密钥或修复data/key/目录下的文件。
如果不幸的事发生建议您删除data/key/目录与data/data.db文件后重新录入信息。
```
### 配置信息
#### 设置MAA
1. 通过`浏览`绑定MAA后单击`设置MAA`进行MAA全局设置。
2. 在打开的MAA界面完成`性能设置``游戏设置``连接设置``启动设置``界面设置``软件更新`等基本配置以及代理任务的详细配置。
3. 完成基本配置后关闭MAA页面AUTO_MAA会自动保存您的配置。
- 注意在MAA的设置过程中若MAA要求`立刻重启应用更改`,请选择`稍后`。否则MAA重启后的一切更改都不会被程序记录。
- 特别的,您需要确保自己:
- 取消勾选`开机自启动MAA`
- 配置自己模拟器所在的位置并根据实际情况填写`等待模拟器启动时间`建议预留10s以防意外
- 如果是模拟器多开用户,还需要填写`附加命令`,具体填写值参见多开模拟器对应快捷方式路径(如`-v 1`)。
![MAA配置](https://github.com/DLmaster361/AUTO_MAA/blob/main/resources/images/README/MAA配置.png "MAA配置")
#### 设置AUTO_MAA
本项目已基本完成GUI开发您可以直接在设置页配置AUTO_MAA相关信息页面简介如下
- `MAA路径`该项无法直接编辑仅用于展示当前程序所绑定MAA的路径。
- `浏览`选择MAA文件夹。
- `设置MAA`编辑MAA全局配置具体使用方法参见前文。
- `日常限制`执行自动代理的日常部分时的超时阈值当MAA日志无变化时间超过阈值时视为超时。
- `剿灭限制`执行自动代理的剿灭部分时的超时阈值当MAA日志无变化时间超过阈值时视为超时。
- `运行失败重试次数上限`:对于每一用户,若超过该次数限制仍未完成代理,视为代理失败。
- `开机自动启动AUTO_MAA`实现AUTO_MAA的自启动。
- `AUTO_MAA启动时禁止电脑休眠`:仅阻止电脑自动休眠,不会影响屏幕是否熄灭。
- `启动AUTO_MAA后直接代理`在AUTO_MAA启动后立即执行代理任务。
- `通过邮件通知结果`在AUTO_MAA完成任务后将结果发送至用户指定邮箱。
- `检查版本更新`从GitHub镜像源获取版本更新信息并调起更新器完成程序更新更新器已支持多个代理地址。
- `修改管理密钥`:修改管理密钥,当用户列表中无用户时,将跳过验证旧管理密钥。
#### 设置用户配置
本项目已基本完成GUI开发您可以直接在用户管理页配置用户相关信息页面简介如下
- `新建``删除`:新建一个用户到当前用户配置列表、删除当前所选第一行所对应的用户。
- `转为高级/简洁`:将当前所选第一行所对应的用户转为高级/简洁配置模式。
- `显示密码`:输入管理密钥以显示用户密码,仅当管理密钥正确时能够修改`密码栏目`
- `刷新`:清除临时保存的管理密钥。
- `简洁用户配置列表`仅支持核心代理选项的设置其它设置选项沿用MAA的全局设置部分代理核心功能选项由程序托管。
- `高级用户配置列表`:支持几乎所有代理选项的设置,通过`修改配置`进行MAA自定义仅部分代理核心功能选项由程序托管。
- `用户配置列表栏目`:详解如下:
- `用户名`:展示在执行界面的用户名,用于区分不同用户。
- `账号ID`MAA进行账号切换所需的凭据官服用户请输入手机号码、B服请输入B站ID。
- `服务器`当前支持官服、B服。
- `代理天数`:剩余需要进行代理的天数,输入`任意负数`可设置为无限代理天数当剩余天数为0时不再代理或排查。
- `状态`:用户的状态,禁用时将不再对其进行代理或排查。
- `执行情况`:当日执行情况,不可编辑。
- `关卡``备选关卡-1``备选关卡-2`:关卡号。
- `日常`单独设定是否进行自动代理的日常部分可进一步配置MAA的具体代理任务该配置与全局MAA配置相互独立。
- `剿灭`单独设定是否进行自动代理的剿灭部分高级配置模式下可进一步配置MAA的具体代理任务该配置与全局MAA配置相互独立。
- `自定义基建`:是否启用自定义基建功能,需要进一步配置自定义基建文件,该配置与其他用户相互独立。
- `密码`:仅用于登记用户的密码,可留空。
- `备注`:用于备注用户信息。
- 特别的:
- 对于`简洁用户配置列表的关卡、备选关卡-1、备选关卡-2栏目`您可以自定义关卡号替换方案。
- 程序会读取`data/gameid.txt`中的数据,依据此进行关卡号的替换,便于常用关卡的使用。
- `gameid.txt`会在程序首次运行时生成,其中将预置一些常用资源本的替换方案。
![gameid](https://github.com/DLmaster361/AUTO_MAA/blob/main/resources/images/README/gameid.png "gameid")
## 运行代理任务
### 直接运行
- 在执行页单击`立即执行`直接运行。
### 定时运行
- 在执行页的`定时执行`栏设置时间。
- 保持软件打开,软件会在设定的时间自动运行。
## 人工排查代理结果
### 直接开始人工排查
- 在执行页单击`开始排查`启动排查进程。
- 软件将调起MAA依次登录各用户的账号。
- 完成PRTS登录后请人工检查代理情况可以手动完成未代理的任务。
- 在对话框中单击对应账号的代理情况。
- 结束人工排查后,相应排查情况将被写入用户管理页的`备注栏目`
--- ---
# 关于 # 关于
## 未来开发方向 ## 项目开发情况
- [ ] 尝试接入更多开源社区成果 可在[《AUTO_MAA开发者协作文档》](https://docs.qq.com/aio/DQ3Z5eHNxdmxFQmZX)的`开发任务`页面中查看开发进度。
- [ ] 支持对MAA运行状况的进一步识别
- [x] 添加更多通知手段
- [ ] GUI界面美化
## 贡献者 ## 贡献者
@@ -216,6 +82,8 @@ MAA多账号管理与自动化软件
![Alt](https://repobeats.axiom.co/api/embed/6c2f834141eff1ac297db70d12bd11c6236a58a5.svg "Repobeats analytics image") ![Alt](https://repobeats.axiom.co/api/embed/6c2f834141eff1ac297db70d12bd11c6236a58a5.svg "Repobeats analytics image")
感谢 [AoXuan (@ClozyA)](https://github.com/ClozyA) 为本项目提供的下载服务器
## Star History ## Star History
[![Star History Chart](https://api.star-history.com/svg?repos=DLmaster361/AUTO_MAA&type=Date)](https://star-history.com/#DLmaster361/AUTO_MAA&Date) [![Star History Chart](https://api.star-history.com/svg?repos=DLmaster361/AUTO_MAA&type=Date)](https://star-history.com/#DLmaster361/AUTO_MAA&Date)
@@ -224,7 +92,7 @@ MAA多账号管理与自动化软件
欢迎加入AUTO_MAA项目组欢迎反馈bug 欢迎加入AUTO_MAA项目组欢迎反馈bug
- QQ群[957750551](https://qm.qq.com/cgi-bin/qm/qr?k=EET-OL_o52KPlDLEmbzaNkKUXuyQ4WZY&jump_from=webapi&authKey=6NxGwEu9JAOLHqfdEmNfrZy4tUvC/3ar2j5+Go7Hgf3j+ntAK1VS6SUOLOjYVKTt) - QQ交流群:[957750551](https://qm.qq.com/q/bd9fISNoME)
--- ---

View File

@@ -29,7 +29,7 @@ __version__ = "4.2.0"
__author__ = "DLmaster361 <DLmaster_361@163.com>" __author__ = "DLmaster361 <DLmaster_361@163.com>"
__license__ = "GPL-3.0 license" __license__ = "GPL-3.0 license"
from .core import AppConfig, QueueConfig, MaaConfig, Task, Task_manager, Main_timer from .core import AppConfig, QueueConfig, MaaConfig, Task, TaskManager, MainTimer
from .models import MaaManager from .models import MaaManager
from .services import Notify, Crypto, System from .services import Notify, Crypto, System
from .ui import AUTO_MAA from .ui import AUTO_MAA
@@ -40,8 +40,8 @@ __all__ = [
"QueueConfig", "QueueConfig",
"MaaConfig", "MaaConfig",
"Task", "Task",
"Task_manager", "TaskManager",
"Main_timer", "MainTimer",
"MaaManager", "MaaManager",
"Notify", "Notify",
"Crypto", "Crypto",

View File

@@ -31,8 +31,8 @@ __license__ = "GPL-3.0 license"
from .config import AppConfig, QueueConfig, MaaConfig, Config from .config import AppConfig, QueueConfig, MaaConfig, Config
from .main_info_bar import MainInfoBar from .main_info_bar import MainInfoBar
from .task_manager import Task, Task_manager from .task_manager import Task, TaskManager
from .timer import Main_timer from .timer import MainTimer
__all__ = [ __all__ = [
"AppConfig", "AppConfig",
@@ -41,6 +41,6 @@ __all__ = [
"MaaConfig", "MaaConfig",
"MainInfoBar", "MainInfoBar",
"Task", "Task",
"Task_manager", "TaskManager",
"Main_timer", "MainTimer",
] ]

View File

@@ -30,6 +30,9 @@ import sqlite3
import json import json
import sys import sys
import shutil import shutil
import re
from datetime import datetime
from collections import defaultdict
from pathlib import Path from pathlib import Path
from qfluentwidgets import ( from qfluentwidgets import (
QConfig, QConfig,
@@ -39,7 +42,10 @@ from qfluentwidgets import (
FolderValidator, FolderValidator,
BoolValidator, BoolValidator,
RangeValidator, RangeValidator,
OptionsValidator,
qconfig,
) )
from typing import Union, Dict, List, Tuple
class AppConfig: class AppConfig:
@@ -52,7 +58,7 @@ class AppConfig:
self.log_path = self.app_path / "debug/AUTO_MAA.log" self.log_path = self.app_path / "debug/AUTO_MAA.log"
self.database_path = self.app_path / "data/data.db" self.database_path = self.app_path / "data/data.db"
self.config_path = self.app_path / "config/config.json" self.config_path = self.app_path / "config/config.json"
self.history_path = self.app_path / "config/history.json" self.history_path = self.app_path / "history/main.json"
self.key_path = self.app_path / "data/key" self.key_path = self.app_path / "data/key"
self.gameid_path = self.app_path / "data/gameid.txt" self.gameid_path = self.app_path / "data/gameid.txt"
self.version_path = self.app_path / "resources/version.json" self.version_path = self.app_path / "resources/version.json"
@@ -72,6 +78,7 @@ class AppConfig:
(self.app_path / "config").mkdir(parents=True, exist_ok=True) (self.app_path / "config").mkdir(parents=True, exist_ok=True)
(self.app_path / "data").mkdir(parents=True, exist_ok=True) (self.app_path / "data").mkdir(parents=True, exist_ok=True)
(self.app_path / "debug").mkdir(parents=True, exist_ok=True) (self.app_path / "debug").mkdir(parents=True, exist_ok=True)
(self.app_path / "history").mkdir(parents=True, exist_ok=True)
# 生成版本信息文件 # 生成版本信息文件
if not self.version_path.exists(): if not self.version_path.exists():
@@ -125,6 +132,17 @@ class AppConfig:
self.queue_config = QueueConfig() self.queue_config = QueueConfig()
self.maa_config = MaaConfig() self.maa_config = MaaConfig()
qconfig.load(self.config_path, self.global_config)
config_list = self.search_config()
for config in config_list:
if config[0] == "Maa":
qconfig.load(config[1], self.maa_config)
self.maa_config.save()
elif config[0] == "Queue":
qconfig.load(config[1], self.queue_config)
self.queue_config.save()
logger.info("配置类初始化完成") logger.info("配置类初始化完成")
def init_database(self, mode: str) -> None: def init_database(self, mode: str) -> None:
@@ -378,6 +396,22 @@ class AppConfig:
db.close() db.close()
logger.info("数据文件版本更新完成") logger.info("数据文件版本更新完成")
def search_config(self) -> list:
"""搜索所有子配置文件"""
config_list = []
if (self.app_path / "config/MaaConfig").exists():
for subdir in (self.app_path / "config/MaaConfig").iterdir():
if subdir.is_dir():
config_list.append(["Maa", subdir / "config.json"])
if (self.app_path / "config/QueueConfig").exists():
for json_file in (self.app_path / "config/QueueConfig").glob("*.json"):
config_list.append(["Queue", json_file])
return config_list
def open_database(self, mode: str, index: str = None) -> None: def open_database(self, mode: str, index: str = None) -> None:
"""打开数据库""" """打开数据库"""
@@ -432,6 +466,224 @@ class AppConfig:
cur.close() cur.close()
db.close() db.close()
def save_maa_log(self, log_path: Path, logs: list, maa_result: str) -> bool:
"""保存MAA日志"""
data: Dict[str, Union[str, Dict[str, Union[int, dict]]]] = {
"recruit_statistics": defaultdict(int),
"drop_statistics": defaultdict(dict),
"maa_result": maa_result,
}
if_six_star = False
# 公招统计(仅统计招募到的)
confirmed_recruit = False
current_star_level = None
i = 0
while i < len(logs):
if "公招识别结果:" in logs[i]:
current_star_level = None # 每次识别公招时清空之前的星级
i += 1
while i < len(logs) and "Tags" not in logs[i]: # 读取所有公招标签
i += 1
if i < len(logs) and "Tags" in logs[i]: # 识别星级
star_match = re.search(r"(\d+)\s*★ Tags", logs[i])
if star_match:
current_star_level = f"{star_match.group(1)}"
if current_star_level == "6★":
if_six_star = True
if "已确认招募" in logs[i]: # 只有确认招募后才统计
confirmed_recruit = True
if confirmed_recruit and current_star_level:
data["recruit_statistics"][current_star_level] += 1
confirmed_recruit = False # 重置,等待下一次公招
current_star_level = None # 清空已处理的星级
i += 1
# 掉落统计
current_stage = None
stage_drops = {}
for i, line in enumerate(logs):
drop_match = re.search(r"([A-Za-z0-9\-]+) 掉落统计:", line)
if drop_match:
# 发现新关卡,保存前一个关卡数据
if current_stage and stage_drops:
data["drop_statistics"][current_stage] = stage_drops
current_stage = drop_match.group(1)
if current_stage == "WE":
current_stage = "剿灭模式"
stage_drops = {}
continue
if current_stage:
item_match: List[str] = re.findall(
r"^(?!\[)([\u4e00-\u9fa5A-Za-z0-9\-]+)\s*:\s*([\d,]+)(?:\s*\(\+[\d,]+\))?",
line,
re.M,
)
for item, total in item_match:
# 解析数值时去掉逗号 (如 2,160 -> 2160
total = int(total.replace(",", ""))
# 黑名单
if item not in [
"当前次数",
"理智",
"最快截图耗时",
"专精等级",
"剩余时间",
]:
stage_drops[item] = total
# 处理最后一个关卡的掉落数据
if current_stage and stage_drops:
data["drop_statistics"][current_stage] = stage_drops
# 保存日志
log_path.parent.mkdir(parents=True, exist_ok=True)
with log_path.open("w", encoding="utf-8") as f:
f.writelines(logs)
with log_path.with_suffix(".json").open("w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=4)
logger.info(f"处理完成:{log_path}")
self.merge_maa_logs("所有项", log_path.parent)
return if_six_star
def merge_maa_logs(self, mode: str, logs_path: Union[Path, List[Path]]) -> dict:
"""合并指定数据统计信息文件"""
data = {
"recruit_statistics": defaultdict(int),
"drop_statistics": defaultdict(dict),
"maa_result": defaultdict(str),
}
if mode == "所有项":
logs_path_list = list(logs_path.glob("*.json"))
elif mode == "指定项":
logs_path_list = logs_path
for json_file in logs_path_list:
with json_file.open("r", encoding="utf-8") as f:
single_data: Dict[str, Union[str, Dict[str, Union[int, dict]]]] = (
json.load(f)
)
# 合并公招统计
for star_level, count in single_data["recruit_statistics"].items():
data["recruit_statistics"][star_level] += count
# 合并掉落统计
for stage, drops in single_data["drop_statistics"].items():
if stage not in data["drop_statistics"]:
data["drop_statistics"][stage] = {} # 初始化关卡
for item, count in drops.items():
if item in data["drop_statistics"][stage]:
data["drop_statistics"][stage][item] += count
else:
data["drop_statistics"][stage][item] = count
# 合并MAA结果
data["maa_result"][
json_file.name.replace(".json", "").replace("-", ":")
] = single_data["maa_result"]
# 生成汇总 JSON 文件
if mode == "所有项":
with logs_path.with_suffix(".json").open("w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=4)
logger.info(f"统计完成:{logs_path.with_suffix(".json")}")
return data
def load_maa_logs(
self, mode: str, json_path: Path
) -> Dict[str, Union[str, list, Dict[str, list]]]:
"""加载MAA日志统计信息"""
if mode == "总览":
with json_path.open("r", encoding="utf-8") as f:
info: Dict[str, Dict[str, Union[int, dict]]] = json.load(f)
data = {}
data["条目索引"] = [
[k, "完成" if v == "Success!" else "异常"]
for k, v in info["maa_result"].items()
]
data["条目索引"].insert(0, ["数据总览", "运行"])
data["统计数据"] = {"公招统计": list(info["recruit_statistics"].items())}
for game_id, drops in info["drop_statistics"].items():
data["统计数据"][f"掉落统计:{game_id}"] = list(drops.items())
data["统计数据"]["报错汇总"] = [
[k, v] for k, v in info["maa_result"].items() if v != "Success!"
]
elif mode == "单项":
with json_path.open("r", encoding="utf-8") as f:
info: Dict[str, Union[str, Dict[str, Union[int, dict]]]] = json.load(f)
data = {}
data["统计数据"] = {"公招统计": list(info["recruit_statistics"].items())}
for game_id, drops in info["drop_statistics"].items():
data["统计数据"][f"掉落统计:{game_id}"] = list(drops.items())
with json_path.with_suffix(".log").open("r", encoding="utf-8") as f:
log = f.read()
data["日志信息"] = log
return data
def search_history(self) -> dict:
"""搜索所有历史记录"""
history_dict = {}
for date_folder in (Config.app_path / "history").iterdir():
if not date_folder.is_dir():
continue # 只处理日期文件夹
try:
date = datetime.strptime(date_folder.name, "%Y-%m-%d")
history_dict[date.strftime("%Y年 %m月 %d")] = list(
date_folder.glob("*.json")
)
except ValueError:
logger.warning(f"非日期格式的目录: {date_folder}")
return {
k: v
for k, v in sorted(
history_dict.items(),
key=lambda x: datetime.strptime(x[0], "%Y年 %m月 %d"),
reverse=True,
)
}
def save_history(self, key: str, content: dict) -> None: def save_history(self, key: str, content: dict) -> None:
"""保存历史记录""" """保存历史记录"""
@@ -459,6 +711,8 @@ class AppConfig:
self.maa_config.set(self.maa_config.MaaSet_Name, "") self.maa_config.set(self.maa_config.MaaSet_Name, "")
self.maa_config.set(self.maa_config.MaaSet_Path, ".") self.maa_config.set(self.maa_config.MaaSet_Path, ".")
self.maa_config.set(self.maa_config.RunSet_TaskTransitionMethod, "ExitEmulator")
self.maa_config.set(self.maa_config.RunSet_ProxyTimesLimit, 0)
self.maa_config.set(self.maa_config.RunSet_AnnihilationTimeLimit, 40) self.maa_config.set(self.maa_config.RunSet_AnnihilationTimeLimit, 40)
self.maa_config.set(self.maa_config.RunSet_RoutineTimeLimit, 10) self.maa_config.set(self.maa_config.RunSet_RoutineTimeLimit, 10)
self.maa_config.set(self.maa_config.RunSet_RunTimesLimit, 3) self.maa_config.set(self.maa_config.RunSet_RunTimesLimit, 3)
@@ -471,6 +725,7 @@ class AppConfig:
self.queue_config.set(self.queue_config.queueSet_Name, "") self.queue_config.set(self.queue_config.queueSet_Name, "")
self.queue_config.set(self.queue_config.queueSet_Enabled, False) self.queue_config.set(self.queue_config.queueSet_Enabled, False)
self.queue_config.set(self.queue_config.queueSet_AfterAccomplish, "None")
self.queue_config.set(self.queue_config.time_TimeEnabled_0, False) self.queue_config.set(self.queue_config.time_TimeEnabled_0, False)
self.queue_config.set(self.queue_config.time_TimeSet_0, "00:00") self.queue_config.set(self.queue_config.time_TimeSet_0, "00:00")
@@ -508,14 +763,29 @@ class AppConfig:
class GlobalConfig(QConfig): class GlobalConfig(QConfig):
"""全局配置""" """全局配置"""
function_HomeImageMode = OptionsConfigItem(
"Function",
"HomeImageMode",
"默认",
OptionsValidator(["默认", "自定义", "主题图像"]),
)
function_HistoryRetentionTime = OptionsConfigItem(
"Function", "HistoryRetentionTime", 0, OptionsValidator([7, 15, 30, 60, 0])
)
function_IfAllowSleep = ConfigItem( function_IfAllowSleep = ConfigItem(
"Function", "IfAllowSleep", False, BoolValidator() "Function", "IfAllowSleep", False, BoolValidator()
) )
function_IfSilence = ConfigItem("Function", "IfSilence", False, BoolValidator()) function_IfSilence = ConfigItem("Function", "IfSilence", False, BoolValidator())
function_BossKey = ConfigItem("Function", "BossKey", "") function_BossKey = ConfigItem("Function", "BossKey", "")
function_IfAgreeBilibili = ConfigItem(
"Function", "IfAgreeBilibili", False, BoolValidator()
)
start_IfSelfStart = ConfigItem("Start", "IfSelfStart", False, BoolValidator()) start_IfSelfStart = ConfigItem("Start", "IfSelfStart", False, BoolValidator())
start_IfRunDirectly = ConfigItem("Start", "IfRunDirectly", False, BoolValidator()) start_IfRunDirectly = ConfigItem("Start", "IfRunDirectly", False, BoolValidator())
start_IfMinimizeDirectly = ConfigItem(
"Start", "IfMinimizeDirectly", False, BoolValidator()
)
ui_IfShowTray = ConfigItem("UI", "IfShowTray", False, BoolValidator()) ui_IfShowTray = ConfigItem("UI", "IfShowTray", False, BoolValidator())
ui_IfToTray = ConfigItem("UI", "IfToTray", False, BoolValidator()) ui_IfToTray = ConfigItem("UI", "IfToTray", False, BoolValidator())
@@ -523,14 +793,37 @@ class GlobalConfig(QConfig):
ui_location = ConfigItem("UI", "location", "100x100") ui_location = ConfigItem("UI", "location", "100x100")
ui_maximized = ConfigItem("UI", "maximized", False, BoolValidator()) ui_maximized = ConfigItem("UI", "maximized", False, BoolValidator())
notify_SendTaskResultTime = OptionsConfigItem(
"Notify",
"SendTaskResultTime",
"不推送",
OptionsValidator(["不推送", "任何时刻", "仅失败时"]),
)
notify_IfSendStatistic = ConfigItem(
"Notify", "IfSendStatistic", False, BoolValidator()
)
notify_IfSendSixStar = ConfigItem("Notify", "IfSendSixStar", False, BoolValidator())
notify_IfPushPlyer = ConfigItem("Notify", "IfPushPlyer", False, BoolValidator()) notify_IfPushPlyer = ConfigItem("Notify", "IfPushPlyer", False, BoolValidator())
notify_IfSendMail = ConfigItem("Notify", "IfSendMail", False, BoolValidator()) notify_IfSendMail = ConfigItem("Notify", "IfSendMail", False, BoolValidator())
notify_IfSendErrorOnly = ConfigItem( notify_SMTPServerAddress = ConfigItem("Notify", "SMTPServerAddress", "")
"Notify", "IfSendErrorOnly", False, BoolValidator() notify_AuthorizationCode = ConfigItem("Notify", "AuthorizationCode", "")
notify_FromAddress = ConfigItem("Notify", "FromAddress", "")
notify_ToAddress = ConfigItem("Notify", "ToAddress", "")
notify_IfServerChan = ConfigItem("Notify", "IfServerChan", False, BoolValidator())
notify_ServerChanKey = ConfigItem("Notify", "ServerChanKey", "")
notify_ServerChanChannel = ConfigItem("Notify", "ServerChanChannel", "")
notify_ServerChanTag = ConfigItem("Notify", "ServerChanTag", "")
notify_IfCompanyWebHookBot = ConfigItem(
"Notify", "IfCompanyWebHookBot", False, BoolValidator()
) )
notify_MailAddress = ConfigItem("Notify", "MailAddress", "") notify_CompanyWebHookBotUrl = ConfigItem("Notify", "CompanyWebHookBotUrl", "")
notify_IfPushDeer = ConfigItem("Notify", "IfPushDeer", False, BoolValidator())
notify_IfPushDeerKey = ConfigItem("Notify", "PushDeerKey", "")
update_IfAutoUpdate = ConfigItem("Update", "IfAutoUpdate", False, BoolValidator()) update_IfAutoUpdate = ConfigItem("Update", "IfAutoUpdate", False, BoolValidator())
update_UpdateType = OptionsConfigItem(
"Update", "UpdateType", "main", OptionsValidator(["main", "dev"])
)
class QueueConfig(QConfig): class QueueConfig(QConfig):
@@ -538,6 +831,12 @@ class QueueConfig(QConfig):
queueSet_Name = ConfigItem("QueueSet", "Name", "") queueSet_Name = ConfigItem("QueueSet", "Name", "")
queueSet_Enabled = ConfigItem("QueueSet", "Enabled", False, BoolValidator()) queueSet_Enabled = ConfigItem("QueueSet", "Enabled", False, BoolValidator())
queueSet_AfterAccomplish = OptionsConfigItem(
"QueueSet",
"AfterAccomplish",
"None",
OptionsValidator(["None", "KillSelf", "Sleep", "Hibernate", "Shutdown"]),
)
time_TimeEnabled_0 = ConfigItem("Time", "TimeEnabled_0", False, BoolValidator()) time_TimeEnabled_0 = ConfigItem("Time", "TimeEnabled_0", False, BoolValidator())
time_TimeSet_0 = ConfigItem("Time", "TimeSet_0", "00:00") time_TimeSet_0 = ConfigItem("Time", "TimeSet_0", "00:00")
@@ -587,6 +886,15 @@ class MaaConfig(QConfig):
MaaSet_Name = ConfigItem("MaaSet", "Name", "") MaaSet_Name = ConfigItem("MaaSet", "Name", "")
MaaSet_Path = ConfigItem("MaaSet", "Path", ".", FolderValidator()) MaaSet_Path = ConfigItem("MaaSet", "Path", ".", FolderValidator())
RunSet_TaskTransitionMethod = OptionsConfigItem(
"RunSet",
"TaskTransitionMethod",
"ExitEmulator",
OptionsValidator(["NoAction", "ExitGame", "ExitEmulator"]),
)
RunSet_ProxyTimesLimit = RangeConfigItem(
"RunSet", "ProxyTimesLimit", 0, RangeValidator(0, 1024)
)
RunSet_AnnihilationTimeLimit = RangeConfigItem( RunSet_AnnihilationTimeLimit = RangeConfigItem(
"RunSet", "AnnihilationTimeLimit", 40, RangeValidator(1, 1024) "RunSet", "AnnihilationTimeLimit", 40, RangeValidator(1, 1024)
) )

View File

@@ -36,14 +36,14 @@ from qfluentwidgets import (
class _MainInfoBar: class _MainInfoBar:
"""信息通知栏""" """信息通知栏"""
def __init__(self, parent=None): def __init__(self, main_window=None):
self.parent = parent self.main_window = main_window
def push_info_bar(self, mode: str, title: str, content: str, time: int): def push_info_bar(self, mode: str, title: str, content: str, time: int):
"""推送到信息通知栏""" """推送到信息通知栏"""
if self.parent is None: if self.main_window is None:
logger.error("信息通知栏未设置父窗口") logger.error("信息通知栏未设置父窗口")
return None return None
@@ -55,7 +55,7 @@ class _MainInfoBar:
isClosable=True, isClosable=True,
position=InfoBarPosition.TOP_RIGHT, position=InfoBarPosition.TOP_RIGHT,
duration=time, duration=time,
parent=self.parent, parent=self.main_window,
) )
elif mode == "warning": elif mode == "warning":
InfoBar.warning( InfoBar.warning(
@@ -65,7 +65,7 @@ class _MainInfoBar:
isClosable=True, isClosable=True,
position=InfoBarPosition.TOP_RIGHT, position=InfoBarPosition.TOP_RIGHT,
duration=time, duration=time,
parent=self.parent, parent=self.main_window,
) )
elif mode == "error": elif mode == "error":
InfoBar.error( InfoBar.error(
@@ -75,7 +75,7 @@ class _MainInfoBar:
isClosable=True, isClosable=True,
position=InfoBarPosition.TOP_RIGHT, position=InfoBarPosition.TOP_RIGHT,
duration=time, duration=time,
parent=self.parent, parent=self.main_window,
) )
elif mode == "info": elif mode == "info":
InfoBar.info( InfoBar.info(
@@ -85,7 +85,7 @@ class _MainInfoBar:
isClosable=True, isClosable=True,
position=InfoBarPosition.TOP_RIGHT, position=InfoBarPosition.TOP_RIGHT,
duration=time, duration=time,
parent=self.parent, parent=self.main_window,
) )

View File

@@ -28,6 +28,7 @@ v4.2
from loguru import logger from loguru import logger
from PySide6.QtCore import QThread, QObject, Signal from PySide6.QtCore import QThread, QObject, Signal
from qfluentwidgets import Dialog from qfluentwidgets import Dialog
import json
from pathlib import Path from pathlib import Path
from datetime import datetime from datetime import datetime
from typing import Dict, Union from typing import Dict, Union
@@ -35,6 +36,7 @@ from typing import Dict, Union
from .config import Config from .config import Config
from .main_info_bar import MainInfoBar from .main_info_bar import MainInfoBar
from app.models import MaaManager from app.models import MaaManager
from app.services import System
class Task(QThread): class Task(QThread):
@@ -52,10 +54,7 @@ class Task(QThread):
accomplish = Signal(list) accomplish = Signal(list)
def __init__( def __init__(
self, self, mode: str, name: str, info: Dict[str, Dict[str, Union[str, int, bool]]]
mode: str,
name: str,
info: Dict[str, Dict[str, Union[str, int, bool]]],
): ):
super(Task, self).__init__() super(Task, self).__init__()
@@ -92,41 +91,41 @@ class Task(QThread):
else: else:
self.member_dict = self.search_member() self.member_dict = self.search_member()
self.task_list = [ self.task_dict = [
[value, "等待"] [value, "等待"]
for _, value in self.info["Queue"].items() for _, value in self.info["Queue"].items()
if value != "禁用" if value != "禁用"
] ]
self.create_task_list.emit(self.task_list) self.create_task_list.emit(self.task_dict)
for i in range(len(self.task_list)): for i in range(len(self.task_dict)):
if self.isInterruptionRequested(): if self.isInterruptionRequested():
break break
self.task_list[i][1] = "运行" self.task_dict[i][1] = "运行"
self.update_task_list.emit(self.task_list) self.update_task_list.emit(self.task_dict)
if self.task_list[i][0] in Config.running_list: if self.task_dict[i][0] in Config.running_list:
self.task_list[i][1] = "跳过" self.task_dict[i][1] = "跳过"
self.update_task_list.emit(self.task_list) self.update_task_list.emit(self.task_dict)
logger.info(f"跳过任务:{self.task_list[i][0]}") logger.info(f"跳过任务:{self.task_dict[i][0]}")
self.push_info_bar.emit( self.push_info_bar.emit(
"info", "跳过任务", self.task_list[i][0], 3000 "info", "跳过任务", self.task_dict[i][0], 3000
) )
continue continue
Config.running_list.append(self.task_list[i][0]) Config.running_list.append(self.task_dict[i][0])
logger.info(f"任务开始:{self.task_list[i][0]}") logger.info(f"任务开始:{self.task_dict[i][0]}")
self.push_info_bar.emit("info", "任务开始", self.task_list[i][0], 3000) self.push_info_bar.emit("info", "任务开始", self.task_dict[i][0], 3000)
if self.member_dict[self.task_list[i][0]][0] == "Maa": if self.member_dict[self.task_dict[i][0]][0] == "Maa":
self.task = MaaManager( self.task = MaaManager(
self.mode[0:4], self.mode[0:4],
self.member_dict[self.task_list[i][0]][1], self.member_dict[self.task_dict[i][0]][1],
) )
self.task.question.connect(self.question.emit) self.task.question.connect(self.question.emit)
@@ -138,7 +137,7 @@ class Task(QThread):
self.task.update_log_text.connect(self.update_log_text.emit) self.task.update_log_text.connect(self.update_log_text.emit)
self.task.update_user_info.connect( self.task.update_user_info.connect(
lambda modes, uids, days, lasts, notes, numbs: self.update_user_info.emit( lambda modes, uids, days, lasts, notes, numbs: self.update_user_info.emit(
self.member_dict[self.task_list[i][0]][1], self.member_dict[self.task_dict[i][0]][1],
modes, modes,
uids, uids,
days, days,
@@ -148,16 +147,16 @@ class Task(QThread):
) )
) )
self.task.accomplish.connect( self.task.accomplish.connect(
lambda log: self.save_log(self.task_list[i][0], log) lambda log: self.task_accomplish(self.task_dict[i][0], log)
) )
self.task.run() self.task.run()
Config.running_list.remove(self.task_list[i][0]) Config.running_list.remove(self.task_dict[i][0])
self.task_list[i][1] = "完成" self.task_dict[i][1] = "完成"
logger.info(f"任务完成:{self.task_list[i][0]}") logger.info(f"任务完成:{self.task_dict[i][0]}")
self.push_info_bar.emit("info", "任务完成", self.task_list[i][0], 3000) self.push_info_bar.emit("info", "任务完成", self.task_dict[i][0], 3000)
self.accomplish.emit(self.logs) self.accomplish.emit(self.logs)
@@ -174,13 +173,14 @@ class Task(QThread):
return member_dict return member_dict
def save_log(self, name: str, log: dict): def task_accomplish(self, name: str, log: dict):
"""保存保存任务结果""" """保存保存任务结果"""
self.logs.append([name, log]) self.logs.append([name, log])
self.task.deleteLater()
class TaskManager(QObject): class _TaskManager(QObject):
"""业务调度器""" """业务调度器"""
create_gui = Signal(Task) create_gui = Signal(Task)
@@ -188,16 +188,16 @@ class TaskManager(QObject):
push_info_bar = Signal(str, str, str, int) push_info_bar = Signal(str, str, str, int)
def __init__(self): def __init__(self):
super(TaskManager, self).__init__() super(_TaskManager, self).__init__()
self.task_list: Dict[str, Task] = {} self.task_dict: Dict[str, Task] = {}
def add_task( def add_task(
self, mode: str, name: str, info: Dict[str, Dict[str, Union[str, int, bool]]] self, mode: str, name: str, info: Dict[str, Dict[str, Union[str, int, bool]]]
): ):
"""添加任务""" """添加任务"""
if name in Config.running_list or name in self.task_list: if name in Config.running_list or name in self.task_dict:
logger.warning(f"任务已存在:{name}") logger.warning(f"任务已存在:{name}")
MainInfoBar.push_info_bar("warning", "任务已存在", name, 5000) MainInfoBar.push_info_bar("warning", "任务已存在", name, 5000)
@@ -207,23 +207,23 @@ class TaskManager(QObject):
MainInfoBar.push_info_bar("info", "任务开始", name, 3000) MainInfoBar.push_info_bar("info", "任务开始", name, 3000)
Config.running_list.append(name) Config.running_list.append(name)
self.task_list[name] = Task(mode, name, info) self.task_dict[name] = Task(mode, name, info)
self.task_list[name].question.connect( self.task_dict[name].question.connect(
lambda title, content: self.push_dialog(name, title, content) lambda title, content: self.push_dialog(name, title, content)
) )
self.task_list[name].push_info_bar.connect(MainInfoBar.push_info_bar) self.task_dict[name].push_info_bar.connect(MainInfoBar.push_info_bar)
self.task_list[name].update_user_info.connect(Config.change_user_info) self.task_dict[name].update_user_info.connect(Config.change_user_info)
self.task_list[name].accomplish.connect( self.task_dict[name].accomplish.connect(
lambda logs: self.remove_task(name, logs) lambda logs: self.remove_task(mode, name, logs)
) )
if "窗口" in mode: if "调度台" in mode:
self.create_gui.emit(self.task_list[name]) self.create_gui.emit(self.task_dict[name])
elif "窗口" in mode: elif "调度台" in mode:
self.connect_gui.emit(self.task_list[name]) self.connect_gui.emit(self.task_dict[name])
self.task_list[name].start() self.task_dict[name].start()
def stop_task(self, name: str): def stop_task(self, name: str):
"""中止任务""" """中止任务"""
@@ -233,26 +233,28 @@ class TaskManager(QObject):
if name == "ALL": if name == "ALL":
for name in self.task_list: for name in self.task_dict:
self.task_list[name].task.requestInterruption() self.task_dict[name].task.requestInterruption()
self.task_list[name].requestInterruption() self.task_dict[name].requestInterruption()
self.task_list[name].quit() self.task_dict[name].quit()
self.task_list[name].wait() self.task_dict[name].wait()
elif name in self.task_list: elif name in self.task_dict:
self.task_list[name].task.requestInterruption() self.task_dict[name].task.requestInterruption()
self.task_list[name].requestInterruption() self.task_dict[name].requestInterruption()
self.task_list[name].quit() self.task_dict[name].quit()
self.task_list[name].wait() self.task_dict[name].wait()
def remove_task(self, name: str, logs: str): def remove_task(self, mode: str, name: str, logs: str):
"""移除任务标记""" """任务结束后的处理"""
logger.info(f"任务结束:{name}") logger.info(f"任务结束:{name}")
MainInfoBar.push_info_bar("info", "任务结束", name, 3000) MainInfoBar.push_info_bar("info", "任务结束", name, 3000)
self.task_dict[name].deleteLater()
if len(logs) > 0: if len(logs) > 0:
time = logs[0][1]["Time"] time = logs[0][1]["Time"]
history = "" history = ""
@@ -271,9 +273,16 @@ class TaskManager(QObject):
}, },
) )
self.task_list.pop(name) self.task_dict.pop(name)
Config.running_list.remove(name) Config.running_list.remove(name)
if "调度队列" in name and "人工排查" not in mode:
with (Config.app_path / f"config/QueueConfig/{name}.json").open(
"r", encoding="utf-8"
) as f:
info = json.load(f)
System.set_power(info["QueueSet"]["AfterAccomplish"])
def push_dialog(self, name: str, title: str, content: str): def push_dialog(self, name: str, title: str, content: str):
"""推送对话框""" """推送对话框"""
@@ -281,7 +290,7 @@ class TaskManager(QObject):
choice.yesButton.setText("") choice.yesButton.setText("")
choice.cancelButton.setText("") choice.cancelButton.setText("")
self.task_list[name].question_response.emit(bool(choice.exec_())) self.task_dict[name].question_response.emit(bool(choice.exec_()))
Task_manager = TaskManager() TaskManager = _TaskManager()

View File

@@ -33,16 +33,13 @@ from datetime import datetime
import pyautogui import pyautogui
from .config import Config from .config import Config
from .task_manager import Task_manager from .task_manager import TaskManager
from app.services import System from app.services import System
class MainTimer(QWidget): class _MainTimer(QWidget):
def __init__( def __init__(self, parent=None):
self,
parent=None,
):
super().__init__(parent) super().__init__(parent)
self.if_FailSafeException = False self.if_FailSafeException = False
@@ -81,30 +78,35 @@ class MainTimer(QWidget):
): ):
logger.info(f"定时任务:{name}") logger.info(f"定时任务:{name}")
Task_manager.add_task("自动代理_新窗口", name, info) TaskManager.add_task("自动代理_新调度台", name, info)
def set_silence(self): def set_silence(self):
"""设置静默模式""" """设置静默模式"""
windows = System.get_window_info() if (
if any( Config.global_config.get(Config.global_config.function_IfSilence)
str(emulator_path) in window and Config.global_config.get(Config.global_config.function_BossKey) != ""
for window in windows
for emulator_path in Config.silence_list
): ):
try:
pyautogui.hotkey( windows = System.get_window_info()
*[ if any(
_.strip().lower() str(emulator_path) in window
for _ in Config.global_config.get( for window in windows
Config.global_config.function_BossKey for emulator_path in Config.silence_list
).split("+") ):
] try:
) pyautogui.hotkey(
except pyautogui.FailSafeException as e: *[
if not self.if_FailSafeException: _.strip().lower()
logger.warning(f"FailSafeException: {e}") for _ in Config.global_config.get(
self.if_FailSafeException = True Config.global_config.function_BossKey
).split("+")
]
)
except pyautogui.FailSafeException as e:
if not self.if_FailSafeException:
logger.warning(f"FailSafeException: {e}")
self.if_FailSafeException = True
def search_queue(self) -> list: def search_queue(self) -> list:
"""搜索所有调度队列实例""" """搜索所有调度队列实例"""
@@ -120,4 +122,4 @@ class MainTimer(QWidget):
return queue_list return queue_list
Main_timer = MainTimer() MainTimer = _MainTimer()

File diff suppressed because it is too large Load Diff

View File

@@ -25,18 +25,32 @@ v4.2
作者DLmaster_361 作者DLmaster_361
""" """
from PySide6.QtWidgets import QWidget
from PySide6.QtCore import Signal
import requests
from loguru import logger
from plyer import notification from plyer import notification
import re
import smtplib import smtplib
from email.mime.text import MIMEText from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.header import Header from email.header import Header
from email.utils import formataddr from email.utils import formataddr
from serverchan_sdk import sc_send
from app.core import Config from app.core import Config
from app.services.security import Crypto
class Notification: class Notification(QWidget):
def push_notification(self, title, message, ticker, t): push_info_bar = Signal(str, str, str, int)
def __init__(self, parent=None):
super().__init__(parent)
def push_plyer(self, title, message, ticker, t):
"""推送系统通知""" """推送系统通知"""
if Config.global_config.get(Config.global_config.notify_IfPushPlyer): if Config.global_config.get(Config.global_config.notify_IfPushPlyer):
@@ -53,48 +67,181 @@ class Notification:
return True return True
def send_mail(self, title, content): def send_mail(self, mode, title, content) -> None:
"""使用官方专用邮箱推送邮件通知""" """推送邮件通知"""
# 声明此邮箱为AUTO_MAA项目组资产未经授权不得私自使用
# 注意此声明注释只有使用者更换发信邮箱时才能删除本条规则优先级高于GPLv3
if Config.global_config.get(Config.global_config.notify_IfSendMail): if Config.global_config.get(Config.global_config.notify_IfSendMail):
# 第三方 SMTP 服务配置 if (
mail_host = "smtp.163.com" # 设置服务器 Config.global_config.get(Config.global_config.notify_SMTPServerAddress)
mail_sender = "AUTO_MAA_server@163.com" # 用户名 == ""
mail_key = "SYrq87nDLD4RNB5T" # 授权码 24/11/15 or Config.global_config.get(
Config.global_config.notify_AuthorizationCode
# 定义邮件正文
message = MIMEText(content, "plain", "utf-8")
message["From"] = formataddr(
(
Header("AUTO_MAA通知服务", "utf-8").encode(),
"AUTO_MAA_server@163.com",
) )
) # 发件人显示的名字 == ""
message["To"] = formataddr( or not bool(
( re.match(
Header("AUTO_MAA用户", "utf-8").encode(), r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$",
Config.global_config.get(Config.global_config.notify_MailAddress), Config.global_config.get(
Config.global_config.notify_FromAddress
),
)
) )
) # 收件人显示的名字 or not bool(
message["Subject"] = Header(title, "utf-8") re.match(
r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$",
Config.global_config.get(Config.global_config.notify_ToAddress),
)
)
):
logger.error(
"请正确设置邮件通知的SMTP服务器地址、授权码、发件人地址和收件人地址"
)
self.push_info_bar.emit(
"error",
"邮件通知推送异常",
"请正确设置邮件通知的SMTP服务器地址、授权码、发件人地址和收件人地址",
-1,
)
return None
try: try:
smtpObj = smtplib.SMTP_SSL(mail_host, 465) # 465为SMTP_SSL默认端口 # 定义邮件正文
smtpObj.login(mail_sender, mail_key) if mode == "文本":
message = MIMEText(content, "plain", "utf-8")
elif mode == "网页":
message = MIMEMultipart("alternative")
message["From"] = formataddr(
(
Header("AUTO_MAA通知服务", "utf-8").encode(),
Config.global_config.get(
Config.global_config.notify_FromAddress
),
)
) # 发件人显示的名字
message["To"] = formataddr(
(
Header("AUTO_MAA用户", "utf-8").encode(),
Config.global_config.get(Config.global_config.notify_ToAddress),
)
) # 收件人显示的名字
message["Subject"] = Header(title, "utf-8")
if mode == "网页":
message.attach(MIMEText(content, "html", "utf-8"))
smtpObj = smtplib.SMTP_SSL(
Config.global_config.get(
Config.global_config.notify_SMTPServerAddress
),
465,
)
smtpObj.login(
Config.global_config.get(Config.global_config.notify_FromAddress),
Crypto.win_decryptor(
Config.global_config.get(
Config.global_config.notify_AuthorizationCode
)
),
)
smtpObj.sendmail( smtpObj.sendmail(
mail_sender, Config.global_config.get(Config.global_config.notify_FromAddress),
Config.global_config.get(Config.global_config.notify_MailAddress), Config.global_config.get(Config.global_config.notify_ToAddress),
message.as_string(), message.as_string(),
) )
return True
except smtplib.SMTPException as e:
return f"发送邮件时出错:\n{e}"
finally:
smtpObj.quit() 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酱推送通知"""
if Config.global_config.get(Config.global_config.notify_IfServerChan):
send_key = Config.global_config.get(
Config.global_config.notify_ServerChanKey
)
option = {}
is_valid = lambda s: s == "" or (
s == "|".join(s.split("|")) and (s.count("|") == 0 or all(s.split("|")))
)
"""
is_valid => True, 如果启用的话需要正确设置Tag和Channel。
允许空的Tag和Channel即不启用但不允许例如a||b|a|ba|b|||||
"""
send_tag = Config.global_config.get(
Config.global_config.notify_ServerChanTag
)
send_channel = Config.global_config.get(
Config.global_config.notify_ServerChanChannel
)
if is_valid(send_tag):
option["tags"] = send_tag
else:
option["tags"] = ""
logger.warning("请正确设置Auto_MAA中ServerChan的Tag。")
self.push_info_bar.emit(
"warning",
"Server酱通知推送异常",
"请正确设置Auto_MAA中ServerChan的Tag。",
-1,
)
if is_valid(send_channel):
option["channel"] = send_channel
else:
option["channel"] = ""
logger.warning("请正确设置Auto_MAA中ServerChan的Channel。")
self.push_info_bar.emit(
"warning",
"Server酱通知推送异常",
"请正确设置Auto_MAA中ServerChan的Channel。",
-1,
)
response = sc_send(send_key, title, content, option)
if response["code"] == 0:
logger.info("Server酱推送通知成功")
return True
else:
logger.info("Server酱推送通知失败")
logger.error(response)
self.push_info_bar.emit(
"error",
"Server酱通知推送失败",
f'使用Server酱推送通知时出错\n{response["data"]['error']}',
-1,
)
return f'使用Server酱推送通知时出错\n{response["data"]['error']}'
def CompanyWebHookBotPush(self, title, content):
"""使用企业微信群机器人推送通知"""
if Config.global_config.get(Config.global_config.notify_IfCompanyWebHookBot):
content = f"{title}\n{content}"
data = {"msgtype": "text", "text": {"content": content}}
response = requests.post(
url=Config.global_config.get(
Config.global_config.notify_CompanyWebHookBotUrl
),
json=data,
)
if response.json()["errcode"] == 0:
logger.info("企业微信群机器人推送通知成功")
return True
else:
logger.info("企业微信群机器人推送通知失败")
logger.error(response.json())
self.push_info_bar.emit(
"error",
"企业微信群机器人通知推送失败",
f'使用企业微信群机器人推送通知时出错:\n{response.json()["errmsg"]}',
-1,
)
return (
f'使用企业微信群机器人推送通知时出错:\n{response.json()["errmsg"]}'
)
Notify = Notification() Notify = Notification()

View File

@@ -30,6 +30,8 @@ import sqlite3
import hashlib import hashlib
import random import random
import secrets import secrets
import base64
import win32crypt
from pathlib import Path from pathlib import Path
from Crypto.Cipher import AES from Crypto.Cipher import AES
from Crypto.PublicKey import RSA from Crypto.PublicKey import RSA
@@ -83,8 +85,8 @@ class CryptoHandler:
private_key_local = AES_key.encrypt(pad(private_key.exportKey(), 32)) private_key_local = AES_key.encrypt(pad(private_key.exportKey(), 32))
(Config.app_path / "data/key/private_key.bin").write_bytes(private_key_local) (Config.app_path / "data/key/private_key.bin").write_bytes(private_key_local)
def encryptx(self, note: str) -> bytes: def AUTO_encryptor(self, note: str) -> bytes:
"""加密数据""" """使用AUTO_MAA的算法加密数据"""
# 读取RSA公钥 # 读取RSA公钥
public_key_local = RSA.import_key( public_key_local = RSA.import_key(
@@ -95,8 +97,8 @@ class CryptoHandler:
encrypted = cipher.encrypt(note.encode("utf-8")) encrypted = cipher.encrypt(note.encode("utf-8"))
return encrypted return encrypted
def decryptx(self, note: bytes, PASSWORD: str) -> str: def AUTO_decryptor(self, note: bytes, PASSWORD: str) -> str:
"""解密数据""" """使用AUTO_MAA的算法解密数据"""
# 读入RSA私钥密文、盐与校验哈希值 # 读入RSA私钥密文、盐与校验哈希值
private_key_local = ( private_key_local = (
@@ -150,7 +152,9 @@ class CryptoHandler:
# 使用旧管理密钥解密 # 使用旧管理密钥解密
user_data["Password"] = [] user_data["Password"] = []
for i in range(len(data)): for i in range(len(data)):
user_data["Password"].append(self.decryptx(data[i][12], PASSWORD_old)) user_data["Password"].append(
self.AUTO_decryptor(data[i][12], PASSWORD_old)
)
cur.close() cur.close()
db.close() db.close()
@@ -169,7 +173,7 @@ class CryptoHandler:
cur.execute( cur.execute(
"UPDATE adminx SET password = ? WHERE mode = ? AND uid = ?", "UPDATE adminx SET password = ? WHERE mode = ? AND uid = ?",
( (
self.encryptx(user_data["Password"][i]), self.AUTO_encryptor(user_data["Password"][i]),
data[i][15], data[i][15],
data[i][16], data[i][16],
), ),
@@ -181,6 +185,27 @@ class CryptoHandler:
cur.close() cur.close()
db.close() db.close()
def win_encryptor(
self, note: str, description: str = None, entropy: bytes = None
) -> str:
"""使用Windows DPAPI加密数据"""
encrypted = win32crypt.CryptProtectData(
note.encode("utf-8"), description, entropy, None, None, 0
)
return base64.b64encode(encrypted).decode("utf-8")
def win_decryptor(self, note: str, entropy: bytes = None) -> str:
"""使用Windows DPAPI解密数据"""
if note == "":
return ""
decrypted = win32crypt.CryptUnprotectData(
base64.b64decode(note), entropy, None, None, 0
)
return decrypted[1].decode("utf-8")
def search_member(self) -> List[Dict[str, Union[Path, list]]]: def search_member(self) -> List[Dict[str, Union[Path, list]]]:
"""搜索所有脚本实例及其用户数据库路径""" """搜索所有脚本实例及其用户数据库路径"""
@@ -197,7 +222,9 @@ class CryptoHandler:
def check_PASSWORD(self, PASSWORD: str) -> bool: def check_PASSWORD(self, PASSWORD: str) -> bool:
"""验证管理密钥""" """验证管理密钥"""
return bool(self.decryptx(self.encryptx(""), PASSWORD) != "管理密钥错误") return bool(
self.AUTO_decryptor(self.AUTO_encryptor(""), PASSWORD) != "管理密钥错误"
)
Crypto = CryptoHandler() Crypto = CryptoHandler()

View File

@@ -25,26 +25,33 @@ v4.2
作者DLmaster_361 作者DLmaster_361
""" """
from loguru import logger
from PySide6.QtWidgets import QWidget
import sys
import ctypes import ctypes
import win32gui import win32gui
import win32process import win32process
import winreg import winreg
import psutil import psutil
import subprocess
from pathlib import Path
from app.core import Config from app.core import Config
class SystemHandler: class _SystemHandler:
ES_CONTINUOUS = 0x80000000 ES_CONTINUOUS = 0x80000000
ES_SYSTEM_REQUIRED = 0x00000001 ES_SYSTEM_REQUIRED = 0x00000001
def __init__(self): def __init__(self, main_window: QWidget = None):
self.main_window = main_window
self.set_Sleep() self.set_Sleep()
self.set_SelfStart() self.set_SelfStart()
def set_Sleep(self): def set_Sleep(self) -> None:
"""同步系统休眠状态""" """同步系统休眠状态"""
if Config.global_config.get(Config.global_config.function_IfAllowSleep): if Config.global_config.get(Config.global_config.function_IfAllowSleep):
@@ -56,7 +63,7 @@ class SystemHandler:
# 恢复系统电源状态 # 恢复系统电源状态
ctypes.windll.kernel32.SetThreadExecutionState(self.ES_CONTINUOUS) ctypes.windll.kernel32.SetThreadExecutionState(self.ES_CONTINUOUS)
def set_SelfStart(self): def set_SelfStart(self) -> None:
"""同步开机自启""" """同步开机自启"""
if ( if (
@@ -84,7 +91,61 @@ class SystemHandler:
winreg.DeleteValue(key, "AUTO_MAA") winreg.DeleteValue(key, "AUTO_MAA")
winreg.CloseKey(key) winreg.CloseKey(key)
def is_startup(self): def set_power(self, mode) -> None:
if sys.platform.startswith("win"):
if mode == "None":
logger.info("不执行系统电源操作")
elif mode == "Shutdown":
logger.info("执行关机操作")
subprocess.run(["shutdown", "/s", "/t", "0"])
elif mode == "Hibernate":
logger.info("执行休眠操作")
subprocess.run(["shutdown", "/h"])
elif mode == "Sleep":
logger.info("执行睡眠操作")
subprocess.run(
["rundll32.exe", "powrprof.dll,SetSuspendState", "0,1,0"]
)
elif mode == "KillSelf":
self.main_window.close()
elif sys.platform.startswith("linux"):
if mode == "None":
logger.info("不执行系统电源操作")
elif mode == "Shutdown":
logger.info("执行关机操作")
subprocess.run(["shutdown", "-h", "now"])
elif mode == "Hibernate":
logger.info("执行休眠操作")
subprocess.run(["systemctl", "hibernate"])
elif mode == "Sleep":
logger.info("执行睡眠操作")
subprocess.run(["systemctl", "suspend"])
elif mode == "KillSelf":
self.main_window.close()
def is_startup(self) -> bool:
"""判断程序是否已经开机自启""" """判断程序是否已经开机自启"""
key = winreg.OpenKey( key = winreg.OpenKey(
@@ -102,7 +163,7 @@ class SystemHandler:
winreg.CloseKey(key) winreg.CloseKey(key)
return False return False
def get_window_info(self): def get_window_info(self) -> list:
"""获取当前窗口信息""" """获取当前窗口信息"""
def callback(hwnd, window_info): def callback(hwnd, window_info):
@@ -116,5 +177,29 @@ class SystemHandler:
win32gui.EnumWindows(callback, window_info) win32gui.EnumWindows(callback, window_info)
return window_info return window_info
def kill_process(self, path: Path) -> None:
"""根据路径中止进程"""
System = SystemHandler() for pid in self.search_pids(path):
killprocess = subprocess.Popen(
f"taskkill /F /T /PID {pid}",
shell=True,
creationflags=subprocess.CREATE_NO_WINDOW,
)
killprocess.wait()
def search_pids(self, path: Path) -> list:
"""根据路径查找进程PID"""
pids = []
for proc in psutil.process_iter(["pid", "exe"]):
try:
if proc.info["exe"] and proc.info["exe"].lower() == str(path).lower():
pids.append(proc.info["pid"])
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
# 进程可能在此期间已结束或无法访问,忽略这些异常
pass
return pids
System = _SystemHandler()

View File

@@ -25,9 +25,9 @@ v4.2
作者DLmaster_361 作者DLmaster_361
""" """
from PySide6.QtCore import Qt, QTime
from PySide6.QtGui import QIcon
from PySide6.QtWidgets import QWidget, QHBoxLayout from PySide6.QtWidgets import QWidget, QHBoxLayout
from PySide6.QtCore import Qt, QTime, QEvent
from PySide6.QtGui import QIcon, QPixmap, QPainter, QPainterPath
from qfluentwidgets import ( from qfluentwidgets import (
LineEdit, LineEdit,
PasswordLineEdit, PasswordLineEdit,
@@ -39,19 +39,29 @@ from qfluentwidgets import (
Signal, Signal,
ComboBox, ComboBox,
CheckBox, CheckBox,
IconWidget,
FluentIcon,
CardWidget,
BodyLabel,
qconfig, qconfig,
ConfigItem, ConfigItem,
TimeEdit, TimeEdit,
OptionsConfigItem, OptionsConfigItem,
TeachingTip,
TransparentToolButton,
TeachingTipTailPosition,
) )
from qfluentwidgets.common.overload import singledispatchmethod
import os
from typing import Optional, Union, List
from typing import Union, List from app.services import Crypto
class InputMessageBox(MessageBoxBase): class LineEditMessageBox(MessageBoxBase):
"""输入对话框""" """输入对话框"""
def __init__(self, parent, title: str, content: str, mode: str, list: list = None): def __init__(self, parent, title: str, content: str, mode: str):
super().__init__(parent) super().__init__(parent)
self.title = SubtitleLabel(title) self.title = SubtitleLabel(title)
@@ -60,10 +70,6 @@ class InputMessageBox(MessageBoxBase):
self.input.setClearButtonEnabled(True) self.input.setClearButtonEnabled(True)
elif mode == "密码": elif mode == "密码":
self.input = PasswordLineEdit() self.input = PasswordLineEdit()
elif mode == "选择":
self.input = ComboBox()
self.input.addItems(list)
self.input.setCurrentIndex(-1)
self.input.setPlaceholderText(content) self.input.setPlaceholderText(content)
@@ -72,8 +78,8 @@ class InputMessageBox(MessageBoxBase):
self.viewLayout.addWidget(self.input) self.viewLayout.addWidget(self.input)
class SetMessageBox(MessageBoxBase): class ComboBoxMessageBox(MessageBoxBase):
"""输入对话框""" """选择对话框"""
def __init__(self, parent, title: str, content: List[str], list: List[List[str]]): def __init__(self, parent, title: str, content: List[str], list: List[List[str]]):
super().__init__(parent) super().__init__(parent)
@@ -98,7 +104,7 @@ class SetMessageBox(MessageBoxBase):
class LineEditSettingCard(SettingCard): class LineEditSettingCard(SettingCard):
"""Setting card with switch button""" """Setting card with LineEdit"""
textChanged = Signal(str) textChanged = Signal(str)
@@ -138,7 +144,49 @@ class LineEditSettingCard(SettingCard):
self.LineEdit.setText(content) self.LineEdit.setText(content)
class PasswordLineEditSettingCard(SettingCard):
"""Setting card with PasswordLineEdit"""
textChanged = Signal(str)
def __init__(
self,
text,
icon: Union[str, QIcon, FluentIconBase],
title,
content=None,
configItem: ConfigItem = None,
parent=None,
):
super().__init__(icon, title, content, parent)
self.configItem = configItem
self.LineEdit = PasswordLineEdit(self)
self.LineEdit.setMinimumWidth(250)
self.LineEdit.setPlaceholderText(text)
if configItem:
self.setValue(qconfig.get(configItem))
configItem.valueChanged.connect(self.setValue)
self.hBoxLayout.addWidget(self.LineEdit, 0, Qt.AlignRight)
self.hBoxLayout.addSpacing(16)
self.LineEdit.textChanged.connect(self.__textChanged)
def __textChanged(self, content: str):
self.setValue(Crypto.win_encryptor(content))
self.textChanged.emit(content)
def setValue(self, content: str):
if self.configItem:
qconfig.set(self.configItem, content)
self.LineEdit.setText(Crypto.win_decryptor(content))
class SpinBoxSettingCard(SettingCard): class SpinBoxSettingCard(SettingCard):
"""Setting card with SpinBox"""
textChanged = Signal(int) textChanged = Signal(int)
@@ -280,3 +328,207 @@ class TimeEditSettingCard(SettingCard):
qconfig.set(self.configItem_time, value) qconfig.set(self.configItem_time, value)
self.TimeEdit.setTime(QTime.fromString(value, "HH:mm")) self.TimeEdit.setTime(QTime.fromString(value, "HH:mm"))
class StatefulItemCard(CardWidget):
def __init__(self, item: list, parent=None):
super().__init__(parent)
self.Layout = QHBoxLayout(self)
self.Label = BodyLabel(item[0], self)
self.icon = IconWidget(FluentIcon.MORE, self)
self.icon.setFixedSize(16, 16)
self.update_status(item[1])
self.Layout.addWidget(self.icon)
self.Layout.addWidget(self.Label)
self.Layout.addStretch(1)
def update_status(self, status: str):
if status == "完成":
self.icon.setIcon(FluentIcon.ACCEPT)
self.Label.setTextColor("#0eb840", "#0eb840")
elif status == "等待":
self.icon.setIcon(FluentIcon.MORE)
self.Label.setTextColor("#161823", "#e3f9fd")
elif status == "运行":
self.icon.setIcon(FluentIcon.PLAY)
self.Label.setTextColor("#177cb0", "#70f3ff")
elif status == "跳过":
self.icon.setIcon(FluentIcon.REMOVE)
self.Label.setTextColor("#75878a", "#7397ab")
elif status == "异常":
self.icon.setIcon(FluentIcon.CLOSE)
self.Label.setTextColor("#ff2121", "#ff2121")
class QuantifiedItemCard(CardWidget):
def __init__(self, item: list, parent=None):
super().__init__(parent)
self.Layout = QHBoxLayout(self)
self.Name = BodyLabel(item[0], self)
self.Numb = BodyLabel(str(item[1]), self)
self.Layout.addWidget(self.Name)
self.Layout.addStretch(1)
self.Layout.addWidget(self.Numb)
class IconButton(TransparentToolButton):
"""包含下拉框的自定义设置卡片类。"""
@singledispatchmethod
def __init__(self, parent: QWidget = None):
TransparentToolButton.__init__(self, parent)
self._tooltip: Optional[TeachingTip] = None
@__init__.register
def _(self, icon: Union[str, QIcon, FluentIconBase], parent: QWidget = None):
self.__init__(parent)
self.setIcon(icon)
@__init__.register
def _(
self,
icon: Union[str, QIcon, FluentIconBase],
isTooltip: bool,
tip_title: str,
tip_content: str,
parent: QWidget = None,
):
self.__init__(parent)
self.setIcon(icon)
# 处理工具提示
if isTooltip:
self.installEventFilter(self)
self.tip_title: str = tip_title
self.tip_content: str = tip_content
def eventFilter(self, obj, event: QEvent) -> bool:
"""处理鼠标事件。"""
if event.type() == QEvent.Type.Enter:
self._show_tooltip()
elif event.type() == QEvent.Type.Leave:
self._hide_tooltip()
return super().eventFilter(obj, event)
def _show_tooltip(self) -> None:
"""显示工具提示。"""
self._tooltip = TeachingTip.create(
target=self,
title=self.tip_title,
content=self.tip_content,
tailPosition=TeachingTipTailPosition.RIGHT,
isClosable=False,
duration=-1,
parent=self,
)
# 设置偏移
if self._tooltip:
tooltip_pos = self.mapToGlobal(self.rect().topRight())
tooltip_pos.setX(
tooltip_pos.x() - self._tooltip.size().width() - 40
) # 水平偏移
tooltip_pos.setY(
tooltip_pos.y() - self._tooltip.size().height() / 2 + 35
) # 垂直偏移
self._tooltip.move(tooltip_pos)
def _hide_tooltip(self) -> None:
"""隐藏工具提示。"""
if self._tooltip:
self._tooltip.close()
self._tooltip = None
def __hash__(self):
return id(self)
def __eq__(self, other):
if not isinstance(other, self.__class__):
return NotImplemented
return self is other
class Banner(QWidget):
"""展示带有圆角的固定大小横幅小部件"""
def __init__(self, image_path: str = None, parent=None):
QWidget.__init__(self, parent)
self.image_path = None
self.banner_image = None
self.scaled_image = None
if image_path:
self.set_banner_image(image_path)
def set_banner_image(self, image_path: str):
"""设置横幅图片"""
self.image_path = image_path
self.banner_image = self.load_banner_image(image_path)
self.update_scaled_image()
def load_banner_image(self, image_path: str) -> QPixmap:
"""加载横幅图片,或创建渐变备用图片"""
if os.path.isfile(image_path):
return QPixmap(image_path)
return self._create_fallback_image()
def _create_fallback_image(self):
"""创建渐变备用图片"""
fallback_image = QPixmap(2560, 1280) # 使用原始图片的大小
fallback_image.fill(Qt.GlobalColor.gray)
return fallback_image
def update_scaled_image(self):
"""按高度缩放图片,宽度保持比例,超出裁剪"""
if self.banner_image:
self.scaled_image = self.banner_image.scaled(
self.size(),
Qt.AspectRatioMode.KeepAspectRatioByExpanding,
Qt.TransformationMode.SmoothTransformation,
)
self.update()
def paintEvent(self, event):
"""重载 paintEvent 以绘制缩放后的图片"""
if self.scaled_image:
painter = QPainter(self)
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
painter.setRenderHint(QPainter.RenderHint.SmoothPixmapTransform)
# 创建圆角路径
path = QPainterPath()
path.addRoundedRect(self.rect(), 20, 20)
painter.setClipPath(path)
# 计算绘制位置,使图片居中
x = (self.width() - self.scaled_image.width()) // 2
y = (self.height() - self.scaled_image.height()) // 2
# 绘制缩放后的图片
painter.drawPixmap(x, y, self.scaled_image)
def resizeEvent(self, event):
"""重载 resizeEvent 以更新缩放后的图片"""
self.update_scaled_image()
QWidget.resizeEvent(self, event)
def set_percentage_size(self, width_percentage, height_percentage):
"""设置 Banner 的大小为窗口大小的百分比"""
parent = self.parentWidget()
if parent:
new_width = int(parent.width() * width_percentage)
new_height = int(parent.height() * height_percentage)
self.setFixedSize(new_width, new_height)
self.update_scaled_image()

View File

@@ -34,8 +34,6 @@ from PySide6.QtWidgets import (
) )
from qfluentwidgets import ( from qfluentwidgets import (
CardWidget, CardWidget,
IconWidget,
BodyLabel,
Pivot, Pivot,
ScrollArea, ScrollArea,
FluentIcon, FluentIcon,
@@ -52,7 +50,8 @@ from typing import List, Dict
import json import json
from app.core import Config, Task_manager, Task, MainInfoBar from app.core import Config, TaskManager, Task, MainInfoBar
from .Widget import StatefulItemCard
class DispatchCenter(QWidget): class DispatchCenter(QWidget):
@@ -77,7 +76,6 @@ class DispatchCenter(QWidget):
onClick=self.update_top_bar, onClick=self.update_top_bar,
icon=FluentIcon.CAFE, icon=FluentIcon.CAFE,
) )
self.update_top_bar()
self.Layout.addWidget(self.pivot, 0, Qt.AlignHCenter) self.Layout.addWidget(self.pivot, 0, Qt.AlignHCenter)
self.Layout.addWidget(self.stackedWidget) self.Layout.addWidget(self.stackedWidget)
@@ -93,7 +91,7 @@ class DispatchCenter(QWidget):
dispatch_box = DispatchBox(task.name, self) dispatch_box = DispatchBox(task.name, self)
dispatch_box.top_bar.button.clicked.connect( dispatch_box.top_bar.button.clicked.connect(
lambda: Task_manager.stop_task(task.name) lambda: TaskManager.stop_task(task.name)
) )
task.create_task_list.connect(dispatch_box.info.task.create_task) task.create_task_list.connect(dispatch_box.info.task.create_task)
@@ -120,10 +118,16 @@ class DispatchCenter(QWidget):
def connect_main_board(self, task: Task) -> None: def connect_main_board(self, task: Task) -> None:
"""连接主调度台""" """连接主调度台"""
self.script_list["主调度台"].top_bar.Lable.setText(
f"{task.name} - {task.mode.replace("_主调度台","")}模式"
)
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.button.clicked.disconnect() self.script_list["主调度台"].top_bar.button.clicked.disconnect()
self.script_list["主调度台"].top_bar.button.setText("中止任务") self.script_list["主调度台"].top_bar.button.setText("中止任务")
self.script_list["主调度台"].top_bar.button.clicked.connect( self.script_list["主调度台"].top_bar.button.clicked.connect(
lambda: Task_manager.stop_task(task.name) lambda: TaskManager.stop_task(task.name)
) )
task.create_task_list.connect( task.create_task_list.connect(
self.script_list["主调度台"].info.task.create_task self.script_list["主调度台"].info.task.create_task
@@ -145,6 +149,9 @@ class DispatchCenter(QWidget):
def disconnect_main_board(self, name: str) -> None: def disconnect_main_board(self, name: str) -> None:
"""断开主调度台""" """断开主调度台"""
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.button.clicked.disconnect() self.script_list["主调度台"].top_bar.button.clicked.disconnect()
self.script_list["主调度台"].top_bar.button.setText("开始任务") self.script_list["主调度台"].top_bar.button.setText("开始任务")
self.script_list["主调度台"].top_bar.button.clicked.connect( self.script_list["主调度台"].top_bar.button.clicked.connect(
@@ -158,18 +165,31 @@ class DispatchCenter(QWidget):
"""更新顶栏""" """更新顶栏"""
list = [] list = []
queue_numb, member_numb = 0, 0
if (Config.app_path / "config/QueueConfig").exists(): if (Config.app_path / "config/QueueConfig").exists():
for json_file in (Config.app_path / "config/QueueConfig").glob("*.json"): for json_file in (Config.app_path / "config/QueueConfig").glob("*.json"):
list.append(f"队列 - {json_file.stem}") list.append(f"队列 - {json_file.stem}")
queue_numb += 1
if (Config.app_path / "config/MaaConfig").exists(): if (Config.app_path / "config/MaaConfig").exists():
for subdir in (Config.app_path / "config/MaaConfig").iterdir(): for subdir in (Config.app_path / "config/MaaConfig").iterdir():
if subdir.is_dir(): if subdir.is_dir():
list.append(f"实例 - Maa - {subdir.name}") list.append(f"实例 - Maa - {subdir.name}")
member_numb += 1
self.script_list["主调度台"].top_bar.object.clear() self.script_list["主调度台"].top_bar.object.clear()
self.script_list["主调度台"].top_bar.object.addItems(list) self.script_list["主调度台"].top_bar.object.addItems(list)
self.script_list["主调度台"].top_bar.mode.clear()
self.script_list["主调度台"].top_bar.mode.addItems(["自动代理", "人工排查"])
if queue_numb == 1:
self.script_list["主调度台"].top_bar.object.setCurrentIndex(0)
elif member_numb == 1:
self.script_list["主调度台"].top_bar.object.setCurrentIndex(queue_numb)
else:
self.script_list["主调度台"].top_bar.object.setCurrentIndex(-1)
self.script_list["主调度台"].top_bar.mode.setCurrentIndex(0)
class DispatchBox(QWidget): class DispatchBox(QWidget):
@@ -208,17 +228,17 @@ class DispatchBox(QWidget):
if name == "主调度台": if name == "主调度台":
self.Lable = SubtitleLabel("", self)
self.Lable.hide()
self.object = ComboBox() self.object = ComboBox()
self.object.setCurrentIndex(-1)
self.object.setPlaceholderText("请选择调度对象") self.object.setPlaceholderText("请选择调度对象")
self.mode = ComboBox() self.mode = ComboBox()
self.mode.addItems(["自动代理", "人工排查"])
self.mode.setCurrentIndex(-1)
self.mode.setPlaceholderText("请选择调度模式") self.mode.setPlaceholderText("请选择调度模式")
self.button = PushButton("开始任务") self.button = PushButton("开始任务")
self.button.clicked.connect(self.start_task) self.button.clicked.connect(self.start_task)
Layout.addWidget(self.Lable)
Layout.addWidget(self.object) Layout.addWidget(self.object)
Layout.addWidget(self.mode) Layout.addWidget(self.mode)
Layout.addStretch(1) Layout.addStretch(1)
@@ -265,7 +285,7 @@ class DispatchBox(QWidget):
info = json.load(f) info = json.load(f)
logger.info(f"用户添加任务:{name}") logger.info(f"用户添加任务:{name}")
Task_manager.add_task(f"{self.mode.currentText()}_主窗口", name, info) TaskManager.add_task(f"{self.mode.currentText()}_主调度台", name, info)
elif self.object.currentText().split(" - ")[0] == "实例": elif self.object.currentText().split(" - ")[0] == "实例":
@@ -274,8 +294,8 @@ class DispatchBox(QWidget):
info = {"Queue": {"Member_1": name}} info = {"Queue": {"Member_1": name}}
logger.info(f"用户添加任务:{name}") logger.info(f"用户添加任务:{name}")
Task_manager.add_task( TaskManager.add_task(
f"{self.mode.currentText()}_主窗口", "用户自定义队列", info f"{self.mode.currentText()}_主调度台", "自定义队列", info
) )
class DispatchInfoCard(HeaderCardWidget): class DispatchInfoCard(HeaderCardWidget):
@@ -314,7 +334,7 @@ class DispatchBox(QWidget):
self.viewLayout.addLayout(self.Layout) self.viewLayout.addLayout(self.Layout)
self.viewLayout.setContentsMargins(3, 0, 3, 3) self.viewLayout.setContentsMargins(3, 0, 3, 3)
self.task_cards: List[ItemCard] = [] self.task_cards: List[StatefulItemCard] = []
def create_task(self, task_list: list): def create_task(self, task_list: list):
"""创建任务队列""" """创建任务队列"""
@@ -330,7 +350,7 @@ class DispatchBox(QWidget):
for task in task_list: for task in task_list:
self.task_cards.append(ItemCard(task)) self.task_cards.append(StatefulItemCard(task))
self.Layout.addWidget(self.task_cards[-1]) self.Layout.addWidget(self.task_cards[-1])
self.Layout.addStretch(1) self.Layout.addStretch(1)
@@ -352,7 +372,7 @@ class DispatchBox(QWidget):
self.viewLayout.addLayout(self.Layout) self.viewLayout.addLayout(self.Layout)
self.viewLayout.setContentsMargins(3, 0, 3, 3) self.viewLayout.setContentsMargins(3, 0, 3, 3)
self.user_cards: List[ItemCard] = [] self.user_cards: List[StatefulItemCard] = []
def create_user(self, user_list: list): def create_user(self, user_list: list):
"""创建用户队列""" """创建用户队列"""
@@ -368,7 +388,7 @@ class DispatchBox(QWidget):
for user in user_list: for user in user_list:
self.user_cards.append(ItemCard(user)) self.user_cards.append(StatefulItemCard(user))
self.Layout.addWidget(self.user_cards[-1]) self.Layout.addWidget(self.user_cards[-1])
self.Layout.addStretch(1) self.Layout.addStretch(1)
@@ -398,38 +418,3 @@ class DispatchBox(QWidget):
self.text.moveCursor(QTextCursor.End) self.text.moveCursor(QTextCursor.End)
self.text.ensureCursorVisible() self.text.ensureCursorVisible()
class ItemCard(CardWidget):
def __init__(self, task_item: list, parent=None):
super().__init__(parent)
self.Layout = QHBoxLayout(self)
self.Label = BodyLabel(task_item[0], self)
self.icon = IconWidget(FluentIcon.MORE, self)
self.icon.setFixedSize(16, 16)
self.update_status(task_item[1])
self.Layout.addWidget(self.icon)
self.Layout.addWidget(self.Label)
self.Layout.addStretch(1)
def update_status(self, status: str):
if status == "完成":
self.icon.setIcon(FluentIcon.ACCEPT)
self.Label.setTextColor("#0eb840", "#0eb840")
elif status == "等待":
self.icon.setIcon(FluentIcon.MORE)
self.Label.setTextColor("#7397ab", "#7397ab")
elif status == "运行":
self.icon.setIcon(FluentIcon.PLAY)
self.Label.setTextColor("#2e4e7e", "#2e4e7e")
elif status == "跳过":
self.icon.setIcon(FluentIcon.REMOVE)
self.Label.setTextColor("#606060", "#d2d2d2")
elif status == "异常":
self.icon.setIcon(FluentIcon.CLOSE)
self.Label.setTextColor("#ff2121", "#ff2121")

258
app/ui/history.py Normal file
View File

@@ -0,0 +1,258 @@
# <AUTO_MAA:A MAA Multi Account Management and Automation Tool>
# Copyright © <2024> <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/>.
# DLmaster_361@163.com
"""
AUTO_MAA
AUTO_MAA历史记录界面
v4.2
作者DLmaster_361
"""
from loguru import logger
from PySide6.QtWidgets import (
QWidget,
QVBoxLayout,
QHBoxLayout,
)
from qfluentwidgets import (
ScrollArea,
FluentIcon,
HeaderCardWidget,
PushButton,
ExpandGroupSettingCard,
TextBrowser,
)
from PySide6.QtCore import Signal
import os
from functools import partial
from pathlib import Path
from typing import List
from app.core import Config
from .Widget import StatefulItemCard, QuantifiedItemCard
class History(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setObjectName("历史记录")
content_widget = QWidget()
self.content_layout = QVBoxLayout(content_widget)
scrollArea = ScrollArea()
scrollArea.setWidgetResizable(True)
scrollArea.setWidget(content_widget)
layout = QVBoxLayout()
layout.addWidget(scrollArea)
self.setLayout(layout)
self.history_card_list = []
self.refresh()
def refresh(self):
"""刷新脚本实例界面"""
while self.content_layout.count() > 0:
item = self.content_layout.takeAt(0)
if item.spacerItem():
self.content_layout.removeItem(item.spacerItem())
elif item.widget():
item.widget().deleteLater()
self.history_card_list = []
history_dict = Config.search_history()
for date, user_list in history_dict.items():
self.history_card_list.append(HistoryCard(date, user_list, self))
self.content_layout.addWidget(self.history_card_list[-1])
self.content_layout.addStretch(1)
class HistoryCard(ExpandGroupSettingCard):
def __init__(self, date: str, user_list: List[Path], parent=None):
super().__init__(
FluentIcon.HISTORY, date, f"{date}的历史运行记录与统计信息", parent
)
widget = QWidget()
Layout = QVBoxLayout(widget)
self.viewLayout.setContentsMargins(0, 0, 0, 0)
self.viewLayout.setSpacing(0)
self.addGroupWidget(widget)
self.user_history_card_list = []
for user_path in user_list:
self.user_history_card_list.append(self.UserHistoryCard(user_path, self))
Layout.addWidget(self.user_history_card_list[-1])
class UserHistoryCard(HeaderCardWidget):
def __init__(
self,
user_history_path: Path,
parent=None,
):
super().__init__(parent)
self.setTitle(user_history_path.name.replace(".json", ""))
self.user_history_path = user_history_path
self.main_history = Config.load_maa_logs("总览", user_history_path)
self.index_card = self.IndexCard(self.main_history["条目索引"], self)
self.statistics_card = QHBoxLayout()
self.log_card = self.LogCard(self)
self.index_card.index_changed.connect(self.update_info)
self.viewLayout.addWidget(self.index_card)
self.viewLayout.addLayout(self.statistics_card)
self.viewLayout.addWidget(self.log_card)
self.viewLayout.setContentsMargins(0, 0, 0, 0)
self.viewLayout.setSpacing(0)
self.viewLayout.setStretch(0, 1)
self.viewLayout.setStretch(2, 4)
self.update_info("数据总览")
def update_info(self, index: str) -> None:
"""更新信息"""
if index == "数据总览":
while self.statistics_card.count() > 0:
item = self.statistics_card.takeAt(0)
if item.spacerItem():
self.statistics_card.removeItem(item.spacerItem())
elif item.widget():
item.widget().deleteLater()
for name, item_list in self.main_history["统计数据"].items():
statistics_card = self.StatisticsCard(name, item_list, self)
self.statistics_card.addWidget(statistics_card)
self.log_card.hide()
else:
single_history = Config.load_maa_logs(
"单项",
self.user_history_path.with_suffix("")
/ f"{index.replace(":","-")}.json",
)
while self.statistics_card.count() > 0:
item = self.statistics_card.takeAt(0)
if item.spacerItem():
self.statistics_card.removeItem(item.spacerItem())
elif item.widget():
item.widget().deleteLater()
for name, item_list in single_history["统计数据"].items():
statistics_card = self.StatisticsCard(name, item_list, self)
self.statistics_card.addWidget(statistics_card)
self.log_card.text.setText(single_history["日志信息"])
self.log_card.button.clicked.disconnect()
self.log_card.button.clicked.connect(
lambda: os.startfile(
self.user_history_path.with_suffix("")
/ f"{index.replace(":","-")}.log"
)
)
self.log_card.show()
self.viewLayout.setStretch(1, self.statistics_card.count())
self.setMinimumHeight(300)
class IndexCard(HeaderCardWidget):
index_changed = Signal(str)
def __init__(self, index_list: list, parent=None):
super().__init__(parent)
self.setTitle("记录条目")
self.Layout = QVBoxLayout()
self.viewLayout.addLayout(self.Layout)
self.viewLayout.setContentsMargins(3, 0, 3, 3)
self.index_cards: List[StatefulItemCard] = []
for index in index_list:
self.index_cards.append(StatefulItemCard(index))
self.index_cards[-1].clicked.connect(
partial(self.index_changed.emit, index[0])
)
self.Layout.addWidget(self.index_cards[-1])
self.Layout.addStretch(1)
class StatisticsCard(HeaderCardWidget):
def __init__(self, name: str, item_list: list, parent=None):
super().__init__(parent)
self.setTitle(name)
self.Layout = QVBoxLayout()
self.viewLayout.addLayout(self.Layout)
self.viewLayout.setContentsMargins(3, 0, 3, 3)
self.item_cards: List[QuantifiedItemCard] = []
for item in item_list:
self.item_cards.append(QuantifiedItemCard(item))
self.Layout.addWidget(self.item_cards[-1])
if len(item_list) == 0:
self.Layout.addWidget(QuantifiedItemCard(["暂无记录", ""]))
self.Layout.addStretch(1)
class LogCard(HeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setTitle("日志")
self.text = TextBrowser(self)
self.button = PushButton("打开日志文件", self)
self.button.clicked.connect(lambda: print("打开日志文件"))
Layout = QVBoxLayout()
Layout.addWidget(self.text)
Layout.addWidget(self.button)
self.viewLayout.setContentsMargins(3, 0, 3, 3)
self.viewLayout.addLayout(Layout)

423
app/ui/home.py Normal file
View File

@@ -0,0 +1,423 @@
# <AUTO_MAA:A MAA Multi Account Management and Automation Tool>
# Copyright © <2024> <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/>.
# DLmaster_361@163.com
"""
AUTO_MAA
AUTO_MAA主界面
v4.2
作者DLmaster_361
"""
from loguru import logger
from PySide6.QtWidgets import (
QWidget,
QVBoxLayout,
QHBoxLayout,
QSpacerItem,
QSizePolicy,
QFileDialog,
)
from PySide6.QtCore import Qt, QSize, QUrl
from PySide6.QtGui import QDesktopServices, QColor
from qfluentwidgets import (
FluentIcon,
ScrollArea,
SimpleCardWidget,
PrimaryToolButton,
TextBrowser,
)
import re
import shutil
import requests
import json
import time
from datetime import datetime
from pathlib import Path
from app.core import Config, MainInfoBar
from .Widget import Banner, IconButton
class Home(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setObjectName("主页")
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)
v_layout.setAlignment(Qt.AlignmentFlag.AlignTop)
# 空白占位符
v_layout.addItem(
QSpacerItem(10, 20, QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Minimum)
)
# 顶部部分 (按钮组)
h1_layout = QHBoxLayout()
h1_layout.setAlignment(Qt.AlignmentFlag.AlignTop)
# 左边留白区域
h1_layout.addStretch()
# 按钮组
buttonGroup = ButtonGroup()
buttonGroup.setMaximumHeight(320)
h1_layout.addWidget(buttonGroup)
# 空白占位符
h1_layout.addItem(
QSpacerItem(20, 10, QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Minimum)
)
# 将顶部水平布局添加到垂直布局
v_layout.addLayout(h1_layout)
# 中间留白区域
v_layout.addItem(
QSpacerItem(10, 10, QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Minimum)
)
v_layout.addStretch()
# 中间留白区域
v_layout.addItem(
QSpacerItem(10, 10, QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Minimum)
)
v_layout.addStretch()
# 底部部分 (图片切换按钮)
h2_layout = QHBoxLayout()
h2_layout.setAlignment(Qt.AlignmentFlag.AlignTop)
# 左边留白区域
h2_layout.addItem(
QSpacerItem(20, 10, QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Minimum)
)
# # 公告卡片
# noticeCard = NoticeCard()
# h2_layout.addWidget(noticeCard)
h2_layout.addStretch()
# 自定义图像按钮布局
self.imageButton = PrimaryToolButton(FluentIcon.IMAGE_EXPORT)
self.imageButton.setFixedSize(56, 56)
self.imageButton.setIconSize(QSize(32, 32))
self.imageButton.clicked.connect(self.get_home_image)
v1_layout = QVBoxLayout()
v1_layout.addWidget(self.imageButton, alignment=Qt.AlignmentFlag.AlignBottom)
h2_layout.addLayout(v1_layout)
# 空白占位符
h2_layout.addItem(
QSpacerItem(25, 10, QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Minimum)
)
# 将底部水平布局添加到垂直布局
v_layout.addLayout(h2_layout)
layout = QVBoxLayout()
scrollArea = ScrollArea()
scrollArea.setWidgetResizable(True)
scrollArea.setWidget(widget)
layout.addWidget(scrollArea)
self.setLayout(layout)
self.set_banner()
def get_home_image(self) -> None:
"""获取主页图片"""
if (
Config.global_config.get(Config.global_config.function_HomeImageMode)
== "默认"
):
pass
elif (
Config.global_config.get(Config.global_config.function_HomeImageMode)
== "自定义"
):
file_path, _ = QFileDialog.getOpenFileName(
self, "打开自定义主页图片", "", "图片文件 (*.png *.jpg *.bmp)"
)
if file_path:
for file in Config.app_path.glob(
"resources/images/Home/BannerCustomize.*"
):
file.unlink()
shutil.copy(
file_path,
Config.app_path
/ f"resources/images/Home/BannerCustomize{Path(file_path).suffix}",
)
logger.info(f"自定义主页图片更换成功:{file_path}")
MainInfoBar.push_info_bar(
"success",
"主页图片更换成功",
"自定义主页图片更换成功!",
3000,
)
else:
logger.warning("自定义主页图片更换失败:未选择图片文件")
MainInfoBar.push_info_bar(
"warning",
"主页图片更换失败",
"未选择图片文件!",
5000,
)
elif (
Config.global_config.get(Config.global_config.function_HomeImageMode)
== "主题图像"
):
# 从远程服务器获取最新主题图像
for _ in range(3):
try:
response = requests.get(
"https://gitee.com/DLmaster_361/AUTO_MAA/raw/server/theme_image.json"
)
theme_image = response.json()
break
except Exception as e:
err = e
time.sleep(0.1)
else:
logger.error(f"获取最新主题图像时出错:\n{err}")
MainInfoBar.push_info_bar(
"error",
"主题图像获取失败",
f"获取最新主题图像信息时出错:\n{err}",
-1,
)
return None
if (Config.app_path / "resources/theme_image.json").exists():
with (Config.app_path / "resources/theme_image.json").open(
mode="r", encoding="utf-8"
) as f:
theme_image_local = json.load(f)
time_local = datetime.strptime(
theme_image_local["time"], "%Y-%m-%d %H:%M"
)
else:
time_local = datetime.strptime("2000-01-01 00:00", "%Y-%m-%d %H:%M")
if not (
Config.app_path / "resources/images/Home/BannerTheme.jpg"
).exists() or (
datetime.now()
> datetime.strptime(theme_image["time"], "%Y-%m-%d %H:%M")
and datetime.strptime(theme_image["time"], "%Y-%m-%d %H:%M")
> time_local
):
response = requests.get(theme_image["url"])
if response.status_code == 200:
with open(
Config.app_path / "resources/images/Home/BannerTheme.jpg", "wb"
) as file:
file.write(response.content)
logger.info(f"主题图像「{theme_image["name"]}」下载成功")
MainInfoBar.push_info_bar(
"success",
"主题图像下载成功",
f"{theme_image["name"]}」下载成功!",
3000,
)
else:
logger.error("主题图像下载失败")
MainInfoBar.push_info_bar(
"error",
"主题图像下载失败",
f"主题图像下载失败:{response.status_code}",
-1,
)
with (Config.app_path / "resources/theme_image.json").open(
mode="w", encoding="utf-8"
) as f:
json.dump(theme_image, f, ensure_ascii=False, indent=4)
else:
logger.info("主题图像已是最新")
MainInfoBar.push_info_bar(
"info",
"主题图像已是最新",
"主题图像已是最新!",
3000,
)
self.set_banner()
def set_banner(self):
"""设置主页图像"""
if (
Config.global_config.get(Config.global_config.function_HomeImageMode)
== "默认"
):
self.banner.set_banner_image(
str(Config.app_path / "resources/images/Home/BannerDefault.png")
)
self.imageButton.hide()
self.banner_text.setVisible(False)
elif (
Config.global_config.get(Config.global_config.function_HomeImageMode)
== "自定义"
):
for file in Config.app_path.glob("resources/images/Home/BannerCustomize.*"):
self.banner.set_banner_image(str(file))
break
self.imageButton.show()
self.banner_text.setVisible(False)
elif (
Config.global_config.get(Config.global_config.function_HomeImageMode)
== "主题图像"
):
self.banner.set_banner_image(
str(Config.app_path / "resources/images/Home/BannerTheme.jpg")
)
self.imageButton.show()
self.banner_text.setVisible(True)
if (Config.app_path / "resources/theme_image.json").exists():
with (Config.app_path / "resources/theme_image.json").open(
mode="r", encoding="utf-8"
) as f:
theme_image = json.load(f)
html_content = theme_image["html"]
else:
html_content = "<h1>主题图像</h1><p>主题图像信息未知</p>"
self.banner_text.setHtml(re.sub(r"<img[^>]*>", "", html_content))
class ButtonGroup(SimpleCardWidget):
"""显示主页和 GitHub 按钮的竖直按钮组"""
def __init__(self, parent=None):
super().__init__(parent=parent)
self.setFixedSize(56, 180)
layout = QVBoxLayout(self)
layout.setAlignment(Qt.AlignmentFlag.AlignTop)
# 创建主页按钮
home_button = IconButton(
FluentIcon.HOME.icon(color=QColor("#fff")),
tip_title="AUTO_MAA官网",
tip_content="AUTO_MAA官方文档站",
isTooltip=True,
)
home_button.setIconSize(QSize(32, 32))
home_button.clicked.connect(self.open_home)
layout.addWidget(home_button)
# 创建 GitHub 按钮
github_button = IconButton(
FluentIcon.GITHUB.icon(color=QColor("#fff")),
tip_title="Github仓库",
tip_content="如果本项目有帮助到您~\n不妨给项目点一个Star⭐",
isTooltip=True,
)
github_button.setIconSize(QSize(32, 32))
github_button.clicked.connect(self.open_github)
layout.addWidget(github_button)
# # 创建 文档 按钮
# doc_button = IconButton(
# FluentIcon.DICTIONARY.icon(color=QColor("#fff")),
# tip_title="自助排障文档",
# tip_content="点击打开自助排障文档,好孩子都能看懂",
# isTooltip=True,
# )
# doc_button.setIconSize(QSize(32, 32))
# doc_button.clicked.connect(self.open_doc)
# layout.addWidget(doc_button)
# 创建 Q群 按钮
doc_button = IconButton(
FluentIcon.CHAT.icon(color=QColor("#fff")),
tip_title="官方社群",
tip_content="加入官方群聊【AUTO_MAA绝赞DeBug中",
isTooltip=True,
)
doc_button.setIconSize(QSize(32, 32))
doc_button.clicked.connect(self.open_chat)
layout.addWidget(doc_button)
# 创建 官方店铺 按钮 (当然没有)
doc_button = IconButton(
FluentIcon.SHOPPING_CART.icon(color=QColor("#fff")),
tip_title="官方店铺",
tip_content="暂时没有官方店铺,但是可以加入官方群聊哦~",
isTooltip=True,
)
doc_button.setIconSize(QSize(32, 32))
doc_button.clicked.connect(self.open_sales)
layout.addWidget(doc_button)
def _normalBackgroundColor(self):
return QColor(0, 0, 0, 96)
def open_home(self):
"""打开主页链接"""
QDesktopServices.openUrl(QUrl("https://clozya.github.io/AUTOMAA_docs"))
def open_github(self):
"""打开 GitHub 链接"""
QDesktopServices.openUrl(QUrl("https://github.com/DLmaster361/AUTO_MAA"))
def open_chat(self):
"""打开 Q群 链接"""
QDesktopServices.openUrl(QUrl("https://qm.qq.com/q/bd9fISNoME"))
def open_doc(self):
"""打开 文档 链接"""
QDesktopServices.openUrl(QUrl("https://clozya.github.io/AUTOMAA_docs"))
def open_sales(self):
"""其实还是打开 Q群 链接"""
QDesktopServices.openUrl(QUrl("https://qm.qq.com/q/bd9fISNoME"))

View File

@@ -26,10 +26,7 @@ v4.2
""" """
from loguru import logger from loguru import logger
from PySide6.QtWidgets import ( from PySide6.QtWidgets import QSystemTrayIcon
QApplication,
QSystemTrayIcon,
)
from qfluentwidgets import ( from qfluentwidgets import (
Action, Action,
PushButton, PushButton,
@@ -39,20 +36,27 @@ from qfluentwidgets import (
InfoBar, InfoBar,
InfoBarPosition, InfoBarPosition,
setTheme, setTheme,
isDarkTheme,
SystemThemeListener,
Theme, Theme,
MSFluentWindow, MSFluentWindow,
NavigationItemPosition, NavigationItemPosition,
qconfig, qconfig,
) )
from PySide6.QtGui import QIcon, QCloseEvent from PySide6.QtGui import QIcon, QCloseEvent
from PySide6.QtCore import Qt from PySide6.QtCore import Qt, QTimer
import json
from datetime import datetime, timedelta
import shutil
from app.core import Config, Task_manager, Main_timer, MainInfoBar from app.core import Config, TaskManager, MainTimer, MainInfoBar
from app.services import Notify, Crypto, System from app.services import Notify, Crypto, System
from .setting import Setting from .home import Home
from .member_manager import MemberManager from .member_manager import MemberManager
from .queue_manager import QueueManager from .queue_manager import QueueManager
from .dispatch_center import DispatchCenter from .dispatch_center import DispatchCenter
from .history import History
from .setting import Setting
class AUTO_MAA(MSFluentWindow): class AUTO_MAA(MSFluentWindow):
@@ -63,25 +67,28 @@ class AUTO_MAA(MSFluentWindow):
self.setWindowIcon(QIcon(str(Config.app_path / "resources/icons/AUTO_MAA.ico"))) self.setWindowIcon(QIcon(str(Config.app_path / "resources/icons/AUTO_MAA.ico")))
self.setWindowTitle("AUTO_MAA") self.setWindowTitle("AUTO_MAA")
setTheme(Theme.AUTO) setTheme(Theme.AUTO, lazy=True)
self.splashScreen = SplashScreen(self.windowIcon(), self) self.splashScreen = SplashScreen(self.windowIcon(), self)
self.show_ui("显示主窗口", if_quick=True) self.show_ui("显示主窗口", if_quick=True)
MainInfoBar.parent = self MainInfoBar.main_window = self.window()
System.main_window = self.window()
# 创建主窗口 # 创建主窗口
self.setting = Setting(self) self.home = Home(self)
self.member_manager = MemberManager(self) self.member_manager = MemberManager(self)
self.queue_manager = QueueManager(self) self.queue_manager = QueueManager(self)
self.dispatch_center = DispatchCenter(self) self.dispatch_center = DispatchCenter(self)
self.history = History(self)
self.setting = Setting(self)
self.addSubInterface( self.addSubInterface(
self.setting, self.home,
FluentIcon.SETTING, FluentIcon.HOME,
"设置", "主页",
FluentIcon.SETTING, FluentIcon.HOME,
NavigationItemPosition.BOTTOM, NavigationItemPosition.TOP,
) )
self.addSubInterface( self.addSubInterface(
self.member_manager, self.member_manager,
@@ -104,6 +111,20 @@ class AUTO_MAA(MSFluentWindow):
FluentIcon.IOT, FluentIcon.IOT,
NavigationItemPosition.TOP, NavigationItemPosition.TOP,
) )
self.addSubInterface(
self.history,
FluentIcon.HISTORY,
"历史记录",
FluentIcon.HISTORY,
NavigationItemPosition.BOTTOM,
)
self.addSubInterface(
self.setting,
FluentIcon.SETTING,
"设置",
FluentIcon.SETTING,
NavigationItemPosition.BOTTOM,
)
self.stackedWidget.currentChanged.connect( self.stackedWidget.currentChanged.connect(
lambda index: (self.member_manager.refresh() if index == 1 else None) lambda index: (self.member_manager.refresh() if index == 1 else None)
) )
@@ -122,11 +143,13 @@ class AUTO_MAA(MSFluentWindow):
self.dispatch_center.update_top_bar() if index == 3 else None self.dispatch_center.update_top_bar() if index == 3 else None
) )
) )
self.stackedWidget.currentChanged.connect(
lambda index: (self.history.refresh() if index == 4 else None)
)
# 创建系统托盘及其菜单 # 创建系统托盘及其菜单
self.tray = QSystemTrayIcon( self.tray = QSystemTrayIcon(
QIcon(str(Config.app_path / "resources/icons/AUTO_MAA.ico")), QIcon(str(Config.app_path / "resources/icons/AUTO_MAA.ico")), self
self,
) )
self.tray.setToolTip("AUTO_MAA") self.tray.setToolTip("AUTO_MAA")
self.tray_menu = SystemTrayMenu("AUTO_MAA", self) self.tray_menu = SystemTrayMenu("AUTO_MAA", self)
@@ -142,50 +165,79 @@ class AUTO_MAA(MSFluentWindow):
self.tray_menu.addSeparator() self.tray_menu.addSeparator()
# 开始任务菜单项 # 开始任务菜单项
# self.tray_menu.addActions( self.tray_menu.addActions(
# [ [
# Action( Action(FluentIcon.PLAY, "运行自动代理", triggered=self.start_main_task),
# FluentIcon.PLAY, Action(
# "运行自动代理", FluentIcon.PAUSE,
# triggered=lambda: self.start_task("自动代理"), "中止所有任务",
# ), triggered=lambda: TaskManager.stop_task("ALL"),
# Action( ),
# FluentIcon.PLAY, ]
# "运行人工排查", )
# triggered=lambda: self.start_task("人工排查"), self.tray_menu.addSeparator()
# ),
# Action(FluentIcon.PAUSE, "中止当前任务", triggered=self.stop_task),
# ]
# )
# self.tray_menu.addSeparator()
# 退出主程序菜单项 # 退出主程序菜单项
self.tray_menu.addAction( self.tray_menu.addAction(
Action(FluentIcon.POWER_BUTTON, "退出主程序", triggered=self.kill_main) Action(FluentIcon.POWER_BUTTON, "退出主程序", triggered=self.window().close)
) )
# 设置托盘菜单 # 设置托盘菜单
self.tray.setContextMenu(self.tray_menu) self.tray.setContextMenu(self.tray_menu)
self.tray.activated.connect(self.on_tray_activated) self.tray.activated.connect(self.on_tray_activated)
Task_manager.create_gui.connect(self.dispatch_center.add_board) TaskManager.create_gui.connect(self.dispatch_center.add_board)
Task_manager.connect_gui.connect(self.dispatch_center.connect_main_board) TaskManager.connect_gui.connect(self.dispatch_center.connect_main_board)
Notify.push_info_bar.connect(MainInfoBar.push_info_bar)
self.setting.ui.card_IfShowTray.checkedChanged.connect( self.setting.ui.card_IfShowTray.checkedChanged.connect(
lambda: self.show_ui("配置托盘") lambda: self.show_ui("配置托盘")
) )
self.setting.ui.card_IfToTray.checkedChanged.connect(self.set_min_method) self.setting.ui.card_IfToTray.checkedChanged.connect(self.set_min_method)
self.setting.function.card_HomeImageMode.comboBox.currentIndexChanged.connect(
lambda index: (
self.home.get_home_image() if index == 2 else self.home.set_banner()
)
)
self.splashScreen.finish() self.splashScreen.finish()
self.themeListener = SystemThemeListener(self)
self.themeListener.systemThemeChanged.connect(self.switch_theme)
self.themeListener.start()
def switch_theme(self):
"""切换主题"""
setTheme(Theme.AUTO, lazy=True)
QTimer.singleShot(100, lambda: setTheme(Theme.AUTO, lazy=True))
# 云母特效启用时需要增加重试机制
if self.isMicaEffectEnabled():
QTimer.singleShot(
100,
lambda: self.windowEffect.setMicaEffect(self.winId(), isDarkTheme()),
)
def start_up_task(self) -> None: def start_up_task(self) -> None:
"""启动时任务""" """启动时任务"""
# 加载配置 # 加载配置
qconfig.load(Config.config_path, Config.global_config) qconfig.load(Config.config_path, Config.global_config)
Config.global_config.save()
# 清理旧日志
self.clean_old_logs()
# 检查密码 # 检查密码
self.setting.check_PASSWORD() self.setting.check_PASSWORD()
# 获取主题图像
if (
Config.global_config.get(Config.global_config.function_HomeImageMode)
== "主题图像"
):
self.home.get_home_image()
# 获取公告 # 获取公告
self.setting.show_notice(if_show=False) self.setting.show_notice(if_show=False)
@@ -210,6 +262,16 @@ class AUTO_MAA(MSFluentWindow):
info.addWidget(Up) info.addWidget(Up)
info.show() info.show()
# 直接运行主任务
if Config.global_config.get(Config.global_config.start_IfRunDirectly):
self.start_main_task()
# 直接最小化
if Config.global_config.get(Config.global_config.start_IfMinimizeDirectly):
self.titleBar.minBtn.click()
def set_min_method(self) -> None: def set_min_method(self) -> None:
"""设置最小化方法""" """设置最小化方法"""
@@ -221,52 +283,73 @@ class AUTO_MAA(MSFluentWindow):
else: else:
self.titleBar.minBtn.clicked.disconnect() self.titleBar.minBtn.clicked.disconnect()
self.titleBar.minBtn.clicked.connect(self.showMinimized) self.titleBar.minBtn.clicked.connect(self.window().showMinimized)
def on_tray_activated(self, reason): def on_tray_activated(self, reason):
"""双击返回主界面""" """双击返回主界面"""
if reason == QSystemTrayIcon.DoubleClick: if reason == QSystemTrayIcon.DoubleClick:
self.show_ui("显示主窗口") self.show_ui("显示主窗口")
# def start_task(self, mode): def clean_old_logs(self):
# """调起对应任务""" """
# if self.main.MaaManager.isRunning(): 删除超过用户设定天数的日志文件(基于目录日期)
# Notify.push_notification( """
# f"无法运行{mode}",
# "当前已有任务正在运行,请在该任务结束后重试",
# "当前已有任务正在运行,请在该任务结束后重试",
# 3,
# )
# else:
# self.main.maa_starter(mode)
# def stop_task(self): if (
# """中止当前任务""" Config.global_config.get(Config.global_config.function_HistoryRetentionTime)
# if self.main.MaaManager.isRunning(): == 0
# if ( ):
# self.main.MaaManager.mode == "自动代理" logger.info("由于用户设置日志永久保留,跳过日志清理")
# or self.main.MaaManager.mode == "人工排查" return
# ):
# self.main.maa_ender(f"{self.main.MaaManager.mode}_结束")
# elif "设置MAA" in self.main.MaaManager.mode:
# Notify.push_notification(
# "正在设置MAA",
# "正在运行设置MAA任务无法中止",
# "正在运行设置MAA任务无法中止",
# 3,
# )
# else:
# Notify.push_notification(
# "无任务运行!",
# "当前无任务正在运行,无需中止",
# "当前无任务正在运行,无需中止",
# 3,
# )
def kill_main(self) -> None: deleted_count = 0
"""退出主程序"""
self.close() for date_folder in (Config.app_path / "history").iterdir():
QApplication.quit() if not date_folder.is_dir():
continue # 只处理日期文件夹
try:
# 只检查 `YYYY-MM-DD` 格式的文件夹
folder_date = datetime.strptime(date_folder.name, "%Y-%m-%d")
if datetime.now() - folder_date > timedelta(
days=Config.global_config.get(
Config.global_config.function_HistoryRetentionTime
)
):
shutil.rmtree(date_folder, ignore_errors=True)
deleted_count += 1
logger.info(f"已删除超期日志目录: {date_folder}")
except ValueError:
logger.warning(f"非日期格式的目录: {date_folder}")
logger.info(f"清理完成: {deleted_count} 个日期目录")
def start_main_task(self) -> None:
"""启动主任务"""
if (Config.app_path / "config/QueueConfig/调度队列_1.json").exists():
with (Config.app_path / "config/QueueConfig/调度队列_1.json").open(
mode="r", encoding="utf-8"
) as f:
info = json.load(f)
logger.info("自动添加任务调度队列_1")
TaskManager.add_task("自动代理_主调度台", "主任务队列", info)
elif (Config.app_path / "config/MaaConfig/脚本_1").exists():
info = {"Queue": {"Member_1": "脚本_1"}}
logger.info("自动添加任务脚本_1")
TaskManager.add_task("自动代理_主调度台", "主任务队列", info)
else:
logger.worning("启动主任务失败:未找到有效的主任务配置文件")
MainInfoBar.push_info_bar(
"warning", "启动主任务失败", "“调度队列_1”与“脚本_1”均不存在", -1
)
def show_ui(self, mode: str, if_quick: bool = False) -> None: def show_ui(self, mode: str, if_quick: bool = False) -> None:
"""配置窗口状态""" """配置窗口状态"""
@@ -288,11 +371,13 @@ class AUTO_MAA(MSFluentWindow):
), ),
) )
) )
self.setGeometry(location[0], location[1], size[0], size[1]) self.window().setGeometry(location[0], location[1], size[0], size[1])
self.show() self.window().show()
self.window().raise_()
self.window().activateWindow()
if not if_quick: if not if_quick:
if Config.global_config.get(Config.global_config.ui_maximized): if Config.global_config.get(Config.global_config.ui_maximized):
self.showMaximized() self.window().showMaximized()
self.set_min_method() self.set_min_method()
self.show_ui("配置托盘") self.show_ui("配置托盘")
@@ -306,7 +391,7 @@ class AUTO_MAA(MSFluentWindow):
elif mode == "隐藏到托盘": elif mode == "隐藏到托盘":
# 保存窗口相关属性 # 保存窗口相关属性
if not self.isMaximized(): if not self.window().isMaximized():
Config.global_config.set( Config.global_config.set(
Config.global_config.ui_size, Config.global_config.ui_size,
@@ -317,14 +402,14 @@ class AUTO_MAA(MSFluentWindow):
f"{self.geometry().x()}x{self.geometry().y()}", f"{self.geometry().x()}x{self.geometry().y()}",
) )
Config.global_config.set( Config.global_config.set(
Config.global_config.ui_maximized, self.isMaximized() Config.global_config.ui_maximized, self.window().isMaximized()
) )
Config.global_config.save() Config.global_config.save()
# 隐藏主窗口 # 隐藏主窗口
if not if_quick: if not if_quick:
self.hide() self.window().hide()
self.tray.show() self.tray.show()
def closeEvent(self, event: QCloseEvent): def closeEvent(self, event: QCloseEvent):
@@ -333,14 +418,18 @@ class AUTO_MAA(MSFluentWindow):
self.show_ui("隐藏到托盘", if_quick=True) self.show_ui("隐藏到托盘", if_quick=True)
# 清理各功能线程 # 清理各功能线程
Main_timer.Timer.stop() MainTimer.Timer.stop()
Main_timer.Timer.deleteLater() MainTimer.Timer.deleteLater()
Task_manager.stop_task("ALL") TaskManager.stop_task("ALL")
# 关闭数据库连接 # 关闭数据库连接
Config.close_database() Config.close_database()
# 关闭主题监听
self.themeListener.terminate()
self.themeListener.deleteLater()
logger.info("AUTO_MAA主程序关闭") logger.info("AUTO_MAA主程序关闭")
logger.info("===================================") logger.info("----------------END----------------")
event.accept() event.accept()

View File

@@ -46,9 +46,12 @@ from qfluentwidgets import (
HeaderCardWidget, HeaderCardWidget,
CommandBar, CommandBar,
ExpandGroupSettingCard, ExpandGroupSettingCard,
ComboBoxSettingCard,
PushSettingCard, PushSettingCard,
) )
from PySide6.QtCore import Qt from PySide6.QtCore import Qt
import requests
import time
from functools import partial from functools import partial
from pathlib import Path from pathlib import Path
from typing import List from typing import List
@@ -56,22 +59,20 @@ from datetime import datetime, timedelta
import json import json
import shutil import shutil
from app.core import Config, MainInfoBar, Task_manager from app.core import Config, MainInfoBar, TaskManager
from app.services import Crypto from app.services import Crypto
from app.utils import Updater
from .Widget import ( from .Widget import (
InputMessageBox, LineEditMessageBox,
LineEditSettingCard, LineEditSettingCard,
SpinBoxSettingCard, SpinBoxSettingCard,
SetMessageBox, ComboBoxMessageBox,
) )
class MemberManager(QWidget): class MemberManager(QWidget):
def __init__( def __init__(self, parent=None):
self,
parent=None,
):
super().__init__(parent) super().__init__(parent)
self.setObjectName("脚本管理") self.setObjectName("脚本管理")
@@ -107,14 +108,21 @@ class MemberManager(QWidget):
] ]
) )
self.tools.addSeparator() self.tools.addSeparator()
self.key = Action(
FluentIcon.HIDE,
"显示/隐藏密码",
checkable=True,
triggered=self.show_password,
)
self.tools.addAction( self.tools.addAction(
self.key, Action(
FluentIcon.DOWNLOAD,
"脚本下载器",
triggered=self.member_downloader,
)
)
self.tools.addSeparator()
self.tools.addAction(
Action(
FluentIcon.HIDE,
"显示/隐藏密码",
checkable=True,
triggered=self.show_password,
)
) )
layout.addWidget(self.tools) layout.addWidget(self.tools)
@@ -123,16 +131,15 @@ class MemberManager(QWidget):
def add_setting_box(self): def add_setting_box(self):
"""添加一个脚本实例""" """添加一个脚本实例"""
choice = InputMessageBox( choice = ComboBoxMessageBox(
self, self.window(),
"选择一个脚本类型添加相应脚本实例", "选择一个脚本类型添加相应脚本实例",
"选择脚本类型", ["选择脚本类型"],
"选择", [["MAA"]],
["MAA"],
) )
if choice.exec() and choice.input.currentIndex() != -1: if choice.exec() and choice.input[0].currentIndex() != -1:
if choice.input.currentText() == "MAA": if choice.input[0].currentText() == "MAA":
index = len(self.member_manager.search_member()) + 1 index = len(self.member_manager.search_member()) + 1
@@ -170,7 +177,7 @@ class MemberManager(QWidget):
choice = MessageBox( choice = MessageBox(
"确认", "确认",
f"确定要删除 {name} 实例吗?", f"确定要删除 {name} 实例吗?",
self, self.window(),
) )
if choice.exec(): if choice.exec():
@@ -292,11 +299,70 @@ class MemberManager(QWidget):
self.member_manager.show_SettingBox(index + 1) self.member_manager.show_SettingBox(index + 1)
def member_downloader(self):
"""脚本下载器"""
choice = ComboBoxMessageBox(
self.window(),
"选择一个脚本类型以下载相应脚本",
["选择脚本类型"],
[["MAA"]],
)
if choice.exec() and choice.input[0].currentIndex() != -1:
if choice.input[0].currentText() == "MAA":
(Config.app_path / "script/MAA").mkdir(parents=True, exist_ok=True)
folder = QFileDialog.getExistingDirectory(
self, "选择MAA下载目录", str(Config.app_path / "script/MAA")
)
if not folder:
logger.warning("选择MAA下载目录时未选择文件夹")
MainInfoBar.push_info_bar(
"warning", "警告", "未选择MAA下载目录", 5000
)
return None
# 从mirrorc服务器获取最新版本信息
for _ in range(3):
try:
response = requests.get(
"https://mirrorc.top/api/resources/MAA/latest?user_agent=MaaWpfGui&os=win&arch=x64&channel=stable"
)
maa_info = response.json()
break
except Exception as e:
err = e
time.sleep(0.1)
else:
choice = MessageBox(
"错误",
f"获取版本信息时出错:\n{err}",
self.window(),
)
choice.cancelButton.hide()
choice.buttonLayout.insertStretch(1)
if choice.exec():
return None
maa_version = list(
map(
int,
maa_info["data"]["version_name"][1:]
.replace("-beta", "")
.split("."),
)
)
while len(maa_version) < 4:
maa_version.append(0)
self.downloader = Updater(Path(folder), "MAA", maa_version, [])
self.downloader.show()
def show_password(self): def show_password(self):
if Config.PASSWORD == "": if Config.PASSWORD == "":
choice = InputMessageBox( choice = LineEditMessageBox(
self, self.window(),
"请输入管理密钥", "请输入管理密钥",
"管理密钥", "管理密钥",
"密码", "密码",
@@ -523,7 +589,7 @@ class MaaSettingBox(QWidget):
) )
) )
self.card_Set.clicked.connect( self.card_Set.clicked.connect(
lambda: Task_manager.add_task("设置MAA_全局", self.name, None) lambda: TaskManager.add_task("设置MAA_全局", self.name, None)
) )
Layout.addWidget(self.card_Name) Layout.addWidget(self.card_Name)
@@ -535,7 +601,11 @@ class MaaSettingBox(QWidget):
def PathClicked(self): def PathClicked(self):
folder = QFileDialog.getExistingDirectory(self, "选择MAA目录", "./") folder = QFileDialog.getExistingDirectory(
self,
"选择MAA目录",
Config.maa_config.get(Config.maa_config.MaaSet_Path),
)
if ( if (
not folder not folder
or Config.maa_config.get(Config.maa_config.MaaSet_Path) == folder or Config.maa_config.get(Config.maa_config.MaaSet_Path) == folder
@@ -568,16 +638,22 @@ class MaaSettingBox(QWidget):
class RunSetSettingCard(ExpandGroupSettingCard): class RunSetSettingCard(ExpandGroupSettingCard):
def __init__(self, parent=None): def __init__(self, parent=None):
super().__init__( super().__init__(FluentIcon.SETTING, "运行", "MAA运行调控选项", parent)
FluentIcon.SETTING,
"运行", self.card_TaskTransitionMethod = ComboBoxSettingCard(
"MAA运行调控选项", configItem=Config.maa_config.RunSet_TaskTransitionMethod,
parent, icon=FluentIcon.PAGE_RIGHT,
title="任务切换方式",
content="简洁用户列表下相邻两个任务间的切换方式",
texts=["直接切换账号", "重启明日方舟", "重启模拟器"],
)
self.ProxyTimesLimit = SpinBoxSettingCard(
(0, 1024),
FluentIcon.PAGE_RIGHT,
"用户单日代理次数上限",
"当用户本日代理成功次数超过该阈值时跳过代理阈值为“0”时视为无代理次数上限",
Config.maa_config.RunSet_ProxyTimesLimit,
) )
widget = QWidget()
Layout = QVBoxLayout(widget)
self.AnnihilationTimeLimit = SpinBoxSettingCard( self.AnnihilationTimeLimit = SpinBoxSettingCard(
(1, 1024), (1, 1024),
FluentIcon.PAGE_RIGHT, FluentIcon.PAGE_RIGHT,
@@ -585,7 +661,6 @@ class MaaSettingBox(QWidget):
"MAA日志无变化时间超过该阈值视为超时单位为分钟", "MAA日志无变化时间超过该阈值视为超时单位为分钟",
Config.maa_config.RunSet_AnnihilationTimeLimit, Config.maa_config.RunSet_AnnihilationTimeLimit,
) )
self.RoutineTimeLimit = SpinBoxSettingCard( self.RoutineTimeLimit = SpinBoxSettingCard(
(1, 1024), (1, 1024),
FluentIcon.PAGE_RIGHT, FluentIcon.PAGE_RIGHT,
@@ -593,7 +668,6 @@ class MaaSettingBox(QWidget):
"MAA日志无变化时间超过该阈值视为超时单位为分钟", "MAA日志无变化时间超过该阈值视为超时单位为分钟",
Config.maa_config.RunSet_RoutineTimeLimit, Config.maa_config.RunSet_RoutineTimeLimit,
) )
self.RunTimesLimit = SpinBoxSettingCard( self.RunTimesLimit = SpinBoxSettingCard(
(1, 1024), (1, 1024),
FluentIcon.PAGE_RIGHT, FluentIcon.PAGE_RIGHT,
@@ -602,13 +676,15 @@ class MaaSettingBox(QWidget):
Config.maa_config.RunSet_RunTimesLimit, Config.maa_config.RunSet_RunTimesLimit,
) )
widget = QWidget()
Layout = QVBoxLayout(widget)
Layout.addWidget(self.card_TaskTransitionMethod)
Layout.addWidget(self.ProxyTimesLimit)
Layout.addWidget(self.AnnihilationTimeLimit) Layout.addWidget(self.AnnihilationTimeLimit)
Layout.addWidget(self.RoutineTimeLimit) Layout.addWidget(self.RoutineTimeLimit)
Layout.addWidget(self.RunTimesLimit) Layout.addWidget(self.RunTimesLimit)
self.viewLayout.setContentsMargins(0, 0, 0, 0) self.viewLayout.setContentsMargins(0, 0, 0, 0)
self.viewLayout.setSpacing(0) self.viewLayout.setSpacing(0)
self.addGroupWidget(widget) self.addGroupWidget(widget)
class UserSettingCard(HeaderCardWidget): class UserSettingCard(HeaderCardWidget):
@@ -674,14 +750,15 @@ class MaaSettingBox(QWidget):
Config.cur.execute("SELECT * FROM adminx WHERE True") Config.cur.execute("SELECT * FROM adminx WHERE True")
data = Config.cur.fetchall() data = Config.cur.fetchall()
data = sorted(data, key=lambda x: (-len(x[15]), x[16]))
if self.user_list.pivot.currentRouteKey() == f"{self.name}_简洁用户列表": if self.user_list.pivot.currentRouteKey() == f"{self.name}_简洁用户列表":
user_list = [_[0] for _ in data if _[15] == "simple"] user_list = [_[0] for _ in data if _[15] == "simple"]
set_list = ["自定义基建"] set_list = ["自定义基建"]
choice = SetMessageBox( choice = ComboBoxMessageBox(
self.parent().parent().parent().parent().parent().parent().parent(), self.window(),
"用户选项配置", "用户选项配置",
["选择要配置的用户", "选择要配置的选项"], ["选择要配置的用户", "选择要配置的选项"],
[user_list, set_list], [user_list, set_list],
@@ -707,7 +784,7 @@ class MaaSettingBox(QWidget):
shutil.copy( shutil.copy(
file_path, file_path,
Config.app_path Config.app_path
/ f"config/MaaConfig/{self.name}/simple/{choice.input[0].currentIndex()}/infrastructure", / f"config/MaaConfig/{self.name}/simple/{choice.input[0].currentIndex()}/infrastructure/infrastructure.json",
) )
else: else:
logger.warning("未选择自定义基建文件") logger.warning("未选择自定义基建文件")
@@ -720,8 +797,8 @@ class MaaSettingBox(QWidget):
user_list = [_[0] for _ in data if _[15] == "beta"] user_list = [_[0] for _ in data if _[15] == "beta"]
set_list = ["MAA日常配置", "MAA剿灭配置"] set_list = ["MAA日常配置", "MAA剿灭配置"]
choice = SetMessageBox( choice = ComboBoxMessageBox(
self.parent().parent().parent().parent().parent().parent().parent(), self.window(),
"用户选项配置", "用户选项配置",
["选择要配置的用户", "选择要配置的选项"], ["选择要配置的用户", "选择要配置的选项"],
[user_list, set_list], [user_list, set_list],
@@ -733,7 +810,7 @@ class MaaSettingBox(QWidget):
): ):
set_book = ["routine", "annihilation"] set_book = ["routine", "annihilation"]
Task_manager.add_task( TaskManager.add_task(
"设置MAA_用户", "设置MAA_用户",
self.name, self.name,
{ {
@@ -867,6 +944,9 @@ class MaaSettingBox(QWidget):
self.user_list_simple.itemChanged.connect( self.user_list_simple.itemChanged.connect(
lambda item: self.change_user_Item(item, "simple") lambda item: self.change_user_Item(item, "simple")
) )
self.user_list_beta.itemChanged.connect(
lambda item: self.change_user_Item(item, "beta")
)
self.stackedWidget.addWidget(self.user_list_simple) self.stackedWidget.addWidget(self.user_list_simple)
self.pivot.addItem( self.pivot.addItem(
@@ -963,18 +1043,16 @@ class MaaSettingBox(QWidget):
elif j == 5: elif j == 5:
curdate = server_date() curdate = server_date()
if curdate != value: if curdate != value:
item = QTableWidgetItem("今日未代理") item = QTableWidgetItem("未代理")
else: else:
item = QTableWidgetItem( item = QTableWidgetItem(f"已代理{data_simple[i][14]}")
f"今日已代理{data_simple[i][14]}"
)
item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
elif j == 12: elif j == 12:
if Config.PASSWORD == "": if Config.PASSWORD == "":
item = QTableWidgetItem("******") item = QTableWidgetItem("******")
item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
else: else:
result = Crypto.decryptx(value, Config.PASSWORD) result = Crypto.AUTO_decryptor(value, Config.PASSWORD)
item = QTableWidgetItem(result) item = QTableWidgetItem(result)
if result == "管理密钥错误": if result == "管理密钥错误":
item.setFlags( item.setFlags(
@@ -1032,18 +1110,16 @@ class MaaSettingBox(QWidget):
elif j == 5: elif j == 5:
curdate = server_date() curdate = server_date()
if curdate != value: if curdate != value:
item = QTableWidgetItem("今日未代理") item = QTableWidgetItem("未代理")
else: else:
item = QTableWidgetItem( item = QTableWidgetItem(f"已代理{data_beta[i][14]}")
f"今日已代理{data_beta[i][14]}"
)
item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
elif j == 12: elif j == 12:
if Config.PASSWORD == "": if Config.PASSWORD == "":
item = QTableWidgetItem("******") item = QTableWidgetItem("******")
item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
else: else:
result = Crypto.decryptx(value, Config.PASSWORD) result = Crypto.AUTO_decryptor(value, Config.PASSWORD)
item = QTableWidgetItem(result) item = QTableWidgetItem(result)
if result == "管理密钥错误": if result == "管理密钥错误":
item.setFlags( item.setFlags(
@@ -1114,7 +1190,7 @@ class MaaSettingBox(QWidget):
games[game_in.strip()] = game_out.strip() games[game_in.strip()] = game_out.strip()
text = games.get(text, text) text = games.get(text, text)
if item.column() == 11: # 密码 if item.column() == 11: # 密码
text = Crypto.encryptx(text) text = Crypto.AUTO_encryptor(text)
# 保存至本地数据库 # 保存至本地数据库
if text != "": if text != "":
@@ -1132,7 +1208,7 @@ class MaaSettingBox(QWidget):
self.update_user_info("normal") self.update_user_info("normal")
return None return None
if item.column() == 6: # 密码 if item.column() == 6: # 密码
text = Crypto.encryptx(text) text = Crypto.AUTO_encryptor(text)
# 保存至本地数据库 # 保存至本地数据库
if text != "": if text != "":
@@ -1214,7 +1290,7 @@ class MaaSettingBox(QWidget):
Config.cur.execute( Config.cur.execute(
"INSERT INTO adminx VALUES('新用户','手机号码(官服)/B站IDB服','Official',-1,'y','2000-01-01','1-7','-','-','n','n','n',?,'',0,?,?)", "INSERT INTO adminx VALUES('新用户','手机号码(官服)/B站IDB服','Official',-1,'y','2000-01-01','1-7','-','-','n','n','n',?,'',0,?,?)",
( (
Crypto.encryptx("未设置"), Crypto.AUTO_encryptor("未设置"),
set_book[0], set_book[0],
set_book[1], set_book[1],
), ),
@@ -1264,15 +1340,7 @@ class MaaSettingBox(QWidget):
choice = MessageBox( choice = MessageBox(
"确认", "确认",
f"确定要删除用户 {data[0][0]} 吗?", f"确定要删除用户 {data[0][0]} 吗?",
self.parent() self.window(),
.parent()
.parent()
.parent()
.parent()
.parent()
.parent()
.parent()
.parent(),
) )
# 删除用户 # 删除用户
@@ -1348,6 +1416,10 @@ class MaaSettingBox(QWidget):
return None return None
if row == 0: if row == 0:
logger.warning("向上移动用户时已到达最上端")
MainInfoBar.push_info_bar(
"warning", "已经是第一个用户", "无法向上移动", 5000
)
return None return None
Config.cur.execute( Config.cur.execute(
@@ -1445,6 +1517,10 @@ class MaaSettingBox(QWidget):
return None return None
if row == current_numb - 1: if row == current_numb - 1:
logger.warning("向下移动用户时已到达最下端")
MainInfoBar.push_info_bar(
"warning", "已经是最后一个用户", "无法向下移动", 5000
)
return None return None
Config.cur.execute( Config.cur.execute(
@@ -1553,15 +1629,7 @@ class MaaSettingBox(QWidget):
choice = MessageBox( choice = MessageBox(
"确认", "确认",
f"确定要将用户 {data[0][0]} 转为{mode_list[1 - mode]}配置模式吗?", f"确定要将用户 {data[0][0]} 转为{mode_list[1 - mode]}配置模式吗?",
self.parent() self.window(),
.parent()
.parent()
.parent()
.parent()
.parent()
.parent()
.parent()
.parent(),
) )
# 切换用户 # 切换用户

View File

@@ -43,6 +43,7 @@ from qfluentwidgets import (
TextBrowser, TextBrowser,
CommandBar, CommandBar,
SwitchSettingCard, SwitchSettingCard,
ComboBoxSettingCard,
) )
from PySide6.QtCore import Qt from PySide6.QtCore import Qt
from typing import List from typing import List
@@ -59,10 +60,7 @@ from .Widget import (
class QueueManager(QWidget): class QueueManager(QWidget):
def __init__( def __init__(self, parent=None):
self,
parent=None,
):
super().__init__(parent) super().__init__(parent)
self.setObjectName("调度队列") self.setObjectName("调度队列")
@@ -138,7 +136,7 @@ class QueueManager(QWidget):
choice = MessageBox( choice = MessageBox(
"确认", "确认",
f"确定要删除 {name} 吗?", f"确定要删除 {name} 吗?",
self, self.window(),
) )
if choice.exec(): if choice.exec():
@@ -412,9 +410,23 @@ class QueueMemberSettingBox(QWidget):
"调度队列状态", "调度队列状态",
Config.queue_config.queueSet_Enabled, Config.queue_config.queueSet_Enabled,
) )
self.card_AfterAccomplish = ComboBoxSettingCard(
configItem=Config.queue_config.queueSet_AfterAccomplish,
icon=FluentIcon.POWER_BUTTON,
title="调度队列结束后",
content="选择调度队列结束后的操作",
texts=[
"无动作",
"退出AUTO_MAA",
"睡眠win系统需禁用休眠",
"休眠",
"关机",
],
)
Layout.addWidget(self.card_Name) Layout.addWidget(self.card_Name)
Layout.addWidget(self.card_Enable) Layout.addWidget(self.card_Enable)
Layout.addWidget(self.card_AfterAccomplish)
self.viewLayout.addLayout(Layout) self.viewLayout.addLayout(Layout)

View File

@@ -42,6 +42,7 @@ from qfluentwidgets import (
SwitchSettingCard, SwitchSettingCard,
ExpandGroupSettingCard, ExpandGroupSettingCard,
PushSettingCard, PushSettingCard,
ComboBoxSettingCard,
) )
from datetime import datetime from datetime import datetime
import json import json
@@ -52,24 +53,15 @@ import requests
from app.core import Config, MainInfoBar from app.core import Config, MainInfoBar
from app.services import Crypto, System from app.services import Crypto, System
from app.utils import Updater from app.utils import Updater
from .Widget import InputMessageBox, LineEditSettingCard from .Widget import LineEditMessageBox, LineEditSettingCard, PasswordLineEditSettingCard
class Setting(QWidget): class Setting(QWidget):
def __init__( def __init__(self, parent=None):
self,
parent=None,
):
super().__init__(parent) super().__init__(parent)
self.setObjectName("设置") self.setObjectName("设置")
layout = QVBoxLayout()
scrollArea = ScrollArea()
scrollArea.setWidgetResizable(True)
content_widget = QWidget() content_widget = QWidget()
content_layout = QVBoxLayout(content_widget) content_layout = QVBoxLayout(content_widget)
@@ -82,6 +74,7 @@ class Setting(QWidget):
self.other = OtherSettingCard(self) self.other = OtherSettingCard(self)
self.function.card_IfAllowSleep.checkedChanged.connect(System.set_Sleep) self.function.card_IfAllowSleep.checkedChanged.connect(System.set_Sleep)
self.function.card_IfAgreeBilibili.checkedChanged.connect(self.agree_bilibili)
self.start.card_IfSelfStart.checkedChanged.connect(System.set_SelfStart) self.start.card_IfSelfStart.checkedChanged.connect(System.set_SelfStart)
self.security.card_changePASSWORD.clicked.connect(self.change_PASSWORD) self.security.card_changePASSWORD.clicked.connect(self.change_PASSWORD)
self.updater.card_CheckUpdate.clicked.connect(self.get_update) self.updater.card_CheckUpdate.clicked.connect(self.get_update)
@@ -95,12 +88,38 @@ class Setting(QWidget):
content_layout.addWidget(self.updater) content_layout.addWidget(self.updater)
content_layout.addWidget(self.other) content_layout.addWidget(self.other)
scrollArea = ScrollArea()
scrollArea.setWidgetResizable(True)
scrollArea.setWidget(content_widget) scrollArea.setWidget(content_widget)
layout = QVBoxLayout()
layout.addWidget(scrollArea) layout.addWidget(scrollArea)
self.setLayout(layout) self.setLayout(layout)
def agree_bilibili(self) -> None:
"""授权bilibili游戏隐私政策"""
if not Config.global_config.get(Config.global_config.function_IfAgreeBilibili):
logger.info("取消授权bilibili游戏隐私政策")
MainInfoBar.push_info_bar(
"info", "操作成功", "已取消授权bilibili游戏隐私政策", 3000
)
return None
choice = MessageBox(
"授权声明",
"开启“托管bilibili游戏隐私政策”功能即代表您已完整阅读并同意《哔哩哔哩弹幕网用户使用协议》、《哔哩哔哩隐私政策》和《哔哩哔哩游戏中心用户协议》并授权AUTO_MAA在其认定需要时以其认定合适的方法替您处理相关弹窗\n\n是否同意授权?",
self.window(),
)
if choice.exec():
logger.success("确认授权bilibili游戏隐私政策")
MainInfoBar.push_info_bar(
"success", "操作成功", "已确认授权bilibili游戏隐私政策", 3000
)
else:
Config.global_config.set(
Config.global_config.function_IfAgreeBilibili, False
)
def check_PASSWORD(self) -> None: def check_PASSWORD(self) -> None:
"""检查并配置管理密钥""" """检查并配置管理密钥"""
@@ -109,8 +128,8 @@ class Setting(QWidget):
while True: while True:
choice = InputMessageBox( choice = LineEditMessageBox(
self.parent().parent().parent(), self.window(),
"未检测到管理密钥,请设置您的管理密钥", "未检测到管理密钥,请设置您的管理密钥",
"管理密钥", "管理密钥",
"密码", "密码",
@@ -122,12 +141,11 @@ class Setting(QWidget):
choice = MessageBox( choice = MessageBox(
"警告", "警告",
"您没有设置管理密钥,无法使用本软件,请先设置管理密钥", "您没有设置管理密钥,无法使用本软件,请先设置管理密钥",
self.parent().parent().parent(), self.window(),
) )
choice.cancelButton.hide() choice.cancelButton.hide()
choice.buttonLayout.insertStretch(1) choice.buttonLayout.insertStretch(1)
if choice.exec(): choice.exec()
pass
def change_PASSWORD(self) -> None: def change_PASSWORD(self) -> None:
"""修改管理密钥""" """修改管理密钥"""
@@ -136,8 +154,8 @@ class Setting(QWidget):
while if_change: while if_change:
choice = InputMessageBox( choice = LineEditMessageBox(
self, self.window(),
"请输入旧的管理密钥", "请输入旧的管理密钥",
"旧管理密钥", "旧管理密钥",
"密码", "密码",
@@ -151,8 +169,8 @@ class Setting(QWidget):
# 获取新的管理密钥 # 获取新的管理密钥
while True: while True:
choice = InputMessageBox( choice = LineEditMessageBox(
self, self.window(),
"请输入新的管理密钥", "请输入新的管理密钥",
"新管理密钥", "新管理密钥",
"密码", "密码",
@@ -172,23 +190,22 @@ class Setting(QWidget):
choice = MessageBox( choice = MessageBox(
"确认", "确认",
"您没有输入新的管理密钥,是否取消修改管理密钥?", "您没有输入新的管理密钥,是否取消修改管理密钥?",
self, self.window(),
) )
if choice.exec(): if choice.exec():
if_change = False if_change = False
break break
else: else:
choice = MessageBox("错误", "管理密钥错误", self) choice = MessageBox("错误", "管理密钥错误", self.window())
choice.cancelButton.hide() choice.cancelButton.hide()
choice.buttonLayout.insertStretch(1) choice.buttonLayout.insertStretch(1)
if choice.exec(): choice.exec()
pass
else: else:
choice = MessageBox( choice = MessageBox(
"确认", "确认",
"您没有输入管理密钥,是否取消修改管理密钥?", "您没有输入管理密钥,是否取消修改管理密钥?",
self, self.window(),
) )
if choice.exec(): if choice.exec():
break break
@@ -207,7 +224,7 @@ class Setting(QWidget):
for _ in range(3): for _ in range(3):
try: try:
response = requests.get( response = requests.get(
"https://gitee.com/DLmaster_361/AUTO_MAA/raw/main/resources/version.json" f"https://gitee.com/DLmaster_361/AUTO_MAA/raw/{Config.global_config.get(Config.global_config.update_UpdateType)}/resources/version.json"
) )
version_remote = response.json() version_remote = response.json()
break break
@@ -249,7 +266,7 @@ class Setting(QWidget):
for _ in range(3): for _ in range(3):
try: try:
response = requests.get( response = requests.get(
"https://gitee.com/DLmaster_361/AUTO_MAA/raw/main/resources/version.json" f"https://gitee.com/DLmaster_361/AUTO_MAA/raw/{Config.global_config.get(Config.global_config.update_UpdateType)}/resources/version.json"
) )
version_remote = response.json() version_remote = response.json()
break break
@@ -260,7 +277,7 @@ class Setting(QWidget):
choice = MessageBox( choice = MessageBox(
"错误", "错误",
f"获取版本信息时出错:\n{err}", f"获取版本信息时出错:\n{err}",
self, self.window(),
) )
choice.cancelButton.hide() choice.cancelButton.hide()
choice.buttonLayout.insertStretch(1) choice.buttonLayout.insertStretch(1)
@@ -296,7 +313,7 @@ class Setting(QWidget):
choice = MessageBox( choice = MessageBox(
"版本更新", "版本更新",
f"发现新版本:\n{main_version_info}{updater_version_info} 更新说明:\n{version_remote['announcement'].replace("\n# ","\n ").replace("\n## ","\n - ").replace("\n- ","\n · ")}\n\n是否开始更新?\n\n 注意主程序更新时AUTO_MAA将自动关闭", f"发现新版本:\n{main_version_info}{updater_version_info} 更新说明:\n{version_remote['announcement'].replace("\n# ","\n ").replace("\n## ","\n - ").replace("\n- ","\n · ")}\n\n是否开始更新?\n\n 注意主程序更新时AUTO_MAA将自动关闭",
self, self.window(),
) )
if not choice.exec(): if not choice.exec():
return None return None
@@ -314,7 +331,7 @@ class Setting(QWidget):
if main_version_remote > main_version_current: if main_version_remote > main_version_current:
self.updater.update_process.accomplish.connect(self.update_main) self.updater.update_process.accomplish.connect(self.update_main)
# 显示更新页面 # 显示更新页面
self.updater.ui.show() self.updater.show()
# 更新主程序 # 更新主程序
elif main_version_remote > main_version_current: elif main_version_remote > main_version_current:
@@ -338,7 +355,7 @@ class Setting(QWidget):
def show_notice(self, if_show: bool = True): def show_notice(self, if_show: bool = True):
"""显示公告""" """显示公告"""
# 从远程服务器获取最新版本信息 # 从远程服务器获取最新公告
for _ in range(3): for _ in range(3):
try: try:
response = requests.get( response = requests.get(
@@ -390,24 +407,42 @@ class FunctionSettingCard(HeaderCardWidget):
def __init__(self, parent=None): def __init__(self, parent=None):
super().__init__(parent) super().__init__(parent)
self.setTitle("功能") self.setTitle("功能")
Layout = QVBoxLayout() self.card_HomeImageMode = ComboBoxSettingCard(
configItem=Config.global_config.function_HomeImageMode,
icon=FluentIcon.PAGE_RIGHT,
title="主页背景图模式",
content="选择主页背景图的来源",
texts=["默认", "自定义", "主题图像"],
)
self.card_HistoryRetentionTime = ComboBoxSettingCard(
configItem=Config.global_config.function_HistoryRetentionTime,
icon=FluentIcon.PAGE_RIGHT,
title="历史记录保留时间",
content="选择历史记录的保留时间,超期自动清理",
texts=["7 天", "15 天", "30 天", "60 天", "永久"],
)
self.card_IfAllowSleep = SwitchSettingCard( self.card_IfAllowSleep = SwitchSettingCard(
icon=FluentIcon.PAGE_RIGHT, icon=FluentIcon.PAGE_RIGHT,
title="启动时阻止系统休眠", title="启动时阻止系统休眠",
content="仅阻止电脑自动休眠,不会影响屏幕是否熄灭", content="仅阻止电脑自动休眠,不会影响屏幕是否熄灭",
configItem=Config.global_config.function_IfAllowSleep, configItem=Config.global_config.function_IfAllowSleep,
) )
self.card_IfSilence = self.SilenceSettingCard(self) self.card_IfSilence = self.SilenceSettingCard(self)
self.card_IfAgreeBilibili = SwitchSettingCard(
icon=FluentIcon.PAGE_RIGHT,
title="托管bilibili游戏隐私政策",
content="授权AUTO_MAA同意bilibili游戏隐私政策",
configItem=Config.global_config.function_IfAgreeBilibili,
)
# 添加各组到设置卡中 Layout = QVBoxLayout()
Layout.addWidget(self.card_HomeImageMode)
Layout.addWidget(self.card_HistoryRetentionTime)
Layout.addWidget(self.card_IfAllowSleep) Layout.addWidget(self.card_IfAllowSleep)
Layout.addWidget(self.card_IfSilence) Layout.addWidget(self.card_IfSilence)
Layout.addWidget(self.card_IfAgreeBilibili)
self.viewLayout.addLayout(Layout) self.viewLayout.addLayout(Layout)
class SilenceSettingCard(ExpandGroupSettingCard): class SilenceSettingCard(ExpandGroupSettingCard):
@@ -420,31 +455,26 @@ class FunctionSettingCard(HeaderCardWidget):
parent, parent,
) )
widget = QWidget()
Layout = QVBoxLayout(widget)
self.card_IfSilence = SwitchSettingCard( self.card_IfSilence = SwitchSettingCard(
icon=FluentIcon.PAGE_RIGHT, icon=FluentIcon.PAGE_RIGHT,
title="静默模式", title="静默模式",
content="是否启用静默模式", content="是否启用静默模式",
configItem=Config.global_config.function_IfSilence, configItem=Config.global_config.function_IfSilence,
) )
self.card_BossKey = LineEditSettingCard( self.card_BossKey = LineEditSettingCard(
text="请输入安卓模拟器老", text="请输入安卓模拟器老",
icon=FluentIcon.PAGE_RIGHT, icon=FluentIcon.PAGE_RIGHT,
title="模拟器老", title="模拟器老",
content="输入模拟器老快捷键,以“+”分隔", content="输入模拟器老快捷键,以“+”分隔",
configItem=Config.global_config.function_BossKey, configItem=Config.global_config.function_BossKey,
) )
widget = QWidget()
Layout = QVBoxLayout(widget)
Layout.addWidget(self.card_IfSilence) Layout.addWidget(self.card_IfSilence)
Layout.addWidget(self.card_BossKey) Layout.addWidget(self.card_BossKey)
# 调整内部布局
self.viewLayout.setContentsMargins(0, 0, 0, 0) self.viewLayout.setContentsMargins(0, 0, 0, 0)
self.viewLayout.setSpacing(0) self.viewLayout.setSpacing(0)
self.addGroupWidget(widget) self.addGroupWidget(widget)
@@ -452,31 +482,31 @@ class StartSettingCard(HeaderCardWidget):
def __init__(self, parent=None): def __init__(self, parent=None):
super().__init__(parent) super().__init__(parent)
self.setTitle("启动") self.setTitle("启动")
Layout = QVBoxLayout()
self.card_IfSelfStart = SwitchSettingCard( self.card_IfSelfStart = SwitchSettingCard(
icon=FluentIcon.PAGE_RIGHT, icon=FluentIcon.PAGE_RIGHT,
title="开机时自动启动", title="开机时自动启动",
content="将AUTO_MAA添加到开机启动项", content="将AUTO_MAA添加到开机启动项",
configItem=Config.global_config.start_IfSelfStart, configItem=Config.global_config.start_IfSelfStart,
) )
self.card_IfRunDirectly = SwitchSettingCard( self.card_IfRunDirectly = SwitchSettingCard(
icon=FluentIcon.PAGE_RIGHT, icon=FluentIcon.PAGE_RIGHT,
title="启动后直接运行", title="启动后直接运行主任务",
content="启动AUTO_MAA后自动运行任务(暂不可用)", content="启动AUTO_MAA后自动运行自动代理任务,优先级:调度队列 1 > 脚本 1",
configItem=Config.global_config.start_IfRunDirectly, configItem=Config.global_config.start_IfRunDirectly,
) )
self.card_IfMinimizeDirectly = SwitchSettingCard(
# 添加各组到设置卡中 icon=FluentIcon.PAGE_RIGHT,
Layout.addWidget( title="启动后直接最小化",
self.card_IfSelfStart, content="启动AUTO_MAA后直接最小化",
configItem=Config.global_config.start_IfMinimizeDirectly,
) )
Layout.addWidget(self.card_IfRunDirectly)
Layout = QVBoxLayout()
Layout.addWidget(self.card_IfSelfStart)
Layout.addWidget(self.card_IfRunDirectly)
Layout.addWidget(self.card_IfMinimizeDirectly)
self.viewLayout.addLayout(Layout) self.viewLayout.addLayout(Layout)
@@ -484,18 +514,14 @@ class UiSettingCard(HeaderCardWidget):
def __init__(self, parent=None): def __init__(self, parent=None):
super().__init__(parent) super().__init__(parent)
self.setTitle("界面") self.setTitle("界面")
Layout = QVBoxLayout()
self.card_IfShowTray = SwitchSettingCard( self.card_IfShowTray = SwitchSettingCard(
icon=FluentIcon.PAGE_RIGHT, icon=FluentIcon.PAGE_RIGHT,
title="显示托盘图标", title="显示托盘图标",
content="常态显示托盘图标", content="常态显示托盘图标",
configItem=Config.global_config.ui_IfShowTray, configItem=Config.global_config.ui_IfShowTray,
) )
self.card_IfToTray = SwitchSettingCard( self.card_IfToTray = SwitchSettingCard(
icon=FluentIcon.PAGE_RIGHT, icon=FluentIcon.PAGE_RIGHT,
title="最小化到托盘", title="最小化到托盘",
@@ -503,10 +529,9 @@ class UiSettingCard(HeaderCardWidget):
configItem=Config.global_config.ui_IfToTray, configItem=Config.global_config.ui_IfToTray,
) )
# 添加各组到设置卡中 Layout = QVBoxLayout()
Layout.addWidget(self.card_IfShowTray) Layout.addWidget(self.card_IfShowTray)
Layout.addWidget(self.card_IfToTray) Layout.addWidget(self.card_IfToTray)
self.viewLayout.addLayout(Layout) self.viewLayout.addLayout(Layout)
@@ -517,34 +542,83 @@ class NotifySettingCard(HeaderCardWidget):
self.setTitle("通知") self.setTitle("通知")
self.card_NotifyContent = self.NotifyContentSettingCard(self)
self.card_Plyer = self.PlyerSettingCard(self)
self.card_EMail = self.EMailSettingCard(self)
self.card_ServerChan = self.ServerChanSettingCard(self)
self.card_CompanyWebhookBot = self.CompanyWechatPushSettingCard(self)
Layout = QVBoxLayout() Layout = QVBoxLayout()
Layout.addWidget(self.card_NotifyContent)
self.card_IfPushPlyer = SwitchSettingCard( Layout.addWidget(self.card_Plyer)
icon=FluentIcon.PAGE_RIGHT, Layout.addWidget(self.card_EMail)
title="推送系统通知", Layout.addWidget(self.card_ServerChan)
content="推送系统级通知,不会在通知中心停留", Layout.addWidget(self.card_CompanyWebhookBot)
configItem=Config.global_config.notify_IfPushPlyer,
)
self.card_SendMail = self.SendMailSettingCard(self)
Layout.addWidget(self.card_IfPushPlyer)
Layout.addWidget(self.card_SendMail)
self.viewLayout.addLayout(Layout) self.viewLayout.addLayout(Layout)
class SendMailSettingCard(ExpandGroupSettingCard): class NotifyContentSettingCard(ExpandGroupSettingCard):
def __init__(self, parent=None): def __init__(self, parent=None):
super().__init__( super().__init__(
FluentIcon.SETTING, FluentIcon.SETTING, "通知内容选项", "选择需要推送的通知内容", parent
"推送邮件通知", )
"通过AUTO_MAA官方通知服务邮箱推送任务结果",
parent, self.card_SendTaskResultTime = ComboBoxSettingCard(
configItem=Config.global_config.notify_SendTaskResultTime,
icon=FluentIcon.PAGE_RIGHT,
title="推送任务结果选项",
content="选择推送自动代理与人工排查任务结果的时机",
texts=["不推送", "任何时刻", "仅失败时"],
)
self.card_IfSendStatistic = SwitchSettingCard(
icon=FluentIcon.PAGE_RIGHT,
title="推送统计信息",
content="推送自动代理统计信息的通知",
configItem=Config.global_config.notify_IfSendStatistic,
)
self.card_IfSendSixStar = SwitchSettingCard(
icon=FluentIcon.PAGE_RIGHT,
title="推送公招高资喜报",
content="公招出现六星词条时推送喜报",
configItem=Config.global_config.notify_IfSendSixStar,
) )
widget = QWidget() widget = QWidget()
Layout = QVBoxLayout(widget) Layout = QVBoxLayout(widget)
Layout.addWidget(self.card_SendTaskResultTime)
Layout.addWidget(self.card_IfSendStatistic)
Layout.addWidget(self.card_IfSendSixStar)
self.viewLayout.setContentsMargins(0, 0, 0, 0)
self.viewLayout.setSpacing(0)
self.addGroupWidget(widget)
class PlyerSettingCard(ExpandGroupSettingCard):
def __init__(self, parent=None):
super().__init__(
FluentIcon.SETTING, "推送系统通知", "Plyer系统通知推送渠道", parent
)
self.card_IfPushPlyer = SwitchSettingCard(
icon=FluentIcon.PAGE_RIGHT,
title="推送系统通知",
content="使用Plyer推送系统级通知不会在通知中心停留",
configItem=Config.global_config.notify_IfPushPlyer,
)
widget = QWidget()
Layout = QVBoxLayout(widget)
Layout.addWidget(self.card_IfPushPlyer)
self.viewLayout.setContentsMargins(0, 0, 0, 0)
self.viewLayout.setSpacing(0)
self.addGroupWidget(widget)
class EMailSettingCard(ExpandGroupSettingCard):
def __init__(self, parent=None):
super().__init__(
FluentIcon.SETTING, "推送邮件通知", "电子邮箱通知推送渠道", parent
)
self.card_IfSendMail = SwitchSettingCard( self.card_IfSendMail = SwitchSettingCard(
icon=FluentIcon.PAGE_RIGHT, icon=FluentIcon.PAGE_RIGHT,
@@ -552,30 +626,122 @@ class NotifySettingCard(HeaderCardWidget):
content="是否启用邮件通知功能", content="是否启用邮件通知功能",
configItem=Config.global_config.notify_IfSendMail, configItem=Config.global_config.notify_IfSendMail,
) )
self.card_SMTPServerAddress = LineEditSettingCard(
self.MailAddress = LineEditSettingCard( text="请输入SMTP服务器地址",
text="请输入邮箱地址",
icon=FluentIcon.PAGE_RIGHT, icon=FluentIcon.PAGE_RIGHT,
title="邮箱地址", title="SMTP服务器地址",
content="发信邮箱的SMTP服务器地址",
configItem=Config.global_config.notify_SMTPServerAddress,
)
self.card_FromAddress = LineEditSettingCard(
text="请输入发信邮箱地址",
icon=FluentIcon.PAGE_RIGHT,
title="发信邮箱地址",
content="发送通知的邮箱地址",
configItem=Config.global_config.notify_FromAddress,
)
self.card_AuthorizationCode = PasswordLineEditSettingCard(
text="请输入发信邮箱授权码",
icon=FluentIcon.PAGE_RIGHT,
title="发信邮箱授权码",
content="发送通知的邮箱授权码",
configItem=Config.global_config.notify_AuthorizationCode,
)
self.card_ToAddress = LineEditSettingCard(
text="请输入收信邮箱地址",
icon=FluentIcon.PAGE_RIGHT,
title="收信邮箱地址",
content="接收通知的邮箱地址", content="接收通知的邮箱地址",
configItem=Config.global_config.notify_MailAddress, configItem=Config.global_config.notify_ToAddress,
)
self.card_IfSendErrorOnly = SwitchSettingCard(
icon=FluentIcon.PAGE_RIGHT,
title="仅推送异常信息",
content="仅在任务出现异常时推送通知",
configItem=Config.global_config.notify_IfSendErrorOnly,
) )
widget = QWidget()
Layout = QVBoxLayout(widget)
Layout.addWidget(self.card_IfSendMail) Layout.addWidget(self.card_IfSendMail)
Layout.addWidget(self.MailAddress) Layout.addWidget(self.card_SMTPServerAddress)
Layout.addWidget(self.card_IfSendErrorOnly) Layout.addWidget(self.card_FromAddress)
Layout.addWidget(self.card_AuthorizationCode)
# 调整内部布局 Layout.addWidget(self.card_ToAddress)
self.viewLayout.setContentsMargins(0, 0, 0, 0) self.viewLayout.setContentsMargins(0, 0, 0, 0)
self.viewLayout.setSpacing(0) self.viewLayout.setSpacing(0)
self.addGroupWidget(widget)
class ServerChanSettingCard(ExpandGroupSettingCard):
def __init__(self, parent=None):
super().__init__(
FluentIcon.SETTING,
"推送ServerChan通知",
"ServerChan通知推送渠道",
parent,
)
self.card_IfServerChan = SwitchSettingCard(
icon=FluentIcon.PAGE_RIGHT,
title="推送SeverChan通知",
content="是否启用SeverChan通知功能",
configItem=Config.global_config.notify_IfServerChan,
)
self.card_ServerChanKey = LineEditSettingCard(
text="请输入SendKey",
icon=FluentIcon.PAGE_RIGHT,
title="SendKey",
content="Server酱的SendKeySC3与SCT都可以",
configItem=Config.global_config.notify_ServerChanKey,
)
self.card_ServerChanChannel = LineEditSettingCard(
text="请输入需要推送的Channel代码SCT生效",
icon=FluentIcon.PAGE_RIGHT,
title="ServerChanChannel代码",
content="可以留空,留空则默认。可以多个,请使用“|”隔开",
configItem=Config.global_config.notify_ServerChanChannel,
)
self.card_ServerChanTag = LineEditSettingCard(
text="请输入加入推送的TagSC3生效",
icon=FluentIcon.PAGE_RIGHT,
title="Tag内容",
content="可以留空,留空则默认。可以多个,请使用“|”隔开",
configItem=Config.global_config.notify_ServerChanTag,
)
widget = QWidget()
Layout = QVBoxLayout(widget)
Layout.addWidget(self.card_IfServerChan)
Layout.addWidget(self.card_ServerChanKey)
Layout.addWidget(self.card_ServerChanChannel)
Layout.addWidget(self.card_ServerChanTag)
self.viewLayout.setContentsMargins(0, 0, 0, 0)
self.viewLayout.setSpacing(0)
self.addGroupWidget(widget)
class CompanyWechatPushSettingCard(ExpandGroupSettingCard):
def __init__(self, parent=None):
super().__init__(
FluentIcon.SETTING,
"推送企业微信机器人通知",
"企业微信机器人Webhook通知推送渠道",
parent,
)
self.card_IfCompanyWechat = SwitchSettingCard(
icon=FluentIcon.PAGE_RIGHT,
title="推送企业微信机器人通知",
content="是否启用企业微信机器人通知功能",
configItem=Config.global_config.notify_IfCompanyWebHookBot,
)
self.card_CompanyWebHookBotUrl = LineEditSettingCard(
text="请输入Webhook的Url",
icon=FluentIcon.PAGE_RIGHT,
title="WebhookUrl",
content="企业微信群机器人的Webhook地址",
configItem=Config.global_config.notify_CompanyWebHookBotUrl,
)
widget = QWidget()
Layout = QVBoxLayout(widget)
Layout.addWidget(self.card_IfCompanyWechat)
Layout.addWidget(self.card_CompanyWebHookBotUrl)
self.viewLayout.setContentsMargins(0, 0, 0, 0)
self.viewLayout.setSpacing(0)
self.addGroupWidget(widget) self.addGroupWidget(widget)
@@ -583,11 +749,8 @@ class SecuritySettingCard(HeaderCardWidget):
def __init__(self, parent=None): def __init__(self, parent=None):
super().__init__(parent) super().__init__(parent)
self.setTitle("安全") self.setTitle("安全")
Layout = QVBoxLayout()
self.card_changePASSWORD = PushSettingCard( self.card_changePASSWORD = PushSettingCard(
text="修改", text="修改",
icon=FluentIcon.VPN, icon=FluentIcon.VPN,
@@ -595,8 +758,8 @@ class SecuritySettingCard(HeaderCardWidget):
content="修改用于解密用户密码的管理密钥", content="修改用于解密用户密码的管理密钥",
) )
Layout = QVBoxLayout()
Layout.addWidget(self.card_changePASSWORD) Layout.addWidget(self.card_changePASSWORD)
self.viewLayout.addLayout(Layout) self.viewLayout.addLayout(Layout)
@@ -604,18 +767,21 @@ class UpdaterSettingCard(HeaderCardWidget):
def __init__(self, parent=None): def __init__(self, parent=None):
super().__init__(parent) super().__init__(parent)
self.setTitle("更新") self.setTitle("更新")
Layout = QVBoxLayout()
self.card_IfAutoUpdate = SwitchSettingCard( self.card_IfAutoUpdate = SwitchSettingCard(
icon=FluentIcon.PAGE_RIGHT, icon=FluentIcon.PAGE_RIGHT,
title="自动检查更新", title="自动检查更新",
content="将在启动时自动检查AUTO_MAA是否有新版本", content="将在启动时自动检查AUTO_MAA是否有新版本",
configItem=Config.global_config.update_IfAutoUpdate, configItem=Config.global_config.update_IfAutoUpdate,
) )
self.card_UpdateType = ComboBoxSettingCard(
configItem=Config.global_config.update_UpdateType,
icon=FluentIcon.PAGE_RIGHT,
title="版本更新类别",
content="选择AUTO_MAA的更新类别",
texts=["稳定版", "公测版"],
)
self.card_CheckUpdate = PushSettingCard( self.card_CheckUpdate = PushSettingCard(
text="检查更新", text="检查更新",
icon=FluentIcon.UPDATE, icon=FluentIcon.UPDATE,
@@ -623,9 +789,10 @@ class UpdaterSettingCard(HeaderCardWidget):
content="检查AUTO_MAA是否有新版本", content="检查AUTO_MAA是否有新版本",
) )
Layout = QVBoxLayout()
Layout.addWidget(self.card_IfAutoUpdate) Layout.addWidget(self.card_IfAutoUpdate)
Layout.addWidget(self.card_UpdateType)
Layout.addWidget(self.card_CheckUpdate) Layout.addWidget(self.card_CheckUpdate)
self.viewLayout.addLayout(Layout) self.viewLayout.addLayout(Layout)
@@ -633,7 +800,6 @@ class OtherSettingCard(HeaderCardWidget):
def __init__(self, parent=None): def __init__(self, parent=None):
super().__init__(parent) super().__init__(parent)
self.setTitle("其他") self.setTitle("其他")
self.card_Notice = PushSettingCard( self.card_Notice = PushSettingCard(
@@ -642,10 +808,18 @@ class OtherSettingCard(HeaderCardWidget):
title="公告", title="公告",
content="查看AUTO_MAA的最新公告", content="查看AUTO_MAA的最新公告",
) )
self.card_UserDocs = HyperlinkCard(
url="https://clozya.github.io/AUTOMAA_docs",
text="访问",
icon=FluentIcon.PAGE_RIGHT,
title="AUTO_MAA官方文档站",
content="访问AUTO_MAA的官方文档站获取使用指南和项目相关信息",
)
self.card_Association = self.AssociationSettingCard() self.card_Association = self.AssociationSettingCard()
Layout = QVBoxLayout() Layout = QVBoxLayout()
Layout.addWidget(self.card_Notice) Layout.addWidget(self.card_Notice)
Layout.addWidget(self.card_UserDocs)
Layout.addWidget(self.card_Association) Layout.addWidget(self.card_Association)
self.viewLayout.addLayout(Layout) self.viewLayout.addLayout(Layout)

View File

@@ -33,14 +33,16 @@ import subprocess
import time import time
from pathlib import Path from pathlib import Path
from PySide6.QtWidgets import ( from PySide6.QtWidgets import QApplication, QDialog, QVBoxLayout, QHBoxLayout
QApplication, from qfluentwidgets import (
QDialog, ProgressBar,
QVBoxLayout, IndeterminateProgressBar,
BodyLabel,
PushButton,
EditableComboBox,
) )
from qfluentwidgets import ProgressBar, IndeterminateProgressBar, BodyLabel from PySide6.QtGui import QIcon, QCloseEvent
from PySide6.QtGui import QIcon from PySide6.QtCore import QThread, Signal, QEventLoop
from PySide6.QtCore import QObject, QThread, Signal
def version_text(version_numb: list) -> str: def version_text(version_numb: list) -> str:
@@ -59,6 +61,8 @@ class UpdateProcess(QThread):
info = Signal(str) info = Signal(str)
progress = Signal(int, int, int) progress = Signal(int, int, int)
question = Signal(dict)
question_response = Signal(str)
accomplish = Signal() accomplish = Signal()
def __init__( def __init__(
@@ -70,8 +74,11 @@ class UpdateProcess(QThread):
self.name = name self.name = name
self.main_version = main_version self.main_version = main_version
self.updater_version = updater_version self.updater_version = updater_version
self.download_path = app_path / "AUTO_MAA_Update.zip" # 临时下载文件的路径 self.download_path = app_path / "DOWNLOAD_TEMP.zip" # 临时下载文件的路径
self.version_path = app_path / "resources/version.json" self.version_path = app_path / "resources/version.json"
self.response = None
self.question_response.connect(self._capture_response)
def run(self) -> None: def run(self) -> None:
@@ -81,25 +88,41 @@ class UpdateProcess(QThread):
self.info.emit("正在获取下载链接") self.info.emit("正在获取下载链接")
url_list = self.get_download_url() url_list = self.get_download_url()
url_dict = {}
# 验证下载地址
for i, url in enumerate(url_list):
if self.isInterruptionRequested():
return None
self.progress.emit(0, len(url_list), i)
# 验证下载地址并获取文件大小
for i in range(len(url_list)):
try: try:
self.info.emit(f"正在验证下载地址:{url_list[i]}") self.info.emit(f"正在验证下载地址:{url}")
response = requests.get(url_list[i], stream=True) response = requests.get(url, stream=True)
if response.status_code != 200: if response.status_code != 200:
self.info.emit( self.info.emit(f"连接失败,错误代码 {response.status_code}")
f"连接失败,错误代码 {response.status_code} ,正在切换代理({i+1}/{len(url_list)}"
)
time.sleep(1) time.sleep(1)
continue continue
file_size = response.headers.get("Content-Length") url_dict[url] = response.elapsed.total_seconds()
break
except requests.RequestException: except requests.RequestException:
self.info.emit(f"请求超时,正在切换代理({i+1}/{len(url_list)}") self.info.emit(f"请求超时")
time.sleep(1) time.sleep(1)
else:
self.info.emit(f"连接失败,已尝试所有{len(url_list)}个代理") download_url = self.push_question(url_dict)
# 获取文件大小
try:
self.info.emit(f"正在连接下载地址:{download_url}")
self.progress.emit(0, 0, 0)
response = requests.get(download_url, stream=True)
if response.status_code != 200:
self.info.emit(f"连接失败,错误代码 {response.status_code}")
return None
file_size = response.headers.get("Content-Length")
except requests.RequestException:
self.info.emit(f"请求超时")
return None return None
if file_size is None: if file_size is None:
@@ -118,6 +141,9 @@ class UpdateProcess(QThread):
for chunk in response.iter_content(chunk_size=8192): for chunk in response.iter_content(chunk_size=8192):
if self.isInterruptionRequested():
break
# 写入已下载数据 # 写入已下载数据
f.write(chunk) f.write(chunk)
downloaded_size += len(chunk) downloaded_size += len(chunk)
@@ -143,6 +169,10 @@ class UpdateProcess(QThread):
) )
self.progress.emit(0, 100, int(downloaded_size / file_size * 100)) self.progress.emit(0, 100, int(downloaded_size / file_size * 100))
if self.isInterruptionRequested() and self.download_path.exists():
self.download_path.unlink()
return None
except Exception as e: except Exception as e:
e = str(e) e = str(e)
e = "\n".join([e[_ : _ + 75] for _ in range(0, len(e), 75)]) e = "\n".join([e[_ : _ + 75] for _ in range(0, len(e), 75)])
@@ -153,6 +183,9 @@ class UpdateProcess(QThread):
try: try:
while True: while True:
if self.isInterruptionRequested():
self.download_path.unlink()
return None
try: try:
self.info.emit("正在解压更新文件") self.info.emit("正在解压更新文件")
self.progress.emit(0, 0, 0) self.progress.emit(0, 0, 0)
@@ -160,7 +193,7 @@ class UpdateProcess(QThread):
zip_ref.extractall(self.app_path) zip_ref.extractall(self.app_path)
break break
except PermissionError: except PermissionError:
self.info.emit("解压出错:AUTO_MAA正在运行,正在等待其关闭") self.info.emit(f"解压出错:{self.name}正在运行,正在等待其关闭")
time.sleep(1) time.sleep(1)
self.info.emit("正在删除临时文件") self.info.emit("正在删除临时文件")
@@ -178,22 +211,34 @@ class UpdateProcess(QThread):
return None return None
# 更新version文件 # 更新version文件
with open(self.version_path, "r", encoding="utf-8") as f: if not self.isInterruptionRequested and self.name in [
version_info = json.load(f) "AUTO_MAA主程序",
if self.name == "AUTO_MAA更新器": "AUTO_MAA更新器",
version_info["updater_version"] = ".".join(map(str, self.updater_version)) ]:
elif self.name == "AUTO_MAA主程序": with open(self.version_path, "r", encoding="utf-8") as f:
version_info["main_version"] = ".".join(map(str, self.main_version)) version_info = json.load(f)
with open(self.version_path, "w", encoding="utf-8") as f: if self.name == "AUTO_MAA主程序":
json.dump(version_info, f, ensure_ascii=False, indent=4) version_info["main_version"] = ".".join(map(str, self.main_version))
elif self.name == "AUTO_MAA更新器":
version_info["updater_version"] = ".".join(
map(str, self.updater_version)
)
with open(self.version_path, "w", encoding="utf-8") as f:
json.dump(version_info, f, ensure_ascii=False, indent=4)
# 主程序更新完成后打开AUTO_MAA # 主程序更新完成后打开AUTO_MAA
if self.name == "AUTO_MAA主程序": if not self.isInterruptionRequested and self.name == "AUTO_MAA主程序":
subprocess.Popen( subprocess.Popen(
str(self.app_path / "AUTO_MAA.exe"), str(self.app_path / "AUTO_MAA.exe"),
shell=True, shell=True,
creationflags=subprocess.CREATE_NO_WINDOW, creationflags=subprocess.CREATE_NO_WINDOW,
) )
elif not self.isInterruptionRequested and self.name == "MAA":
subprocess.Popen(
str(self.app_path / "MAA.exe"),
shell=True,
creationflags=subprocess.CREATE_NO_WINDOW,
)
self.accomplish.emit() self.accomplish.emit()
@@ -230,7 +275,7 @@ class UpdateProcess(QThread):
"https://gh.llkk.cc/", "https://gh.llkk.cc/",
"https://github.akams.cn/", "https://github.akams.cn/",
"https://www.ghproxy.cn/", "https://www.ghproxy.cn/",
"https://ghp.ci/", "https://ghfast.top/",
] ]
time.sleep(1) time.sleep(1)
@@ -239,6 +284,9 @@ class UpdateProcess(QThread):
url_list.append( url_list.append(
f"https://gitee.com/DLmaster_361/AUTO_MAA/releases/download/{version_text(self.main_version)}/AUTO_MAA_{version_text(self.main_version)}.zip" f"https://gitee.com/DLmaster_361/AUTO_MAA/releases/download/{version_text(self.main_version)}/AUTO_MAA_{version_text(self.main_version)}.zip"
) )
url_list.append(
f"https://jp-download.fearr.xyz/AUTO_MAA/AUTO_MAA_{version_text(self.main_version)}.zip"
)
for i in range(len(PROXY_list)): for i in range(len(PROXY_list)):
url_list.append( url_list.append(
f"{PROXY_list[i]}https://github.com/DLmaster361/AUTO_MAA/releases/download/{version_text(self.main_version)}/AUTO_MAA_{version_text(self.main_version)}.zip" f"{PROXY_list[i]}https://github.com/DLmaster361/AUTO_MAA/releases/download/{version_text(self.main_version)}/AUTO_MAA_{version_text(self.main_version)}.zip"
@@ -247,33 +295,63 @@ class UpdateProcess(QThread):
url_list.append( url_list.append(
f"https://gitee.com/DLmaster_361/AUTO_MAA/releases/download/{version_text(self.main_version)}/Updater_{version_text(self.updater_version)}.zip" f"https://gitee.com/DLmaster_361/AUTO_MAA/releases/download/{version_text(self.main_version)}/Updater_{version_text(self.updater_version)}.zip"
) )
url_list.append(
f"https://jp-download.fearr.xyz/AUTO_MAA/Updater_{version_text(self.updater_version)}.zip"
)
for i in range(len(PROXY_list)): for i in range(len(PROXY_list)):
url_list.append( url_list.append(
f"{PROXY_list[i]}https://github.com/DLmaster361/AUTO_MAA/releases/download/{version_text(self.main_version)}/Updater_{version_text(self.updater_version)}.zip" f"{PROXY_list[i]}https://github.com/DLmaster361/AUTO_MAA/releases/download/{version_text(self.main_version)}/Updater_{version_text(self.updater_version)}.zip"
) )
elif self.name == "MAA":
url_list.append(
f"https://jp-download.fearr.xyz/MAA/MAA-{version_text(self.main_version)}-win-x64.zip"
)
for i in range(len(PROXY_list)):
url_list.append(
f"{PROXY_list[i]}https://github.com/MaaAssistantArknights/MaaAssistantArknights/releases/download/{version_text(self.main_version)}/MAA-{version_text(self.main_version)}-win-x64.zip"
)
return url_list return url_list
def push_question(self, url_dict: dict) -> str:
self.question.emit(url_dict)
loop = QEventLoop()
self.question_response.connect(loop.quit)
loop.exec()
return self.response
class Updater(QObject): def _capture_response(self, response: str) -> None:
self.response = response
class Updater(QDialog):
def __init__( def __init__(
self, app_path: Path, name: str, main_version: list, updater_version: list self, app_path: Path, name: str, main_version: list, updater_version: list
) -> None: ) -> None:
super().__init__() super().__init__()
self.ui = QDialog() self.setWindowTitle("AUTO_MAA更新器")
self.ui.setWindowTitle("AUTO_MAA更新器") self.setWindowIcon(
self.ui.resize(700, 70) QIcon(
self.ui.setWindowIcon( str(
QIcon(str(app_path / "resources/icons/AUTO_MAA_Updater.ico")) Path(sys.argv[0]).resolve().parent
/ "resources/icons/AUTO_MAA_Updater.ico"
)
)
) )
# 创建垂直布局 # 创建垂直布局
self.Layout = QVBoxLayout(self.ui) self.Layout = QVBoxLayout(self)
self.info = BodyLabel("正在初始化", self.ui) self.info = BodyLabel("正在初始化", self)
self.progress_1 = IndeterminateProgressBar(self.ui) self.progress_1 = IndeterminateProgressBar(self)
self.progress_2 = ProgressBar(self.ui) self.progress_2 = ProgressBar(self)
self.combo_box = EditableComboBox(self)
self.button = PushButton("继续", self)
self.h_layout = QHBoxLayout()
self.h_layout.addStretch(1)
self.h_layout.addWidget(self.button)
self.update_progress(0, 0, 0) self.update_progress(0, 0, 0)
@@ -281,6 +359,8 @@ class Updater(QObject):
self.Layout.addStretch(1) self.Layout.addStretch(1)
self.Layout.addWidget(self.progress_1) self.Layout.addWidget(self.progress_1)
self.Layout.addWidget(self.progress_2) self.Layout.addWidget(self.progress_2)
self.Layout.addWidget(self.combo_box)
self.Layout.addLayout(self.h_layout)
self.Layout.addStretch(1) self.Layout.addStretch(1)
self.update_process = UpdateProcess( self.update_process = UpdateProcess(
@@ -289,21 +369,59 @@ class Updater(QObject):
self.update_process.info.connect(self.update_info) self.update_process.info.connect(self.update_info)
self.update_process.progress.connect(self.update_progress) self.update_process.progress.connect(self.update_progress)
self.update_process.question.connect(self.question)
self.update_process.start() self.update_process.start()
def update_info(self, text: str) -> None: def update_info(self, text: str) -> None:
self.info.setText(text) self.info.setText(text)
def update_progress(self, begin: int, end: int, current: int) -> None: def update_progress(
if begin == 0 and end == 0: self, begin: int, end: int, current: int, if_show_combo_box: bool = False
) -> None:
self.combo_box.setVisible(if_show_combo_box)
self.button.setVisible(if_show_combo_box)
if if_show_combo_box:
self.progress_1.setVisible(False)
self.progress_2.setVisible(False)
self.resize(1000, 90)
elif begin == 0 and end == 0:
self.progress_2.setVisible(False) self.progress_2.setVisible(False)
self.progress_1.setVisible(True) self.progress_1.setVisible(True)
self.resize(700, 70)
else: else:
self.progress_1.setVisible(False) self.progress_1.setVisible(False)
self.progress_2.setVisible(True) self.progress_2.setVisible(True)
self.progress_2.setRange(begin, end) self.progress_2.setRange(begin, end)
self.progress_2.setValue(current) self.progress_2.setValue(current)
self.resize(700, 70)
def question(self, url_dict: dict) -> None:
self.update_info("测速完成,请选择或自行输入一个合适下载地址:")
self.update_progress(0, 0, 0, True)
url_dict = dict(sorted(url_dict.items(), key=lambda item: item[1]))
for url, time in url_dict.items():
self.combo_box.addItem(f"{url} | 响应时间:{time:.3f}")
self.button.clicked.connect(
lambda: self.update_process.question_response.emit(
self.combo_box.currentText().split(" | ")[0]
)
)
def closeEvent(self, event: QCloseEvent):
"""清理残余进程"""
self.update_process.requestInterruption()
self.update_process.quit()
self.update_process.wait()
event.accept()
class AUTO_MAA_Updater(QApplication): class AUTO_MAA_Updater(QApplication):
@@ -313,13 +431,13 @@ class AUTO_MAA_Updater(QApplication):
super().__init__() super().__init__()
self.main = Updater(app_path, name, main_version, updater_version) self.main = Updater(app_path, name, main_version, updater_version)
self.main.ui.show() self.main.show()
if __name__ == "__main__": if __name__ == "__main__":
# 获取软件自身的路径 # 获取软件自身的路径
app_path = Path.cwd() app_path = Path(sys.argv[0]).resolve().parent
# 从本地版本信息文件获取当前版本信息 # 从本地版本信息文件获取当前版本信息
if (app_path / "resources/version.json").exists(): if (app_path / "resources/version.json").exists():
@@ -333,11 +451,22 @@ if __name__ == "__main__":
else: else:
main_version_current = [0, 0, 0, 0] main_version_current = [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 and "UpdateType" in config["Update"]:
update_type = config["Update"]["UpdateType"]
else:
update_type = "main"
else:
update_type = "main"
# 从远程服务器获取最新版本信息 # 从远程服务器获取最新版本信息
for _ in range(3): for _ in range(3):
try: try:
response = requests.get( response = requests.get(
"https://gitee.com/DLmaster_361/AUTO_MAA/raw/main/resources/version.json" f"https://gitee.com/DLmaster_361/AUTO_MAA/raw/{update_type}/resources/version.json"
) )
version_remote = response.json() version_remote = response.json()
main_version_remote = list( main_version_remote = list(

View File

@@ -8,4 +8,6 @@ pywin32
pyautogui pyautogui
pycryptodome pycryptodome
requests requests
Jinja2
serverchan_sdk
nuitka==2.6 nuitka==2.6

View File

@@ -9,6 +9,6 @@
"https://gh.llkk.cc/", "https://gh.llkk.cc/",
"https://github.akams.cn/", "https://github.akams.cn/",
"https://www.ghproxy.cn/", "https://www.ghproxy.cn/",
"https://ghp.ci/" "https://ghfast.top/"
] ]
} }

View File

@@ -1,5 +1,7 @@
#主界面 #主界面
"MainFunction.PostActions": "12" #完成后 "MainFunction.PostActions": "8" #完成后退出MAA
"MainFunction.PostActions": "9" #完成后退出MAA和游戏
"MainFunction.PostActions": "12" #完成后退出MAA和模拟器
"TaskQueue.WakeUp.IsChecked": "True" #开始唤醒 "TaskQueue.WakeUp.IsChecked": "True" #开始唤醒
"TaskQueue.Recruiting.IsChecked": "True" #自动公招 "TaskQueue.Recruiting.IsChecked": "True" #自动公招
"TaskQueue.Base.IsChecked": "True" #基建换班 "TaskQueue.Base.IsChecked": "True" #基建换班

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

BIN
resources/images/Home.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

View File

@@ -1,7 +1,7 @@
{ {
"main_version": "4.2.2.0", "main_version": "4.2.5.1",
"updater_version": "1.1.1.0", "updater_version": "1.1.2.1",
"announcement": "\n## 新增功能\n- 调度队列上线支持MAA多开 #12\n- 公告系统上线\n## 修复BUG\n- 修复手机号码不全时引发的混乱\n- 添加了一堆BUG确信\n## 程序优化\n- 界面重构,引入`QFluentWidgets`美化界面", "announcement": "\n## 新增功能\n- 暂无\n## 修复BUG\n- 修复统计信息HTML模板公招匹配错误\n## 程序优化\n- 暂无",
"proxy_list": [ "proxy_list": [
"", "",
"https://gitproxy.click/", "https://gitproxy.click/",
@@ -9,6 +9,6 @@
"https://gh.llkk.cc/", "https://gh.llkk.cc/",
"https://github.akams.cn/", "https://github.akams.cn/",
"https://www.ghproxy.cn/", "https://www.ghproxy.cn/",
"https://ghp.ci/" "https://ghfast.top/"
] ]
} }