Compare commits

...

193 Commits
v3.1 ... v4.2.2

Author SHA1 Message Date
DLmaster
37502b6fd8 Merge branch 'dev' 2025-01-28 20:05:58 +08:00
DLmaster
8c1c3c5675 发布v4.2.2 2025-01-28 20:04:56 +08:00
DLmaster
b4228e3f35 fix(core): 补充公告逻辑 2025-01-28 19:28:04 +08:00
DLmaster
e78f3973be fix(services): 修复管理密钥修改逻辑 2025-01-28 17:28:51 +08:00
DLmaster
29536003a4 fix(core): 修复调度队列为空时定时执行被反复调起 2025-01-28 16:13:12 +08:00
DLmaster
ffa3767198 fix(package): 固定nuitka版本号 2025-01-28 14:53:36 +08:00
DLmaster
8702070725 test package -2 2025-01-28 13:36:01 +08:00
DLmaster
793259a194 test package -1 2025-01-28 12:50:49 +08:00
DLmaster
3a63a73244 fix(test): 补充更新公告 2025-01-28 11:55:50 +08:00
DLmaster
43448cc68d fix(timer): 修复反复记录FailSafeException影响log阅读 2025-01-28 10:22:17 +08:00
DLmaster
f0d272cce5 Merge branch 'user_edit_dev' into dev 2025-01-27 23:18:34 +08:00
DLmaster
9983455b60 fix(maa): 修复手机号码不全时发生的混乱 2025-01-27 23:10:46 +08:00
DLmaster
0a411c150a fix(core): 规范自动化构筑流程 2025-01-27 22:29:45 +08:00
DLmaster
89b49a1143 Merge branch 'audio_dev' into dev 2025-01-27 22:17:54 +08:00
DLmaster
917454c0b9 fix(core): 调整公告方式 2025-01-27 22:17:31 +08:00
DLmaster
170b87e7a8 feat(core): 添加公告功能 2025-01-27 21:40:34 +08:00
DLmaster
68db248a7e 重复打包 2025-01-27 17:06:35 +08:00
DLmaster
24614099ed Merge branch 'fluent-gui-dev' into dev 2025-01-27 12:32:12 +08:00
DLmaster
8bbfdcbc04 fix(utils): 修复 version_text 引起的打包问题 2025-01-27 12:20:07 +08:00
DLmaster
126799d2a2 feat(core): 恢复基本所有原功能,数据文件版本更新 2025-01-27 10:38:17 +08:00
DLmaster
c625354dec feat(core):初步完成主调度自动代理功能开发 2025-01-26 07:58:33 +08:00
DLmaster
7e08c88a3e feat(core):初步完成定时执行功能开发 2025-01-25 18:00:56 +08:00
DLmaster
764d0afb50 Release v4.2.1 2025-01-22 19:01:01 +08:00
DLmaster
fe87547406 Merge pull request #16 from DLmaster361/dev
对于MAAv5.12.1版本后两个字段`Start.RunDirectly`与`Start.OpenEmulatorAfterLaunch`的适配
2025-01-22 18:53:19 +08:00
heziziziscool
a1fd27722b Merge remote-tracking branch 'origin/dev' into dev
# Conflicts:
#	app/models/MAA.py
2025-01-22 18:29:07 +08:00
heziziziscool
3bd6611cd5 对于MAAv5.12.1版本后两个字段Start.RunDirectlyStart.OpenEmulatorAfterLaunch的适配 2025-01-22 18:28:42 +08:00
heziziziscool
f3e1b4580a 对于MAAv5.12.1版本后两个字段Start.RunDirectlyStart.OpenEmulatorAfterLaunch的适配 2025-01-22 17:24:52 +08:00
heziziziscool
449d8a032e 对于MAAv5.12.1版本后两个字段Start.RunDirectlyStart.OpenEmulatorAfterLaunch的适配 2025-01-22 17:24:26 +08:00
heziziziscool
b40fa35622 对于MAAv5.12.1版本后两个字段Start.RunDirectlyStart.OpenEmulatorAfterLaunch的适配 2025-01-22 17:22:01 +08:00
DLmaster
ff7e433634 fix(gui): 修复界面显示的若干方法 2025-01-12 11:59:22 +08:00
DLmaster
1ea9d10bb4 修复开机自启时目录错误 2025-01-07 11:32:06 +08:00
DLmaster
04fd2b10fa 修改缓存文件夹 2025-01-06 18:45:48 +08:00
DLmaster
e79417ec5e 调度队列逻辑修复 2025-01-06 16:23:45 +08:00
DLmaster
684211c129 初步完成调度队列开发 2025-01-05 19:24:01 +08:00
DLmaster
f94e129cba 添加启动界面 2025-01-04 14:22:45 +08:00
DLmaster
87a140d373 覆盖pre版本 2025-01-04 04:03:27 +08:00
DLmaster
f135a6510f 设置界面与实例管理界面初步重构 2025-01-04 04:02:20 +08:00
DLmaster
6960184911 回退版本 2025-01-02 10:37:49 +08:00
DLmaster
7bd270c662 Merge branch 'fluent-gui-dev' 2025-01-01 21:55:16 +08:00
DLmaster
f54e83673f 初步完成UI美化 2025-01-01 21:50:40 +08:00
DLmaster
ee40fdb3c3 Merge branch 'main' 2025-01-01 11:54:55 +08:00
DLmaster
52ebf7b027 改用pathlib处理文件目录 2025-01-01 11:52:38 +08:00
DLmaster
85891dc918 修正更新器产品号 2024-12-30 23:16:21 +08:00
DLmaster
7348a87a20 优化自动化打包流程显示 2024-12-30 22:11:50 +08:00
DLmaster
1dfa3e3f44 Merge branch 'Dev' 2024-12-30 20:16:22 +08:00
DLmaster
37ced2e535 修改自动化流程提示文本样式 2024-12-30 20:01:38 +08:00
DLmaster
11876acc62 更新版本至4.2.0,添加nuitka支持,优化项目结构并模块化功能组件 2024-12-30 19:51:57 +08:00
DLmaster
756c0926ec 完善打包流程 2024-12-30 19:41:25 +08:00
DLmaster
9604fc9a8e 修正打包参数 2024-12-29 17:35:36 +08:00
DLmaster
5bcc527889 优化打包参数 2024-12-29 17:18:28 +08:00
DLmaster
0d616289ed 添加pyautogui异常逻辑捕获处理 2024-12-29 17:10:27 +08:00
DLmaster
a8473b6a04 清除无效整合步骤 2024-12-29 01:55:48 +08:00
DLmaster
e1352586b7 Updater改为独立打包 2024-12-29 01:42:03 +08:00
DLmaster
849d5f18eb 添加新架构测试文件 2024-12-29 00:01:55 +08:00
DLmaster
77298f4dab 修正工作流名称 2024-12-28 23:59:06 +08:00
DLmaster
b7a2b045fb 创建全新模块化架构 2024-12-28 23:31:50 +08:00
DLmaster
c7072da81d 同步“启动MAA后直接最小化”字段 2024-12-27 20:48:54 +08:00
DLmaster
4c9b9fb74a tips文案更新 2024-12-25 23:03:29 +08:00
DLmaster
feb4b516a4 修复初始设置的异常 2024-12-24 22:28:40 +08:00
DLmaster
d50191c6b8 添加psutil库 2024-12-23 21:19:24 +08:00
DLmaster
3db3fa4e1f 后台静默代理功能上线 2024-12-23 20:30:12 +08:00
DLmaster
ad31961c2b 修正使用方法适配最新版本 2024-12-19 17:52:16 +08:00
DLmaster
ccdaefeb61 统一标点符号 2024-12-18 02:51:54 +08:00
DLmaster
8c9bcba198 修正更新器的若干问题 2024-12-18 02:41:40 +08:00
DLmaster
a273af0abe 更新器优化;添加邮件仅推送异常信息选项 2024-12-18 02:04:40 +08:00
DLmaster
05a063b001 修改配置方法 2024-12-14 13:41:15 +08:00
DLmaster
8487195512 调整通知停留时长 2024-12-10 20:48:09 +08:00
DLmaster
250c47ccb7 添加托盘中止任务选项 2024-12-10 19:59:12 +08:00
DLmaster
e5aeb4f3a7 添加托盘名称 2024-12-05 22:12:57 +08:00
DLmaster
eab8d984e6 Merge branch 'main' of https://github.com/DLmaster361/AUTO_MAA 2024-12-05 21:37:14 +08:00
DLmaster
6b3f19a618 修复浅色模式下UI异常 2024-12-05 21:35:32 +08:00
DLmaster
d5f8871064 重构MainTimer逻辑 2024-12-05 17:21:39 +08:00
DLmaster
77e899957c Merge pull request #11 from ClozyA/patch-1
fix: MAA项目地址
2024-12-04 15:54:23 +08:00
AoXuan
dd3515305c fix: MAA项目地址 2024-12-04 15:17:49 +08:00
DLmaster
73c3ec4820 修复深色模式下UI异常 2024-12-04 14:27:58 +08:00
DLmaster
fde5160d56 补充重要声明 2024-12-01 10:46:10 +08:00
DLmaster
13923705e5 修复窗口管理方面的部分问题 2024-12-01 00:16:49 +08:00
DLmaster
d3afc95261 启动时不显示托盘 2024-11-29 18:58:50 +08:00
DLmaster
326d7e474c 修复新用户无法设置MAA路径 2024-11-29 18:02:16 +08:00
DLmaster
537b5d9725 移除最小化启动模拟器选项 2024-11-29 11:01:55 +08:00
DLmaster
697c1b5b43 优化记忆窗口与最小化到托盘的体验 2024-11-28 20:43:12 +08:00
DLmaster
2b6057161e 更新时彻底关闭主程序 2024-11-27 21:17:05 +08:00
DLmaster
8e2a6444bd 修复设定时刻无法中止任务的问题 2024-11-27 20:36:51 +08:00
DLmaster
b04df40b7d 图标优化;添加最小化到托盘功能;更新器摆脱UI文件限制 2024-11-27 00:04:37 +08:00
DLmaster
3e17d94904 合并MAA启动器配置流程 2024-11-21 19:52:15 +08:00
DLmaster
29123054cc 3^3 2024-11-19 20:31:52 +08:00
DLmaster
530c038985 3^3 2024-11-19 20:25:53 +08:00
DLmaster
2e9d2f0491 添加软件介绍 2024-11-19 20:20:46 +08:00
DLmaster
69ab9b4f15 邮件通知功能上线 2024-11-16 10:42:56 +08:00
DLmaster
1c8b940925 记忆窗口位置 2024-11-15 10:08:50 +08:00
DLmaster
0e05a4ea18 修正version配置 2024-11-14 15:50:06 +08:00
DLmaster
9e4285b4c5 发布4.1.2.0 2024-11-14 15:40:46 +08:00
DLmaster
e89d2af8ae 发布4.1.2正式版 2024-11-14 15:35:32 +08:00
DLmaster
8e7d663060 补充注释 2024-11-10 01:51:24 +08:00
DLmaster
179b33387e 完成主程序更新后打开AUTO_MAA 2024-11-10 01:46:38 +08:00
DLmaster
2c9a7c443f 加注释 2024-11-10 01:30:51 +08:00
DLmaster
0de964e68c 修复解压失败时本地版本号异常变动问题 2024-11-09 16:30:25 +08:00
DLmaster
97046af931 version修复缺失字段 2024-11-09 16:24:44 +08:00
DLmaster
ec24e776e7 修复#9与人工排查模式报错 2024-11-09 16:09:28 +08:00
DLmaster
92d4ef05fa 进一步限制log_text长度 2024-11-09 11:58:16 +08:00
DLmaster
fb27c50c75 解决log_text过长导致的log显示于中间 2024-11-09 11:15:44 +08:00
DLmaster
0141bf039a 添加清理可能存在的临时文件步骤 2024-11-08 22:11:48 +08:00
DLmaster
54f9bb7f21 修复url错误 2024-11-08 22:05:06 +08:00
DLmaster
2c4e5eb5cc 发布v4.1.2 2024-11-08 21:57:28 +08:00
DLmaster
cd42f45a1f 打包目录结构优化 2024-11-08 20:32:06 +08:00
DLmaster
ec38895765 修改更新器图标 2024-11-08 19:01:16 +08:00
DLmaster
fc30579bbc 修复部分组件缩放问题 2024-11-07 23:45:19 +08:00
DLmaster
3e6223e4e5 发布公测消息 2024-11-07 23:35:04 +08:00
DLmaster
27c71124ff 尝试解决log框卡中间问题 2024-11-07 23:18:34 +08:00
DLmaster
3a8a8a36f5 去除误注释 2024-11-07 21:55:04 +08:00
DLmaster
ed6de5e806 打包方式优化 2024-11-07 21:51:15 +08:00
DLmaster
bf8a5befa3 添加无限代理天数模式 2024-11-07 21:00:32 +08:00
DLmaster
a3fe641f6b 添加启动AUTO_MAA后直接代理功能 2024-11-07 17:25:58 +08:00
DLmaster
f4f99c25db 适配“启动MAA后自动开启模拟器”字段更改 2024-11-06 21:51:56 +08:00
DLmaster
640d80e334 当前为最新版本通知修复,报错格式化 2024-11-03 01:23:32 +08:00
DLmaster
4f196b516f 部分功能恢复 2024-11-03 00:55:18 +08:00
DLmaster
87f07bf95c 调整参数适配主分支 2024-11-02 23:57:23 +08:00
DLmaster
1bfd7891b5 Merge branch 'Updater' 2024-11-02 23:30:16 +08:00
DLmaster
472eb0ee52 修正部分措辞 2024-10-31 20:12:30 +08:00
DLmaster
7638c67da6 更详细的使用说明书 2024-10-31 20:04:17 +08:00
DLmaster
7a0c143b5b 适配MAA的adb连接文案修改 2024-10-29 08:55:01 +08:00
DLmaster
fff8e11524 恢复代理时的自动更新 2024-10-29 08:11:06 +08:00
DLmaster
af24208cf3 无命令行中止MAA进程 2024-10-26 15:31:49 +08:00
DLmaster
85bccea2dd 删除冗余代码 2024-10-26 11:20:12 +08:00
DLmaster
f7c8cac6ec 人工排查接管更多MAA启动设置 2024-10-26 10:41:02 +08:00
DLmaster
97a5ac5bb0 修复MAA切换配置选项 2024-10-25 18:50:34 +08:00
DLmaster
1d57076010 修复自定义基建默认配置 2024-10-25 18:30:15 +08:00
DLmaster
d298ac872c 优化自定义基建配置方法 2024-10-25 15:04:05 +08:00
DLmaster
c0581e781c 工作流适配新目录结构 2024-10-24 20:08:22 +08:00
DLmaster
6befd6341a gameid.txt改由主程序进行初始化 2024-10-24 20:03:23 +08:00
DLmaster
504ef8dd68 修改部分格式 2024-10-23 21:56:41 +08:00
DLmaster
127500d890 禁用MAA路径直接编辑;更新README至最新版 2024-10-23 21:41:35 +08:00
DLmaster
ab94173df7 支持B服,beta模式上线 2024-10-23 15:19:49 +08:00
DLmaster
e50697aae1 添加系统通知功能 2024-10-09 14:16:37 +08:00
DLmaster
c71389fc0b 优化MAA设置逻辑 2024-10-08 19:26:42 +08:00
DLmaster
732129ba34 修正变量命名逻辑 2024-10-07 20:08:26 +08:00
DLmaster
77dfc895ed 修复内部任务失败逻辑错误 2024-10-06 22:09:33 +08:00
DLmaster
0c6f3123f8 人工排查功能上线;修复未完成用户无法正确加载的问题 2024-10-06 15:48:06 +08:00
DLmaster
e872d30b3a 除去冗余通配符 2024-10-02 15:19:01 +08:00
DLmaster
48fa4413c8 修复打包目录结构 2024-10-02 14:54:42 +08:00
DLmaster
94e693db77 添加对内部任务失败的识别 2024-09-20 23:27:16 +08:00
DLmaster
025d328d79 修复索引错误 2024-09-10 11:55:54 +08:00
DLmaster
d086a5a152 修改更新release的逻辑 2024-09-10 08:44:10 +08:00
DLmaster
5f2337d949 修复部分语法错误 2024-09-10 08:12:38 +08:00
DLmaster
5e163181a8 去除测试标记 2024-09-10 07:50:13 +08:00
DLmaster
0913cbb813 修改部分语法错误 2024-09-10 07:40:42 +08:00
DLmaster
6aed64ce4e 添加创建zip动作 2024-09-10 07:32:32 +08:00
DLmaster
3c95e58fb2 五次测试 2024-09-09 23:02:25 +08:00
DLmaster
64d0d4ed41 四次测试 2024-09-09 22:45:05 +08:00
DLmaster
7fb659c511 三处测试 2024-09-09 22:40:00 +08:00
DLmaster
d4ec91940f 二次测试 2024-09-09 22:35:47 +08:00
DLmaster
77f63ebb44 测试 2024-09-09 22:24:34 +08:00
DLmaster
051b062e29 修复发布新版本时无法上传软件包 2024-09-09 22:14:21 +08:00
DLmaster
ec9083b556 修复发布新版本的换行符问题 2024-09-09 22:03:30 +08:00
DLmaster
156080a3b7 修复无法发布新版本 2024-09-09 21:55:08 +08:00
DLmaster
5b6effd43e 优化log文件读取策略,MAA运行判定修改前期准备 2024-09-09 21:34:06 +08:00
DLmaster
97e8d41c39 修复部分语法错误 2024-09-06 22:42:50 +08:00
DLmaster
d64ef447d2 修复部分语法错误 2024-09-06 22:35:07 +08:00
DLmaster
461b2a62b0 修复版本号不显示 2024-09-06 22:04:03 +08:00
DLmaster
f8988651b5 修复换行符 2024-09-06 21:34:06 +08:00
DLmaster
ffe865e29e 忽略部分pr以减少自动构建压力 2024-09-06 21:26:04 +08:00
DLmaster
cd5250c09f 适配最新语法规则 2024-09-06 21:20:04 +08:00
DLmaster
8f01bf6027 修复部分语法错误 2024-09-06 21:12:13 +08:00
DLmaster
81eb11f5c5 修复部分语法错误 2024-09-06 21:04:58 +08:00
DLmaster
80cadfa52c 添加覆盖更新能力 2024-09-06 20:30:57 +08:00
DLmaster
9974d70d99 修复部分语法错误 2024-09-06 20:19:28 +08:00
DLmaster
6e3e4662f4 修复部分语法错误 2024-09-06 20:12:19 +08:00
DLmaster
e2fb2e5565 修复部分语法错误 2024-09-06 19:48:30 +08:00
DLmaster
6f8be226b0 修复部分语法错误 2024-09-06 19:42:23 +08:00
DLmaster
2606f1e587 修复部分语法错误 2024-09-06 19:37:17 +08:00
DLmaster
4d1fd7ea76 修复部分语法错误 2024-09-06 19:31:58 +08:00
DLmaster
e61fbe6c69 修复部分语法错误 2024-09-06 19:18:50 +08:00
DLmaster
0c287577ca 精简步骤 2024-09-06 19:06:38 +08:00
DLmaster
a4aa562db9 修复部分语法错误 2024-09-06 17:32:26 +08:00
DLmaster
33c5ff3a52 修复部分语法错误 2024-09-06 17:03:18 +08:00
DLmaster
7c8e43bd35 修复部分语法错误 2024-09-06 16:34:05 +08:00
DLmaster
bc0014dbe6 修复部分语法错误 2024-09-06 16:26:36 +08:00
DLmaster
e41eca33bc 修复部分语法错误 2024-09-06 16:01:46 +08:00
DLmaster
e39b965459 修复部分语法错误 2024-09-06 14:57:25 +08:00
DLmaster
f2aa3d7347 修复部分语法错误 2024-09-06 14:51:33 +08:00
DLmaster
76c126284f 自动构建 2024-09-06 14:32:30 +08:00
DLmaster
44ee917d88 同步到MAA新版本的配置方法 2024-08-03 13:11:54 +08:00
DLmaster
669019c051 优化超时判定 2024-07-28 10:42:55 +08:00
DLmaster
4685c12570 Update python-app.yml
自动构建
2024-07-27 22:25:28 +08:00
DLmaster
d815529510 Update python-app.yml
自动构建
2024-07-27 22:03:02 +08:00
DLmaster
1da3620feb Update python-app.yml
自动构建
2024-07-27 22:00:23 +08:00
DLmaster
44d529c60f Update python-app.yml
自动构建
2024-07-27 21:53:49 +08:00
DLmaster
cdbcebd945 Update python-app.yml
自动构建
2024-07-27 21:51:37 +08:00
DLmaster
5bc2bf9397 Update python-app.yml
自动构建
2024-07-27 21:48:27 +08:00
DLmaster
d41419a579 Update python-app.yml
自动构建
2024-07-27 21:40:34 +08:00
DLmaster
a7e15e509e Create python-app.yml
自动构建
2024-07-27 21:30:41 +08:00
DLmaster
6518a378ac 修正v3.0_Beta版更新说明 2024-07-24 21:13:50 +08:00
DLmaster
ebb73182f7 添加开机自启与阻止休眠功能;优化配置文件处理方式 2024-07-24 20:57:15 +08:00
49 changed files with 7861 additions and 2169 deletions

158
.github/workflows/build-app.yml vendored Normal file
View File

@@ -0,0 +1,158 @@
# <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
name: Build AUTO_MAA
on:
push:
branches: [ "main" ]
paths-ignore:
- '**.md'
- 'LICENSE'
pull_request:
branches: [ "main" ]
paths-ignore:
- '**.md'
- 'LICENSE'
permissions:
contents: read
jobs:
pre_check:
name: Pre Checks
runs-on: ubuntu-latest
steps:
- name: Repo Check
id: repo_check
run: |
if [[ "$GITHUB_REPOSITORY" != "DLmaster361/AUTO_MAA" ]]; then
echo "When forking this repository to make your own builds, you have to adjust this check."
exit 1
fi
exit 0
build_AUTO_MAA:
runs-on: windows-latest
needs: pre_check
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python 3.12
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install flake8 pytest
pip install -r requirements.txt
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
- name: Package
id: package
run: |
copy app\utils\package.py .\
python package.py
- name: Read version
id: read_version
run: |
$MAIN_VERSION=(Get-Content -Path "version_info.txt" -TotalCount 1).Trim()
"AUTO_MAA_version=$MAIN_VERSION" | Out-File -FilePath $env:GITHUB_ENV -Append
$UPDATER_VERSION=(Get-Content -Path "version_info.txt" -TotalCount 2 | Select-Object -Index 1).Trim()
"updater_version=$UPDATER_VERSION" | Out-File -FilePath $env:GITHUB_ENV -Append
- name: Create Zip
id: create_zip
run: |
Compress-Archive -Path app,resources,main.py,AUTO_MAA.exe,requirements.txt,README.md,LICENSE -DestinationPath AUTO_MAA_${{ env.AUTO_MAA_version }}.zip
Compress-Archive -Path Updater.exe -DestinationPath Updater_${{ env.updater_version }}.zip
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: AUTO_MAA_${{ env.AUTO_MAA_version }}
path: |
AUTO_MAA_${{ env.AUTO_MAA_version }}.zip
Updater_${{ env.updater_version }}.zip
- name: Upload Version_Info Artifact
uses: actions/upload-artifact@v4
with:
name: version_info
path: version_info.txt
publish_release:
name: Publish release
needs: build_AUTO_MAA
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Download artifacts
uses: actions/download-artifact@v4
with:
pattern: AUTO_MAA_*
merge-multiple: true
path: artifacts
- name: Download Version_Info
uses: actions/download-artifact@v4
with:
name: version_info
path: ./
- name: Check if release exists
id: check_if_release_exists
run: |
release_id=$(gh release view $(sed 's/\r$//g' <(head -n 1 version_info.txt)) --json id --jq .id || true)
if [[ -z $release_id ]]; then
echo "release_exists=false" >> $GITHUB_OUTPUT
else
echo "release_exists=true" >> $GITHUB_OUTPUT
fi
env:
GITHUB_TOKEN: ${{ secrets.WORKFLOW_TOKEN }}
- name: Create release
id: create_release
if: steps.check_if_release_exists.outputs.release_exists == 'false'
run: |
set -xe
shopt -s nullglob
NAME="$(sed 's/\r$//g' <(head -n 1 version_info.txt))"
TAGNAME="$(sed 's/\r$//g' <(head -n 1 version_info.txt))"
NOTES_MAIN="$(sed 's/\r$//g' <(tail -n +3 version_info.txt))"
NOTES_TAIL="\`\`\`本release通过GitHub Actions自动构建\`\`\`"
NOTES="$NOTES_MAIN<br><br>$NOTES_TAIL"
gh release create "$TAGNAME" --target "main" --title "$NAME" --notes "$NOTES" artifacts/*
env:
GITHUB_TOKEN: ${{ secrets.WORKFLOW_TOKEN }}
- name: Update release
id: update_release
if: steps.check_if_release_exists.outputs.release_exists == 'true'
run: |
set -xe
shopt -s nullglob
NAME="$(sed 's/\r$//g' <(head -n 1 version_info.txt))"
TAGNAME="$(sed 's/\r$//g' <(head -n 1 version_info.txt))"
NOTES_MAIN="$(sed 's/\r$//g' <(tail -n +3 version_info.txt))"
NOTES_TAIL="\`\`\`本release通过GitHub Actions自动构建\`\`\`"
NOTES="$NOTES_MAIN<br><br>$NOTES_TAIL"
gh release delete "$TAGNAME" --yes
gh release create "$TAGNAME" --target "main" --title "$NAME" --notes "$NOTES" artifacts/*
env:
GITHUB_TOKEN: ${{ secrets.WORKFLOW_TOKEN }}

158
.github/workflows/build-pre.yml vendored Normal file
View File

@@ -0,0 +1,158 @@
# <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
name: Build AUTO_MAA_Pre
on:
push:
branches: [ "dev" ]
paths-ignore:
- '**.md'
- 'LICENSE'
pull_request:
branches: [ "dev" ]
paths-ignore:
- '**.md'
- 'LICENSE'
permissions:
contents: read
jobs:
pre_check:
name: Pre Checks
runs-on: ubuntu-latest
steps:
- name: Repo Check
id: repo_check
run: |
if [[ "$GITHUB_REPOSITORY" != "DLmaster361/AUTO_MAA" ]]; then
echo "When forking this repository to make your own builds, you have to adjust this check."
exit 1
fi
exit 0
build_AUTO_MAA:
runs-on: windows-latest
needs: pre_check
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python 3.12
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install flake8 pytest
pip install -r requirements.txt
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
- name: Package
id: package
run: |
copy app\utils\package.py .\
python package.py
- name: Read version
id: read_version
run: |
$MAIN_VERSION=(Get-Content -Path "version_info.txt" -TotalCount 1).Trim()
"AUTO_MAA_version=$MAIN_VERSION" | Out-File -FilePath $env:GITHUB_ENV -Append
$UPDATER_VERSION=(Get-Content -Path "version_info.txt" -TotalCount 2 | Select-Object -Index 1).Trim()
"updater_version=$UPDATER_VERSION" | Out-File -FilePath $env:GITHUB_ENV -Append
- name: Create Zip
id: create_zip
run: |
Compress-Archive -Path app,resources,main.py,AUTO_MAA.exe,requirements.txt,README.md,LICENSE -DestinationPath AUTO_MAA_${{ env.AUTO_MAA_version }}.zip
Compress-Archive -Path Updater.exe -DestinationPath Updater_${{ env.updater_version }}.zip
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: AUTO_MAA_${{ env.AUTO_MAA_version }}
path: |
AUTO_MAA_${{ env.AUTO_MAA_version }}.zip
Updater_${{ env.updater_version }}.zip
- name: Upload Version_Info Artifact
uses: actions/upload-artifact@v4
with:
name: version_info
path: version_info.txt
publish_prerelease:
name: Publish prerelease
needs: build_AUTO_MAA
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Download artifacts
uses: actions/download-artifact@v4
with:
pattern: AUTO_MAA_*
merge-multiple: true
path: artifacts
- name: Download Version_Info
uses: actions/download-artifact@v4
with:
name: version_info
path: ./
- name: Check if release exists
id: check_if_release_exists
run: |
release_id=$(gh release view $(sed 's/\r$//g' <(head -n 1 version_info.txt)) --json id --jq .id || true)
if [[ -z $release_id ]]; then
echo "release_exists=false" >> $GITHUB_OUTPUT
else
echo "release_exists=true" >> $GITHUB_OUTPUT
fi
env:
GITHUB_TOKEN: ${{ secrets.WORKFLOW_TOKEN }}
- name: Create prerelease
id: create_prerelease
if: steps.check_if_release_exists.outputs.release_exists == 'false'
run: |
set -xe
shopt -s nullglob
NAME="$(sed 's/\r$//g' <(head -n 1 version_info.txt))"
TAGNAME="$(sed 's/\r$//g' <(head -n 1 version_info.txt))"
NOTES_MAIN="$(sed 's/\r$//g' <(tail -n +3 version_info.txt))"
NOTES_TAIL="\`\`\`本release通过GitHub Actions自动构建\`\`\`"
NOTES="$NOTES_MAIN<br><br>$NOTES_TAIL"
gh release create "$TAGNAME" --target "main" --title "$NAME" --notes "$NOTES" --prerelease artifacts/*
env:
GITHUB_TOKEN: ${{ secrets.WORKFLOW_TOKEN }}
- name: Update prerelease
id: update_prerelease
if: steps.check_if_release_exists.outputs.release_exists == 'true'
run: |
set -xe
shopt -s nullglob
NAME="$(sed 's/\r$//g' <(head -n 1 version_info.txt))"
TAGNAME="$(sed 's/\r$//g' <(head -n 1 version_info.txt))"
NOTES_MAIN="$(sed 's/\r$//g' <(tail -n +3 version_info.txt))"
NOTES_TAIL="\`\`\`本release通过GitHub Actions自动构建\`\`\`"
NOTES="$NOTES_MAIN<br><br>$NOTES_TAIL"
gh release delete "$TAGNAME" --yes
gh release create "$TAGNAME" --target "main" --title "$NAME" --notes "$NOTES" --prerelease artifacts/*
env:
GITHUB_TOKEN: ${{ secrets.WORKFLOW_TOKEN }}

Binary file not shown.

File diff suppressed because it is too large Load Diff

231
README.md
View File

@@ -1,120 +1,209 @@
# AUTO_MAA
MAA多账号管理与自动化软件
!["软件图标"](https://github.com/DLmaster361/AUTO_MAA/blob/main/res/AUTO_MAA.png "软件图标")
----------------------------------------------------------------------------------------------
## 重要声明
本软件是一个外部工具旨在优化MAA多账号功能体验。该软件包可以存储明日方舟多账号数据并通过修改MAA配置文件、读取MAA日志等行为自动完成多账号代理。本开发团队承诺不会修改明日方舟游戏本体与相关配置文件。
本项目使用GPL开源相关细则如下
- **作者:** AUTO_MAA软件作者为DLmaster、DLmaster361或DLmaster_361以上均指代同一人。
- **使用:** AUTO_MAA使用者可以按自己的意愿自由使用本软件。依据GPL对于由此可能产生的损失AUTO_MAA项目组不负任何责任
- **分发:** AUTO_MAA允许任何人自由分发本软件包括进行商业活动牟利。但所有分发者必须遵循GPL向接收者提供本软件项目地址、完整的软件源码与GPL协议原文违反者可能会被追究法律责任
- **传播:** AUTO_MAA原则上允许传播者自由传播本软件。但由于软件性质项目组不希望发现任何人在明日方舟官方媒体包括官方媒体账号与森空岛社区等或明日方舟游戏相关内容包括同好群、线下活动与游戏内容讨论等下提及AUTO_MAA或MAA希望各位理解
- **衍生:** AUTO_MAA允许任何人对软件本体或软件部分代码进行二次开发或利用。但依据GPL相关成果也必须使用GPL开源
- **授权:** 如果希望在使用AUTO_MAA的相关成果后仍保持自己的项目闭源请在Issues中说明来意。得到项目组认可后我们可以提供另一份使用不同协议的代码此协议主要内容如下被授权者可以自由使用该代码并维持闭源被授权者必须定期为AUTO_MAA作出贡献
- **贡献:** 不论是直接参与软件的维护编写或是撰写文档、测试、反馈BUG、给出建议、参与讨论都为AUTO_MAA项目的发展完善做出了不可忽视的贡献。项目组提倡各位贡献者遵照GitHub开源社区惯例发布Issues参与项目。避免私信或私发邮件安全性漏洞或敏感问题除外以帮助更多用户
以上细则是本项目对GPL的相关补充与强调。未提及的以GPL为准发生冲突的以本细则为准。如有不清楚的部分请发Issues询问。若发生纠纷相关内容也没有在Issues上提及的项目组拥有最终解释权
**注意**
- 由于本软件有修改其它目录JSON文件等行为使用前请将AUTO_MAA添加入Windows Defender信任区以及防病毒软件的信任区或开发者目录避免被误杀
!["软件图标"](https://github.com/DLmaster361/AUTO_MAA/blob/main/resources/images/AUTO_MAA.png "软件图标")
---
</h1>
[![GitHub Stars](https://img.shields.io/github/stars/DLmaster361/AUTO_MAA?style=flat-square)](https://github.com/DLmaster361/AUTO_MAA/stargazers)
[![GitHub Forks](https://img.shields.io/github/forks/DLmaster361/AUTO_MAA?style=flat-square)](https://github.com/DLmaster361/AUTO_MAA/network)
[![GitHub Issues](https://img.shields.io/github/issues/DLmaster361/AUTO_MAA?style=flat-square)](https://github.com/DLmaster361/AUTO_MAA/issues)
[![GitHub Contributors](https://img.shields.io/github/contributors/DLmaster361/AUTO_MAA?style=flat-square)](https://github.com/DLmaster361/AUTO_MAA/graphs/contributors)
[![GitHub License](https://img.shields.io/github/license/DLmaster361/AUTO_MAA?style=flat-square)](https://github.com/DLmaster361/AUTO_MAA/blob/main/LICENSE)
</div>
## 软件介绍
### 性质
本软件是明日方舟第三方软件`MAA`的第三方工具即第3<sup>3</sup>方软件。旨在优化MAA多账号功能体验并通过一些方法解决MAA项目未能解决的部分问题提高代理的稳定性。
### 原理
本软件可以存储多个明日方舟账号数据,并通过以下流程实现代理功能:
1. **配置:** 根据对应用户的配置信息生成配置文件并将其导入MAA。
2. **监测:** 在MAA开始代理后持续读取MAA的日志以判断其运行状态。当软件认定MAA出现异常时通过重启MAA使之仍能继续完成任务。
3. **循环:** 重复上述步骤使MAA依次完成各个用户的自动代理任务。
### 优势
- **节省运行开销:** 只需要一份MAA软件与一个模拟器无需多开就能完成多账号代理羸弱的电脑也能代理日常。
- **自定义空间大:** 依靠高级用户配置模式支持MAA几乎所有设置选项自定义同时保留对模拟器多开的支持。
- **一键代理无忧:** 无须中途手动修改MAA配置将繁琐交给AUTO_MAA把游戏留给自己。
- **代理结果复核:** 通过人工排查功能核实各用户代理情况,堵住自动代理的最后一丝风险。
## 重要声明
本开发团队承诺不会修改明日方舟游戏本体与相关配置文件。本项目使用GPL开源相关细则如下
- **作者:** AUTO_MAA软件作者为DLmaster、DLmaster361或DLmaster_361以上均指代同一人。
- **使用:** AUTO_MAA使用者可以按自己的意愿自由使用本软件。依据GPL对于由此可能产生的损失AUTO_MAA项目组不负任何责任。
- **分发:** AUTO_MAA允许任何人自由分发本软件包括进行商业活动牟利。若为直接分发本软件必须遵循GPL向接收者提供本软件项目地址、完整的软件源码与GPL协议原文若为修改软件后进行分发必须遵循GPL向接收者提供本软件项目地址、修改前的完整软件源码副本与GPL协议原文违反者可能会被追究法律责任。
- **传播:** AUTO_MAA原则上允许传播者自由传播本软件但无论在何种传播过程中不得删除项目作者与开发者所留版权声明不得隐瞒项目作者与相关开发者的存在。由于软件性质项目组不希望发现任何人在明日方舟官方媒体包括官方媒体账号与森空岛社区等或明日方舟游戏相关内容包括同好群、线下活动与游戏内容讨论等下提及AUTO_MAA或MAA希望各位理解。
- **衍生:** AUTO_MAA允许任何人对软件本体或软件部分代码进行二次开发或利用。但依据GPL相关成果也必须使用GPL开源。
- **授权:** 如果希望在使用AUTO_MAA的相关成果后仍保持自己的项目闭源请在Issues中说明来意。得到项目组认可后我们可以提供另一份使用不同协议的代码此协议主要内容如下被授权者可以自由使用该代码并维持闭源被授权者必须定期为AUTO_MAA作出贡献。
- **贡献:** 不论是直接参与软件的维护编写或是撰写文档、测试、反馈BUG、给出建议、参与讨论都为AUTO_MAA项目的发展完善做出了不可忽视的贡献。项目组提倡各位贡献者遵照GitHub开源社区惯例发布Issues参与项目。避免私信或私发邮件安全性漏洞或敏感问题除外以帮助更多用户。
以上细则是本项目对GPL的相关补充与强调。未提及的以GPL为准发生冲突的以本细则为准。如有不清楚的部分请发Issues询问。若发生纠纷相关内容也没有在Issues上提及的项目组拥有最终解释权。
**注意**
- 由于本软件有修改其它目录JSON文件等行为使用前请将AUTO_MAA添加入Windows Defender信任区以及防病毒软件的信任区或开发者目录避免被误杀。
---
# 使用方法
## 安装与配置MAA
## 安装软件
```
本软件是MAA的外部工具需要安装配置MAA后才能使用。
本软件是MAA的外部工具需要安装MAA后才能使用。
```
### MAA安装
### 下载MAA
- 什么是MAA [官网](https://maa.plus/)/[GitHub](https://github.com/CHNZYX/Auto_Simulated_Universe/archive/refs/heads/main.zip)
- 什么是MAA [官网](https://maa.plus/)/[GitHub](https://github.com/MaaAssistantArknights/MaaAssistantArknights)
- MAA下载地址 [GitHub下载](https://github.com/MaaAssistantArknights/MaaAssistantArknights/releases)
### MAA配置
### 安装MAA
1. 完成MAA的adb配置等基本配置
- 将MAA压缩包解压至任意普通文件夹即可。
2. 确保当前配置名为“Default”取消所有“定时执行”
- 若为首次安装MAA请双击`MAA.exe`启动MAA程序以生成MAA配置文件。
![MAA配置1](https://github.com/DLmaster361/AUTO_MAA/blob/main/res/README/MAA配置1.png "MAA配置1")
3. 取消勾选“开机自启动MAA”勾选“启动MAA后直接运行”和“启动MAA后自动开启模拟器”。配置自己模拟器所在的位置并根据实际情况填写“等待模拟器启动时间”建议预留10s以防意外。如果是多开用户需要填写“附加命令”具体填写值参见多开模拟器对应快捷方式路径如“-v 1”
![MAA配置2](https://github.com/DLmaster361/AUTO_MAA/blob/main/res/README/MAA配置2.png "MAA配置2")
4. 勾选“定时检查更新”、“自动下载更新包”和“自动安装更新包”
![MAA配置3](https://github.com/DLmaster361/AUTO_MAA/blob/main/res/README/MAA配置3.png "MAA配置3")
## 下载AUTO_MAA [![](https://img.shields.io/github/downloads/DLmaster361/AUTO_MAA/total?color=66ccff)](https://github.com/DLmaster361/AUTO_MAA/releases)
### 下载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
- 双击`AUTO_MAA.exe`以启动软件
- 双击`AUTO_MAA.exe`以启动软件
```
注意:
首次启动时会要求设置管理密钥。
首次启动时会要求设置管理密钥。
管理密钥是解密用户密码的唯一凭证,与数据库绑定。
密钥丢失或data/key/目录下任一文件损坏都将导致解密无法正常进行。
管理密钥是解密用户密码的唯一凭证,与数据库绑定。
密钥丢失或data/key/目录下任一文件损坏都将导致解密无法正常进行。
本项目采用自主开发的混合加密模式项目组也无法找回您的管理密钥或修复data/key/目录下的文件。
如果不幸的事发生建议您删除data/key/目录与data/data.db文件后重新录入信息。
本项目采用自主开发的混合加密模式项目组也无法找回您的管理密钥或修复data/key/目录下的文件。
如果不幸的事发生建议您删除data/key/目录与data/data.db文件后重新录入信息。
```
### 配置信息
- 本项目已初步完成GUI开发您可以在用户管理页与设置页配置信息。
#### 设置MAA
```
特别的:
1. 通过`浏览`绑定MAA后单击`设置MAA`进行MAA全局设置。
你可以自定义关卡号替换方案
程序会读取data/gameid.txt中的数据依据此进行关卡号的替换便于常用关卡的使用。
gameid.txt在初始已经存储了一些常用资源本的替代方案。
```
2. 在打开的MAA界面完成`性能设置``游戏设置``连接设置``启动设置``界面设置``软件更新`等基本配置以及代理任务的详细配置
![gameid](https://github.com/DLmaster361/AUTO_MAA/blob/main/res/README/gameid.png "gameid")
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项目组欢迎反馈bug
## 未来开发方向
QQ群957750551
- [ ] 尝试接入更多开源社区成果
- [ ] 支持对MAA运行状况的进一步识别
- [x] 添加更多通知手段
- [ ] GUI界面美化
----------------------------------------------------------------------------------------------
作者即将高考,如果喜欢本项目,请祝福他
----------------------------------------------------------------------------------------------
## 贡献者
感谢以下贡献者对本项目做出的贡献
@@ -129,4 +218,16 @@ QQ群957750551
## 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)
## 交流与赞助
欢迎加入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)
---
如果喜欢这个项目的话,给作者来杯咖啡吧!
![payid](https://github.com/DLmaster361/AUTO_MAA/blob/main/resources/README/payid.png "payid")

51
app/__init__.py Normal file
View File

@@ -0,0 +1,51 @@
# <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
"""
__version__ = "4.2.0"
__author__ = "DLmaster361 <DLmaster_361@163.com>"
__license__ = "GPL-3.0 license"
from .core import AppConfig, QueueConfig, MaaConfig, Task, Task_manager, Main_timer
from .models import MaaManager
from .services import Notify, Crypto, System
from .ui import AUTO_MAA
from .utils import Updater
__all__ = [
"AppConfig",
"QueueConfig",
"MaaConfig",
"Task",
"Task_manager",
"Main_timer",
"MaaManager",
"Notify",
"Crypto",
"System",
"AUTO_MAA",
"Updater",
]

46
app/core/__init__.py Normal file
View File

@@ -0,0 +1,46 @@
# <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
"""
__version__ = "4.2.0"
__author__ = "DLmaster361 <DLmaster_361@163.com>"
__license__ = "GPL-3.0 license"
from .config import AppConfig, QueueConfig, MaaConfig, Config
from .main_info_bar import MainInfoBar
from .task_manager import Task, Task_manager
from .timer import Main_timer
__all__ = [
"AppConfig",
"Config",
"QueueConfig",
"MaaConfig",
"MainInfoBar",
"Task",
"Task_manager",
"Main_timer",
]

601
app/core/config.py Normal file
View File

@@ -0,0 +1,601 @@
# <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
import sqlite3
import json
import sys
import shutil
from pathlib import Path
from qfluentwidgets import (
QConfig,
ConfigItem,
OptionsConfigItem,
RangeConfigItem,
FolderValidator,
BoolValidator,
RangeValidator,
)
class AppConfig:
def __init__(self) -> None:
self.app_path = Path(sys.argv[0]).resolve().parent # 获取软件根目录
self.app_path_sys = str(Path(sys.argv[0]).resolve()) # 获取软件自身的路径
self.log_path = self.app_path / "debug/AUTO_MAA.log"
self.database_path = self.app_path / "data/data.db"
self.config_path = self.app_path / "config/config.json"
self.history_path = self.app_path / "config/history.json"
self.key_path = self.app_path / "data/key"
self.gameid_path = self.app_path / "data/gameid.txt"
self.version_path = self.app_path / "resources/version.json"
self.PASSWORD = ""
self.running_list = []
self.silence_list = []
self.if_database_opened = False
# 检查文件完整性
self.initialize()
def initialize(self) -> None:
"""初始化程序的配置文件"""
# 检查目录
(self.app_path / "config").mkdir(parents=True, exist_ok=True)
(self.app_path / "data").mkdir(parents=True, exist_ok=True)
(self.app_path / "debug").mkdir(parents=True, exist_ok=True)
# 生成版本信息文件
if not self.version_path.exists():
version = {
"main_version": "0.0.0.0",
"updater_version": "0.0.0.0",
}
with self.version_path.open(mode="w", encoding="utf-8") as f:
json.dump(version, f, ensure_ascii=False, indent=4)
# 生成预设gameid替换方案文件
if not self.gameid_path.exists():
self.gameid_path.write_text(
"龙门币CE-6\n技能CA-5\n红票AP-5\n经验LS-6\n剿灭模式Annihilation",
encoding="utf-8",
)
self.init_logger()
self.init_config()
self.check_data()
logger.info("程序配置管理模块初始化完成")
def init_logger(self) -> None:
"""初始化日志记录器"""
logger.remove(0)
logger.add(
sink=self.log_path,
level="DEBUG",
format="<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | <level>{level: <8}</level> | <level>{message}</level>",
enqueue=True,
backtrace=True,
diagnose=True,
rotation="1 week",
retention="1 month",
compression="zip",
)
logger.info("===================================")
logger.info("AUTO_MAA 主程序")
logger.info("版本号: v4.2.1.1")
logger.info(f"根目录: {self.app_path}")
logger.info("===================================")
logger.info("日志记录器初始化完成")
def init_config(self) -> None:
"""初始化配置类"""
self.global_config = GlobalConfig()
self.queue_config = QueueConfig()
self.maa_config = MaaConfig()
logger.info("配置类初始化完成")
def init_database(self, mode: str) -> None:
"""初始化用户数据库"""
if mode == "Maa":
self.cur.execute(
"CREATE TABLE adminx(admin text,id text,server text,day int,status text,last date,game text,game_1 text,game_2 text,routine text,annihilation text,infrastructure text,password byte,notes text,numb int,mode text,uid int)"
)
self.cur.execute("CREATE TABLE version(v text)")
self.cur.execute("INSERT INTO version VALUES(?)", ("v1.4",))
self.db.commit()
logger.info("用户数据库初始化完成")
def check_data(self) -> None:
"""检查用户数据文件并处理数据文件版本更新"""
# 生成主数据库
if not self.database_path.exists():
db = sqlite3.connect(self.database_path)
cur = db.cursor()
cur.execute("CREATE TABLE version(v text)")
cur.execute("INSERT INTO version VALUES(?)", ("v1.4",))
db.commit()
cur.close()
db.close()
# 数据文件版本更新
db = sqlite3.connect(self.database_path)
cur = db.cursor()
cur.execute("SELECT * FROM version WHERE True")
version = cur.fetchall()
if version[0][0] != "v1.4":
logger.info("数据文件版本更新开始")
if_streaming = False
# v1.0-->v1.1
if version[0][0] == "v1.0" or if_streaming:
logger.info("数据文件版本更新v1.0-->v1.1")
if_streaming = True
cur.execute("SELECT * FROM adminx WHERE True")
data = cur.fetchall()
cur.execute("DROP TABLE IF EXISTS adminx")
cur.execute(
"CREATE TABLE adminx(admin text,id text,server text,day int,status text,last date,game text,game_1 text,game_2 text,routines text,annihilation text,infrastructure text,password byte,notes text,numb int,mode text,uid int)"
)
for i in range(len(data)):
cur.execute(
"INSERT INTO adminx VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
(
data[i][0], # 0 0 0
data[i][1], # 1 1 -
"Official", # 2 2 -
data[i][2], # 3 3 1
data[i][3], # 4 4 2
data[i][4], # 5 5 3
data[i][5], # 6 6 -
data[i][6], # 7 7 -
data[i][7], # 8 8 -
"y", # 9 - 4
data[i][8], # 10 9 5
data[i][9], # 11 10 -
data[i][10], # 12 11 6
data[i][11], # 13 12 7
data[i][12], # 14 - -
"simple", # 15 - -
data[i][13], # 16 - -
),
)
cur.execute("DELETE FROM version WHERE v = ?", ("v1.0",))
cur.execute("INSERT INTO version VALUES(?)", ("v1.1",))
db.commit()
# v1.1-->v1.2
if version[0][0] == "v1.1" or if_streaming:
logger.info("数据文件版本更新v1.1-->v1.2")
if_streaming = True
cur.execute("SELECT * FROM adminx WHERE True")
data = cur.fetchall()
for i in range(len(data)):
cur.execute(
"UPDATE adminx SET infrastructure = 'n' WHERE mode = ? AND uid = ?",
(
data[i][15],
data[i][16],
),
)
cur.execute("DELETE FROM version WHERE v = ?", ("v1.1",))
cur.execute("INSERT INTO version VALUES(?)", ("v1.2",))
db.commit()
# v1.2-->v1.3
if version[0][0] == "v1.2" or if_streaming:
logger.info("数据文件版本更新v1.2-->v1.3")
if_streaming = True
cur.execute("ALTER TABLE adminx RENAME COLUMN routines TO routine")
cur.execute("DELETE FROM version WHERE v = ?", ("v1.2",))
cur.execute("INSERT INTO version VALUES(?)", ("v1.3",))
db.commit()
# v1.3-->v1.4
if version[0][0] == "v1.3" or if_streaming:
logger.info("数据文件版本更新v1.3-->v1.4")
if_streaming = True
(self.app_path / "config/MaaConfig").mkdir(parents=True, exist_ok=True)
shutil.move(
self.app_path / "data/MaaConfig",
self.app_path / "config/MaaConfig",
)
(self.app_path / "config/MaaConfig/MaaConfig").rename(
self.app_path / "config/MaaConfig/脚本_1"
)
shutil.copy(
self.database_path,
self.app_path / "config/MaaConfig/脚本_1/user_data.db",
)
cur.execute("DROP TABLE IF EXISTS adminx")
cur.execute("DELETE FROM version WHERE v = ?", ("v1.3",))
cur.execute("INSERT INTO version VALUES(?)", ("v1.4",))
db.commit()
with (self.app_path / "config/gui.json").open(
"r", encoding="utf-8"
) as f:
info = json.load(f)
maa_config = {
"MaaSet": {
"Name": "",
"Path": info["Default"]["MaaSet.path"],
},
"RunSet": {
"AnnihilationTimeLimit": info["Default"][
"TimeLimit.annihilation"
],
"RoutineTimeLimit": info["Default"]["TimeLimit.routine"],
"RunTimesLimit": info["Default"]["TimesLimit.run"],
},
}
with (self.app_path / "config/MaaConfig/脚本_1/config.json").open(
"w", encoding="utf-8"
) as f:
json.dump(maa_config, f, ensure_ascii=False, indent=4)
config = {
"Function": {
"BossKey": info["Default"]["SelfSet.BossKey"],
"IfAllowSleep": bool(
info["Default"]["SelfSet.IfSleep"] == "True"
),
"IfSilence": bool(
info["Default"]["SelfSet.IfSilence"] == "True"
),
},
"Notify": {
"IfPushPlyer": True,
"IfSendErrorOnly": bool(
info["Default"]["SelfSet.IfSendMail.OnlyError"] == "True"
),
"IfSendMail": bool(
info["Default"]["SelfSet.IfSendMail"] == "True"
),
"MailAddress": info["Default"]["SelfSet.MailAddress"],
},
"Start": {
"IfRunDirectly": bool(
info["Default"]["SelfSet.IfProxyDirectly"] == "True"
),
"IfSelfStart": bool(
info["Default"]["SelfSet.IfSelfStart"] == "True"
),
},
"UI": {
"IfShowTray": bool(
info["Default"]["SelfSet.IfToTray"] == "True"
),
"IfToTray": bool(info["Default"]["SelfSet.IfToTray"] == "True"),
"location": info["Default"]["SelfSet.UIlocation"],
"maximized": bool(
info["Default"]["SelfSet.UImaximized"] == "True"
),
"size": info["Default"]["SelfSet.UIsize"],
},
"Update": {"IfAutoUpdate": False},
}
with (self.app_path / "config/config.json").open(
"w", encoding="utf-8"
) as f:
json.dump(config, f, ensure_ascii=False, indent=4)
queue_config = {
"QueueSet": {"Enabled": True, "Name": ""},
"Queue": {
"Member_1": "脚本_1",
"Member_10": "禁用",
"Member_2": "禁用",
"Member_3": "禁用",
"Member_4": "禁用",
"Member_5": "禁用",
"Member_6": "禁用",
"Member_7": "禁用",
"Member_8": "禁用",
"Member_9": "禁用",
},
"Time": {
"TimeEnabled_0": bool(
info["Default"]["TimeSet.set1"] == "True"
),
"TimeEnabled_1": bool(
info["Default"]["TimeSet.set2"] == "True"
),
"TimeEnabled_2": bool(
info["Default"]["TimeSet.set3"] == "True"
),
"TimeEnabled_3": bool(
info["Default"]["TimeSet.set4"] == "True"
),
"TimeEnabled_4": bool(
info["Default"]["TimeSet.set5"] == "True"
),
"TimeEnabled_5": bool(
info["Default"]["TimeSet.set6"] == "True"
),
"TimeEnabled_6": bool(
info["Default"]["TimeSet.set7"] == "True"
),
"TimeEnabled_7": bool(
info["Default"]["TimeSet.set8"] == "True"
),
"TimeEnabled_8": bool(
info["Default"]["TimeSet.set9"] == "True"
),
"TimeEnabled_9": bool(
info["Default"]["TimeSet.set10"] == "True"
),
"TimeSet_0": info["Default"]["TimeSet.run1"],
"TimeSet_1": info["Default"]["TimeSet.run2"],
"TimeSet_2": info["Default"]["TimeSet.run3"],
"TimeSet_3": info["Default"]["TimeSet.run4"],
"TimeSet_4": info["Default"]["TimeSet.run5"],
"TimeSet_5": info["Default"]["TimeSet.run6"],
"TimeSet_6": info["Default"]["TimeSet.run7"],
"TimeSet_7": info["Default"]["TimeSet.run8"],
"TimeSet_8": info["Default"]["TimeSet.run9"],
"TimeSet_9": info["Default"]["TimeSet.run10"],
},
}
(self.app_path / "config/QueueConfig").mkdir(
parents=True, exist_ok=True
)
with (self.app_path / "config/QueueConfig/调度队列_1.json").open(
"w", encoding="utf-8"
) as f:
json.dump(queue_config, f, ensure_ascii=False, indent=4)
(self.app_path / "config/gui.json").unlink()
cur.close()
db.close()
logger.info("数据文件版本更新完成")
def open_database(self, mode: str, index: str = None) -> None:
"""打开数据库"""
self.close_database()
self.db = sqlite3.connect(
self.app_path / f"config/{mode}Config/{index}/user_data.db"
)
self.cur = self.db.cursor()
self.if_database_opened = True
def close_database(self) -> None:
"""关闭数据库"""
if self.if_database_opened:
self.cur.close()
self.db.close()
self.if_database_opened = False
def change_user_info(
self,
data_path: Path,
modes: list,
uids: list,
days: list,
lasts: list,
notes: list,
numbs: list,
) -> None:
"""将代理完成后发生改动的用户信息同步至本地数据库"""
db = sqlite3.connect(data_path / "user_data.db")
cur = db.cursor()
for index in range(len(uids)):
cur.execute(
"UPDATE adminx SET day = ? WHERE mode = ? AND uid = ?",
(days[index], modes[index], uids[index]),
)
cur.execute(
"UPDATE adminx SET last = ? WHERE mode = ? AND uid = ?",
(lasts[index], modes[index], uids[index]),
)
cur.execute(
"UPDATE adminx SET notes = ? WHERE mode = ? AND uid = ?",
(notes[index], modes[index], uids[index]),
)
cur.execute(
"UPDATE adminx SET numb = ? WHERE mode = ? AND uid = ?",
(numbs[index], modes[index], uids[index]),
)
db.commit()
cur.close()
db.close()
def save_history(self, key: str, content: dict) -> None:
"""保存历史记录"""
history = {}
if self.history_path.exists():
with self.history_path.open(mode="r", encoding="utf-8") as f:
history = json.load(f)
history[key] = content
with self.history_path.open(mode="w", encoding="utf-8") as f:
json.dump(history, f, ensure_ascii=False, indent=4)
def get_history(self, key: str) -> dict:
"""获取历史记录"""
history = {}
if self.history_path.exists():
with self.history_path.open(mode="r", encoding="utf-8") as f:
history = json.load(f)
return history.get(
key, {"Time": "0000-00-00 00:00", "History": "暂无历史运行记录"}
)
def clear_maa_config(self) -> None:
"""清空MAA配置"""
self.maa_config.set(self.maa_config.MaaSet_Name, "")
self.maa_config.set(self.maa_config.MaaSet_Path, ".")
self.maa_config.set(self.maa_config.RunSet_AnnihilationTimeLimit, 40)
self.maa_config.set(self.maa_config.RunSet_RoutineTimeLimit, 10)
self.maa_config.set(self.maa_config.RunSet_RunTimesLimit, 3)
self.maa_config.set(self.maa_config.MaaSet_Name, "")
self.maa_config.set(self.maa_config.MaaSet_Name, "")
self.maa_config.set(self.maa_config.MaaSet_Name, "")
def clear_queue_config(self) -> None:
"""清空队列配置"""
self.queue_config.set(self.queue_config.queueSet_Name, "")
self.queue_config.set(self.queue_config.queueSet_Enabled, False)
self.queue_config.set(self.queue_config.time_TimeEnabled_0, False)
self.queue_config.set(self.queue_config.time_TimeSet_0, "00:00")
self.queue_config.set(self.queue_config.time_TimeEnabled_1, False)
self.queue_config.set(self.queue_config.time_TimeSet_1, "00:00")
self.queue_config.set(self.queue_config.time_TimeEnabled_2, False)
self.queue_config.set(self.queue_config.time_TimeSet_2, "00:00")
self.queue_config.set(self.queue_config.time_TimeEnabled_3, False)
self.queue_config.set(self.queue_config.time_TimeSet_3, "00:00")
self.queue_config.set(self.queue_config.time_TimeEnabled_4, False)
self.queue_config.set(self.queue_config.time_TimeSet_4, "00:00")
self.queue_config.set(self.queue_config.time_TimeEnabled_5, False)
self.queue_config.set(self.queue_config.time_TimeSet_5, "00:00")
self.queue_config.set(self.queue_config.time_TimeEnabled_6, False)
self.queue_config.set(self.queue_config.time_TimeSet_6, "00:00")
self.queue_config.set(self.queue_config.time_TimeEnabled_7, False)
self.queue_config.set(self.queue_config.time_TimeSet_7, "00:00")
self.queue_config.set(self.queue_config.time_TimeEnabled_8, False)
self.queue_config.set(self.queue_config.time_TimeSet_8, "00:00")
self.queue_config.set(self.queue_config.time_TimeEnabled_9, False)
self.queue_config.set(self.queue_config.time_TimeSet_9, "00:00")
self.queue_config.set(self.queue_config.queue_Member_1, "禁用")
self.queue_config.set(self.queue_config.queue_Member_2, "禁用")
self.queue_config.set(self.queue_config.queue_Member_3, "禁用")
self.queue_config.set(self.queue_config.queue_Member_4, "禁用")
self.queue_config.set(self.queue_config.queue_Member_5, "禁用")
self.queue_config.set(self.queue_config.queue_Member_6, "禁用")
self.queue_config.set(self.queue_config.queue_Member_7, "禁用")
self.queue_config.set(self.queue_config.queue_Member_8, "禁用")
self.queue_config.set(self.queue_config.queue_Member_9, "禁用")
self.queue_config.set(self.queue_config.queue_Member_10, "禁用")
class GlobalConfig(QConfig):
"""全局配置"""
function_IfAllowSleep = ConfigItem(
"Function", "IfAllowSleep", False, BoolValidator()
)
function_IfSilence = ConfigItem("Function", "IfSilence", False, BoolValidator())
function_BossKey = ConfigItem("Function", "BossKey", "")
start_IfSelfStart = ConfigItem("Start", "IfSelfStart", False, BoolValidator())
start_IfRunDirectly = ConfigItem("Start", "IfRunDirectly", False, BoolValidator())
ui_IfShowTray = ConfigItem("UI", "IfShowTray", False, BoolValidator())
ui_IfToTray = ConfigItem("UI", "IfToTray", False, BoolValidator())
ui_size = ConfigItem("UI", "size", "1200x700")
ui_location = ConfigItem("UI", "location", "100x100")
ui_maximized = ConfigItem("UI", "maximized", False, BoolValidator())
notify_IfPushPlyer = ConfigItem("Notify", "IfPushPlyer", False, BoolValidator())
notify_IfSendMail = ConfigItem("Notify", "IfSendMail", False, BoolValidator())
notify_IfSendErrorOnly = ConfigItem(
"Notify", "IfSendErrorOnly", False, BoolValidator()
)
notify_MailAddress = ConfigItem("Notify", "MailAddress", "")
update_IfAutoUpdate = ConfigItem("Update", "IfAutoUpdate", False, BoolValidator())
class QueueConfig(QConfig):
"""队列配置"""
queueSet_Name = ConfigItem("QueueSet", "Name", "")
queueSet_Enabled = ConfigItem("QueueSet", "Enabled", False, BoolValidator())
time_TimeEnabled_0 = ConfigItem("Time", "TimeEnabled_0", False, BoolValidator())
time_TimeSet_0 = ConfigItem("Time", "TimeSet_0", "00:00")
time_TimeEnabled_1 = ConfigItem("Time", "TimeEnabled_1", False, BoolValidator())
time_TimeSet_1 = ConfigItem("Time", "TimeSet_1", "00:00")
time_TimeEnabled_2 = ConfigItem("Time", "TimeEnabled_2", False, BoolValidator())
time_TimeSet_2 = ConfigItem("Time", "TimeSet_2", "00:00")
time_TimeEnabled_3 = ConfigItem("Time", "TimeEnabled_3", False, BoolValidator())
time_TimeSet_3 = ConfigItem("Time", "TimeSet_3", "00:00")
time_TimeEnabled_4 = ConfigItem("Time", "TimeEnabled_4", False, BoolValidator())
time_TimeSet_4 = ConfigItem("Time", "TimeSet_4", "00:00")
time_TimeEnabled_5 = ConfigItem("Time", "TimeEnabled_5", False, BoolValidator())
time_TimeSet_5 = ConfigItem("Time", "TimeSet_5", "00:00")
time_TimeEnabled_6 = ConfigItem("Time", "TimeEnabled_6", False, BoolValidator())
time_TimeSet_6 = ConfigItem("Time", "TimeSet_6", "00:00")
time_TimeEnabled_7 = ConfigItem("Time", "TimeEnabled_7", False, BoolValidator())
time_TimeSet_7 = ConfigItem("Time", "TimeSet_7", "00:00")
time_TimeEnabled_8 = ConfigItem("Time", "TimeEnabled_8", False, BoolValidator())
time_TimeSet_8 = ConfigItem("Time", "TimeSet_8", "00:00")
time_TimeEnabled_9 = ConfigItem("Time", "TimeEnabled_9", False, BoolValidator())
time_TimeSet_9 = ConfigItem("Time", "TimeSet_9", "00:00")
queue_Member_1 = OptionsConfigItem("Queue", "Member_1", "禁用")
queue_Member_2 = OptionsConfigItem("Queue", "Member_2", "禁用")
queue_Member_3 = OptionsConfigItem("Queue", "Member_3", "禁用")
queue_Member_4 = OptionsConfigItem("Queue", "Member_4", "禁用")
queue_Member_5 = OptionsConfigItem("Queue", "Member_5", "禁用")
queue_Member_6 = OptionsConfigItem("Queue", "Member_6", "禁用")
queue_Member_7 = OptionsConfigItem("Queue", "Member_7", "禁用")
queue_Member_8 = OptionsConfigItem("Queue", "Member_8", "禁用")
queue_Member_9 = OptionsConfigItem("Queue", "Member_9", "禁用")
queue_Member_10 = OptionsConfigItem("Queue", "Member_10", "禁用")
class MaaConfig(QConfig):
"""MAA配置"""
MaaSet_Name = ConfigItem("MaaSet", "Name", "")
MaaSet_Path = ConfigItem("MaaSet", "Path", ".", FolderValidator())
RunSet_AnnihilationTimeLimit = RangeConfigItem(
"RunSet", "AnnihilationTimeLimit", 40, RangeValidator(1, 1024)
)
RunSet_RoutineTimeLimit = RangeConfigItem(
"RunSet", "RoutineTimeLimit", 10, RangeValidator(1, 1024)
)
RunSet_RunTimesLimit = RangeConfigItem(
"RunSet", "RunTimesLimit", 3, RangeValidator(1, 1024)
)
Config = AppConfig()

92
app/core/main_info_bar.py Normal file
View File

@@ -0,0 +1,92 @@
# <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.QtCore import Qt
from qfluentwidgets import (
InfoBar,
InfoBarPosition,
)
class _MainInfoBar:
"""信息通知栏"""
def __init__(self, parent=None):
self.parent = parent
def push_info_bar(self, mode: str, title: str, content: str, time: int):
"""推送到信息通知栏"""
if self.parent is None:
logger.error("信息通知栏未设置父窗口")
return None
if mode == "success":
InfoBar.success(
title=title,
content=content,
orient=Qt.Horizontal,
isClosable=True,
position=InfoBarPosition.TOP_RIGHT,
duration=time,
parent=self.parent,
)
elif mode == "warning":
InfoBar.warning(
title=title,
content=content,
orient=Qt.Horizontal,
isClosable=True,
position=InfoBarPosition.TOP_RIGHT,
duration=time,
parent=self.parent,
)
elif mode == "error":
InfoBar.error(
title=title,
content=content,
orient=Qt.Horizontal,
isClosable=True,
position=InfoBarPosition.TOP_RIGHT,
duration=time,
parent=self.parent,
)
elif mode == "info":
InfoBar.info(
title=title,
content=content,
orient=Qt.Horizontal,
isClosable=True,
position=InfoBarPosition.TOP_RIGHT,
duration=time,
parent=self.parent,
)
MainInfoBar = _MainInfoBar()

287
app/core/task_manager.py Normal file
View File

@@ -0,0 +1,287 @@
# <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.QtCore import QThread, QObject, Signal
from qfluentwidgets import Dialog
from pathlib import Path
from datetime import datetime
from typing import Dict, Union
from .config import Config
from .main_info_bar import MainInfoBar
from app.models import MaaManager
class Task(QThread):
"""业务线程"""
push_info_bar = Signal(str, str, str, int)
question = Signal(str, str)
question_response = Signal(bool)
update_user_info = Signal(Path, list, list, list, list, list, list)
create_task_list = Signal(list)
create_user_list = Signal(list)
update_task_list = Signal(list)
update_user_list = Signal(list)
update_log_text = Signal(str)
accomplish = Signal(list)
def __init__(
self,
mode: str,
name: str,
info: Dict[str, Dict[str, Union[str, int, bool]]],
):
super(Task, self).__init__()
self.mode = mode
self.name = name
self.info = info
self.logs = []
self.question_response.connect(lambda: print("response"))
def run(self):
if "设置MAA" in self.mode:
logger.info(f"任务开始:设置{self.name}")
self.push_info_bar.emit("info", "设置MAA", self.name, 3000)
self.task = MaaManager(
self.mode,
Config.app_path / f"config/MaaConfig/{self.name}",
(
None
if "全局" in self.mode
else Config.app_path
/ f"config/MaaConfig/{self.name}/beta/{self.info["SetMaaInfo"]["UserId"]}/{self.info["SetMaaInfo"]["SetType"]}"
),
)
self.task.push_info_bar.connect(self.push_info_bar.emit)
self.task.accomplish.connect(lambda: self.accomplish.emit([]))
self.task.run()
else:
self.member_dict = self.search_member()
self.task_list = [
[value, "等待"]
for _, value in self.info["Queue"].items()
if value != "禁用"
]
self.create_task_list.emit(self.task_list)
for i in range(len(self.task_list)):
if self.isInterruptionRequested():
break
self.task_list[i][1] = "运行"
self.update_task_list.emit(self.task_list)
if self.task_list[i][0] in Config.running_list:
self.task_list[i][1] = "跳过"
self.update_task_list.emit(self.task_list)
logger.info(f"跳过任务:{self.task_list[i][0]}")
self.push_info_bar.emit(
"info", "跳过任务", self.task_list[i][0], 3000
)
continue
Config.running_list.append(self.task_list[i][0])
logger.info(f"任务开始:{self.task_list[i][0]}")
self.push_info_bar.emit("info", "任务开始", self.task_list[i][0], 3000)
if self.member_dict[self.task_list[i][0]][0] == "Maa":
self.task = MaaManager(
self.mode[0:4],
self.member_dict[self.task_list[i][0]][1],
)
self.task.question.connect(self.question.emit)
self.question_response.disconnect()
self.question_response.connect(self.task.question_response.emit)
self.task.push_info_bar.connect(self.push_info_bar.emit)
self.task.create_user_list.connect(self.create_user_list.emit)
self.task.update_user_list.connect(self.update_user_list.emit)
self.task.update_log_text.connect(self.update_log_text.emit)
self.task.update_user_info.connect(
lambda modes, uids, days, lasts, notes, numbs: self.update_user_info.emit(
self.member_dict[self.task_list[i][0]][1],
modes,
uids,
days,
lasts,
notes,
numbs,
)
)
self.task.accomplish.connect(
lambda log: self.save_log(self.task_list[i][0], log)
)
self.task.run()
Config.running_list.remove(self.task_list[i][0])
self.task_list[i][1] = "完成"
logger.info(f"任务完成:{self.task_list[i][0]}")
self.push_info_bar.emit("info", "任务完成", self.task_list[i][0], 3000)
self.accomplish.emit(self.logs)
def search_member(self) -> dict:
"""搜索所有脚本实例及其路径"""
member_dict = {}
if (Config.app_path / "config/MaaConfig").exists():
for subdir in (Config.app_path / "config/MaaConfig").iterdir():
if subdir.is_dir():
member_dict[subdir.name] = ["Maa", subdir]
return member_dict
def save_log(self, name: str, log: dict):
"""保存保存任务结果"""
self.logs.append([name, log])
class TaskManager(QObject):
"""业务调度器"""
create_gui = Signal(Task)
connect_gui = Signal(Task)
push_info_bar = Signal(str, str, str, int)
def __init__(self):
super(TaskManager, self).__init__()
self.task_list: Dict[str, Task] = {}
def add_task(
self, mode: str, name: str, info: Dict[str, Dict[str, Union[str, int, bool]]]
):
"""添加任务"""
if name in Config.running_list or name in self.task_list:
logger.warning(f"任务已存在:{name}")
MainInfoBar.push_info_bar("warning", "任务已存在", name, 5000)
return None
logger.info(f"任务开始:{name}")
MainInfoBar.push_info_bar("info", "任务开始", name, 3000)
Config.running_list.append(name)
self.task_list[name] = Task(mode, name, info)
self.task_list[name].question.connect(
lambda title, content: self.push_dialog(name, title, content)
)
self.task_list[name].push_info_bar.connect(MainInfoBar.push_info_bar)
self.task_list[name].update_user_info.connect(Config.change_user_info)
self.task_list[name].accomplish.connect(
lambda logs: self.remove_task(name, logs)
)
if "新窗口" in mode:
self.create_gui.emit(self.task_list[name])
elif "主窗口" in mode:
self.connect_gui.emit(self.task_list[name])
self.task_list[name].start()
def stop_task(self, name: str):
"""中止任务"""
logger.info(f"中止任务:{name}")
MainInfoBar.push_info_bar("info", "中止任务", name, 3000)
if name == "ALL":
for name in self.task_list:
self.task_list[name].task.requestInterruption()
self.task_list[name].requestInterruption()
self.task_list[name].quit()
self.task_list[name].wait()
elif name in self.task_list:
self.task_list[name].task.requestInterruption()
self.task_list[name].requestInterruption()
self.task_list[name].quit()
self.task_list[name].wait()
def remove_task(self, name: str, logs: str):
"""移除任务标记"""
logger.info(f"任务结束:{name}")
MainInfoBar.push_info_bar("info", "任务结束", name, 3000)
if len(logs) > 0:
time = logs[0][1]["Time"]
history = ""
for log in logs:
Config.save_history(log[0], log[1])
history += (
f"任务名称:{log[0]}{log[1]["History"].replace("\n","\n ")}\n"
)
Config.save_history(name, {"Time": time, "History": history})
else:
Config.save_history(
name,
{
"Time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"History": "没有任务被执行",
},
)
self.task_list.pop(name)
Config.running_list.remove(name)
def push_dialog(self, name: str, title: str, content: str):
"""推送对话框"""
choice = Dialog(title, content, None)
choice.yesButton.setText("")
choice.cancelButton.setText("")
self.task_list[name].question_response.emit(bool(choice.exec_()))
Task_manager = TaskManager()

123
app/core/timer.py Normal file
View File

@@ -0,0 +1,123 @@
# <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
from PySide6.QtCore import QTimer
import json
from datetime import datetime
import pyautogui
from .config import Config
from .task_manager import Task_manager
from app.services import System
class MainTimer(QWidget):
def __init__(
self,
parent=None,
):
super().__init__(parent)
self.if_FailSafeException = False
self.Timer = QTimer()
self.Timer.timeout.connect(self.timed_start)
self.Timer.timeout.connect(self.set_silence)
self.Timer.start(1000)
def timed_start(self):
"""定时启动代理任务"""
# 获取定时列表
queue_list = self.search_queue()
for i in queue_list:
name, info = i
if not info["QueueSet"]["Enabled"]:
continue
history = Config.get_history(name)
time_set = [
info["Time"][f"TimeSet_{_}"]
for _ in range(10)
if info["Time"][f"TimeEnabled_{_}"]
]
# 按时间调起代理任务
curtime = datetime.now().strftime("%Y-%m-%d %H:%M")
if (
curtime[11:16] in time_set
and curtime != history["Time"][:16]
and name not in Config.running_list
):
logger.info(f"定时任务:{name}")
Task_manager.add_task("自动代理_新窗口", name, info)
def set_silence(self):
"""设置静默模式"""
windows = System.get_window_info()
if any(
str(emulator_path) in window
for window in windows
for emulator_path in Config.silence_list
):
try:
pyautogui.hotkey(
*[
_.strip().lower()
for _ in Config.global_config.get(
Config.global_config.function_BossKey
).split("+")
]
)
except pyautogui.FailSafeException as e:
if not self.if_FailSafeException:
logger.warning(f"FailSafeException: {e}")
self.if_FailSafeException = True
def search_queue(self) -> list:
"""搜索所有调度队列实例"""
queue_list = []
if (Config.app_path / "config/QueueConfig").exists():
for json_file in (Config.app_path / "config/QueueConfig").glob("*.json"):
with json_file.open("r", encoding="utf-8") as f:
info = json.load(f)
queue_list.append([json_file.stem, info])
return queue_list
Main_timer = MainTimer()

1018
app/models/MAA.py Normal file

File diff suppressed because it is too large Load Diff

34
app/models/__init__.py Normal file
View File

@@ -0,0 +1,34 @@
# <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
"""
__version__ = "4.2.0"
__author__ = "DLmaster361 <DLmaster_361@163.com>"
__license__ = "GPL-3.0 license"
from .MAA import MaaManager
__all__ = ["MaaManager"]

36
app/services/__init__.py Normal file
View File

@@ -0,0 +1,36 @@
# <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
"""
__version__ = "4.2.0"
__author__ = "DLmaster361 <DLmaster_361@163.com>"
__license__ = "GPL-3.0 license"
from .notification import Notify
from .security import Crypto
from .system import System
__all__ = ["Notify", "Crypto", "System"]

View File

@@ -0,0 +1,100 @@
# <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 plyer import notification
import smtplib
from email.mime.text import MIMEText
from email.header import Header
from email.utils import formataddr
from app.core import Config
class Notification:
def push_notification(self, title, message, ticker, t):
"""推送系统通知"""
if Config.global_config.get(Config.global_config.notify_IfPushPlyer):
notification.notify(
title=title,
message=message,
app_name="AUTO_MAA",
app_icon=str(Config.app_path / "resources/icons/AUTO_MAA.ico"),
timeout=t,
ticker=ticker,
toast=True,
)
return True
def send_mail(self, title, content):
"""使用官方专用邮箱推送邮件通知"""
# 声明此邮箱为AUTO_MAA项目组资产未经授权不得私自使用
# 注意此声明注释只有使用者更换发信邮箱时才能删除本条规则优先级高于GPLv3
if Config.global_config.get(Config.global_config.notify_IfSendMail):
# 第三方 SMTP 服务配置
mail_host = "smtp.163.com" # 设置服务器
mail_sender = "AUTO_MAA_server@163.com" # 用户名
mail_key = "SYrq87nDLD4RNB5T" # 授权码 24/11/15
# 定义邮件正文
message = MIMEText(content, "plain", "utf-8")
message["From"] = formataddr(
(
Header("AUTO_MAA通知服务", "utf-8").encode(),
"AUTO_MAA_server@163.com",
)
) # 发件人显示的名字
message["To"] = formataddr(
(
Header("AUTO_MAA用户", "utf-8").encode(),
Config.global_config.get(Config.global_config.notify_MailAddress),
)
) # 收件人显示的名字
message["Subject"] = Header(title, "utf-8")
try:
smtpObj = smtplib.SMTP_SSL(mail_host, 465) # 465为SMTP_SSL默认端口
smtpObj.login(mail_sender, mail_key)
smtpObj.sendmail(
mail_sender,
Config.global_config.get(Config.global_config.notify_MailAddress),
message.as_string(),
)
return True
except smtplib.SMTPException as e:
return f"发送邮件时出错:\n{e}"
finally:
smtpObj.quit()
Notify = Notification()

203
app/services/security.py Normal file
View File

@@ -0,0 +1,203 @@
# <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
import sqlite3
import hashlib
import random
import secrets
from pathlib import Path
from Crypto.Cipher import AES
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP
from Crypto.Util.Padding import pad, unpad
from typing import List, Dict, Union
from app.core import Config
class CryptoHandler:
def get_PASSWORD(self, PASSWORD: str) -> None:
"""配置管理密钥"""
# 生成目录
Config.key_path.mkdir(parents=True, exist_ok=True)
# 生成RSA密钥对
key = RSA.generate(2048)
public_key_local = key.publickey()
private_key = key
# 保存RSA公钥
(Config.app_path / "data/key/public_key.pem").write_bytes(
public_key_local.exportKey()
)
# 生成密钥转换与校验随机盐
PASSWORD_salt = secrets.token_hex(random.randint(32, 1024))
(Config.app_path / "data/key/PASSWORDsalt.txt").write_text(
PASSWORD_salt,
encoding="utf-8",
)
verify_salt = secrets.token_hex(random.randint(32, 1024))
(Config.app_path / "data/key/verifysalt.txt").write_text(
verify_salt,
encoding="utf-8",
)
# 将管理密钥转化为AES-256密钥
AES_password = hashlib.sha256(
(PASSWORD + PASSWORD_salt).encode("utf-8")
).digest()
# 生成AES-256密钥校验哈希值并保存
AES_password_verify = hashlib.sha256(
AES_password + verify_salt.encode("utf-8")
).digest()
(Config.app_path / "data/key/AES_password_verify.bin").write_bytes(
AES_password_verify
)
# AES-256加密RSA私钥并保存密文
AES_key = AES.new(AES_password, AES.MODE_ECB)
private_key_local = AES_key.encrypt(pad(private_key.exportKey(), 32))
(Config.app_path / "data/key/private_key.bin").write_bytes(private_key_local)
def encryptx(self, note: str) -> bytes:
"""加密数据"""
# 读取RSA公钥
public_key_local = RSA.import_key(
(Config.app_path / "data/key/public_key.pem").read_bytes()
)
# 使用RSA公钥对数据进行加密
cipher = PKCS1_OAEP.new(public_key_local)
encrypted = cipher.encrypt(note.encode("utf-8"))
return encrypted
def decryptx(self, note: bytes, PASSWORD: str) -> str:
"""解密数据"""
# 读入RSA私钥密文、盐与校验哈希值
private_key_local = (
(Config.app_path / "data/key/private_key.bin").read_bytes().strip()
)
PASSWORD_salt = (
(Config.app_path / "data/key/PASSWORDsalt.txt")
.read_text(encoding="utf-8")
.strip()
)
verify_salt = (
(Config.app_path / "data/key/verifysalt.txt")
.read_text(encoding="utf-8")
.strip()
)
AES_password_verify = (
(Config.app_path / "data/key/AES_password_verify.bin").read_bytes().strip()
)
# 将管理密钥转化为AES-256密钥并验证
AES_password = hashlib.sha256(
(PASSWORD + PASSWORD_salt).encode("utf-8")
).digest()
AES_password_SHA = hashlib.sha256(
AES_password + verify_salt.encode("utf-8")
).digest()
if AES_password_SHA != AES_password_verify:
return "管理密钥错误"
else:
# AES解密RSA私钥
AES_key = AES.new(AES_password, AES.MODE_ECB)
private_key_pem = unpad(AES_key.decrypt(private_key_local), 32)
private_key = RSA.import_key(private_key_pem)
# 使用RSA私钥解密数据
decrypter = PKCS1_OAEP.new(private_key)
note = decrypter.decrypt(note)
return note.decode("utf-8")
def change_PASSWORD(self, PASSWORD_old: str, PASSWORD_new: str) -> None:
"""修改管理密钥"""
member_list = self.search_member()
for user_data in member_list:
# 读取用户数据
db = sqlite3.connect(user_data["Path"])
cur = db.cursor()
cur.execute("SELECT * FROM adminx WHERE True")
data = cur.fetchall()
# 使用旧管理密钥解密
user_data["Password"] = []
for i in range(len(data)):
user_data["Password"].append(self.decryptx(data[i][12], PASSWORD_old))
cur.close()
db.close()
self.get_PASSWORD(PASSWORD_new)
for user_data in member_list:
# 读取用户数据
db = sqlite3.connect(user_data["Path"])
cur = db.cursor()
cur.execute("SELECT * FROM adminx WHERE True")
data = cur.fetchall()
# 使用新管理密钥重新加密
for i in range(len(data)):
cur.execute(
"UPDATE adminx SET password = ? WHERE mode = ? AND uid = ?",
(
self.encryptx(user_data["Password"][i]),
data[i][15],
data[i][16],
),
)
db.commit()
user_data["Password"][i] = None
del user_data["Password"]
cur.close()
db.close()
def search_member(self) -> List[Dict[str, Union[Path, list]]]:
"""搜索所有脚本实例及其用户数据库路径"""
member_list = []
if (Config.app_path / "config/MaaConfig").exists():
for subdir in (Config.app_path / "config/MaaConfig").iterdir():
if subdir.is_dir():
member_list.append({"Path": subdir / "user_data.db"})
return member_list
def check_PASSWORD(self, PASSWORD: str) -> bool:
"""验证管理密钥"""
return bool(self.decryptx(self.encryptx(""), PASSWORD) != "管理密钥错误")
Crypto = CryptoHandler()

120
app/services/system.py Normal file
View File

@@ -0,0 +1,120 @@
# <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
"""
import ctypes
import win32gui
import win32process
import winreg
import psutil
from app.core import Config
class SystemHandler:
ES_CONTINUOUS = 0x80000000
ES_SYSTEM_REQUIRED = 0x00000001
def __init__(self):
self.set_Sleep()
self.set_SelfStart()
def set_Sleep(self):
"""同步系统休眠状态"""
if Config.global_config.get(Config.global_config.function_IfAllowSleep):
# 设置系统电源状态
ctypes.windll.kernel32.SetThreadExecutionState(
self.ES_CONTINUOUS | self.ES_SYSTEM_REQUIRED
)
else:
# 恢复系统电源状态
ctypes.windll.kernel32.SetThreadExecutionState(self.ES_CONTINUOUS)
def set_SelfStart(self):
"""同步开机自启"""
if (
Config.global_config.get(Config.global_config.start_IfSelfStart)
and not self.is_startup()
):
key = winreg.OpenKey(
winreg.HKEY_CURRENT_USER,
r"Software\Microsoft\Windows\CurrentVersion\Run",
winreg.KEY_SET_VALUE,
winreg.KEY_ALL_ACCESS | winreg.KEY_WRITE | winreg.KEY_CREATE_SUB_KEY,
)
winreg.SetValueEx(key, "AUTO_MAA", 0, winreg.REG_SZ, Config.app_path_sys)
winreg.CloseKey(key)
elif (
not Config.global_config.get(Config.global_config.start_IfSelfStart)
and self.is_startup()
):
key = winreg.OpenKey(
winreg.HKEY_CURRENT_USER,
r"Software\Microsoft\Windows\CurrentVersion\Run",
winreg.KEY_SET_VALUE,
winreg.KEY_ALL_ACCESS | winreg.KEY_WRITE | winreg.KEY_CREATE_SUB_KEY,
)
winreg.DeleteValue(key, "AUTO_MAA")
winreg.CloseKey(key)
def is_startup(self):
"""判断程序是否已经开机自启"""
key = winreg.OpenKey(
winreg.HKEY_CURRENT_USER,
r"Software\Microsoft\Windows\CurrentVersion\Run",
0,
winreg.KEY_READ,
)
try:
value, _ = winreg.QueryValueEx(key, "AUTO_MAA")
winreg.CloseKey(key)
return True
except FileNotFoundError:
winreg.CloseKey(key)
return False
def get_window_info(self):
"""获取当前窗口信息"""
def callback(hwnd, window_info):
if win32gui.IsWindowVisible(hwnd) and win32gui.GetWindowText(hwnd):
_, pid = win32process.GetWindowThreadProcessId(hwnd)
process = psutil.Process(pid)
window_info.append((win32gui.GetWindowText(hwnd), process.exe()))
return True
window_info = []
win32gui.EnumWindows(callback, window_info)
return window_info
System = SystemHandler()

282
app/ui/Widget.py Normal file
View File

@@ -0,0 +1,282 @@
# <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 PySide6.QtCore import Qt, QTime
from PySide6.QtGui import QIcon
from PySide6.QtWidgets import QWidget, QHBoxLayout
from qfluentwidgets import (
LineEdit,
PasswordLineEdit,
MessageBoxBase,
SubtitleLabel,
SettingCard,
SpinBox,
FluentIconBase,
Signal,
ComboBox,
CheckBox,
qconfig,
ConfigItem,
TimeEdit,
OptionsConfigItem,
)
from typing import Union, List
class InputMessageBox(MessageBoxBase):
"""输入对话框"""
def __init__(self, parent, title: str, content: str, mode: str, list: list = None):
super().__init__(parent)
self.title = SubtitleLabel(title)
if mode == "明文":
self.input = LineEdit()
self.input.setClearButtonEnabled(True)
elif mode == "密码":
self.input = PasswordLineEdit()
elif mode == "选择":
self.input = ComboBox()
self.input.addItems(list)
self.input.setCurrentIndex(-1)
self.input.setPlaceholderText(content)
# 将组件添加到布局中
self.viewLayout.addWidget(self.title)
self.viewLayout.addWidget(self.input)
class SetMessageBox(MessageBoxBase):
"""输入对话框"""
def __init__(self, parent, title: str, content: List[str], list: List[List[str]]):
super().__init__(parent)
self.title = SubtitleLabel(title)
Widget = QWidget()
Layout = QHBoxLayout(Widget)
self.input: List[ComboBox] = []
for i in range(len(content)):
self.input.append(ComboBox())
self.input[i].addItems(list[i])
self.input[i].setCurrentIndex(-1)
self.input[i].setPlaceholderText(content[i])
Layout.addWidget(self.input[i])
# 将组件添加到布局中
self.viewLayout.addWidget(self.title)
self.viewLayout.addWidget(Widget)
class LineEditSettingCard(SettingCard):
"""Setting card with switch button"""
textChanged = Signal(str)
def __init__(
self,
text,
icon: Union[str, QIcon, FluentIconBase],
title,
content=None,
configItem: ConfigItem = None,
parent=None,
):
super().__init__(icon, title, content, parent)
self.configItem = configItem
self.LineEdit = LineEdit(self)
self.LineEdit.setMinimumWidth(250)
self.LineEdit.setPlaceholderText(text)
if configItem:
self.setValue(qconfig.get(configItem))
configItem.valueChanged.connect(self.setValue)
self.hBoxLayout.addWidget(self.LineEdit, 0, Qt.AlignRight)
self.hBoxLayout.addSpacing(16)
self.LineEdit.textChanged.connect(self.__textChanged)
def __textChanged(self, content: str):
self.setValue(content)
self.textChanged.emit(content)
def setValue(self, content: str):
if self.configItem:
qconfig.set(self.configItem, content)
self.LineEdit.setText(content)
class SpinBoxSettingCard(SettingCard):
textChanged = Signal(int)
def __init__(
self,
range: tuple[int, int],
icon: Union[str, QIcon, FluentIconBase],
title,
content=None,
configItem: ConfigItem = None,
parent=None,
):
super().__init__(icon, title, content, parent)
self.configItem = configItem
self.SpinBox = SpinBox(self)
self.SpinBox.setRange(range[0], range[1])
self.SpinBox.setMinimumWidth(150)
if configItem:
self.setValue(qconfig.get(configItem))
configItem.valueChanged.connect(self.setValue)
self.hBoxLayout.addWidget(self.SpinBox, 0, Qt.AlignRight)
self.hBoxLayout.addSpacing(16)
self.SpinBox.valueChanged.connect(self.__valueChanged)
def __valueChanged(self, value: int):
self.setValue(value)
self.textChanged.emit(value)
def setValue(self, value: int):
if self.configItem:
qconfig.set(self.configItem, value)
self.SpinBox.setValue(value)
class NoOptionComboBoxSettingCard(SettingCard):
def __init__(
self,
configItem: OptionsConfigItem,
icon: Union[str, QIcon, FluentIconBase],
title,
content=None,
value=None,
texts=None,
parent=None,
):
super().__init__(icon, title, content, parent)
self.configItem = configItem
self.comboBox = ComboBox(self)
self.comboBox.setMinimumWidth(250)
self.hBoxLayout.addWidget(self.comboBox, 0, Qt.AlignRight)
self.hBoxLayout.addSpacing(16)
self.optionToText = {o: t for o, t in zip(value, texts)}
for text, option in zip(texts, value):
self.comboBox.addItem(text, userData=option)
self.comboBox.setCurrentText(self.optionToText[qconfig.get(configItem)])
self.comboBox.currentIndexChanged.connect(self._onCurrentIndexChanged)
configItem.valueChanged.connect(self.setValue)
def _onCurrentIndexChanged(self, index: int):
qconfig.set(self.configItem, self.comboBox.itemData(index))
def setValue(self, value):
if value not in self.optionToText:
return
self.comboBox.setCurrentText(self.optionToText[value])
qconfig.set(self.configItem, value)
class TimeEditSettingCard(SettingCard):
enabledChanged = Signal(bool)
timeChanged = Signal(str)
def __init__(
self,
icon: Union[str, QIcon, FluentIconBase],
title,
content=None,
configItem_bool: ConfigItem = None,
configItem_time: ConfigItem = None,
parent=None,
):
super().__init__(icon, title, content, parent)
self.configItem_bool = configItem_bool
self.configItem_time = configItem_time
self.CheckBox = CheckBox(self)
self.CheckBox.setTristate(False)
self.TimeEdit = TimeEdit(self)
self.TimeEdit.setDisplayFormat("HH:mm")
self.TimeEdit.setMinimumWidth(150)
if configItem_bool:
self.setValue_bool(qconfig.get(configItem_bool))
configItem_bool.valueChanged.connect(self.setValue_bool)
if configItem_time:
self.setValue_time(qconfig.get(configItem_time))
configItem_time.valueChanged.connect(self.setValue_time)
self.hBoxLayout.addWidget(self.CheckBox, 0, Qt.AlignRight)
self.hBoxLayout.addWidget(self.TimeEdit, 0, Qt.AlignRight)
self.hBoxLayout.addSpacing(16)
self.CheckBox.stateChanged.connect(self.__enableChanged)
self.TimeEdit.timeChanged.connect(self.__timeChanged)
def __timeChanged(self, value: QTime):
self.setValue_time(value.toString("HH:mm"))
self.timeChanged.emit(value.toString("HH:mm"))
def __enableChanged(self, value: int):
if value == 0:
self.setValue_bool(False)
self.enabledChanged.emit(False)
else:
self.setValue_bool(True)
self.enabledChanged.emit(True)
def setValue_bool(self, value: bool):
if self.configItem_bool:
qconfig.set(self.configItem_bool, value)
self.CheckBox.setChecked(value)
def setValue_time(self, value: str):
if self.configItem_time:
qconfig.set(self.configItem_time, value)
self.TimeEdit.setTime(QTime.fromString(value, "HH:mm"))

34
app/ui/__init__.py Normal file
View File

@@ -0,0 +1,34 @@
# <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
"""
__version__ = "4.2.0"
__author__ = "DLmaster361 <DLmaster_361@163.com>"
__license__ = "GPL-3.0 license"
from .main_window import AUTO_MAA
__all__ = ["AUTO_MAA"]

435
app/ui/dispatch_center.py Normal file
View File

@@ -0,0 +1,435 @@
# <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,
QStackedWidget,
QHBoxLayout,
)
from qfluentwidgets import (
CardWidget,
IconWidget,
BodyLabel,
Pivot,
ScrollArea,
FluentIcon,
HeaderCardWidget,
FluentIcon,
TextBrowser,
ComboBox,
SubtitleLabel,
PushButton,
)
from PySide6.QtCore import Qt
from PySide6.QtGui import QTextCursor
from typing import List, Dict
import json
from app.core import Config, Task_manager, Task, MainInfoBar
class DispatchCenter(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setObjectName("调度中枢")
self.pivot = Pivot(self)
self.stackedWidget = QStackedWidget(self)
self.Layout = QVBoxLayout(self)
self.script_list: Dict[str, DispatchBox] = {}
dispatch_box = DispatchBox("主调度台", self)
self.script_list["主调度台"] = dispatch_box
self.stackedWidget.addWidget(self.script_list["主调度台"])
self.pivot.addItem(
routeKey="主调度台",
text="主调度台",
onClick=self.update_top_bar,
icon=FluentIcon.CAFE,
)
self.update_top_bar()
self.Layout.addWidget(self.pivot, 0, Qt.AlignHCenter)
self.Layout.addWidget(self.stackedWidget)
self.Layout.setContentsMargins(0, 0, 0, 0)
self.pivot.currentItemChanged.connect(
lambda index: self.stackedWidget.setCurrentWidget(self.script_list[index])
)
def add_board(self, task: Task) -> None:
"""添加一个调度台界面"""
dispatch_box = DispatchBox(task.name, self)
dispatch_box.top_bar.button.clicked.connect(
lambda: Task_manager.stop_task(task.name)
)
task.create_task_list.connect(dispatch_box.info.task.create_task)
task.create_user_list.connect(dispatch_box.info.user.create_user)
task.update_task_list.connect(dispatch_box.info.task.update_task)
task.update_user_list.connect(dispatch_box.info.user.update_user)
task.update_log_text.connect(dispatch_box.info.log_text.text.setText)
task.accomplish.connect(lambda: self.del_board(f"调度台_{task.name}"))
self.script_list[f"调度台_{task.name}"] = dispatch_box
self.stackedWidget.addWidget(self.script_list[f"调度台_{task.name}"])
self.pivot.addItem(routeKey=f"调度台_{task.name}", text=f"调度台 {task.name}")
def del_board(self, name: str) -> None:
"""删除指定子界面"""
self.pivot.setCurrentItem("主调度台")
self.stackedWidget.removeWidget(self.script_list[name])
self.script_list[name].deleteLater()
self.pivot.removeWidget(name)
def connect_main_board(self, task: Task) -> None:
"""连接主调度台"""
self.script_list["主调度台"].top_bar.button.clicked.disconnect()
self.script_list["主调度台"].top_bar.button.setText("中止任务")
self.script_list["主调度台"].top_bar.button.clicked.connect(
lambda: Task_manager.stop_task(task.name)
)
task.create_task_list.connect(
self.script_list["主调度台"].info.task.create_task
)
task.create_user_list.connect(
self.script_list["主调度台"].info.user.create_user
)
task.update_task_list.connect(
self.script_list["主调度台"].info.task.update_task
)
task.update_user_list.connect(
self.script_list["主调度台"].info.user.update_user
)
task.update_log_text.connect(
self.script_list["主调度台"].info.log_text.text.setText
)
task.accomplish.connect(lambda: self.disconnect_main_board(task.name))
def disconnect_main_board(self, name: str) -> None:
"""断开主调度台"""
self.script_list["主调度台"].top_bar.button.clicked.disconnect()
self.script_list["主调度台"].top_bar.button.setText("开始任务")
self.script_list["主调度台"].top_bar.button.clicked.connect(
self.script_list["主调度台"].top_bar.start_task
)
self.script_list["主调度台"].info.log_text.text.setText(
Config.get_history(name)["History"]
)
def update_top_bar(self):
"""更新顶栏"""
list = []
if (Config.app_path / "config/QueueConfig").exists():
for json_file in (Config.app_path / "config/QueueConfig").glob("*.json"):
list.append(f"队列 - {json_file.stem}")
if (Config.app_path / "config/MaaConfig").exists():
for subdir in (Config.app_path / "config/MaaConfig").iterdir():
if subdir.is_dir():
list.append(f"实例 - Maa - {subdir.name}")
self.script_list["主调度台"].top_bar.object.clear()
self.script_list["主调度台"].top_bar.object.addItems(list)
class DispatchBox(QWidget):
def __init__(self, name: str, parent=None):
super().__init__(parent)
self.setObjectName(name)
layout = QVBoxLayout()
scrollArea = ScrollArea()
scrollArea.setWidgetResizable(True)
content_widget = QWidget()
content_layout = QVBoxLayout(content_widget)
self.top_bar = self.DispatchTopBar(self, name)
self.info = self.DispatchInfoCard(self)
content_layout.addWidget(self.top_bar)
content_layout.addWidget(self.info)
scrollArea.setWidget(content_widget)
layout.addWidget(scrollArea)
self.setLayout(layout)
class DispatchTopBar(CardWidget):
def __init__(self, parent=None, name: str = None):
super().__init__(parent)
Layout = QHBoxLayout(self)
if name == "主调度台":
self.object = ComboBox()
self.object.setCurrentIndex(-1)
self.object.setPlaceholderText("请选择调度对象")
self.mode = ComboBox()
self.mode.addItems(["自动代理", "人工排查"])
self.mode.setCurrentIndex(-1)
self.mode.setPlaceholderText("请选择调度模式")
self.button = PushButton("开始任务")
self.button.clicked.connect(self.start_task)
Layout.addWidget(self.object)
Layout.addWidget(self.mode)
Layout.addStretch(1)
Layout.addWidget(self.button)
else:
self.Lable = SubtitleLabel(name, self)
self.button = PushButton("中止任务")
Layout.addWidget(self.Lable)
Layout.addStretch(1)
Layout.addWidget(self.button)
def start_task(self):
"""开始任务"""
if self.object.currentIndex() == -1:
logger.warning("未选择调度对象")
MainInfoBar.push_info_bar(
"warning", "未选择调度对象", "请选择后再开始任务", 5000
)
return None
if self.mode.currentIndex() == -1:
logger.warning("未选择调度模式")
MainInfoBar.push_info_bar(
"warning", "未选择调度模式", "请选择后再开始任务", 5000
)
return None
name = self.object.currentText().split(" - ")[-1]
if name in Config.running_list:
logger.warning(f"任务已存在:{name}")
MainInfoBar.push_info_bar("warning", "任务已存在", name, 5000)
return None
if self.object.currentText().split(" - ")[0] == "队列":
with (Config.app_path / f"config/QueueConfig/{name}.json").open(
mode="r", encoding="utf-8"
) as f:
info = json.load(f)
logger.info(f"用户添加任务:{name}")
Task_manager.add_task(f"{self.mode.currentText()}_主窗口", name, info)
elif self.object.currentText().split(" - ")[0] == "实例":
if self.object.currentText().split(" - ")[1] == "Maa":
info = {"Queue": {"Member_1": name}}
logger.info(f"用户添加任务:{name}")
Task_manager.add_task(
f"{self.mode.currentText()}_主窗口", "用户自定义队列", info
)
class DispatchInfoCard(HeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setTitle("调度信息")
self.task = self.TaskInfoCard(self)
self.user = self.UserInfoCard(self)
self.log_text = self.LogCard(self)
self.viewLayout.addWidget(self.task)
self.viewLayout.addWidget(self.user)
self.viewLayout.addWidget(self.log_text)
self.viewLayout.setStretch(0, 1)
self.viewLayout.setStretch(1, 1)
self.viewLayout.setStretch(2, 5)
def update_board(self, task_list: list, user_list: list, log: str):
"""更新调度信息"""
self.task.update_task(task_list)
self.user.update_user(user_list)
self.log_text.text.setText(log)
class TaskInfoCard(HeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setTitle("任务队列")
self.Layout = QVBoxLayout()
self.viewLayout.addLayout(self.Layout)
self.viewLayout.setContentsMargins(3, 0, 3, 3)
self.task_cards: List[ItemCard] = []
def create_task(self, task_list: list):
"""创建任务队列"""
while self.Layout.count() > 0:
item = self.Layout.takeAt(0)
if item.spacerItem():
self.Layout.removeItem(item.spacerItem())
elif item.widget():
item.widget().deleteLater()
self.task_cards = []
for task in task_list:
self.task_cards.append(ItemCard(task))
self.Layout.addWidget(self.task_cards[-1])
self.Layout.addStretch(1)
def update_task(self, task_list: list):
"""更新任务队列"""
for i in range(len(task_list)):
self.task_cards[i].update_status(task_list[i][1])
class UserInfoCard(HeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setTitle("用户队列")
self.Layout = QVBoxLayout()
self.viewLayout.addLayout(self.Layout)
self.viewLayout.setContentsMargins(3, 0, 3, 3)
self.user_cards: List[ItemCard] = []
def create_user(self, user_list: list):
"""创建用户队列"""
while self.Layout.count() > 0:
item = self.Layout.takeAt(0)
if item.spacerItem():
self.Layout.removeItem(item.spacerItem())
elif item.widget():
item.widget().deleteLater()
self.user_cards = []
for user in user_list:
self.user_cards.append(ItemCard(user))
self.Layout.addWidget(self.user_cards[-1])
self.Layout.addStretch(1)
def update_user(self, user_list: list):
"""更新用户队列"""
for i in range(len(user_list)):
self.user_cards[i].Label.setText(user_list[i][0])
self.user_cards[i].update_status(user_list[i][1])
class LogCard(HeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setTitle("日志")
self.text = TextBrowser()
self.viewLayout.setContentsMargins(3, 0, 3, 3)
self.viewLayout.addWidget(self.text)
self.text.textChanged.connect(self.to_end)
def to_end(self):
"""滚动到底部"""
self.text.moveCursor(QTextCursor.End)
self.text.ensureCursorVisible()
class ItemCard(CardWidget):
def __init__(self, task_item: list, parent=None):
super().__init__(parent)
self.Layout = QHBoxLayout(self)
self.Label = BodyLabel(task_item[0], self)
self.icon = IconWidget(FluentIcon.MORE, self)
self.icon.setFixedSize(16, 16)
self.update_status(task_item[1])
self.Layout.addWidget(self.icon)
self.Layout.addWidget(self.Label)
self.Layout.addStretch(1)
def update_status(self, status: str):
if status == "完成":
self.icon.setIcon(FluentIcon.ACCEPT)
self.Label.setTextColor("#0eb840", "#0eb840")
elif status == "等待":
self.icon.setIcon(FluentIcon.MORE)
self.Label.setTextColor("#7397ab", "#7397ab")
elif status == "运行":
self.icon.setIcon(FluentIcon.PLAY)
self.Label.setTextColor("#2e4e7e", "#2e4e7e")
elif status == "跳过":
self.icon.setIcon(FluentIcon.REMOVE)
self.Label.setTextColor("#606060", "#d2d2d2")
elif status == "异常":
self.icon.setIcon(FluentIcon.CLOSE)
self.Label.setTextColor("#ff2121", "#ff2121")

346
app/ui/main_window.py Normal file
View File

@@ -0,0 +1,346 @@
# <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 (
QApplication,
QSystemTrayIcon,
)
from qfluentwidgets import (
Action,
PushButton,
SystemTrayMenu,
SplashScreen,
FluentIcon,
InfoBar,
InfoBarPosition,
setTheme,
Theme,
MSFluentWindow,
NavigationItemPosition,
qconfig,
)
from PySide6.QtGui import QIcon, QCloseEvent
from PySide6.QtCore import Qt
from app.core import Config, Task_manager, Main_timer, MainInfoBar
from app.services import Notify, Crypto, System
from .setting import Setting
from .member_manager import MemberManager
from .queue_manager import QueueManager
from .dispatch_center import DispatchCenter
class AUTO_MAA(MSFluentWindow):
def __init__(self):
super().__init__()
self.setWindowIcon(QIcon(str(Config.app_path / "resources/icons/AUTO_MAA.ico")))
self.setWindowTitle("AUTO_MAA")
setTheme(Theme.AUTO)
self.splashScreen = SplashScreen(self.windowIcon(), self)
self.show_ui("显示主窗口", if_quick=True)
MainInfoBar.parent = self
# 创建主窗口
self.setting = Setting(self)
self.member_manager = MemberManager(self)
self.queue_manager = QueueManager(self)
self.dispatch_center = DispatchCenter(self)
self.addSubInterface(
self.setting,
FluentIcon.SETTING,
"设置",
FluentIcon.SETTING,
NavigationItemPosition.BOTTOM,
)
self.addSubInterface(
self.member_manager,
FluentIcon.ROBOT,
"脚本管理",
FluentIcon.ROBOT,
NavigationItemPosition.TOP,
)
self.addSubInterface(
self.queue_manager,
FluentIcon.BOOK_SHELF,
"调度队列",
FluentIcon.BOOK_SHELF,
NavigationItemPosition.TOP,
)
self.addSubInterface(
self.dispatch_center,
FluentIcon.IOT,
"调度中心",
FluentIcon.IOT,
NavigationItemPosition.TOP,
)
self.stackedWidget.currentChanged.connect(
lambda index: (self.member_manager.refresh() if index == 1 else None)
)
self.stackedWidget.currentChanged.connect(
lambda index: self.queue_manager.refresh() if index == 2 else None
)
self.stackedWidget.currentChanged.connect(
lambda index: (
self.dispatch_center.pivot.setCurrentItem("主调度台")
if index == 3
else None
)
)
self.stackedWidget.currentChanged.connect(
lambda index: (
self.dispatch_center.update_top_bar() if index == 3 else None
)
)
# 创建系统托盘及其菜单
self.tray = QSystemTrayIcon(
QIcon(str(Config.app_path / "resources/icons/AUTO_MAA.ico")),
self,
)
self.tray.setToolTip("AUTO_MAA")
self.tray_menu = SystemTrayMenu("AUTO_MAA", self)
# 显示主界面菜单项
self.tray_menu.addAction(
Action(
FluentIcon.CAFE,
"显示主界面",
triggered=lambda: self.show_ui("显示主窗口"),
)
)
self.tray_menu.addSeparator()
# 开始任务菜单项
# self.tray_menu.addActions(
# [
# Action(
# FluentIcon.PLAY,
# "运行自动代理",
# triggered=lambda: self.start_task("自动代理"),
# ),
# Action(
# FluentIcon.PLAY,
# "运行人工排查",
# triggered=lambda: self.start_task("人工排查"),
# ),
# Action(FluentIcon.PAUSE, "中止当前任务", triggered=self.stop_task),
# ]
# )
# self.tray_menu.addSeparator()
# 退出主程序菜单项
self.tray_menu.addAction(
Action(FluentIcon.POWER_BUTTON, "退出主程序", triggered=self.kill_main)
)
# 设置托盘菜单
self.tray.setContextMenu(self.tray_menu)
self.tray.activated.connect(self.on_tray_activated)
Task_manager.create_gui.connect(self.dispatch_center.add_board)
Task_manager.connect_gui.connect(self.dispatch_center.connect_main_board)
self.setting.ui.card_IfShowTray.checkedChanged.connect(
lambda: self.show_ui("配置托盘")
)
self.setting.ui.card_IfToTray.checkedChanged.connect(self.set_min_method)
self.splashScreen.finish()
def start_up_task(self) -> None:
"""启动时任务"""
# 加载配置
qconfig.load(Config.config_path, Config.global_config)
# 检查密码
self.setting.check_PASSWORD()
# 获取公告
self.setting.show_notice(if_show=False)
# 检查更新
if Config.global_config.get(Config.global_config.update_IfAutoUpdate):
result = self.setting.get_update_info()
if result == "已是最新版本~":
MainInfoBar.push_info_bar("success", "更新检查", result, 3000)
else:
info = InfoBar.info(
title="更新检查",
content=result,
orient=Qt.Horizontal,
isClosable=True,
position=InfoBarPosition.BOTTOM_LEFT,
duration=-1,
parent=self,
)
Up = PushButton("更新")
Up.clicked.connect(lambda: self.setting.get_update(if_question=False))
Up.clicked.connect(info.close)
info.addWidget(Up)
info.show()
def set_min_method(self) -> None:
"""设置最小化方法"""
if Config.global_config.get(Config.global_config.ui_IfToTray):
self.titleBar.minBtn.clicked.disconnect()
self.titleBar.minBtn.clicked.connect(lambda: self.show_ui("隐藏到托盘"))
else:
self.titleBar.minBtn.clicked.disconnect()
self.titleBar.minBtn.clicked.connect(self.showMinimized)
def on_tray_activated(self, reason):
"""双击返回主界面"""
if reason == QSystemTrayIcon.DoubleClick:
self.show_ui("显示主窗口")
# def start_task(self, mode):
# """调起对应任务"""
# if self.main.MaaManager.isRunning():
# Notify.push_notification(
# f"无法运行{mode}",
# "当前已有任务正在运行,请在该任务结束后重试",
# "当前已有任务正在运行,请在该任务结束后重试",
# 3,
# )
# else:
# self.main.maa_starter(mode)
# def stop_task(self):
# """中止当前任务"""
# if self.main.MaaManager.isRunning():
# if (
# self.main.MaaManager.mode == "自动代理"
# or self.main.MaaManager.mode == "人工排查"
# ):
# self.main.maa_ender(f"{self.main.MaaManager.mode}_结束")
# elif "设置MAA" in self.main.MaaManager.mode:
# Notify.push_notification(
# "正在设置MAA",
# "正在运行设置MAA任务无法中止",
# "正在运行设置MAA任务无法中止",
# 3,
# )
# else:
# Notify.push_notification(
# "无任务运行!",
# "当前无任务正在运行,无需中止",
# "当前无任务正在运行,无需中止",
# 3,
# )
def kill_main(self) -> None:
"""退出主程序"""
self.close()
QApplication.quit()
def show_ui(self, mode: str, if_quick: bool = False) -> None:
"""配置窗口状态"""
if mode == "显示主窗口":
# 配置主窗口
size = list(
map(
int,
Config.global_config.get(Config.global_config.ui_size).split("x"),
)
)
location = list(
map(
int,
Config.global_config.get(Config.global_config.ui_location).split(
"x"
),
)
)
self.setGeometry(location[0], location[1], size[0], size[1])
self.show()
if not if_quick:
if Config.global_config.get(Config.global_config.ui_maximized):
self.showMaximized()
self.set_min_method()
self.show_ui("配置托盘")
elif mode == "配置托盘":
if Config.global_config.get(Config.global_config.ui_IfShowTray):
self.tray.show()
else:
self.tray.hide()
elif mode == "隐藏到托盘":
# 保存窗口相关属性
if not self.isMaximized():
Config.global_config.set(
Config.global_config.ui_size,
f"{self.geometry().width()}x{self.geometry().height()}",
)
Config.global_config.set(
Config.global_config.ui_location,
f"{self.geometry().x()}x{self.geometry().y()}",
)
Config.global_config.set(
Config.global_config.ui_maximized, self.isMaximized()
)
Config.global_config.save()
# 隐藏主窗口
if not if_quick:
self.hide()
self.tray.show()
def closeEvent(self, event: QCloseEvent):
"""清理残余进程"""
self.show_ui("隐藏到托盘", if_quick=True)
# 清理各功能线程
Main_timer.Timer.stop()
Main_timer.Timer.deleteLater()
Task_manager.stop_task("ALL")
# 关闭数据库连接
Config.close_database()
logger.info("AUTO_MAA主程序关闭")
logger.info("===================================")
event.accept()

1629
app/ui/member_manager.py Normal file

File diff suppressed because it is too large Load Diff

657
app/ui/queue_manager.py Normal file
View File

@@ -0,0 +1,657 @@
# <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,
QStackedWidget,
QHBoxLayout,
)
from qfluentwidgets import (
Action,
qconfig,
Pivot,
ScrollArea,
FluentIcon,
MessageBox,
HeaderCardWidget,
TextBrowser,
CommandBar,
SwitchSettingCard,
)
from PySide6.QtCore import Qt
from typing import List
import json
import shutil
from app.core import Config, MainInfoBar
from .Widget import (
LineEditSettingCard,
TimeEditSettingCard,
NoOptionComboBoxSettingCard,
)
class QueueManager(QWidget):
def __init__(
self,
parent=None,
):
super().__init__(parent)
self.setObjectName("调度队列")
layout = QVBoxLayout(self)
self.tools = CommandBar()
self.queue_manager = QueueSettingBox(self)
# 逐个添加动作
self.tools.addActions(
[
Action(
FluentIcon.ADD_TO, "新建调度队列", triggered=self.add_setting_box
),
Action(
FluentIcon.REMOVE_FROM,
"删除调度队列",
triggered=self.del_setting_box,
),
]
)
self.tools.addSeparator()
self.tools.addActions(
[
Action(
FluentIcon.LEFT_ARROW, "向左移动", triggered=self.left_setting_box
),
Action(
FluentIcon.RIGHT_ARROW, "向右移动", triggered=self.right_setting_box
),
]
)
layout.addWidget(self.tools)
layout.addWidget(self.queue_manager)
def add_setting_box(self):
"""添加一个调度队列"""
index = len(self.queue_manager.search_queue()) + 1
qconfig.load(
Config.app_path / f"config/QueueConfig/调度队列_{index}.json",
Config.queue_config,
)
Config.clear_queue_config()
Config.queue_config.save()
self.queue_manager.add_QueueSettingBox(index)
self.queue_manager.switch_SettingBox(index)
def del_setting_box(self):
"""删除一个调度队列实例"""
name = self.queue_manager.pivot.currentRouteKey()
if name == None:
logger.warning("未选择调度队列")
MainInfoBar.push_info_bar(
"warning", "未选择调度队列", "请先选择一个调度队列", 5000
)
return None
if name in Config.running_list:
logger.warning("调度队列正在运行")
MainInfoBar.push_info_bar(
"warning", "调度队列正在运行", "请先停止调度队列", 5000
)
return None
choice = MessageBox(
"确认",
f"确定要删除 {name} 吗?",
self,
)
if choice.exec():
queue_list = self.queue_manager.search_queue()
move_list = [_ for _ in queue_list if int(_[0][5:]) > int(name[5:])]
index = max(int(name[5:]) - 1, 1)
self.queue_manager.clear_SettingBox()
(Config.app_path / f"config/QueueConfig/{name}.json").unlink()
for queue in move_list:
if (Config.app_path / f"config/QueueConfig/{queue[0]}.json").exists():
(Config.app_path / f"config/QueueConfig/{queue[0]}.json").rename(
Config.app_path
/ f"config/QueueConfig/调度队列_{int(queue[0][5:])-1}.json",
)
self.queue_manager.show_SettingBox(index)
def left_setting_box(self):
"""向左移动调度队列实例"""
name = self.queue_manager.pivot.currentRouteKey()
if name == None:
logger.warning("未选择调度队列")
MainInfoBar.push_info_bar(
"warning", "未选择调度队列", "请先选择一个调度队列", 5000
)
return None
index = int(name[5:])
if index == 1:
logger.warning("向左移动调度队列时已到达最左端")
MainInfoBar.push_info_bar(
"warning", "已经是第一个调度队列", "无法向左移动", 5000
)
return None
if name in Config.running_list or f"调度队列_{index-1}" in Config.running_list:
logger.warning("相关调度队列正在运行")
MainInfoBar.push_info_bar(
"warning", "相关调度队列正在运行", "请先停止调度队列", 5000
)
return None
self.queue_manager.clear_SettingBox()
(Config.app_path / f"config/QueueConfig/调度队列_{index}.json").rename(
Config.app_path / f"config/QueueConfig/调度队列_0.json",
)
shutil.move(
str(Config.app_path / f"config/QueueConfig/调度队列_{index-1}.json"),
str(Config.app_path / f"config/QueueConfig/调度队列_{index}.json"),
)
(Config.app_path / f"config/QueueConfig/调度队列_0.json").rename(
Config.app_path / f"config/QueueConfig/调度队列_{index-1}.json",
)
self.queue_manager.show_SettingBox(index - 1)
def right_setting_box(self):
"""向右移动调度队列实例"""
name = self.queue_manager.pivot.currentRouteKey()
if name == None:
logger.warning("未选择调度队列")
MainInfoBar.push_info_bar(
"warning", "未选择调度队列", "请先选择一个调度队列", 5000
)
return None
queue_list = self.queue_manager.search_queue()
index = int(name[5:])
if index == len(queue_list):
logger.warning("向右移动调度队列时已到达最右端")
MainInfoBar.push_info_bar(
"warning", "已经是最后一个调度队列", "无法向右移动", 5000
)
return None
if name in Config.running_list or f"调度队列_{index+1}" in Config.running_list:
logger.warning("相关调度队列正在运行")
MainInfoBar.push_info_bar(
"warning", "相关调度队列正在运行", "请先停止调度队列", 5000
)
return None
self.queue_manager.clear_SettingBox()
(Config.app_path / f"config/QueueConfig/调度队列_{index}.json").rename(
Config.app_path / f"config/QueueConfig/调度队列_0.json",
)
(Config.app_path / f"config/QueueConfig/调度队列_{index+1}.json").rename(
Config.app_path / f"config/QueueConfig/调度队列_{index}.json",
)
(Config.app_path / f"config/QueueConfig/调度队列_0.json").rename(
Config.app_path / f"config/QueueConfig/调度队列_{index+1}.json",
)
self.queue_manager.show_SettingBox(index + 1)
def refresh(self):
"""刷新调度队列界面"""
if len(self.queue_manager.search_queue()) == 0:
index = 0
else:
index = int(self.queue_manager.pivot.currentRouteKey()[5:])
self.queue_manager.clear_SettingBox()
self.queue_manager.show_SettingBox(index)
class QueueSettingBox(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setObjectName("调度队列管理")
self.pivot = Pivot(self)
self.stackedWidget = QStackedWidget(self)
self.Layout = QVBoxLayout(self)
self.script_list: List[QueueMemberSettingBox] = []
self.Layout.addWidget(self.pivot, 0, Qt.AlignHCenter)
self.Layout.addWidget(self.stackedWidget)
self.Layout.setContentsMargins(0, 0, 0, 0)
self.pivot.currentItemChanged.connect(
lambda index: self.switch_SettingBox(int(index[5:]), if_change_pivot=False)
)
self.show_SettingBox(1)
def show_SettingBox(self, index) -> None:
"""加载所有子界面"""
queue_list = self.search_queue()
qconfig.load(
Config.app_path / "config/临时.json",
Config.queue_config,
)
Config.clear_queue_config()
for queue in queue_list:
self.add_QueueSettingBox(int(queue[0][5:]))
if (Config.app_path / "config/临时.json").exists():
(Config.app_path / "config/临时.json").unlink()
self.switch_SettingBox(index)
def switch_SettingBox(self, index: int, if_change_pivot: bool = True) -> None:
"""切换到指定的子界面"""
queue_list = self.search_queue()
if len(queue_list) == 0:
return None
if index > len(queue_list):
return None
qconfig.load(
Config.app_path
/ f"config/QueueConfig/{self.script_list[index-1].objectName()}.json",
Config.queue_config,
)
if if_change_pivot:
self.pivot.setCurrentItem(self.script_list[index - 1].objectName())
self.stackedWidget.setCurrentWidget(self.script_list[index - 1])
def clear_SettingBox(self) -> None:
"""清空所有子界面"""
for sub_interface in self.script_list:
self.stackedWidget.removeWidget(sub_interface)
sub_interface.deleteLater()
self.script_list.clear()
self.pivot.clear()
qconfig.load(
Config.app_path / "config/临时.json",
Config.queue_config,
)
Config.clear_queue_config()
if (Config.app_path / "config/临时.json").exists():
(Config.app_path / "config/临时.json").unlink()
def add_QueueSettingBox(self, uid: int) -> None:
"""添加一个调度队列设置界面"""
maa_setting_box = QueueMemberSettingBox(uid, self)
self.script_list.append(maa_setting_box)
self.stackedWidget.addWidget(self.script_list[-1])
self.pivot.addItem(routeKey=f"调度队列_{uid}", text=f"调度队列 {uid}")
def search_queue(self) -> list:
"""搜索所有调度队列实例"""
queue_list = []
if (Config.app_path / "config/QueueConfig").exists():
for json_file in (Config.app_path / "config/QueueConfig").glob("*.json"):
with json_file.open("r", encoding="utf-8") as f:
info = json.load(f)
queue_list.append([json_file.stem, info["QueueSet"]["Name"]])
return queue_list
class QueueMemberSettingBox(QWidget):
def __init__(self, uid: int, parent=None):
super().__init__(parent)
self.setObjectName(f"调度队列_{uid}")
layout = QVBoxLayout()
scrollArea = ScrollArea()
scrollArea.setWidgetResizable(True)
content_widget = QWidget()
content_layout = QVBoxLayout(content_widget)
self.queue_set = self.QueueSetSettingCard(self)
self.time = self.TimeSettingCard(self)
self.task = self.TaskSettingCard(self)
self.history = self.HistoryCard(self, f"调度队列_{uid}")
content_layout.addWidget(self.queue_set)
content_layout.addWidget(self.time)
content_layout.addWidget(self.task)
content_layout.addWidget(self.history)
content_layout.addStretch(1)
scrollArea.setWidget(content_widget)
layout.addWidget(scrollArea)
self.setLayout(layout)
class QueueSetSettingCard(HeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setTitle("队列设置")
Layout = QVBoxLayout()
self.card_Name = LineEditSettingCard(
"请输入调度队列名称",
FluentIcon.EDIT,
"调度队列名称",
"用于标识调度队列的名称",
Config.queue_config.queueSet_Name,
)
self.card_Enable = SwitchSettingCard(
FluentIcon.HOME,
"状态",
"调度队列状态",
Config.queue_config.queueSet_Enabled,
)
Layout.addWidget(self.card_Name)
Layout.addWidget(self.card_Enable)
self.viewLayout.addLayout(Layout)
class TimeSettingCard(HeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setTitle("定时设置")
widget_1 = QWidget()
Layout_1 = QVBoxLayout(widget_1)
widget_2 = QWidget()
Layout_2 = QVBoxLayout(widget_2)
Layout = QHBoxLayout()
self.card_Time_0 = TimeEditSettingCard(
FluentIcon.STOP_WATCH,
"定时 1",
"",
Config.queue_config.time_TimeEnabled_0,
Config.queue_config.time_TimeSet_0,
)
self.card_Time_1 = TimeEditSettingCard(
FluentIcon.STOP_WATCH,
"定时 2",
"",
Config.queue_config.time_TimeEnabled_1,
Config.queue_config.time_TimeSet_1,
)
self.card_Time_2 = TimeEditSettingCard(
FluentIcon.STOP_WATCH,
"定时 3",
"",
Config.queue_config.time_TimeEnabled_2,
Config.queue_config.time_TimeSet_2,
)
self.card_Time_3 = TimeEditSettingCard(
FluentIcon.STOP_WATCH,
"定时 4",
"",
Config.queue_config.time_TimeEnabled_3,
Config.queue_config.time_TimeSet_3,
)
self.card_Time_4 = TimeEditSettingCard(
FluentIcon.STOP_WATCH,
"定时 5",
"",
Config.queue_config.time_TimeEnabled_4,
Config.queue_config.time_TimeSet_4,
)
self.card_Time_5 = TimeEditSettingCard(
FluentIcon.STOP_WATCH,
"定时 6",
"",
Config.queue_config.time_TimeEnabled_5,
Config.queue_config.time_TimeSet_5,
)
self.card_Time_6 = TimeEditSettingCard(
FluentIcon.STOP_WATCH,
"定时 7",
"",
Config.queue_config.time_TimeEnabled_6,
Config.queue_config.time_TimeSet_6,
)
self.card_Time_7 = TimeEditSettingCard(
FluentIcon.STOP_WATCH,
"定时 8",
"",
Config.queue_config.time_TimeEnabled_7,
Config.queue_config.time_TimeSet_7,
)
self.card_Time_8 = TimeEditSettingCard(
FluentIcon.STOP_WATCH,
"定时 9",
"",
Config.queue_config.time_TimeEnabled_8,
Config.queue_config.time_TimeSet_8,
)
self.card_Time_9 = TimeEditSettingCard(
FluentIcon.STOP_WATCH,
"定时 10",
"",
Config.queue_config.time_TimeEnabled_9,
Config.queue_config.time_TimeSet_9,
)
Layout_1.addWidget(self.card_Time_0)
Layout_1.addWidget(self.card_Time_1)
Layout_1.addWidget(self.card_Time_2)
Layout_1.addWidget(self.card_Time_3)
Layout_1.addWidget(self.card_Time_4)
Layout_2.addWidget(self.card_Time_5)
Layout_2.addWidget(self.card_Time_6)
Layout_2.addWidget(self.card_Time_7)
Layout_2.addWidget(self.card_Time_8)
Layout_2.addWidget(self.card_Time_9)
Layout.addWidget(widget_1)
Layout.addWidget(widget_2)
self.viewLayout.addLayout(Layout)
class TaskSettingCard(HeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setTitle("任务队列")
Layout = QVBoxLayout()
member_list = self.search_member()
self.card_Member_1 = NoOptionComboBoxSettingCard(
Config.queue_config.queue_Member_1,
FluentIcon.APPLICATION,
"任务实例 1",
"第一个调起的脚本任务实例",
member_list[0],
member_list[1],
)
self.card_Member_2 = NoOptionComboBoxSettingCard(
Config.queue_config.queue_Member_2,
FluentIcon.APPLICATION,
"任务实例 2",
"第二个调起的脚本任务实例",
member_list[0],
member_list[1],
)
self.card_Member_3 = NoOptionComboBoxSettingCard(
Config.queue_config.queue_Member_3,
FluentIcon.APPLICATION,
"任务实例 3",
"第三个调起的脚本任务实例",
member_list[0],
member_list[1],
)
self.card_Member_4 = NoOptionComboBoxSettingCard(
Config.queue_config.queue_Member_4,
FluentIcon.APPLICATION,
"任务实例 4",
"第四个调起的脚本任务实例",
member_list[0],
member_list[1],
)
self.card_Member_5 = NoOptionComboBoxSettingCard(
Config.queue_config.queue_Member_5,
FluentIcon.APPLICATION,
"任务实例 5",
"第五个调起的脚本任务实例",
member_list[0],
member_list[1],
)
self.card_Member_6 = NoOptionComboBoxSettingCard(
Config.queue_config.queue_Member_6,
FluentIcon.APPLICATION,
"任务实例 6",
"第六个调起的脚本任务实例",
member_list[0],
member_list[1],
)
self.card_Member_7 = NoOptionComboBoxSettingCard(
Config.queue_config.queue_Member_7,
FluentIcon.APPLICATION,
"任务实例 7",
"第七个调起的脚本任务实例",
member_list[0],
member_list[1],
)
self.card_Member_8 = NoOptionComboBoxSettingCard(
Config.queue_config.queue_Member_8,
FluentIcon.APPLICATION,
"任务实例 8",
"第八个调起的脚本任务实例",
member_list[0],
member_list[1],
)
self.card_Member_9 = NoOptionComboBoxSettingCard(
Config.queue_config.queue_Member_9,
FluentIcon.APPLICATION,
"任务实例 9",
"第九个调起的脚本任务实例",
member_list[0],
member_list[1],
)
self.card_Member_10 = NoOptionComboBoxSettingCard(
Config.queue_config.queue_Member_10,
FluentIcon.APPLICATION,
"任务实例 10",
"第十个调起的脚本任务实例",
member_list[0],
member_list[1],
)
Layout.addWidget(self.card_Member_1)
Layout.addWidget(self.card_Member_2)
Layout.addWidget(self.card_Member_3)
Layout.addWidget(self.card_Member_4)
Layout.addWidget(self.card_Member_5)
Layout.addWidget(self.card_Member_6)
Layout.addWidget(self.card_Member_7)
Layout.addWidget(self.card_Member_8)
Layout.addWidget(self.card_Member_9)
Layout.addWidget(self.card_Member_10)
self.viewLayout.addLayout(Layout)
def search_member(self) -> list:
"""搜索所有脚本实例"""
member_list_name = ["禁用"]
member_list_text = ["未启用"]
if (Config.app_path / "config/MaaConfig").exists():
for subdir in (Config.app_path / "config/MaaConfig").iterdir():
if subdir.is_dir():
member_list_name.append(subdir.name)
with (subdir / "config.json").open("r", encoding="utf-8") as f:
info = json.load(f)
if info["MaaSet"]["Name"] != "":
member_list_text.append(
f"{subdir.name} - {info["MaaSet"]["Name"]}"
)
else:
member_list_text.append(subdir.name)
return [member_list_name, member_list_text]
class HistoryCard(HeaderCardWidget):
def __init__(self, parent=None, name: str = None):
super().__init__(parent)
self.setTitle("历史运行记录")
self.text = TextBrowser()
self.text.setMinimumHeight(300)
history = Config.get_history(name)
self.text.setPlainText(history["History"])
self.viewLayout.addWidget(self.text)

695
app/ui/setting.py Normal file
View File

@@ -0,0 +1,695 @@
# <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,
QApplication,
QVBoxLayout,
QVBoxLayout,
)
from qfluentwidgets import (
ScrollArea,
FluentIcon,
MessageBox,
Dialog,
HyperlinkCard,
HeaderCardWidget,
SwitchSettingCard,
ExpandGroupSettingCard,
PushSettingCard,
)
from datetime import datetime
import json
import subprocess
import time
import requests
from app.core import Config, MainInfoBar
from app.services import Crypto, System
from app.utils import Updater
from .Widget import InputMessageBox, LineEditSettingCard
class Setting(QWidget):
def __init__(
self,
parent=None,
):
super().__init__(parent)
self.setObjectName("设置")
layout = QVBoxLayout()
scrollArea = ScrollArea()
scrollArea.setWidgetResizable(True)
content_widget = QWidget()
content_layout = QVBoxLayout(content_widget)
self.function = FunctionSettingCard(self)
self.start = StartSettingCard(self)
self.ui = UiSettingCard(self)
self.notification = NotifySettingCard(self)
self.security = SecuritySettingCard(self)
self.updater = UpdaterSettingCard(self)
self.other = OtherSettingCard(self)
self.function.card_IfAllowSleep.checkedChanged.connect(System.set_Sleep)
self.start.card_IfSelfStart.checkedChanged.connect(System.set_SelfStart)
self.security.card_changePASSWORD.clicked.connect(self.change_PASSWORD)
self.updater.card_CheckUpdate.clicked.connect(self.get_update)
self.other.card_Notice.clicked.connect(self.show_notice)
content_layout.addWidget(self.function)
content_layout.addWidget(self.start)
content_layout.addWidget(self.ui)
content_layout.addWidget(self.notification)
content_layout.addWidget(self.security)
content_layout.addWidget(self.updater)
content_layout.addWidget(self.other)
scrollArea.setWidget(content_widget)
layout.addWidget(scrollArea)
self.setLayout(layout)
def check_PASSWORD(self) -> None:
"""检查并配置管理密钥"""
if Config.key_path.exists():
return None
while True:
choice = InputMessageBox(
self.parent().parent().parent(),
"未检测到管理密钥,请设置您的管理密钥",
"管理密钥",
"密码",
)
if choice.exec() and choice.input.text() != "":
Crypto.get_PASSWORD(choice.input.text())
break
else:
choice = MessageBox(
"警告",
"您没有设置管理密钥,无法使用本软件,请先设置管理密钥",
self.parent().parent().parent(),
)
choice.cancelButton.hide()
choice.buttonLayout.insertStretch(1)
if choice.exec():
pass
def change_PASSWORD(self) -> None:
"""修改管理密钥"""
if_change = True
while if_change:
choice = InputMessageBox(
self,
"请输入旧的管理密钥",
"旧管理密钥",
"密码",
)
if choice.exec() and choice.input.text() != "":
# 验证旧管理密钥
if Crypto.check_PASSWORD(choice.input.text()):
PASSWORD_old = choice.input.text()
# 获取新的管理密钥
while True:
choice = InputMessageBox(
self,
"请输入新的管理密钥",
"新管理密钥",
"密码",
)
if choice.exec() and choice.input.text() != "":
# 修改管理密钥
Crypto.change_PASSWORD(PASSWORD_old, choice.input.text())
MainInfoBar.push_info_bar(
"success", "操作成功", "管理密钥修改成功", 3000
)
if_change = False
break
else:
choice = MessageBox(
"确认",
"您没有输入新的管理密钥,是否取消修改管理密钥?",
self,
)
if choice.exec():
if_change = False
break
else:
choice = MessageBox("错误", "管理密钥错误", self)
choice.cancelButton.hide()
choice.buttonLayout.insertStretch(1)
if choice.exec():
pass
else:
choice = MessageBox(
"确认",
"您没有输入管理密钥,是否取消修改管理密钥?",
self,
)
if choice.exec():
break
def get_update_info(self) -> str:
"""检查主程序版本更新,返回更新信息"""
# 从本地版本信息文件获取当前版本信息
with Config.version_path.open(mode="r", encoding="utf-8") as f:
version_current = json.load(f)
main_version_current = list(
map(int, version_current["main_version"].split("."))
)
# 从远程服务器获取最新版本信息
for _ in range(3):
try:
response = requests.get(
"https://gitee.com/DLmaster_361/AUTO_MAA/raw/main/resources/version.json"
)
version_remote = response.json()
break
except Exception as e:
err = e
time.sleep(0.1)
else:
return f"获取版本信息时出错:\n{err}"
main_version_remote = list(map(int, version_remote["main_version"].split(".")))
# 有版本更新
if main_version_remote > main_version_current:
main_version_info = f" 主程序:{version_text(main_version_current)} --> {version_text(main_version_remote)}\n"
return f"发现新版本:\n{main_version_info} 更新说明:\n{version_remote['announcement'].replace("\n# ","\n ").replace("\n## ","\n - ").replace("\n- ","\n · ")}\n\n是否开始更新?\n\n 注意主程序更新时AUTO_MAA将自动关闭"
else:
return "已是最新版本~"
def get_update(self, if_question: bool = True) -> None:
"""检查版本更新,调起文件下载进程"""
# 从本地版本信息文件获取当前版本信息
with Config.version_path.open(mode="r", encoding="utf-8") as f:
version_current = json.load(f)
main_version_current = list(
map(int, version_current["main_version"].split("."))
)
updater_version_current = list(
map(int, version_current["updater_version"].split("."))
)
# 检查更新器是否存在
if not (Config.app_path / "Updater.exe").exists():
updater_version_current = [0, 0, 0, 0]
# 从远程服务器获取最新版本信息
for _ in range(3):
try:
response = requests.get(
"https://gitee.com/DLmaster_361/AUTO_MAA/raw/main/resources/version.json"
)
version_remote = response.json()
break
except Exception as e:
err = e
time.sleep(0.1)
else:
choice = MessageBox(
"错误",
f"获取版本信息时出错:\n{err}",
self,
)
choice.cancelButton.hide()
choice.buttonLayout.insertStretch(1)
if choice.exec():
return None
main_version_remote = list(map(int, version_remote["main_version"].split(".")))
updater_version_remote = list(
map(int, version_remote["updater_version"].split("."))
)
# 有版本更新
if (main_version_remote > main_version_current) or (
updater_version_remote > updater_version_current
):
# 生成版本更新信息
if main_version_remote > main_version_current:
main_version_info = f" 主程序:{version_text(main_version_current)} --> {version_text(main_version_remote)}\n"
else:
main_version_info = (
f" 主程序:{version_text(main_version_current)}\n"
)
if updater_version_remote > updater_version_current:
updater_version_info = f" 更新器:{version_text(updater_version_current)} --> {version_text(updater_version_remote)}\n"
else:
updater_version_info = (
f" 更新器:{version_text(updater_version_current)}\n"
)
# 询问是否开始版本更新
if if_question:
choice = MessageBox(
"版本更新",
f"发现新版本:\n{main_version_info}{updater_version_info} 更新说明:\n{version_remote['announcement'].replace("\n# ","\n ").replace("\n## ","\n - ").replace("\n- ","\n · ")}\n\n是否开始更新?\n\n 注意主程序更新时AUTO_MAA将自动关闭",
self,
)
if not choice.exec():
return None
# 更新更新器
if updater_version_remote > updater_version_current:
# 创建更新进程
self.updater = Updater(
Config.app_path,
"AUTO_MAA更新器",
main_version_remote,
updater_version_remote,
)
# 完成更新器的更新后更新主程序
if main_version_remote > main_version_current:
self.updater.update_process.accomplish.connect(self.update_main)
# 显示更新页面
self.updater.ui.show()
# 更新主程序
elif main_version_remote > main_version_current:
self.update_main()
# 无版本更新
else:
MainInfoBar.push_info_bar("success", "更新检查", "已是最新版本~", 3000)
def update_main(self) -> None:
"""更新主程序"""
subprocess.Popen(
str(Config.app_path / "Updater.exe"),
shell=True,
creationflags=subprocess.CREATE_NO_WINDOW,
)
self.close()
QApplication.quit()
def show_notice(self, if_show: bool = True):
"""显示公告"""
# 从远程服务器获取最新版本信息
for _ in range(3):
try:
response = requests.get(
"https://gitee.com/DLmaster_361/AUTO_MAA/raw/server/notice.json"
)
notice = response.json()
break
except Exception as e:
err = e
time.sleep(0.1)
else:
logger.warning(f"获取最新公告时出错:\n{err}")
if if_show:
choice = Dialog(
"网络错误",
f"获取最新公告时出错:\n{err}",
self,
)
choice.cancelButton.hide()
choice.buttonLayout.insertStretch(1)
choice.exec()
return None
if (Config.app_path / "resources/notice.json").exists():
with (Config.app_path / "resources/notice.json").open(
mode="r", encoding="utf-8"
) as f:
notice_local = json.load(f)
time_local = datetime.strptime(notice_local["time"], "%Y-%m-%d %H:%M")
else:
time_local = datetime.strptime("2000-01-01 00:00", "%Y-%m-%d %H:%M")
if if_show or (
datetime.now() > datetime.strptime(notice["time"], "%Y-%m-%d %H:%M")
and datetime.strptime(notice["time"], "%Y-%m-%d %H:%M") > time_local
):
choice = Dialog("公告", notice["content"], self)
choice.cancelButton.hide()
choice.buttonLayout.insertStretch(1)
if choice.exec():
with (Config.app_path / "resources/notice.json").open(
mode="w", encoding="utf-8"
) as f:
json.dump(notice, f, ensure_ascii=False, indent=4)
class FunctionSettingCard(HeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setTitle("功能")
Layout = QVBoxLayout()
self.card_IfAllowSleep = SwitchSettingCard(
icon=FluentIcon.PAGE_RIGHT,
title="启动时阻止系统休眠",
content="仅阻止电脑自动休眠,不会影响屏幕是否熄灭",
configItem=Config.global_config.function_IfAllowSleep,
)
self.card_IfSilence = self.SilenceSettingCard(self)
# 添加各组到设置卡中
Layout.addWidget(self.card_IfAllowSleep)
Layout.addWidget(self.card_IfSilence)
self.viewLayout.addLayout(Layout)
class SilenceSettingCard(ExpandGroupSettingCard):
def __init__(self, parent=None):
super().__init__(
FluentIcon.SETTING,
"静默模式",
"将各代理窗口置于后台运行,减少对前台的干扰",
parent,
)
widget = QWidget()
Layout = QVBoxLayout(widget)
self.card_IfSilence = SwitchSettingCard(
icon=FluentIcon.PAGE_RIGHT,
title="静默模式",
content="是否启用静默模式",
configItem=Config.global_config.function_IfSilence,
)
self.card_BossKey = LineEditSettingCard(
text="请输入安卓模拟器老版键",
icon=FluentIcon.PAGE_RIGHT,
title="模拟器老版键",
content="输入模拟器老版快捷键,以“+”分隔",
configItem=Config.global_config.function_BossKey,
)
Layout.addWidget(self.card_IfSilence)
Layout.addWidget(self.card_BossKey)
# 调整内部布局
self.viewLayout.setContentsMargins(0, 0, 0, 0)
self.viewLayout.setSpacing(0)
self.addGroupWidget(widget)
class StartSettingCard(HeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setTitle("启动")
Layout = QVBoxLayout()
self.card_IfSelfStart = SwitchSettingCard(
icon=FluentIcon.PAGE_RIGHT,
title="开机时自动启动",
content="将AUTO_MAA添加到开机启动项",
configItem=Config.global_config.start_IfSelfStart,
)
self.card_IfRunDirectly = SwitchSettingCard(
icon=FluentIcon.PAGE_RIGHT,
title="启动后直接运行",
content="启动AUTO_MAA后自动运行任务(暂不可用)",
configItem=Config.global_config.start_IfRunDirectly,
)
# 添加各组到设置卡中
Layout.addWidget(
self.card_IfSelfStart,
)
Layout.addWidget(self.card_IfRunDirectly)
self.viewLayout.addLayout(Layout)
class UiSettingCard(HeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setTitle("界面")
Layout = QVBoxLayout()
self.card_IfShowTray = SwitchSettingCard(
icon=FluentIcon.PAGE_RIGHT,
title="显示托盘图标",
content="常态显示托盘图标",
configItem=Config.global_config.ui_IfShowTray,
)
self.card_IfToTray = SwitchSettingCard(
icon=FluentIcon.PAGE_RIGHT,
title="最小化到托盘",
content="最小化时隐藏到托盘",
configItem=Config.global_config.ui_IfToTray,
)
# 添加各组到设置卡中
Layout.addWidget(self.card_IfShowTray)
Layout.addWidget(self.card_IfToTray)
self.viewLayout.addLayout(Layout)
class NotifySettingCard(HeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setTitle("通知")
Layout = QVBoxLayout()
self.card_IfPushPlyer = SwitchSettingCard(
icon=FluentIcon.PAGE_RIGHT,
title="推送系统通知",
content="推送系统级通知,不会在通知中心停留",
configItem=Config.global_config.notify_IfPushPlyer,
)
self.card_SendMail = self.SendMailSettingCard(self)
Layout.addWidget(self.card_IfPushPlyer)
Layout.addWidget(self.card_SendMail)
self.viewLayout.addLayout(Layout)
class SendMailSettingCard(ExpandGroupSettingCard):
def __init__(self, parent=None):
super().__init__(
FluentIcon.SETTING,
"推送邮件通知",
"通过AUTO_MAA官方通知服务邮箱推送任务结果",
parent,
)
widget = QWidget()
Layout = QVBoxLayout(widget)
self.card_IfSendMail = SwitchSettingCard(
icon=FluentIcon.PAGE_RIGHT,
title="推送邮件通知",
content="是否启用邮件通知功能",
configItem=Config.global_config.notify_IfSendMail,
)
self.MailAddress = LineEditSettingCard(
text="请输入邮箱地址",
icon=FluentIcon.PAGE_RIGHT,
title="邮箱地址",
content="接收通知的邮箱地址",
configItem=Config.global_config.notify_MailAddress,
)
self.card_IfSendErrorOnly = SwitchSettingCard(
icon=FluentIcon.PAGE_RIGHT,
title="仅推送异常信息",
content="仅在任务出现异常时推送通知",
configItem=Config.global_config.notify_IfSendErrorOnly,
)
Layout.addWidget(self.card_IfSendMail)
Layout.addWidget(self.MailAddress)
Layout.addWidget(self.card_IfSendErrorOnly)
# 调整内部布局
self.viewLayout.setContentsMargins(0, 0, 0, 0)
self.viewLayout.setSpacing(0)
self.addGroupWidget(widget)
class SecuritySettingCard(HeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setTitle("安全")
Layout = QVBoxLayout()
self.card_changePASSWORD = PushSettingCard(
text="修改",
icon=FluentIcon.VPN,
title="修改管理密钥",
content="修改用于解密用户密码的管理密钥",
)
Layout.addWidget(self.card_changePASSWORD)
self.viewLayout.addLayout(Layout)
class UpdaterSettingCard(HeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setTitle("更新")
Layout = QVBoxLayout()
self.card_IfAutoUpdate = SwitchSettingCard(
icon=FluentIcon.PAGE_RIGHT,
title="自动检查更新",
content="将在启动时自动检查AUTO_MAA是否有新版本",
configItem=Config.global_config.update_IfAutoUpdate,
)
self.card_CheckUpdate = PushSettingCard(
text="检查更新",
icon=FluentIcon.UPDATE,
title="获取最新版本",
content="检查AUTO_MAA是否有新版本",
)
Layout.addWidget(self.card_IfAutoUpdate)
Layout.addWidget(self.card_CheckUpdate)
self.viewLayout.addLayout(Layout)
class OtherSettingCard(HeaderCardWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setTitle("其他")
self.card_Notice = PushSettingCard(
text="查看",
icon=FluentIcon.PAGE_RIGHT,
title="公告",
content="查看AUTO_MAA的最新公告",
)
self.card_Association = self.AssociationSettingCard()
Layout = QVBoxLayout()
Layout.addWidget(self.card_Notice)
Layout.addWidget(self.card_Association)
self.viewLayout.addLayout(Layout)
class AssociationSettingCard(ExpandGroupSettingCard):
def __init__(self, parent=None):
super().__init__(
FluentIcon.SETTING,
"AUTO_MAA官方社群",
"加入AUTO_MAA官方社群获取更多帮助",
parent,
)
self.card_GitHubRepository = HyperlinkCard(
url="https://github.com/DLmaster361/AUTO_MAA",
text="访问GitHub仓库",
icon=FluentIcon.GITHUB,
title="GitHub",
content="查看AUTO_MAA的源代码提交问题和建议欢迎参与开发",
)
self.card_QQGroup = HyperlinkCard(
url="https://qm.qq.com/q/bd9fISNoME",
text="加入官方QQ交流群",
icon=FluentIcon.CHAT,
title="QQ群",
content="与AUTO_MAA开发者和用户交流",
)
widget = QWidget()
Layout = QVBoxLayout(widget)
Layout.addWidget(self.card_GitHubRepository)
Layout.addWidget(self.card_QQGroup)
self.viewLayout.setContentsMargins(0, 0, 0, 0)
self.viewLayout.setSpacing(0)
self.addGroupWidget(widget)
def version_text(version_numb: list) -> str:
"""将版本号列表转为可读的文本信息"""
if version_numb[3] == 0:
version = f"v{'.'.join(str(_) for _ in version_numb[0:3])}"
else:
version = (
f"v{'.'.join(str(_) for _ in version_numb[0:3])}-beta.{version_numb[3]}"
)
return version

361
app/utils/Updater.py Normal file
View File

@@ -0,0 +1,361 @@
# <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更新器
v1.1
作者DLmaster_361
"""
import sys
import json
import zipfile
import requests
import subprocess
import time
from pathlib import Path
from PySide6.QtWidgets import (
QApplication,
QDialog,
QVBoxLayout,
)
from qfluentwidgets import ProgressBar, IndeterminateProgressBar, BodyLabel
from PySide6.QtGui import QIcon
from PySide6.QtCore import QObject, QThread, Signal
def version_text(version_numb: list) -> str:
"""将版本号列表转为可读的文本信息"""
if version_numb[3] == 0:
version = f"v{'.'.join(str(_) for _ in version_numb[0:3])}"
else:
version = (
f"v{'.'.join(str(_) for _ in version_numb[0:3])}-beta.{version_numb[3]}"
)
return version
class UpdateProcess(QThread):
info = Signal(str)
progress = Signal(int, int, int)
accomplish = Signal()
def __init__(
self, app_path: Path, name: str, main_version: list, updater_version: list
) -> None:
super(UpdateProcess, self).__init__()
self.app_path = app_path
self.name = name
self.main_version = main_version
self.updater_version = updater_version
self.download_path = app_path / "AUTO_MAA_Update.zip" # 临时下载文件的路径
self.version_path = app_path / "resources/version.json"
def run(self) -> None:
# 清理可能存在的临时文件
if self.download_path.exists():
self.download_path.unlink()
self.info.emit("正在获取下载链接")
url_list = self.get_download_url()
# 验证下载地址并获取文件大小
for i in range(len(url_list)):
try:
self.info.emit(f"正在验证下载地址:{url_list[i]}")
response = requests.get(url_list[i], stream=True)
if response.status_code != 200:
self.info.emit(
f"连接失败,错误代码 {response.status_code} ,正在切换代理({i+1}/{len(url_list)}"
)
time.sleep(1)
continue
file_size = response.headers.get("Content-Length")
break
except requests.RequestException:
self.info.emit(f"请求超时,正在切换代理({i+1}/{len(url_list)}")
time.sleep(1)
else:
self.info.emit(f"连接失败,已尝试所有{len(url_list)}个代理")
return None
if file_size is None:
file_size = 1
else:
file_size = int(file_size)
try:
# 下载文件
with open(self.download_path, "wb") as f:
downloaded_size = 0
last_download_size = 0
speed = 0
last_time = time.time()
for chunk in response.iter_content(chunk_size=8192):
# 写入已下载数据
f.write(chunk)
downloaded_size += len(chunk)
# 计算下载速度
if time.time() - last_time >= 1.0:
speed = (
(downloaded_size - last_download_size)
/ (time.time() - last_time)
/ 1024
)
last_download_size = downloaded_size
last_time = time.time()
# 更新下载进度
if speed >= 1024:
self.info.emit(
f"正在下载:{self.name} 已下载:{downloaded_size / 1048576:.2f}/{file_size / 1048576:.2f} MB {downloaded_size / file_size * 100:.2f}% 下载速度:{speed / 1024:.2f} MB/s",
)
else:
self.info.emit(
f"正在下载:{self.name} 已下载:{downloaded_size / 1048576:.2f}/{file_size / 1048576:.2f} MB {downloaded_size / file_size * 100:.2f}% 下载速度:{speed:.2f} KB/s",
)
self.progress.emit(0, 100, int(downloaded_size / file_size * 100))
except Exception as e:
e = str(e)
e = "\n".join([e[_ : _ + 75] for _ in range(0, len(e), 75)])
self.info.emit(f"下载{self.name}时出错:\n{e}")
return None
# 解压
try:
while True:
try:
self.info.emit("正在解压更新文件")
self.progress.emit(0, 0, 0)
with zipfile.ZipFile(self.download_path, "r") as zip_ref:
zip_ref.extractall(self.app_path)
break
except PermissionError:
self.info.emit("解压出错AUTO_MAA正在运行正在等待其关闭")
time.sleep(1)
self.info.emit("正在删除临时文件")
self.progress.emit(0, 0, 0)
self.download_path.unlink()
self.info.emit(f"{self.name}更新成功!")
self.progress.emit(0, 100, 100)
except Exception as e:
e = str(e)
e = "\n".join([e[_ : _ + 75] for _ in range(0, len(e), 75)])
self.info.emit(f"解压更新时出错:\n{e}")
return None
# 更新version文件
with open(self.version_path, "r", encoding="utf-8") as f:
version_info = json.load(f)
if self.name == "AUTO_MAA更新器":
version_info["updater_version"] = ".".join(map(str, self.updater_version))
elif self.name == "AUTO_MAA主程序":
version_info["main_version"] = ".".join(map(str, self.main_version))
with open(self.version_path, "w", encoding="utf-8") as f:
json.dump(version_info, f, ensure_ascii=False, indent=4)
# 主程序更新完成后打开AUTO_MAA
if self.name == "AUTO_MAA主程序":
subprocess.Popen(
str(self.app_path / "AUTO_MAA.exe"),
shell=True,
creationflags=subprocess.CREATE_NO_WINDOW,
)
self.accomplish.emit()
def get_download_url(self) -> list:
"""获取下载链接"""
try_num = 3
for i in range(try_num):
try:
response = requests.get(
"https://gitee.com/DLmaster_361/AUTO_MAA/raw/main/resources/version.json"
)
if response.status_code != 200:
self.info.emit(
f"连接失败,错误代码 {response.status_code} ,正在重试({i+1}/{try_num}"
)
time.sleep(0.1)
continue
version_remote = response.json()
PROXY_list = version_remote["proxy_list"]
break
except requests.RequestException:
self.info.emit(f"请求超时,正在重试({i+1}/{try_num}")
time.sleep(0.1)
except KeyError:
self.info.emit(f"未找到远端代理网址项,正在重试({i+1}/{try_num}")
time.sleep(0.1)
else:
self.info.emit("获取远端代理信息失败,将使用默认代理地址")
PROXY_list = [
"",
"https://gitproxy.click/",
"https://cdn.moran233.xyz/",
"https://gh.llkk.cc/",
"https://github.akams.cn/",
"https://www.ghproxy.cn/",
"https://ghp.ci/",
]
time.sleep(1)
url_list = []
if self.name == "AUTO_MAA主程序":
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"
)
for i in range(len(PROXY_list)):
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"
)
elif self.name == "AUTO_MAA更新器":
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"
)
for i in range(len(PROXY_list)):
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"
)
return url_list
class Updater(QObject):
def __init__(
self, app_path: Path, name: str, main_version: list, updater_version: list
) -> None:
super().__init__()
self.ui = QDialog()
self.ui.setWindowTitle("AUTO_MAA更新器")
self.ui.resize(700, 70)
self.ui.setWindowIcon(
QIcon(str(app_path / "resources/icons/AUTO_MAA_Updater.ico"))
)
# 创建垂直布局
self.Layout = QVBoxLayout(self.ui)
self.info = BodyLabel("正在初始化", self.ui)
self.progress_1 = IndeterminateProgressBar(self.ui)
self.progress_2 = ProgressBar(self.ui)
self.update_progress(0, 0, 0)
self.Layout.addWidget(self.info)
self.Layout.addStretch(1)
self.Layout.addWidget(self.progress_1)
self.Layout.addWidget(self.progress_2)
self.Layout.addStretch(1)
self.update_process = UpdateProcess(
app_path, name, main_version, updater_version
)
self.update_process.info.connect(self.update_info)
self.update_process.progress.connect(self.update_progress)
self.update_process.start()
def update_info(self, text: str) -> None:
self.info.setText(text)
def update_progress(self, begin: int, end: int, current: int) -> None:
if begin == 0 and end == 0:
self.progress_2.setVisible(False)
self.progress_1.setVisible(True)
else:
self.progress_1.setVisible(False)
self.progress_2.setVisible(True)
self.progress_2.setRange(begin, end)
self.progress_2.setValue(current)
class AUTO_MAA_Updater(QApplication):
def __init__(
self, app_path: Path, name: str, main_version: list, updater_version: list
) -> None:
super().__init__()
self.main = Updater(app_path, name, main_version, updater_version)
self.main.ui.show()
if __name__ == "__main__":
# 获取软件自身的路径
app_path = Path.cwd()
# 从本地版本信息文件获取当前版本信息
if (app_path / "resources/version.json").exists():
with (app_path / "resources/version.json").open(
mode="r", encoding="utf-8"
) as f:
version_current = json.load(f)
main_version_current = list(
map(int, version_current["main_version"].split("."))
)
else:
main_version_current = [0, 0, 0, 0]
# 从远程服务器获取最新版本信息
for _ in range(3):
try:
response = requests.get(
"https://gitee.com/DLmaster_361/AUTO_MAA/raw/main/resources/version.json"
)
version_remote = response.json()
main_version_remote = list(
map(int, version_remote["main_version"].split("."))
)
break
except Exception as e:
err = e
time.sleep(0.1)
else:
sys.exit(f"获取版本信息时出错:\n{err}")
# 启动更新线程
if main_version_remote > main_version_current:
app = AUTO_MAA_Updater(
app_path,
"AUTO_MAA主程序",
main_version_remote,
[],
)
sys.exit(app.exec())

View File

@@ -18,8 +18,17 @@
# DLmaster_361@163.com
import os
"""
AUTO_MAA
AUTO_MAA工具包
v4.2
作者DLmaster_361
"""
os.system(
"pyinstaller -F --version-file res/info.txt -w --icon=res/AUTO_MAA.ico AUTO_MAA.py"
)
__version__ = "4.2.0"
__author__ = "DLmaster361 <DLmaster_361@163.com>"
__license__ = "GPL-3.0 license"
from .Updater import Updater
__all__ = ["Updater"]

109
app/utils/package.py Normal file
View File

@@ -0,0 +1,109 @@
# <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
"""
import os
import sys
import json
import shutil
from pathlib import Path
def version_text(version_numb: list) -> str:
"""将版本号列表转为可读的文本信息"""
if version_numb[3] == 0:
version = f"v{'.'.join(str(_) for _ in version_numb[0:3])}"
else:
version = (
f"v{'.'.join(str(_) for _ in version_numb[0:3])}-beta.{version_numb[3]}"
)
return version
if __name__ == "__main__":
root_path = Path(sys.argv[0]).resolve().parent
with (root_path / "resources/version.json").open(mode="r", encoding="utf-8") as f:
version = json.load(f)
main_version_numb = list(map(int, version["main_version"].split(".")))
updater_version_numb = list(map(int, version["updater_version"].split(".")))
print("Packaging AUTO_MAA main program ...")
os.system(
"powershell -Command python -m nuitka --standalone --onefile --mingw64"
" --enable-plugins=pyside6 --windows-console-mode=disable"
" --onefile-tempdir-spec='{TEMP}\\AUTO_MAA'"
" --windows-icon-from-ico=resources\\icons\\AUTO_MAA.ico"
" --company-name='AUTO_MAA Team' --product-name=AUTO_MAA"
f" --file-version={version["main_version"]}"
f" --product-version={version["main_version"]}"
" --file-description='AUTO_MAA Component'"
" --copyright='Copyright © 2024 DLmaster361'"
" --assume-yes-for-downloads --output-filename=AUTO_MAA"
" --remove-output main.py"
)
print("AUTO_MAA main program packaging completed !")
shutil.copy(root_path / "app/utils/Updater.py", root_path)
file_content = (root_path / "Updater.py").read_text(encoding="utf-8")
(root_path / "Updater.py").write_text(
file_content.replace(
"from .version import version_text", "from app import version_text"
),
encoding="utf-8",
)
print("Packaging AUTO_MAA update program ...")
os.system(
"powershell -Command python -m nuitka --standalone --onefile --mingw64"
" --enable-plugins=pyside6 --windows-console-mode=disable"
" --onefile-tempdir-spec='{TEMP}\\AUTO_MAA_Updater'"
" --windows-icon-from-ico=resources\\icons\\AUTO_MAA_Updater.ico"
" --company-name='AUTO_MAA Team' --product-name=AUTO_MAA"
f" --file-version={version["updater_version"]}"
f" --product-version={version["main_version"]}"
" --file-description='AUTO_MAA Component'"
" --copyright='Copyright © 2024 DLmaster361'"
" --assume-yes-for-downloads --output-filename=Updater"
" --remove-output Updater.py"
)
print("AUTO_MAA update program packaging completed !")
(root_path / "Updater.py").unlink()
(root_path / "version_info.txt").write_text(
f"{version_text(main_version_numb)}\n{version_text(updater_version_numb)}{version["announcement"]}",
encoding="utf-8",
)

View File

@@ -1,5 +0,0 @@
龙门币CE-6
技能CA-5
红票AP-5
经验LS-6
剿灭模式Annihilation

View File

@@ -1,978 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AUTO_MAA</class>
<widget class="QWidget" name="AUTO_MAA">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1280</width>
<height>720</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_13">
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>2</number>
</property>
<widget class="QWidget" name="tab_users">
<property name="enabled">
<bool>true</bool>
</property>
<attribute name="title">
<string>用户管理</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QFrame" name="frame_manage">
<property name="frameShape">
<enum>QFrame::Shape::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Shadow::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="pushButton_new">
<property name="text">
<string>新建</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton_del">
<property name="text">
<string>删除</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="pushButton_password">
<property name="text">
<string>显示密码</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton_refresh">
<property name="text">
<string>刷新</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QTableWidget" name="tableWidget_userlist">
<column>
<property name="text">
<string>用户名</string>
</property>
</column>
<column>
<property name="text">
<string>手机号码</string>
</property>
</column>
<column>
<property name="text">
<string>代理天数</string>
</property>
</column>
<column>
<property name="text">
<string>状态</string>
</property>
</column>
<column>
<property name="text">
<string>执行情况</string>
</property>
</column>
<column>
<property name="text">
<string>关卡</string>
</property>
</column>
<column>
<property name="text">
<string>备选关卡-1</string>
</property>
</column>
<column>
<property name="text">
<string>备选关卡-2</string>
</property>
</column>
<column>
<property name="text">
<string>剿灭</string>
</property>
</column>
<column>
<property name="text">
<string>自定义基建</string>
</property>
</column>
<column>
<property name="text">
<string>密码</string>
</property>
</column>
<column>
<property name="text">
<string>备注</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_run">
<attribute name="title">
<string>执行</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_11">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>定时执行</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="2" column="0">
<widget class="QFrame" name="frame_timeset1">
<property name="frameShape">
<enum>QFrame::Shape::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Shadow::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_7" stretch="0,0">
<item>
<widget class="QCheckBox" name="checkBox_t1">
<property name="enabled">
<bool>true</bool>
</property>
<property name="minimumSize">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QTimeEdit" name="timeEdit_1"/>
</item>
</layout>
</widget>
</item>
<item row="3" column="4">
<widget class="QFrame" name="frame_timeset10">
<property name="frameShape">
<enum>QFrame::Shape::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Shadow::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_16">
<item>
<widget class="QCheckBox" name="checkBox_t10">
<property name="enabled">
<bool>true</bool>
</property>
<property name="minimumSize">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QTimeEdit" name="timeEdit_10"/>
</item>
</layout>
</widget>
</item>
<item row="2" column="2">
<widget class="QFrame" name="frame_timeset3">
<property name="frameShape">
<enum>QFrame::Shape::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Shadow::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_9">
<item>
<widget class="QCheckBox" name="checkBox_t3">
<property name="enabled">
<bool>true</bool>
</property>
<property name="minimumSize">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QTimeEdit" name="timeEdit_3"/>
</item>
</layout>
</widget>
</item>
<item row="2" column="1">
<widget class="QFrame" name="frame_timeset2">
<property name="frameShape">
<enum>QFrame::Shape::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Shadow::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_8">
<item>
<widget class="QCheckBox" name="checkBox_t2">
<property name="enabled">
<bool>true</bool>
</property>
<property name="minimumSize">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QTimeEdit" name="timeEdit_2"/>
</item>
</layout>
</widget>
</item>
<item row="3" column="3">
<widget class="QFrame" name="frame_timeset9">
<property name="frameShape">
<enum>QFrame::Shape::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Shadow::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_15">
<item>
<widget class="QCheckBox" name="checkBox_t9">
<property name="enabled">
<bool>true</bool>
</property>
<property name="minimumSize">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QTimeEdit" name="timeEdit_9"/>
</item>
</layout>
</widget>
</item>
<item row="2" column="3">
<widget class="QFrame" name="frame_timeset4">
<property name="frameShape">
<enum>QFrame::Shape::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Shadow::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_10">
<item>
<widget class="QCheckBox" name="checkBox_t4">
<property name="enabled">
<bool>true</bool>
</property>
<property name="minimumSize">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QTimeEdit" name="timeEdit_4"/>
</item>
</layout>
</widget>
</item>
<item row="3" column="0">
<widget class="QFrame" name="frame_timeset6">
<property name="frameShape">
<enum>QFrame::Shape::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Shadow::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_12">
<item>
<widget class="QCheckBox" name="checkBox_t6">
<property name="enabled">
<bool>true</bool>
</property>
<property name="minimumSize">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QTimeEdit" name="timeEdit_6"/>
</item>
</layout>
</widget>
</item>
<item row="2" column="4">
<widget class="QFrame" name="frame_timeset5">
<property name="frameShape">
<enum>QFrame::Shape::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Shadow::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_11">
<item>
<widget class="QCheckBox" name="checkBox_t5">
<property name="enabled">
<bool>true</bool>
</property>
<property name="minimumSize">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QTimeEdit" name="timeEdit_5"/>
</item>
</layout>
</widget>
</item>
<item row="3" column="2">
<widget class="QFrame" name="frame_timeset8">
<property name="frameShape">
<enum>QFrame::Shape::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Shadow::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_13">
<item>
<widget class="QCheckBox" name="checkBox_t8">
<property name="enabled">
<bool>true</bool>
</property>
<property name="minimumSize">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QTimeEdit" name="timeEdit_7"/>
</item>
</layout>
</widget>
</item>
<item row="3" column="1">
<widget class="QFrame" name="frame_timeset7">
<property name="frameShape">
<enum>QFrame::Shape::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Shadow::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_14">
<item>
<widget class="QCheckBox" name="checkBox_t7">
<property name="enabled">
<bool>true</bool>
</property>
<property name="minimumSize">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QTimeEdit" name="timeEdit_8"/>
</item>
</layout>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>调度台</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<layout class="QVBoxLayout" name="verticalLayout_9">
<item>
<widget class="QFrame" name="frame_main">
<property name="frameShape">
<enum>QFrame::Shape::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Shadow::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label_main">
<property name="text">
<string>调度器</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="pushButton_runnow">
<property name="text">
<string>立即执行</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QFrame" name="frame_run">
<property name="frameShape">
<enum>QFrame::Shape::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Shadow::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="QLabel" name="label_run">
<property name="text">
<string>代理中</string>
</property>
</widget>
</item>
<item>
<widget class="QTextBrowser" name="textBrowser_run"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QFrame" name="frame_wait">
<property name="frameShape">
<enum>QFrame::Shape::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Shadow::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<widget class="QLabel" name="label_wait">
<property name="text">
<string>等待中</string>
</property>
</widget>
</item>
<item>
<widget class="QTextBrowser" name="textBrowser_wait"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QFrame" name="frame_over">
<property name="frameShape">
<enum>QFrame::Shape::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Shadow::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_7">
<item>
<widget class="QLabel" name="label_over">
<property name="text">
<string>已完成</string>
</property>
</widget>
</item>
<item>
<widget class="QTextBrowser" name="textBrowser_over"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QFrame" name="frame_error">
<property name="frameShape">
<enum>QFrame::Shape::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Shadow::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_8">
<item>
<widget class="QLabel" name="label_error">
<property name="text">
<string>异常</string>
</property>
</widget>
</item>
<item>
<widget class="QTextBrowser" name="textBrowser_error"/>
</item>
</layout>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QFrame" name="frame">
<property name="frameShape">
<enum>QFrame::Shape::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Shadow::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_10">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>日志</string>
</property>
</widget>
</item>
<item>
<widget class="QTextBrowser" name="textBrowser_log"/>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_set">
<attribute name="title">
<string>设置</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_12">
<item>
<widget class="QGroupBox" name="groupBox_4">
<property name="title">
<string>MAA设置</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_14">
<item>
<widget class="QFrame" name="frame_maaset">
<property name="frameShape">
<enum>QFrame::Shape::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Shadow::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>MAA路径</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="lineEdit_MAApath"/>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_3">
<property name="title">
<string>时间限制</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QFrame" name="frame_routine">
<property name="frameShape">
<enum>QFrame::Shape::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Shadow::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QLabel" name="label_routine">
<property name="text">
<string>日常限制</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QSpinBox" name="spinBox_routine">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_routineunit">
<property name="text">
<string>分钟</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="0" column="1">
<widget class="QFrame" name="frame_annihilation">
<property name="frameShape">
<enum>QFrame::Shape::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Shadow::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<widget class="QLabel" name="label_annihilation">
<property name="text">
<string>剿灭限制</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QSpinBox" name="spinBox_annihilation">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_annihilationunit">
<property name="text">
<string>分钟</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="0" column="2">
<widget class="QFrame" name="frame_annihilation_2">
<property name="frameShape">
<enum>QFrame::Shape::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Shadow::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_17">
<item>
<widget class="QLabel" name="label_num">
<property name="text">
<string>运行失败重试次数上限</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_5">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QSpinBox" name="spinBox_numt">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_num_2">
<property name="text">
<string>次</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_5">
<property name="title">
<string>管理密钥</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0">
<widget class="QPushButton" name="pushButton_changePASSWORD">
<property name="text">
<string>修改管理密钥</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_6">
<property name="title">
<string>tips</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_15">
<item>
<widget class="QTextBrowser" name="textBrowser">
<property name="html">
<string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;meta charset=&quot;utf-8&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
hr { height: 1px; border-width: 0; }
li.unchecked::marker { content: &quot;\2610&quot;; }
li.checked::marker { content: &quot;\2612&quot;; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Microsoft YaHei UI'; font-size:9pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;致用户:&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt; 这是AUTO_MAA_v3.1,项目基本完成可视化。&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt; 正式版去除了命令行窗口但这不意味着BUG不会出现。由于用户与项目贡献者的稀缺我们无法确保正式版足够完善还望谅解。&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt; 您可以通过官方仓库 &lt;a href=&quot;https://github.com/DLmaster361/AUTO_MAA/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#007ad6;&quot;&gt;DLmaster361/AUTO_MAA&lt;/span&gt;&lt;/a&gt; 发布Issues求助。&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt; 官方QQ群957750551&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p align=&quot;right&quot; style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;DLmaster&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_notice">
<property name="text">
<string>注意:在用户管理页与设置页执行的所有更改,不会对正在执行的任务生效。</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

54
main.py Normal file
View File

@@ -0,0 +1,54 @@
# <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 QApplication
from PySide6.QtCore import Qt
from qfluentwidgets import FluentTranslator
import sys
@logger.catch
def main():
application = QApplication(sys.argv)
QApplication.setAttribute(Qt.AA_DontCreateNativeWidgetSiblings)
translator = FluentTranslator()
application.installTranslator(translator)
from app.ui.main_window import AUTO_MAA
window = AUTO_MAA()
window.show_ui("显示主窗口")
window.start_up_task()
sys.exit(application.exec())
if __name__ == "__main__":
main()

View File

@@ -1,3 +1,11 @@
loguru
plyer
PySide6
PySide6-Fluent-Widgets[full]
psutil
opencv-python
pywin32
pyautogui
pycryptodome
pyinstaller
requests
nuitka==2.6

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 746 KiB

View File

@@ -1,43 +0,0 @@
# UTF-8
#
VSVersionInfo(
ffi=FixedFileInfo(
# filevers and prodvers should be always a tuple with four items: (1, 2, 3, 4)
# Set not needed items to zero 0.
filevers=(3, 1, 0, 0),
prodvers=(0, 0, 0, 0),
# Contains a bitmask that specifies the valid bits 'flags'r
mask=0x3f,
# Contains a bitmask that specifies the Boolean attributes of the file.
flags=0x0,
# The operating system for which this file was designed.
# 0x4 - NT and there is no need to change it.
OS=0x4,
# The general type of file.
# 0x1 - the file is an application.
fileType=0x1,
# The function of the file.
# 0x0 - the function is not defined for this fileType
subtype=0x0,
# Creation date and time stamp.
date=(0, 0)
),
kids=[
VarFileInfo([VarStruct('Translation', [0, 1200])]),
StringFileInfo(
[
StringTable(
'000004b0',
[StringStruct('Comments', 'https://github.com/DLmaster361/AUTO_MAA/'),
StringStruct('CompanyName', 'AUTO_MAA Team'),
StringStruct('FileDescription', 'AUTO_MAA Component'),
StringStruct('FileVersion', '3.1'),
StringStruct('InternalName', 'AUTO_MAA'),
StringStruct('LegalCopyright', 'Copyright © 2024 DLmaster361'),
StringStruct('OriginalFilename', 'AUTO_MAA'),
StringStruct('ProductName', 'AUTO_MAA'),
StringStruct('ProductVersion', 'v3.1'),
StringStruct('Assembly Version', 'v3.1')])
])
]
)

14
res/version.json Normal file
View File

@@ -0,0 +1,14 @@
{
"main_version": "4.2.0.0",
"updater_version": "1.1.0.0",
"announcement": "\n# 这是一个中转版本,此版本后更换程序架构方式。\n# 由于更新方法无法通用,您需要在完成本次更新后再次检查更新以获取最新版本。\n",
"proxy_list":[
"",
"https://gitproxy.click/",
"https://cdn.moran233.xyz/",
"https://gh.llkk.cc/",
"https://github.akams.cn/",
"https://www.ghproxy.cn/",
"https://ghp.ci/"
]
}

View File

@@ -1,11 +1,13 @@
#主界面
"MainFunction.ActionAfterCompleted": "ExitEmulatorAndSelf" #完成后
"MainFunction.PostActions": "12" #完成后
"TaskQueue.WakeUp.IsChecked": "True" #开始唤醒
"TaskQueue.Recruiting.IsChecked": "True" #自动公招
"TaskQueue.Base.IsChecked": "True" #基建换班
"TaskQueue.Combat.IsChecked": "True" #刷理智
"TaskQueue.Mission.IsChecked": "True" #领取奖励
"TaskQueue.Mall.IsChecked": "True" #获取信用及购物
"TaskQueue.AutoRoguelike.IsChecked": "False" #自动肉鸽
"TaskQueue.Reclamation.IsChecked": "False" #生息演算
#刷理智
"MainFunction.Stage1": "" #主关卡
"MainFunction.Stage2": "" #备选关卡1
@@ -16,16 +18,28 @@
"GUI.CustomStageCode": "False" #手动输入关卡名
"GUI.UseAlternateStage": "False" #使用备选关卡
"Fight.UseRemainingSanityStage": "True" #使用剩余理智
"GUI.AllowUseStoneSave": "False" #允许吃源石保持状态
"Fight.UseExpiringMedicine": "False" #无限吃48小时内过期的理智药
"GUI.HideUnavailableStage": "False" #隐藏当日不开放关卡
"GUI.HideSeries": "False" #隐藏连战次数
"Infrast.CustomInfrastPlanShowInFightSettings": "False" #显示基建计划
"Penguin.EnablePenguin": "True" #上报企鹅物流
"Yituliu.EnableYituliu": "True" #上报一图流
#基建换班
"Infrast.CustomInfrastEnabled": "True" #启用自定义基建配置
"Infrast.CustomInfrastPlanIndex": "1" #自定义基建配置索引
"Infrast.DefaultInfrast": "user_defined" #内置配置
"Infrast.IsCustomInfrastFileReadOnly": "False" #自定义基建配置文件只读
"Infrast.CustomInfrastFile": "" #自定义基建配置文件地址
#设置
"Start.ClientType": "Bilibili"、 "Official" #服务器
G"Timer.Timer1": "False" #时间设置1
G"VersionUpdate.ScheduledUpdateCheck": "True" #定时检查更新
G"VersionUpdate.AutoDownloadUpdatePackage": "True" #自动下载更新包
G"VersionUpdate.AutoInstallUpdatePackage": "True" #自动安装更新包
G"Start.MinimizeDirectly": "True" #启动MAA后直接最小化
"Start.RunDirectly": "True" #启动MAA后直接运行
"Start.StartEmulator": "True" #启动MAA后自动开启模拟器
"Start.OpenEmulatorAfterLaunch": "True" #启动MAA后自动开启模拟器
G"GUI.UseTray": "True" #显示托盘图标
G"GUI.MinimizeToTray": "False" #最小化时隐藏至托盘
"Start.EmulatorPath" #模拟器路径

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 309 KiB

14
resources/version.json Normal file
View File

@@ -0,0 +1,14 @@
{
"main_version": "4.2.2.0",
"updater_version": "1.1.1.0",
"announcement": "\n## 新增功能\n- 调度队列上线支持MAA多开 #12\n- 公告系统上线\n## 修复BUG\n- 修复手机号码不全时引发的混乱\n- 添加了一堆BUG确信\n## 程序优化\n- 界面重构,引入`QFluentWidgets`美化界面",
"proxy_list": [
"",
"https://gitproxy.click/",
"https://cdn.moran233.xyz/",
"https://gh.llkk.cc/",
"https://github.akams.cn/",
"https://www.ghproxy.cn/",
"https://ghp.ci/"
]
}

View File

@@ -1,4 +0,0 @@
项目初始阶段,不会提供专门的版本更新程序,您需要手动更新程序。
v2.1.5及以前的用户,由于新版本采用全新的架构,您需要手动输入之前的信息。
v3.0_Beta版用户直接用AUTO_MAA.exe替代gui.exe后重新设置每个用户的“自定义基建”选项输入“-”以关闭该功能,输入自定义基建配置文件地址以开启该功能)。
新用户请忽略本说明。