Compare commits
506 Commits
v3.1
...
v4.3.10-be
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1aa4da1adf | ||
|
|
0e8b6b0b6b | ||
|
|
1a2c1b976f | ||
|
|
1cc242fa51 | ||
|
|
18dfdba15d | ||
|
|
b04ac4eec6 | ||
|
|
c009f0c891 | ||
|
|
d2dc0bd295 | ||
|
|
ddbb5b7f19 | ||
|
|
954c25090b | ||
|
|
0b6cc59de1 | ||
|
|
2271b5741d | ||
|
|
8a438b041f | ||
|
|
dd92fcc4d8 | ||
|
|
8f66ca0e16 | ||
|
|
895ba1d24a | ||
| e49b807bef | |||
|
|
73c15b5e93 | ||
|
|
e505ea8c51 | ||
|
|
21e7df7c3e | ||
|
|
2d72ca66a4 | ||
|
|
4725a30165 | ||
|
|
f3c977f1b3 | ||
|
|
9a0e7265c6 | ||
|
|
3f8e2fbe6b | ||
|
|
590b13e916 | ||
|
|
0f6aee56e5 | ||
|
|
daf18e7295 | ||
|
|
9bcc87f663 | ||
|
|
e7205ce0aa | ||
|
|
e3c4b2edc8 | ||
|
|
222a3b35a2 | ||
|
|
cd5dfd56b2 | ||
|
|
7d5c6b8222 | ||
|
|
4dbf4736e4 | ||
|
|
d50504181e | ||
|
|
c7e94dfcd1 | ||
|
|
a752b67ca1 | ||
|
|
078736337d | ||
|
|
de1058a28c | ||
|
|
740797a689 | ||
|
|
26328920a2 | ||
|
|
9c447bbdf9 | ||
|
|
fac85a889f | ||
|
|
f5d898c89e | ||
| 974a4b634a | |||
| 3127c83603 | |||
|
|
8d69e43f72 | ||
|
|
86df9e7a50 | ||
| 59ff9bf818 | |||
|
|
1641e32e3d | ||
|
|
a482087abd | ||
| bc5b15cec2 | |||
| 3787c25a77 | |||
|
|
0b06b499e4 | ||
|
|
04079dd57b | ||
|
|
34ac0c5ab3 | ||
| 0d904b229e | |||
| c0f887ff9d | |||
|
|
cf95075d01 | ||
|
|
d78a764d87 | ||
|
|
a368f4b722 | ||
|
|
803fe4568f | ||
|
|
1162d5dcc1 | ||
|
|
aa83058e39 | ||
|
|
061f205224 | ||
|
|
5d966f98df | ||
|
|
0037914db8 | ||
|
|
13d0115475 | ||
|
|
5bdb5ad2bf | ||
| a5d733c8df | |||
|
|
0b038e2997 | ||
|
|
5e46040db6 | ||
|
|
f2b04dd0f6 | ||
|
|
2177c1b40e | ||
|
|
d1f4cffe8f | ||
|
|
74ce441b90 | ||
|
|
5893aa2426 | ||
|
|
cb7e7bf9d4 | ||
|
|
fbfdc6aa12 | ||
|
|
e7b6743e10 | ||
|
|
ff4283e917 | ||
|
|
890886d62d | ||
|
|
fd75dda2b1 | ||
|
|
f22c1aeae3 | ||
|
|
d68d49a469 | ||
|
|
1900d4eaf5 | ||
|
|
02833209d5 | ||
| 2058c0218c | |||
|
|
8896e723eb | ||
|
|
edcc614833 | ||
|
|
23fe1ff0be | ||
|
|
19d1dc9f28 | ||
|
|
24b93cfcad | ||
|
|
d3298fac8a | ||
|
|
fba5395bf0 | ||
|
|
2c4508ee16 | ||
| d239443555 | |||
| e45ad08fab | |||
| ddf5d26c4b | |||
|
|
ce74dcf912 | ||
|
|
41412e1ef4 | ||
|
|
1395d48cd0 | ||
|
|
418c3d4742 | ||
|
|
17ec962a22 | ||
|
|
989ee73549 | ||
|
|
7e452e1253 | ||
|
|
5bdb5c8025 | ||
|
|
924a5fea0b | ||
|
|
b51a57a6ee | ||
|
|
4079188881 | ||
|
|
174163e305 | ||
|
|
0886439685 | ||
|
|
34bf5a4fe8 | ||
|
|
e6a97f2b17 | ||
|
|
fecff625a3 | ||
|
|
6f540036a0 | ||
|
|
86d72aec39 | ||
|
|
39876832f3 | ||
|
|
f3af6ddbbc | ||
|
|
ba7299e20c | ||
|
|
5db9d934b2 | ||
|
|
5c8eebf12c | ||
|
|
e725f6d2b2 | ||
|
|
494b655156 | ||
|
|
2940f2557c | ||
|
|
5e4660670f | ||
| e8d592ae76 | |||
|
|
97ea51df59 | ||
|
|
986061dc97 | ||
|
|
fe1910d16f | ||
|
|
63cb1aaa74 | ||
| 49ebd50077 | |||
|
|
4a6f874210 | ||
|
|
9394c7a9c5 | ||
|
|
7e502420fa | ||
|
|
12f4b764de | ||
|
|
4da4b7d552 | ||
|
|
d38abbbaa0 | ||
|
|
67bf7f649e | ||
|
|
acb35403b0 | ||
|
|
7d5dccc649 | ||
|
|
a7e0e7b217 | ||
|
|
9ce75b2dda | ||
|
|
d2022819f6 | ||
|
|
c8b342ba01 | ||
|
|
63823d5c89 | ||
|
|
cb17cc32da | ||
|
|
14d0e6d438 | ||
|
|
878fbad06a | ||
|
|
deb0506163 | ||
|
|
c4aeb673fd | ||
|
|
915ee59643 | ||
|
|
1568e120be | ||
|
|
d19dd3496d | ||
|
|
62c86ce477 | ||
|
|
c727eddc54 | ||
|
|
9c946ef6dc | ||
|
|
38a04fc4b2 | ||
| bded794647 | |||
|
|
539cb1de99 | ||
| 2e9ff47dbb | |||
|
|
c01079af1b | ||
|
|
cca1acb6f6 | ||
|
|
d7e502e22f | ||
|
|
bbeab360bc | ||
|
|
a78b7fdb29 | ||
| 273fbe2261 | |||
| ba9855c616 | |||
| c54f894f4f | |||
|
|
9f88f92ec0 | ||
|
|
a80e96c2cd | ||
|
|
7774612810 | ||
| 088ea1817c | |||
|
|
f362c8f7ef | ||
|
|
648f42b7e0 | ||
|
|
9a56cc350d | ||
|
|
50cd49217f | ||
|
|
7ed4b7db57 | ||
| b359cd623b | |||
|
|
a363e8dc34 | ||
|
|
52affc0d76 | ||
|
|
fe26f29f93 | ||
|
|
67b8725156 | ||
|
|
2a235b2bc9 | ||
|
|
dd022cf356 | ||
|
|
62e5bb30e2 | ||
|
|
675e11960a | ||
|
|
0c274ecbe0 | ||
|
|
2dfcd3f131 | ||
|
|
053acd138f | ||
|
|
3f20ae62be | ||
|
|
d342c7c827 | ||
|
|
3da0cfd0d0 | ||
|
|
acc4045580 | ||
|
|
6ee577302f | ||
|
|
d52856180a | ||
|
|
d4d479ca20 | ||
|
|
364af4b9c5 | ||
|
|
9e0d81fb1d | ||
|
|
2ee2c37479 | ||
|
|
528925b969 | ||
| 4851b40777 | |||
|
|
6372ad4e0a | ||
|
|
465bc9137e | ||
|
|
e8b6f5d893 | ||
|
|
54b697f2ee | ||
|
|
70df428825 | ||
|
|
8993d66056 | ||
|
|
863e6fb25e | ||
|
|
181856173e | ||
|
|
576fe59bbc | ||
|
|
c73aca71f7 | ||
|
|
ce264de963 | ||
|
|
1feb0cf83f | ||
|
|
6292624d41 | ||
| 4271a07f03 | |||
|
|
254fb6916f | ||
|
|
21857325a2 | ||
|
|
175d6860a3 | ||
|
|
d1c8f98408 | ||
|
|
3499fa9067 | ||
|
|
cca2cd774c | ||
|
|
6d60f8adb8 | ||
|
|
3b406a7974 | ||
| a116b3359c | |||
| 928019390b | |||
| 022b698f54 | |||
| 0228ac8393 | |||
|
|
a99f381f7f | ||
|
|
7c0af24bf5 | ||
|
|
d3aa45cfb9 | ||
|
|
f5461deb81 | ||
|
|
c19068128f | ||
|
|
1367daf1b7 | ||
|
|
5fc6e74cd6 | ||
|
|
5d7227c009 | ||
| 3a9c670172 | |||
|
|
2768faed53 | ||
|
|
85f3d6f09f | ||
|
|
c99707ecb4 | ||
|
|
2b8e648fe6 | ||
|
|
fcf61fd39a | ||
|
|
8e3026f91e | ||
|
|
8e00676faf | ||
|
|
ae293c4c20 | ||
| df4a5f3318 | |||
|
|
1da96c4d1d | ||
|
|
144c7f5db7 | ||
|
|
b3a3ccfea3 | ||
|
|
c3212f0ca1 | ||
|
|
a946999782 | ||
|
|
284c41feb7 | ||
|
|
ddf905cb13 | ||
|
|
d5082d18ef | ||
|
|
af51831062 | ||
|
|
fe4707df84 | ||
|
|
292e7f51e0 | ||
|
|
8936b1c41d | ||
|
|
d45da439bd | ||
|
|
7dc057e30f | ||
|
|
eb2f9d2cea | ||
|
|
fb1895c4eb | ||
|
|
90d3dad8c8 | ||
|
|
de12339c3e | ||
|
|
f07cd2b44a | ||
|
|
c7fbbf6f50 | ||
|
|
de262ee6bd | ||
|
|
0c123e9389 | ||
|
|
d13fbb063d | ||
|
|
5c24eb7d56 | ||
|
|
6c2f19a884 | ||
|
|
4ff632ed2a | ||
|
|
d445c0054f | ||
|
|
748fa7a004 | ||
|
|
c3e710b5cf | ||
|
|
a93a60d125 | ||
|
|
07f24c6168 | ||
|
|
7f5478b098 | ||
|
|
fb7a429ff2 | ||
| 3307793a3d | |||
|
|
0da9f4b7ab | ||
|
|
f45dc3a34c | ||
|
|
1c17f3d878 | ||
|
|
75e4d2b290 | ||
|
|
32fe941735 | ||
|
|
27633b1017 | ||
|
|
c34ca0dea9 | ||
|
|
0574e9c6cb | ||
|
|
b7f09141f1 | ||
|
|
022e59e65c | ||
|
|
a0731331a8 | ||
|
|
4b01222648 | ||
|
|
cae4b26c89 | ||
|
|
427c2332f5 | ||
|
|
6f0aec329b | ||
|
|
4e4d1d068f | ||
|
|
074f4f2ca9 | ||
|
|
c51f9ad901 | ||
|
|
792452c048 | ||
|
|
662eb0bc7f | ||
|
|
94a9bdbb93 | ||
|
|
df96183f42 | ||
|
|
a5b4f6f59f | ||
|
|
6f7497cbe9 | ||
|
|
dbdc2144b7 | ||
|
|
e34106f857 | ||
|
|
c3c07804cd | ||
|
|
0b5ac6bb6e | ||
|
|
ea87eefb9b | ||
|
|
20dc4656dc | ||
|
|
3f0f1612e3 | ||
|
|
e92b6ecfe6 | ||
|
|
37502b6fd8 | ||
|
|
8c1c3c5675 | ||
|
|
b4228e3f35 | ||
|
|
e78f3973be | ||
|
|
29536003a4 | ||
|
|
ffa3767198 | ||
|
|
8702070725 | ||
|
|
793259a194 | ||
|
|
3a63a73244 | ||
|
|
43448cc68d | ||
|
|
f0d272cce5 | ||
|
|
9983455b60 | ||
|
|
0a411c150a | ||
|
|
89b49a1143 | ||
|
|
917454c0b9 | ||
|
|
170b87e7a8 | ||
|
|
68db248a7e | ||
|
|
24614099ed | ||
|
|
8bbfdcbc04 | ||
|
|
126799d2a2 | ||
|
|
c625354dec | ||
|
|
7e08c88a3e | ||
|
|
764d0afb50 | ||
|
|
fe87547406 | ||
|
|
a1fd27722b | ||
|
|
3bd6611cd5 | ||
|
|
f3e1b4580a | ||
|
|
449d8a032e | ||
|
|
b40fa35622 | ||
|
|
ff7e433634 | ||
|
|
1ea9d10bb4 | ||
|
|
04fd2b10fa | ||
|
|
e79417ec5e | ||
|
|
684211c129 | ||
|
|
f94e129cba | ||
|
|
87a140d373 | ||
|
|
f135a6510f | ||
|
|
6960184911 | ||
|
|
7bd270c662 | ||
|
|
f54e83673f | ||
|
|
ee40fdb3c3 | ||
|
|
52ebf7b027 | ||
|
|
85891dc918 | ||
|
|
7348a87a20 | ||
|
|
1dfa3e3f44 | ||
|
|
37ced2e535 | ||
|
|
11876acc62 | ||
|
|
756c0926ec | ||
|
|
9604fc9a8e | ||
|
|
5bcc527889 | ||
|
|
0d616289ed | ||
|
|
a8473b6a04 | ||
|
|
e1352586b7 | ||
|
|
849d5f18eb | ||
|
|
77298f4dab | ||
|
|
b7a2b045fb | ||
|
|
c7072da81d | ||
|
|
4c9b9fb74a | ||
|
|
feb4b516a4 | ||
|
|
d50191c6b8 | ||
|
|
3db3fa4e1f | ||
|
|
ad31961c2b | ||
|
|
ccdaefeb61 | ||
|
|
8c9bcba198 | ||
|
|
a273af0abe | ||
|
|
05a063b001 | ||
|
|
8487195512 | ||
|
|
250c47ccb7 | ||
|
|
e5aeb4f3a7 | ||
|
|
eab8d984e6 | ||
|
|
6b3f19a618 | ||
|
|
d5f8871064 | ||
|
|
77e899957c | ||
|
|
dd3515305c | ||
|
|
73c3ec4820 | ||
|
|
fde5160d56 | ||
|
|
13923705e5 | ||
|
|
d3afc95261 | ||
|
|
326d7e474c | ||
|
|
537b5d9725 | ||
|
|
697c1b5b43 | ||
|
|
2b6057161e | ||
|
|
8e2a6444bd | ||
|
|
b04df40b7d | ||
|
|
3e17d94904 | ||
|
|
29123054cc | ||
|
|
530c038985 | ||
|
|
2e9d2f0491 | ||
|
|
69ab9b4f15 | ||
|
|
1c8b940925 | ||
|
|
0e05a4ea18 | ||
|
|
9e4285b4c5 | ||
|
|
e89d2af8ae | ||
|
|
8e7d663060 | ||
|
|
179b33387e | ||
|
|
2c9a7c443f | ||
|
|
0de964e68c | ||
|
|
97046af931 | ||
|
|
ec24e776e7 | ||
|
|
92d4ef05fa | ||
|
|
fb27c50c75 | ||
|
|
0141bf039a | ||
|
|
54f9bb7f21 | ||
|
|
2c4e5eb5cc | ||
|
|
cd42f45a1f | ||
|
|
ec38895765 | ||
|
|
fc30579bbc | ||
|
|
3e6223e4e5 | ||
|
|
27c71124ff | ||
|
|
3a8a8a36f5 | ||
|
|
ed6de5e806 | ||
|
|
bf8a5befa3 | ||
|
|
a3fe641f6b | ||
|
|
f4f99c25db | ||
|
|
640d80e334 | ||
|
|
4f196b516f | ||
|
|
87f07bf95c | ||
|
|
1bfd7891b5 | ||
|
|
472eb0ee52 | ||
|
|
7638c67da6 | ||
|
|
7a0c143b5b | ||
|
|
fff8e11524 | ||
|
|
af24208cf3 | ||
|
|
85bccea2dd | ||
|
|
f7c8cac6ec | ||
|
|
97a5ac5bb0 | ||
|
|
1d57076010 | ||
|
|
d298ac872c | ||
|
|
c0581e781c | ||
|
|
6befd6341a | ||
|
|
504ef8dd68 | ||
|
|
127500d890 | ||
|
|
ab94173df7 | ||
|
|
e50697aae1 | ||
|
|
c71389fc0b | ||
|
|
732129ba34 | ||
|
|
77dfc895ed | ||
|
|
0c6f3123f8 | ||
|
|
e872d30b3a | ||
|
|
48fa4413c8 | ||
|
|
94e693db77 | ||
|
|
025d328d79 | ||
|
|
d086a5a152 | ||
|
|
5f2337d949 | ||
|
|
5e163181a8 | ||
|
|
0913cbb813 | ||
|
|
6aed64ce4e | ||
|
|
3c95e58fb2 | ||
|
|
64d0d4ed41 | ||
|
|
7fb659c511 | ||
|
|
d4ec91940f | ||
|
|
77f63ebb44 | ||
|
|
051b062e29 | ||
|
|
ec9083b556 | ||
|
|
156080a3b7 | ||
|
|
5b6effd43e | ||
|
|
97e8d41c39 | ||
|
|
d64ef447d2 | ||
|
|
461b2a62b0 | ||
|
|
f8988651b5 | ||
|
|
ffe865e29e | ||
|
|
cd5250c09f | ||
|
|
8f01bf6027 | ||
|
|
81eb11f5c5 | ||
|
|
80cadfa52c | ||
|
|
9974d70d99 | ||
|
|
6e3e4662f4 | ||
|
|
e2fb2e5565 | ||
|
|
6f8be226b0 | ||
|
|
2606f1e587 | ||
|
|
4d1fd7ea76 | ||
|
|
e61fbe6c69 | ||
|
|
0c287577ca | ||
|
|
a4aa562db9 | ||
|
|
33c5ff3a52 | ||
|
|
7c8e43bd35 | ||
|
|
bc0014dbe6 | ||
|
|
e41eca33bc | ||
|
|
e39b965459 | ||
|
|
f2aa3d7347 | ||
|
|
76c126284f | ||
|
|
44ee917d88 | ||
|
|
669019c051 | ||
|
|
4685c12570 | ||
|
|
d815529510 | ||
|
|
1da3620feb | ||
|
|
44d529c60f | ||
|
|
cdbcebd945 | ||
|
|
5bc2bf9397 | ||
|
|
d41419a579 | ||
|
|
a7e15e509e | ||
|
|
6518a378ac | ||
|
|
ebb73182f7 |
264
.github/workflows/build-app.yml
vendored
Normal file
@@ -0,0 +1,264 @@
|
||||
# AUTO_MAA:A MAA Multi Account Management and Automation Tool
|
||||
# Copyright © 2024-2025 DLmaster361
|
||||
|
||||
# This file is part of AUTO_MAA.
|
||||
|
||||
# AUTO_MAA is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License,
|
||||
# or (at your option) any later version.
|
||||
|
||||
# AUTO_MAA is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
|
||||
# the GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# Contact: DLmaster_361@163.com
|
||||
|
||||
name: Build AUTO_MAA
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
actions: write
|
||||
|
||||
jobs:
|
||||
|
||||
pre_check:
|
||||
name: Pre Checks
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Repo Check
|
||||
id: repo_check
|
||||
run: |
|
||||
if [[ "$GITHUB_REPOSITORY" != "DLmaster361/AUTO_MAA" ]]; then
|
||||
echo "When forking this repository to make your own builds, you have to adjust this check."
|
||||
exit 1
|
||||
fi
|
||||
exit 0
|
||||
|
||||
build_AUTO_MAA:
|
||||
runs-on: windows-latest
|
||||
needs: pre_check
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.12'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install flake8 pytest
|
||||
pip install -r requirements.txt
|
||||
|
||||
- name: Lint with flake8
|
||||
run: |
|
||||
# stop the build if there are Python syntax errors or undefined names
|
||||
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
|
||||
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
|
||||
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
|
||||
|
||||
- name: Get version
|
||||
id: get_version
|
||||
run: |
|
||||
$version = (Get-Content resources/version.json | ConvertFrom-Json).main_version
|
||||
echo "main_version=$version" >> $env:GITHUB_OUTPUT
|
||||
|
||||
- name: Nuitka build main program
|
||||
uses: Nuitka/Nuitka-Action@main
|
||||
with:
|
||||
script-name: main.py
|
||||
mode: app
|
||||
enable-plugins: pyside6
|
||||
onefile-tempdir-spec: "{TEMP}/AUTO_MAA"
|
||||
windows-console-mode: attach
|
||||
windows-icon-from-ico: resources/icons/AUTO_MAA.ico
|
||||
company-name: AUTO_MAA Team
|
||||
product-name: AUTO_MAA
|
||||
file-version: ${{ steps.get_version.outputs.main_version }}
|
||||
product-version: ${{ steps.get_version.outputs.main_version }}
|
||||
file-description: AUTO_MAA Component
|
||||
copyright: Copyright © 2024-2025 DLmaster361
|
||||
assume-yes-for-downloads: true
|
||||
output-file: AUTO_MAA
|
||||
output-dir: AUTO_MAA
|
||||
|
||||
- name: Upload unsigned main program
|
||||
id: upload-unsigned-main-program
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: AUTO_MAA
|
||||
path: AUTO_MAA/AUTO_MAA.exe
|
||||
|
||||
- name: Sign main program
|
||||
id: sign_main_program
|
||||
uses: signpath/github-action-submit-signing-request@v1.2
|
||||
with:
|
||||
api-token: '${{ secrets.SIGNPATH_API_TOKEN }}'
|
||||
organization-id: '787a1d5f-6177-4f30-9559-d2646473584a'
|
||||
project-slug: 'AUTO_MAA_'
|
||||
signing-policy-slug: 'test-signing'
|
||||
artifact-configuration-slug: "AUTO_MAA"
|
||||
github-artifact-id: '${{ steps.upload-unsigned-main-program.outputs.artifact-id }}'
|
||||
wait-for-completion: true
|
||||
output-artifact-directory: 'AUTO_MAA'
|
||||
|
||||
- name: Add other resources
|
||||
shell: pwsh
|
||||
run: |
|
||||
$root = "${{ github.workspace }}"
|
||||
$ver = "${{ steps.get_version.outputs.main_version }}"
|
||||
Copy-Item "$root/app" "$root/AUTO_MAA/app" -Recurse
|
||||
Copy-Item "$root/resources" "$root/AUTO_MAA/resources" -Recurse
|
||||
Copy-Item "$root/main.py" "$root/AUTO_MAA/"
|
||||
Copy-Item "$root/requirements.txt" "$root/AUTO_MAA/"
|
||||
Copy-Item "$root/README.md" "$root/AUTO_MAA/"
|
||||
Copy-Item "$root/LICENSE" "$root/AUTO_MAA/"
|
||||
|
||||
- name: Create Inno Setup script
|
||||
shell: pwsh
|
||||
run: |
|
||||
$root = "${{ github.workspace }}"
|
||||
$ver = "${{ steps.get_version.outputs.main_version }}"
|
||||
$iss = Get-Content "$root/app/utils/AUTO_MAA.iss" -Raw
|
||||
$iss = $iss -replace '#define MyAppVersion ""', "#define MyAppVersion `"$ver`""
|
||||
$iss = $iss -replace '#define MyAppPath ""', "#define MyAppPath `"$root/AUTO_MAA`""
|
||||
$iss = $iss -replace '#define OutputDir ""', "#define OutputDir `"$root`""
|
||||
Set-Content -Path "$root/AUTO_MAA.iss" -Value $iss
|
||||
|
||||
- name: Build setup program
|
||||
uses: Minionguyjpro/Inno-Setup-Action@v1.2.5
|
||||
with:
|
||||
path: AUTO_MAA.iss
|
||||
|
||||
- name: Upload unsigned setup program
|
||||
id: upload-unsigned-setup-program
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: AUTO_MAA-Setup
|
||||
path: AUTO_MAA-Setup.exe
|
||||
|
||||
- name: Sign setup program
|
||||
id: sign_setup_program
|
||||
uses: signpath/github-action-submit-signing-request@v1.2
|
||||
with:
|
||||
api-token: '${{ secrets.SIGNPATH_API_TOKEN }}'
|
||||
organization-id: '787a1d5f-6177-4f30-9559-d2646473584a'
|
||||
project-slug: 'AUTO_MAA_'
|
||||
signing-policy-slug: 'test-signing'
|
||||
artifact-configuration-slug: "AUTO_MAA-Setup"
|
||||
github-artifact-id: '${{ steps.upload-unsigned-setup-program.outputs.artifact-id }}'
|
||||
wait-for-completion: true
|
||||
output-artifact-directory: 'AUTO_MAA_Setup'
|
||||
|
||||
- name: Compress setup exe
|
||||
shell: pwsh
|
||||
run: Compress-Archive -Path AUTO_MAA_Setup/* -DestinationPath AUTO_MAA_${{ steps.get_version.outputs.main_version }}.zip
|
||||
|
||||
- name: Generate version info
|
||||
shell: python
|
||||
run: |
|
||||
import json
|
||||
from pathlib import Path
|
||||
def version_text(version_numb):
|
||||
while len(version_numb) < 4:
|
||||
version_numb.append(0)
|
||||
if version_numb[3] == 0:
|
||||
return f"v{'.'.join(str(_) for _ in version_numb[0:3])}"
|
||||
else:
|
||||
return f"v{'.'.join(str(_) for _ in version_numb[0:3])}-beta.{version_numb[3]}"
|
||||
def version_info_markdown(info):
|
||||
version_info = ""
|
||||
for key, value in info.items():
|
||||
version_info += f"## {key}\n"
|
||||
for v in value:
|
||||
version_info += f"- {v}\n"
|
||||
return version_info
|
||||
root_path = Path(".")
|
||||
version = json.loads((root_path / "resources/version.json").read_text(encoding="utf-8"))
|
||||
main_version_numb = list(map(int, version["main_version"].split(".")))
|
||||
all_version_info = {}
|
||||
for v_i in version["version_info"].values():
|
||||
for key, value in v_i.items():
|
||||
if key in all_version_info:
|
||||
all_version_info[key] += value.copy()
|
||||
else:
|
||||
all_version_info[key] = value.copy()
|
||||
(root_path / "version_info.txt").write_text(
|
||||
f"{version_text(main_version_numb)}\n\n<!--{json.dumps(version['version_info'], ensure_ascii=False)}-->\n{version_info_markdown(all_version_info)}",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: AUTO_MAA_${{ steps.get_version.outputs.main_version }}
|
||||
path: AUTO_MAA_${{ steps.get_version.outputs.main_version }}.zip
|
||||
|
||||
- name: Upload Version_Info Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: version_info
|
||||
path: version_info.txt
|
||||
|
||||
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: Create release
|
||||
id: create_release
|
||||
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="$NOTES_MAIN
|
||||
|
||||
[已有 Mirror酱 CDK ?前往 Mirror酱 高速下载](https://mirrorchyan.com/zh/projects?rid=AUTO_MAA&source=auto_maa-release)
|
||||
|
||||
\`\`\`本release通过GitHub Actions自动构建\`\`\`"
|
||||
if [ "${{ github.ref_name }}" == "main" ]; then
|
||||
PRERELEASE_FLAG=""
|
||||
else
|
||||
PRERELEASE_FLAG="--prerelease"
|
||||
fi
|
||||
gh release create "$TAGNAME" --target "main" --title "$NAME" --notes "$NOTES" $PRERELEASE_FLAG artifacts/*
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.WORKFLOW_TOKEN }}
|
||||
|
||||
- name: Trigger MirrorChyanUploading
|
||||
run: |
|
||||
gh workflow run --repo $GITHUB_REPOSITORY mirrorchyan
|
||||
gh workflow run --repo $GITHUB_REPOSITORY mirrorchyan_release_note
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
21
.github/workflows/mirrorchyan.yml
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
name: mirrorchyan
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
mirrorchyan:
|
||||
runs-on: macos-latest
|
||||
|
||||
steps:
|
||||
- id: uploading
|
||||
uses: MirrorChyan/uploading-action@v1
|
||||
with:
|
||||
filetype: latest-release
|
||||
filename: "AUTO_MAA*.zip"
|
||||
mirrorchyan_rid: AUTO_MAA
|
||||
|
||||
owner: DLmaster361
|
||||
repo: AUTO_MAA
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
upload_token: ${{ secrets.MirrorChyanUploadToken }}
|
||||
19
.github/workflows/mirrorchyan_release_note.yml
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
name: mirrorchyan_release_note
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
release:
|
||||
types: [edited]
|
||||
|
||||
jobs:
|
||||
mirrorchyan:
|
||||
runs-on: macos-latest
|
||||
|
||||
steps:
|
||||
- id: uploading
|
||||
uses: MirrorChyan/release-note-action@v1
|
||||
with:
|
||||
mirrorchyan_rid: AUTO_MAA
|
||||
|
||||
upload_token: ${{ secrets.MirrorChyanUploadToken }}
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
8
.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
__pycache__/
|
||||
config/
|
||||
data/
|
||||
debug/
|
||||
history/
|
||||
resources/notice.json
|
||||
resources/theme_image.json
|
||||
resources/images/Home/BannerTheme.jpg
|
||||
BIN
AUTO_MAA.exe
1067
AUTO_MAA.py
168
README.md
@@ -1,120 +1,86 @@
|
||||
# AUTO_MAA
|
||||
MAA多账号管理与自动化软件
|
||||
<h1 align="center">AUTO_MAA</h1>
|
||||
<p align="center">
|
||||
MAA多账号管理与自动化软件<br><br>
|
||||
<img alt="软件图标" src="https://github.com/DLmaster361/AUTO_MAA/blob/main/resources/images/AUTO_MAA.png">
|
||||
</p>
|
||||
|
||||

|
||||
---
|
||||
|
||||
----------------------------------------------------------------------------------------------
|
||||
<p align="center">
|
||||
<a href="https://github.com/DLmaster361/AUTO_MAA/stargazers"><img alt="GitHub Stars" src="https://img.shields.io/github/stars/DLmaster361/AUTO_MAA?style=flat-square"></a>
|
||||
<a href="https://github.com/DLmaster361/AUTO_MAA/network"><img alt="GitHub Forks" src="https://img.shields.io/github/forks/DLmaster361/AUTO_MAA?style=flat-square"></a>
|
||||
<a href="https://github.com/DLmaster361/AUTO_MAA/releases/latest"><img alt="GitHub Downloads" src="https://img.shields.io/github/downloads/DLmaster361/AUTO_MAA/total?style=flat-square"></a>
|
||||
<a href="https://github.com/DLmaster361/AUTO_MAA/issues"><img alt="GitHub Issues" src="https://img.shields.io/github/issues/DLmaster361/AUTO_MAA?style=flat-square"></a>
|
||||
<a href="https://github.com/DLmaster361/AUTO_MAA/graphs/contributors"><img alt="GitHub Contributors" src="https://img.shields.io/github/contributors/DLmaster361/AUTO_MAA?style=flat-square"></a>
|
||||
<a href="https://github.com/DLmaster361/AUTO_MAA/blob/main/LICENSE"><img alt="GitHub License" src="https://img.shields.io/github/license/DLmaster361/AUTO_MAA?style=flat-square"></a>
|
||||
<a href="https://deepwiki.com/DLmaster361/AUTO_MAA"><img alt="DeepWiki" src="https://deepwiki.com/badge.svg"></a>
|
||||
<a href="https://mirrorchyan.com/zh/projects?rid=AUTO_MAA&source=auto_maa-readme"><img alt="mirrorc" src="https://img.shields.io/badge/Mirror%E9%85%B1-%239af3f6?logo=countingworkspro&logoColor=4f46e5"></a>
|
||||
</p>
|
||||
|
||||
## 软件介绍
|
||||
|
||||
### 性质
|
||||
|
||||
本软件是明日方舟第三方软件`MAA`的第三方工具,即第3<sup>3</sup>方软件。旨在优化MAA多账号功能体验,并通过一些方法解决MAA项目未能解决的部分问题,提高代理的稳定性。
|
||||
|
||||
- **集中管理**:一站式管理多个MAA脚本与多个用户配置,和凌乱的散装脚本窗口说再见!
|
||||
- **无人值守**:自动处理MAA相关报错,再也不用为代理任务卡死时自己不在电脑旁烦恼啦!
|
||||
- **配置灵活**:通过调度队列与脚本的组合,自由实现您能想到的所有调度需求!
|
||||
- **信息统计**:自动统计用户的公招与关卡掉落物,看看这个月您的收益是多少!
|
||||
|
||||
### 原理
|
||||
|
||||
本软件可以存储多个明日方舟账号数据,并通过以下流程实现代理功能:
|
||||
|
||||
1. **配置:** 根据对应用户的配置信息,生成配置文件并将其导入MAA。
|
||||
2. **监测:** 在MAA开始代理后,持续读取MAA的日志以判断其运行状态。当软件认定MAA出现异常时,通过重启MAA使之仍能继续完成任务。
|
||||
3. **循环:** 重复上述步骤,使MAA依次完成各个用户的自动代理任务。
|
||||
|
||||
### 优势
|
||||
|
||||
- **高效稳定**:通过日志监测、异常处理等机制,保障代理任务顺利完成。
|
||||
- **简洁易用**:无需手动修改配置文件,实现自动化调度与多开管理。
|
||||
- **兼容扩展**:支持 MAA 几乎所有的配置选项,满足不同用户需求。
|
||||
|
||||
## 重要声明
|
||||
本软件是一个外部工具,旨在优化MAA多账号功能体验。该软件包可以存储明日方舟多账号数据,并通过修改MAA配置文件、读取MAA日志等行为自动完成多账号代理。本开发团队承诺,不会修改明日方舟游戏本体与相关配置文件。
|
||||
|
||||
本项目使用GPL开源,相关细则如下:
|
||||
本开发团队承诺,不会修改明日方舟游戏本体与相关配置文件。本项目使用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参与项目。避免私信或私发邮件(安全性漏洞或敏感问题除外),以帮助更多用户
|
||||
- **使用:** AUTO_MAA使用者可以按自己的意愿自由使用本软件。依据GPL,对于由此可能产生的损失,AUTO_MAA项目组不负任何责任。
|
||||
- **分发:** AUTO_MAA允许任何人自由分发本软件,包括进行商业活动牟利。若为直接分发本软件,必须遵循GPL向接收者提供本软件项目地址、完整的软件源码与GPL协议原文(件);若为修改软件后进行分发,必须遵循GPL向接收者提供本软件项目地址、修改前的完整软件源码副本与GPL协议原文(件),违反者可能会被追究法律责任。
|
||||
- **传播:** AUTO_MAA原则上允许传播者自由传播本软件,但无论在何种传播过程中,不得删除项目作者与开发者所留版权声明,不得隐瞒项目作者与相关开发者的存在。由于软件性质,项目组不希望发现任何人在明日方舟官方媒体(包括官方媒体账号与森空岛社区等)或明日方舟游戏相关内容(包括同好群、线下活动与游戏内容讨论等)下提及AUTO_MAA或MAA,希望各位理解。
|
||||
- **衍生:** AUTO_MAA允许任何人对软件本体或软件部分代码进行二次开发或利用。但依据GPL,相关成果再次分发时也必须使用GPL或兼容的协议开源。
|
||||
- **贡献:** 不论是直接参与软件的维护编写,或是撰写文档、测试、反馈BUG、给出建议、参与讨论,都为AUTO_MAA项目的发展完善做出了不可忽视的贡献。项目组提倡各位贡献者遵照GitHub开源社区惯例,发布Issues参与项目。避免私信或私发邮件(安全性漏洞或敏感问题除外),以帮助更多用户。
|
||||
|
||||
以上细则是本项目对GPL的相关补充与强调。未提及的以GPL为准,发生冲突的以本细则为准。如有不清楚的部分,请发Issues询问。若发生纠纷,相关内容也没有在Issues上提及的,项目组拥有最终解释权
|
||||
以上细则是本项目对GPL的相关补充与强调。未提及的以GPL为准,发生冲突的以本细则为准。如有不清楚的部分,请发Issues询问。若发生纠纷,相关内容也没有在Issues上提及的,项目组拥有最终解释权。
|
||||
|
||||
**注意**
|
||||
- 由于本软件有修改其它目录JSON文件等行为,使用前请将AUTO_MAA添加入Windows Defender信任区以及防病毒软件的信任区或开发者目录,避免被误杀
|
||||
|
||||
- 由于本软件有修改其它目录JSON文件等行为,使用前请将AUTO_MAA添加入Windows Defender信任区以及防病毒软件的信任区或开发者目录,避免被误杀。
|
||||
|
||||
---
|
||||
|
||||
# 使用方法
|
||||
|
||||
## 安装与配置MAA
|
||||
访问AUTO_MAA官方文档站以获取使用指南和项目相关信息
|
||||
|
||||
```
|
||||
本软件是MAA的外部工具,需要安装配置MAA后才能使用。
|
||||
```
|
||||
|
||||
### MAA安装
|
||||
|
||||
- 什么是MAA? [官网](https://maa.plus/)/[GitHub](https://github.com/CHNZYX/Auto_Simulated_Universe/archive/refs/heads/main.zip)
|
||||
|
||||
- MAA下载地址 [GitHub下载](https://github.com/MaaAssistantArknights/MaaAssistantArknights/releases)
|
||||
|
||||
### MAA配置
|
||||
|
||||
1. 完成MAA的adb配置等基本配置
|
||||
|
||||
2. 确保当前配置名为“Default”,取消所有“定时执行”
|
||||
|
||||

|
||||
|
||||
3. 取消勾选“开机自启动MAA”,勾选“启动MAA后直接运行”和“启动MAA后自动开启模拟器”。配置自己模拟器所在的位置并根据实际情况填写“等待模拟器启动时间”(建议预留10s以防意外)。如果是多开用户,需要填写“附加命令”,具体填写值参见多开模拟器对应快捷方式路径(如“-v 1”)。
|
||||
|
||||

|
||||
|
||||
4. 勾选“定时检查更新”、“自动下载更新包”和“自动安装更新包”
|
||||
|
||||

|
||||
|
||||
## 下载AUTO_MAA [](https://github.com/DLmaster361/AUTO_MAA/releases)
|
||||
|
||||
- GitHub下载地址 [GitHub下载](https://github.com/DLmaster361/AUTO_MAA/releases)
|
||||
|
||||
## 配置AUTO_MAA
|
||||
|
||||
|
||||
### 启动AUTO_MAA
|
||||
|
||||
- 双击`AUTO_MAA.exe`以启动软件
|
||||
|
||||
```
|
||||
注意:
|
||||
|
||||
首次启动时会要求设置管理密钥。
|
||||
|
||||
管理密钥是解密用户密码的唯一凭证,与数据库绑定。
|
||||
密钥丢失或data/key/目录下任一文件损坏都将导致解密无法正常进行。
|
||||
|
||||
本项目采用自主开发的混合加密模式,项目组也无法找回您的管理密钥或修复data/key/目录下的文件。
|
||||
如果不幸的事发生,建议您删除data/key/目录与data/data.db文件后重新录入信息。
|
||||
```
|
||||
|
||||
### 配置信息
|
||||
|
||||
- 本项目已初步完成GUI开发,您可以在用户管理页与设置页配置信息。
|
||||
|
||||
```
|
||||
特别的:
|
||||
|
||||
你可以自定义关卡号替换方案。
|
||||
程序会读取data/gameid.txt中的数据,依据此进行关卡号的替换,便于常用关卡的使用。
|
||||
gameid.txt在初始已经存储了一些常用资源本的替代方案。
|
||||
```
|
||||
|
||||

|
||||
|
||||
## 运行代理任务
|
||||
|
||||
### 直接运行
|
||||
|
||||
- 在执行页单击`立即执行`直接运行
|
||||
|
||||
### 定时运行
|
||||
|
||||
- 在执行页的`定时执行`栏设置时间
|
||||
|
||||
- 保持软件打开,软件会在设定的时间自动运行
|
||||
- [AUTO_MAA官方文档站](https://clozya.github.io/AUTOMAA_docs)
|
||||
|
||||
---
|
||||
|
||||
# 关于
|
||||
|
||||
欢迎加入AUTO_MAA项目组,欢迎反馈bug
|
||||
## 项目开发情况
|
||||
|
||||
QQ群:957750551
|
||||
可在[《AUTO_MAA开发者协作文档》](https://docs.qq.com/aio/DQ3Z5eHNxdmxFQmZX)的`开发任务`页面中查看开发进度。
|
||||
|
||||
----------------------------------------------------------------------------------------------
|
||||
## 特别鸣谢
|
||||
|
||||
作者即将高考,如果喜欢本项目,请祝福他
|
||||
- 下载服务器:由[AoXuan (@ClozyA)](https://github.com/ClozyA) 个人为项目赞助。
|
||||
|
||||
- EXE签名: Free code signing provided by [SignPath.io](https://signpath.io/), certificate by [SignPath Foundation](https://signpath.org/)
|
||||
|
||||
----------------------------------------------------------------------------------------------
|
||||
## 贡献者
|
||||
|
||||
感谢以下贡献者对本项目做出的贡献
|
||||
@@ -129,4 +95,16 @@ QQ群:957750551
|
||||
|
||||
## Star History
|
||||
|
||||
[](https://star-history.com/#DLmaster361/AUTO_MAA&Date)
|
||||
[](https://star-history.com/#DLmaster361/AUTO_MAA&Date)
|
||||
|
||||
## 交流与赞助
|
||||
|
||||
欢迎加入AUTO_MAA项目组,欢迎反馈bug
|
||||
|
||||
- QQ交流群:[957750551](https://qm.qq.com/q/bd9fISNoME)
|
||||
|
||||
---
|
||||
|
||||
如果喜欢这个项目的话,给作者来杯咖啡吧!
|
||||
|
||||

|
||||
|
||||
49
app/__init__.py
Normal file
@@ -0,0 +1,49 @@
|
||||
# AUTO_MAA:A MAA Multi Account Management and Automation Tool
|
||||
# Copyright © 2024-2025 DLmaster361
|
||||
|
||||
# This file is part of AUTO_MAA.
|
||||
|
||||
# AUTO_MAA is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License,
|
||||
# or (at your option) any later version.
|
||||
|
||||
# AUTO_MAA is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
|
||||
# the GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# Contact: DLmaster_361@163.com
|
||||
|
||||
"""
|
||||
AUTO_MAA
|
||||
AUTO_MAA主程序包
|
||||
v4.3
|
||||
作者:DLmaster_361
|
||||
"""
|
||||
|
||||
__version__ = "4.2.0"
|
||||
__author__ = "DLmaster361 <DLmaster_361@163.com>"
|
||||
__license__ = "GPL-3.0 license"
|
||||
|
||||
from .core import QueueConfig, MaaConfig, MaaUserConfig, Task, TaskManager, MainTimer
|
||||
from .models import MaaManager
|
||||
from .services import Notify, Crypto, System
|
||||
from .ui import AUTO_MAA
|
||||
|
||||
__all__ = [
|
||||
"QueueConfig",
|
||||
"MaaConfig",
|
||||
"MaaUserConfig",
|
||||
"Task",
|
||||
"TaskManager",
|
||||
"MainTimer",
|
||||
"MaaManager",
|
||||
"Notify",
|
||||
"Crypto",
|
||||
"System",
|
||||
"AUTO_MAA",
|
||||
]
|
||||
51
app/core/__init__.py
Normal file
@@ -0,0 +1,51 @@
|
||||
# AUTO_MAA:A MAA Multi Account Management and Automation Tool
|
||||
# Copyright © 2024-2025 DLmaster361
|
||||
|
||||
# This file is part of AUTO_MAA.
|
||||
|
||||
# AUTO_MAA is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License,
|
||||
# or (at your option) any later version.
|
||||
|
||||
# AUTO_MAA is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
|
||||
# the GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# Contact: DLmaster_361@163.com
|
||||
|
||||
"""
|
||||
AUTO_MAA
|
||||
AUTO_MAA核心组件包
|
||||
v4.3
|
||||
作者:DLmaster_361
|
||||
"""
|
||||
|
||||
__version__ = "4.2.0"
|
||||
__author__ = "DLmaster361 <DLmaster_361@163.com>"
|
||||
__license__ = "GPL-3.0 license"
|
||||
|
||||
from .config import QueueConfig, MaaConfig, MaaUserConfig, MaaPlanConfig, Config
|
||||
from .main_info_bar import MainInfoBar
|
||||
from .network import Network
|
||||
from .sound_player import SoundPlayer
|
||||
from .task_manager import Task, TaskManager
|
||||
from .timer import MainTimer
|
||||
|
||||
__all__ = [
|
||||
"Config",
|
||||
"QueueConfig",
|
||||
"MaaConfig",
|
||||
"MaaUserConfig",
|
||||
"MaaPlanConfig",
|
||||
"MainInfoBar",
|
||||
"Network",
|
||||
"SoundPlayer",
|
||||
"Task",
|
||||
"TaskManager",
|
||||
"MainTimer",
|
||||
]
|
||||
1625
app/core/config.py
Normal file
89
app/core/main_info_bar.py
Normal file
@@ -0,0 +1,89 @@
|
||||
# AUTO_MAA:A MAA Multi Account Management and Automation Tool
|
||||
# Copyright © 2024-2025 DLmaster361
|
||||
|
||||
# This file is part of AUTO_MAA.
|
||||
|
||||
# AUTO_MAA is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License,
|
||||
# or (at your option) any later version.
|
||||
|
||||
# AUTO_MAA is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
|
||||
# the GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# Contact: DLmaster_361@163.com
|
||||
|
||||
"""
|
||||
AUTO_MAA
|
||||
AUTO_MAA信息通知栏
|
||||
v4.3
|
||||
作者:DLmaster_361
|
||||
"""
|
||||
|
||||
from loguru import logger
|
||||
from PySide6.QtCore import Qt
|
||||
from qfluentwidgets import InfoBar, InfoBarPosition
|
||||
|
||||
from .config import Config
|
||||
from .sound_player import SoundPlayer
|
||||
|
||||
|
||||
class _MainInfoBar:
|
||||
"""信息通知栏"""
|
||||
|
||||
# 模式到 InfoBar 方法的映射
|
||||
mode_mapping = {
|
||||
"success": InfoBar.success,
|
||||
"warning": InfoBar.warning,
|
||||
"error": InfoBar.error,
|
||||
"info": InfoBar.info,
|
||||
}
|
||||
|
||||
def push_info_bar(
|
||||
self, mode: str, title: str, content: str, time: int, if_force: bool = False
|
||||
):
|
||||
"""推送到信息通知栏"""
|
||||
if Config.main_window is None:
|
||||
logger.error("信息通知栏未设置父窗口")
|
||||
return None
|
||||
|
||||
# 根据 mode 获取对应的 InfoBar 方法
|
||||
info_bar_method = self.mode_mapping.get(mode)
|
||||
|
||||
if not info_bar_method:
|
||||
logger.error(f"未知的通知栏模式: {mode}")
|
||||
return None
|
||||
|
||||
if Config.main_window.isVisible():
|
||||
info_bar_method(
|
||||
title=title,
|
||||
content=content,
|
||||
orient=Qt.Horizontal,
|
||||
isClosable=True,
|
||||
position=InfoBarPosition.TOP_RIGHT,
|
||||
duration=time,
|
||||
parent=Config.main_window,
|
||||
)
|
||||
elif if_force:
|
||||
# 如果主窗口不可见且强制推送,则录入消息队列等待窗口显示后推送
|
||||
info_bar_item = {
|
||||
"mode": mode,
|
||||
"title": title,
|
||||
"content": content,
|
||||
"time": time,
|
||||
}
|
||||
if info_bar_item not in Config.info_bar_list:
|
||||
Config.info_bar_list.append(info_bar_item)
|
||||
|
||||
if mode == "warning":
|
||||
SoundPlayer.play("发生异常")
|
||||
if mode == "error":
|
||||
SoundPlayer.play("发生错误")
|
||||
|
||||
|
||||
MainInfoBar = _MainInfoBar()
|
||||
151
app/core/network.py
Normal file
@@ -0,0 +1,151 @@
|
||||
# AUTO_MAA:A MAA Multi Account Management and Automation Tool
|
||||
# Copyright © 2024-2025 DLmaster361
|
||||
|
||||
# This file is part of AUTO_MAA.
|
||||
|
||||
# AUTO_MAA is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License,
|
||||
# or (at your option) any later version.
|
||||
|
||||
# AUTO_MAA is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
|
||||
# the GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# Contact: DLmaster_361@163.com
|
||||
|
||||
"""
|
||||
AUTO_MAA
|
||||
AUTO_MAA网络请求线程
|
||||
v4.3
|
||||
作者:DLmaster_361
|
||||
"""
|
||||
|
||||
from loguru import logger
|
||||
from PySide6.QtCore import QObject, QThread, QEventLoop
|
||||
import re
|
||||
import time
|
||||
import requests
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class NetworkThread(QThread):
|
||||
"""网络请求线程类"""
|
||||
|
||||
max_retries = 3
|
||||
timeout = 10
|
||||
backoff_factor = 0.1
|
||||
|
||||
def __init__(self, mode: str, url: str, path: Path = None) -> None:
|
||||
super().__init__()
|
||||
|
||||
self.setObjectName(
|
||||
f"NetworkThread-{mode}-{re.sub(r'(&cdk=)[^&]+(&)', r'\1******\2', url)}"
|
||||
)
|
||||
|
||||
self.mode = mode
|
||||
self.url = url
|
||||
self.path = path
|
||||
|
||||
self.status_code = None
|
||||
self.response_json = None
|
||||
self.error_message = None
|
||||
|
||||
self.loop = QEventLoop()
|
||||
|
||||
@logger.catch
|
||||
def run(self) -> None:
|
||||
"""运行网络请求线程"""
|
||||
|
||||
if self.mode == "get":
|
||||
self.get_json(self.url)
|
||||
elif self.mode == "get_file":
|
||||
self.get_file(self.url, self.path)
|
||||
|
||||
def get_json(self, url: str) -> None:
|
||||
"""通过get方法获取json数据"""
|
||||
|
||||
response = None
|
||||
|
||||
for _ in range(self.max_retries):
|
||||
try:
|
||||
response = requests.get(url, timeout=self.timeout)
|
||||
self.status_code = response.status_code
|
||||
self.response_json = response.json()
|
||||
self.error_message = None
|
||||
break
|
||||
except Exception as e:
|
||||
self.status_code = response.status_code if response else None
|
||||
self.response_json = None
|
||||
self.error_message = str(e)
|
||||
time.sleep(self.backoff_factor)
|
||||
|
||||
self.loop.quit()
|
||||
|
||||
def get_file(self, url: str, path: Path) -> None:
|
||||
"""通过get方法下载文件"""
|
||||
|
||||
response = None
|
||||
|
||||
try:
|
||||
response = requests.get(url, timeout=10)
|
||||
if response.status_code == 200:
|
||||
with open(path, "wb") as file:
|
||||
file.write(response.content)
|
||||
self.status_code = response.status_code
|
||||
else:
|
||||
self.status_code = response.status_code
|
||||
self.error_message = "下载失败"
|
||||
|
||||
except Exception as e:
|
||||
self.status_code = response.status_code if response else None
|
||||
self.error_message = str(e)
|
||||
|
||||
self.loop.quit()
|
||||
|
||||
|
||||
class _Network(QObject):
|
||||
"""网络请求线程类"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
|
||||
self.task_queue = []
|
||||
|
||||
def add_task(self, mode: str, url: str, path: Path = None) -> NetworkThread:
|
||||
"""添加网络请求任务"""
|
||||
|
||||
network_thread = NetworkThread(mode, url, path)
|
||||
|
||||
self.task_queue.append(network_thread)
|
||||
|
||||
network_thread.start()
|
||||
|
||||
return network_thread
|
||||
|
||||
def get_result(self, network_thread: NetworkThread) -> dict:
|
||||
"""获取网络请求结果"""
|
||||
|
||||
result = {
|
||||
"status_code": network_thread.status_code,
|
||||
"response_json": network_thread.response_json,
|
||||
"error_message": (
|
||||
re.sub(r"(&cdk=)[^&]+(&)", r"\1******\2", network_thread.error_message)
|
||||
if network_thread.error_message
|
||||
else None
|
||||
),
|
||||
}
|
||||
|
||||
network_thread.quit()
|
||||
network_thread.wait()
|
||||
self.task_queue.remove(network_thread)
|
||||
network_thread.deleteLater()
|
||||
|
||||
return result
|
||||
|
||||
|
||||
Network = _Network()
|
||||
69
app/core/sound_player.py
Normal file
@@ -0,0 +1,69 @@
|
||||
# AUTO_MAA:A MAA Multi Account Management and Automation Tool
|
||||
# Copyright © 2024-2025 DLmaster361
|
||||
|
||||
# This file is part of AUTO_MAA.
|
||||
|
||||
# AUTO_MAA is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License,
|
||||
# or (at your option) any later version.
|
||||
|
||||
# AUTO_MAA is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
|
||||
# the GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# Contact: DLmaster_361@163.com
|
||||
|
||||
"""
|
||||
AUTO_MAA
|
||||
AUTO_MAA音效播放器
|
||||
v4.3
|
||||
作者:DLmaster_361
|
||||
"""
|
||||
|
||||
from loguru import logger
|
||||
from PySide6.QtCore import QObject, QUrl
|
||||
from PySide6.QtMultimedia import QSoundEffect
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
from .config import Config
|
||||
|
||||
|
||||
class _SoundPlayer(QObject):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self.sounds_path = Config.app_path / "resources/sounds"
|
||||
|
||||
def play(self, sound_name: str):
|
||||
|
||||
if not Config.get(Config.voice_Enabled):
|
||||
return
|
||||
|
||||
if (self.sounds_path / f"both/{sound_name}.wav").exists():
|
||||
|
||||
self.play_voice(self.sounds_path / f"both/{sound_name}.wav")
|
||||
|
||||
elif (
|
||||
self.sounds_path / Config.get(Config.voice_Type) / f"{sound_name}.wav"
|
||||
).exists():
|
||||
|
||||
self.play_voice(
|
||||
self.sounds_path / Config.get(Config.voice_Type) / f"{sound_name}.wav"
|
||||
)
|
||||
|
||||
def play_voice(self, sound_path: Path):
|
||||
|
||||
effect = QSoundEffect(self)
|
||||
effect.setVolume(1)
|
||||
effect.setSource(QUrl.fromLocalFile(sound_path))
|
||||
effect.play()
|
||||
|
||||
|
||||
SoundPlayer = _SoundPlayer()
|
||||
332
app/core/task_manager.py
Normal file
@@ -0,0 +1,332 @@
|
||||
# AUTO_MAA:A MAA Multi Account Management and Automation Tool
|
||||
# Copyright © 2024-2025 DLmaster361
|
||||
|
||||
# This file is part of AUTO_MAA.
|
||||
|
||||
# AUTO_MAA is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License,
|
||||
# or (at your option) any later version.
|
||||
|
||||
# AUTO_MAA is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
|
||||
# the GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# Contact: DLmaster_361@163.com
|
||||
|
||||
"""
|
||||
AUTO_MAA
|
||||
AUTO_MAA业务调度器
|
||||
v4.3
|
||||
作者:DLmaster_361
|
||||
"""
|
||||
|
||||
from loguru import logger
|
||||
from PySide6.QtCore import QThread, QObject, Signal
|
||||
from qfluentwidgets import MessageBox
|
||||
from datetime import datetime
|
||||
from packaging import version
|
||||
from typing import Dict, Union
|
||||
|
||||
from .config import Config
|
||||
from .main_info_bar import MainInfoBar
|
||||
from .network import Network
|
||||
from .sound_player import SoundPlayer
|
||||
from app.models import MaaManager
|
||||
from app.services import System
|
||||
|
||||
|
||||
class Task(QThread):
|
||||
"""业务线程"""
|
||||
|
||||
check_maa_version = Signal(str)
|
||||
push_info_bar = Signal(str, str, str, int)
|
||||
play_sound = Signal(str)
|
||||
question = Signal(str, str)
|
||||
question_response = Signal(bool)
|
||||
update_user_info = Signal(str, dict)
|
||||
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.setObjectName(f"Task-{mode}-{name}")
|
||||
|
||||
self.mode = mode
|
||||
self.name = name
|
||||
self.info = info
|
||||
|
||||
self.logs = []
|
||||
|
||||
self.question_response.connect(lambda: print("response"))
|
||||
|
||||
@logger.catch
|
||||
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.member_dict[self.name],
|
||||
(None if "全局" in self.mode else self.info["SetMaaInfo"]["Path"]),
|
||||
)
|
||||
self.task.check_maa_version.connect(self.check_maa_version.emit)
|
||||
self.task.push_info_bar.connect(self.push_info_bar.emit)
|
||||
self.task.play_sound.connect(self.play_sound.emit)
|
||||
self.task.accomplish.connect(lambda: self.accomplish.emit([]))
|
||||
|
||||
self.task.run()
|
||||
|
||||
else:
|
||||
|
||||
self.task_list = [
|
||||
[
|
||||
(
|
||||
value
|
||||
if Config.member_dict[value]["Config"].get(
|
||||
Config.member_dict[value]["Config"].MaaSet_Name
|
||||
)
|
||||
== ""
|
||||
else f"{value} - {Config.member_dict[value]["Config"].get(Config.member_dict[value]["Config"].MaaSet_Name)}"
|
||||
),
|
||||
"等待",
|
||||
value,
|
||||
]
|
||||
for _, value in sorted(
|
||||
self.info["Queue"].items(), key=lambda x: int(x[0][7:])
|
||||
)
|
||||
if value != "禁用"
|
||||
]
|
||||
|
||||
self.create_task_list.emit(self.task_list)
|
||||
|
||||
for task in self.task_list:
|
||||
|
||||
if self.isInterruptionRequested():
|
||||
break
|
||||
|
||||
task[1] = "运行"
|
||||
self.update_task_list.emit(self.task_list)
|
||||
|
||||
if task[2] in Config.running_list:
|
||||
|
||||
task[1] = "跳过"
|
||||
self.update_task_list.emit(self.task_list)
|
||||
logger.info(f"跳过任务:{task[0]}")
|
||||
self.push_info_bar.emit("info", "跳过任务", task[0], 3000)
|
||||
continue
|
||||
|
||||
Config.running_list.append(task[2])
|
||||
logger.info(f"任务开始:{task[0]}")
|
||||
self.push_info_bar.emit("info", "任务开始", task[0], 3000)
|
||||
|
||||
if Config.member_dict[task[2]]["Type"] == "Maa":
|
||||
|
||||
self.task = MaaManager(
|
||||
self.mode[0:4],
|
||||
Config.member_dict[task[2]],
|
||||
)
|
||||
|
||||
self.task.check_maa_version.connect(self.check_maa_version.emit)
|
||||
self.task.question.connect(self.question.emit)
|
||||
self.question_response.disconnect()
|
||||
self.question_response.connect(self.task.question_response.emit)
|
||||
self.task.push_info_bar.connect(self.push_info_bar.emit)
|
||||
self.task.play_sound.connect(self.play_sound.emit)
|
||||
self.task.create_user_list.connect(self.create_user_list.emit)
|
||||
self.task.update_user_list.connect(self.update_user_list.emit)
|
||||
self.task.update_log_text.connect(self.update_log_text.emit)
|
||||
self.task.update_user_info.connect(self.update_user_info.emit)
|
||||
self.task.accomplish.connect(
|
||||
lambda log: self.task_accomplish(task[2], log)
|
||||
)
|
||||
|
||||
self.task.run()
|
||||
|
||||
Config.running_list.remove(task[2])
|
||||
|
||||
task[1] = "完成"
|
||||
self.update_task_list.emit(self.task_list)
|
||||
logger.info(f"任务完成:{task[0]}")
|
||||
self.push_info_bar.emit("info", "任务完成", task[0], 3000)
|
||||
|
||||
self.accomplish.emit(self.logs)
|
||||
|
||||
def task_accomplish(self, name: str, log: dict):
|
||||
"""保存保存任务结果"""
|
||||
|
||||
self.logs.append([name, log])
|
||||
self.task.deleteLater()
|
||||
|
||||
|
||||
class _TaskManager(QObject):
|
||||
"""业务调度器"""
|
||||
|
||||
create_gui = Signal(Task)
|
||||
connect_gui = Signal(Task)
|
||||
|
||||
def __init__(self):
|
||||
super(_TaskManager, self).__init__()
|
||||
|
||||
self.task_dict: 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_dict:
|
||||
|
||||
logger.warning(f"任务已存在:{name}")
|
||||
MainInfoBar.push_info_bar("warning", "任务已存在", name, 5000)
|
||||
return None
|
||||
|
||||
logger.info(f"任务开始:{name}")
|
||||
MainInfoBar.push_info_bar("info", "任务开始", name, 3000)
|
||||
SoundPlayer.play("任务开始")
|
||||
|
||||
Config.running_list.append(name)
|
||||
self.task_dict[name] = Task(mode, name, info)
|
||||
self.task_dict[name].check_maa_version.connect(self.check_maa_version)
|
||||
self.task_dict[name].question.connect(
|
||||
lambda title, content: self.push_dialog(name, title, content)
|
||||
)
|
||||
self.task_dict[name].push_info_bar.connect(MainInfoBar.push_info_bar)
|
||||
self.task_dict[name].play_sound.connect(SoundPlayer.play)
|
||||
self.task_dict[name].update_user_info.connect(Config.change_user_info)
|
||||
self.task_dict[name].accomplish.connect(
|
||||
lambda logs: self.remove_task(mode, name, logs)
|
||||
)
|
||||
|
||||
if "新调度台" in mode:
|
||||
self.create_gui.emit(self.task_dict[name])
|
||||
|
||||
elif "主调度台" in mode:
|
||||
self.connect_gui.emit(self.task_dict[name])
|
||||
|
||||
self.task_dict[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_dict:
|
||||
|
||||
self.task_dict[name].task.requestInterruption()
|
||||
self.task_dict[name].requestInterruption()
|
||||
self.task_dict[name].quit()
|
||||
self.task_dict[name].wait()
|
||||
|
||||
elif name in self.task_dict:
|
||||
|
||||
self.task_dict[name].task.requestInterruption()
|
||||
self.task_dict[name].requestInterruption()
|
||||
self.task_dict[name].quit()
|
||||
self.task_dict[name].wait()
|
||||
|
||||
def remove_task(self, mode: str, name: str, logs: list):
|
||||
"""任务结束后的处理"""
|
||||
|
||||
logger.info(f"任务结束:{name}")
|
||||
MainInfoBar.push_info_bar("info", "任务结束", name, 3000)
|
||||
SoundPlayer.play("任务结束")
|
||||
|
||||
self.task_dict[name].deleteLater()
|
||||
self.task_dict.pop(name)
|
||||
Config.running_list.remove(name)
|
||||
|
||||
if "调度队列" in name and "人工排查" not in mode:
|
||||
|
||||
if len(logs) > 0:
|
||||
time = logs[0][1]["Time"]
|
||||
history = ""
|
||||
for log in logs:
|
||||
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": "没有任务被执行",
|
||||
},
|
||||
)
|
||||
|
||||
if (
|
||||
Config.queue_dict[name]["Config"].get(
|
||||
Config.queue_dict[name]["Config"].queueSet_AfterAccomplish
|
||||
)
|
||||
!= "NoAction"
|
||||
and Config.power_sign == "NoAction"
|
||||
):
|
||||
Config.set_power_sign(
|
||||
Config.queue_dict[name]["Config"].get(
|
||||
Config.queue_dict[name]["Config"].queueSet_AfterAccomplish
|
||||
)
|
||||
)
|
||||
|
||||
if Config.args.mode == "cli" and Config.power_sign == "NoAction":
|
||||
Config.set_power_sign("KillSelf")
|
||||
|
||||
def check_maa_version(self, v: str):
|
||||
"""检查MAA版本"""
|
||||
|
||||
network = Network.add_task(
|
||||
mode="get",
|
||||
url="https://mirrorchyan.com/api/resources/MAA/latest?user_agent=AutoMaaGui&os=win&arch=x64&channel=stable",
|
||||
)
|
||||
network.loop.exec()
|
||||
network_result = Network.get_result(network)
|
||||
if network_result["status_code"] == 200:
|
||||
maa_info = network_result["response_json"]
|
||||
else:
|
||||
logger.warning(f"获取MAA版本信息时出错:{network_result['error_message']}")
|
||||
MainInfoBar.push_info_bar(
|
||||
"warning",
|
||||
"获取MAA版本信息时出错",
|
||||
f"网络错误:{network_result['status_code']}",
|
||||
5000,
|
||||
)
|
||||
return None
|
||||
|
||||
if version.parse(maa_info["data"]["version_name"]) > version.parse(v):
|
||||
|
||||
logger.info(
|
||||
f"检测到MAA版本过低:{v},最新版本:{maa_info['data']['version_name']}"
|
||||
)
|
||||
MainInfoBar.push_info_bar(
|
||||
"info",
|
||||
"MAA版本过低",
|
||||
f"当前版本:{v},最新稳定版:{maa_info['data']['version_name']}",
|
||||
-1,
|
||||
)
|
||||
|
||||
def push_dialog(self, name: str, title: str, content: str):
|
||||
"""推送对话框"""
|
||||
|
||||
choice = MessageBox(title, content, Config.main_window)
|
||||
choice.yesButton.setText("是")
|
||||
choice.cancelButton.setText("否")
|
||||
|
||||
self.task_dict[name].question_response.emit(bool(choice.exec()))
|
||||
|
||||
|
||||
TaskManager = _TaskManager()
|
||||
150
app/core/timer.py
Normal file
@@ -0,0 +1,150 @@
|
||||
# AUTO_MAA:A MAA Multi Account Management and Automation Tool
|
||||
# Copyright © 2024-2025 DLmaster361
|
||||
|
||||
# This file is part of AUTO_MAA.
|
||||
|
||||
# AUTO_MAA is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License,
|
||||
# or (at your option) any later version.
|
||||
|
||||
# AUTO_MAA is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
|
||||
# the GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# Contact: DLmaster_361@163.com
|
||||
|
||||
"""
|
||||
AUTO_MAA
|
||||
AUTO_MAA主业务定时器
|
||||
v4.3
|
||||
作者:DLmaster_361
|
||||
"""
|
||||
|
||||
from loguru import logger
|
||||
from PySide6.QtCore import QObject, QTimer
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
import pyautogui
|
||||
|
||||
from .config import Config
|
||||
from .task_manager import TaskManager
|
||||
from app.services import System
|
||||
|
||||
|
||||
class _MainTimer(QObject):
|
||||
|
||||
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.timeout.connect(self.check_power)
|
||||
self.Timer.start(1000)
|
||||
self.LongTimer = QTimer()
|
||||
self.LongTimer.timeout.connect(self.long_timed_task)
|
||||
self.LongTimer.start(3600000)
|
||||
|
||||
def long_timed_task(self):
|
||||
"""长时间定期检定任务"""
|
||||
|
||||
Config.get_gameid()
|
||||
Config.main_window.setting.show_notice()
|
||||
if Config.get(Config.update_IfAutoUpdate):
|
||||
Config.main_window.setting.check_update()
|
||||
|
||||
def timed_start(self):
|
||||
"""定时启动代理任务"""
|
||||
|
||||
for name, info in Config.queue_dict.items():
|
||||
|
||||
if not info["Config"].get(info["Config"].queueSet_Enabled):
|
||||
continue
|
||||
|
||||
data = info["Config"].toDict()
|
||||
|
||||
time_set = [
|
||||
data["Time"][f"TimeSet_{_}"]
|
||||
for _ in range(10)
|
||||
if data["Time"][f"TimeEnabled_{_}"]
|
||||
]
|
||||
# 按时间调起代理任务
|
||||
curtime = datetime.now().strftime("%Y-%m-%d %H:%M")
|
||||
if (
|
||||
curtime[11:16] in time_set
|
||||
and curtime
|
||||
!= info["Config"].get(info["Config"].Data_LastProxyTime)[:16]
|
||||
and name not in Config.running_list
|
||||
):
|
||||
|
||||
logger.info(f"定时任务:{name}")
|
||||
TaskManager.add_task("自动代理_新调度台", name, data)
|
||||
|
||||
def set_silence(self):
|
||||
"""设置静默模式"""
|
||||
|
||||
if (
|
||||
not Config.if_ignore_silence
|
||||
and Config.get(Config.function_IfSilence)
|
||||
and Config.get(Config.function_BossKey) != ""
|
||||
):
|
||||
|
||||
windows = System.get_window_info()
|
||||
|
||||
# 排除雷电名为新通知的窗口
|
||||
windows = [
|
||||
window
|
||||
for window in windows
|
||||
if not (
|
||||
window[0] == "新通知" and Path(window[1]) in Config.silence_list
|
||||
)
|
||||
]
|
||||
|
||||
if any(
|
||||
str(emulator_path) in window
|
||||
for window in windows
|
||||
for emulator_path in Config.silence_list
|
||||
):
|
||||
try:
|
||||
pyautogui.hotkey(
|
||||
*[
|
||||
_.strip().lower()
|
||||
for _ in Config.get(Config.function_BossKey).split("+")
|
||||
]
|
||||
)
|
||||
except pyautogui.FailSafeException as e:
|
||||
if not self.if_FailSafeException:
|
||||
logger.warning(f"FailSafeException: {e}")
|
||||
self.if_FailSafeException = True
|
||||
|
||||
def check_power(self):
|
||||
|
||||
if Config.power_sign != "NoAction" and not Config.running_list:
|
||||
|
||||
from app.ui import ProgressRingMessageBox
|
||||
|
||||
mode_book = {
|
||||
"KillSelf": "退出软件",
|
||||
"Sleep": "睡眠",
|
||||
"Hibernate": "休眠",
|
||||
"Shutdown": "关机",
|
||||
}
|
||||
|
||||
choice = ProgressRingMessageBox(
|
||||
Config.main_window, f"{mode_book[Config.power_sign]}倒计时"
|
||||
)
|
||||
if choice.exec():
|
||||
System.set_power(Config.power_sign)
|
||||
Config.set_power_sign("NoAction")
|
||||
else:
|
||||
Config.set_power_sign("NoAction")
|
||||
|
||||
|
||||
MainTimer = _MainTimer()
|
||||
2042
app/models/MAA.py
Normal file
34
app/models/__init__.py
Normal file
@@ -0,0 +1,34 @@
|
||||
# AUTO_MAA:A MAA Multi Account Management and Automation Tool
|
||||
# Copyright © 2024-2025 DLmaster361
|
||||
|
||||
# This file is part of AUTO_MAA.
|
||||
|
||||
# AUTO_MAA is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License,
|
||||
# or (at your option) any later version.
|
||||
|
||||
# AUTO_MAA is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
|
||||
# the GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# Contact: DLmaster_361@163.com
|
||||
|
||||
"""
|
||||
AUTO_MAA
|
||||
AUTO_MAA模组包
|
||||
v4.3
|
||||
作者:DLmaster_361
|
||||
"""
|
||||
|
||||
__version__ = "4.2.0"
|
||||
__author__ = "DLmaster361 <DLmaster_361@163.com>"
|
||||
__license__ = "GPL-3.0 license"
|
||||
|
||||
from .MAA import MaaManager
|
||||
|
||||
__all__ = ["MaaManager"]
|
||||
37
app/services/__init__.py
Normal file
@@ -0,0 +1,37 @@
|
||||
# AUTO_MAA:A MAA Multi Account Management and Automation Tool
|
||||
# Copyright © 2024-2025 DLmaster361
|
||||
|
||||
# This file is part of AUTO_MAA.
|
||||
|
||||
# AUTO_MAA is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License,
|
||||
# or (at your option) any later version.
|
||||
|
||||
# AUTO_MAA is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
|
||||
# the GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# Contact: DLmaster_361@163.com
|
||||
|
||||
"""
|
||||
AUTO_MAA
|
||||
AUTO_MAA服务包
|
||||
v4.3
|
||||
作者:DLmaster_361
|
||||
"""
|
||||
|
||||
__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
|
||||
from .skland import skland_sign_in
|
||||
|
||||
__all__ = ["Notify", "Crypto", "System", "skland_sign_in"]
|
||||
314
app/services/notification.py
Normal file
@@ -0,0 +1,314 @@
|
||||
# AUTO_MAA:A MAA Multi Account Management and Automation Tool
|
||||
# Copyright © 2024-2025 DLmaster361
|
||||
|
||||
# This file is part of AUTO_MAA.
|
||||
|
||||
# AUTO_MAA is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License,
|
||||
# or (at your option) any later version.
|
||||
|
||||
# AUTO_MAA is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
|
||||
# the GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# Contact: DLmaster_361@163.com
|
||||
|
||||
"""
|
||||
AUTO_MAA
|
||||
AUTO_MAA通知服务
|
||||
v4.3
|
||||
作者:DLmaster_361
|
||||
"""
|
||||
|
||||
import re
|
||||
import smtplib
|
||||
import time
|
||||
from email.header import Header
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from email.mime.text import MIMEText
|
||||
from email.utils import formataddr
|
||||
|
||||
import requests
|
||||
from PySide6.QtCore import QObject, Signal
|
||||
from loguru import logger
|
||||
from plyer import notification
|
||||
|
||||
from app.core import Config
|
||||
from app.services.security import Crypto
|
||||
|
||||
|
||||
class Notification(QObject):
|
||||
|
||||
push_info_bar = Signal(str, str, str, int)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
def push_plyer(self, title, message, ticker, t):
|
||||
"""推送系统通知"""
|
||||
|
||||
if Config.get(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, mode, title, content, to_address) -> None:
|
||||
"""推送邮件通知"""
|
||||
if (
|
||||
Config.get(Config.notify_SMTPServerAddress) == ""
|
||||
or Config.get(Config.notify_AuthorizationCode) == ""
|
||||
or not bool(
|
||||
re.match(
|
||||
r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$",
|
||||
Config.get(Config.notify_FromAddress),
|
||||
)
|
||||
)
|
||||
or not bool(
|
||||
re.match(
|
||||
r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$",
|
||||
to_address,
|
||||
)
|
||||
)
|
||||
):
|
||||
logger.error(
|
||||
"请正确设置邮件通知的SMTP服务器地址、授权码、发件人地址和收件人地址"
|
||||
)
|
||||
self.push_info_bar.emit(
|
||||
"error",
|
||||
"邮件通知推送异常",
|
||||
"请正确设置邮件通知的SMTP服务器地址、授权码、发件人地址和收件人地址",
|
||||
-1,
|
||||
)
|
||||
return None
|
||||
|
||||
try:
|
||||
# 定义邮件正文
|
||||
if mode == "文本":
|
||||
message = MIMEText(content, "plain", "utf-8")
|
||||
elif mode == "网页":
|
||||
message = MIMEMultipart("alternative")
|
||||
message["From"] = formataddr(
|
||||
(
|
||||
Header("AUTO_MAA通知服务", "utf-8").encode(),
|
||||
Config.get(Config.notify_FromAddress),
|
||||
)
|
||||
) # 发件人显示的名字
|
||||
message["To"] = formataddr(
|
||||
(
|
||||
Header("AUTO_MAA用户", "utf-8").encode(),
|
||||
to_address,
|
||||
)
|
||||
) # 收件人显示的名字
|
||||
message["Subject"] = Header(title, "utf-8")
|
||||
|
||||
if mode == "网页":
|
||||
message.attach(MIMEText(content, "html", "utf-8"))
|
||||
|
||||
smtpObj = smtplib.SMTP_SSL(
|
||||
Config.get(Config.notify_SMTPServerAddress),
|
||||
465,
|
||||
)
|
||||
smtpObj.login(
|
||||
Config.get(Config.notify_FromAddress),
|
||||
Crypto.win_decryptor(Config.get(Config.notify_AuthorizationCode)),
|
||||
)
|
||||
smtpObj.sendmail(
|
||||
Config.get(Config.notify_FromAddress),
|
||||
to_address,
|
||||
message.as_string(),
|
||||
)
|
||||
smtpObj.quit()
|
||||
logger.success("邮件发送成功")
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.error(f"发送邮件时出错:\n{e}")
|
||||
self.push_info_bar.emit("error", "发送邮件时出错", f"{e}", -1)
|
||||
return None
|
||||
return None
|
||||
|
||||
def ServerChanPush(self, title, content, send_key, tag, channel):
|
||||
"""使用Server酱推送通知"""
|
||||
if not send_key:
|
||||
logger.error("请正确设置Server酱的SendKey")
|
||||
self.push_info_bar.emit(
|
||||
"error", "Server酱通知推送异常", "请正确设置Server酱的SendKey", -1
|
||||
)
|
||||
return None
|
||||
|
||||
try:
|
||||
# 构造 URL
|
||||
if send_key.startswith("sctp"):
|
||||
match = re.match(r"^sctp(\d+)t", send_key)
|
||||
if match:
|
||||
url = f"https://{match.group(1)}.push.ft07.com/send/{send_key}.send"
|
||||
else:
|
||||
raise ValueError("SendKey 格式错误(sctp)")
|
||||
else:
|
||||
url = f"https://sctapi.ftqq.com/{send_key}.send"
|
||||
|
||||
# 构建 tags 和 channel
|
||||
def is_valid(s):
|
||||
return s == "" or (
|
||||
s == "|".join(s.split("|"))
|
||||
and (s.count("|") == 0 or all(s.split("|")))
|
||||
)
|
||||
|
||||
tags = "|".join(_.strip() for _ in tag.split("|"))
|
||||
channels = "|".join(_.strip() for _ in channel.split("|"))
|
||||
|
||||
options = {}
|
||||
if is_valid(tags):
|
||||
options["tags"] = tags
|
||||
else:
|
||||
logger.warning("Server酱 Tag 配置不正确,将被忽略")
|
||||
self.push_info_bar.emit(
|
||||
"warning",
|
||||
"Server酱通知推送异常",
|
||||
"请正确设置 ServerChan 的 Tag",
|
||||
-1,
|
||||
)
|
||||
|
||||
if is_valid(channels):
|
||||
options["channel"] = channels
|
||||
else:
|
||||
logger.warning("Server酱 Channel 配置不正确,将被忽略")
|
||||
self.push_info_bar.emit(
|
||||
"warning",
|
||||
"Server酱通知推送异常",
|
||||
"请正确设置 ServerChan 的 Channel",
|
||||
-1,
|
||||
)
|
||||
|
||||
# 请求发送
|
||||
params = {"title": title, "desp": content, **options}
|
||||
headers = {"Content-Type": "application/json;charset=utf-8"}
|
||||
|
||||
response = requests.post(url, json=params, headers=headers, timeout=10)
|
||||
result = response.json()
|
||||
|
||||
if result.get("code") == 0:
|
||||
logger.info("Server酱推送通知成功")
|
||||
return True
|
||||
else:
|
||||
error_code = result.get("code", "-1")
|
||||
logger.error(f"Server酱通知推送失败:响应码:{error_code}")
|
||||
self.push_info_bar.emit(
|
||||
"error", "Server酱通知推送失败", f"响应码:{error_code}", -1
|
||||
)
|
||||
return f"Server酱通知推送失败:{error_code}"
|
||||
|
||||
except Exception as e:
|
||||
logger.exception("Server酱通知推送异常")
|
||||
self.push_info_bar.emit(
|
||||
"error",
|
||||
"Server酱通知推送异常",
|
||||
"请检查相关设置和网络连接。如全部配置正确,请稍后再试。",
|
||||
-1,
|
||||
)
|
||||
return f"Server酱通知推送异常:{str(e)}"
|
||||
|
||||
def CompanyWebHookBotPush(self, title, content, webhook_url):
|
||||
"""使用企业微信群机器人推送通知"""
|
||||
if webhook_url == "":
|
||||
logger.error("请正确设置企业微信群机器人的WebHook地址")
|
||||
self.push_info_bar.emit(
|
||||
"error",
|
||||
"企业微信群机器人通知推送异常",
|
||||
"请正确设置企业微信群机器人的WebHook地址",
|
||||
-1,
|
||||
)
|
||||
return None
|
||||
|
||||
content = f"{title}\n{content}"
|
||||
data = {"msgtype": "text", "text": {"content": content}}
|
||||
|
||||
for _ in range(3):
|
||||
try:
|
||||
response = requests.post(
|
||||
url=webhook_url,
|
||||
json=data,
|
||||
timeout=10,
|
||||
)
|
||||
info = response.json()
|
||||
break
|
||||
except Exception as e:
|
||||
err = e
|
||||
time.sleep(0.1)
|
||||
else:
|
||||
logger.error(f"推送企业微信群机器人时出错:{err}")
|
||||
self.push_info_bar.emit(
|
||||
"error",
|
||||
"企业微信群机器人通知推送失败",
|
||||
f"使用企业微信群机器人推送通知时出错:{err}",
|
||||
-1,
|
||||
)
|
||||
return None
|
||||
|
||||
if info["errcode"] == 0:
|
||||
logger.info("企业微信群机器人推送通知成功")
|
||||
return True
|
||||
else:
|
||||
logger.error(f"企业微信群机器人推送通知失败:{info}")
|
||||
self.push_info_bar.emit(
|
||||
"error",
|
||||
"企业微信群机器人通知推送失败",
|
||||
f"使用企业微信群机器人推送通知时出错:{err}",
|
||||
-1,
|
||||
)
|
||||
return f"使用企业微信群机器人推送通知时出错:{err}"
|
||||
|
||||
def send_test_notification(self):
|
||||
"""发送测试通知到所有已启用的通知渠道"""
|
||||
# 发送系统通知
|
||||
self.push_plyer(
|
||||
"测试通知",
|
||||
"这是 AUTO_MAA 外部通知测试信息。如果你看到了这段内容,说明 AUTO_MAA 的通知功能已经正确配置且可以正常工作!",
|
||||
"测试通知",
|
||||
3,
|
||||
)
|
||||
|
||||
# 发送邮件通知
|
||||
if Config.get(Config.notify_IfSendMail):
|
||||
self.send_mail(
|
||||
"文本",
|
||||
"AUTO_MAA测试通知",
|
||||
"这是 AUTO_MAA 外部通知测试信息。如果你看到了这段内容,说明 AUTO_MAA 的通知功能已经正确配置且可以正常工作!",
|
||||
Config.get(Config.notify_ToAddress),
|
||||
)
|
||||
|
||||
# 发送Server酱通知
|
||||
if Config.get(Config.notify_IfServerChan):
|
||||
self.ServerChanPush(
|
||||
"AUTO_MAA测试通知",
|
||||
"这是 AUTO_MAA 外部通知测试信息。如果你看到了这段内容,说明 AUTO_MAA 的通知功能已经正确配置且可以正常工作!",
|
||||
Config.get(Config.notify_ServerChanKey),
|
||||
Config.get(Config.notify_ServerChanTag),
|
||||
Config.get(Config.notify_ServerChanChannel),
|
||||
)
|
||||
|
||||
# 发送企业微信机器人通知
|
||||
if Config.get(Config.notify_IfCompanyWebHookBot):
|
||||
self.CompanyWebHookBotPush(
|
||||
"AUTO_MAA测试通知",
|
||||
"这是 AUTO_MAA 外部通知测试信息。如果你看到了这段内容,说明 AUTO_MAA 的通知功能已经正确配置且可以正常工作!",
|
||||
Config.get(Config.notify_CompanyWebHookBotUrl),
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
Notify = Notification()
|
||||
212
app/services/security.py
Normal file
@@ -0,0 +1,212 @@
|
||||
# AUTO_MAA:A MAA Multi Account Management and Automation Tool
|
||||
# Copyright © 2024-2025 DLmaster361
|
||||
|
||||
# This file is part of AUTO_MAA.
|
||||
|
||||
# AUTO_MAA is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License,
|
||||
# or (at your option) any later version.
|
||||
|
||||
# AUTO_MAA is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
|
||||
# the GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# Contact: DLmaster_361@163.com
|
||||
|
||||
"""
|
||||
AUTO_MAA
|
||||
AUTO_MAA安全服务
|
||||
v4.3
|
||||
作者:DLmaster_361
|
||||
"""
|
||||
|
||||
from loguru import logger
|
||||
import hashlib
|
||||
import random
|
||||
import secrets
|
||||
import base64
|
||||
import win32crypt
|
||||
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 AUTO_encryptor(self, note: str) -> str:
|
||||
"""使用AUTO_MAA的算法加密数据"""
|
||||
|
||||
if note == "":
|
||||
return ""
|
||||
|
||||
# 读取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 base64.b64encode(encrypted).decode("utf-8")
|
||||
|
||||
def AUTO_decryptor(self, note: str, PASSWORD: str) -> str:
|
||||
"""使用AUTO_MAA的算法解密数据"""
|
||||
|
||||
if note == "":
|
||||
return ""
|
||||
|
||||
# 读入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(base64.b64decode(note)).decode("utf-8")
|
||||
return note
|
||||
|
||||
def change_PASSWORD(self, PASSWORD_old: str, PASSWORD_new: str) -> None:
|
||||
"""修改管理密钥"""
|
||||
|
||||
for member in Config.member_dict.values():
|
||||
|
||||
# 使用旧管理密钥解密
|
||||
for user in member["UserData"].values():
|
||||
user["Password"] = self.AUTO_decryptor(
|
||||
user["Config"].get(user["Config"].Info_Password), PASSWORD_old
|
||||
)
|
||||
|
||||
self.get_PASSWORD(PASSWORD_new)
|
||||
|
||||
for member in Config.member_dict.values():
|
||||
|
||||
# 使用新管理密钥重新加密
|
||||
for user in member["UserData"].values():
|
||||
user["Config"].set(
|
||||
user["Config"].Info_Password, self.AUTO_encryptor(user["Password"])
|
||||
)
|
||||
user["Password"] = None
|
||||
del user["Password"]
|
||||
|
||||
def win_encryptor(
|
||||
self, note: str, description: str = None, entropy: bytes = None
|
||||
) -> str:
|
||||
"""使用Windows DPAPI加密数据"""
|
||||
|
||||
if note == "":
|
||||
return ""
|
||||
|
||||
encrypted = win32crypt.CryptProtectData(
|
||||
note.encode("utf-8"), description, entropy, None, None, 0
|
||||
)
|
||||
return base64.b64encode(encrypted).decode("utf-8")
|
||||
|
||||
def win_decryptor(self, note: str, entropy: bytes = None) -> str:
|
||||
"""使用Windows DPAPI解密数据"""
|
||||
|
||||
if note == "":
|
||||
return ""
|
||||
|
||||
decrypted = win32crypt.CryptUnprotectData(
|
||||
base64.b64decode(note), entropy, None, None, 0
|
||||
)
|
||||
return decrypted[1].decode("utf-8")
|
||||
|
||||
def search_member(self) -> List[Dict[str, Union[Path, list]]]:
|
||||
"""搜索所有脚本实例及其用户数据库路径"""
|
||||
|
||||
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.AUTO_decryptor(self.AUTO_encryptor("-"), PASSWORD) != "管理密钥错误"
|
||||
)
|
||||
|
||||
|
||||
Crypto = CryptoHandler()
|
||||
239
app/services/skland.py
Normal file
@@ -0,0 +1,239 @@
|
||||
# AUTO_MAA:A MAA Multi Account Management and Automation Tool
|
||||
# Copyright © 2024-2025 DLmaster361
|
||||
|
||||
# This file incorporates work covered by the following copyright and
|
||||
# permission notice:
|
||||
#
|
||||
# skland-checkin-ghaction Copyright © 2023 Yanstory
|
||||
# https://github.com/Yanstory/skland-checkin-ghaction
|
||||
|
||||
# This file is part of AUTO_MAA.
|
||||
|
||||
# AUTO_MAA is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License,
|
||||
# or (at your option) any later version.
|
||||
|
||||
# AUTO_MAA is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
|
||||
# the GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# Contact: DLmaster_361@163.com
|
||||
|
||||
|
||||
"""
|
||||
AUTO_MAA
|
||||
AUTO_MAA森空岛服务
|
||||
v4.3
|
||||
作者:DLmaster_361、ClozyA
|
||||
"""
|
||||
|
||||
from loguru import logger
|
||||
import time
|
||||
import json
|
||||
import hmac
|
||||
import hashlib
|
||||
import requests
|
||||
from urllib import parse
|
||||
|
||||
|
||||
def skland_sign_in(token) -> dict:
|
||||
"""森空岛签到"""
|
||||
|
||||
app_code = "4ca99fa6b56cc2ba"
|
||||
# 用于获取grant code
|
||||
grant_code_url = "https://as.hypergryph.com/user/oauth2/v2/grant"
|
||||
# 用于获取cred
|
||||
cred_code_url = "https://zonai.skland.com/api/v1/user/auth/generate_cred_by_code"
|
||||
# 查询角色绑定
|
||||
binding_url = "https://zonai.skland.com/api/v1/game/player/binding"
|
||||
# 签到接口
|
||||
sign_url = "https://zonai.skland.com/api/v1/game/attendance"
|
||||
|
||||
# 基础请求头
|
||||
header = {
|
||||
"cred": "",
|
||||
"User-Agent": "Skland/1.5.1 (com.hypergryph.skland; build:100501001; Android 34;) Okhttp/4.11.0",
|
||||
"Accept-Encoding": "gzip",
|
||||
"Connection": "close",
|
||||
}
|
||||
header_login = header.copy()
|
||||
header_for_sign = {
|
||||
"platform": "1",
|
||||
"timestamp": "",
|
||||
"dId": "",
|
||||
"vName": "1.5.1",
|
||||
}
|
||||
|
||||
# 生成签名
|
||||
def generate_signature(token_for_sign: str, path, body_or_query):
|
||||
"""
|
||||
生成请求签名
|
||||
:param token_for_sign: 用于加密的token
|
||||
:param path: 请求路径(如 /api/v1/game/player/binding)
|
||||
:param body_or_query: GET用query字符串,POST用body字符串
|
||||
:return: (sign, 新的header_for_sign字典)
|
||||
"""
|
||||
t = str(int(time.time()) - 2) # 时间戳,-2秒以防服务器时间不一致
|
||||
token_bytes = token_for_sign.encode("utf-8")
|
||||
header_ca = dict(header_for_sign)
|
||||
header_ca["timestamp"] = t
|
||||
header_ca_str = json.dumps(header_ca, separators=(",", ":"))
|
||||
s = path + body_or_query + t + header_ca_str # 拼接原始字符串
|
||||
# HMAC-SHA256 + MD5得到最终sign
|
||||
hex_s = hmac.new(token_bytes, s.encode("utf-8"), hashlib.sha256).hexdigest()
|
||||
md5 = hashlib.md5(hex_s.encode("utf-8")).hexdigest()
|
||||
return md5, header_ca
|
||||
|
||||
# 获取带签名的header
|
||||
def get_sign_header(url: str, method, body, old_header, sign_token):
|
||||
"""
|
||||
获取带签名的请求头
|
||||
:param url: 请求完整url
|
||||
:param method: 请求方式 GET/POST
|
||||
:param body: POST请求体或GET时为None
|
||||
:param old_header: 原始请求头
|
||||
:param sign_token: 当前会话的签名token
|
||||
:return: 新请求头
|
||||
"""
|
||||
h = json.loads(json.dumps(old_header))
|
||||
p = parse.urlparse(url)
|
||||
if method.lower() == "get":
|
||||
sign, header_ca = generate_signature(sign_token, p.path, p.query)
|
||||
else:
|
||||
sign, header_ca = generate_signature(
|
||||
sign_token, p.path, json.dumps(body) if body else ""
|
||||
)
|
||||
h["sign"] = sign
|
||||
for i in header_ca:
|
||||
h[i] = header_ca[i]
|
||||
return h
|
||||
|
||||
# 复制请求头并添加cred
|
||||
def copy_header(cred):
|
||||
v = json.loads(json.dumps(header))
|
||||
v["cred"] = cred
|
||||
return v
|
||||
|
||||
# 使用token一步步拿到cred和sign_token
|
||||
def login_by_token(token_code):
|
||||
"""
|
||||
:param token_code: 你的skyland token
|
||||
:return: (cred, sign_token)
|
||||
"""
|
||||
try:
|
||||
# token为json对象时提取data.content
|
||||
t = json.loads(token_code)
|
||||
token_code = t["data"]["content"]
|
||||
except:
|
||||
pass
|
||||
grant_code = get_grant_code(token_code)
|
||||
return get_cred(grant_code)
|
||||
|
||||
# 通过grant code换cred和sign_token
|
||||
def get_cred(grant):
|
||||
rsp = requests.post(
|
||||
cred_code_url, json={"code": grant, "kind": 1}, headers=header_login
|
||||
).json()
|
||||
if rsp["code"] != 0:
|
||||
raise Exception(f'获得cred失败:{rsp.get("messgae")}')
|
||||
sign_token = rsp["data"]["token"]
|
||||
cred = rsp["data"]["cred"]
|
||||
return cred, sign_token
|
||||
|
||||
# 通过token换grant code
|
||||
def get_grant_code(token):
|
||||
rsp = requests.post(
|
||||
grant_code_url,
|
||||
json={"appCode": app_code, "token": token, "type": 0},
|
||||
headers=header_login,
|
||||
).json()
|
||||
if rsp["status"] != 0:
|
||||
raise Exception(f'使用token: {token} 获得认证代码失败:{rsp.get("msg")}')
|
||||
return rsp["data"]["code"]
|
||||
|
||||
# 获取已绑定的角色列表
|
||||
def get_binding_list(cred, sign_token):
|
||||
"""
|
||||
查询绑定的角色
|
||||
:param cred: 当前cred
|
||||
:param sign_token: 当前sign_token
|
||||
:return: 角色列表
|
||||
"""
|
||||
v = []
|
||||
rsp = requests.get(
|
||||
binding_url,
|
||||
headers=get_sign_header(
|
||||
binding_url, "get", None, copy_header(cred), sign_token
|
||||
),
|
||||
).json()
|
||||
if rsp["code"] != 0:
|
||||
logger.error(f"森空岛服务 | 请求角色列表出现问题:{rsp['message']}")
|
||||
if rsp.get("message") == "用户未登录":
|
||||
logger.error(f"森空岛服务 | 用户登录可能失效了,请重新登录!")
|
||||
return v
|
||||
# 只取明日方舟(arknights)的绑定账号
|
||||
for i in rsp["data"]["list"]:
|
||||
if i.get("appCode") != "arknights":
|
||||
continue
|
||||
v.extend(i.get("bindingList"))
|
||||
return v
|
||||
|
||||
# 执行签到
|
||||
def do_sign(cred, sign_token) -> dict:
|
||||
"""
|
||||
对所有绑定的角色进行签到
|
||||
:param cred: 当前cred
|
||||
:param sign_token: 当前sign_token
|
||||
:return: 签到结果字典
|
||||
"""
|
||||
|
||||
characters = get_binding_list(cred, sign_token)
|
||||
result = {"成功": [], "重复": [], "失败": [], "总计": len(characters)}
|
||||
|
||||
for character in characters:
|
||||
|
||||
body = {
|
||||
"uid": character.get("uid"),
|
||||
"gameId": character.get("channelMasterId"),
|
||||
}
|
||||
rsp = requests.post(
|
||||
sign_url,
|
||||
headers=get_sign_header(
|
||||
sign_url, "post", body, copy_header(cred), sign_token
|
||||
),
|
||||
json=body,
|
||||
).json()
|
||||
|
||||
if rsp["code"] != 0:
|
||||
|
||||
result[
|
||||
"重复" if rsp.get("message") == "请勿重复签到!" else "失败"
|
||||
].append(
|
||||
f"{character.get("nickName")}({character.get("channelName")})"
|
||||
)
|
||||
|
||||
else:
|
||||
|
||||
result["成功"].append(
|
||||
f"{character.get("nickName")}({character.get("channelName")})"
|
||||
)
|
||||
|
||||
time.sleep(3)
|
||||
|
||||
return result
|
||||
|
||||
# 主流程
|
||||
try:
|
||||
# 拿到cred和sign_token
|
||||
cred, sign_token = login_by_token(token)
|
||||
time.sleep(1)
|
||||
# 依次签到
|
||||
return do_sign(cred, sign_token)
|
||||
except Exception as e:
|
||||
logger.error(f"森空岛服务 | 森空岛签到失败: {e}")
|
||||
return {"成功": [], "重复": [], "失败": [], "总计": 0}
|
||||
201
app/services/system.py
Normal file
@@ -0,0 +1,201 @@
|
||||
# AUTO_MAA:A MAA Multi Account Management and Automation Tool
|
||||
# Copyright © 2024-2025 DLmaster361
|
||||
|
||||
# This file is part of AUTO_MAA.
|
||||
|
||||
# AUTO_MAA is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License,
|
||||
# or (at your option) any later version.
|
||||
|
||||
# AUTO_MAA is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
|
||||
# the GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# Contact: DLmaster_361@163.com
|
||||
|
||||
"""
|
||||
AUTO_MAA
|
||||
AUTO_MAA系统服务
|
||||
v4.3
|
||||
作者:DLmaster_361
|
||||
"""
|
||||
|
||||
from loguru import logger
|
||||
from PySide6.QtWidgets import QApplication
|
||||
import sys
|
||||
import ctypes
|
||||
import win32gui
|
||||
import win32process
|
||||
import winreg
|
||||
import psutil
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
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) -> None:
|
||||
"""同步系统休眠状态"""
|
||||
|
||||
if Config.get(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) -> None:
|
||||
"""同步开机自启"""
|
||||
|
||||
if Config.get(Config.start_IfSelfStart) and not self.is_startup():
|
||||
key = winreg.OpenKey(
|
||||
winreg.HKEY_CURRENT_USER,
|
||||
r"Software\Microsoft\Windows\CurrentVersion\Run",
|
||||
winreg.KEY_SET_VALUE,
|
||||
winreg.KEY_ALL_ACCESS | winreg.KEY_WRITE | winreg.KEY_CREATE_SUB_KEY,
|
||||
)
|
||||
winreg.SetValueEx(key, "AUTO_MAA", 0, winreg.REG_SZ, Config.app_path_sys)
|
||||
winreg.CloseKey(key)
|
||||
elif not Config.get(Config.start_IfSelfStart) and self.is_startup():
|
||||
key = winreg.OpenKey(
|
||||
winreg.HKEY_CURRENT_USER,
|
||||
r"Software\Microsoft\Windows\CurrentVersion\Run",
|
||||
winreg.KEY_SET_VALUE,
|
||||
winreg.KEY_ALL_ACCESS | winreg.KEY_WRITE | winreg.KEY_CREATE_SUB_KEY,
|
||||
)
|
||||
winreg.DeleteValue(key, "AUTO_MAA")
|
||||
winreg.CloseKey(key)
|
||||
|
||||
def set_power(self, mode) -> None:
|
||||
|
||||
if sys.platform.startswith("win"):
|
||||
|
||||
if mode == "NoAction":
|
||||
|
||||
logger.info("不执行系统电源操作")
|
||||
|
||||
elif mode == "Shutdown":
|
||||
|
||||
logger.info("执行关机操作")
|
||||
subprocess.run(["shutdown", "/s", "/t", "0"])
|
||||
|
||||
elif mode == "Hibernate":
|
||||
|
||||
logger.info("执行休眠操作")
|
||||
subprocess.run(["shutdown", "/h"])
|
||||
|
||||
elif mode == "Sleep":
|
||||
|
||||
logger.info("执行睡眠操作")
|
||||
subprocess.run(
|
||||
["rundll32.exe", "powrprof.dll,SetSuspendState", "0,1,0"]
|
||||
)
|
||||
|
||||
elif mode == "KillSelf":
|
||||
|
||||
Config.main_window.close()
|
||||
QApplication.quit()
|
||||
sys.exit(0)
|
||||
|
||||
elif sys.platform.startswith("linux"):
|
||||
|
||||
if mode == "NoAction":
|
||||
|
||||
logger.info("不执行系统电源操作")
|
||||
|
||||
elif mode == "Shutdown":
|
||||
|
||||
logger.info("执行关机操作")
|
||||
subprocess.run(["shutdown", "-h", "now"])
|
||||
|
||||
elif mode == "Hibernate":
|
||||
|
||||
logger.info("执行休眠操作")
|
||||
subprocess.run(["systemctl", "hibernate"])
|
||||
|
||||
elif mode == "Sleep":
|
||||
|
||||
logger.info("执行睡眠操作")
|
||||
subprocess.run(["systemctl", "suspend"])
|
||||
|
||||
elif mode == "KillSelf":
|
||||
|
||||
Config.main_window.close()
|
||||
QApplication.quit()
|
||||
sys.exit(0)
|
||||
|
||||
def is_startup(self) -> bool:
|
||||
"""判断程序是否已经开机自启"""
|
||||
|
||||
key = winreg.OpenKey(
|
||||
winreg.HKEY_CURRENT_USER,
|
||||
r"Software\Microsoft\Windows\CurrentVersion\Run",
|
||||
0,
|
||||
winreg.KEY_READ,
|
||||
)
|
||||
|
||||
try:
|
||||
value, _ = winreg.QueryValueEx(key, "AUTO_MAA")
|
||||
winreg.CloseKey(key)
|
||||
return True
|
||||
except FileNotFoundError:
|
||||
winreg.CloseKey(key)
|
||||
return False
|
||||
|
||||
def get_window_info(self) -> list:
|
||||
"""获取当前窗口信息"""
|
||||
|
||||
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
|
||||
|
||||
def kill_process(self, path: Path) -> None:
|
||||
"""根据路径中止进程"""
|
||||
|
||||
for pid in self.search_pids(path):
|
||||
killprocess = subprocess.Popen(
|
||||
f"taskkill /F /T /PID {pid}",
|
||||
shell=True,
|
||||
creationflags=subprocess.CREATE_NO_WINDOW,
|
||||
)
|
||||
killprocess.wait()
|
||||
|
||||
def search_pids(self, path: Path) -> list:
|
||||
"""根据路径查找进程PID"""
|
||||
|
||||
pids = []
|
||||
for proc in psutil.process_iter(["pid", "exe"]):
|
||||
try:
|
||||
if proc.info["exe"] and proc.info["exe"].lower() == str(path).lower():
|
||||
pids.append(proc.info["pid"])
|
||||
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
|
||||
# 进程可能在此期间已结束或无法访问,忽略这些异常
|
||||
pass
|
||||
return pids
|
||||
|
||||
|
||||
System = _SystemHandler()
|
||||
2045
app/ui/Widget.py
Normal file
35
app/ui/__init__.py
Normal file
@@ -0,0 +1,35 @@
|
||||
# AUTO_MAA:A MAA Multi Account Management and Automation Tool
|
||||
# Copyright © 2024-2025 DLmaster361
|
||||
|
||||
# This file is part of AUTO_MAA.
|
||||
|
||||
# AUTO_MAA is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License,
|
||||
# or (at your option) any later version.
|
||||
|
||||
# AUTO_MAA is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
|
||||
# the GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# Contact: DLmaster_361@163.com
|
||||
|
||||
"""
|
||||
AUTO_MAA
|
||||
AUTO_MAA图形化界面包
|
||||
v4.3
|
||||
作者:DLmaster_361
|
||||
"""
|
||||
|
||||
__version__ = "4.2.0"
|
||||
__author__ = "DLmaster361 <DLmaster_361@163.com>"
|
||||
__license__ = "GPL-3.0 license"
|
||||
|
||||
from .main_window import AUTO_MAA
|
||||
from .Widget import ProgressRingMessageBox
|
||||
|
||||
__all__ = ["AUTO_MAA", "ProgressRingMessageBox"]
|
||||
551
app/ui/dispatch_center.py
Normal file
@@ -0,0 +1,551 @@
|
||||
# AUTO_MAA:A MAA Multi Account Management and Automation Tool
|
||||
# Copyright © 2024-2025 DLmaster361
|
||||
|
||||
# This file is part of AUTO_MAA.
|
||||
|
||||
# AUTO_MAA is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License,
|
||||
# or (at your option) any later version.
|
||||
|
||||
# AUTO_MAA is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
|
||||
# the GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# Contact: DLmaster_361@163.com
|
||||
|
||||
"""
|
||||
AUTO_MAA
|
||||
AUTO_MAA调度中枢界面
|
||||
v4.3
|
||||
作者:DLmaster_361
|
||||
"""
|
||||
|
||||
from loguru import logger
|
||||
from PySide6.QtWidgets import (
|
||||
QWidget,
|
||||
QVBoxLayout,
|
||||
QStackedWidget,
|
||||
QHBoxLayout,
|
||||
)
|
||||
from qfluentwidgets import (
|
||||
BodyLabel,
|
||||
CardWidget,
|
||||
ScrollArea,
|
||||
FluentIcon,
|
||||
HeaderCardWidget,
|
||||
FluentIcon,
|
||||
TextBrowser,
|
||||
ComboBox,
|
||||
SubtitleLabel,
|
||||
PushButton,
|
||||
)
|
||||
from PySide6.QtGui import QTextCursor
|
||||
from typing import List, Dict
|
||||
|
||||
|
||||
from app.core import Config, TaskManager, Task, MainInfoBar, SoundPlayer
|
||||
from .Widget import StatefulItemCard, ComboBoxMessageBox, PivotArea
|
||||
|
||||
|
||||
class DispatchCenter(QWidget):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
self.setObjectName("调度中枢")
|
||||
|
||||
self.multi_button = PushButton(FluentIcon.ADD, "添加任务", self)
|
||||
self.multi_button.setToolTip("添加任务")
|
||||
self.multi_button.clicked.connect(self.start_multi_task)
|
||||
|
||||
self.power_combox = ComboBox()
|
||||
self.power_combox.addItem("无动作", userData="NoAction")
|
||||
self.power_combox.addItem("退出软件", userData="KillSelf")
|
||||
self.power_combox.addItem("睡眠", userData="Sleep")
|
||||
self.power_combox.addItem("休眠", userData="Hibernate")
|
||||
self.power_combox.addItem("关机", userData="Shutdown")
|
||||
self.power_combox.setCurrentText("无动作")
|
||||
self.power_combox.currentIndexChanged.connect(self.set_power_sign)
|
||||
|
||||
self.pivotArea = PivotArea(self)
|
||||
self.pivot = self.pivotArea.pivot
|
||||
|
||||
self.stackedWidget = QStackedWidget(self)
|
||||
self.stackedWidget.setContentsMargins(0, 0, 0, 0)
|
||||
self.stackedWidget.setStyleSheet("background: transparent; border: none;")
|
||||
|
||||
self.script_list: Dict[str, DispatchCenter.DispatchBox] = {}
|
||||
|
||||
dispatch_box = self.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,
|
||||
)
|
||||
|
||||
h_layout = QHBoxLayout()
|
||||
h_layout.addWidget(self.multi_button)
|
||||
h_layout.addWidget(self.pivotArea)
|
||||
h_layout.addWidget(BodyLabel("全部完成后", self))
|
||||
h_layout.addWidget(self.power_combox)
|
||||
h_layout.setContentsMargins(11, 5, 11, 0)
|
||||
|
||||
self.Layout = QVBoxLayout(self)
|
||||
self.Layout.addLayout(h_layout)
|
||||
self.Layout.addWidget(self.stackedWidget)
|
||||
self.Layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
self.pivot.currentItemChanged.connect(
|
||||
lambda index: self.stackedWidget.setCurrentWidget(self.script_list[index])
|
||||
)
|
||||
|
||||
def add_board(self, task: Task) -> None:
|
||||
"""添加一个调度台界面"""
|
||||
|
||||
dispatch_box = self.DispatchBox(task.name, self)
|
||||
|
||||
dispatch_box.top_bar.main_button.clicked.connect(
|
||||
lambda: TaskManager.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.Lable.setText(
|
||||
f"{task.name} - {task.mode.replace("_主调度台","")}模式"
|
||||
)
|
||||
self.script_list["主调度台"].top_bar.Lable.show()
|
||||
self.script_list["主调度台"].top_bar.object.hide()
|
||||
self.script_list["主调度台"].top_bar.mode.hide()
|
||||
self.script_list["主调度台"].top_bar.main_button.clicked.disconnect()
|
||||
self.script_list["主调度台"].top_bar.main_button.setText("中止任务")
|
||||
self.script_list["主调度台"].top_bar.main_button.clicked.connect(
|
||||
lambda: TaskManager.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 logs: self.disconnect_main_board(task.name, logs)
|
||||
)
|
||||
|
||||
def disconnect_main_board(self, name: str, logs: list) -> None:
|
||||
"""断开主调度台"""
|
||||
|
||||
self.script_list["主调度台"].top_bar.Lable.hide()
|
||||
self.script_list["主调度台"].top_bar.object.show()
|
||||
self.script_list["主调度台"].top_bar.mode.show()
|
||||
self.script_list["主调度台"].top_bar.main_button.clicked.disconnect()
|
||||
self.script_list["主调度台"].top_bar.main_button.setText("开始任务")
|
||||
self.script_list["主调度台"].top_bar.main_button.clicked.connect(
|
||||
self.script_list["主调度台"].top_bar.start_main_task
|
||||
)
|
||||
if len(logs) > 0:
|
||||
history = ""
|
||||
for log in logs:
|
||||
history += (
|
||||
f"任务名称:{log[0]},{log[1]["History"].replace("\n","\n ")}\n"
|
||||
)
|
||||
self.script_list["主调度台"].info.log_text.text.setText(history)
|
||||
else:
|
||||
self.script_list["主调度台"].info.log_text.text.setText("没有任务被执行")
|
||||
|
||||
def update_top_bar(self):
|
||||
"""更新顶栏"""
|
||||
|
||||
self.script_list["主调度台"].top_bar.object.clear()
|
||||
|
||||
for name, info in Config.queue_dict.items():
|
||||
self.script_list["主调度台"].top_bar.object.addItem(
|
||||
(
|
||||
"队列"
|
||||
if info["Config"].get(info["Config"].queueSet_Name) == ""
|
||||
else f"队列 - {info["Config"].get(info["Config"].queueSet_Name)}"
|
||||
),
|
||||
userData=name,
|
||||
)
|
||||
|
||||
for name, info in Config.member_dict.items():
|
||||
self.script_list["主调度台"].top_bar.object.addItem(
|
||||
(
|
||||
f"实例 - {info['Type']}"
|
||||
if info["Config"].get(info["Config"].MaaSet_Name) == ""
|
||||
else f"实例 - {info['Type']} - {info["Config"].get(info["Config"].MaaSet_Name)}"
|
||||
),
|
||||
userData=name,
|
||||
)
|
||||
|
||||
if len(Config.queue_dict) == 1:
|
||||
self.script_list["主调度台"].top_bar.object.setCurrentIndex(0)
|
||||
elif len(Config.member_dict) == 1:
|
||||
self.script_list["主调度台"].top_bar.object.setCurrentIndex(
|
||||
len(Config.queue_dict)
|
||||
)
|
||||
else:
|
||||
self.script_list["主调度台"].top_bar.object.setCurrentIndex(-1)
|
||||
|
||||
self.script_list["主调度台"].top_bar.mode.clear()
|
||||
self.script_list["主调度台"].top_bar.mode.addItems(["自动代理", "人工排查"])
|
||||
self.script_list["主调度台"].top_bar.mode.setCurrentIndex(0)
|
||||
|
||||
def update_power_sign(self) -> None:
|
||||
"""更新电源设置"""
|
||||
|
||||
mode_book = {
|
||||
"NoAction": "无动作",
|
||||
"KillSelf": "退出软件",
|
||||
"Sleep": "睡眠",
|
||||
"Hibernate": "休眠",
|
||||
"Shutdown": "关机",
|
||||
}
|
||||
self.power_combox.currentIndexChanged.disconnect()
|
||||
self.power_combox.setCurrentText(mode_book[Config.power_sign])
|
||||
self.power_combox.currentIndexChanged.connect(self.set_power_sign)
|
||||
|
||||
def set_power_sign(self) -> None:
|
||||
"""设置所有任务完成后动作"""
|
||||
|
||||
if not Config.running_list:
|
||||
|
||||
self.power_combox.currentIndexChanged.disconnect()
|
||||
self.power_combox.setCurrentText("无动作")
|
||||
self.power_combox.currentIndexChanged.connect(self.set_power_sign)
|
||||
logger.warning("没有正在运行的任务,无法设置任务完成后动作")
|
||||
MainInfoBar.push_info_bar(
|
||||
"warning",
|
||||
"没有正在运行的任务",
|
||||
"无法设置任务完成后动作",
|
||||
5000,
|
||||
)
|
||||
|
||||
else:
|
||||
|
||||
Config.set_power_sign(self.power_combox.currentData())
|
||||
|
||||
def start_multi_task(self) -> None:
|
||||
"""开始任务"""
|
||||
|
||||
# 获取所有可用的队列和实例
|
||||
text_list = []
|
||||
data_list = []
|
||||
for name, info in Config.queue_dict.items():
|
||||
if name in Config.running_list:
|
||||
continue
|
||||
text_list.append(
|
||||
"队列"
|
||||
if info["Config"].get(info["Config"].queueSet_Name) == ""
|
||||
else f"队列 - {info["Config"].get(info["Config"].queueSet_Name)}"
|
||||
)
|
||||
data_list.append(name)
|
||||
|
||||
for name, info in Config.member_dict.items():
|
||||
if name in Config.running_list:
|
||||
continue
|
||||
text_list.append(
|
||||
f"实例 - {info['Type']}"
|
||||
if info["Config"].get(info["Config"].MaaSet_Name) == ""
|
||||
else f"实例 - {info['Type']} - {info["Config"].get(info["Config"].MaaSet_Name)}"
|
||||
)
|
||||
data_list.append(name)
|
||||
|
||||
choice = ComboBoxMessageBox(
|
||||
self.window(),
|
||||
"选择一个对象以添加相应多开任务",
|
||||
["选择调度对象"],
|
||||
[text_list],
|
||||
[data_list],
|
||||
)
|
||||
|
||||
if choice.exec() and choice.input[0].currentIndex() != -1:
|
||||
|
||||
if choice.input[0].currentData() in Config.running_list:
|
||||
logger.warning(f"任务已存在:{choice.input[0].currentData()}")
|
||||
MainInfoBar.push_info_bar(
|
||||
"warning", "任务已存在", choice.input[0].currentData(), 5000
|
||||
)
|
||||
return None
|
||||
|
||||
if "调度队列" in choice.input[0].currentData():
|
||||
|
||||
logger.info(f"用户添加任务:{choice.input[0].currentData()}")
|
||||
TaskManager.add_task(
|
||||
"自动代理_新调度台",
|
||||
choice.input[0].currentData(),
|
||||
Config.queue_dict[choice.input[0].currentData()]["Config"].toDict(),
|
||||
)
|
||||
|
||||
elif "脚本" in choice.input[0].currentData():
|
||||
|
||||
if Config.member_dict[choice.input[0].currentData()]["Type"] == "Maa":
|
||||
|
||||
logger.info(f"用户添加任务:{choice.input[0].currentData()}")
|
||||
TaskManager.add_task(
|
||||
"自动代理_新调度台",
|
||||
f"自定义队列 - {choice.input[0].currentData()}",
|
||||
{"Queue": {"Member_1": choice.input[0].currentData()}},
|
||||
)
|
||||
|
||||
class DispatchBox(QWidget):
|
||||
|
||||
def __init__(self, name: str, parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
self.setObjectName(name)
|
||||
|
||||
self.top_bar = self.DispatchTopBar(self, name)
|
||||
self.info = self.DispatchInfoCard(self)
|
||||
|
||||
content_widget = QWidget()
|
||||
content_layout = QVBoxLayout(content_widget)
|
||||
content_layout.setContentsMargins(0, 0, 0, 0)
|
||||
content_layout.addWidget(self.top_bar)
|
||||
content_layout.addWidget(self.info)
|
||||
|
||||
scrollArea = ScrollArea()
|
||||
scrollArea.setWidgetResizable(True)
|
||||
scrollArea.setContentsMargins(0, 0, 0, 0)
|
||||
scrollArea.setStyleSheet("background: transparent; border: none;")
|
||||
scrollArea.setWidget(content_widget)
|
||||
|
||||
layout = QVBoxLayout(self)
|
||||
layout.addWidget(scrollArea)
|
||||
|
||||
class DispatchTopBar(CardWidget):
|
||||
|
||||
def __init__(self, parent=None, name: str = None):
|
||||
super().__init__(parent)
|
||||
|
||||
Layout = QHBoxLayout(self)
|
||||
|
||||
if name == "主调度台":
|
||||
|
||||
self.Lable = SubtitleLabel("", self)
|
||||
self.Lable.hide()
|
||||
self.object = ComboBox()
|
||||
self.object.setPlaceholderText("请选择调度对象")
|
||||
self.mode = ComboBox()
|
||||
self.mode.setPlaceholderText("请选择调度模式")
|
||||
|
||||
self.main_button = PushButton("开始任务")
|
||||
self.main_button.clicked.connect(self.start_main_task)
|
||||
|
||||
Layout.addWidget(self.Lable)
|
||||
Layout.addWidget(self.object)
|
||||
Layout.addWidget(self.mode)
|
||||
Layout.addStretch(1)
|
||||
Layout.addWidget(self.main_button)
|
||||
|
||||
else:
|
||||
|
||||
self.Lable = SubtitleLabel(name, self)
|
||||
self.main_button = PushButton("中止任务")
|
||||
|
||||
Layout.addWidget(self.Lable)
|
||||
Layout.addStretch(1)
|
||||
Layout.addWidget(self.main_button)
|
||||
|
||||
def start_main_task(self):
|
||||
"""开始任务"""
|
||||
|
||||
if self.object.currentIndex() == -1:
|
||||
logger.warning("未选择调度对象")
|
||||
MainInfoBar.push_info_bar(
|
||||
"warning", "未选择调度对象", "请选择后再开始任务", 5000
|
||||
)
|
||||
return None
|
||||
|
||||
if self.mode.currentIndex() == -1:
|
||||
logger.warning("未选择调度模式")
|
||||
MainInfoBar.push_info_bar(
|
||||
"warning", "未选择调度模式", "请选择后再开始任务", 5000
|
||||
)
|
||||
return None
|
||||
|
||||
if self.object.currentData() in Config.running_list:
|
||||
logger.warning(f"任务已存在:{self.object.currentData()}")
|
||||
MainInfoBar.push_info_bar(
|
||||
"warning", "任务已存在", self.object.currentData(), 5000
|
||||
)
|
||||
return None
|
||||
|
||||
if "调度队列" in self.object.currentData():
|
||||
|
||||
logger.info(f"用户添加任务:{self.object.currentData()}")
|
||||
TaskManager.add_task(
|
||||
f"{self.mode.currentText()}_主调度台",
|
||||
self.object.currentData(),
|
||||
Config.queue_dict[self.object.currentData()]["Config"].toDict(),
|
||||
)
|
||||
|
||||
elif "脚本" in self.object.currentData():
|
||||
|
||||
if Config.member_dict[self.object.currentData()]["Type"] == "Maa":
|
||||
|
||||
logger.info(f"用户添加任务:{self.object.currentData()}")
|
||||
TaskManager.add_task(
|
||||
f"{self.mode.currentText()}_主调度台",
|
||||
"自定义队列",
|
||||
{"Queue": {"Member_1": self.object.currentData()}},
|
||||
)
|
||||
|
||||
class DispatchInfoCard(HeaderCardWidget):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
self.setTitle("调度信息")
|
||||
|
||||
self.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[StatefulItemCard] = []
|
||||
|
||||
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(StatefulItemCard(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[StatefulItemCard] = []
|
||||
|
||||
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(StatefulItemCard(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()
|
||||
608
app/ui/downloader.py
Normal file
@@ -0,0 +1,608 @@
|
||||
# AUTO_MAA:A MAA Multi Account Management and Automation Tool
|
||||
# Copyright © 2024-2025 DLmaster361
|
||||
|
||||
# This file is part of AUTO_MAA.
|
||||
|
||||
# AUTO_MAA is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License,
|
||||
# or (at your option) any later version.
|
||||
|
||||
# AUTO_MAA is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
|
||||
# the GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# Contact: DLmaster_361@163.com
|
||||
|
||||
"""
|
||||
AUTO_MAA
|
||||
AUTO_MAA更新器
|
||||
v4.3
|
||||
作者:DLmaster_361
|
||||
"""
|
||||
|
||||
from loguru import logger
|
||||
import zipfile
|
||||
import requests
|
||||
import subprocess
|
||||
import time
|
||||
import psutil
|
||||
from functools import partial
|
||||
from pathlib import Path
|
||||
|
||||
from PySide6.QtWidgets import QDialog, QVBoxLayout
|
||||
from qfluentwidgets import (
|
||||
ProgressBar,
|
||||
IndeterminateProgressBar,
|
||||
BodyLabel,
|
||||
setTheme,
|
||||
Theme,
|
||||
)
|
||||
from PySide6.QtGui import QCloseEvent
|
||||
from PySide6.QtCore import QThread, Signal, QTimer, QEventLoop
|
||||
|
||||
from typing import List, Dict, Union
|
||||
|
||||
|
||||
def version_text(version_numb: list) -> str:
|
||||
"""将版本号列表转为可读的文本信息"""
|
||||
|
||||
while len(version_numb) < 4:
|
||||
version_numb.append(0)
|
||||
|
||||
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 DownloadProcess(QThread):
|
||||
"""分段下载子线程"""
|
||||
|
||||
progress = Signal(int)
|
||||
accomplish = Signal(float)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
url: str,
|
||||
start_byte: int,
|
||||
end_byte: int,
|
||||
download_path: Path,
|
||||
check_times: int = -1,
|
||||
) -> None:
|
||||
super(DownloadProcess, self).__init__()
|
||||
|
||||
self.setObjectName(f"DownloadProcess-{url}-{start_byte}-{end_byte}")
|
||||
|
||||
self.url = url
|
||||
self.start_byte = start_byte
|
||||
self.end_byte = end_byte
|
||||
self.download_path = download_path
|
||||
self.check_times = check_times
|
||||
|
||||
@logger.catch
|
||||
def run(self) -> None:
|
||||
|
||||
# 清理可能存在的临时文件
|
||||
if self.download_path.exists():
|
||||
self.download_path.unlink()
|
||||
|
||||
headers = {"Range": f"bytes={self.start_byte}-{self.end_byte}"}
|
||||
|
||||
while not self.isInterruptionRequested() and self.check_times != 0:
|
||||
|
||||
try:
|
||||
|
||||
start_time = time.time()
|
||||
|
||||
response = requests.get(
|
||||
self.url, headers=headers, timeout=10, stream=True
|
||||
)
|
||||
|
||||
if response.status_code != 206:
|
||||
|
||||
if self.check_times != -1:
|
||||
self.check_times -= 1
|
||||
|
||||
time.sleep(1)
|
||||
continue
|
||||
|
||||
downloaded_size = 0
|
||||
with self.download_path.open(mode="wb") as f:
|
||||
|
||||
for chunk in response.iter_content(chunk_size=8192):
|
||||
|
||||
if self.isInterruptionRequested():
|
||||
break
|
||||
|
||||
f.write(chunk)
|
||||
downloaded_size += len(chunk)
|
||||
|
||||
self.progress.emit(downloaded_size)
|
||||
|
||||
if self.isInterruptionRequested():
|
||||
|
||||
if self.download_path.exists():
|
||||
self.download_path.unlink()
|
||||
self.accomplish.emit(0)
|
||||
|
||||
else:
|
||||
|
||||
self.accomplish.emit(time.time() - start_time)
|
||||
|
||||
break
|
||||
|
||||
except Exception as e:
|
||||
|
||||
if self.check_times != -1:
|
||||
self.check_times -= 1
|
||||
time.sleep(1)
|
||||
|
||||
else:
|
||||
|
||||
if self.download_path.exists():
|
||||
self.download_path.unlink()
|
||||
self.accomplish.emit(0)
|
||||
|
||||
|
||||
class ZipExtractProcess(QThread):
|
||||
"""解压子线程"""
|
||||
|
||||
info = Signal(str)
|
||||
accomplish = Signal()
|
||||
|
||||
def __init__(self, name: str, app_path: Path, download_path: Path) -> None:
|
||||
super(ZipExtractProcess, self).__init__()
|
||||
|
||||
self.setObjectName(f"ZipExtractProcess-{name}")
|
||||
|
||||
self.name = name
|
||||
self.app_path = app_path
|
||||
self.download_path = download_path
|
||||
|
||||
@logger.catch
|
||||
def run(self) -> None:
|
||||
|
||||
try:
|
||||
|
||||
while True:
|
||||
|
||||
if self.isInterruptionRequested():
|
||||
self.download_path.unlink()
|
||||
return None
|
||||
try:
|
||||
with zipfile.ZipFile(self.download_path, "r") as zip_ref:
|
||||
zip_ref.extractall(self.app_path)
|
||||
self.accomplish.emit()
|
||||
break
|
||||
except PermissionError:
|
||||
if self.name == "AUTO_MAA":
|
||||
self.info.emit(f"解压出错:AUTO_MAA正在运行,正在尝试将其关闭")
|
||||
self.kill_process(self.app_path / "AUTO_MAA.exe")
|
||||
else:
|
||||
self.info.emit(f"解压出错:{self.name}正在运行,正在等待其关闭")
|
||||
time.sleep(1)
|
||||
|
||||
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
|
||||
|
||||
def kill_process(self, path: Path) -> None:
|
||||
"""根据路径中止进程"""
|
||||
|
||||
for pid in self.search_pids(path):
|
||||
killprocess = subprocess.Popen(
|
||||
f"taskkill /F /PID {pid}",
|
||||
shell=True,
|
||||
creationflags=subprocess.CREATE_NO_WINDOW,
|
||||
)
|
||||
killprocess.wait()
|
||||
|
||||
def search_pids(self, path: Path) -> list:
|
||||
"""根据路径查找进程PID"""
|
||||
|
||||
pids = []
|
||||
for proc in psutil.process_iter(["pid", "exe"]):
|
||||
try:
|
||||
if proc.info["exe"] and proc.info["exe"].lower() == str(path).lower():
|
||||
pids.append(proc.info["pid"])
|
||||
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
|
||||
# 进程可能在此期间已结束或无法访问,忽略这些异常
|
||||
pass
|
||||
return pids
|
||||
|
||||
|
||||
class DownloadManager(QDialog):
|
||||
"""下载管理器"""
|
||||
|
||||
speed_test_accomplish = Signal()
|
||||
download_accomplish = Signal()
|
||||
download_process_clear = Signal()
|
||||
|
||||
isInterruptionRequested = False
|
||||
|
||||
def __init__(self, app_path: Path, name: str, version: list, config: dict) -> None:
|
||||
super().__init__()
|
||||
|
||||
self.app_path = app_path
|
||||
self.name = name
|
||||
self.version = version
|
||||
self.config = config
|
||||
self.download_path = app_path / "DOWNLOAD_TEMP.zip" # 临时下载文件的路径
|
||||
self.download_process_dict: Dict[str, DownloadProcess] = {}
|
||||
self.timer_dict: Dict[str, QTimer] = {}
|
||||
self.if_speed_test_accomplish = False
|
||||
|
||||
self.resize(700, 70)
|
||||
|
||||
setTheme(Theme.AUTO, lazy=True)
|
||||
|
||||
# 创建垂直布局
|
||||
self.Layout = QVBoxLayout(self)
|
||||
|
||||
self.info = BodyLabel("正在初始化", self)
|
||||
self.progress_1 = IndeterminateProgressBar(self)
|
||||
self.progress_2 = ProgressBar(self)
|
||||
|
||||
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)
|
||||
|
||||
def run(self) -> None:
|
||||
|
||||
if self.name == "AUTO_MAA":
|
||||
if self.config["mode"] == "Proxy":
|
||||
self.test_speed_task1()
|
||||
self.speed_test_accomplish.connect(self.download_task1)
|
||||
elif self.config["mode"] == "MirrorChyan":
|
||||
self.download_task1()
|
||||
elif self.config["mode"] == "MirrorChyan":
|
||||
self.download_task1()
|
||||
|
||||
def get_download_url(self, mode: str) -> Union[str, Dict[str, str]]:
|
||||
"""获取下载链接"""
|
||||
|
||||
url_dict = {}
|
||||
|
||||
if mode == "测速":
|
||||
|
||||
url_dict["GitHub站"] = (
|
||||
f"https://github.com/DLmaster361/AUTO_MAA/releases/download/{version_text(self.version)}/AUTO_MAA_{version_text(self.version)}.zip"
|
||||
)
|
||||
url_dict["官方镜像站"] = (
|
||||
f"https://gitee.com/DLmaster_361/AUTO_MAA/releases/download/{version_text(self.version)}/AUTO_MAA_{version_text(self.version)}.zip"
|
||||
)
|
||||
for name, download_url_head in self.config["download_dict"].items():
|
||||
url_dict[name] = (
|
||||
f"{download_url_head}AUTO_MAA_{version_text(self.version)}.zip"
|
||||
)
|
||||
for proxy_url in self.config["proxy_list"]:
|
||||
url_dict[proxy_url] = (
|
||||
f"{proxy_url}https://github.com/DLmaster361/AUTO_MAA/releases/download/{version_text(self.version)}/AUTO_MAA_{version_text(self.version)}.zip"
|
||||
)
|
||||
return url_dict
|
||||
|
||||
elif mode == "下载":
|
||||
|
||||
if self.name == "AUTO_MAA":
|
||||
|
||||
if self.config["mode"] == "Proxy":
|
||||
|
||||
if "selected" in self.config:
|
||||
selected_url = self.config["selected"]
|
||||
elif "speed_result" in self.config:
|
||||
selected_url = max(
|
||||
self.config["speed_result"],
|
||||
key=self.config["speed_result"].get,
|
||||
)
|
||||
|
||||
if selected_url == "GitHub站":
|
||||
return f"https://github.com/DLmaster361/AUTO_MAA/releases/download/{version_text(self.version)}/AUTO_MAA_{version_text(self.version)}.zip"
|
||||
elif selected_url == "官方镜像站":
|
||||
return f"https://gitee.com/DLmaster_361/AUTO_MAA/releases/download/{version_text(self.version)}/AUTO_MAA_{version_text(self.version)}.zip"
|
||||
elif selected_url in self.config["download_dict"].keys():
|
||||
return f"{self.config["download_dict"][selected_url]}AUTO_MAA_{version_text(self.version)}.zip"
|
||||
else:
|
||||
return f"{selected_url}https://github.com/DLmaster361/AUTO_MAA/releases/download/{version_text(self.version)}/AUTO_MAA_{version_text(self.version)}.zip"
|
||||
|
||||
elif self.config["mode"] == "MirrorChyan":
|
||||
|
||||
with requests.get(
|
||||
self.config["url"],
|
||||
allow_redirects=True,
|
||||
timeout=10,
|
||||
stream=True,
|
||||
) as response:
|
||||
if response.status_code == 200:
|
||||
return response.url
|
||||
|
||||
elif self.config["mode"] == "MirrorChyan":
|
||||
|
||||
with requests.get(
|
||||
self.config["url"], allow_redirects=True, timeout=10, stream=True
|
||||
) as response:
|
||||
if response.status_code == 200:
|
||||
return response.url
|
||||
|
||||
def test_speed_task1(self) -> None:
|
||||
|
||||
if self.isInterruptionRequested:
|
||||
return None
|
||||
|
||||
url_dict = self.get_download_url("测速")
|
||||
self.test_speed_result: Dict[str, float] = {}
|
||||
|
||||
for name, url in url_dict.items():
|
||||
|
||||
if self.isInterruptionRequested:
|
||||
break
|
||||
|
||||
# 创建测速线程,下载4MB文件以测试下载速度
|
||||
self.download_process_dict[name] = DownloadProcess(
|
||||
url,
|
||||
0,
|
||||
4194304,
|
||||
self.app_path / f"{name.replace('/','').replace(':','')}.zip",
|
||||
10,
|
||||
)
|
||||
self.test_speed_result[name] = -1
|
||||
self.download_process_dict[name].accomplish.connect(
|
||||
partial(self.test_speed_task2, name)
|
||||
)
|
||||
|
||||
self.download_process_dict[name].start()
|
||||
timer = QTimer(self)
|
||||
timer.setSingleShot(True)
|
||||
timer.timeout.connect(partial(self.kill_speed_test, name))
|
||||
timer.start(30000)
|
||||
self.timer_dict[name] = timer
|
||||
|
||||
self.update_info("正在测速,预计用时30秒")
|
||||
self.update_progress(0, 1, 0)
|
||||
|
||||
def kill_speed_test(self, name: str) -> None:
|
||||
|
||||
if name in self.download_process_dict:
|
||||
self.download_process_dict[name].requestInterruption()
|
||||
|
||||
def test_speed_task2(self, name: str, t: float) -> None:
|
||||
|
||||
# 计算下载速度
|
||||
if self.isInterruptionRequested:
|
||||
self.update_info(f"已中止测速进程:{name}")
|
||||
self.test_speed_result[name] = 0
|
||||
elif t != 0:
|
||||
self.update_info(f"{name}:{ 4 / t:.2f} MB/s")
|
||||
self.test_speed_result[name] = 4 / t
|
||||
else:
|
||||
self.update_info(f"{name}:{ 0:.2f} MB/s")
|
||||
self.test_speed_result[name] = 0
|
||||
self.update_progress(
|
||||
0,
|
||||
len(self.test_speed_result),
|
||||
sum(1 for speed in self.test_speed_result.values() if speed != -1),
|
||||
)
|
||||
|
||||
# 删除临时文件
|
||||
if (self.app_path / f"{name.replace('/','').replace(':','')}.zip").exists():
|
||||
(self.app_path / f"{name.replace('/','').replace(':','')}.zip").unlink()
|
||||
|
||||
# 清理下载线程
|
||||
self.timer_dict[name].stop()
|
||||
self.timer_dict[name].deleteLater()
|
||||
self.timer_dict.pop(name)
|
||||
self.download_process_dict[name].requestInterruption()
|
||||
self.download_process_dict[name].quit()
|
||||
self.download_process_dict[name].wait()
|
||||
self.download_process_dict[name].deleteLater()
|
||||
self.download_process_dict.pop(name)
|
||||
if not self.download_process_dict:
|
||||
self.download_process_clear.emit()
|
||||
|
||||
# 当有速度大于1 MB/s的链接或存在3个即以上链接测速完成时,停止其他测速
|
||||
if not self.if_speed_test_accomplish and (
|
||||
sum(1 for speed in self.test_speed_result.values() if speed > 0) >= 3
|
||||
or any(speed > 1 for speed in self.test_speed_result.values())
|
||||
):
|
||||
self.if_speed_test_accomplish = True
|
||||
for timer in self.timer_dict.values():
|
||||
timer.timeout.emit()
|
||||
|
||||
if any(speed == -1 for _, speed in self.test_speed_result.items()):
|
||||
return None
|
||||
|
||||
# 保存测速结果
|
||||
self.config["speed_result"] = self.test_speed_result
|
||||
|
||||
self.update_info("测速完成!")
|
||||
self.speed_test_accomplish.emit()
|
||||
|
||||
def download_task1(self) -> None:
|
||||
|
||||
if self.isInterruptionRequested:
|
||||
return None
|
||||
|
||||
url = self.get_download_url("下载")
|
||||
self.downloaded_size_list: List[List[int, bool]] = []
|
||||
|
||||
response = requests.head(url, timeout=10)
|
||||
|
||||
self.file_size = int(response.headers.get("content-length", 0))
|
||||
part_size = self.file_size // self.config["thread_numb"]
|
||||
self.downloaded_size = 0
|
||||
self.last_download_size = 0
|
||||
self.last_time = time.time()
|
||||
self.speed = 0
|
||||
|
||||
# 拆分下载任务,启用多线程下载
|
||||
for i in range(self.config["thread_numb"]):
|
||||
|
||||
if self.isInterruptionRequested:
|
||||
break
|
||||
|
||||
# 计算单任务下载范围
|
||||
start_byte = i * part_size
|
||||
end_byte = (
|
||||
(i + 1) * part_size - 1
|
||||
if (i != self.config["thread_numb"] - 1)
|
||||
else self.file_size - 1
|
||||
)
|
||||
|
||||
# 创建下载子线程
|
||||
self.download_process_dict[f"part{i}"] = DownloadProcess(
|
||||
url,
|
||||
start_byte,
|
||||
end_byte,
|
||||
self.download_path.with_suffix(f".part{i}"),
|
||||
1 if self.config["mode"] == "MirrorChyan" else -1,
|
||||
)
|
||||
self.downloaded_size_list.append([0, False])
|
||||
self.download_process_dict[f"part{i}"].progress.connect(
|
||||
partial(self.download_task2, i)
|
||||
)
|
||||
self.download_process_dict[f"part{i}"].accomplish.connect(
|
||||
partial(self.download_task3, i)
|
||||
)
|
||||
self.download_process_dict[f"part{i}"].start()
|
||||
|
||||
def download_task2(self, index: str, current: int) -> None:
|
||||
"""更新下载进度"""
|
||||
|
||||
self.downloaded_size_list[index][0] = current
|
||||
self.downloaded_size = sum([_[0] for _ in self.downloaded_size_list])
|
||||
self.update_progress(0, self.file_size, self.downloaded_size)
|
||||
|
||||
if time.time() - self.last_time >= 1.0:
|
||||
self.speed = (
|
||||
(self.downloaded_size - self.last_download_size)
|
||||
/ (time.time() - self.last_time)
|
||||
/ 1024
|
||||
)
|
||||
self.last_download_size = self.downloaded_size
|
||||
self.last_time = time.time()
|
||||
|
||||
if self.speed >= 1024:
|
||||
self.update_info(
|
||||
f"正在下载:{self.name} 已下载:{self.downloaded_size / 1048576:.2f}/{self.file_size / 1048576:.2f} MB ({self.downloaded_size / self.file_size * 100:.2f}%) 下载速度:{self.speed / 1024:.2f} MB/s",
|
||||
)
|
||||
else:
|
||||
self.update_info(
|
||||
f"正在下载:{self.name} 已下载:{self.downloaded_size / 1048576:.2f}/{self.file_size / 1048576:.2f} MB ({self.downloaded_size / self.file_size * 100:.2f}%) 下载速度:{self.speed:.2f} KB/s",
|
||||
)
|
||||
|
||||
def download_task3(self, index: str, t: float) -> None:
|
||||
|
||||
# 标记下载线程完成
|
||||
self.downloaded_size_list[index][1] = True
|
||||
|
||||
# 清理下载线程
|
||||
self.download_process_dict[f"part{index}"].requestInterruption()
|
||||
self.download_process_dict[f"part{index}"].quit()
|
||||
self.download_process_dict[f"part{index}"].wait()
|
||||
self.download_process_dict[f"part{index}"].deleteLater()
|
||||
self.download_process_dict.pop(f"part{index}")
|
||||
if not self.download_process_dict:
|
||||
self.download_process_clear.emit()
|
||||
|
||||
if (
|
||||
any([not _[1] for _ in self.downloaded_size_list])
|
||||
or self.isInterruptionRequested
|
||||
):
|
||||
return None
|
||||
|
||||
# 合并下载的分段文件
|
||||
with self.download_path.open(mode="wb") as outfile:
|
||||
for i in range(self.config["thread_numb"]):
|
||||
with self.download_path.with_suffix(f".part{i}").open(
|
||||
mode="rb"
|
||||
) as infile:
|
||||
outfile.write(infile.read())
|
||||
self.download_path.with_suffix(f".part{i}").unlink()
|
||||
|
||||
self.update_info("正在解压更新文件")
|
||||
self.update_progress(0, 0, 0)
|
||||
|
||||
# 创建解压线程
|
||||
self.zip_extract = ZipExtractProcess(
|
||||
self.name, self.app_path, self.download_path
|
||||
)
|
||||
self.zip_loop = QEventLoop()
|
||||
self.zip_extract.info.connect(self.update_info)
|
||||
self.zip_extract.accomplish.connect(self.zip_loop.quit)
|
||||
self.zip_extract.start()
|
||||
self.zip_loop.exec()
|
||||
|
||||
self.update_info("正在删除临时文件")
|
||||
self.update_progress(0, 0, 0)
|
||||
if (self.app_path / "changes.json").exists():
|
||||
(self.app_path / "changes.json").unlink()
|
||||
if self.download_path.exists():
|
||||
self.download_path.unlink()
|
||||
|
||||
# 下载完成后打开对应程序
|
||||
if not self.isInterruptionRequested and self.name == "MAA":
|
||||
subprocess.Popen(
|
||||
[self.app_path / "MAA.exe"],
|
||||
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP
|
||||
| subprocess.DETACHED_PROCESS
|
||||
| subprocess.CREATE_NO_WINDOW,
|
||||
)
|
||||
if self.name == "AUTO_MAA":
|
||||
self.update_info(f"即将安装{self.name}")
|
||||
else:
|
||||
self.update_info(f"{self.name}下载成功!")
|
||||
self.update_progress(0, 100, 100)
|
||||
self.download_accomplish.emit()
|
||||
|
||||
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)
|
||||
|
||||
def requestInterruption(self) -> None:
|
||||
|
||||
self.isInterruptionRequested = True
|
||||
|
||||
if hasattr(self, "zip_extract") and self.zip_extract:
|
||||
self.zip_extract.requestInterruption()
|
||||
|
||||
if hasattr(self, "zip_loop") and self.zip_loop:
|
||||
self.zip_loop.quit()
|
||||
|
||||
for process in self.download_process_dict.values():
|
||||
process.requestInterruption()
|
||||
|
||||
if self.download_process_dict:
|
||||
loop = QEventLoop()
|
||||
self.download_process_clear.connect(loop.quit)
|
||||
loop.exec()
|
||||
|
||||
def closeEvent(self, event: QCloseEvent):
|
||||
"""清理残余进程"""
|
||||
|
||||
self.requestInterruption()
|
||||
|
||||
event.accept()
|
||||
393
app/ui/history.py
Normal file
@@ -0,0 +1,393 @@
|
||||
# AUTO_MAA:A MAA Multi Account Management and Automation Tool
|
||||
# Copyright © 2024-2025 DLmaster361
|
||||
|
||||
# This file is part of AUTO_MAA.
|
||||
|
||||
# AUTO_MAA is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License,
|
||||
# or (at your option) any later version.
|
||||
|
||||
# AUTO_MAA is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
|
||||
# the GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# Contact: DLmaster_361@163.com
|
||||
|
||||
"""
|
||||
AUTO_MAA
|
||||
AUTO_MAA历史记录界面
|
||||
v4.3
|
||||
作者:DLmaster_361
|
||||
"""
|
||||
|
||||
from loguru import logger
|
||||
from PySide6.QtWidgets import (
|
||||
QWidget,
|
||||
QVBoxLayout,
|
||||
QHBoxLayout,
|
||||
)
|
||||
from qfluentwidgets import (
|
||||
ScrollArea,
|
||||
FluentIcon,
|
||||
HeaderCardWidget,
|
||||
PushButton,
|
||||
TextBrowser,
|
||||
CardWidget,
|
||||
ComboBox,
|
||||
ZhDatePicker,
|
||||
SubtitleLabel,
|
||||
)
|
||||
from PySide6.QtCore import Signal, QDate
|
||||
import os
|
||||
import subprocess
|
||||
from datetime import datetime, timedelta
|
||||
from functools import partial
|
||||
from pathlib import Path
|
||||
from typing import Union, List, Dict
|
||||
|
||||
|
||||
from app.core import Config, SoundPlayer
|
||||
from .Widget import StatefulItemCard, QuantifiedItemCard, QuickExpandGroupCard
|
||||
|
||||
|
||||
class History(QWidget):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setObjectName("历史记录")
|
||||
|
||||
self.history_top_bar = self.HistoryTopBar(self)
|
||||
self.history_top_bar.search_history.connect(self.reload_history)
|
||||
|
||||
content_widget = QWidget()
|
||||
self.content_layout = QVBoxLayout(content_widget)
|
||||
self.content_layout.setContentsMargins(0, 0, 11, 0)
|
||||
|
||||
scrollArea = ScrollArea()
|
||||
scrollArea.setWidgetResizable(True)
|
||||
scrollArea.setContentsMargins(0, 0, 0, 0)
|
||||
scrollArea.setStyleSheet("background: transparent; border: none;")
|
||||
scrollArea.setWidget(content_widget)
|
||||
|
||||
layout = QVBoxLayout(self)
|
||||
layout.addWidget(self.history_top_bar)
|
||||
layout.addWidget(scrollArea)
|
||||
|
||||
self.history_card_list = []
|
||||
|
||||
def reload_history(self, mode: str, start_date: QDate, end_date: QDate) -> None:
|
||||
"""加载历史记录界面"""
|
||||
|
||||
SoundPlayer.play("历史记录查询")
|
||||
|
||||
while self.content_layout.count() > 0:
|
||||
item = self.content_layout.takeAt(0)
|
||||
if item.spacerItem():
|
||||
self.content_layout.removeItem(item.spacerItem())
|
||||
elif item.widget():
|
||||
item.widget().deleteLater()
|
||||
|
||||
self.history_card_list = []
|
||||
|
||||
history_dict = Config.search_history(
|
||||
mode,
|
||||
datetime(start_date.year(), start_date.month(), start_date.day()),
|
||||
datetime(end_date.year(), end_date.month(), end_date.day()),
|
||||
)
|
||||
|
||||
for date, user in history_dict.items():
|
||||
|
||||
self.history_card_list.append(self.HistoryCard(mode, date, user, self))
|
||||
self.content_layout.addWidget(self.history_card_list[-1])
|
||||
|
||||
self.content_layout.addStretch(1)
|
||||
|
||||
class HistoryTopBar(CardWidget):
|
||||
"""历史记录顶部工具栏"""
|
||||
|
||||
search_history = Signal(str, QDate, QDate)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
Layout = QHBoxLayout(self)
|
||||
|
||||
self.lable_1 = SubtitleLabel("查询范围:")
|
||||
self.start_date = ZhDatePicker()
|
||||
self.start_date.setDate(QDate(2019, 5, 1))
|
||||
self.lable_2 = SubtitleLabel("→")
|
||||
self.end_date = ZhDatePicker()
|
||||
server_date = Config.server_date()
|
||||
self.end_date.setDate(
|
||||
QDate(server_date.year, server_date.month, server_date.day)
|
||||
)
|
||||
self.mode = ComboBox()
|
||||
self.mode.setPlaceholderText("请选择查询模式")
|
||||
self.mode.addItems(["按日合并", "按周合并", "按月合并"])
|
||||
|
||||
self.select_month = PushButton(FluentIcon.TAG, "最近一月")
|
||||
self.select_week = PushButton(FluentIcon.TAG, "最近一周")
|
||||
self.search = PushButton(FluentIcon.SEARCH, "查询")
|
||||
self.select_month.clicked.connect(lambda: self.select_date("month"))
|
||||
self.select_week.clicked.connect(lambda: self.select_date("week"))
|
||||
self.search.clicked.connect(
|
||||
lambda: self.search_history.emit(
|
||||
self.mode.currentText(),
|
||||
self.start_date.getDate(),
|
||||
self.end_date.getDate(),
|
||||
)
|
||||
)
|
||||
|
||||
Layout.addWidget(self.lable_1)
|
||||
Layout.addWidget(self.start_date)
|
||||
Layout.addWidget(self.lable_2)
|
||||
Layout.addWidget(self.end_date)
|
||||
Layout.addWidget(self.mode)
|
||||
Layout.addStretch(1)
|
||||
Layout.addWidget(self.select_month)
|
||||
Layout.addWidget(self.select_week)
|
||||
Layout.addWidget(self.search)
|
||||
|
||||
def select_date(self, date: str) -> None:
|
||||
"""选中最近一段时间并启动查询"""
|
||||
|
||||
server_date = Config.server_date()
|
||||
if date == "week":
|
||||
begin_date = server_date - timedelta(weeks=1)
|
||||
elif date == "month":
|
||||
begin_date = server_date - timedelta(days=30)
|
||||
|
||||
self.start_date.setDate(
|
||||
QDate(begin_date.year, begin_date.month, begin_date.day)
|
||||
)
|
||||
self.end_date.setDate(
|
||||
QDate(server_date.year, server_date.month, server_date.day)
|
||||
)
|
||||
|
||||
self.search.clicked.emit()
|
||||
|
||||
class HistoryCard(QuickExpandGroupCard):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
mode: str,
|
||||
date: str,
|
||||
user: Union[List[Path], Dict[str, List[Path]]],
|
||||
parent=None,
|
||||
):
|
||||
super().__init__(
|
||||
FluentIcon.HISTORY, date, f"{date}的历史运行记录与统计信息", parent
|
||||
)
|
||||
|
||||
widget = QWidget()
|
||||
Layout = QVBoxLayout(widget)
|
||||
self.viewLayout.setContentsMargins(0, 0, 0, 0)
|
||||
self.viewLayout.setSpacing(0)
|
||||
self.addGroupWidget(widget)
|
||||
|
||||
self.user_history_card_list = []
|
||||
|
||||
if mode == "按日合并":
|
||||
|
||||
for user_path in user:
|
||||
self.user_history_card_list.append(
|
||||
self.UserHistoryCard(mode, user_path.stem, user_path, self)
|
||||
)
|
||||
Layout.addWidget(self.user_history_card_list[-1])
|
||||
|
||||
elif mode in ["按周合并", "按月合并"]:
|
||||
|
||||
for user, info in user.items():
|
||||
self.user_history_card_list.append(
|
||||
self.UserHistoryCard(mode, user, info, self)
|
||||
)
|
||||
Layout.addWidget(self.user_history_card_list[-1])
|
||||
|
||||
class UserHistoryCard(HeaderCardWidget):
|
||||
"""用户历史记录卡片"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
mode: str,
|
||||
name: str,
|
||||
user_history: Union[Path, List[Path]],
|
||||
parent=None,
|
||||
):
|
||||
super().__init__(parent)
|
||||
|
||||
self.setTitle(name)
|
||||
|
||||
if mode == "按日合并":
|
||||
|
||||
self.user_history_path = user_history
|
||||
self.main_history = Config.load_maa_logs("总览", user_history)
|
||||
|
||||
self.index_card = self.IndexCard(
|
||||
self.main_history["条目索引"], self
|
||||
)
|
||||
self.index_card.index_changed.connect(self.update_info)
|
||||
self.viewLayout.addWidget(self.index_card)
|
||||
|
||||
elif mode in ["按周合并", "按月合并"]:
|
||||
|
||||
history = Config.merge_maa_logs("指定项", user_history)
|
||||
|
||||
self.main_history = {}
|
||||
self.main_history["统计数据"] = {
|
||||
"公招统计": list(history["recruit_statistics"].items())
|
||||
}
|
||||
|
||||
for game_id, drops in history["drop_statistics"].items():
|
||||
self.main_history["统计数据"][f"掉落统计:{game_id}"] = list(
|
||||
drops.items()
|
||||
)
|
||||
|
||||
self.statistics_card = QHBoxLayout()
|
||||
self.log_card = self.LogCard(self)
|
||||
|
||||
self.viewLayout.addLayout(self.statistics_card)
|
||||
self.viewLayout.addWidget(self.log_card)
|
||||
self.viewLayout.setContentsMargins(0, 0, 0, 0)
|
||||
self.viewLayout.setSpacing(0)
|
||||
self.viewLayout.setStretch(0, 1)
|
||||
self.viewLayout.setStretch(2, 4)
|
||||
|
||||
self.update_info("数据总览")
|
||||
|
||||
def update_info(self, index: str) -> None:
|
||||
"""更新信息"""
|
||||
|
||||
if index == "数据总览":
|
||||
|
||||
while self.statistics_card.count() > 0:
|
||||
item = self.statistics_card.takeAt(0)
|
||||
if item.spacerItem():
|
||||
self.statistics_card.removeItem(item.spacerItem())
|
||||
elif item.widget():
|
||||
item.widget().deleteLater()
|
||||
|
||||
for name, item_list in self.main_history["统计数据"].items():
|
||||
|
||||
statistics_card = self.StatisticsCard(name, item_list, self)
|
||||
self.statistics_card.addWidget(statistics_card)
|
||||
|
||||
self.log_card.hide()
|
||||
|
||||
else:
|
||||
|
||||
single_history = Config.load_maa_logs(
|
||||
"单项",
|
||||
self.user_history_path.with_suffix("")
|
||||
/ f"{index.replace(":","-")}.json",
|
||||
)
|
||||
|
||||
while self.statistics_card.count() > 0:
|
||||
item = self.statistics_card.takeAt(0)
|
||||
if item.spacerItem():
|
||||
self.statistics_card.removeItem(item.spacerItem())
|
||||
elif item.widget():
|
||||
item.widget().deleteLater()
|
||||
|
||||
for name, item_list in single_history["统计数据"].items():
|
||||
|
||||
statistics_card = self.StatisticsCard(name, item_list, self)
|
||||
self.statistics_card.addWidget(statistics_card)
|
||||
|
||||
self.log_card.text.setText(single_history["日志信息"])
|
||||
self.log_card.open_file.clicked.disconnect()
|
||||
self.log_card.open_file.clicked.connect(
|
||||
lambda: os.startfile(
|
||||
self.user_history_path.with_suffix("")
|
||||
/ f"{index.replace(":","-")}.log"
|
||||
)
|
||||
)
|
||||
self.log_card.open_dir.clicked.disconnect()
|
||||
self.log_card.open_dir.clicked.connect(
|
||||
lambda: subprocess.Popen(
|
||||
[
|
||||
"explorer",
|
||||
"/select,",
|
||||
str(
|
||||
self.user_history_path.with_suffix("")
|
||||
/ f"{index.replace(":","-")}.log"
|
||||
),
|
||||
]
|
||||
)
|
||||
)
|
||||
self.log_card.show()
|
||||
|
||||
self.viewLayout.setStretch(1, self.statistics_card.count())
|
||||
|
||||
self.setMinimumHeight(300)
|
||||
|
||||
class IndexCard(HeaderCardWidget):
|
||||
|
||||
index_changed = Signal(str)
|
||||
|
||||
def __init__(self, index_list: list, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setTitle("记录条目")
|
||||
|
||||
self.Layout = QVBoxLayout()
|
||||
self.viewLayout.addLayout(self.Layout)
|
||||
self.viewLayout.setContentsMargins(3, 0, 3, 3)
|
||||
|
||||
self.index_cards: List[StatefulItemCard] = []
|
||||
|
||||
for index in index_list:
|
||||
|
||||
self.index_cards.append(StatefulItemCard(index))
|
||||
self.index_cards[-1].clicked.connect(
|
||||
partial(self.index_changed.emit, index[0])
|
||||
)
|
||||
self.Layout.addWidget(self.index_cards[-1])
|
||||
|
||||
self.Layout.addStretch(1)
|
||||
|
||||
class StatisticsCard(HeaderCardWidget):
|
||||
|
||||
def __init__(self, name: str, item_list: list, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setTitle(name)
|
||||
|
||||
self.Layout = QVBoxLayout()
|
||||
self.viewLayout.addLayout(self.Layout)
|
||||
self.viewLayout.setContentsMargins(3, 0, 3, 3)
|
||||
|
||||
self.item_cards: List[QuantifiedItemCard] = []
|
||||
|
||||
for item in item_list:
|
||||
|
||||
self.item_cards.append(QuantifiedItemCard(item))
|
||||
self.Layout.addWidget(self.item_cards[-1])
|
||||
|
||||
if len(item_list) == 0:
|
||||
self.Layout.addWidget(QuantifiedItemCard(["暂无记录", ""]))
|
||||
|
||||
self.Layout.addStretch(1)
|
||||
|
||||
class LogCard(HeaderCardWidget):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setTitle("日志")
|
||||
|
||||
self.text = TextBrowser(self)
|
||||
self.open_file = PushButton("打开日志文件", self)
|
||||
self.open_file.clicked.connect(lambda: print("打开日志文件"))
|
||||
self.open_dir = PushButton("打开所在目录", self)
|
||||
self.open_dir.clicked.connect(lambda: print("打开所在文件"))
|
||||
|
||||
Layout = QVBoxLayout()
|
||||
h_layout = QHBoxLayout()
|
||||
h_layout.addWidget(self.open_file)
|
||||
h_layout.addWidget(self.open_dir)
|
||||
Layout.addWidget(self.text)
|
||||
Layout.addLayout(h_layout)
|
||||
self.viewLayout.setContentsMargins(3, 0, 3, 3)
|
||||
self.viewLayout.addLayout(Layout)
|
||||
410
app/ui/home.py
Normal file
@@ -0,0 +1,410 @@
|
||||
# AUTO_MAA:A MAA Multi Account Management and Automation Tool
|
||||
# Copyright © 2024-2025 DLmaster361
|
||||
|
||||
# This file is part of AUTO_MAA.
|
||||
|
||||
# AUTO_MAA is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License,
|
||||
# or (at your option) any later version.
|
||||
|
||||
# AUTO_MAA is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
|
||||
# the GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# Contact: DLmaster_361@163.com
|
||||
|
||||
"""
|
||||
AUTO_MAA
|
||||
AUTO_MAA主界面
|
||||
v4.3
|
||||
作者:DLmaster_361
|
||||
"""
|
||||
|
||||
from loguru import logger
|
||||
from PySide6.QtWidgets import (
|
||||
QWidget,
|
||||
QVBoxLayout,
|
||||
QHBoxLayout,
|
||||
QSpacerItem,
|
||||
QSizePolicy,
|
||||
QFileDialog,
|
||||
)
|
||||
from PySide6.QtCore import Qt, QSize, QUrl
|
||||
from PySide6.QtGui import QDesktopServices, QColor
|
||||
from qfluentwidgets import (
|
||||
FluentIcon,
|
||||
ScrollArea,
|
||||
SimpleCardWidget,
|
||||
PrimaryToolButton,
|
||||
TextBrowser,
|
||||
)
|
||||
import re
|
||||
import shutil
|
||||
import json
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
from app.core import Config, MainInfoBar, Network
|
||||
from .Widget import Banner, IconButton
|
||||
|
||||
|
||||
class Home(QWidget):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setObjectName("主页")
|
||||
|
||||
self.banner = Banner()
|
||||
self.banner_text = TextBrowser()
|
||||
|
||||
v_layout = QVBoxLayout(self.banner)
|
||||
v_layout.setContentsMargins(0, 0, 0, 15)
|
||||
v_layout.setSpacing(5)
|
||||
v_layout.setAlignment(Qt.AlignmentFlag.AlignTop)
|
||||
|
||||
# 空白占位符
|
||||
v_layout.addItem(
|
||||
QSpacerItem(10, 20, QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Minimum)
|
||||
)
|
||||
|
||||
# 顶部部分 (按钮组)
|
||||
h1_layout = QHBoxLayout()
|
||||
h1_layout.setAlignment(Qt.AlignmentFlag.AlignTop)
|
||||
|
||||
# 左边留白区域
|
||||
h1_layout.addStretch()
|
||||
|
||||
# 按钮组
|
||||
buttonGroup = ButtonGroup()
|
||||
buttonGroup.setMaximumHeight(320)
|
||||
h1_layout.addWidget(buttonGroup)
|
||||
|
||||
# 空白占位符
|
||||
h1_layout.addItem(
|
||||
QSpacerItem(20, 10, QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Minimum)
|
||||
)
|
||||
|
||||
# 将顶部水平布局添加到垂直布局
|
||||
v_layout.addLayout(h1_layout)
|
||||
|
||||
# 中间留白区域
|
||||
v_layout.addItem(
|
||||
QSpacerItem(10, 10, QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Minimum)
|
||||
)
|
||||
v_layout.addStretch()
|
||||
|
||||
# 中间留白区域
|
||||
v_layout.addItem(
|
||||
QSpacerItem(10, 10, QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Minimum)
|
||||
)
|
||||
v_layout.addStretch()
|
||||
|
||||
# 底部部分 (图片切换按钮)
|
||||
h2_layout = QHBoxLayout()
|
||||
h2_layout.setAlignment(Qt.AlignmentFlag.AlignTop)
|
||||
|
||||
# 左边留白区域
|
||||
h2_layout.addItem(
|
||||
QSpacerItem(20, 10, QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Minimum)
|
||||
)
|
||||
|
||||
# # 公告卡片
|
||||
# noticeCard = NoticeCard()
|
||||
# h2_layout.addWidget(noticeCard)
|
||||
|
||||
h2_layout.addStretch()
|
||||
|
||||
# 自定义图像按钮布局
|
||||
self.imageButton = PrimaryToolButton(FluentIcon.IMAGE_EXPORT)
|
||||
self.imageButton.setFixedSize(56, 56)
|
||||
self.imageButton.setIconSize(QSize(32, 32))
|
||||
self.imageButton.clicked.connect(self.get_home_image)
|
||||
|
||||
v1_layout = QVBoxLayout()
|
||||
v1_layout.addWidget(self.imageButton, alignment=Qt.AlignmentFlag.AlignBottom)
|
||||
|
||||
h2_layout.addLayout(v1_layout)
|
||||
|
||||
# 空白占位符
|
||||
h2_layout.addItem(
|
||||
QSpacerItem(25, 10, QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Minimum)
|
||||
)
|
||||
|
||||
# 将底部水平布局添加到垂直布局
|
||||
v_layout.addLayout(h2_layout)
|
||||
|
||||
content_widget = QWidget()
|
||||
content_layout = QVBoxLayout(content_widget)
|
||||
content_layout.setContentsMargins(0, 0, 0, 0)
|
||||
content_layout.addWidget(self.banner)
|
||||
content_layout.addWidget(self.banner_text)
|
||||
content_layout.setStretch(0, 2)
|
||||
content_layout.setStretch(1, 3)
|
||||
|
||||
scrollArea = ScrollArea()
|
||||
scrollArea.setWidgetResizable(True)
|
||||
scrollArea.setContentsMargins(0, 0, 0, 0)
|
||||
scrollArea.setStyleSheet("background: transparent; border: none;")
|
||||
scrollArea.setWidget(content_widget)
|
||||
|
||||
layout = QVBoxLayout(self)
|
||||
layout.addWidget(scrollArea)
|
||||
|
||||
self.set_banner()
|
||||
|
||||
def get_home_image(self) -> None:
|
||||
"""获取主页图片"""
|
||||
|
||||
if Config.get(Config.function_HomeImageMode) == "默认":
|
||||
pass
|
||||
elif Config.get(Config.function_HomeImageMode) == "自定义":
|
||||
|
||||
file_path, _ = QFileDialog.getOpenFileName(
|
||||
self, "打开自定义主页图片", "", "图片文件 (*.png *.jpg *.bmp)"
|
||||
)
|
||||
if file_path:
|
||||
|
||||
for file in Config.app_path.glob(
|
||||
"resources/images/Home/BannerCustomize.*"
|
||||
):
|
||||
file.unlink()
|
||||
|
||||
shutil.copy(
|
||||
file_path,
|
||||
Config.app_path
|
||||
/ f"resources/images/Home/BannerCustomize{Path(file_path).suffix}",
|
||||
)
|
||||
|
||||
logger.info(f"自定义主页图片更换成功:{file_path}")
|
||||
MainInfoBar.push_info_bar(
|
||||
"success",
|
||||
"主页图片更换成功",
|
||||
"自定义主页图片更换成功!",
|
||||
3000,
|
||||
)
|
||||
|
||||
else:
|
||||
logger.warning("自定义主页图片更换失败:未选择图片文件")
|
||||
MainInfoBar.push_info_bar(
|
||||
"warning",
|
||||
"主页图片更换失败",
|
||||
"未选择图片文件!",
|
||||
5000,
|
||||
)
|
||||
elif Config.get(Config.function_HomeImageMode) == "主题图像":
|
||||
|
||||
# 从远程服务器获取最新主题图像
|
||||
network = Network.add_task(
|
||||
mode="get",
|
||||
url="https://gitee.com/DLmaster_361/AUTO_MAA/raw/server/theme_image.json",
|
||||
)
|
||||
network.loop.exec()
|
||||
network_result = Network.get_result(network)
|
||||
if network_result["status_code"] == 200:
|
||||
theme_image = network_result["response_json"]
|
||||
else:
|
||||
logger.warning(
|
||||
f"获取最新主题图像时出错:{network_result['error_message']}"
|
||||
)
|
||||
MainInfoBar.push_info_bar(
|
||||
"warning",
|
||||
"获取最新主题图像时出错",
|
||||
f"网络错误:{network_result['status_code']}",
|
||||
5000,
|
||||
)
|
||||
return None
|
||||
|
||||
if (Config.app_path / "resources/theme_image.json").exists():
|
||||
with (Config.app_path / "resources/theme_image.json").open(
|
||||
mode="r", encoding="utf-8"
|
||||
) as f:
|
||||
theme_image_local = json.load(f)
|
||||
time_local = datetime.strptime(
|
||||
theme_image_local["time"], "%Y-%m-%d %H:%M"
|
||||
)
|
||||
else:
|
||||
time_local = datetime.strptime("2000-01-01 00:00", "%Y-%m-%d %H:%M")
|
||||
|
||||
if not (
|
||||
Config.app_path / "resources/images/Home/BannerTheme.jpg"
|
||||
).exists() or (
|
||||
datetime.now()
|
||||
> datetime.strptime(theme_image["time"], "%Y-%m-%d %H:%M")
|
||||
> time_local
|
||||
):
|
||||
|
||||
network = Network.add_task(
|
||||
mode="get_file",
|
||||
url=theme_image["url"],
|
||||
path=Config.app_path / "resources/images/Home/BannerTheme.jpg",
|
||||
)
|
||||
network.loop.exec()
|
||||
network_result = Network.get_result(network)
|
||||
|
||||
if network_result["status_code"] == 200:
|
||||
|
||||
with (Config.app_path / "resources/theme_image.json").open(
|
||||
mode="w", encoding="utf-8"
|
||||
) as f:
|
||||
json.dump(theme_image, f, ensure_ascii=False, indent=4)
|
||||
|
||||
logger.success(f"主题图像「{theme_image["name"]}」下载成功")
|
||||
MainInfoBar.push_info_bar(
|
||||
"success",
|
||||
"主题图像下载成功",
|
||||
f"「{theme_image["name"]}」下载成功!",
|
||||
3000,
|
||||
)
|
||||
|
||||
else:
|
||||
|
||||
logger.warning(
|
||||
f"下载最新主题图像时出错:{network_result['error_message']}"
|
||||
)
|
||||
MainInfoBar.push_info_bar(
|
||||
"warning",
|
||||
"下载最新主题图像时出错",
|
||||
f"网络错误:{network_result['status_code']}",
|
||||
5000,
|
||||
)
|
||||
|
||||
else:
|
||||
|
||||
logger.info("主题图像已是最新")
|
||||
MainInfoBar.push_info_bar(
|
||||
"info",
|
||||
"主题图像已是最新",
|
||||
"主题图像已是最新!",
|
||||
3000,
|
||||
)
|
||||
|
||||
self.set_banner()
|
||||
|
||||
def set_banner(self):
|
||||
"""设置主页图像"""
|
||||
if Config.get(Config.function_HomeImageMode) == "默认":
|
||||
self.banner.set_banner_image(
|
||||
str(Config.app_path / "resources/images/Home/BannerDefault.png")
|
||||
)
|
||||
self.imageButton.hide()
|
||||
self.banner_text.setVisible(False)
|
||||
elif Config.get(Config.function_HomeImageMode) == "自定义":
|
||||
for file in Config.app_path.glob("resources/images/Home/BannerCustomize.*"):
|
||||
self.banner.set_banner_image(str(file))
|
||||
break
|
||||
self.imageButton.show()
|
||||
self.banner_text.setVisible(False)
|
||||
elif Config.get(Config.function_HomeImageMode) == "主题图像":
|
||||
self.banner.set_banner_image(
|
||||
str(Config.app_path / "resources/images/Home/BannerTheme.jpg")
|
||||
)
|
||||
self.imageButton.show()
|
||||
self.banner_text.setVisible(True)
|
||||
|
||||
if (Config.app_path / "resources/theme_image.json").exists():
|
||||
with (Config.app_path / "resources/theme_image.json").open(
|
||||
mode="r", encoding="utf-8"
|
||||
) as f:
|
||||
theme_image = json.load(f)
|
||||
html_content = theme_image["html"]
|
||||
else:
|
||||
html_content = "<h1>主题图像</h1><p>主题图像信息未知</p>"
|
||||
|
||||
self.banner_text.setHtml(re.sub(r"<img[^>]*>", "", html_content))
|
||||
|
||||
|
||||
class ButtonGroup(SimpleCardWidget):
|
||||
"""显示主页和 GitHub 按钮的竖直按钮组"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent=parent)
|
||||
|
||||
self.setFixedSize(56, 180)
|
||||
|
||||
layout = QVBoxLayout(self)
|
||||
layout.setAlignment(Qt.AlignmentFlag.AlignTop)
|
||||
|
||||
# 创建主页按钮
|
||||
home_button = IconButton(
|
||||
FluentIcon.HOME.icon(color=QColor("#fff")),
|
||||
tip_title="AUTO_MAA官网",
|
||||
tip_content="AUTO_MAA官方文档站",
|
||||
isTooltip=True,
|
||||
)
|
||||
home_button.setIconSize(QSize(32, 32))
|
||||
home_button.clicked.connect(self.open_home)
|
||||
layout.addWidget(home_button)
|
||||
|
||||
# 创建 GitHub 按钮
|
||||
github_button = IconButton(
|
||||
FluentIcon.GITHUB.icon(color=QColor("#fff")),
|
||||
tip_title="Github仓库",
|
||||
tip_content="如果本项目有帮助到您~\n不妨给项目点一个Star⭐",
|
||||
isTooltip=True,
|
||||
)
|
||||
github_button.setIconSize(QSize(32, 32))
|
||||
github_button.clicked.connect(self.open_github)
|
||||
layout.addWidget(github_button)
|
||||
|
||||
# # 创建 文档 按钮
|
||||
# doc_button = IconButton(
|
||||
# FluentIcon.DICTIONARY.icon(color=QColor("#fff")),
|
||||
# tip_title="自助排障文档",
|
||||
# tip_content="点击打开自助排障文档,好孩子都能看懂",
|
||||
# isTooltip=True,
|
||||
# )
|
||||
# doc_button.setIconSize(QSize(32, 32))
|
||||
# doc_button.clicked.connect(self.open_doc)
|
||||
# layout.addWidget(doc_button)
|
||||
|
||||
# 创建 Q群 按钮
|
||||
doc_button = IconButton(
|
||||
FluentIcon.CHAT.icon(color=QColor("#fff")),
|
||||
tip_title="官方社群",
|
||||
tip_content="加入官方群聊【AUTO_MAA绝赞DeBug中!】",
|
||||
isTooltip=True,
|
||||
)
|
||||
doc_button.setIconSize(QSize(32, 32))
|
||||
doc_button.clicked.connect(self.open_chat)
|
||||
layout.addWidget(doc_button)
|
||||
|
||||
# 创建 MirrorChyan 按钮
|
||||
doc_button = IconButton(
|
||||
FluentIcon.SHOPPING_CART.icon(color=QColor("#fff")),
|
||||
tip_title="非官方店铺",
|
||||
tip_content="获取 MirrorChyan CDK,更新快人一步",
|
||||
isTooltip=True,
|
||||
)
|
||||
doc_button.setIconSize(QSize(32, 32))
|
||||
doc_button.clicked.connect(self.open_sales)
|
||||
layout.addWidget(doc_button)
|
||||
|
||||
def _normalBackgroundColor(self):
|
||||
return QColor(0, 0, 0, 96)
|
||||
|
||||
def open_home(self):
|
||||
"""打开主页链接"""
|
||||
QDesktopServices.openUrl(QUrl("https://clozya.github.io/AUTOMAA_docs"))
|
||||
|
||||
def open_github(self):
|
||||
"""打开 GitHub 链接"""
|
||||
QDesktopServices.openUrl(QUrl("https://github.com/DLmaster361/AUTO_MAA"))
|
||||
|
||||
def open_chat(self):
|
||||
"""打开 Q群 链接"""
|
||||
QDesktopServices.openUrl(QUrl("https://qm.qq.com/q/bd9fISNoME"))
|
||||
|
||||
def open_doc(self):
|
||||
"""打开 文档 链接"""
|
||||
QDesktopServices.openUrl(QUrl("https://clozya.github.io/AUTOMAA_docs"))
|
||||
|
||||
def open_sales(self):
|
||||
"""打开 MirrorChyan 链接"""
|
||||
QDesktopServices.openUrl(
|
||||
QUrl("https://mirrorchyan.com/zh/get-start?source=auto_maa-home")
|
||||
)
|
||||
505
app/ui/main_window.py
Normal file
@@ -0,0 +1,505 @@
|
||||
# AUTO_MAA:A MAA Multi Account Management and Automation Tool
|
||||
# Copyright © 2024-2025 DLmaster361
|
||||
|
||||
# This file is part of AUTO_MAA.
|
||||
|
||||
# AUTO_MAA is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License,
|
||||
# or (at your option) any later version.
|
||||
|
||||
# AUTO_MAA is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
|
||||
# the GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# Contact: DLmaster_361@163.com
|
||||
|
||||
"""
|
||||
AUTO_MAA
|
||||
AUTO_MAA主界面
|
||||
v4.3
|
||||
作者:DLmaster_361
|
||||
"""
|
||||
|
||||
from loguru import logger
|
||||
from PySide6.QtWidgets import QApplication, QSystemTrayIcon
|
||||
from qfluentwidgets import (
|
||||
Action,
|
||||
SystemTrayMenu,
|
||||
SplashScreen,
|
||||
FluentIcon,
|
||||
setTheme,
|
||||
isDarkTheme,
|
||||
SystemThemeListener,
|
||||
Theme,
|
||||
MSFluentWindow,
|
||||
NavigationItemPosition,
|
||||
)
|
||||
from PySide6.QtGui import QIcon, QCloseEvent
|
||||
from PySide6.QtCore import QTimer
|
||||
from datetime import datetime, timedelta
|
||||
import shutil
|
||||
import darkdetect
|
||||
|
||||
from app.core import Config, TaskManager, MainTimer, MainInfoBar, SoundPlayer
|
||||
from app.services import Notify, Crypto, System
|
||||
from .home import Home
|
||||
from .member_manager import MemberManager
|
||||
from .plan_manager import PlanManager
|
||||
from .queue_manager import QueueManager
|
||||
from .dispatch_center import DispatchCenter
|
||||
from .history import History
|
||||
from .setting import Setting
|
||||
|
||||
|
||||
class AUTO_MAA(MSFluentWindow):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self.setWindowIcon(QIcon(str(Config.app_path / "resources/icons/AUTO_MAA.ico")))
|
||||
|
||||
version_numb = list(map(int, Config.VERSION.split(".")))
|
||||
version_text = (
|
||||
f"v{'.'.join(str(_) for _ in version_numb[0:3])}"
|
||||
if version_numb[3] == 0
|
||||
else f"v{'.'.join(str(_) for _ in version_numb[0:3])}-beta.{version_numb[3]}"
|
||||
)
|
||||
|
||||
self.setWindowTitle(f"AUTO_MAA - {version_text}")
|
||||
|
||||
self.switch_theme()
|
||||
|
||||
self.splashScreen = SplashScreen(self.windowIcon(), self)
|
||||
self.show_ui("显示主窗口", if_quick=True)
|
||||
|
||||
Config.main_window = self.window()
|
||||
|
||||
# 创建主窗口
|
||||
self.home = Home(self)
|
||||
self.plan_manager = PlanManager(self)
|
||||
self.member_manager = MemberManager(self)
|
||||
self.queue_manager = QueueManager(self)
|
||||
self.dispatch_center = DispatchCenter(self)
|
||||
self.history = History(self)
|
||||
self.setting = Setting(self)
|
||||
|
||||
self.addSubInterface(
|
||||
self.home,
|
||||
FluentIcon.HOME,
|
||||
"主页",
|
||||
FluentIcon.HOME,
|
||||
NavigationItemPosition.TOP,
|
||||
)
|
||||
self.addSubInterface(
|
||||
self.member_manager,
|
||||
FluentIcon.ROBOT,
|
||||
"脚本管理",
|
||||
FluentIcon.ROBOT,
|
||||
NavigationItemPosition.TOP,
|
||||
)
|
||||
self.addSubInterface(
|
||||
self.plan_manager,
|
||||
FluentIcon.CALENDAR,
|
||||
"计划管理",
|
||||
FluentIcon.CALENDAR,
|
||||
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.addSubInterface(
|
||||
self.history,
|
||||
FluentIcon.HISTORY,
|
||||
"历史记录",
|
||||
FluentIcon.HISTORY,
|
||||
NavigationItemPosition.BOTTOM,
|
||||
)
|
||||
self.addSubInterface(
|
||||
self.setting,
|
||||
FluentIcon.SETTING,
|
||||
"设置",
|
||||
FluentIcon.SETTING,
|
||||
NavigationItemPosition.BOTTOM,
|
||||
)
|
||||
self.stackedWidget.currentChanged.connect(self.__currentChanged)
|
||||
|
||||
# 创建系统托盘及其菜单
|
||||
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=self.start_main_task),
|
||||
Action(
|
||||
FluentIcon.PAUSE,
|
||||
"中止所有任务",
|
||||
triggered=lambda: TaskManager.stop_task("ALL"),
|
||||
),
|
||||
]
|
||||
)
|
||||
self.tray_menu.addSeparator()
|
||||
|
||||
# 退出主程序菜单项
|
||||
self.tray_menu.addAction(
|
||||
Action(
|
||||
FluentIcon.POWER_BUTTON,
|
||||
"退出主程序",
|
||||
triggered=lambda: System.set_power("KillSelf"),
|
||||
)
|
||||
)
|
||||
|
||||
# 设置托盘菜单
|
||||
self.tray.setContextMenu(self.tray_menu)
|
||||
self.tray.activated.connect(self.on_tray_activated)
|
||||
|
||||
self.set_min_method()
|
||||
|
||||
Config.user_info_changed.connect(self.member_manager.refresh_dashboard)
|
||||
Config.power_sign_changed.connect(self.dispatch_center.update_power_sign)
|
||||
TaskManager.create_gui.connect(self.dispatch_center.add_board)
|
||||
TaskManager.connect_gui.connect(self.dispatch_center.connect_main_board)
|
||||
Notify.push_info_bar.connect(MainInfoBar.push_info_bar)
|
||||
self.setting.ui.card_IfShowTray.checkedChanged.connect(
|
||||
lambda: self.show_ui("配置托盘")
|
||||
)
|
||||
self.setting.ui.card_IfToTray.checkedChanged.connect(self.set_min_method)
|
||||
self.setting.function.card_HomeImageMode.comboBox.currentIndexChanged.connect(
|
||||
lambda index: (
|
||||
self.home.get_home_image() if index == 2 else self.home.set_banner()
|
||||
)
|
||||
)
|
||||
|
||||
self.splashScreen.finish()
|
||||
|
||||
self.themeListener = SystemThemeListener(self)
|
||||
self.themeListener.systemThemeChanged.connect(self.switch_theme)
|
||||
self.themeListener.start()
|
||||
|
||||
def switch_theme(self) -> None:
|
||||
"""切换主题"""
|
||||
|
||||
setTheme(
|
||||
Theme(darkdetect.theme()) if darkdetect.theme() else Theme.LIGHT, lazy=True
|
||||
)
|
||||
QTimer.singleShot(300, lambda: setTheme(Theme.AUTO, lazy=True))
|
||||
|
||||
# 云母特效启用时需要增加重试机制
|
||||
# 云母特效不兼容Win10,如果True则通过云母进行主题转换,False则根据当前主题设置背景颜色
|
||||
if self.isMicaEffectEnabled():
|
||||
QTimer.singleShot(
|
||||
300,
|
||||
lambda: self.windowEffect.setMicaEffect(self.winId(), isDarkTheme()),
|
||||
)
|
||||
|
||||
else:
|
||||
# 根据当前主题设置背景颜色
|
||||
if isDarkTheme():
|
||||
self.setStyleSheet(
|
||||
"""
|
||||
CardWidget {background-color: #313131;}
|
||||
HeaderCardWidget {background-color: #313131;}
|
||||
background-color: #313131;
|
||||
"""
|
||||
)
|
||||
else:
|
||||
self.setStyleSheet("background-color: #ffffff;")
|
||||
|
||||
def set_min_method(self) -> None:
|
||||
"""设置最小化方法"""
|
||||
|
||||
if Config.get(Config.ui_IfToTray):
|
||||
|
||||
self.titleBar.minBtn.clicked.disconnect()
|
||||
self.titleBar.minBtn.clicked.connect(lambda: self.show_ui("隐藏到托盘"))
|
||||
|
||||
else:
|
||||
|
||||
self.titleBar.minBtn.clicked.disconnect()
|
||||
self.titleBar.minBtn.clicked.connect(self.window().showMinimized)
|
||||
|
||||
def on_tray_activated(self, reason):
|
||||
"""双击返回主界面"""
|
||||
if reason == QSystemTrayIcon.DoubleClick:
|
||||
self.show_ui("显示主窗口")
|
||||
|
||||
def show_ui(
|
||||
self, mode: str, if_quick: bool = False, if_start: bool = False
|
||||
) -> None:
|
||||
"""配置窗口状态"""
|
||||
|
||||
if Config.args.mode != "gui":
|
||||
return None
|
||||
|
||||
self.switch_theme()
|
||||
|
||||
if mode == "显示主窗口":
|
||||
|
||||
# 配置主窗口
|
||||
if not self.window().isVisible():
|
||||
size = list(
|
||||
map(
|
||||
int,
|
||||
Config.get(Config.ui_size).split("x"),
|
||||
)
|
||||
)
|
||||
location = list(
|
||||
map(
|
||||
int,
|
||||
Config.get(Config.ui_location).split("x"),
|
||||
)
|
||||
)
|
||||
if self.window().isMaximized():
|
||||
self.window().showNormal()
|
||||
self.window().setGeometry(location[0], location[1], size[0], size[1])
|
||||
self.window().show()
|
||||
if not if_quick:
|
||||
if (
|
||||
Config.get(Config.ui_maximized)
|
||||
and not self.window().isMaximized()
|
||||
):
|
||||
self.titleBar.maxBtn.click()
|
||||
SoundPlayer.play("欢迎回来")
|
||||
self.show_ui("配置托盘")
|
||||
elif if_start:
|
||||
if Config.get(Config.ui_maximized) and not self.window().isMaximized():
|
||||
self.titleBar.maxBtn.click()
|
||||
self.show_ui("配置托盘")
|
||||
|
||||
# 如果窗口不在屏幕内,则重置窗口位置
|
||||
if not any(
|
||||
self.window().geometry().intersects(screen.availableGeometry())
|
||||
for screen in QApplication.screens()
|
||||
):
|
||||
self.window().showNormal()
|
||||
self.window().setGeometry(100, 100, 1200, 700)
|
||||
|
||||
self.window().raise_()
|
||||
self.window().activateWindow()
|
||||
|
||||
while Config.info_bar_list:
|
||||
info_bar_item = Config.info_bar_list.pop(0)
|
||||
MainInfoBar.push_info_bar(
|
||||
info_bar_item["mode"],
|
||||
info_bar_item["title"],
|
||||
info_bar_item["content"],
|
||||
info_bar_item["time"],
|
||||
)
|
||||
|
||||
elif mode == "配置托盘":
|
||||
|
||||
if Config.get(Config.ui_IfShowTray):
|
||||
self.tray.show()
|
||||
else:
|
||||
self.tray.hide()
|
||||
|
||||
elif mode == "隐藏到托盘":
|
||||
|
||||
# 保存窗口相关属性
|
||||
if not self.window().isMaximized():
|
||||
|
||||
Config.set(
|
||||
Config.ui_size,
|
||||
f"{self.geometry().width()}x{self.geometry().height()}",
|
||||
)
|
||||
Config.set(
|
||||
Config.ui_location,
|
||||
f"{self.geometry().x()}x{self.geometry().y()}",
|
||||
)
|
||||
|
||||
Config.set(Config.ui_maximized, self.window().isMaximized())
|
||||
Config.save()
|
||||
|
||||
# 隐藏主窗口
|
||||
if not if_quick:
|
||||
|
||||
self.window().hide()
|
||||
self.tray.show()
|
||||
|
||||
def start_up_task(self) -> None:
|
||||
"""启动时任务"""
|
||||
|
||||
# 清理旧日志
|
||||
self.clean_old_logs()
|
||||
|
||||
# 清理安装包
|
||||
if (Config.app_path / "AUTO_MAA-Setup.exe").exists():
|
||||
try:
|
||||
(Config.app_path / "AUTO_MAA-Setup.exe").unlink()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 检查密码
|
||||
self.setting.check_PASSWORD()
|
||||
|
||||
# 获取主题图像
|
||||
if Config.get(Config.function_HomeImageMode) == "主题图像":
|
||||
self.home.get_home_image()
|
||||
|
||||
# 直接运行主任务
|
||||
if Config.get(Config.start_IfRunDirectly):
|
||||
|
||||
self.start_main_task()
|
||||
|
||||
# 获取公告
|
||||
self.setting.show_notice(if_first=True)
|
||||
|
||||
# 检查更新
|
||||
if Config.get(Config.update_IfAutoUpdate):
|
||||
self.setting.check_update(if_first=True)
|
||||
|
||||
# 直接最小化
|
||||
if Config.get(Config.start_IfMinimizeDirectly):
|
||||
|
||||
self.titleBar.minBtn.click()
|
||||
|
||||
if Config.args.config:
|
||||
|
||||
for config in [_ for _ in Config.args.config if _ in Config.queue_dict]:
|
||||
|
||||
TaskManager.add_task(
|
||||
"自动代理_新调度台",
|
||||
config,
|
||||
Config.queue_dict["调度队列_1"]["Config"].toDict(),
|
||||
)
|
||||
|
||||
for config in [_ for _ in Config.args.config if _ in Config.member_dict]:
|
||||
|
||||
TaskManager.add_task(
|
||||
"自动代理_新调度台",
|
||||
"自定义队列",
|
||||
{"Queue": {"Member_1": config}},
|
||||
)
|
||||
|
||||
if not any(
|
||||
_ in (list(Config.member_dict.keys()) + list(Config.queue_dict.keys()))
|
||||
for _ in Config.args.config
|
||||
):
|
||||
|
||||
logger.warning(
|
||||
"当前运行模式为命令行模式,由于您使用了错误的 --config 参数进行配置,程序自动退出"
|
||||
)
|
||||
System.set_power("KillSelf")
|
||||
|
||||
elif Config.args.mode == "cli":
|
||||
|
||||
logger.warning(
|
||||
"当前运行模式为命令行模式,由于您未使用 --config 参数进行配置,程序自动退出"
|
||||
)
|
||||
System.set_power("KillSelf")
|
||||
|
||||
def clean_old_logs(self):
|
||||
"""
|
||||
删除超过用户设定天数的日志文件(基于目录日期)
|
||||
"""
|
||||
|
||||
if Config.get(Config.function_HistoryRetentionTime) == 0:
|
||||
logger.info("由于用户设置日志永久保留,跳过日志清理")
|
||||
return
|
||||
|
||||
deleted_count = 0
|
||||
|
||||
for date_folder in (Config.app_path / "history").iterdir():
|
||||
if not date_folder.is_dir():
|
||||
continue # 只处理日期文件夹
|
||||
|
||||
try:
|
||||
# 只检查 `YYYY-MM-DD` 格式的文件夹
|
||||
folder_date = datetime.strptime(date_folder.name, "%Y-%m-%d")
|
||||
if datetime.now() - folder_date > timedelta(
|
||||
days=Config.get(Config.function_HistoryRetentionTime)
|
||||
):
|
||||
shutil.rmtree(date_folder, ignore_errors=True)
|
||||
deleted_count += 1
|
||||
logger.info(f"已删除超期日志目录: {date_folder}")
|
||||
except ValueError:
|
||||
logger.warning(f"非日期格式的目录: {date_folder}")
|
||||
|
||||
logger.info(f"清理完成: {deleted_count} 个日期目录")
|
||||
|
||||
def start_main_task(self) -> None:
|
||||
"""启动主任务"""
|
||||
|
||||
if "调度队列_1" in Config.queue_dict:
|
||||
|
||||
logger.info("自动添加任务:调度队列_1")
|
||||
TaskManager.add_task(
|
||||
"自动代理_主调度台",
|
||||
"调度队列_1",
|
||||
Config.queue_dict["调度队列_1"]["Config"].toDict(),
|
||||
)
|
||||
|
||||
elif "脚本_1" in Config.member_dict:
|
||||
|
||||
logger.info("自动添加任务:脚本_1")
|
||||
TaskManager.add_task(
|
||||
"自动代理_主调度台", "自定义队列", {"Queue": {"Member_1": "脚本_1"}}
|
||||
)
|
||||
|
||||
else:
|
||||
|
||||
logger.warning("启动主任务失败:未找到有效的主任务配置文件")
|
||||
MainInfoBar.push_info_bar(
|
||||
"warning", "启动主任务失败", "“调度队列_1”与“脚本_1”均不存在", -1
|
||||
)
|
||||
|
||||
def __currentChanged(self, index: int) -> None:
|
||||
"""切换界面时任务"""
|
||||
|
||||
if index == 1:
|
||||
self.member_manager.reload_plan_name()
|
||||
elif index == 3:
|
||||
self.queue_manager.reload_member_name()
|
||||
elif index == 4:
|
||||
self.dispatch_center.pivot.setCurrentItem("主调度台")
|
||||
self.dispatch_center.update_top_bar()
|
||||
|
||||
def closeEvent(self, event: QCloseEvent):
|
||||
"""清理残余进程"""
|
||||
|
||||
self.show_ui("隐藏到托盘", if_quick=True)
|
||||
|
||||
# 清理各功能线程
|
||||
MainTimer.Timer.stop()
|
||||
MainTimer.Timer.deleteLater()
|
||||
MainTimer.LongTimer.stop()
|
||||
MainTimer.LongTimer.deleteLater()
|
||||
TaskManager.stop_task("ALL")
|
||||
|
||||
# 关闭主题监听
|
||||
self.themeListener.terminate()
|
||||
self.themeListener.deleteLater()
|
||||
|
||||
logger.info("AUTO_MAA主程序关闭")
|
||||
logger.info("----------------END----------------")
|
||||
|
||||
event.accept()
|
||||
2156
app/ui/member_manager.py
Normal file
497
app/ui/plan_manager.py
Normal file
@@ -0,0 +1,497 @@
|
||||
# AUTO_MAA:A MAA Multi Account Management and Automation Tool
|
||||
# Copyright © 2024-2025 DLmaster361
|
||||
|
||||
# This file is part of AUTO_MAA.
|
||||
|
||||
# AUTO_MAA is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License,
|
||||
# or (at your option) any later version.
|
||||
|
||||
# AUTO_MAA is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
|
||||
# the GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# Contact: DLmaster_361@163.com
|
||||
|
||||
"""
|
||||
AUTO_MAA
|
||||
AUTO_MAA计划管理界面
|
||||
v4.3
|
||||
作者:DLmaster_361
|
||||
"""
|
||||
|
||||
from loguru import logger
|
||||
from PySide6.QtWidgets import (
|
||||
QWidget,
|
||||
QVBoxLayout,
|
||||
QStackedWidget,
|
||||
QHeaderView,
|
||||
)
|
||||
from qfluentwidgets import (
|
||||
Action,
|
||||
FluentIcon,
|
||||
MessageBox,
|
||||
HeaderCardWidget,
|
||||
CommandBar,
|
||||
TableWidget,
|
||||
)
|
||||
from typing import List, Dict, Union
|
||||
import shutil
|
||||
|
||||
from app.core import Config, MainInfoBar, MaaPlanConfig, SoundPlayer
|
||||
from .Widget import (
|
||||
ComboBoxMessageBox,
|
||||
LineEditSettingCard,
|
||||
ComboBoxSettingCard,
|
||||
SpinBoxSetting,
|
||||
EditableComboBoxSetting,
|
||||
ComboBoxSetting,
|
||||
PivotArea,
|
||||
)
|
||||
|
||||
|
||||
class PlanManager(QWidget):
|
||||
"""计划管理父界面"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
self.setObjectName("计划管理")
|
||||
|
||||
layout = QVBoxLayout(self)
|
||||
|
||||
self.tools = CommandBar()
|
||||
|
||||
self.plan_manager = self.PlanSettingBox(self)
|
||||
|
||||
# 逐个添加动作
|
||||
self.tools.addActions(
|
||||
[
|
||||
Action(FluentIcon.ADD_TO, "新建计划表", triggered=self.add_setting_box),
|
||||
Action(
|
||||
FluentIcon.REMOVE_FROM, "删除计划表", triggered=self.del_setting_box
|
||||
),
|
||||
]
|
||||
)
|
||||
self.tools.addSeparator()
|
||||
self.tools.addActions(
|
||||
[
|
||||
Action(
|
||||
FluentIcon.LEFT_ARROW, "向左移动", triggered=self.left_setting_box
|
||||
),
|
||||
Action(
|
||||
FluentIcon.RIGHT_ARROW, "向右移动", triggered=self.right_setting_box
|
||||
),
|
||||
]
|
||||
)
|
||||
self.tools.addSeparator()
|
||||
|
||||
layout.addWidget(self.tools)
|
||||
layout.addWidget(self.plan_manager)
|
||||
|
||||
def add_setting_box(self):
|
||||
"""添加一个计划表"""
|
||||
|
||||
choice = ComboBoxMessageBox(
|
||||
self.window(),
|
||||
"选择一个计划类型以添加相应计划表",
|
||||
["选择计划类型"],
|
||||
[["MAA"]],
|
||||
)
|
||||
if choice.exec() and choice.input[0].currentIndex() != -1:
|
||||
|
||||
if choice.input[0].currentText() == "MAA":
|
||||
|
||||
index = len(Config.plan_dict) + 1
|
||||
|
||||
maa_plan_config = MaaPlanConfig()
|
||||
maa_plan_config.load(
|
||||
Config.app_path / f"config/MaaPlanConfig/计划_{index}/config.json",
|
||||
maa_plan_config,
|
||||
)
|
||||
maa_plan_config.save()
|
||||
|
||||
Config.plan_dict[f"计划_{index}"] = {
|
||||
"Type": "Maa",
|
||||
"Path": Config.app_path / f"config/MaaPlanConfig/计划_{index}",
|
||||
"Config": maa_plan_config,
|
||||
}
|
||||
|
||||
self.plan_manager.add_MaaPlanSettingBox(index)
|
||||
self.plan_manager.switch_SettingBox(index)
|
||||
|
||||
logger.success(f"计划管理 计划_{index} 添加成功")
|
||||
MainInfoBar.push_info_bar(
|
||||
"success", "操作成功", f"添加计划表 计划_{index}", 3000
|
||||
)
|
||||
SoundPlayer.play("添加计划表")
|
||||
|
||||
def del_setting_box(self):
|
||||
"""删除一个计划表"""
|
||||
|
||||
name = self.plan_manager.pivot.currentRouteKey()
|
||||
|
||||
if name is None:
|
||||
logger.warning("删除计划表时未选择计划表")
|
||||
MainInfoBar.push_info_bar(
|
||||
"warning", "未选择计划表", "请选择一个计划表", 5000
|
||||
)
|
||||
return None
|
||||
|
||||
if len(Config.running_list) > 0:
|
||||
logger.warning("删除计划表时调度队列未停止运行")
|
||||
MainInfoBar.push_info_bar(
|
||||
"warning", "调度中心正在执行任务", "请等待或手动中止任务", 5000
|
||||
)
|
||||
return None
|
||||
|
||||
choice = MessageBox("确认", f"确定要删除 {name} 吗?", self.window())
|
||||
if choice.exec():
|
||||
|
||||
self.plan_manager.clear_SettingBox()
|
||||
|
||||
shutil.rmtree(Config.plan_dict[name]["Path"])
|
||||
Config.change_plan(name, "禁用")
|
||||
for i in range(int(name[3:]) + 1, len(Config.plan_dict) + 1):
|
||||
if Config.plan_dict[f"计划_{i}"]["Path"].exists():
|
||||
Config.plan_dict[f"计划_{i}"]["Path"].rename(
|
||||
Config.plan_dict[f"计划_{i}"]["Path"].with_name(f"计划_{i-1}")
|
||||
)
|
||||
Config.change_plan(f"计划_{i}", f"计划_{i-1}")
|
||||
|
||||
self.plan_manager.show_SettingBox(max(int(name[3:]) - 1, 1))
|
||||
|
||||
logger.success(f"计划表 {name} 删除成功")
|
||||
MainInfoBar.push_info_bar("success", "操作成功", f"删除计划表 {name}", 3000)
|
||||
SoundPlayer.play("删除计划表")
|
||||
|
||||
def left_setting_box(self):
|
||||
"""向左移动计划表"""
|
||||
|
||||
name = self.plan_manager.pivot.currentRouteKey()
|
||||
|
||||
if name is None:
|
||||
logger.warning("向左移动计划表时未选择计划表")
|
||||
MainInfoBar.push_info_bar(
|
||||
"warning", "未选择计划表", "请选择一个计划表", 5000
|
||||
)
|
||||
return None
|
||||
|
||||
index = int(name[3:])
|
||||
|
||||
if index == 1:
|
||||
logger.warning("向左移动计划表时已到达最左端")
|
||||
MainInfoBar.push_info_bar(
|
||||
"warning", "已经是第一个计划表", "无法向左移动", 5000
|
||||
)
|
||||
return None
|
||||
|
||||
if len(Config.running_list) > 0:
|
||||
logger.warning("向左移动计划表时调度队列未停止运行")
|
||||
MainInfoBar.push_info_bar(
|
||||
"warning", "调度中心正在执行任务", "请等待或手动中止任务", 5000
|
||||
)
|
||||
return None
|
||||
|
||||
self.plan_manager.clear_SettingBox()
|
||||
|
||||
Config.plan_dict[name]["Path"].rename(
|
||||
Config.plan_dict[name]["Path"].with_name("计划_0")
|
||||
)
|
||||
Config.change_plan(name, "计划_0")
|
||||
Config.plan_dict[f"计划_{index-1}"]["Path"].rename(
|
||||
Config.plan_dict[name]["Path"]
|
||||
)
|
||||
Config.change_plan(f"计划_{index-1}", name)
|
||||
Config.plan_dict[name]["Path"].with_name("计划_0").rename(
|
||||
Config.plan_dict[f"计划_{index-1}"]["Path"]
|
||||
)
|
||||
Config.change_plan("计划_0", f"计划_{index-1}")
|
||||
|
||||
self.plan_manager.show_SettingBox(index - 1)
|
||||
|
||||
logger.success(f"计划表 {name} 左移成功")
|
||||
MainInfoBar.push_info_bar("success", "操作成功", f"左移计划表 {name}", 3000)
|
||||
|
||||
def right_setting_box(self):
|
||||
"""向右移动计划表"""
|
||||
|
||||
name = self.plan_manager.pivot.currentRouteKey()
|
||||
|
||||
if name is None:
|
||||
logger.warning("向右移动计划表时未选择计划表")
|
||||
MainInfoBar.push_info_bar(
|
||||
"warning", "未选择计划表", "请选择一个计划表", 5000
|
||||
)
|
||||
return None
|
||||
|
||||
index = int(name[3:])
|
||||
|
||||
if index == len(Config.plan_dict):
|
||||
logger.warning("向右移动计划表时已到达最右端")
|
||||
MainInfoBar.push_info_bar(
|
||||
"warning", "已经是最后一个计划表", "无法向右移动", 5000
|
||||
)
|
||||
return None
|
||||
|
||||
if len(Config.running_list) > 0:
|
||||
logger.warning("向右移动计划表时调度队列未停止运行")
|
||||
MainInfoBar.push_info_bar(
|
||||
"warning", "调度中心正在执行任务", "请等待或手动中止任务", 5000
|
||||
)
|
||||
return None
|
||||
|
||||
self.plan_manager.clear_SettingBox()
|
||||
|
||||
Config.plan_dict[name]["Path"].rename(
|
||||
Config.plan_dict[name]["Path"].with_name("计划_0")
|
||||
)
|
||||
Config.change_plan(name, "计划_0")
|
||||
Config.plan_dict[f"计划_{index+1}"]["Path"].rename(
|
||||
Config.plan_dict[name]["Path"]
|
||||
)
|
||||
Config.change_plan(f"计划_{index+1}", name)
|
||||
Config.plan_dict[name]["Path"].with_name("计划_0").rename(
|
||||
Config.plan_dict[f"计划_{index+1}"]["Path"]
|
||||
)
|
||||
Config.change_plan("计划_0", f"计划_{index+1}")
|
||||
|
||||
self.plan_manager.show_SettingBox(index + 1)
|
||||
|
||||
logger.success(f"计划表 {name} 右移成功")
|
||||
MainInfoBar.push_info_bar("success", "操作成功", f"右移计划表 {name}", 3000)
|
||||
|
||||
class PlanSettingBox(QWidget):
|
||||
"""计划管理子页面组"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
self.setObjectName("计划管理页面组")
|
||||
|
||||
self.pivotArea = PivotArea(self)
|
||||
self.pivot = self.pivotArea.pivot
|
||||
|
||||
self.stackedWidget = QStackedWidget(self)
|
||||
self.stackedWidget.setContentsMargins(0, 0, 0, 0)
|
||||
self.stackedWidget.setStyleSheet("background: transparent; border: none;")
|
||||
|
||||
self.script_list: List[PlanManager.PlanSettingBox.MaaPlanSettingBox] = []
|
||||
|
||||
self.Layout = QVBoxLayout(self)
|
||||
self.Layout.addWidget(self.pivotArea)
|
||||
self.Layout.addWidget(self.stackedWidget)
|
||||
self.Layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
self.pivot.currentItemChanged.connect(
|
||||
lambda index: self.switch_SettingBox(
|
||||
int(index[3:]), if_chang_pivot=False
|
||||
)
|
||||
)
|
||||
|
||||
self.show_SettingBox(1)
|
||||
|
||||
def show_SettingBox(self, index) -> None:
|
||||
"""加载所有子界面"""
|
||||
|
||||
Config.search_plan()
|
||||
|
||||
for name, info in Config.plan_dict.items():
|
||||
if info["Type"] == "Maa":
|
||||
self.add_MaaPlanSettingBox(int(name[3:]))
|
||||
|
||||
self.switch_SettingBox(index)
|
||||
|
||||
def switch_SettingBox(self, index: int, if_chang_pivot: bool = True) -> None:
|
||||
"""切换到指定的子界面"""
|
||||
|
||||
if len(Config.plan_dict) == 0:
|
||||
return None
|
||||
|
||||
if index > len(Config.plan_dict):
|
||||
return None
|
||||
|
||||
if if_chang_pivot:
|
||||
self.pivot.setCurrentItem(self.script_list[index - 1].objectName())
|
||||
self.stackedWidget.setCurrentWidget(self.script_list[index - 1])
|
||||
|
||||
def clear_SettingBox(self) -> None:
|
||||
"""清空所有子界面"""
|
||||
|
||||
for sub_interface in self.script_list:
|
||||
Config.gameid_refreshed.disconnect(sub_interface.refresh_gameid)
|
||||
self.stackedWidget.removeWidget(sub_interface)
|
||||
sub_interface.deleteLater()
|
||||
self.script_list.clear()
|
||||
self.pivot.clear()
|
||||
|
||||
def add_MaaPlanSettingBox(self, uid: int) -> None:
|
||||
"""添加一个MAA设置界面"""
|
||||
|
||||
maa_plan_setting_box = self.MaaPlanSettingBox(uid, self)
|
||||
|
||||
self.script_list.append(maa_plan_setting_box)
|
||||
|
||||
self.stackedWidget.addWidget(self.script_list[-1])
|
||||
|
||||
self.pivot.addItem(routeKey=f"计划_{uid}", text=f"计划 {uid}")
|
||||
|
||||
class MaaPlanSettingBox(HeaderCardWidget):
|
||||
"""MAA类计划设置界面"""
|
||||
|
||||
def __init__(self, uid: int, parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
self.setObjectName(f"计划_{uid}")
|
||||
self.setTitle("MAA计划表")
|
||||
self.config = Config.plan_dict[f"计划_{uid}"]["Config"]
|
||||
|
||||
self.card_Name = LineEditSettingCard(
|
||||
icon=FluentIcon.EDIT,
|
||||
title="计划表名称",
|
||||
content="用于标识计划表的名称",
|
||||
text="请输入计划表名称",
|
||||
qconfig=self.config,
|
||||
configItem=self.config.Info_Name,
|
||||
parent=self,
|
||||
)
|
||||
self.card_Mode = ComboBoxSettingCard(
|
||||
icon=FluentIcon.DICTIONARY,
|
||||
title="计划模式",
|
||||
content="全局模式下计划内容固定,周计划模式下计划按周一到周日切换",
|
||||
texts=["全局", "周计划"],
|
||||
qconfig=self.config,
|
||||
configItem=self.config.Info_Mode,
|
||||
parent=self,
|
||||
)
|
||||
|
||||
self.table = TableWidget(self)
|
||||
self.table.setColumnCount(8)
|
||||
self.table.setRowCount(6)
|
||||
self.table.setHorizontalHeaderLabels(
|
||||
["全局", "周一", "周二", "周三", "周四", "周五", "周六", "周日"]
|
||||
)
|
||||
self.table.setVerticalHeaderLabels(
|
||||
[
|
||||
"吃理智药",
|
||||
"连战次数",
|
||||
"关卡选择",
|
||||
"备选 - 1",
|
||||
"备选 - 2",
|
||||
"剩余理智",
|
||||
]
|
||||
)
|
||||
self.table.setAlternatingRowColors(False)
|
||||
self.table.setEditTriggers(TableWidget.NoEditTriggers)
|
||||
for col in range(8):
|
||||
self.table.horizontalHeader().setSectionResizeMode(
|
||||
col, QHeaderView.ResizeMode.Stretch
|
||||
)
|
||||
for row in range(6):
|
||||
self.table.verticalHeader().setSectionResizeMode(
|
||||
row, QHeaderView.ResizeMode.ResizeToContents
|
||||
)
|
||||
|
||||
self.item_dict: Dict[
|
||||
str,
|
||||
Dict[
|
||||
str,
|
||||
Union[SpinBoxSetting, ComboBoxSetting, EditableComboBoxSetting],
|
||||
],
|
||||
] = {}
|
||||
|
||||
for col, (group, name_dict) in enumerate(
|
||||
self.config.config_item_dict.items()
|
||||
):
|
||||
|
||||
self.item_dict[group] = {}
|
||||
|
||||
for row, (name, configItem) in enumerate(name_dict.items()):
|
||||
|
||||
if name == "MedicineNumb":
|
||||
self.item_dict[group][name] = SpinBoxSetting(
|
||||
range=(0, 1024),
|
||||
qconfig=self.config,
|
||||
configItem=configItem,
|
||||
parent=self,
|
||||
)
|
||||
elif name == "SeriesNumb":
|
||||
self.item_dict[group][name] = ComboBoxSetting(
|
||||
texts=["AUTO", "6", "5", "4", "3", "2", "1", "不选择"],
|
||||
qconfig=self.config,
|
||||
configItem=configItem,
|
||||
parent=self,
|
||||
)
|
||||
elif name == "GameId_Remain":
|
||||
self.item_dict[group][name] = EditableComboBoxSetting(
|
||||
value=Config.gameid_dict[group]["value"],
|
||||
texts=[
|
||||
"不使用" if _ == "当前/上次" else _
|
||||
for _ in Config.gameid_dict[group]["text"]
|
||||
],
|
||||
qconfig=self.config,
|
||||
configItem=configItem,
|
||||
parent=self,
|
||||
)
|
||||
elif "GameId" in name:
|
||||
self.item_dict[group][name] = EditableComboBoxSetting(
|
||||
value=Config.gameid_dict[group]["value"],
|
||||
texts=Config.gameid_dict[group]["text"],
|
||||
qconfig=self.config,
|
||||
configItem=configItem,
|
||||
parent=self,
|
||||
)
|
||||
|
||||
self.table.setCellWidget(row, col, self.item_dict[group][name])
|
||||
|
||||
Layout = QVBoxLayout()
|
||||
Layout.addWidget(self.card_Name)
|
||||
Layout.addWidget(self.card_Mode)
|
||||
Layout.addWidget(self.table)
|
||||
|
||||
self.viewLayout.addLayout(Layout)
|
||||
self.viewLayout.setSpacing(3)
|
||||
self.viewLayout.setContentsMargins(3, 0, 3, 3)
|
||||
|
||||
self.card_Mode.comboBox.currentIndexChanged.connect(self.switch_mode)
|
||||
Config.gameid_refreshed.connect(self.refresh_gameid)
|
||||
|
||||
self.switch_mode()
|
||||
|
||||
def switch_mode(self) -> None:
|
||||
"""切换计划模式"""
|
||||
|
||||
for group, name_dict in self.item_dict.items():
|
||||
for name, setting_item in name_dict.items():
|
||||
setting_item.setEnabled(
|
||||
(group == "ALL")
|
||||
== (self.config.get(self.config.Info_Mode) == "ALL")
|
||||
)
|
||||
|
||||
def refresh_gameid(self):
|
||||
|
||||
for group, name_dict in self.item_dict.items():
|
||||
|
||||
for name, setting_item in name_dict.items():
|
||||
|
||||
if name == "GameId_Remain":
|
||||
|
||||
setting_item.reLoadOptions(
|
||||
Config.gameid_dict[group]["value"],
|
||||
[
|
||||
"不使用" if _ == "当前/上次" else _
|
||||
for _ in Config.gameid_dict[group]["text"]
|
||||
],
|
||||
)
|
||||
|
||||
elif "GameId" in name:
|
||||
|
||||
setting_item.reLoadOptions(
|
||||
Config.gameid_dict[group]["value"],
|
||||
Config.gameid_dict[group]["text"],
|
||||
)
|
||||
709
app/ui/queue_manager.py
Normal file
@@ -0,0 +1,709 @@
|
||||
# AUTO_MAA:A MAA Multi Account Management and Automation Tool
|
||||
# Copyright © 2024-2025 DLmaster361
|
||||
|
||||
# This file is part of AUTO_MAA.
|
||||
|
||||
# AUTO_MAA is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License,
|
||||
# or (at your option) any later version.
|
||||
|
||||
# AUTO_MAA is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
|
||||
# the GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# Contact: DLmaster_361@163.com
|
||||
|
||||
"""
|
||||
AUTO_MAA
|
||||
AUTO_MAA调度队列界面
|
||||
v4.3
|
||||
作者:DLmaster_361
|
||||
"""
|
||||
|
||||
from loguru import logger
|
||||
from PySide6.QtWidgets import (
|
||||
QWidget,
|
||||
QVBoxLayout,
|
||||
QStackedWidget,
|
||||
QHBoxLayout,
|
||||
)
|
||||
from qfluentwidgets import (
|
||||
Action,
|
||||
ScrollArea,
|
||||
FluentIcon,
|
||||
MessageBox,
|
||||
HeaderCardWidget,
|
||||
CommandBar,
|
||||
)
|
||||
from typing import List
|
||||
|
||||
from app.core import QueueConfig, Config, MainInfoBar, SoundPlayer
|
||||
from .Widget import (
|
||||
SwitchSettingCard,
|
||||
ComboBoxSettingCard,
|
||||
LineEditSettingCard,
|
||||
TimeEditSettingCard,
|
||||
NoOptionComboBoxSettingCard,
|
||||
HistoryCard,
|
||||
PivotArea,
|
||||
)
|
||||
|
||||
|
||||
class QueueManager(QWidget):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
self.setObjectName("调度队列")
|
||||
|
||||
layout = QVBoxLayout(self)
|
||||
|
||||
self.tools = CommandBar()
|
||||
|
||||
self.queue_manager = self.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(Config.queue_dict) + 1
|
||||
|
||||
queue_config = QueueConfig()
|
||||
queue_config.load(
|
||||
Config.app_path / f"config/QueueConfig/调度队列_{index}.json", queue_config
|
||||
)
|
||||
queue_config.save()
|
||||
|
||||
Config.queue_dict[f"调度队列_{index}"] = {
|
||||
"Path": Config.app_path / f"config/QueueConfig/调度队列_{index}.json",
|
||||
"Config": queue_config,
|
||||
}
|
||||
|
||||
self.queue_manager.add_QueueSettingBox(index)
|
||||
self.queue_manager.switch_SettingBox(index)
|
||||
|
||||
logger.success(f"调度队列_{index} 添加成功")
|
||||
MainInfoBar.push_info_bar("success", "操作成功", f"添加 调度队列_{index}", 3000)
|
||||
SoundPlayer.play("添加调度队列")
|
||||
|
||||
def del_setting_box(self):
|
||||
"""删除一个调度队列实例"""
|
||||
|
||||
name = self.queue_manager.pivot.currentRouteKey()
|
||||
|
||||
if name is 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.window())
|
||||
if choice.exec():
|
||||
|
||||
self.queue_manager.clear_SettingBox()
|
||||
|
||||
Config.queue_dict[name]["Path"].unlink()
|
||||
for i in range(int(name[5:]) + 1, len(Config.queue_dict) + 1):
|
||||
if Config.queue_dict[f"调度队列_{i}"]["Path"].exists():
|
||||
Config.queue_dict[f"调度队列_{i}"]["Path"].rename(
|
||||
Config.queue_dict[f"调度队列_{i}"]["Path"].with_name(
|
||||
f"调度队列_{i-1}.json"
|
||||
)
|
||||
)
|
||||
|
||||
self.queue_manager.show_SettingBox(max(int(name[5:]) - 1, 1))
|
||||
|
||||
logger.success(f"{name} 删除成功")
|
||||
MainInfoBar.push_info_bar("success", "操作成功", f"删除 {name}", 3000)
|
||||
SoundPlayer.play("删除调度队列")
|
||||
|
||||
def left_setting_box(self):
|
||||
"""向左移动调度队列实例"""
|
||||
|
||||
name = self.queue_manager.pivot.currentRouteKey()
|
||||
|
||||
if name is 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.queue_dict[name]["Path"].rename(
|
||||
Config.queue_dict[name]["Path"].with_name("调度队列_0.json")
|
||||
)
|
||||
Config.queue_dict[f"调度队列_{index-1}"]["Path"].rename(
|
||||
Config.queue_dict[name]["Path"]
|
||||
)
|
||||
Config.queue_dict[name]["Path"].with_name("调度队列_0.json").rename(
|
||||
Config.queue_dict[f"调度队列_{index-1}"]["Path"]
|
||||
)
|
||||
|
||||
self.queue_manager.show_SettingBox(index - 1)
|
||||
|
||||
logger.success(f"{name} 左移成功")
|
||||
MainInfoBar.push_info_bar("success", "操作成功", f"左移 {name}", 3000)
|
||||
|
||||
def right_setting_box(self):
|
||||
"""向右移动调度队列实例"""
|
||||
|
||||
name = self.queue_manager.pivot.currentRouteKey()
|
||||
|
||||
if name is None:
|
||||
logger.warning("未选择调度队列")
|
||||
MainInfoBar.push_info_bar(
|
||||
"warning", "未选择调度队列", "请先选择一个调度队列", 5000
|
||||
)
|
||||
return None
|
||||
|
||||
index = int(name[5:])
|
||||
|
||||
if index == len(Config.queue_dict):
|
||||
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.queue_dict[name]["Path"].rename(
|
||||
Config.queue_dict[name]["Path"].with_name("调度队列_0.json")
|
||||
)
|
||||
Config.queue_dict[f"调度队列_{index+1}"]["Path"].rename(
|
||||
Config.queue_dict[name]["Path"]
|
||||
)
|
||||
Config.queue_dict[name]["Path"].with_name("调度队列_0.json").rename(
|
||||
Config.queue_dict[f"调度队列_{index+1}"]["Path"]
|
||||
)
|
||||
|
||||
self.queue_manager.show_SettingBox(index + 1)
|
||||
|
||||
logger.success(f"{name} 右移成功")
|
||||
MainInfoBar.push_info_bar("success", "操作成功", f"右移 {name}", 3000)
|
||||
|
||||
def reload_member_name(self):
|
||||
"""刷新调度队列成员"""
|
||||
|
||||
member_list = [
|
||||
["禁用"] + [_ for _ in Config.member_dict.keys()],
|
||||
["未启用"]
|
||||
+ [
|
||||
(
|
||||
k
|
||||
if v["Config"].get(v["Config"].MaaSet_Name) == ""
|
||||
else f"{k} - {v["Config"].get(v["Config"].MaaSet_Name)}"
|
||||
)
|
||||
for k, v in Config.member_dict.items()
|
||||
],
|
||||
]
|
||||
for script in self.queue_manager.script_list:
|
||||
|
||||
script.task.card_Member_1.reLoadOptions(
|
||||
value=member_list[0], texts=member_list[1]
|
||||
)
|
||||
script.task.card_Member_2.reLoadOptions(
|
||||
value=member_list[0], texts=member_list[1]
|
||||
)
|
||||
script.task.card_Member_3.reLoadOptions(
|
||||
value=member_list[0], texts=member_list[1]
|
||||
)
|
||||
script.task.card_Member_4.reLoadOptions(
|
||||
value=member_list[0], texts=member_list[1]
|
||||
)
|
||||
script.task.card_Member_5.reLoadOptions(
|
||||
value=member_list[0], texts=member_list[1]
|
||||
)
|
||||
script.task.card_Member_6.reLoadOptions(
|
||||
value=member_list[0], texts=member_list[1]
|
||||
)
|
||||
script.task.card_Member_7.reLoadOptions(
|
||||
value=member_list[0], texts=member_list[1]
|
||||
)
|
||||
script.task.card_Member_8.reLoadOptions(
|
||||
value=member_list[0], texts=member_list[1]
|
||||
)
|
||||
script.task.card_Member_9.reLoadOptions(
|
||||
value=member_list[0], texts=member_list[1]
|
||||
)
|
||||
script.task.card_Member_10.reLoadOptions(
|
||||
value=member_list[0], texts=member_list[1]
|
||||
)
|
||||
|
||||
class QueueSettingBox(QWidget):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
self.setObjectName("调度队列管理")
|
||||
|
||||
self.pivotArea = PivotArea()
|
||||
self.pivot = self.pivotArea.pivot
|
||||
|
||||
self.stackedWidget = QStackedWidget(self)
|
||||
self.stackedWidget.setContentsMargins(0, 0, 0, 0)
|
||||
self.stackedWidget.setStyleSheet("background: transparent; border: none;")
|
||||
|
||||
self.script_list: List[
|
||||
QueueManager.QueueSettingBox.QueueMemberSettingBox
|
||||
] = []
|
||||
|
||||
self.Layout = QVBoxLayout(self)
|
||||
self.Layout.addWidget(self.pivotArea)
|
||||
self.Layout.addWidget(self.stackedWidget)
|
||||
self.Layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
self.pivot.currentItemChanged.connect(
|
||||
lambda index: self.switch_SettingBox(
|
||||
int(index[5:]), if_change_pivot=False
|
||||
)
|
||||
)
|
||||
|
||||
self.show_SettingBox(1)
|
||||
|
||||
def show_SettingBox(self, index) -> None:
|
||||
"""加载所有子界面"""
|
||||
|
||||
Config.search_queue()
|
||||
|
||||
for name in Config.queue_dict.keys():
|
||||
self.add_QueueSettingBox(int(name[5:]))
|
||||
|
||||
self.switch_SettingBox(index)
|
||||
|
||||
def switch_SettingBox(self, index: int, if_change_pivot: bool = True) -> None:
|
||||
"""切换到指定的子界面"""
|
||||
|
||||
if len(Config.queue_dict) == 0:
|
||||
return None
|
||||
|
||||
if index > len(Config.queue_dict):
|
||||
return None
|
||||
|
||||
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()
|
||||
|
||||
def add_QueueSettingBox(self, uid: int) -> None:
|
||||
"""添加一个调度队列设置界面"""
|
||||
|
||||
maa_setting_box = self.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}")
|
||||
|
||||
class QueueMemberSettingBox(QWidget):
|
||||
|
||||
def __init__(self, uid: int, parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
self.setObjectName(f"调度队列_{uid}")
|
||||
self.config = Config.queue_dict[f"调度队列_{uid}"]["Config"]
|
||||
|
||||
self.queue_set = self.QueueSetSettingCard(self.config, self)
|
||||
self.time = self.TimeSettingCard(self.config, self)
|
||||
self.task = self.TaskSettingCard(self.config, self)
|
||||
self.history = HistoryCard(
|
||||
qconfig=self.config,
|
||||
configItem=self.config.Data_LastProxyHistory,
|
||||
parent=self,
|
||||
)
|
||||
|
||||
content_widget = QWidget()
|
||||
content_layout = QVBoxLayout(content_widget)
|
||||
content_layout.setContentsMargins(0, 0, 11, 0)
|
||||
content_layout.addWidget(self.queue_set)
|
||||
content_layout.addWidget(self.time)
|
||||
content_layout.addWidget(self.task)
|
||||
content_layout.addWidget(self.history)
|
||||
content_layout.addStretch(1)
|
||||
|
||||
scrollArea = ScrollArea()
|
||||
scrollArea.setWidgetResizable(True)
|
||||
scrollArea.setContentsMargins(0, 0, 0, 0)
|
||||
scrollArea.setStyleSheet("background: transparent; border: none;")
|
||||
scrollArea.setWidget(content_widget)
|
||||
|
||||
layout = QVBoxLayout(self)
|
||||
layout.addWidget(scrollArea)
|
||||
|
||||
class QueueSetSettingCard(HeaderCardWidget):
|
||||
|
||||
def __init__(self, config: QueueConfig, parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
self.setTitle("队列设置")
|
||||
self.config = config
|
||||
|
||||
self.card_Name = LineEditSettingCard(
|
||||
icon=FluentIcon.EDIT,
|
||||
title="调度队列名称",
|
||||
content="用于标识调度队列的名称",
|
||||
text="请输入调度队列名称",
|
||||
qconfig=self.config,
|
||||
configItem=self.config.queueSet_Name,
|
||||
parent=self,
|
||||
)
|
||||
self.card_Enable = SwitchSettingCard(
|
||||
icon=FluentIcon.HOME,
|
||||
title="状态",
|
||||
content="调度队列状态,仅启用时会执行定时任务",
|
||||
qconfig=self.config,
|
||||
configItem=self.config.queueSet_Enabled,
|
||||
parent=self,
|
||||
)
|
||||
self.card_AfterAccomplish = ComboBoxSettingCard(
|
||||
icon=FluentIcon.POWER_BUTTON,
|
||||
title="调度队列结束后",
|
||||
content="选择调度队列结束后的操作",
|
||||
texts=[
|
||||
"无动作",
|
||||
"退出AUTO_MAA",
|
||||
"睡眠(win系统需禁用休眠)",
|
||||
"休眠",
|
||||
"关机",
|
||||
],
|
||||
qconfig=self.config,
|
||||
configItem=self.config.queueSet_AfterAccomplish,
|
||||
parent=self,
|
||||
)
|
||||
|
||||
Layout = QVBoxLayout()
|
||||
Layout.addWidget(self.card_Name)
|
||||
Layout.addWidget(self.card_Enable)
|
||||
Layout.addWidget(self.card_AfterAccomplish)
|
||||
|
||||
self.viewLayout.addLayout(Layout)
|
||||
|
||||
class TimeSettingCard(HeaderCardWidget):
|
||||
|
||||
def __init__(self, config: QueueConfig, parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
self.setTitle("定时设置")
|
||||
self.config = config
|
||||
|
||||
widget_1 = QWidget()
|
||||
Layout_1 = QVBoxLayout(widget_1)
|
||||
widget_2 = QWidget()
|
||||
Layout_2 = QVBoxLayout(widget_2)
|
||||
Layout = QHBoxLayout()
|
||||
|
||||
self.card_Time_0 = TimeEditSettingCard(
|
||||
icon=FluentIcon.STOP_WATCH,
|
||||
title="定时 1",
|
||||
content=None,
|
||||
qconfig=self.config,
|
||||
configItem_bool=self.config.time_TimeEnabled_0,
|
||||
configItem_time=self.config.time_TimeSet_0,
|
||||
parent=self,
|
||||
)
|
||||
self.card_Time_1 = TimeEditSettingCard(
|
||||
icon=FluentIcon.STOP_WATCH,
|
||||
title="定时 2",
|
||||
content=None,
|
||||
qconfig=self.config,
|
||||
configItem_bool=self.config.time_TimeEnabled_1,
|
||||
configItem_time=self.config.time_TimeSet_1,
|
||||
parent=self,
|
||||
)
|
||||
self.card_Time_2 = TimeEditSettingCard(
|
||||
icon=FluentIcon.STOP_WATCH,
|
||||
title="定时 3",
|
||||
content=None,
|
||||
qconfig=self.config,
|
||||
configItem_bool=self.config.time_TimeEnabled_2,
|
||||
configItem_time=self.config.time_TimeSet_2,
|
||||
parent=self,
|
||||
)
|
||||
self.card_Time_3 = TimeEditSettingCard(
|
||||
icon=FluentIcon.STOP_WATCH,
|
||||
title="定时 4",
|
||||
content=None,
|
||||
qconfig=self.config,
|
||||
configItem_bool=self.config.time_TimeEnabled_3,
|
||||
configItem_time=self.config.time_TimeSet_3,
|
||||
parent=self,
|
||||
)
|
||||
self.card_Time_4 = TimeEditSettingCard(
|
||||
icon=FluentIcon.STOP_WATCH,
|
||||
title="定时 5",
|
||||
content=None,
|
||||
qconfig=self.config,
|
||||
configItem_bool=self.config.time_TimeEnabled_4,
|
||||
configItem_time=self.config.time_TimeSet_4,
|
||||
parent=self,
|
||||
)
|
||||
self.card_Time_5 = TimeEditSettingCard(
|
||||
icon=FluentIcon.STOP_WATCH,
|
||||
title="定时 6",
|
||||
content=None,
|
||||
qconfig=self.config,
|
||||
configItem_bool=self.config.time_TimeEnabled_5,
|
||||
configItem_time=self.config.time_TimeSet_5,
|
||||
parent=self,
|
||||
)
|
||||
self.card_Time_6 = TimeEditSettingCard(
|
||||
icon=FluentIcon.STOP_WATCH,
|
||||
title="定时 7",
|
||||
content=None,
|
||||
qconfig=self.config,
|
||||
configItem_bool=self.config.time_TimeEnabled_6,
|
||||
configItem_time=self.config.time_TimeSet_6,
|
||||
parent=self,
|
||||
)
|
||||
self.card_Time_7 = TimeEditSettingCard(
|
||||
icon=FluentIcon.STOP_WATCH,
|
||||
title="定时 8",
|
||||
content=None,
|
||||
qconfig=self.config,
|
||||
configItem_bool=self.config.time_TimeEnabled_7,
|
||||
configItem_time=self.config.time_TimeSet_7,
|
||||
parent=self,
|
||||
)
|
||||
self.card_Time_8 = TimeEditSettingCard(
|
||||
icon=FluentIcon.STOP_WATCH,
|
||||
title="定时 9",
|
||||
content=None,
|
||||
qconfig=self.config,
|
||||
configItem_bool=self.config.time_TimeEnabled_8,
|
||||
configItem_time=self.config.time_TimeSet_8,
|
||||
parent=self,
|
||||
)
|
||||
self.card_Time_9 = TimeEditSettingCard(
|
||||
icon=FluentIcon.STOP_WATCH,
|
||||
title="定时 10",
|
||||
content=None,
|
||||
qconfig=self.config,
|
||||
configItem_bool=self.config.time_TimeEnabled_9,
|
||||
configItem_time=self.config.time_TimeSet_9,
|
||||
parent=self,
|
||||
)
|
||||
|
||||
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, config: QueueConfig, parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
self.setTitle("任务队列")
|
||||
self.config = config
|
||||
|
||||
member_list = [
|
||||
["禁用"] + [_ for _ in Config.member_dict.keys()],
|
||||
["未启用"]
|
||||
+ [
|
||||
(
|
||||
k
|
||||
if v["Config"].get(v["Config"].MaaSet_Name) == ""
|
||||
else f"{k} - {v["Config"].get(v["Config"].MaaSet_Name)}"
|
||||
)
|
||||
for k, v in Config.member_dict.items()
|
||||
],
|
||||
]
|
||||
|
||||
self.card_Member_1 = NoOptionComboBoxSettingCard(
|
||||
icon=FluentIcon.APPLICATION,
|
||||
title="任务实例 1",
|
||||
content="第一个调起的脚本任务实例",
|
||||
value=member_list[0],
|
||||
texts=member_list[1],
|
||||
qconfig=self.config,
|
||||
configItem=self.config.queue_Member_1,
|
||||
parent=self,
|
||||
)
|
||||
self.card_Member_2 = NoOptionComboBoxSettingCard(
|
||||
icon=FluentIcon.APPLICATION,
|
||||
title="任务实例 2",
|
||||
content="第二个调起的脚本任务实例",
|
||||
value=member_list[0],
|
||||
texts=member_list[1],
|
||||
qconfig=self.config,
|
||||
configItem=self.config.queue_Member_2,
|
||||
parent=self,
|
||||
)
|
||||
self.card_Member_3 = NoOptionComboBoxSettingCard(
|
||||
icon=FluentIcon.APPLICATION,
|
||||
title="任务实例 3",
|
||||
content="第三个调起的脚本任务实例",
|
||||
value=member_list[0],
|
||||
texts=member_list[1],
|
||||
qconfig=self.config,
|
||||
configItem=self.config.queue_Member_3,
|
||||
parent=self,
|
||||
)
|
||||
self.card_Member_4 = NoOptionComboBoxSettingCard(
|
||||
icon=FluentIcon.APPLICATION,
|
||||
title="任务实例 4",
|
||||
content="第四个调起的脚本任务实例",
|
||||
value=member_list[0],
|
||||
texts=member_list[1],
|
||||
qconfig=self.config,
|
||||
configItem=self.config.queue_Member_4,
|
||||
parent=self,
|
||||
)
|
||||
self.card_Member_5 = NoOptionComboBoxSettingCard(
|
||||
icon=FluentIcon.APPLICATION,
|
||||
title="任务实例 5",
|
||||
content="第五个调起的脚本任务实例",
|
||||
value=member_list[0],
|
||||
texts=member_list[1],
|
||||
qconfig=self.config,
|
||||
configItem=self.config.queue_Member_5,
|
||||
parent=self,
|
||||
)
|
||||
self.card_Member_6 = NoOptionComboBoxSettingCard(
|
||||
icon=FluentIcon.APPLICATION,
|
||||
title="任务实例 6",
|
||||
content="第六个调起的脚本任务实例",
|
||||
value=member_list[0],
|
||||
texts=member_list[1],
|
||||
qconfig=self.config,
|
||||
configItem=self.config.queue_Member_6,
|
||||
parent=self,
|
||||
)
|
||||
self.card_Member_7 = NoOptionComboBoxSettingCard(
|
||||
icon=FluentIcon.APPLICATION,
|
||||
title="任务实例 7",
|
||||
content="第七个调起的脚本任务实例",
|
||||
value=member_list[0],
|
||||
texts=member_list[1],
|
||||
qconfig=self.config,
|
||||
configItem=self.config.queue_Member_7,
|
||||
parent=self,
|
||||
)
|
||||
self.card_Member_8 = NoOptionComboBoxSettingCard(
|
||||
icon=FluentIcon.APPLICATION,
|
||||
title="任务实例 8",
|
||||
content="第八个调起的脚本任务实例",
|
||||
value=member_list[0],
|
||||
texts=member_list[1],
|
||||
qconfig=self.config,
|
||||
configItem=self.config.queue_Member_8,
|
||||
parent=self,
|
||||
)
|
||||
self.card_Member_9 = NoOptionComboBoxSettingCard(
|
||||
icon=FluentIcon.APPLICATION,
|
||||
title="任务实例 9",
|
||||
content="第九个调起的脚本任务实例",
|
||||
value=member_list[0],
|
||||
texts=member_list[1],
|
||||
qconfig=self.config,
|
||||
configItem=self.config.queue_Member_9,
|
||||
parent=self,
|
||||
)
|
||||
self.card_Member_10 = NoOptionComboBoxSettingCard(
|
||||
icon=FluentIcon.APPLICATION,
|
||||
title="任务实例 10",
|
||||
content="第十个调起的脚本任务实例",
|
||||
value=member_list[0],
|
||||
texts=member_list[1],
|
||||
qconfig=self.config,
|
||||
configItem=self.config.queue_Member_10,
|
||||
parent=self,
|
||||
)
|
||||
|
||||
Layout = QVBoxLayout()
|
||||
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)
|
||||
1228
app/ui/setting.py
Normal file
88
app/utils/AUTO_MAA.iss
Normal file
@@ -0,0 +1,88 @@
|
||||
; Script generated by the Inno Setup Script Wizard.
|
||||
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
|
||||
|
||||
#define MyAppName "AUTO_MAA"
|
||||
#define MyAppVersion ""
|
||||
#define MyAppPublisher "AUTO_MAA Team"
|
||||
#define MyAppURL "https://doc.automaa.xyz/"
|
||||
#define MyAppExeName "AUTO_MAA.exe"
|
||||
#define MyAppPath ""
|
||||
#define OutputDir ""
|
||||
|
||||
[Setup]
|
||||
; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications.
|
||||
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
|
||||
AppId={{D116A92A-E174-4699-B777-61C5FD837B19}
|
||||
AppName={#MyAppName}
|
||||
AppVersion={#MyAppVersion}
|
||||
AppVerName={#MyAppName}
|
||||
AppPublisher={#MyAppPublisher}
|
||||
AppPublisherURL={#MyAppURL}
|
||||
AppSupportURL={#MyAppURL}
|
||||
AppUpdatesURL={#MyAppURL}
|
||||
DefaultDirName=D:\{#MyAppName}
|
||||
UninstallDisplayIcon={app}\{#MyAppExeName}
|
||||
; "ArchitecturesAllowed=x64compatible" specifies that Setup cannot run
|
||||
; on anything but x64 and Windows 11 on Arm.
|
||||
ArchitecturesAllowed=x64compatible
|
||||
; "ArchitecturesInstallIn64BitMode=x64compatible" requests that the
|
||||
; install be done in "64-bit mode" on x64 or Windows 11 on Arm,
|
||||
; meaning it should use the native 64-bit Program Files directory and
|
||||
; the 64-bit view of the registry.
|
||||
ArchitecturesInstallIn64BitMode=x64compatible
|
||||
DisableProgramGroupPage=yes
|
||||
LicenseFile={#MyAppPath}\LICENSE
|
||||
; Remove the following line to run in administrative install mode (install for all users).
|
||||
PrivilegesRequired=lowest
|
||||
OutputDir={#OutputDir}
|
||||
OutputBaseFilename=AUTO_MAA-Setup
|
||||
SetupIconFile={#MyAppPath}\resources\icons\AUTO_MAA.ico
|
||||
SolidCompression=yes
|
||||
WizardStyle=modern
|
||||
|
||||
[Languages]
|
||||
Name: "Chinese"; MessagesFile: "{#MyAppPath}\resources\docs\ChineseSimplified.isl"
|
||||
Name: "English"; MessagesFile: "compiler:Default.isl"
|
||||
|
||||
[Tasks]
|
||||
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
|
||||
|
||||
[Files]
|
||||
Source: "{#MyAppPath}\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
|
||||
Source: "{#MyAppPath}\app\*"; DestDir: "{app}\app"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||
Source: "{#MyAppPath}\resources\*"; DestDir: "{app}\resources"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||
Source: "{#MyAppPath}\main.py"; DestDir: "{app}"; Flags: ignoreversion
|
||||
Source: "{#MyAppPath}\requirements.txt"; DestDir: "{app}"; Flags: ignoreversion
|
||||
Source: "{#MyAppPath}\README.md"; DestDir: "{app}"; Flags: ignoreversion
|
||||
Source: "{#MyAppPath}\LICENSE"; DestDir: "{app}"; Flags: ignoreversion
|
||||
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
|
||||
|
||||
[Icons]
|
||||
Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
|
||||
Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
|
||||
|
||||
[Run]
|
||||
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall
|
||||
|
||||
[Code]
|
||||
var
|
||||
DeleteDataQuestion: Boolean;
|
||||
|
||||
function InitializeUninstall: Boolean;
|
||||
begin
|
||||
DeleteDataQuestion := MsgBox('您确认要完全移除 AUTO_MAA 的所有用户数据文件与子组件吗?', mbConfirmation, MB_YESNO) = IDYES;
|
||||
Result := True;
|
||||
end;
|
||||
|
||||
procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
|
||||
begin
|
||||
if CurUninstallStep = usPostUninstall then
|
||||
begin
|
||||
DelTree(ExpandConstant('{app}\app'), True, True, True);
|
||||
DelTree(ExpandConstant('{app}\resources'), True, True, True);
|
||||
if DeleteDataQuestion then
|
||||
begin
|
||||
DelTree(ExpandConstant('{app}'), True, True, True);
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
@@ -1,5 +1,5 @@
|
||||
# <AUTO_MAA:A MAA Multi Account Management and Automation Tool>
|
||||
# Copyright © <2024> <DLmaster361>
|
||||
# AUTO_MAA:A MAA Multi Account Management and Automation Tool
|
||||
# Copyright © 2024-2025 DLmaster361
|
||||
|
||||
# This file is part of AUTO_MAA.
|
||||
|
||||
@@ -16,10 +16,17 @@
|
||||
# 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
|
||||
# Contact: DLmaster_361@163.com
|
||||
|
||||
import os
|
||||
"""
|
||||
AUTO_MAA
|
||||
AUTO_MAA工具包
|
||||
v4.3
|
||||
作者: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"
|
||||
|
||||
__all__ = []
|
||||
143
app/utils/package.py
Normal file
@@ -0,0 +1,143 @@
|
||||
# AUTO_MAA:A MAA Multi Account Management and Automation Tool
|
||||
# Copyright © 2024-2025 DLmaster361
|
||||
|
||||
# This file is part of AUTO_MAA.
|
||||
|
||||
# AUTO_MAA is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License,
|
||||
# or (at your option) any later version.
|
||||
|
||||
# AUTO_MAA is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
|
||||
# the GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# Contact: DLmaster_361@163.com
|
||||
|
||||
"""
|
||||
AUTO_MAA
|
||||
AUTO_MAA打包程序
|
||||
v4.3
|
||||
作者:DLmaster_361
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def version_text(version_numb: list) -> str:
|
||||
"""将版本号列表转为可读的文本信息"""
|
||||
|
||||
while len(version_numb) < 4:
|
||||
version_numb.append(0)
|
||||
|
||||
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
|
||||
|
||||
|
||||
def version_info_markdown(info: dict) -> str:
|
||||
"""将版本信息字典转为markdown信息"""
|
||||
|
||||
version_info = ""
|
||||
for key, value in info.items():
|
||||
version_info += f"## {key}\n"
|
||||
for v in value:
|
||||
version_info += f"- {v}\n"
|
||||
return version_info
|
||||
|
||||
|
||||
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(".")))
|
||||
|
||||
print("Packaging AUTO_MAA main program ...")
|
||||
|
||||
os.system(
|
||||
"powershell -Command python -m nuitka --standalone --onefile --mingw64"
|
||||
" --enable-plugins=pyside6 --windows-console-mode=attach"
|
||||
" --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-2025 DLmaster361'"
|
||||
" --assume-yes-for-downloads --output-filename=AUTO_MAA"
|
||||
" --remove-output main.py"
|
||||
)
|
||||
|
||||
print("AUTO_MAA main program packaging completed !")
|
||||
|
||||
print("start to create setup program ...")
|
||||
|
||||
(root_path / "AUTO_MAA").mkdir(parents=True, exist_ok=True)
|
||||
shutil.move(root_path / "AUTO_MAA.exe", root_path / "AUTO_MAA/")
|
||||
shutil.copytree(root_path / "app", root_path / "AUTO_MAA/app")
|
||||
shutil.copytree(root_path / "resources", root_path / "AUTO_MAA/resources")
|
||||
shutil.copy(root_path / "main.py", root_path / "AUTO_MAA/")
|
||||
shutil.copy(root_path / "requirements.txt", root_path / "AUTO_MAA/")
|
||||
shutil.copy(root_path / "README.md", root_path / "AUTO_MAA/")
|
||||
shutil.copy(root_path / "LICENSE", root_path / "AUTO_MAA/")
|
||||
|
||||
with (root_path / "app/utils/AUTO_MAA.iss").open(mode="r", encoding="utf-8") as f:
|
||||
iss = f.read()
|
||||
iss = (
|
||||
iss.replace(
|
||||
'#define MyAppVersion ""',
|
||||
f'#define MyAppVersion "{version["main_version"]}"',
|
||||
)
|
||||
.replace(
|
||||
'#define MyAppPath ""', f'#define MyAppPath "{root_path / "AUTO_MAA"}"'
|
||||
)
|
||||
.replace('#define OutputDir ""', f'#define OutputDir "{root_path}"')
|
||||
)
|
||||
with (root_path / "AUTO_MAA.iss").open(mode="w", encoding="utf-8") as f:
|
||||
f.write(iss)
|
||||
|
||||
os.system(f'ISCC "{root_path / "AUTO_MAA.iss"}"')
|
||||
|
||||
(root_path / "AUTO_MAA_Setup").mkdir(parents=True, exist_ok=True)
|
||||
shutil.move(root_path / "AUTO_MAA-Setup.exe", root_path / "AUTO_MAA_Setup")
|
||||
|
||||
shutil.make_archive(
|
||||
base_name=root_path / f"AUTO_MAA_{version_text(main_version_numb)}",
|
||||
format="zip",
|
||||
root_dir=root_path / "AUTO_MAA_Setup",
|
||||
base_dir=".",
|
||||
)
|
||||
|
||||
print("setup program created !")
|
||||
|
||||
(root_path / "AUTO_MAA.iss").unlink(missing_ok=True)
|
||||
shutil.rmtree(root_path / "AUTO_MAA")
|
||||
shutil.rmtree(root_path / "AUTO_MAA_Setup")
|
||||
|
||||
all_version_info = {}
|
||||
for v_i in version["version_info"].values():
|
||||
for key, value in v_i.items():
|
||||
if key in all_version_info:
|
||||
all_version_info[key] += value.copy()
|
||||
else:
|
||||
all_version_info[key] = value.copy()
|
||||
|
||||
(root_path / "version_info.txt").write_text(
|
||||
f"{version_text(main_version_numb)}\n\n<!--{json.dumps(version["version_info"], ensure_ascii=False)}-->\n{version_info_markdown(all_version_info)}",
|
||||
encoding="utf-8",
|
||||
)
|
||||
@@ -1,5 +0,0 @@
|
||||
龙门币:CE-6
|
||||
技能:CA-5
|
||||
红票:AP-5
|
||||
经验:LS-6
|
||||
剿灭模式:Annihilation
|
||||
978
gui/ui/main.ui
@@ -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><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||
<html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css">
|
||||
p, li { white-space: pre-wrap; }
|
||||
hr { height: 1px; border-width: 0; }
|
||||
li.unchecked::marker { content: "\2610"; }
|
||||
li.checked::marker { content: "\2612"; }
|
||||
</style></head><body style=" font-family:'Microsoft YaHei UI'; font-size:9pt; font-weight:400; font-style:normal;">
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">致用户:</p>
|
||||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> 这是AUTO_MAA_v3.1,项目基本完成可视化。</p>
|
||||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> 正式版去除了命令行窗口,但这不意味着BUG不会出现。由于用户与项目贡献者的稀缺,我们无法确保正式版足够完善,还望谅解。</p>
|
||||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> 您可以通过官方仓库 <a href="https://github.com/DLmaster361/AUTO_MAA/"><span style=" text-decoration: underline; color:#007ad6;">DLmaster361/AUTO_MAA</span></a> 发布Issues求助。</p>
|
||||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> 官方QQ群:957750551</p>
|
||||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p>
|
||||
<p align="right" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">DLmaster</p></body></html></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>
|
||||
69
main.py
Normal file
@@ -0,0 +1,69 @@
|
||||
# AUTO_MAA:A MAA Multi Account Management and Automation Tool
|
||||
# Copyright © 2024-2025 DLmaster361
|
||||
|
||||
# This file is part of AUTO_MAA.
|
||||
|
||||
# AUTO_MAA is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License,
|
||||
# or (at your option) any later version.
|
||||
|
||||
# AUTO_MAA is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
|
||||
# the GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# Contact: DLmaster_361@163.com
|
||||
|
||||
"""
|
||||
AUTO_MAA
|
||||
AUTO_MAA主程序
|
||||
v4.3
|
||||
作者:DLmaster_361
|
||||
"""
|
||||
|
||||
# 屏蔽广告
|
||||
import builtins
|
||||
|
||||
|
||||
def no_print(*args, **kwargs):
|
||||
if (
|
||||
args
|
||||
and isinstance(args[0], str)
|
||||
and "QFluentWidgets Pro is now released." in args[0]
|
||||
):
|
||||
return
|
||||
return __builtins__.print(*args, **kwargs)
|
||||
|
||||
|
||||
builtins.print = no_print
|
||||
|
||||
|
||||
from loguru import logger
|
||||
from PySide6.QtWidgets import QApplication
|
||||
from qfluentwidgets import FluentTranslator
|
||||
import sys
|
||||
|
||||
|
||||
@logger.catch
|
||||
def main():
|
||||
|
||||
application = QApplication(sys.argv)
|
||||
|
||||
translator = FluentTranslator()
|
||||
application.installTranslator(translator)
|
||||
|
||||
from app.ui.main_window import AUTO_MAA
|
||||
|
||||
window = AUTO_MAA()
|
||||
window.show_ui("显示主窗口", if_start=True)
|
||||
window.start_up_task()
|
||||
sys.exit(application.exec())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
main()
|
||||
@@ -1,3 +1,13 @@
|
||||
loguru
|
||||
plyer
|
||||
PySide6
|
||||
PySide6-Fluent-Widgets[full]
|
||||
psutil
|
||||
opencv-python
|
||||
pywin32
|
||||
pyautogui
|
||||
pycryptodome
|
||||
pyinstaller
|
||||
requests
|
||||
markdown
|
||||
Jinja2
|
||||
nuitka
|
||||
BIN
res/AUTO_MAA.ico
|
Before Width: | Height: | Size: 21 KiB |
BIN
res/AUTO_MAA.png
|
Before Width: | Height: | Size: 1.6 MiB |
|
Before Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 746 KiB |
43
res/info.txt
@@ -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')])
|
||||
])
|
||||
]
|
||||
)
|
||||
31
res/set.txt
@@ -1,31 +0,0 @@
|
||||
#主界面
|
||||
"MainFunction.ActionAfterCompleted": "ExitEmulatorAndSelf" #完成后
|
||||
"TaskQueue.WakeUp.IsChecked": "True" #开始唤醒
|
||||
"TaskQueue.Recruiting.IsChecked": "True" #自动公招
|
||||
"TaskQueue.Base.IsChecked": "True" #基建换班
|
||||
"TaskQueue.Combat.IsChecked": "True" #刷理智
|
||||
"TaskQueue.Mission.IsChecked": "True" #领取奖励
|
||||
"TaskQueue.Mall.IsChecked": "True" #获取信用及购物
|
||||
#刷理智
|
||||
"MainFunction.Stage1": "" #主关卡
|
||||
"MainFunction.Stage2": "" #备选关卡1
|
||||
"MainFunction.Stage3": "" #备选关卡2
|
||||
"Fight.RemainingSanityStage": "Annihilation" #剩余理智关卡
|
||||
"MainFunction.Series.Quantity": "1" #连战次数
|
||||
"Penguin.IsDrGrandet": "True" #博朗台模式
|
||||
"GUI.CustomStageCode": "False" #手动输入关卡名
|
||||
"GUI.UseAlternateStage": "False" #使用备选关卡
|
||||
"Fight.UseRemainingSanityStage": "True" #使用剩余理智
|
||||
"Fight.UseExpiringMedicine": "False" #无限吃48小时内过期的理智药
|
||||
"GUI.HideUnavailableStage": "False" #隐藏当日不开放关卡
|
||||
"Infrast.CustomInfrastPlanShowInFightSettings": "False" #显示基建计划
|
||||
"Penguin.EnablePenguin": "True" #上报企鹅物流
|
||||
"Yituliu.EnableYituliu": "True" #上报一图流
|
||||
#基建换班
|
||||
"Infrast.CustomInfrastEnabled": "True" #启用自定义基建配置
|
||||
"Infrast.DefaultInfrast": "user_defined" #内置配置
|
||||
"Infrast.IsCustomInfrastFileReadOnly": "False" #自定义基建配置文件只读
|
||||
"Infrast.CustomInfrastFile": "" #自定义基建配置文件地址
|
||||
#设置
|
||||
"Start.RunDirectly": "True" #启动MAA后直接运行
|
||||
"Start.StartEmulator": "True" #启动MAA后自动开启模拟器
|
||||
403
resources/docs/ChineseSimplified.isl
Normal file
@@ -0,0 +1,403 @@
|
||||
; *** Inno Setup version 6.4.0+ Chinese Simplified messages ***
|
||||
;
|
||||
; To download user-contributed translations of this file, go to:
|
||||
; https://jrsoftware.org/files/istrans/
|
||||
;
|
||||
; Note: When translating this text, do not add periods (.) to the end of
|
||||
; messages that didn't have them already, because on those messages Inno
|
||||
; Setup adds the periods automatically (appending a period would result in
|
||||
; two periods being displayed).
|
||||
;
|
||||
; Maintained by Zhenghan Yang
|
||||
; Email: 847320916@QQ.com
|
||||
; Translation based on network resource
|
||||
; The latest Translation is on https://github.com/kira-96/Inno-Setup-Chinese-Simplified-Translation
|
||||
;
|
||||
|
||||
[LangOptions]
|
||||
; The following three entries are very important. Be sure to read and
|
||||
; understand the '[LangOptions] section' topic in the help file.
|
||||
LanguageName=简体中文
|
||||
; If Language Name display incorrect, uncomment next line
|
||||
; LanguageName=<7B80><4F53><4E2D><6587>
|
||||
; About LanguageID, to reference link:
|
||||
; https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-lcid/a9eac961-e77d-41a6-90a5-ce1a8b0cdb9c
|
||||
LanguageID=$0804
|
||||
; About CodePage, to reference link:
|
||||
; https://docs.microsoft.com/en-us/windows/win32/intl/code-page-identifiers
|
||||
LanguageCodePage=936
|
||||
; If the language you are translating to requires special font faces or
|
||||
; sizes, uncomment any of the following entries and change them accordingly.
|
||||
;DialogFontName=
|
||||
;DialogFontSize=8
|
||||
;WelcomeFontName=Verdana
|
||||
;WelcomeFontSize=12
|
||||
;TitleFontName=Arial
|
||||
;TitleFontSize=29
|
||||
;CopyrightFontName=Arial
|
||||
;CopyrightFontSize=8
|
||||
|
||||
[Messages]
|
||||
|
||||
; *** 应用程序标题
|
||||
SetupAppTitle=安装
|
||||
SetupWindowTitle=安装 - %1
|
||||
UninstallAppTitle=卸载
|
||||
UninstallAppFullTitle=%1 卸载
|
||||
|
||||
; *** Misc. common
|
||||
InformationTitle=信息
|
||||
ConfirmTitle=确认
|
||||
ErrorTitle=错误
|
||||
|
||||
; *** SetupLdr messages
|
||||
SetupLdrStartupMessage=现在将安装 %1。您想要继续吗?
|
||||
LdrCannotCreateTemp=无法创建临时文件。安装程序已中止
|
||||
LdrCannotExecTemp=无法执行临时目录中的文件。安装程序已中止
|
||||
HelpTextNote=
|
||||
|
||||
; *** 启动错误消息
|
||||
LastErrorMessage=%1。%n%n错误 %2: %3
|
||||
SetupFileMissing=安装目录中缺少文件 %1。请修正这个问题或者获取程序的新副本。
|
||||
SetupFileCorrupt=安装文件已损坏。请获取程序的新副本。
|
||||
SetupFileCorruptOrWrongVer=安装文件已损坏,或是与这个安装程序的版本不兼容。请修正这个问题或获取新的程序副本。
|
||||
InvalidParameter=无效的命令行参数:%n%n%1
|
||||
SetupAlreadyRunning=安装程序正在运行。
|
||||
WindowsVersionNotSupported=此程序不支持当前计算机运行的 Windows 版本。
|
||||
WindowsServicePackRequired=此程序需要 %1 服务包 %2 或更高版本。
|
||||
NotOnThisPlatform=此程序不能在 %1 上运行。
|
||||
OnlyOnThisPlatform=此程序只能在 %1 上运行。
|
||||
OnlyOnTheseArchitectures=此程序只能安装到为下列处理器架构设计的 Windows 版本中:%n%n%1
|
||||
WinVersionTooLowError=此程序需要 %1 版本 %2 或更高。
|
||||
WinVersionTooHighError=此程序不能安装于 %1 版本 %2 或更高。
|
||||
AdminPrivilegesRequired=在安装此程序时您必须以管理员身份登录。
|
||||
PowerUserPrivilegesRequired=在安装此程序时您必须以管理员身份或有权限的用户组身份登录。
|
||||
SetupAppRunningError=安装程序发现 %1 当前正在运行。%n%n请先关闭正在运行的程序,然后点击“确定”继续,或点击“取消”退出。
|
||||
UninstallAppRunningError=卸载程序发现 %1 当前正在运行。%n%n请先关闭正在运行的程序,然后点击“确定”继续,或点击“取消”退出。
|
||||
|
||||
; *** 启动问题
|
||||
PrivilegesRequiredOverrideTitle=选择安装程序模式
|
||||
PrivilegesRequiredOverrideInstruction=选择安装模式
|
||||
PrivilegesRequiredOverrideText1=%1 可以为所有用户安装(需要管理员权限),或仅为您安装。
|
||||
PrivilegesRequiredOverrideText2=%1 只能为您安装,或为所有用户安装(需要管理员权限)。
|
||||
PrivilegesRequiredOverrideAllUsers=为所有用户安装(&A)
|
||||
PrivilegesRequiredOverrideAllUsersRecommended=为所有用户安装(&A) (建议选项)
|
||||
PrivilegesRequiredOverrideCurrentUser=只为我安装(&M)
|
||||
PrivilegesRequiredOverrideCurrentUserRecommended=只为我安装(&M) (建议选项)
|
||||
|
||||
; *** 其他错误
|
||||
ErrorCreatingDir=安装程序无法创建目录“%1”
|
||||
ErrorTooManyFilesInDir=无法在目录“%1”中创建文件,因为里面包含太多文件
|
||||
|
||||
; *** 安装程序公共消息
|
||||
ExitSetupTitle=退出安装程序
|
||||
ExitSetupMessage=安装程序尚未完成。如果现在退出,将不会安装该程序。%n%n您之后可以再次运行安装程序完成安装。%n%n现在退出安装程序吗?
|
||||
AboutSetupMenuItem=关于安装程序(&A)...
|
||||
AboutSetupTitle=关于安装程序
|
||||
AboutSetupMessage=%1 版本 %2%n%3%n%n%1 主页:%n%4
|
||||
AboutSetupNote=
|
||||
TranslatorNote=简体中文翻译由Kira(847320916@qq.com)维护。项目地址:https://github.com/kira-96/Inno-Setup-Chinese-Simplified-Translation
|
||||
|
||||
; *** 按钮
|
||||
ButtonBack=< 上一步(&B)
|
||||
ButtonNext=下一步(&N) >
|
||||
ButtonInstall=安装(&I)
|
||||
ButtonOK=确定
|
||||
ButtonCancel=取消
|
||||
ButtonYes=是(&Y)
|
||||
ButtonYesToAll=全是(&A)
|
||||
ButtonNo=否(&N)
|
||||
ButtonNoToAll=全否(&O)
|
||||
ButtonFinish=完成(&F)
|
||||
ButtonBrowse=浏览(&B)...
|
||||
ButtonWizardBrowse=浏览(&R)...
|
||||
ButtonNewFolder=新建文件夹(&M)
|
||||
|
||||
; *** “选择语言”对话框消息
|
||||
SelectLanguageTitle=选择安装语言
|
||||
SelectLanguageLabel=选择安装时使用的语言。
|
||||
|
||||
; *** 公共向导文字
|
||||
ClickNext=点击“下一步”继续,或点击“取消”退出安装程序。
|
||||
BeveledLabel=
|
||||
BrowseDialogTitle=浏览文件夹
|
||||
BrowseDialogLabel=在下面的列表中选择一个文件夹,然后点击“确定”。
|
||||
NewFolderName=新建文件夹
|
||||
|
||||
; *** “欢迎”向导页
|
||||
WelcomeLabel1=欢迎使用 [name] 安装向导
|
||||
WelcomeLabel2=现在将安装 [name/ver] 到您的电脑中。%n%n建议您在继续安装前关闭所有其他应用程序。
|
||||
|
||||
; *** “密码”向导页
|
||||
WizardPassword=密码
|
||||
PasswordLabel1=这个安装程序有密码保护。
|
||||
PasswordLabel3=请输入密码,然后点击“下一步”继续。密码区分大小写。
|
||||
PasswordEditLabel=密码(&P):
|
||||
IncorrectPassword=您输入的密码不正确,请重新输入。
|
||||
|
||||
; *** “许可协议”向导页
|
||||
WizardLicense=许可协议
|
||||
LicenseLabel=请在继续安装前阅读以下重要信息。
|
||||
LicenseLabel3=请仔细阅读下列许可协议。在继续安装前您必须同意这些协议条款。
|
||||
LicenseAccepted=我同意此协议(&A)
|
||||
LicenseNotAccepted=我不同意此协议(&D)
|
||||
|
||||
; *** “信息”向导页
|
||||
WizardInfoBefore=信息
|
||||
InfoBeforeLabel=请在继续安装前阅读以下重要信息。
|
||||
InfoBeforeClickLabel=准备好继续安装后,点击“下一步”。
|
||||
WizardInfoAfter=信息
|
||||
InfoAfterLabel=请在继续安装前阅读以下重要信息。
|
||||
InfoAfterClickLabel=准备好继续安装后,点击“下一步”。
|
||||
|
||||
; *** “用户信息”向导页
|
||||
WizardUserInfo=用户信息
|
||||
UserInfoDesc=请输入您的信息。
|
||||
UserInfoName=用户名(&U):
|
||||
UserInfoOrg=组织(&O):
|
||||
UserInfoSerial=序列号(&S):
|
||||
UserInfoNameRequired=您必须输入用户名。
|
||||
|
||||
; *** “选择目标目录”向导页
|
||||
WizardSelectDir=选择目标位置
|
||||
SelectDirDesc=您想将 [name] 安装在哪里?
|
||||
SelectDirLabel3=安装程序将安装 [name] 到下面的文件夹中。
|
||||
SelectDirBrowseLabel=点击“下一步”继续。如果您想选择其他文件夹,点击“浏览”。
|
||||
DiskSpaceGBLabel=至少需要有 [gb] GB 的可用磁盘空间。
|
||||
DiskSpaceMBLabel=至少需要有 [mb] MB 的可用磁盘空间。
|
||||
CannotInstallToNetworkDrive=安装程序无法安装到一个网络驱动器。
|
||||
CannotInstallToUNCPath=安装程序无法安装到一个 UNC 路径。
|
||||
InvalidPath=您必须输入一个带驱动器卷标的完整路径,例如:%n%nC:\APP%n%n或UNC路径:%n%n\\server\share
|
||||
InvalidDrive=您选定的驱动器或 UNC 共享不存在或不能访问。请选择其他位置。
|
||||
DiskSpaceWarningTitle=磁盘空间不足
|
||||
DiskSpaceWarning=安装程序至少需要 %1 KB 的可用空间才能安装,但选定驱动器只有 %2 KB 的可用空间。%n%n您一定要继续吗?
|
||||
DirNameTooLong=文件夹名称或路径太长。
|
||||
InvalidDirName=文件夹名称无效。
|
||||
BadDirName32=文件夹名称不能包含下列任何字符:%n%n%1
|
||||
DirExistsTitle=文件夹已存在
|
||||
DirExists=文件夹:%n%n%1%n%n已经存在。您一定要安装到这个文件夹中吗?
|
||||
DirDoesntExistTitle=文件夹不存在
|
||||
DirDoesntExist=文件夹:%n%n%1%n%n不存在。您想要创建此文件夹吗?
|
||||
|
||||
; *** “选择组件”向导页
|
||||
WizardSelectComponents=选择组件
|
||||
SelectComponentsDesc=您想安装哪些程序组件?
|
||||
SelectComponentsLabel2=选中您想安装的组件;取消您不想安装的组件。然后点击“下一步”继续。
|
||||
FullInstallation=完全安装
|
||||
; if possible don't translate 'Compact' as 'Minimal' (I mean 'Minimal' in your language)
|
||||
CompactInstallation=简洁安装
|
||||
CustomInstallation=自定义安装
|
||||
NoUninstallWarningTitle=组件已存在
|
||||
NoUninstallWarning=安装程序检测到下列组件已安装在您的电脑中:%n%n%1%n%n取消选中这些组件不会卸载它们。%n%n确定要继续吗?
|
||||
ComponentSize1=%1 KB
|
||||
ComponentSize2=%1 MB
|
||||
ComponentsDiskSpaceGBLabel=当前选择的组件需要至少 [gb] GB 的磁盘空间。
|
||||
ComponentsDiskSpaceMBLabel=当前选择的组件需要至少 [mb] MB 的磁盘空间。
|
||||
|
||||
; *** “选择附加任务”向导页
|
||||
WizardSelectTasks=选择附加任务
|
||||
SelectTasksDesc=您想要安装程序执行哪些附加任务?
|
||||
SelectTasksLabel2=选择您想要安装程序在安装 [name] 时执行的附加任务,然后点击“下一步”。
|
||||
|
||||
; *** “选择开始菜单文件夹”向导页
|
||||
WizardSelectProgramGroup=选择开始菜单文件夹
|
||||
SelectStartMenuFolderDesc=安装程序应该在哪里放置程序的快捷方式?
|
||||
SelectStartMenuFolderLabel3=安装程序将在下列“开始”菜单文件夹中创建程序的快捷方式。
|
||||
SelectStartMenuFolderBrowseLabel=点击“下一步”继续。如果您想选择其他文件夹,点击“浏览”。
|
||||
MustEnterGroupName=您必须输入一个文件夹名。
|
||||
GroupNameTooLong=文件夹名或路径太长。
|
||||
InvalidGroupName=无效的文件夹名字。
|
||||
BadGroupName=文件夹名不能包含下列任何字符:%n%n%1
|
||||
NoProgramGroupCheck2=不创建开始菜单文件夹(&D)
|
||||
|
||||
; *** “准备安装”向导页
|
||||
WizardReady=准备安装
|
||||
ReadyLabel1=安装程序准备就绪,现在可以开始安装 [name] 到您的电脑。
|
||||
ReadyLabel2a=点击“安装”继续此安装程序。如果您想重新考虑或修改任何设置,点击“上一步”。
|
||||
ReadyLabel2b=点击“安装”继续此安装程序。
|
||||
ReadyMemoUserInfo=用户信息:
|
||||
ReadyMemoDir=目标位置:
|
||||
ReadyMemoType=安装类型:
|
||||
ReadyMemoComponents=已选择组件:
|
||||
ReadyMemoGroup=开始菜单文件夹:
|
||||
ReadyMemoTasks=附加任务:
|
||||
|
||||
; *** TExtractionWizardPage wizard page and Extract7ZipArchive
|
||||
ExtractionLabel=正在提取附加文件...
|
||||
ButtonStopExtraction=停止提取(&S)
|
||||
StopExtraction=您确定要停止提取吗?
|
||||
ErrorExtractionAborted=提取已中止
|
||||
ErrorExtractionFailed=提取失败:%1
|
||||
|
||||
; *** TDownloadWizardPage wizard page and DownloadTemporaryFile
|
||||
DownloadingLabel=正在下载附加文件...
|
||||
ButtonStopDownload=停止下载(&S)
|
||||
StopDownload=您确定要停止下载吗?
|
||||
ErrorDownloadAborted=下载已中止
|
||||
ErrorDownloadFailed=下载失败:%1 %2
|
||||
ErrorDownloadSizeFailed=获取下载大小失败:%1 %2
|
||||
ErrorFileHash1=校验文件哈希失败:%1
|
||||
ErrorFileHash2=无效的文件哈希:预期 %1,实际 %2
|
||||
ErrorProgress=无效的进度:%1 / %2
|
||||
ErrorFileSize=文件大小错误:预期 %1,实际 %2
|
||||
|
||||
; *** “正在准备安装”向导页
|
||||
WizardPreparing=正在准备安装
|
||||
PreparingDesc=安装程序正在准备安装 [name] 到您的电脑。
|
||||
PreviousInstallNotCompleted=先前的程序安装或卸载未完成,您需要重启您的电脑以完成。%n%n在重启电脑后,再次运行安装程序以完成 [name] 的安装。
|
||||
CannotContinue=安装程序不能继续。请点击“取消”退出。
|
||||
ApplicationsFound=以下应用程序正在使用将由安装程序更新的文件。建议您允许安装程序自动关闭这些应用程序。
|
||||
ApplicationsFound2=以下应用程序正在使用将由安装程序更新的文件。建议您允许安装程序自动关闭这些应用程序。安装完成后,安装程序将尝试重新启动这些应用程序。
|
||||
CloseApplications=自动关闭应用程序(&A)
|
||||
DontCloseApplications=不要关闭应用程序(&D)
|
||||
ErrorCloseApplications=安装程序无法自动关闭所有应用程序。建议您在继续之前,关闭所有在使用需要由安装程序更新的文件的应用程序。
|
||||
PrepareToInstallNeedsRestart=安装程序必须重启您的计算机。计算机重启后,请再次运行安装程序以完成 [name] 的安装。%n%n是否立即重新启动?
|
||||
|
||||
; *** “正在安装”向导页
|
||||
WizardInstalling=正在安装
|
||||
InstallingLabel=安装程序正在安装 [name] 到您的电脑,请稍候。
|
||||
|
||||
; *** “安装完成”向导页
|
||||
FinishedHeadingLabel=[name] 安装完成
|
||||
FinishedLabelNoIcons=安装程序已在您的电脑中安装了 [name]。
|
||||
FinishedLabel=安装程序已在您的电脑中安装了 [name]。您可以通过已安装的快捷方式运行此应用程序。
|
||||
ClickFinish=点击“完成”退出安装程序。
|
||||
FinishedRestartLabel=为完成 [name] 的安装,安装程序必须重新启动您的电脑。要立即重启吗?
|
||||
FinishedRestartMessage=为完成 [name] 的安装,安装程序必须重新启动您的电脑。%n%n要立即重启吗?
|
||||
ShowReadmeCheck=是,我想查阅自述文件
|
||||
YesRadio=是,立即重启电脑(&Y)
|
||||
NoRadio=否,稍后重启电脑(&N)
|
||||
; used for example as 'Run MyProg.exe'
|
||||
RunEntryExec=运行 %1
|
||||
; used for example as 'View Readme.txt'
|
||||
RunEntryShellExec=查阅 %1
|
||||
|
||||
; *** “安装程序需要下一张磁盘”提示
|
||||
ChangeDiskTitle=安装程序需要下一张磁盘
|
||||
SelectDiskLabel2=请插入磁盘 %1 并点击“确定”。%n%n如果这个磁盘中的文件可以在下列文件夹之外的文件夹中找到,请输入正确的路径或点击“浏览”。
|
||||
PathLabel=路径(&P):
|
||||
FileNotInDir2=“%2”中找不到文件“%1”。请插入正确的磁盘或选择其他文件夹。
|
||||
SelectDirectoryLabel=请指定下一张磁盘的位置。
|
||||
|
||||
; *** 安装状态消息
|
||||
SetupAborted=安装程序未完成安装。%n%n请修正这个问题并重新运行安装程序。
|
||||
AbortRetryIgnoreSelectAction=选择操作
|
||||
AbortRetryIgnoreRetry=重试(&T)
|
||||
AbortRetryIgnoreIgnore=忽略错误并继续(&I)
|
||||
AbortRetryIgnoreCancel=关闭安装程序
|
||||
|
||||
; *** 安装状态消息
|
||||
StatusClosingApplications=正在关闭应用程序...
|
||||
StatusCreateDirs=正在创建目录...
|
||||
StatusExtractFiles=正在解压缩文件...
|
||||
StatusCreateIcons=正在创建快捷方式...
|
||||
StatusCreateIniEntries=正在创建 INI 条目...
|
||||
StatusCreateRegistryEntries=正在创建注册表条目...
|
||||
StatusRegisterFiles=正在注册文件...
|
||||
StatusSavingUninstall=正在保存卸载信息...
|
||||
StatusRunProgram=正在完成安装...
|
||||
StatusRestartingApplications=正在重启应用程序...
|
||||
StatusRollback=正在撤销更改...
|
||||
|
||||
; *** 其他错误
|
||||
ErrorInternal2=内部错误:%1
|
||||
ErrorFunctionFailedNoCode=%1 失败
|
||||
ErrorFunctionFailed=%1 失败;错误代码 %2
|
||||
ErrorFunctionFailedWithMessage=%1 失败;错误代码 %2.%n%3
|
||||
ErrorExecutingProgram=无法执行文件:%n%1
|
||||
|
||||
; *** 注册表错误
|
||||
ErrorRegOpenKey=打开注册表项时出错:%n%1\%2
|
||||
ErrorRegCreateKey=创建注册表项时出错:%n%1\%2
|
||||
ErrorRegWriteKey=写入注册表项时出错:%n%1\%2
|
||||
|
||||
; *** INI 错误
|
||||
ErrorIniEntry=在文件“%1”中创建 INI 条目时出错。
|
||||
|
||||
; *** 文件复制错误
|
||||
FileAbortRetryIgnoreSkipNotRecommended=跳过此文件(&S) (不推荐)
|
||||
FileAbortRetryIgnoreIgnoreNotRecommended=忽略错误并继续(&I) (不推荐)
|
||||
SourceIsCorrupted=源文件已损坏
|
||||
SourceDoesntExist=源文件“%1”不存在
|
||||
ExistingFileReadOnly2=无法替换现有文件,它是只读的。
|
||||
ExistingFileReadOnlyRetry=移除只读属性并重试(&R)
|
||||
ExistingFileReadOnlyKeepExisting=保留现有文件(&K)
|
||||
ErrorReadingExistingDest=尝试读取现有文件时出错:
|
||||
FileExistsSelectAction=选择操作
|
||||
FileExists2=文件已经存在。
|
||||
FileExistsOverwriteExisting=覆盖已存在的文件(&O)
|
||||
FileExistsKeepExisting=保留现有的文件(&K)
|
||||
FileExistsOverwriteOrKeepAll=为所有冲突文件执行此操作(&D)
|
||||
ExistingFileNewerSelectAction=选择操作
|
||||
ExistingFileNewer2=现有的文件比安装程序将要安装的文件还要新。
|
||||
ExistingFileNewerOverwriteExisting=覆盖已存在的文件(&O)
|
||||
ExistingFileNewerKeepExisting=保留现有的文件(&K) (推荐)
|
||||
ExistingFileNewerOverwriteOrKeepAll=为所有冲突文件执行此操作(&D)
|
||||
ErrorChangingAttr=尝试更改下列现有文件的属性时出错:
|
||||
ErrorCreatingTemp=尝试在目标目录创建文件时出错:
|
||||
ErrorReadingSource=尝试读取下列源文件时出错:
|
||||
ErrorCopying=尝试复制下列文件时出错:
|
||||
ErrorReplacingExistingFile=尝试替换现有文件时出错:
|
||||
ErrorRestartReplace=重启并替换失败:
|
||||
ErrorRenamingTemp=尝试重命名下列目标目录中的一个文件时出错:
|
||||
ErrorRegisterServer=无法注册 DLL/OCX:%1
|
||||
ErrorRegSvr32Failed=RegSvr32 失败;退出代码 %1
|
||||
ErrorRegisterTypeLib=无法注册类库:%1
|
||||
|
||||
; *** 卸载显示名字标记
|
||||
; used for example as 'My Program (32-bit)'
|
||||
UninstallDisplayNameMark=%1 (%2)
|
||||
; used for example as 'My Program (32-bit, All users)'
|
||||
UninstallDisplayNameMarks=%1 (%2, %3)
|
||||
UninstallDisplayNameMark32Bit=32 位
|
||||
UninstallDisplayNameMark64Bit=64 位
|
||||
UninstallDisplayNameMarkAllUsers=所有用户
|
||||
UninstallDisplayNameMarkCurrentUser=当前用户
|
||||
|
||||
; *** 安装后错误
|
||||
ErrorOpeningReadme=尝试打开自述文件时出错。
|
||||
ErrorRestartingComputer=安装程序无法重启电脑,请手动重启。
|
||||
|
||||
; *** 卸载消息
|
||||
UninstallNotFound=文件“%1”不存在。无法卸载。
|
||||
UninstallOpenError=文件“%1”不能被打开。无法卸载。
|
||||
UninstallUnsupportedVer=此版本的卸载程序无法识别卸载日志文件“%1”的格式。无法卸载
|
||||
UninstallUnknownEntry=卸载日志中遇到一个未知条目 (%1)
|
||||
ConfirmUninstall=您确认要完全移除 %1 及其所有组件吗?
|
||||
UninstallOnlyOnWin64=仅允许在 64 位 Windows 中卸载此程序。
|
||||
OnlyAdminCanUninstall=仅使用管理员权限的用户能完成此卸载。
|
||||
UninstallStatusLabel=正在从您的电脑中移除 %1,请稍候。
|
||||
UninstalledAll=已顺利从您的电脑中移除 %1。
|
||||
UninstalledMost=%1 卸载完成。%n%n有部分内容未能被删除,但您可以手动删除它们。
|
||||
UninstalledAndNeedsRestart=为完成 %1 的卸载,需要重启您的电脑。%n%n立即重启电脑吗?
|
||||
UninstallDataCorrupted=文件“%1”已损坏。无法卸载
|
||||
|
||||
; *** 卸载状态消息
|
||||
ConfirmDeleteSharedFileTitle=删除共享的文件吗?
|
||||
ConfirmDeleteSharedFile2=系统表示下列共享的文件已不有其他程序使用。您希望卸载程序删除这些共享的文件吗?%n%n如果删除这些文件,但仍有程序在使用这些文件,则这些程序可能出现异常。如果您不能确定,请选择“否”,在系统中保留这些文件以免引发问题。
|
||||
SharedFileNameLabel=文件名:
|
||||
SharedFileLocationLabel=位置:
|
||||
WizardUninstalling=卸载状态
|
||||
StatusUninstalling=正在卸载 %1...
|
||||
|
||||
; *** Shutdown block reasons
|
||||
ShutdownBlockReasonInstallingApp=正在安装 %1。
|
||||
ShutdownBlockReasonUninstallingApp=正在卸载 %1。
|
||||
|
||||
; The custom messages below aren't used by Setup itself, but if you make
|
||||
; use of them in your scripts, you'll want to translate them.
|
||||
|
||||
[CustomMessages]
|
||||
|
||||
NameAndVersion=%1 版本 %2
|
||||
AdditionalIcons=附加快捷方式:
|
||||
CreateDesktopIcon=创建桌面快捷方式(&D)
|
||||
CreateQuickLaunchIcon=创建快速启动栏快捷方式(&Q)
|
||||
ProgramOnTheWeb=%1 网站
|
||||
UninstallProgram=卸载 %1
|
||||
LaunchProgram=运行 %1
|
||||
AssocFileExtension=将 %2 文件扩展名与 %1 建立关联(&A)
|
||||
AssocingFileExtension=正在将 %2 文件扩展名与 %1 建立关联...
|
||||
AutoStartProgramGroupDescription=启动:
|
||||
AutoStartProgram=自动启动 %1
|
||||
AddonHostProgramNotFound=您选择的文件夹中无法找到 %1。%n%n您要继续吗?
|
||||
62
resources/docs/MAA_config_info.txt
Normal file
@@ -0,0 +1,62 @@
|
||||
#主界面
|
||||
"MainFunction.PostActions": "8" #完成后退出MAA
|
||||
"MainFunction.PostActions": "9" #完成后退出MAA和游戏
|
||||
"MainFunction.PostActions": "12" #完成后退出MAA和模拟器
|
||||
"TaskQueue.WakeUp.IsChecked": "True" #开始唤醒
|
||||
"TaskQueue.Recruiting.IsChecked": "True" #自动公招
|
||||
"TaskQueue.Base.IsChecked": "True" #基建换班
|
||||
"TaskQueue.Combat.IsChecked": "True" #刷理智
|
||||
"TaskQueue.Mall.IsChecked": "True" #获取信用及购物
|
||||
"TaskQueue.Mission.IsChecked": "True" #领取奖励
|
||||
"TaskQueue.AutoRoguelike.IsChecked": "False" #自动肉鸽
|
||||
"TaskQueue.Reclamation.IsChecked": "False" #生息演算
|
||||
"TaskQueue.Order.WakeUp": "0"
|
||||
"TaskQueue.Order.Recruiting": "1"
|
||||
"TaskQueue.Order.Base": "2"
|
||||
"TaskQueue.Order.Combat": "3"
|
||||
"TaskQueue.Order.Mall": "4"
|
||||
"TaskQueue.Order.Mission": "5"
|
||||
"TaskQueue.Order.AutoRoguelike": "6"
|
||||
"TaskQueue.Order.Reclamation": "7"
|
||||
#刷理智
|
||||
"MainFunction.UseMedicine": "True" #吃理智药
|
||||
"MainFunction.UseMedicine.Quantity": "999" #吃理智药数量
|
||||
"MainFunction.Stage1": "" #主关卡
|
||||
"MainFunction.Stage2": "" #备选关卡1
|
||||
"MainFunction.Stage3": "" #备选关卡2
|
||||
"Fight.RemainingSanityStage": "Annihilation" #剩余理智关卡
|
||||
"MainFunction.Series.Quantity": "1" #连战次数
|
||||
"Penguin.IsDrGrandet": "True" #博朗台模式
|
||||
"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.InfrastMode": "Normal"、"Rotation"、"Custom" #基建模式
|
||||
"Infrast.CustomInfrastPlanIndex": "1" #自定义基建配置索引号
|
||||
"Infrast.DefaultInfrast": "user_defined" #内置配置
|
||||
"Infrast.IsCustomInfrastFileReadOnly": "False" #自定义基建配置文件只读
|
||||
"Infrast.CustomInfrastFile": "" #自定义基建配置文件地址
|
||||
#设置
|
||||
"Start.ClientType": "Bilibili"、 "Official" #服务器
|
||||
G"Timer.Timer1": "False" #时间设置1
|
||||
"Connect.AdbPath" #ADB路径
|
||||
"Connect.Address": "127.0.0.1:16448" #连接地址
|
||||
G"VersionUpdate.ScheduledUpdateCheck": "True" #定时检查更新
|
||||
G"VersionUpdate.AutoDownloadUpdatePackage": "True" #自动下载更新包
|
||||
G"VersionUpdate.AutoInstallUpdatePackage": "True" #自动安装更新包
|
||||
G"Start.MinimizeDirectly": "True" #启动MAA后直接最小化
|
||||
"Start.RunDirectly": "True" #启动MAA后直接运行
|
||||
"Start.OpenEmulatorAfterLaunch": "True" #启动MAA后自动开启模拟器
|
||||
G"GUI.UseTray": "True" #显示托盘图标
|
||||
G"GUI.MinimizeToTray": "False" #最小化时隐藏至托盘
|
||||
"Start.EmulatorPath" #模拟器路径
|
||||
"Start.EmulatorAddCommand": "-v 2" #附加命令
|
||||
"Start.EmulatorWaitSeconds": "10" #等待模拟器启动时间
|
||||
G"VersionUpdate.package": "MirrorChyanAppv5.15.6.zip" #更新包标识
|
||||
160
resources/html/MAA_result.html
Normal file
26
resources/html/MAA_six_star.html
Normal file
233
resources/html/MAA_statistics.html
Normal file
BIN
resources/icons/AUTO_MAA.ico
Normal file
|
After Width: | Height: | Size: 51 KiB |
BIN
resources/icons/AUTO_MAA_Updater.ico
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
resources/icons/MirrorChyan.ico
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
resources/images/AUTO_MAA.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
resources/images/Home/BannerDefault.png
Normal file
|
After Width: | Height: | Size: 2.8 MiB |
BIN
resources/images/README/payid.png
Normal file
|
After Width: | Height: | Size: 309 KiB |