Compare commits
133 Commits
v4.3.5-bet
...
v4.3.10
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fb979e5639 | ||
|
|
e7d0a85ad5 | ||
|
|
a384711327 | ||
|
|
3fd4778a48 | ||
|
|
4841dc09b3 | ||
|
|
b3aa4fc776 | ||
|
|
a9b3b8b6f4 | ||
|
|
56ef196695 | ||
| 242238d341 | |||
| f66f6d38fe | |||
| d58077f58b | |||
| 4d4d6dbedf | |||
| f60b276916 | |||
| 87857fd499 | |||
| 3c371cd079 | |||
|
|
428b849bcc | ||
|
|
85f3b4f607 | ||
|
|
916396f855 | ||
|
|
211c8d2b04 | ||
|
|
92e274d3fd | ||
|
|
d511ea48d5 | ||
|
|
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 |
206
.github/workflows/build-app.yml
vendored
206
.github/workflows/build-app.yml
vendored
@@ -28,9 +28,11 @@ permissions:
|
|||||||
actions: write
|
actions: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
pre_check:
|
pre_check:
|
||||||
name: Pre Checks
|
name: Pre Checks
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Repo Check
|
- name: Repo Check
|
||||||
id: repo_check
|
id: repo_check
|
||||||
@@ -40,68 +42,198 @@ jobs:
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
exit 0
|
exit 0
|
||||||
|
|
||||||
build_AUTO_MAA:
|
build_AUTO_MAA:
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
needs: pre_check
|
needs: pre_check
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
- name: Set up Python 3.12
|
|
||||||
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: "3.12"
|
python-version: '3.12'
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
pip install flake8 pytest
|
pip install flake8 pytest
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
|
|
||||||
- name: Lint with flake8
|
- name: Lint with flake8
|
||||||
run: |
|
run: |
|
||||||
# stop the build if there are Python syntax errors or undefined names
|
# stop the build if there are Python syntax errors or undefined names
|
||||||
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
|
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
|
||||||
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
|
# 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
|
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
|
||||||
- name: Package
|
|
||||||
id: package
|
- name: Get version
|
||||||
|
id: get_version
|
||||||
run: |
|
run: |
|
||||||
copy app\utils\package.py .\
|
$version = (Get-Content resources/version.json | ConvertFrom-Json).main_version
|
||||||
python package.py
|
echo "main_version=$version" >> $env:GITHUB_OUTPUT
|
||||||
- name: Read version
|
|
||||||
id: read_version
|
- 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: 'release-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: |
|
run: |
|
||||||
$MAIN_VERSION=(Get-Content -Path "version_info.txt" -TotalCount 1).Trim()
|
$root = "${{ github.workspace }}"
|
||||||
"AUTO_MAA_version=$MAIN_VERSION" | Out-File -FilePath $env:GITHUB_ENV -Append
|
$ver = "${{ steps.get_version.outputs.main_version }}"
|
||||||
$UPDATER_VERSION=(Get-Content -Path "version_info.txt" -TotalCount 2 | Select-Object -Index 1).Trim()
|
Copy-Item "$root/app" "$root/AUTO_MAA/app" -Recurse
|
||||||
"updater_version=$UPDATER_VERSION" | Out-File -FilePath $env:GITHUB_ENV -Append
|
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: 'release-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
|
- name: Upload Artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: AUTO_MAA_${{ env.AUTO_MAA_version }}
|
name: AUTO_MAA_${{ steps.get_version.outputs.main_version }}
|
||||||
path: |
|
path: AUTO_MAA_${{ steps.get_version.outputs.main_version }}.zip
|
||||||
AUTO_MAA_${{ env.AUTO_MAA_version }}.zip
|
|
||||||
- name: Upload Version_Info Artifact
|
- name: Upload Version_Info Artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: version_info
|
name: version_info
|
||||||
path: version_info.txt
|
path: version_info.txt
|
||||||
|
|
||||||
publish_release:
|
publish_release:
|
||||||
name: Publish release
|
name: Publish release
|
||||||
needs: build_AUTO_MAA
|
needs: build_AUTO_MAA
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Download artifacts
|
- name: Download artifacts
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
pattern: AUTO_MAA_*
|
pattern: AUTO_MAA_*
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
path: artifacts
|
path: artifacts
|
||||||
|
|
||||||
- name: Download Version_Info
|
- name: Download Version_Info
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: version_info
|
name: version_info
|
||||||
path: ./
|
path: ./
|
||||||
|
|
||||||
- name: Create release
|
- name: Create release
|
||||||
id: create_release
|
id: create_release
|
||||||
run: |
|
run: |
|
||||||
@@ -110,8 +242,23 @@ jobs:
|
|||||||
NAME="$(sed 's/\r$//g' <(head -n 1 version_info.txt))"
|
NAME="$(sed 's/\r$//g' <(head -n 1 version_info.txt))"
|
||||||
TAGNAME="$(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_MAIN="$(sed 's/\r$//g' <(tail -n +3 version_info.txt))"
|
||||||
NOTES_TAIL="\`\`\`本release通过GitHub Actions自动构建\`\`\`"
|
NOTES="$NOTES_MAIN
|
||||||
NOTES="$NOTES_MAIN<br><br>$NOTES_TAIL"
|
|
||||||
|
## 代码签名策略(Code signing policy)
|
||||||
|
|
||||||
|
Free code signing provided by [SignPath.io](https://signpath.io/), certificate by [SignPath Foundation](https://signpath.org/)
|
||||||
|
|
||||||
|
- 审批人(Approvers): [DLmaster (@DLmaster361)](https://github.com/DLmaster361)
|
||||||
|
|
||||||
|
## 隐私政策(Privacy policy)
|
||||||
|
|
||||||
|
除非用户、安装者或使用者特别要求,否则本程序不会将任何信息传输到其他网络系统。
|
||||||
|
|
||||||
|
This program will not transfer any information to other networked systems unless specifically requested by the user or the person installing or operating it.
|
||||||
|
|
||||||
|
[已有 Mirror酱 CDK ?前往 Mirror酱 高速下载](https://mirrorchyan.com/zh/projects?rid=AUTO_MAA&source=auto_maa-release)
|
||||||
|
|
||||||
|
\`\`\`本release通过GitHub Actions自动构建\`\`\`"
|
||||||
if [ "${{ github.ref_name }}" == "main" ]; then
|
if [ "${{ github.ref_name }}" == "main" ]; then
|
||||||
PRERELEASE_FLAG=""
|
PRERELEASE_FLAG=""
|
||||||
else
|
else
|
||||||
@@ -120,33 +267,10 @@ jobs:
|
|||||||
gh release create "$TAGNAME" --target "main" --title "$NAME" --notes "$NOTES" $PRERELEASE_FLAG artifacts/*
|
gh release create "$TAGNAME" --target "main" --title "$NAME" --notes "$NOTES" $PRERELEASE_FLAG artifacts/*
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.WORKFLOW_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.WORKFLOW_TOKEN }}
|
||||||
|
|
||||||
- name: Trigger MirrorChyanUploading
|
- name: Trigger MirrorChyanUploading
|
||||||
run: |
|
run: |
|
||||||
gh workflow run --repo $GITHUB_REPOSITORY mirrorchyan
|
gh workflow run --repo $GITHUB_REPOSITORY mirrorchyan
|
||||||
gh workflow run --repo $GITHUB_REPOSITORY mirrorchyan_release_note
|
gh workflow run --repo $GITHUB_REPOSITORY mirrorchyan_release_note
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Setup SSH Key
|
|
||||||
run: |
|
|
||||||
mkdir -p ~/.ssh
|
|
||||||
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
|
|
||||||
chmod 600 ~/.ssh/id_rsa
|
|
||||||
ssh-keyscan -H ${{ secrets.SERVER_IP }} >> ~/.ssh/known_hosts
|
|
||||||
- name: Upload Release to Server
|
|
||||||
run: |
|
|
||||||
scp -r artifacts/* ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_IP }}:/home/user/files/AUTO_MAA/
|
|
||||||
- name: Install obsutil
|
|
||||||
run: |
|
|
||||||
wget https://obs-community.obs.cn-north-1.myhuaweicloud.com/obsutil/current/obsutil_linux_amd64.tar.gz
|
|
||||||
tar -xzvf obsutil_linux_amd64.tar.gz --strip-components=1
|
|
||||||
chmod 755 obsutil
|
|
||||||
./obsutil version
|
|
||||||
- name: Upload Release to Huawei OBS
|
|
||||||
env:
|
|
||||||
OBS_AK: ${{ secrets.OBS_AK }}
|
|
||||||
OBS_SK: ${{ secrets.OBS_SK }}
|
|
||||||
OBS_ENDPOINT: ${{ secrets.OBS_ENDPOINT }}
|
|
||||||
OBS_BUCKET: ${{ secrets.OBS_BUCKET }}
|
|
||||||
run: |
|
|
||||||
./obsutil config -i $OBS_AK -k $OBS_SK -e $OBS_ENDPOINT
|
|
||||||
./obsutil cp artifacts/ obs://$OBS_BUCKET/releases/ -r -f
|
|
||||||
|
|||||||
67
README.md
67
README.md
@@ -1,20 +1,21 @@
|
|||||||
# AUTO_MAA
|
<h1 align="center">AUTO_MAA</h1>
|
||||||
|
<p align="center">
|
||||||
MAA多账号管理与自动化软件
|
MAA多账号管理与自动化软件<br><br>
|
||||||
|
<img alt="软件图标" src="https://github.com/DLmaster361/AUTO_MAA/blob/main/resources/images/AUTO_MAA.png">
|
||||||

|
</p>
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
</h1>
|
<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>
|
||||||
[](https://github.com/DLmaster361/AUTO_MAA/stargazers)
|
<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>
|
||||||
[](https://github.com/DLmaster361/AUTO_MAA/network)
|
<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>
|
||||||
[](https://github.com/DLmaster361/AUTO_MAA/releases/latest)
|
<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>
|
||||||
[](https://github.com/DLmaster361/AUTO_MAA/issues)
|
<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>
|
||||||
[](https://github.com/DLmaster361/AUTO_MAA/graphs/contributors)
|
<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>
|
||||||
[](https://github.com/DLmaster361/AUTO_MAA/blob/main/LICENSE)
|
<a href="https://deepwiki.com/DLmaster361/AUTO_MAA"><img alt="DeepWiki" src="https://deepwiki.com/badge.svg"></a>
|
||||||
</div>
|
<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>
|
||||||
|
|
||||||
## 软件介绍
|
## 软件介绍
|
||||||
|
|
||||||
@@ -22,6 +23,11 @@ MAA多账号管理与自动化软件
|
|||||||
|
|
||||||
本软件是明日方舟第三方软件`MAA`的第三方工具,即第3<sup>3</sup>方软件。旨在优化MAA多账号功能体验,并通过一些方法解决MAA项目未能解决的部分问题,提高代理的稳定性。
|
本软件是明日方舟第三方软件`MAA`的第三方工具,即第3<sup>3</sup>方软件。旨在优化MAA多账号功能体验,并通过一些方法解决MAA项目未能解决的部分问题,提高代理的稳定性。
|
||||||
|
|
||||||
|
- **集中管理**:一站式管理多个MAA脚本与多个用户配置,和凌乱的散装脚本窗口说再见!
|
||||||
|
- **无人值守**:自动处理MAA相关报错,再也不用为代理任务卡死时自己不在电脑旁烦恼啦!
|
||||||
|
- **配置灵活**:通过调度队列与脚本的组合,自由实现您能想到的所有调度需求!
|
||||||
|
- **信息统计**:自动统计用户的公招与关卡掉落物,看看这个月您的收益是多少!
|
||||||
|
|
||||||
### 原理
|
### 原理
|
||||||
|
|
||||||
本软件可以存储多个明日方舟账号数据,并通过以下流程实现代理功能:
|
本软件可以存储多个明日方舟账号数据,并通过以下流程实现代理功能:
|
||||||
@@ -32,11 +38,9 @@ MAA多账号管理与自动化软件
|
|||||||
|
|
||||||
### 优势
|
### 优势
|
||||||
|
|
||||||
- **节省运行开销:** 只需要一份MAA软件与一个模拟器,无需多开就能完成多账号代理,羸弱的电脑也能代理日常。
|
- **高效稳定**:通过日志监测、异常处理等机制,保障代理任务顺利完成。
|
||||||
- **自定义空间大:** 依靠高级用户配置模式,支持MAA几乎所有设置选项自定义,支持模拟器多开。
|
- **简洁易用**:无需手动修改配置文件,实现自动化调度与多开管理。
|
||||||
- **调度方法自由:** 通过调度队列功能,自由实现MAA多开等多种调度方式。
|
- **兼容扩展**:支持 MAA 几乎所有的配置选项,满足不同用户需求。
|
||||||
- **一键代理无忧:** 无须中途手动修改MAA配置,将繁琐交给AUTO_MAA,把游戏留给自己。
|
|
||||||
- **代理结果复核:** 通过人工排查功能核实各用户代理情况,堵住自动代理的最后一丝风险。
|
|
||||||
|
|
||||||
## 重要声明
|
## 重要声明
|
||||||
|
|
||||||
@@ -48,13 +52,10 @@ MAA多账号管理与自动化软件
|
|||||||
- **传播:** AUTO_MAA原则上允许传播者自由传播本软件,但无论在何种传播过程中,不得删除项目作者与开发者所留版权声明,不得隐瞒项目作者与相关开发者的存在。由于软件性质,项目组不希望发现任何人在明日方舟官方媒体(包括官方媒体账号与森空岛社区等)或明日方舟游戏相关内容(包括同好群、线下活动与游戏内容讨论等)下提及AUTO_MAA或MAA,希望各位理解。
|
- **传播:** AUTO_MAA原则上允许传播者自由传播本软件,但无论在何种传播过程中,不得删除项目作者与开发者所留版权声明,不得隐瞒项目作者与相关开发者的存在。由于软件性质,项目组不希望发现任何人在明日方舟官方媒体(包括官方媒体账号与森空岛社区等)或明日方舟游戏相关内容(包括同好群、线下活动与游戏内容讨论等)下提及AUTO_MAA或MAA,希望各位理解。
|
||||||
- **衍生:** AUTO_MAA允许任何人对软件本体或软件部分代码进行二次开发或利用。但依据GPL,相关成果再次分发时也必须使用GPL或兼容的协议开源。
|
- **衍生:** AUTO_MAA允许任何人对软件本体或软件部分代码进行二次开发或利用。但依据GPL,相关成果再次分发时也必须使用GPL或兼容的协议开源。
|
||||||
- **贡献:** 不论是直接参与软件的维护编写,或是撰写文档、测试、反馈BUG、给出建议、参与讨论,都为AUTO_MAA项目的发展完善做出了不可忽视的贡献。项目组提倡各位贡献者遵照GitHub开源社区惯例,发布Issues参与项目。避免私信或私发邮件(安全性漏洞或敏感问题除外),以帮助更多用户。
|
- **贡献:** 不论是直接参与软件的维护编写,或是撰写文档、测试、反馈BUG、给出建议、参与讨论,都为AUTO_MAA项目的发展完善做出了不可忽视的贡献。项目组提倡各位贡献者遵照GitHub开源社区惯例,发布Issues参与项目。避免私信或私发邮件(安全性漏洞或敏感问题除外),以帮助更多用户。
|
||||||
|
- **图像:** `AUTO_MAA主页默认图像` 并不适用开源协议,著作权归 [NARINpopo](https://space.bilibili.com/1877154) 画师所有,商业使用权归 [DLmaster (@DLmaster361)](https://github.com/DLmaster361) 所有,软件用户仅拥有非商业使用权。不得以开源协议已授权为由在未经授权的情况下使用 `AUTO_MAA主页默认图像`,不得在未经授权的情况下将 `AUTO_MAA主页默认图像` 用于任何商业用途。
|
||||||
|
|
||||||
以上细则是本项目对GPL的相关补充与强调。未提及的以GPL为准,发生冲突的以本细则为准。如有不清楚的部分,请发Issues询问。若发生纠纷,相关内容也没有在Issues上提及的,项目组拥有最终解释权。
|
以上细则是本项目对GPL的相关补充与强调。未提及的以GPL为准,发生冲突的以本细则为准。如有不清楚的部分,请发Issues询问。若发生纠纷,相关内容也没有在Issues上提及的,项目组拥有最终解释权。
|
||||||
|
|
||||||
**注意**
|
|
||||||
|
|
||||||
- 由于本软件有修改其它目录JSON文件等行为,使用前请将AUTO_MAA添加入Windows Defender信任区以及防病毒软件的信任区或开发者目录,避免被误杀。
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# 使用方法
|
# 使用方法
|
||||||
@@ -71,6 +72,24 @@ MAA多账号管理与自动化软件
|
|||||||
|
|
||||||
可在[《AUTO_MAA开发者协作文档》](https://docs.qq.com/aio/DQ3Z5eHNxdmxFQmZX)的`开发任务`页面中查看开发进度。
|
可在[《AUTO_MAA开发者协作文档》](https://docs.qq.com/aio/DQ3Z5eHNxdmxFQmZX)的`开发任务`页面中查看开发进度。
|
||||||
|
|
||||||
|
## 代码签名策略(Code signing policy)
|
||||||
|
|
||||||
|
Free code signing provided by [SignPath.io](https://signpath.io/), certificate by [SignPath Foundation](https://signpath.org/)
|
||||||
|
|
||||||
|
- 审批人(Approvers): [DLmaster (@DLmaster361)](https://github.com/DLmaster361)
|
||||||
|
|
||||||
|
## 隐私政策(Privacy policy)
|
||||||
|
|
||||||
|
除非用户、安装者或使用者特别要求,否则本程序不会将任何信息传输到其他网络系统。
|
||||||
|
|
||||||
|
This program will not transfer any information to other networked systems unless specifically requested by the user or the person installing or operating it.
|
||||||
|
|
||||||
|
## 特别鸣谢
|
||||||
|
|
||||||
|
- 下载服务器:由[AoXuan (@ClozyA)](https://github.com/ClozyA) 个人为项目赞助。
|
||||||
|
|
||||||
|
- EXE签名: 由 [SignPath.io](https://signpath.io/)提供免费代码签名,签名来自[SignPath Foundation](https://signpath.org/)。
|
||||||
|
|
||||||
## 贡献者
|
## 贡献者
|
||||||
|
|
||||||
感谢以下贡献者对本项目做出的贡献
|
感谢以下贡献者对本项目做出的贡献
|
||||||
@@ -83,8 +102,6 @@ MAA多账号管理与自动化软件
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
感谢 [AoXuan (@ClozyA)](https://github.com/ClozyA) 为本项目提供的下载服务器
|
|
||||||
|
|
||||||
## Star History
|
## Star History
|
||||||
|
|
||||||
[](https://star-history.com/#DLmaster361/AUTO_MAA&Date)
|
[](https://star-history.com/#DLmaster361/AUTO_MAA&Date)
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ from .core import QueueConfig, MaaConfig, MaaUserConfig, Task, TaskManager, Main
|
|||||||
from .models import MaaManager
|
from .models import MaaManager
|
||||||
from .services import Notify, Crypto, System
|
from .services import Notify, Crypto, System
|
||||||
from .ui import AUTO_MAA
|
from .ui import AUTO_MAA
|
||||||
from .utils import DownloadManager
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"QueueConfig",
|
"QueueConfig",
|
||||||
@@ -47,5 +46,4 @@ __all__ = [
|
|||||||
"Crypto",
|
"Crypto",
|
||||||
"System",
|
"System",
|
||||||
"AUTO_MAA",
|
"AUTO_MAA",
|
||||||
"DownloadManager",
|
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -29,9 +29,10 @@ __version__ = "4.2.0"
|
|||||||
__author__ = "DLmaster361 <DLmaster_361@163.com>"
|
__author__ = "DLmaster361 <DLmaster_361@163.com>"
|
||||||
__license__ = "GPL-3.0 license"
|
__license__ = "GPL-3.0 license"
|
||||||
|
|
||||||
from .config import QueueConfig, MaaConfig, MaaUserConfig, Config
|
from .config import QueueConfig, MaaConfig, MaaUserConfig, MaaPlanConfig, Config
|
||||||
from .main_info_bar import MainInfoBar
|
from .main_info_bar import MainInfoBar
|
||||||
from .network import Network
|
from .network import Network
|
||||||
|
from .sound_player import SoundPlayer
|
||||||
from .task_manager import Task, TaskManager
|
from .task_manager import Task, TaskManager
|
||||||
from .timer import MainTimer
|
from .timer import MainTimer
|
||||||
|
|
||||||
@@ -40,8 +41,10 @@ __all__ = [
|
|||||||
"QueueConfig",
|
"QueueConfig",
|
||||||
"MaaConfig",
|
"MaaConfig",
|
||||||
"MaaUserConfig",
|
"MaaUserConfig",
|
||||||
|
"MaaPlanConfig",
|
||||||
"MainInfoBar",
|
"MainInfoBar",
|
||||||
"Network",
|
"Network",
|
||||||
|
"SoundPlayer",
|
||||||
"Task",
|
"Task",
|
||||||
"TaskManager",
|
"TaskManager",
|
||||||
"MainTimer",
|
"MainTimer",
|
||||||
|
|||||||
@@ -27,12 +27,14 @@ v4.3
|
|||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from PySide6.QtCore import Signal
|
from PySide6.QtCore import Signal
|
||||||
|
import argparse
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import json
|
import json
|
||||||
import sys
|
import sys
|
||||||
import shutil
|
import shutil
|
||||||
import re
|
import re
|
||||||
import base64
|
import base64
|
||||||
|
import calendar
|
||||||
from datetime import datetime, timedelta, date
|
from datetime import datetime, timedelta, date
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -78,107 +80,12 @@ class UrlListValidator(ConfigValidator):
|
|||||||
return list(set([_ for _ in urls if self.validate(_)]))
|
return list(set([_ for _ in urls if self.validate(_)]))
|
||||||
|
|
||||||
|
|
||||||
class GlobalConfig(QConfig):
|
class LQConfig(QConfig):
|
||||||
"""全局配置"""
|
"""局域配置类"""
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
self.function_HomeImageMode = OptionsConfigItem(
|
|
||||||
"Function",
|
|
||||||
"HomeImageMode",
|
|
||||||
"默认",
|
|
||||||
OptionsValidator(["默认", "自定义", "主题图像"]),
|
|
||||||
)
|
|
||||||
self.function_HistoryRetentionTime = OptionsConfigItem(
|
|
||||||
"Function",
|
|
||||||
"HistoryRetentionTime",
|
|
||||||
0,
|
|
||||||
OptionsValidator([7, 15, 30, 60, 90, 180, 365, 0]),
|
|
||||||
)
|
|
||||||
self.function_IfAllowSleep = ConfigItem(
|
|
||||||
"Function", "IfAllowSleep", False, BoolValidator()
|
|
||||||
)
|
|
||||||
self.function_IfSilence = ConfigItem(
|
|
||||||
"Function", "IfSilence", False, BoolValidator()
|
|
||||||
)
|
|
||||||
self.function_BossKey = ConfigItem("Function", "BossKey", "")
|
|
||||||
self.function_IfAgreeBilibili = ConfigItem(
|
|
||||||
"Function", "IfAgreeBilibili", False, BoolValidator()
|
|
||||||
)
|
|
||||||
self.function_IfSkipMumuSplashAds = ConfigItem(
|
|
||||||
"Function", "IfSkipMumuSplashAds", False, BoolValidator()
|
|
||||||
)
|
|
||||||
|
|
||||||
self.start_IfSelfStart = ConfigItem(
|
|
||||||
"Start", "IfSelfStart", False, BoolValidator()
|
|
||||||
)
|
|
||||||
self.start_IfRunDirectly = ConfigItem(
|
|
||||||
"Start", "IfRunDirectly", False, BoolValidator()
|
|
||||||
)
|
|
||||||
self.start_IfMinimizeDirectly = ConfigItem(
|
|
||||||
"Start", "IfMinimizeDirectly", False, BoolValidator()
|
|
||||||
)
|
|
||||||
|
|
||||||
self.ui_IfShowTray = ConfigItem("UI", "IfShowTray", False, BoolValidator())
|
|
||||||
self.ui_IfToTray = ConfigItem("UI", "IfToTray", False, BoolValidator())
|
|
||||||
self.ui_size = ConfigItem("UI", "size", "1200x700")
|
|
||||||
self.ui_location = ConfigItem("UI", "location", "100x100")
|
|
||||||
self.ui_maximized = ConfigItem("UI", "maximized", False, BoolValidator())
|
|
||||||
|
|
||||||
self.notify_SendTaskResultTime = OptionsConfigItem(
|
|
||||||
"Notify",
|
|
||||||
"SendTaskResultTime",
|
|
||||||
"不推送",
|
|
||||||
OptionsValidator(["不推送", "任何时刻", "仅失败时"]),
|
|
||||||
)
|
|
||||||
self.notify_IfSendStatistic = ConfigItem(
|
|
||||||
"Notify", "IfSendStatistic", False, BoolValidator()
|
|
||||||
)
|
|
||||||
self.notify_IfSendSixStar = ConfigItem(
|
|
||||||
"Notify", "IfSendSixStar", False, BoolValidator()
|
|
||||||
)
|
|
||||||
self.notify_IfPushPlyer = ConfigItem(
|
|
||||||
"Notify", "IfPushPlyer", False, BoolValidator()
|
|
||||||
)
|
|
||||||
self.notify_IfSendMail = ConfigItem(
|
|
||||||
"Notify", "IfSendMail", False, BoolValidator()
|
|
||||||
)
|
|
||||||
self.notify_SMTPServerAddress = ConfigItem("Notify", "SMTPServerAddress", "")
|
|
||||||
self.notify_AuthorizationCode = ConfigItem("Notify", "AuthorizationCode", "")
|
|
||||||
self.notify_FromAddress = ConfigItem("Notify", "FromAddress", "")
|
|
||||||
self.notify_ToAddress = ConfigItem("Notify", "ToAddress", "")
|
|
||||||
self.notify_IfServerChan = ConfigItem(
|
|
||||||
"Notify", "IfServerChan", False, BoolValidator()
|
|
||||||
)
|
|
||||||
self.notify_ServerChanKey = ConfigItem("Notify", "ServerChanKey", "")
|
|
||||||
self.notify_ServerChanChannel = ConfigItem("Notify", "ServerChanChannel", "")
|
|
||||||
self.notify_ServerChanTag = ConfigItem("Notify", "ServerChanTag", "")
|
|
||||||
self.notify_IfCompanyWebHookBot = ConfigItem(
|
|
||||||
"Notify", "IfCompanyWebHookBot", False, BoolValidator()
|
|
||||||
)
|
|
||||||
self.notify_CompanyWebHookBotUrl = ConfigItem(
|
|
||||||
"Notify", "CompanyWebHookBotUrl", ""
|
|
||||||
)
|
|
||||||
self.notify_IfPushDeer = ConfigItem(
|
|
||||||
"Notify", "IfPushDeer", False, BoolValidator()
|
|
||||||
)
|
|
||||||
self.notify_IfPushDeerKey = ConfigItem("Notify", "PushDeerKey", "")
|
|
||||||
|
|
||||||
self.update_IfAutoUpdate = ConfigItem(
|
|
||||||
"Update", "IfAutoUpdate", False, BoolValidator()
|
|
||||||
)
|
|
||||||
self.update_UpdateType = OptionsConfigItem(
|
|
||||||
"Update", "UpdateType", "stable", OptionsValidator(["stable", "beta"])
|
|
||||||
)
|
|
||||||
self.update_ThreadNumb = RangeConfigItem(
|
|
||||||
"Update", "ThreadNumb", 8, RangeValidator(1, 32)
|
|
||||||
)
|
|
||||||
self.update_ProxyUrlList = ConfigItem(
|
|
||||||
"Update", "ProxyUrlList", [], UrlListValidator()
|
|
||||||
)
|
|
||||||
self.update_MirrorChyanCDK = ConfigItem("Update", "MirrorChyanCDK", "")
|
|
||||||
|
|
||||||
def toDict(self, serialize=True):
|
def toDict(self, serialize=True):
|
||||||
"""convert config items to `dict`"""
|
"""convert config items to `dict`"""
|
||||||
items = {}
|
items = {}
|
||||||
@@ -244,7 +151,113 @@ class GlobalConfig(QConfig):
|
|||||||
self.theme = self.get(self._cfg.themeMode)
|
self.theme = self.get(self._cfg.themeMode)
|
||||||
|
|
||||||
|
|
||||||
class QueueConfig(QConfig):
|
class GlobalConfig(LQConfig):
|
||||||
|
"""全局配置"""
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
self.function_HomeImageMode = OptionsConfigItem(
|
||||||
|
"Function",
|
||||||
|
"HomeImageMode",
|
||||||
|
"默认",
|
||||||
|
OptionsValidator(["默认", "自定义", "主题图像"]),
|
||||||
|
)
|
||||||
|
self.function_HistoryRetentionTime = OptionsConfigItem(
|
||||||
|
"Function",
|
||||||
|
"HistoryRetentionTime",
|
||||||
|
0,
|
||||||
|
OptionsValidator([7, 15, 30, 60, 90, 180, 365, 0]),
|
||||||
|
)
|
||||||
|
self.function_IfAllowSleep = ConfigItem(
|
||||||
|
"Function", "IfAllowSleep", False, BoolValidator()
|
||||||
|
)
|
||||||
|
self.function_IfSilence = ConfigItem(
|
||||||
|
"Function", "IfSilence", False, BoolValidator()
|
||||||
|
)
|
||||||
|
self.function_BossKey = ConfigItem("Function", "BossKey", "")
|
||||||
|
self.function_UnattendedMode = ConfigItem(
|
||||||
|
"Function", "UnattendedMode", False, BoolValidator()
|
||||||
|
)
|
||||||
|
self.function_IfAgreeBilibili = ConfigItem(
|
||||||
|
"Function", "IfAgreeBilibili", False, BoolValidator()
|
||||||
|
)
|
||||||
|
self.function_IfSkipMumuSplashAds = ConfigItem(
|
||||||
|
"Function", "IfSkipMumuSplashAds", False, BoolValidator()
|
||||||
|
)
|
||||||
|
|
||||||
|
self.voice_Enabled = ConfigItem("Voice", "Enabled", False, BoolValidator())
|
||||||
|
self.voice_Type = OptionsConfigItem(
|
||||||
|
"Voice", "Type", "simple", OptionsValidator(["simple", "noisy"])
|
||||||
|
)
|
||||||
|
|
||||||
|
self.start_IfSelfStart = ConfigItem(
|
||||||
|
"Start", "IfSelfStart", False, BoolValidator()
|
||||||
|
)
|
||||||
|
self.start_IfRunDirectly = ConfigItem(
|
||||||
|
"Start", "IfRunDirectly", False, BoolValidator()
|
||||||
|
)
|
||||||
|
self.start_IfMinimizeDirectly = ConfigItem(
|
||||||
|
"Start", "IfMinimizeDirectly", False, BoolValidator()
|
||||||
|
)
|
||||||
|
|
||||||
|
self.ui_IfShowTray = ConfigItem("UI", "IfShowTray", False, BoolValidator())
|
||||||
|
self.ui_IfToTray = ConfigItem("UI", "IfToTray", False, BoolValidator())
|
||||||
|
self.ui_size = ConfigItem("UI", "size", "1200x700")
|
||||||
|
self.ui_location = ConfigItem("UI", "location", "100x100")
|
||||||
|
self.ui_maximized = ConfigItem("UI", "maximized", False, BoolValidator())
|
||||||
|
|
||||||
|
self.notify_SendTaskResultTime = OptionsConfigItem(
|
||||||
|
"Notify",
|
||||||
|
"SendTaskResultTime",
|
||||||
|
"不推送",
|
||||||
|
OptionsValidator(["不推送", "任何时刻", "仅失败时"]),
|
||||||
|
)
|
||||||
|
self.notify_IfSendStatistic = ConfigItem(
|
||||||
|
"Notify", "IfSendStatistic", False, BoolValidator()
|
||||||
|
)
|
||||||
|
self.notify_IfSendSixStar = ConfigItem(
|
||||||
|
"Notify", "IfSendSixStar", False, BoolValidator()
|
||||||
|
)
|
||||||
|
self.notify_IfPushPlyer = ConfigItem(
|
||||||
|
"Notify", "IfPushPlyer", False, BoolValidator()
|
||||||
|
)
|
||||||
|
self.notify_IfSendMail = ConfigItem(
|
||||||
|
"Notify", "IfSendMail", False, BoolValidator()
|
||||||
|
)
|
||||||
|
self.notify_SMTPServerAddress = ConfigItem("Notify", "SMTPServerAddress", "")
|
||||||
|
self.notify_AuthorizationCode = ConfigItem("Notify", "AuthorizationCode", "")
|
||||||
|
self.notify_FromAddress = ConfigItem("Notify", "FromAddress", "")
|
||||||
|
self.notify_ToAddress = ConfigItem("Notify", "ToAddress", "")
|
||||||
|
self.notify_IfServerChan = ConfigItem(
|
||||||
|
"Notify", "IfServerChan", False, BoolValidator()
|
||||||
|
)
|
||||||
|
self.notify_ServerChanKey = ConfigItem("Notify", "ServerChanKey", "")
|
||||||
|
self.notify_ServerChanChannel = ConfigItem("Notify", "ServerChanChannel", "")
|
||||||
|
self.notify_ServerChanTag = ConfigItem("Notify", "ServerChanTag", "")
|
||||||
|
self.notify_IfCompanyWebHookBot = ConfigItem(
|
||||||
|
"Notify", "IfCompanyWebHookBot", False, BoolValidator()
|
||||||
|
)
|
||||||
|
self.notify_CompanyWebHookBotUrl = ConfigItem(
|
||||||
|
"Notify", "CompanyWebHookBotUrl", ""
|
||||||
|
)
|
||||||
|
|
||||||
|
self.update_IfAutoUpdate = ConfigItem(
|
||||||
|
"Update", "IfAutoUpdate", False, BoolValidator()
|
||||||
|
)
|
||||||
|
self.update_UpdateType = OptionsConfigItem(
|
||||||
|
"Update", "UpdateType", "stable", OptionsValidator(["stable", "beta"])
|
||||||
|
)
|
||||||
|
self.update_ThreadNumb = RangeConfigItem(
|
||||||
|
"Update", "ThreadNumb", 8, RangeValidator(1, 32)
|
||||||
|
)
|
||||||
|
self.update_ProxyUrlList = ConfigItem(
|
||||||
|
"Update", "ProxyUrlList", [], UrlListValidator()
|
||||||
|
)
|
||||||
|
self.update_MirrorChyanCDK = ConfigItem("Update", "MirrorChyanCDK", "")
|
||||||
|
|
||||||
|
|
||||||
|
class QueueConfig(LQConfig):
|
||||||
"""队列配置"""
|
"""队列配置"""
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
@@ -257,8 +270,10 @@ class QueueConfig(QConfig):
|
|||||||
self.queueSet_AfterAccomplish = OptionsConfigItem(
|
self.queueSet_AfterAccomplish = OptionsConfigItem(
|
||||||
"QueueSet",
|
"QueueSet",
|
||||||
"AfterAccomplish",
|
"AfterAccomplish",
|
||||||
"None",
|
"NoAction",
|
||||||
OptionsValidator(["None", "KillSelf", "Sleep", "Hibernate", "Shutdown"]),
|
OptionsValidator(
|
||||||
|
["NoAction", "KillSelf", "Sleep", "Hibernate", "Shutdown"]
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
self.time_TimeEnabled_0 = ConfigItem(
|
self.time_TimeEnabled_0 = ConfigItem(
|
||||||
@@ -329,72 +344,8 @@ class QueueConfig(QConfig):
|
|||||||
"Data", "LastProxyHistory", "暂无历史运行记录"
|
"Data", "LastProxyHistory", "暂无历史运行记录"
|
||||||
)
|
)
|
||||||
|
|
||||||
def toDict(self, serialize=True):
|
|
||||||
"""convert config items to `dict`"""
|
|
||||||
items = {}
|
|
||||||
for name in dir(self._cfg):
|
|
||||||
item = getattr(self._cfg, name)
|
|
||||||
if not isinstance(item, ConfigItem):
|
|
||||||
continue
|
|
||||||
|
|
||||||
value = item.serialize() if serialize else item.value
|
class MaaConfig(LQConfig):
|
||||||
if not items.get(item.group):
|
|
||||||
if not item.name:
|
|
||||||
items[item.group] = value
|
|
||||||
else:
|
|
||||||
items[item.group] = {}
|
|
||||||
|
|
||||||
if item.name:
|
|
||||||
items[item.group][item.name] = value
|
|
||||||
|
|
||||||
return items
|
|
||||||
|
|
||||||
@exceptionHandler()
|
|
||||||
def load(self, file=None, config=None):
|
|
||||||
"""load config
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
file: str or Path
|
|
||||||
the path of json config file
|
|
||||||
|
|
||||||
config: Config
|
|
||||||
config object to be initialized
|
|
||||||
"""
|
|
||||||
if isinstance(config, QConfig):
|
|
||||||
self._cfg = config
|
|
||||||
self._cfg.themeChanged.connect(self.themeChanged)
|
|
||||||
|
|
||||||
if isinstance(file, (str, Path)):
|
|
||||||
self._cfg.file = Path(file)
|
|
||||||
|
|
||||||
try:
|
|
||||||
with open(self._cfg.file, encoding="utf-8") as f:
|
|
||||||
cfg = json.load(f)
|
|
||||||
except:
|
|
||||||
cfg = {}
|
|
||||||
|
|
||||||
# map config items'key to item
|
|
||||||
items = {}
|
|
||||||
for name in dir(self._cfg):
|
|
||||||
item = getattr(self._cfg, name)
|
|
||||||
if isinstance(item, ConfigItem):
|
|
||||||
items[item.key] = item
|
|
||||||
|
|
||||||
# update the value of config item
|
|
||||||
for k, v in cfg.items():
|
|
||||||
if not isinstance(v, dict) and items.get(k) is not None:
|
|
||||||
items[k].deserializeFrom(v)
|
|
||||||
elif isinstance(v, dict):
|
|
||||||
for key, value in v.items():
|
|
||||||
key = k + "." + key
|
|
||||||
if items.get(key) is not None:
|
|
||||||
items[key].deserializeFrom(value)
|
|
||||||
|
|
||||||
self.theme = self.get(self._cfg.themeMode)
|
|
||||||
|
|
||||||
|
|
||||||
class MaaConfig(QConfig):
|
|
||||||
"""MAA配置"""
|
"""MAA配置"""
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
@@ -409,94 +360,30 @@ class MaaConfig(QConfig):
|
|||||||
"ExitEmulator",
|
"ExitEmulator",
|
||||||
OptionsValidator(["NoAction", "ExitGame", "ExitEmulator"]),
|
OptionsValidator(["NoAction", "ExitGame", "ExitEmulator"]),
|
||||||
)
|
)
|
||||||
self.RunSet_EnhanceTask = OptionsConfigItem(
|
|
||||||
"RunSet",
|
|
||||||
"EnhanceTask",
|
|
||||||
"None",
|
|
||||||
OptionsValidator(["None", "KillADB", "KillEmulator", "KillADB&Emulator"]),
|
|
||||||
)
|
|
||||||
self.RunSet_ProxyTimesLimit = RangeConfigItem(
|
self.RunSet_ProxyTimesLimit = RangeConfigItem(
|
||||||
"RunSet", "ProxyTimesLimit", 0, RangeValidator(0, 1024)
|
"RunSet", "ProxyTimesLimit", 0, RangeValidator(0, 1024)
|
||||||
)
|
)
|
||||||
|
self.RunSet_ADBSearchRange = RangeConfigItem(
|
||||||
|
"RunSet", "ADBSearchRange", 0, RangeValidator(0, 3)
|
||||||
|
)
|
||||||
|
self.RunSet_RunTimesLimit = RangeConfigItem(
|
||||||
|
"RunSet", "RunTimesLimit", 3, RangeValidator(1, 1024)
|
||||||
|
)
|
||||||
self.RunSet_AnnihilationTimeLimit = RangeConfigItem(
|
self.RunSet_AnnihilationTimeLimit = RangeConfigItem(
|
||||||
"RunSet", "AnnihilationTimeLimit", 40, RangeValidator(1, 1024)
|
"RunSet", "AnnihilationTimeLimit", 40, RangeValidator(1, 1024)
|
||||||
)
|
)
|
||||||
self.RunSet_RoutineTimeLimit = RangeConfigItem(
|
self.RunSet_RoutineTimeLimit = RangeConfigItem(
|
||||||
"RunSet", "RoutineTimeLimit", 10, RangeValidator(1, 1024)
|
"RunSet", "RoutineTimeLimit", 10, RangeValidator(1, 1024)
|
||||||
)
|
)
|
||||||
self.RunSet_RunTimesLimit = RangeConfigItem(
|
|
||||||
"RunSet", "RunTimesLimit", 3, RangeValidator(1, 1024)
|
|
||||||
)
|
|
||||||
self.RunSet_AnnihilationWeeklyLimit = ConfigItem(
|
self.RunSet_AnnihilationWeeklyLimit = ConfigItem(
|
||||||
"RunSet", "AnnihilationWeeklyLimit", False, BoolValidator()
|
"RunSet", "AnnihilationWeeklyLimit", False, BoolValidator()
|
||||||
)
|
)
|
||||||
|
self.RunSet_AutoUpdateMaa = ConfigItem(
|
||||||
def toDict(self, serialize=True):
|
"RunSet", "AutoUpdateMaa", False, BoolValidator()
|
||||||
"""convert config items to `dict`"""
|
)
|
||||||
items = {}
|
|
||||||
for name in dir(self._cfg):
|
|
||||||
item = getattr(self._cfg, name)
|
|
||||||
if not isinstance(item, ConfigItem):
|
|
||||||
continue
|
|
||||||
|
|
||||||
value = item.serialize() if serialize else item.value
|
|
||||||
if not items.get(item.group):
|
|
||||||
if not item.name:
|
|
||||||
items[item.group] = value
|
|
||||||
else:
|
|
||||||
items[item.group] = {}
|
|
||||||
|
|
||||||
if item.name:
|
|
||||||
items[item.group][item.name] = value
|
|
||||||
|
|
||||||
return items
|
|
||||||
|
|
||||||
@exceptionHandler()
|
|
||||||
def load(self, file=None, config=None):
|
|
||||||
"""load config
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
file: str or Path
|
|
||||||
the path of json config file
|
|
||||||
|
|
||||||
config: Config
|
|
||||||
config object to be initialized
|
|
||||||
"""
|
|
||||||
if isinstance(config, QConfig):
|
|
||||||
self._cfg = config
|
|
||||||
self._cfg.themeChanged.connect(self.themeChanged)
|
|
||||||
|
|
||||||
if isinstance(file, (str, Path)):
|
|
||||||
self._cfg.file = Path(file)
|
|
||||||
|
|
||||||
try:
|
|
||||||
with open(self._cfg.file, encoding="utf-8") as f:
|
|
||||||
cfg = json.load(f)
|
|
||||||
except:
|
|
||||||
cfg = {}
|
|
||||||
|
|
||||||
# map config items'key to item
|
|
||||||
items = {}
|
|
||||||
for name in dir(self._cfg):
|
|
||||||
item = getattr(self._cfg, name)
|
|
||||||
if isinstance(item, ConfigItem):
|
|
||||||
items[item.key] = item
|
|
||||||
|
|
||||||
# update the value of config item
|
|
||||||
for k, v in cfg.items():
|
|
||||||
if not isinstance(v, dict) and items.get(k) is not None:
|
|
||||||
items[k].deserializeFrom(v)
|
|
||||||
elif isinstance(v, dict):
|
|
||||||
for key, value in v.items():
|
|
||||||
key = k + "." + key
|
|
||||||
if items.get(key) is not None:
|
|
||||||
items[key].deserializeFrom(value)
|
|
||||||
|
|
||||||
self.theme = self.get(self._cfg.themeMode)
|
|
||||||
|
|
||||||
|
|
||||||
class MaaUserConfig(QConfig):
|
class MaaUserConfig(LQConfig):
|
||||||
"""MAA用户配置"""
|
"""MAA用户配置"""
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
@@ -507,9 +394,7 @@ class MaaUserConfig(QConfig):
|
|||||||
self.Info_Mode = OptionsConfigItem(
|
self.Info_Mode = OptionsConfigItem(
|
||||||
"Info", "Mode", "简洁", OptionsValidator(["简洁", "详细"])
|
"Info", "Mode", "简洁", OptionsValidator(["简洁", "详细"])
|
||||||
)
|
)
|
||||||
self.Info_GameIdMode = OptionsConfigItem(
|
self.Info_GameIdMode = ConfigItem("Info", "GameIdMode", "固定")
|
||||||
"Info", "GameIdMode", "固定", OptionsValidator(["固定"])
|
|
||||||
)
|
|
||||||
self.Info_Server = OptionsConfigItem(
|
self.Info_Server = OptionsConfigItem(
|
||||||
"Info", "Server", "Official", OptionsValidator(["Official", "Bilibili"])
|
"Info", "Server", "Official", OptionsValidator(["Official", "Bilibili"])
|
||||||
)
|
)
|
||||||
@@ -521,99 +406,182 @@ class MaaUserConfig(QConfig):
|
|||||||
"Info", "Annihilation", False, BoolValidator()
|
"Info", "Annihilation", False, BoolValidator()
|
||||||
)
|
)
|
||||||
self.Info_Routine = ConfigItem("Info", "Routine", False, BoolValidator())
|
self.Info_Routine = ConfigItem("Info", "Routine", False, BoolValidator())
|
||||||
self.Info_Infrastructure = ConfigItem(
|
self.Info_InfrastMode = OptionsConfigItem(
|
||||||
"Info", "Infrastructure", False, BoolValidator()
|
"Info",
|
||||||
|
"InfrastMode",
|
||||||
|
"Normal",
|
||||||
|
OptionsValidator(["Normal", "Rotation", "Custom"]),
|
||||||
)
|
)
|
||||||
self.Info_Password = ConfigItem("Info", "Password", "")
|
self.Info_Password = ConfigItem("Info", "Password", "")
|
||||||
self.Info_Notes = ConfigItem("Info", "Notes", "无")
|
self.Info_Notes = ConfigItem("Info", "Notes", "无")
|
||||||
self.Info_MedicineNumb = ConfigItem(
|
self.Info_MedicineNumb = ConfigItem(
|
||||||
"Info", "MedicineNumb", 0, RangeValidator(0, 1024)
|
"Info", "MedicineNumb", 0, RangeValidator(0, 1024)
|
||||||
)
|
)
|
||||||
|
self.Info_SeriesNumb = OptionsConfigItem(
|
||||||
|
"Info",
|
||||||
|
"SeriesNumb",
|
||||||
|
"0",
|
||||||
|
OptionsValidator(["0", "6", "5", "4", "3", "2", "1", "-1"]),
|
||||||
|
)
|
||||||
self.Info_GameId = ConfigItem("Info", "GameId", "-")
|
self.Info_GameId = ConfigItem("Info", "GameId", "-")
|
||||||
self.Info_GameId_1 = ConfigItem("Info", "GameId_1", "-")
|
self.Info_GameId_1 = ConfigItem("Info", "GameId_1", "-")
|
||||||
self.Info_GameId_2 = ConfigItem("Info", "GameId_2", "-")
|
self.Info_GameId_2 = ConfigItem("Info", "GameId_2", "-")
|
||||||
|
self.Info_GameId_Remain = ConfigItem("Info", "GameId_Remain", "-")
|
||||||
|
self.Info_IfSkland = ConfigItem("Info", "IfSkland", False, BoolValidator())
|
||||||
|
self.Info_SklandToken = ConfigItem("Info", "SklandToken", "")
|
||||||
|
|
||||||
self.Data_LastProxyDate = ConfigItem("Data", "LastProxyDate", "2000-01-01")
|
self.Data_LastProxyDate = ConfigItem("Data", "LastProxyDate", "2000-01-01")
|
||||||
self.Data_LastAnnihilationDate = ConfigItem(
|
self.Data_LastAnnihilationDate = ConfigItem(
|
||||||
"Data", "LastAnnihilationDate", "2000-01-01"
|
"Data", "LastAnnihilationDate", "2000-01-01"
|
||||||
)
|
)
|
||||||
|
self.Data_LastSklandDate = ConfigItem("Data", "LastSklandDate", "2000-01-01")
|
||||||
self.Data_ProxyTimes = ConfigItem(
|
self.Data_ProxyTimes = ConfigItem(
|
||||||
"Data", "ProxyTimes", 0, RangeValidator(0, 1024)
|
"Data", "ProxyTimes", 0, RangeValidator(0, 1024)
|
||||||
)
|
)
|
||||||
self.Data_IfPassCheck = ConfigItem("Data", "IfPassCheck", True, BoolValidator())
|
self.Data_IfPassCheck = ConfigItem("Data", "IfPassCheck", True, BoolValidator())
|
||||||
|
self.Data_CustomInfrastPlanIndex = ConfigItem(
|
||||||
|
"Data", "CustomInfrastPlanIndex", "0"
|
||||||
|
)
|
||||||
|
|
||||||
def toDict(self, serialize=True):
|
self.Task_IfWakeUp = ConfigItem("Task", "IfWakeUp", True, BoolValidator())
|
||||||
"""convert config items to `dict`"""
|
self.Task_IfRecruiting = ConfigItem(
|
||||||
items = {}
|
"Task", "IfRecruiting", True, BoolValidator()
|
||||||
for name in dir(self._cfg):
|
)
|
||||||
item = getattr(self._cfg, name)
|
self.Task_IfBase = ConfigItem("Task", "IfBase", True, BoolValidator())
|
||||||
if not isinstance(item, ConfigItem):
|
self.Task_IfCombat = ConfigItem("Task", "IfCombat", True, BoolValidator())
|
||||||
continue
|
self.Task_IfMall = ConfigItem("Task", "IfMall", True, BoolValidator())
|
||||||
|
self.Task_IfMission = ConfigItem("Task", "IfMission", True, BoolValidator())
|
||||||
|
self.Task_IfAutoRoguelike = ConfigItem(
|
||||||
|
"Task", "IfAutoRoguelike", False, BoolValidator()
|
||||||
|
)
|
||||||
|
self.Task_IfReclamation = ConfigItem(
|
||||||
|
"Task", "IfReclamation", False, BoolValidator()
|
||||||
|
)
|
||||||
|
|
||||||
value = item.serialize() if serialize else item.value
|
self.Notify_Enabled = ConfigItem("Notify", "Enabled", False, BoolValidator())
|
||||||
if not items.get(item.group):
|
self.Notify_IfSendStatistic = ConfigItem(
|
||||||
if not item.name:
|
"Notify", "IfSendStatistic", False, BoolValidator()
|
||||||
items[item.group] = value
|
)
|
||||||
else:
|
self.Notify_IfSendSixStar = ConfigItem(
|
||||||
items[item.group] = {}
|
"Notify", "IfSendSixStar", False, BoolValidator()
|
||||||
|
)
|
||||||
|
self.Notify_IfSendMail = ConfigItem(
|
||||||
|
"Notify", "IfSendMail", False, BoolValidator()
|
||||||
|
)
|
||||||
|
self.Notify_ToAddress = ConfigItem("Notify", "ToAddress", "")
|
||||||
|
self.Notify_IfServerChan = ConfigItem(
|
||||||
|
"Notify", "IfServerChan", False, BoolValidator()
|
||||||
|
)
|
||||||
|
self.Notify_ServerChanKey = ConfigItem("Notify", "ServerChanKey", "")
|
||||||
|
self.Notify_ServerChanChannel = ConfigItem("Notify", "ServerChanChannel", "")
|
||||||
|
self.Notify_ServerChanTag = ConfigItem("Notify", "ServerChanTag", "")
|
||||||
|
self.Notify_IfCompanyWebHookBot = ConfigItem(
|
||||||
|
"Notify", "IfCompanyWebHookBot", False, BoolValidator()
|
||||||
|
)
|
||||||
|
self.Notify_CompanyWebHookBotUrl = ConfigItem(
|
||||||
|
"Notify", "CompanyWebHookBotUrl", ""
|
||||||
|
)
|
||||||
|
|
||||||
if item.name:
|
def get_plan_info(self) -> Dict[str, Union[str, int]]:
|
||||||
items[item.group][item.name] = value
|
"""获取当前的计划下信息"""
|
||||||
|
|
||||||
return items
|
if self.get(self.Info_GameIdMode) == "固定":
|
||||||
|
return {
|
||||||
|
"MedicineNumb": self.get(self.Info_MedicineNumb),
|
||||||
|
"SeriesNumb": self.get(self.Info_SeriesNumb),
|
||||||
|
"GameId": self.get(self.Info_GameId),
|
||||||
|
"GameId_1": self.get(self.Info_GameId_1),
|
||||||
|
"GameId_2": self.get(self.Info_GameId_2),
|
||||||
|
"GameId_Remain": self.get(self.Info_GameId_Remain),
|
||||||
|
}
|
||||||
|
elif "计划" in self.get(self.Info_GameIdMode):
|
||||||
|
plan = Config.plan_dict[self.get(self.Info_GameIdMode)]["Config"]
|
||||||
|
return {
|
||||||
|
"MedicineNumb": plan.get(plan.get_current_info("MedicineNumb")),
|
||||||
|
"SeriesNumb": plan.get(plan.get_current_info("SeriesNumb")),
|
||||||
|
"GameId": plan.get(plan.get_current_info("GameId")),
|
||||||
|
"GameId_1": plan.get(plan.get_current_info("GameId_1")),
|
||||||
|
"GameId_2": plan.get(plan.get_current_info("GameId_2")),
|
||||||
|
"GameId_Remain": plan.get(plan.get_current_info("GameId_Remain")),
|
||||||
|
}
|
||||||
|
|
||||||
@exceptionHandler()
|
|
||||||
def load(self, file=None, config=None):
|
|
||||||
"""load config
|
|
||||||
|
|
||||||
Parameters
|
class MaaPlanConfig(LQConfig):
|
||||||
----------
|
"""MAA计划表配置"""
|
||||||
file: str or Path
|
|
||||||
the path of json config file
|
|
||||||
|
|
||||||
config: Config
|
def __init__(self) -> None:
|
||||||
config object to be initialized
|
super().__init__()
|
||||||
"""
|
|
||||||
if isinstance(config, QConfig):
|
|
||||||
self._cfg = config
|
|
||||||
self._cfg.themeChanged.connect(self.themeChanged)
|
|
||||||
|
|
||||||
if isinstance(file, (str, Path)):
|
self.Info_Name = ConfigItem("Info", "Name", "")
|
||||||
self._cfg.file = Path(file)
|
self.Info_Mode = OptionsConfigItem(
|
||||||
|
"Info", "Mode", "ALL", OptionsValidator(["ALL", "Weekly"])
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
self.config_item_dict: dict[str, Dict[str, ConfigItem]] = {}
|
||||||
with open(self._cfg.file, encoding="utf-8") as f:
|
|
||||||
cfg = json.load(f)
|
|
||||||
except:
|
|
||||||
cfg = {}
|
|
||||||
|
|
||||||
# map config items'key to item
|
for group in [
|
||||||
items = {}
|
"ALL",
|
||||||
for name in dir(self._cfg):
|
"Monday",
|
||||||
item = getattr(self._cfg, name)
|
"Tuesday",
|
||||||
if isinstance(item, ConfigItem):
|
"Wednesday",
|
||||||
items[item.key] = item
|
"Thursday",
|
||||||
|
"Friday",
|
||||||
|
"Saturday",
|
||||||
|
"Sunday",
|
||||||
|
]:
|
||||||
|
self.config_item_dict[group] = {}
|
||||||
|
|
||||||
# update the value of config item
|
self.config_item_dict[group]["MedicineNumb"] = ConfigItem(
|
||||||
for k, v in cfg.items():
|
group, "MedicineNumb", 0, RangeValidator(0, 1024)
|
||||||
if not isinstance(v, dict) and items.get(k) is not None:
|
)
|
||||||
items[k].deserializeFrom(v)
|
self.config_item_dict[group]["SeriesNumb"] = OptionsConfigItem(
|
||||||
elif isinstance(v, dict):
|
group,
|
||||||
for key, value in v.items():
|
"SeriesNumb",
|
||||||
key = k + "." + key
|
"0",
|
||||||
if items.get(key) is not None:
|
OptionsValidator(["0", "6", "5", "4", "3", "2", "1", "-1"]),
|
||||||
items[key].deserializeFrom(value)
|
)
|
||||||
|
self.config_item_dict[group]["GameId"] = ConfigItem(group, "GameId", "-")
|
||||||
|
self.config_item_dict[group]["GameId_1"] = ConfigItem(
|
||||||
|
group, "GameId_1", "-"
|
||||||
|
)
|
||||||
|
self.config_item_dict[group]["GameId_2"] = ConfigItem(
|
||||||
|
group, "GameId_2", "-"
|
||||||
|
)
|
||||||
|
self.config_item_dict[group]["GameId_Remain"] = ConfigItem(
|
||||||
|
group, "GameId_Remain", "-"
|
||||||
|
)
|
||||||
|
|
||||||
self.theme = self.get(self._cfg.themeMode)
|
for name in [
|
||||||
|
"MedicineNumb",
|
||||||
|
"SeriesNumb",
|
||||||
|
"GameId",
|
||||||
|
"GameId_1",
|
||||||
|
"GameId_2",
|
||||||
|
"GameId_Remain",
|
||||||
|
]:
|
||||||
|
setattr(self, f"{group}_{name}", self.config_item_dict[group][name])
|
||||||
|
|
||||||
|
def get_current_info(self, name: str) -> ConfigItem:
|
||||||
|
"""获取当前的计划表配置项"""
|
||||||
|
|
||||||
|
if self.get(self.Info_Mode) == "ALL":
|
||||||
|
return self.config_item_dict["ALL"][name]
|
||||||
|
elif self.get(self.Info_Mode) == "Weekly":
|
||||||
|
today = datetime.now().strftime("%A")
|
||||||
|
if today in self.config_item_dict:
|
||||||
|
return self.config_item_dict[today][name]
|
||||||
|
else:
|
||||||
|
return self.config_item_dict["ALL"][name]
|
||||||
|
|
||||||
|
|
||||||
class AppConfig(GlobalConfig):
|
class AppConfig(GlobalConfig):
|
||||||
|
|
||||||
VERSION = "4.3.4.0"
|
VERSION = "4.3.10.0"
|
||||||
|
|
||||||
gameid_refreshed = Signal()
|
gameid_refreshed = Signal()
|
||||||
PASSWORD_refreshed = Signal()
|
PASSWORD_refreshed = Signal()
|
||||||
user_info_changed = Signal()
|
user_info_changed = Signal()
|
||||||
|
power_sign_changed = Signal()
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@@ -628,16 +596,46 @@ class AppConfig(GlobalConfig):
|
|||||||
self.gameid_path = self.app_path / "data/gameid.txt"
|
self.gameid_path = self.app_path / "data/gameid.txt"
|
||||||
self.version_path = self.app_path / "resources/version.json"
|
self.version_path = self.app_path / "resources/version.json"
|
||||||
|
|
||||||
|
self.main_window = None
|
||||||
self.PASSWORD = ""
|
self.PASSWORD = ""
|
||||||
self.running_list = []
|
self.running_list = []
|
||||||
self.silence_list = []
|
self.silence_list = []
|
||||||
|
self.info_bar_list = []
|
||||||
self.gameid_dict = {
|
self.gameid_dict = {
|
||||||
"ALL": {"value": [], "text": []},
|
"ALL": {"value": [], "text": []},
|
||||||
"Today": {"value": [], "text": []},
|
"Monday": {"value": [], "text": []},
|
||||||
|
"Tuesday": {"value": [], "text": []},
|
||||||
|
"Wednesday": {"value": [], "text": []},
|
||||||
|
"Thursday": {"value": [], "text": []},
|
||||||
|
"Friday": {"value": [], "text": []},
|
||||||
|
"Saturday": {"value": [], "text": []},
|
||||||
|
"Sunday": {"value": [], "text": []},
|
||||||
}
|
}
|
||||||
|
self.power_sign = "NoAction"
|
||||||
self.if_ignore_silence = False
|
self.if_ignore_silence = False
|
||||||
self.if_database_opened = False
|
self.if_database_opened = False
|
||||||
|
|
||||||
|
self.search_member()
|
||||||
|
self.search_queue()
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
prog="AUTO_MAA",
|
||||||
|
description="A MAA Multi Account Management and Automation Tool",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--mode",
|
||||||
|
choices=["gui", "cli"],
|
||||||
|
default="gui",
|
||||||
|
help="使用UI界面或命令行模式运行程序",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--config",
|
||||||
|
nargs="+",
|
||||||
|
choices=list(self.member_dict.keys()) + list(self.queue_dict.keys()),
|
||||||
|
help="指定需要运行哪些配置项",
|
||||||
|
)
|
||||||
|
self.args = parser.parse_args()
|
||||||
|
|
||||||
self.initialize()
|
self.initialize()
|
||||||
|
|
||||||
def initialize(self) -> None:
|
def initialize(self) -> None:
|
||||||
@@ -660,7 +658,8 @@ class AppConfig(GlobalConfig):
|
|||||||
def init_logger(self) -> None:
|
def init_logger(self) -> None:
|
||||||
"""初始化日志记录器"""
|
"""初始化日志记录器"""
|
||||||
|
|
||||||
logger.remove(0)
|
if self.args.mode != "cli":
|
||||||
|
logger.remove(0)
|
||||||
|
|
||||||
logger.add(
|
logger.add(
|
||||||
sink=self.log_path,
|
sink=self.log_path,
|
||||||
@@ -673,10 +672,14 @@ class AppConfig(GlobalConfig):
|
|||||||
retention="1 month",
|
retention="1 month",
|
||||||
compression="zip",
|
compression="zip",
|
||||||
)
|
)
|
||||||
|
logger.info("")
|
||||||
logger.info("===================================")
|
logger.info("===================================")
|
||||||
logger.info("AUTO_MAA 主程序")
|
logger.info("AUTO_MAA 主程序")
|
||||||
logger.info(f"版本号: v{self.VERSION}")
|
logger.info(f"版本号: v{self.VERSION}")
|
||||||
logger.info(f"根目录: {self.app_path}")
|
logger.info(f"根目录: {self.app_path}")
|
||||||
|
logger.info(
|
||||||
|
f"运行模式: {'图形化界面' if self.args.mode == 'gui' else '命令行界面'}"
|
||||||
|
)
|
||||||
logger.info("===================================")
|
logger.info("===================================")
|
||||||
|
|
||||||
logger.info("日志记录器初始化完成")
|
logger.info("日志记录器初始化完成")
|
||||||
@@ -684,21 +687,23 @@ class AppConfig(GlobalConfig):
|
|||||||
def get_gameid(self) -> None:
|
def get_gameid(self) -> None:
|
||||||
|
|
||||||
# 从MAA服务器获取活动关卡信息
|
# 从MAA服务器获取活动关卡信息
|
||||||
Network.set_info(
|
network = Network.add_task(
|
||||||
mode="get",
|
mode="get",
|
||||||
url="https://ota.maa.plus/MaaAssistantArknights/api/gui/StageActivity.json",
|
url="https://api.maa.plus/MaaAssistantArknights/api/gui/StageActivity.json",
|
||||||
)
|
)
|
||||||
Network.start()
|
network.loop.exec()
|
||||||
Network.loop.exec()
|
network_result = Network.get_result(network)
|
||||||
if Network.stutus_code == 200:
|
if network_result["status_code"] == 200:
|
||||||
gameid_infos: List[Dict[str, Union[str, Dict[str, Union[str, int]]]]] = (
|
gameid_infos: List[Dict[str, Union[str, Dict[str, Union[str, int]]]]] = (
|
||||||
Network.response_json["Official"]["sideStoryStage"]
|
network_result["response_json"]["Official"]["sideStoryStage"]
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
logger.warning(f"无法从MAA服务器获取活动关卡信息:{Network.error_message}")
|
logger.warning(
|
||||||
|
f"无法从MAA服务器获取活动关卡信息:{network_result['error_message']}"
|
||||||
|
)
|
||||||
gameid_infos = []
|
gameid_infos = []
|
||||||
|
|
||||||
gameid_dict = {"value": [], "text": []}
|
ss_gameid_dict = {"value": [], "text": []}
|
||||||
|
|
||||||
for gameid_info in gameid_infos:
|
for gameid_info in gameid_infos:
|
||||||
|
|
||||||
@@ -711,53 +716,11 @@ class AppConfig(GlobalConfig):
|
|||||||
gameid_info["Activity"]["UtcExpireTime"], "%Y/%m/%d %H:%M:%S"
|
gameid_info["Activity"]["UtcExpireTime"], "%Y/%m/%d %H:%M:%S"
|
||||||
)
|
)
|
||||||
):
|
):
|
||||||
gameid_dict["value"].append(gameid_info["Value"])
|
ss_gameid_dict["value"].append(gameid_info["Value"])
|
||||||
gameid_dict["text"].append(gameid_info["Value"])
|
ss_gameid_dict["text"].append(gameid_info["Value"])
|
||||||
|
|
||||||
# 生成全部关卡信息
|
# 生成每日关卡信息
|
||||||
self.gameid_dict["ALL"]["value"] = gameid_dict["value"] + [
|
gameid_daily_info = [
|
||||||
"-",
|
|
||||||
"1-7",
|
|
||||||
"R8-11",
|
|
||||||
"12-17-HARD",
|
|
||||||
"CE-6",
|
|
||||||
"AP-5",
|
|
||||||
"CA-5",
|
|
||||||
"LS-6",
|
|
||||||
"SK-5",
|
|
||||||
"PR-A-1",
|
|
||||||
"PR-A-2",
|
|
||||||
"PR-B-1",
|
|
||||||
"PR-B-2",
|
|
||||||
"PR-C-1",
|
|
||||||
"PR-C-2",
|
|
||||||
"PR-D-1",
|
|
||||||
"PR-D-2",
|
|
||||||
]
|
|
||||||
self.gameid_dict["ALL"]["text"] = gameid_dict["text"] + [
|
|
||||||
"当前/上次",
|
|
||||||
"1-7",
|
|
||||||
"R8-11",
|
|
||||||
"12-17-HARD",
|
|
||||||
"龙门币-6/5",
|
|
||||||
"红票-5",
|
|
||||||
"技能-5",
|
|
||||||
"经验-6/5",
|
|
||||||
"碳-5",
|
|
||||||
"奶/盾芯片",
|
|
||||||
"奶/盾芯片组",
|
|
||||||
"术/狙芯片",
|
|
||||||
"术/狙芯片组",
|
|
||||||
"先/辅芯片",
|
|
||||||
"先/辅芯片组",
|
|
||||||
"近/特芯片",
|
|
||||||
"近/特芯片组",
|
|
||||||
]
|
|
||||||
|
|
||||||
# 生成本日关卡信息
|
|
||||||
days = self.server_date().isoweekday()
|
|
||||||
|
|
||||||
gameid_list = [
|
|
||||||
{"value": "-", "text": "当前/上次", "days": [1, 2, 3, 4, 5, 6, 7]},
|
{"value": "-", "text": "当前/上次", "days": [1, 2, 3, 4, 5, 6, 7]},
|
||||||
{"value": "1-7", "text": "1-7", "days": [1, 2, 3, 4, 5, 6, 7]},
|
{"value": "1-7", "text": "1-7", "days": [1, 2, 3, 4, 5, 6, 7]},
|
||||||
{"value": "R8-11", "text": "R8-11", "days": [1, 2, 3, 4, 5, 6, 7]},
|
{"value": "R8-11", "text": "R8-11", "days": [1, 2, 3, 4, 5, 6, 7]},
|
||||||
@@ -781,12 +744,20 @@ class AppConfig(GlobalConfig):
|
|||||||
{"value": "PR-D-2", "text": "近/特芯片组", "days": [2, 3, 6, 7]},
|
{"value": "PR-D-2", "text": "近/特芯片组", "days": [2, 3, 6, 7]},
|
||||||
]
|
]
|
||||||
|
|
||||||
for gameid_info in gameid_list:
|
for day in range(0, 8):
|
||||||
if days in gameid_info["days"]:
|
|
||||||
gameid_dict["value"].append(gameid_info["value"])
|
|
||||||
gameid_dict["text"].append(gameid_info["text"])
|
|
||||||
|
|
||||||
self.gameid_dict["Today"] = gameid_dict
|
today_gameid_dict = {"value": [], "text": []}
|
||||||
|
|
||||||
|
for gameid_info in gameid_daily_info:
|
||||||
|
|
||||||
|
if day in gameid_info["days"] or day == 0:
|
||||||
|
today_gameid_dict["value"].append(gameid_info["value"])
|
||||||
|
today_gameid_dict["text"].append(gameid_info["text"])
|
||||||
|
|
||||||
|
self.gameid_dict[calendar.day_name[day - 1] if day > 0 else "ALL"] = {
|
||||||
|
"value": today_gameid_dict["value"] + ss_gameid_dict["value"],
|
||||||
|
"text": today_gameid_dict["text"] + ss_gameid_dict["text"],
|
||||||
|
}
|
||||||
|
|
||||||
self.gameid_refreshed.emit()
|
self.gameid_refreshed.emit()
|
||||||
|
|
||||||
@@ -1217,6 +1188,28 @@ class AppConfig(GlobalConfig):
|
|||||||
sorted(user_dict.items(), key=lambda x: int(x[0][3:]))
|
sorted(user_dict.items(), key=lambda x: int(x[0][3:]))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def search_plan(self) -> None:
|
||||||
|
"""搜索所有计划表"""
|
||||||
|
|
||||||
|
self.plan_dict: Dict[str, Dict[str, Union[str, Path, MaaPlanConfig]]] = {}
|
||||||
|
if (self.app_path / "config/MaaPlanConfig").exists():
|
||||||
|
for maa_plan_dir in (self.app_path / "config/MaaPlanConfig").iterdir():
|
||||||
|
if maa_plan_dir.is_dir():
|
||||||
|
|
||||||
|
maa_plan_config = MaaPlanConfig()
|
||||||
|
maa_plan_config.load(maa_plan_dir / "config.json", maa_plan_config)
|
||||||
|
maa_plan_config.save()
|
||||||
|
|
||||||
|
self.plan_dict[maa_plan_dir.name] = {
|
||||||
|
"Type": "Maa",
|
||||||
|
"Path": maa_plan_dir,
|
||||||
|
"Config": maa_plan_config,
|
||||||
|
}
|
||||||
|
|
||||||
|
self.plan_dict = dict(
|
||||||
|
sorted(self.plan_dict.items(), key=lambda x: int(x[0][3:]))
|
||||||
|
)
|
||||||
|
|
||||||
def search_queue(self):
|
def search_queue(self):
|
||||||
"""搜索所有调度队列实例"""
|
"""搜索所有调度队列实例"""
|
||||||
|
|
||||||
@@ -1264,6 +1257,16 @@ class AppConfig(GlobalConfig):
|
|||||||
if queue["Config"].get(queue["Config"].queue_Member_10) == old:
|
if queue["Config"].get(queue["Config"].queue_Member_10) == old:
|
||||||
queue["Config"].set(queue["Config"].queue_Member_10, new)
|
queue["Config"].set(queue["Config"].queue_Member_10, new)
|
||||||
|
|
||||||
|
def change_plan(self, old: str, new: str) -> None:
|
||||||
|
"""修改脚本管理所有下属用户的计划表配置参数"""
|
||||||
|
|
||||||
|
for member in self.member_dict.values():
|
||||||
|
|
||||||
|
for user in member["UserData"].values():
|
||||||
|
|
||||||
|
if user["Config"].get(user["Config"].Info_GameIdMode) == old:
|
||||||
|
user["Config"].set(user["Config"].Info_GameIdMode, new)
|
||||||
|
|
||||||
def change_user_info(
|
def change_user_info(
|
||||||
self, name: str, user_data: Dict[str, Dict[str, Union[str, Path, dict]]]
|
self, name: str, user_data: Dict[str, Dict[str, Union[str, Path, dict]]]
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -1283,15 +1286,29 @@ class AppConfig(GlobalConfig):
|
|||||||
user_config.Data_LastAnnihilationDate,
|
user_config.Data_LastAnnihilationDate,
|
||||||
info["Config"]["Data"]["LastAnnihilationDate"],
|
info["Config"]["Data"]["LastAnnihilationDate"],
|
||||||
)
|
)
|
||||||
|
user_config.set(
|
||||||
|
user_config.Data_LastSklandDate,
|
||||||
|
info["Config"]["Data"]["LastSklandDate"],
|
||||||
|
)
|
||||||
user_config.set(
|
user_config.set(
|
||||||
user_config.Data_ProxyTimes, info["Config"]["Data"]["ProxyTimes"]
|
user_config.Data_ProxyTimes, info["Config"]["Data"]["ProxyTimes"]
|
||||||
)
|
)
|
||||||
user_config.set(
|
user_config.set(
|
||||||
user_config.Data_IfPassCheck, info["Config"]["Data"]["IfPassCheck"]
|
user_config.Data_IfPassCheck, info["Config"]["Data"]["IfPassCheck"]
|
||||||
)
|
)
|
||||||
|
user_config.set(
|
||||||
|
user_config.Data_CustomInfrastPlanIndex,
|
||||||
|
info["Config"]["Data"]["CustomInfrastPlanIndex"],
|
||||||
|
)
|
||||||
|
|
||||||
self.user_info_changed.emit()
|
self.user_info_changed.emit()
|
||||||
|
|
||||||
|
def set_power_sign(self, sign: str) -> None:
|
||||||
|
"""设置当前电源状态"""
|
||||||
|
|
||||||
|
self.power_sign = sign
|
||||||
|
self.power_sign_changed.emit()
|
||||||
|
|
||||||
def save_history(self, key: str, content: dict) -> None:
|
def save_history(self, key: str, content: dict) -> None:
|
||||||
"""保存历史记录"""
|
"""保存历史记录"""
|
||||||
|
|
||||||
|
|||||||
@@ -29,31 +29,37 @@ from loguru import logger
|
|||||||
from PySide6.QtCore import Qt
|
from PySide6.QtCore import Qt
|
||||||
from qfluentwidgets import InfoBar, InfoBarPosition
|
from qfluentwidgets import InfoBar, InfoBarPosition
|
||||||
|
|
||||||
|
from .config import Config
|
||||||
|
from .sound_player import SoundPlayer
|
||||||
|
|
||||||
|
|
||||||
class _MainInfoBar:
|
class _MainInfoBar:
|
||||||
"""信息通知栏"""
|
"""信息通知栏"""
|
||||||
|
|
||||||
def __init__(self, main_window=None):
|
# 模式到 InfoBar 方法的映射
|
||||||
|
mode_mapping = {
|
||||||
|
"success": InfoBar.success,
|
||||||
|
"warning": InfoBar.warning,
|
||||||
|
"error": InfoBar.error,
|
||||||
|
"info": InfoBar.info,
|
||||||
|
}
|
||||||
|
|
||||||
self.main_window = main_window
|
def push_info_bar(
|
||||||
|
self, mode: str, title: str, content: str, time: int, if_force: bool = False
|
||||||
def push_info_bar(self, mode: str, title: str, content: str, time: int):
|
):
|
||||||
"""推送到信息通知栏"""
|
"""推送到信息通知栏"""
|
||||||
if self.main_window is None:
|
if Config.main_window is None:
|
||||||
logger.error("信息通知栏未设置父窗口")
|
logger.error("信息通知栏未设置父窗口")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# 定义模式到 InfoBar 方法的映射
|
|
||||||
mode_mapping = {
|
|
||||||
"success": InfoBar.success,
|
|
||||||
"warning": InfoBar.warning,
|
|
||||||
"error": InfoBar.error,
|
|
||||||
"info": InfoBar.info,
|
|
||||||
}
|
|
||||||
|
|
||||||
# 根据 mode 获取对应的 InfoBar 方法
|
# 根据 mode 获取对应的 InfoBar 方法
|
||||||
info_bar_method = mode_mapping.get(mode)
|
info_bar_method = self.mode_mapping.get(mode)
|
||||||
if info_bar_method:
|
|
||||||
|
if not info_bar_method:
|
||||||
|
logger.error(f"未知的通知栏模式: {mode}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
if Config.main_window.isVisible():
|
||||||
info_bar_method(
|
info_bar_method(
|
||||||
title=title,
|
title=title,
|
||||||
content=content,
|
content=content,
|
||||||
@@ -61,10 +67,23 @@ class _MainInfoBar:
|
|||||||
isClosable=True,
|
isClosable=True,
|
||||||
position=InfoBarPosition.TOP_RIGHT,
|
position=InfoBarPosition.TOP_RIGHT,
|
||||||
duration=time,
|
duration=time,
|
||||||
parent=self.main_window,
|
parent=Config.main_window,
|
||||||
)
|
)
|
||||||
else:
|
elif if_force:
|
||||||
logger.error(f"未知的通知栏模式: {mode}")
|
# 如果主窗口不可见且强制推送,则录入消息队列等待窗口显示后推送
|
||||||
|
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()
|
MainInfoBar = _MainInfoBar()
|
||||||
|
|||||||
@@ -26,67 +26,60 @@ v4.3
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from PySide6.QtCore import QThread, QEventLoop, QTimer
|
from PySide6.QtCore import QObject, QThread, QEventLoop
|
||||||
|
import re
|
||||||
import time
|
import time
|
||||||
import requests
|
import requests
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
class _Network(QThread):
|
class NetworkThread(QThread):
|
||||||
|
"""网络请求线程类"""
|
||||||
|
|
||||||
max_retries = 3
|
max_retries = 3
|
||||||
timeout = 10
|
timeout = 10
|
||||||
backoff_factor = 0.1
|
backoff_factor = 0.1
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self, mode: str, url: str, path: Path = None) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
self.if_running = False
|
self.setObjectName(
|
||||||
self.mode = None
|
f"NetworkThread-{mode}-{re.sub(r'(&cdk=)[^&]+(&)', r'\1******\2', url)}"
|
||||||
self.url = None
|
)
|
||||||
|
|
||||||
|
self.mode = mode
|
||||||
|
self.url = url
|
||||||
|
self.path = path
|
||||||
|
|
||||||
|
self.status_code = None
|
||||||
|
self.response_json = None
|
||||||
|
self.error_message = None
|
||||||
|
|
||||||
self.loop = QEventLoop()
|
self.loop = QEventLoop()
|
||||||
self.wait_loop = QEventLoop()
|
|
||||||
|
|
||||||
@logger.catch
|
@logger.catch
|
||||||
def run(self) -> None:
|
def run(self) -> None:
|
||||||
"""运行网络请求线程"""
|
"""运行网络请求线程"""
|
||||||
|
|
||||||
self.if_running = True
|
|
||||||
|
|
||||||
if self.mode == "get":
|
if self.mode == "get":
|
||||||
self.get_json(self.url)
|
self.get_json(self.url)
|
||||||
elif self.mode == "get_file":
|
elif self.mode == "get_file":
|
||||||
self.get_file(self.url, self.path)
|
self.get_file(self.url, self.path)
|
||||||
|
|
||||||
self.if_running = False
|
|
||||||
|
|
||||||
def set_info(self, mode: str, url: str, path: Path = None) -> None:
|
|
||||||
"""设置网络请求信息"""
|
|
||||||
|
|
||||||
while self.if_running:
|
|
||||||
QTimer.singleShot(self.backoff_factor * 1000, self.wait_loop.quit)
|
|
||||||
self.wait_loop.exec()
|
|
||||||
|
|
||||||
self.mode = mode
|
|
||||||
self.url = url
|
|
||||||
self.path = path
|
|
||||||
|
|
||||||
self.stutus_code = None
|
|
||||||
self.response_json = None
|
|
||||||
self.error_message = None
|
|
||||||
|
|
||||||
def get_json(self, url: str) -> None:
|
def get_json(self, url: str) -> None:
|
||||||
"""通过get方法获取json数据"""
|
"""通过get方法获取json数据"""
|
||||||
|
|
||||||
|
response = None
|
||||||
|
|
||||||
for _ in range(self.max_retries):
|
for _ in range(self.max_retries):
|
||||||
try:
|
try:
|
||||||
response = requests.get(url, timeout=self.timeout)
|
response = requests.get(url, timeout=self.timeout)
|
||||||
self.stutus_code = response.status_code
|
self.status_code = response.status_code
|
||||||
self.response_json = response.json()
|
self.response_json = response.json()
|
||||||
self.error_message = None
|
self.error_message = None
|
||||||
break
|
break
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.stutus_code = response.status_code if response else None
|
self.status_code = response.status_code if response else None
|
||||||
self.response_json = None
|
self.response_json = None
|
||||||
self.error_message = str(e)
|
self.error_message = str(e)
|
||||||
time.sleep(self.backoff_factor)
|
time.sleep(self.backoff_factor)
|
||||||
@@ -94,23 +87,65 @@ class _Network(QThread):
|
|||||||
self.loop.quit()
|
self.loop.quit()
|
||||||
|
|
||||||
def get_file(self, url: str, path: Path) -> None:
|
def get_file(self, url: str, path: Path) -> None:
|
||||||
"""通过get方法获取json数据"""
|
"""通过get方法下载文件"""
|
||||||
|
|
||||||
|
response = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = requests.get(url, timeout=10)
|
response = requests.get(url, timeout=10)
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
with open(path, "wb") as file:
|
with open(path, "wb") as file:
|
||||||
file.write(response.content)
|
file.write(response.content)
|
||||||
self.stutus_code = response.status_code
|
self.status_code = response.status_code
|
||||||
else:
|
else:
|
||||||
self.stutus_code = response.status_code
|
self.status_code = response.status_code
|
||||||
self.error_message = "下载失败"
|
self.error_message = "下载失败"
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.stutus_code = response.status_code if response else None
|
self.status_code = response.status_code if response else None
|
||||||
self.error_message = str(e)
|
self.error_message = str(e)
|
||||||
|
|
||||||
self.loop.quit()
|
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()
|
Network = _Network()
|
||||||
|
|||||||
69
app/core/sound_player.py
Normal file
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()
|
||||||
@@ -35,6 +35,7 @@ from typing import Dict, Union
|
|||||||
from .config import Config
|
from .config import Config
|
||||||
from .main_info_bar import MainInfoBar
|
from .main_info_bar import MainInfoBar
|
||||||
from .network import Network
|
from .network import Network
|
||||||
|
from .sound_player import SoundPlayer
|
||||||
from app.models import MaaManager
|
from app.models import MaaManager
|
||||||
from app.services import System
|
from app.services import System
|
||||||
|
|
||||||
@@ -44,6 +45,7 @@ class Task(QThread):
|
|||||||
|
|
||||||
check_maa_version = Signal(str)
|
check_maa_version = Signal(str)
|
||||||
push_info_bar = Signal(str, str, str, int)
|
push_info_bar = Signal(str, str, str, int)
|
||||||
|
play_sound = Signal(str)
|
||||||
question = Signal(str, str)
|
question = Signal(str, str)
|
||||||
question_response = Signal(bool)
|
question_response = Signal(bool)
|
||||||
update_user_info = Signal(str, dict)
|
update_user_info = Signal(str, dict)
|
||||||
@@ -59,6 +61,8 @@ class Task(QThread):
|
|||||||
):
|
):
|
||||||
super(Task, self).__init__()
|
super(Task, self).__init__()
|
||||||
|
|
||||||
|
self.setObjectName(f"Task-{mode}-{name}")
|
||||||
|
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
self.name = name
|
self.name = name
|
||||||
self.info = info
|
self.info = info
|
||||||
@@ -82,6 +86,7 @@ class Task(QThread):
|
|||||||
)
|
)
|
||||||
self.task.check_maa_version.connect(self.check_maa_version.emit)
|
self.task.check_maa_version.connect(self.check_maa_version.emit)
|
||||||
self.task.push_info_bar.connect(self.push_info_bar.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.accomplish.connect(lambda: self.accomplish.emit([]))
|
||||||
|
|
||||||
self.task.run()
|
self.task.run()
|
||||||
@@ -141,6 +146,7 @@ class Task(QThread):
|
|||||||
self.question_response.disconnect()
|
self.question_response.disconnect()
|
||||||
self.question_response.connect(self.task.question_response.emit)
|
self.question_response.connect(self.task.question_response.emit)
|
||||||
self.task.push_info_bar.connect(self.push_info_bar.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.create_user_list.connect(self.create_user_list.emit)
|
||||||
self.task.update_user_list.connect(self.update_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_log_text.connect(self.update_log_text.emit)
|
||||||
@@ -154,6 +160,7 @@ class Task(QThread):
|
|||||||
Config.running_list.remove(task[2])
|
Config.running_list.remove(task[2])
|
||||||
|
|
||||||
task[1] = "完成"
|
task[1] = "完成"
|
||||||
|
self.update_task_list.emit(self.task_list)
|
||||||
logger.info(f"任务完成:{task[0]}")
|
logger.info(f"任务完成:{task[0]}")
|
||||||
self.push_info_bar.emit("info", "任务完成", task[0], 3000)
|
self.push_info_bar.emit("info", "任务完成", task[0], 3000)
|
||||||
|
|
||||||
@@ -172,10 +179,9 @@ class _TaskManager(QObject):
|
|||||||
create_gui = Signal(Task)
|
create_gui = Signal(Task)
|
||||||
connect_gui = Signal(Task)
|
connect_gui = Signal(Task)
|
||||||
|
|
||||||
def __init__(self, main_window=None):
|
def __init__(self):
|
||||||
super(_TaskManager, self).__init__()
|
super(_TaskManager, self).__init__()
|
||||||
|
|
||||||
self.main_window = main_window
|
|
||||||
self.task_dict: Dict[str, Task] = {}
|
self.task_dict: Dict[str, Task] = {}
|
||||||
|
|
||||||
def add_task(
|
def add_task(
|
||||||
@@ -191,6 +197,7 @@ class _TaskManager(QObject):
|
|||||||
|
|
||||||
logger.info(f"任务开始:{name}")
|
logger.info(f"任务开始:{name}")
|
||||||
MainInfoBar.push_info_bar("info", "任务开始", name, 3000)
|
MainInfoBar.push_info_bar("info", "任务开始", name, 3000)
|
||||||
|
SoundPlayer.play("任务开始")
|
||||||
|
|
||||||
Config.running_list.append(name)
|
Config.running_list.append(name)
|
||||||
self.task_dict[name] = Task(mode, name, info)
|
self.task_dict[name] = Task(mode, name, info)
|
||||||
@@ -199,6 +206,7 @@ class _TaskManager(QObject):
|
|||||||
lambda title, content: self.push_dialog(name, title, content)
|
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].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].update_user_info.connect(Config.change_user_info)
|
||||||
self.task_dict[name].accomplish.connect(
|
self.task_dict[name].accomplish.connect(
|
||||||
lambda logs: self.remove_task(mode, name, logs)
|
lambda logs: self.remove_task(mode, name, logs)
|
||||||
@@ -239,6 +247,7 @@ class _TaskManager(QObject):
|
|||||||
|
|
||||||
logger.info(f"任务结束:{name}")
|
logger.info(f"任务结束:{name}")
|
||||||
MainInfoBar.push_info_bar("info", "任务结束", name, 3000)
|
MainInfoBar.push_info_bar("info", "任务结束", name, 3000)
|
||||||
|
SoundPlayer.play("任务结束")
|
||||||
|
|
||||||
self.task_dict[name].deleteLater()
|
self.task_dict[name].deleteLater()
|
||||||
self.task_dict.pop(name)
|
self.task_dict.pop(name)
|
||||||
@@ -265,46 +274,35 @@ class _TaskManager(QObject):
|
|||||||
Config.queue_dict[name]["Config"].get(
|
Config.queue_dict[name]["Config"].get(
|
||||||
Config.queue_dict[name]["Config"].queueSet_AfterAccomplish
|
Config.queue_dict[name]["Config"].queueSet_AfterAccomplish
|
||||||
)
|
)
|
||||||
!= "None"
|
!= "NoAction"
|
||||||
|
and Config.power_sign == "NoAction"
|
||||||
):
|
):
|
||||||
|
Config.set_power_sign(
|
||||||
from app.ui import ProgressRingMessageBox
|
Config.queue_dict[name]["Config"].get(
|
||||||
|
Config.queue_dict[name]["Config"].queueSet_AfterAccomplish
|
||||||
mode_book = {
|
|
||||||
"Shutdown": "关机",
|
|
||||||
"Hibernate": "休眠",
|
|
||||||
"Sleep": "睡眠",
|
|
||||||
"KillSelf": "关闭AUTO_MAA",
|
|
||||||
}
|
|
||||||
|
|
||||||
choice = ProgressRingMessageBox(
|
|
||||||
self.main_window,
|
|
||||||
f"{mode_book[Config.queue_dict[name]["Config"].get(Config.queue_dict[name]["Config"].queueSet_AfterAccomplish)]}倒计时",
|
|
||||||
)
|
|
||||||
if choice.exec():
|
|
||||||
System.set_power(
|
|
||||||
Config.queue_dict[name]["Config"].get(
|
|
||||||
Config.queue_dict[name]["Config"].queueSet_AfterAccomplish
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if Config.args.mode == "cli" and Config.power_sign == "NoAction":
|
||||||
|
Config.set_power_sign("KillSelf")
|
||||||
|
|
||||||
def check_maa_version(self, v: str):
|
def check_maa_version(self, v: str):
|
||||||
"""检查MAA版本"""
|
"""检查MAA版本"""
|
||||||
|
|
||||||
Network.set_info(
|
network = Network.add_task(
|
||||||
mode="get",
|
mode="get",
|
||||||
url="https://mirrorchyan.com/api/resources/MAA/latest?user_agent=AutoMaaGui&os=win&arch=x64&channel=stable",
|
url="https://mirrorchyan.com/api/resources/MAA/latest?user_agent=AutoMaaGui&os=win&arch=x64&channel=stable",
|
||||||
)
|
)
|
||||||
Network.start()
|
network.loop.exec()
|
||||||
Network.loop.exec()
|
network_result = Network.get_result(network)
|
||||||
if Network.stutus_code == 200:
|
if network_result["status_code"] == 200:
|
||||||
maa_info = Network.response_json
|
maa_info = network_result["response_json"]
|
||||||
else:
|
else:
|
||||||
logger.warning(f"获取MAA版本信息时出错:{Network.error_message}")
|
logger.warning(f"获取MAA版本信息时出错:{network_result['error_message']}")
|
||||||
MainInfoBar.push_info_bar(
|
MainInfoBar.push_info_bar(
|
||||||
"warning",
|
"warning",
|
||||||
"获取MAA版本信息时出错",
|
"获取MAA版本信息时出错",
|
||||||
f"网络错误:{Network.stutus_code}",
|
f"网络错误:{network_result['status_code']}",
|
||||||
5000,
|
5000,
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
@@ -324,7 +322,7 @@ class _TaskManager(QObject):
|
|||||||
def push_dialog(self, name: str, title: str, content: str):
|
def push_dialog(self, name: str, title: str, content: str):
|
||||||
"""推送对话框"""
|
"""推送对话框"""
|
||||||
|
|
||||||
choice = MessageBox(title, content, self.main_window)
|
choice = MessageBox(title, content, Config.main_window)
|
||||||
choice.yesButton.setText("是")
|
choice.yesButton.setText("是")
|
||||||
choice.cancelButton.setText("否")
|
choice.cancelButton.setText("否")
|
||||||
|
|
||||||
|
|||||||
@@ -26,26 +26,25 @@ v4.3
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from PySide6.QtWidgets import QWidget
|
from PySide6.QtCore import QObject, QTimer
|
||||||
from PySide6.QtCore import QTimer
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import pyautogui
|
from pathlib import Path
|
||||||
|
import keyboard
|
||||||
|
|
||||||
from .config import Config
|
from .config import Config
|
||||||
from .task_manager import TaskManager
|
from .task_manager import TaskManager
|
||||||
from app.services import System
|
from app.services import System
|
||||||
|
|
||||||
|
|
||||||
class _MainTimer(QWidget):
|
class _MainTimer(QObject):
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
|
|
||||||
self.if_FailSafeException = False
|
|
||||||
|
|
||||||
self.Timer = QTimer()
|
self.Timer = QTimer()
|
||||||
self.Timer.timeout.connect(self.timed_start)
|
self.Timer.timeout.connect(self.timed_start)
|
||||||
self.Timer.timeout.connect(self.set_silence)
|
self.Timer.timeout.connect(self.set_silence)
|
||||||
|
self.Timer.timeout.connect(self.check_power)
|
||||||
self.Timer.start(1000)
|
self.Timer.start(1000)
|
||||||
self.LongTimer = QTimer()
|
self.LongTimer = QTimer()
|
||||||
self.LongTimer.timeout.connect(self.long_timed_task)
|
self.LongTimer.timeout.connect(self.long_timed_task)
|
||||||
@@ -55,6 +54,9 @@ class _MainTimer(QWidget):
|
|||||||
"""长时间定期检定任务"""
|
"""长时间定期检定任务"""
|
||||||
|
|
||||||
Config.get_gameid()
|
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):
|
def timed_start(self):
|
||||||
"""定时启动代理任务"""
|
"""定时启动代理任务"""
|
||||||
@@ -93,22 +95,44 @@ class _MainTimer(QWidget):
|
|||||||
):
|
):
|
||||||
|
|
||||||
windows = System.get_window_info()
|
windows = System.get_window_info()
|
||||||
|
|
||||||
|
# 此处排除雷电名为新通知的窗口
|
||||||
if any(
|
if any(
|
||||||
str(emulator_path) in window
|
str(emulator_path) in window and window[0] != "新通知"
|
||||||
for window in windows
|
for window in windows
|
||||||
for emulator_path in Config.silence_list
|
for emulator_path in Config.silence_list
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
pyautogui.hotkey(
|
keyboard.press_and_release(
|
||||||
*[
|
"+".join(
|
||||||
_.strip().lower()
|
_.strip().lower()
|
||||||
for _ in Config.get(Config.function_BossKey).split("+")
|
for _ in Config.get(Config.function_BossKey).split("+")
|
||||||
]
|
)
|
||||||
)
|
)
|
||||||
except pyautogui.FailSafeException as e:
|
except Exception as e:
|
||||||
if not self.if_FailSafeException:
|
logger.error(f"模拟按键时出错:{e}")
|
||||||
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()
|
MainTimer = _MainTimer()
|
||||||
|
|||||||
1099
app/models/MAA.py
1099
app/models/MAA.py
File diff suppressed because it is too large
Load Diff
@@ -32,5 +32,6 @@ __license__ = "GPL-3.0 license"
|
|||||||
from .notification import Notify
|
from .notification import Notify
|
||||||
from .security import Crypto
|
from .security import Crypto
|
||||||
from .system import System
|
from .system import System
|
||||||
|
from .skland import skland_sign_in
|
||||||
|
|
||||||
__all__ = ["Notify", "Crypto", "System"]
|
__all__ = ["Notify", "Crypto", "System", "skland_sign_in"]
|
||||||
|
|||||||
@@ -25,26 +25,26 @@ v4.3
|
|||||||
作者:DLmaster_361
|
作者:DLmaster_361
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from PySide6.QtWidgets import QWidget
|
|
||||||
from PySide6.QtCore import Signal
|
|
||||||
import requests
|
|
||||||
import time
|
|
||||||
from loguru import logger
|
|
||||||
from plyer import notification
|
|
||||||
import re
|
import re
|
||||||
import smtplib
|
import smtplib
|
||||||
from email.mime.text import MIMEText
|
import time
|
||||||
from email.mime.multipart import MIMEMultipart
|
|
||||||
from email.header import Header
|
from email.header import Header
|
||||||
|
from email.mime.multipart import MIMEMultipart
|
||||||
|
from email.mime.text import MIMEText
|
||||||
from email.utils import formataddr
|
from email.utils import formataddr
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from serverchan_sdk import sc_send
|
import requests
|
||||||
|
from PySide6.QtCore import QObject, Signal
|
||||||
|
from loguru import logger
|
||||||
|
from plyer import notification
|
||||||
|
|
||||||
from app.core import Config
|
from app.core import Config
|
||||||
from app.services.security import Crypto
|
from app.services.security import Crypto
|
||||||
|
from app.utils.ImageUtils import ImageUtils
|
||||||
|
|
||||||
|
|
||||||
class Notification(QWidget):
|
class Notification(QObject):
|
||||||
|
|
||||||
push_info_bar = Signal(str, str, str, int)
|
push_info_bar = Signal(str, str, str, int)
|
||||||
|
|
||||||
@@ -68,157 +68,231 @@ class Notification(QWidget):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def send_mail(self, mode, title, content) -> None:
|
def send_mail(self, mode, title, content, to_address) -> None:
|
||||||
"""推送邮件通知"""
|
"""推送邮件通知"""
|
||||||
|
if (
|
||||||
if Config.get(Config.notify_IfSendMail):
|
Config.get(Config.notify_SMTPServerAddress) == ""
|
||||||
|
or Config.get(Config.notify_AuthorizationCode) == ""
|
||||||
if (
|
or not bool(
|
||||||
Config.get(Config.notify_SMTPServerAddress) == ""
|
re.match(
|
||||||
or Config.get(Config.notify_AuthorizationCode) == ""
|
r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$",
|
||||||
or not bool(
|
|
||||||
re.match(
|
|
||||||
r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$",
|
|
||||||
Config.get(Config.notify_FromAddress),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
or not bool(
|
|
||||||
re.match(
|
|
||||||
r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$",
|
|
||||||
Config.get(Config.notify_ToAddress),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
):
|
|
||||||
logger.error(
|
|
||||||
"请正确设置邮件通知的SMTP服务器地址、授权码、发件人地址和收件人地址"
|
|
||||||
)
|
|
||||||
self.push_info_bar.emit(
|
|
||||||
"error",
|
|
||||||
"邮件通知推送异常",
|
|
||||||
"请正确设置邮件通知的SMTP服务器地址、授权码、发件人地址和收件人地址",
|
|
||||||
-1,
|
|
||||||
)
|
|
||||||
return None
|
|
||||||
|
|
||||||
try:
|
|
||||||
# 定义邮件正文
|
|
||||||
if mode == "文本":
|
|
||||||
message = MIMEText(content, "plain", "utf-8")
|
|
||||||
elif mode == "网页":
|
|
||||||
message = MIMEMultipart("alternative")
|
|
||||||
message["From"] = formataddr(
|
|
||||||
(
|
|
||||||
Header("AUTO_MAA通知服务", "utf-8").encode(),
|
|
||||||
Config.get(Config.notify_FromAddress),
|
|
||||||
)
|
|
||||||
) # 发件人显示的名字
|
|
||||||
message["To"] = formataddr(
|
|
||||||
(
|
|
||||||
Header("AUTO_MAA用户", "utf-8").encode(),
|
|
||||||
Config.get(Config.notify_ToAddress),
|
|
||||||
)
|
|
||||||
) # 收件人显示的名字
|
|
||||||
message["Subject"] = Header(title, "utf-8")
|
|
||||||
|
|
||||||
if mode == "网页":
|
|
||||||
message.attach(MIMEText(content, "html", "utf-8"))
|
|
||||||
|
|
||||||
smtpObj = smtplib.SMTP_SSL(
|
|
||||||
Config.get(Config.notify_SMTPServerAddress),
|
|
||||||
465,
|
|
||||||
)
|
|
||||||
smtpObj.login(
|
|
||||||
Config.get(Config.notify_FromAddress),
|
Config.get(Config.notify_FromAddress),
|
||||||
Crypto.win_decryptor(Config.get(Config.notify_AuthorizationCode)),
|
|
||||||
)
|
)
|
||||||
smtpObj.sendmail(
|
)
|
||||||
Config.get(Config.notify_FromAddress),
|
or not bool(
|
||||||
Config.get(Config.notify_ToAddress),
|
re.match(
|
||||||
message.as_string(),
|
r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$",
|
||||||
|
to_address,
|
||||||
)
|
)
|
||||||
smtpObj.quit()
|
)
|
||||||
logger.success("邮件发送成功")
|
):
|
||||||
except Exception as e:
|
logger.error(
|
||||||
logger.error(f"发送邮件时出错:\n{e}")
|
"请正确设置邮件通知的SMTP服务器地址、授权码、发件人地址和收件人地址"
|
||||||
self.push_info_bar.emit("error", "发送邮件时出错", f"{e}", -1)
|
)
|
||||||
|
self.push_info_bar.emit(
|
||||||
|
"error",
|
||||||
|
"邮件通知推送异常",
|
||||||
|
"请正确设置邮件通知的SMTP服务器地址、授权码、发件人地址和收件人地址",
|
||||||
|
-1,
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
def ServerChanPush(self, title, content):
|
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酱推送通知"""
|
"""使用Server酱推送通知"""
|
||||||
|
if not send_key:
|
||||||
|
logger.error("请正确设置Server酱的SendKey")
|
||||||
|
self.push_info_bar.emit(
|
||||||
|
"error", "Server酱通知推送异常", "请正确设置Server酱的SendKey", -1
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
if Config.get(Config.notify_IfServerChan):
|
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"
|
||||||
|
|
||||||
if Config.get(Config.notify_ServerChanKey) == "":
|
# 构建 tags 和 channel
|
||||||
logger.error("请正确设置Server酱的SendKey")
|
def is_valid(s):
|
||||||
self.push_info_bar.emit(
|
return s == "" or (
|
||||||
"error",
|
s == "|".join(s.split("|"))
|
||||||
"Server酱通知推送异常",
|
and (s.count("|") == 0 or all(s.split("|")))
|
||||||
"请正确设置Server酱的SendKey",
|
|
||||||
-1,
|
|
||||||
)
|
)
|
||||||
return None
|
|
||||||
else:
|
|
||||||
send_key = Config.get(Config.notify_ServerChanKey)
|
|
||||||
|
|
||||||
option = {}
|
tags = "|".join(_.strip() for _ in tag.split("|"))
|
||||||
is_valid = lambda s: s == "" or (
|
channels = "|".join(_.strip() for _ in channel.split("|"))
|
||||||
s == "|".join(s.split("|")) and (s.count("|") == 0 or all(s.split("|")))
|
|
||||||
)
|
|
||||||
"""
|
|
||||||
is_valid => True, 如果启用的话需要正确设置Tag和Channel。
|
|
||||||
允许空的Tag和Channel即不启用,但不允许例如a||b,|a|b,a|b|,||||
|
|
||||||
"""
|
|
||||||
send_tag = "|".join(
|
|
||||||
_.strip() for _ in Config.get(Config.notify_ServerChanTag).split("|")
|
|
||||||
)
|
|
||||||
send_channel = "|".join(
|
|
||||||
_.strip()
|
|
||||||
for _ in Config.get(Config.notify_ServerChanChannel).split("|")
|
|
||||||
)
|
|
||||||
|
|
||||||
if is_valid(send_tag):
|
options = {}
|
||||||
option["tags"] = send_tag
|
if is_valid(tags):
|
||||||
|
options["tags"] = tags
|
||||||
else:
|
else:
|
||||||
option["tags"] = ""
|
logger.warning("Server酱 Tag 配置不正确,将被忽略")
|
||||||
logger.warning("请正确设置Auto_MAA中ServerChan的Tag。")
|
|
||||||
self.push_info_bar.emit(
|
self.push_info_bar.emit(
|
||||||
"warning",
|
"warning",
|
||||||
"Server酱通知推送异常",
|
"Server酱通知推送异常",
|
||||||
"请正确设置Auto_MAA中ServerChan的Tag。",
|
"请正确设置 ServerChan 的 Tag",
|
||||||
-1,
|
-1,
|
||||||
)
|
)
|
||||||
|
|
||||||
if is_valid(send_channel):
|
if is_valid(channels):
|
||||||
option["channel"] = send_channel
|
options["channel"] = channels
|
||||||
else:
|
else:
|
||||||
option["channel"] = ""
|
logger.warning("Server酱 Channel 配置不正确,将被忽略")
|
||||||
logger.warning("请正确设置Auto_MAA中ServerChan的Channel。")
|
|
||||||
self.push_info_bar.emit(
|
self.push_info_bar.emit(
|
||||||
"warning",
|
"warning",
|
||||||
"Server酱通知推送异常",
|
"Server酱通知推送异常",
|
||||||
"请正确设置Auto_MAA中ServerChan的Channel。",
|
"请正确设置 ServerChan 的 Channel",
|
||||||
-1,
|
-1,
|
||||||
)
|
)
|
||||||
|
|
||||||
response = sc_send(send_key, title, content, option)
|
# 请求发送
|
||||||
if response["code"] == 0:
|
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酱推送通知成功")
|
logger.info("Server酱推送通知成功")
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
logger.info("Server酱推送通知失败")
|
error_code = result.get("code", "-1")
|
||||||
logger.error(response)
|
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 CompanyWebHookBotPushImage(self, image_path: Path, webhook_url: str) -> bool:
|
||||||
|
"""使用企业微信群机器人推送图片通知"""
|
||||||
|
try:
|
||||||
|
# 压缩图片
|
||||||
|
ImageUtils.compress_image_if_needed(image_path)
|
||||||
|
|
||||||
|
# 检查图片是否存在
|
||||||
|
if not image_path.exists():
|
||||||
|
logger.error(
|
||||||
|
"图片推送异常 | 图片不存在或者压缩失败,请检查图片路径是否正确"
|
||||||
|
)
|
||||||
self.push_info_bar.emit(
|
self.push_info_bar.emit(
|
||||||
"error",
|
"error",
|
||||||
"Server酱通知推送失败",
|
"企业微信群机器人通知推送异常",
|
||||||
f'使用Server酱推送通知时出错:\n{response["data"]['error']}',
|
"图片不存在或者压缩失败,请检查图片路径是否正确",
|
||||||
-1,
|
-1,
|
||||||
)
|
)
|
||||||
return f'使用Server酱推送通知时出错:\n{response["data"]['error']}'
|
return False
|
||||||
|
|
||||||
def CompanyWebHookBotPush(self, title, content):
|
if not webhook_url:
|
||||||
"""使用企业微信群机器人推送通知"""
|
|
||||||
if Config.get(Config.notify_IfCompanyWebHookBot):
|
|
||||||
|
|
||||||
if Config.get(Config.notify_CompanyWebHookBotUrl) == "":
|
|
||||||
logger.error("请正确设置企业微信群机器人的WebHook地址")
|
logger.error("请正确设置企业微信群机器人的WebHook地址")
|
||||||
self.push_info_bar.emit(
|
self.push_info_bar.emit(
|
||||||
"error",
|
"error",
|
||||||
@@ -226,45 +300,72 @@ class Notification(QWidget):
|
|||||||
"请正确设置企业微信群机器人的WebHook地址",
|
"请正确设置企业微信群机器人的WebHook地址",
|
||||||
-1,
|
-1,
|
||||||
)
|
)
|
||||||
return None
|
return False
|
||||||
|
|
||||||
|
# 获取图片base64和md5
|
||||||
|
try:
|
||||||
|
image_base64 = ImageUtils.get_base64_from_file(str(image_path))
|
||||||
|
image_md5 = ImageUtils.calculate_md5_from_file(str(image_path))
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"图片编码或MD5计算失败:{e}")
|
||||||
|
self.push_info_bar.emit(
|
||||||
|
"error",
|
||||||
|
"企业微信群机器人通知推送异常",
|
||||||
|
f"图片编码或MD5计算失败:{e}",
|
||||||
|
-1,
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"msgtype": "image",
|
||||||
|
"image": {"base64": image_base64, "md5": image_md5},
|
||||||
|
}
|
||||||
|
|
||||||
content = f"{title}\n{content}"
|
|
||||||
data = {"msgtype": "text", "text": {"content": content}}
|
|
||||||
# 从远程服务器获取最新主题图像
|
|
||||||
for _ in range(3):
|
for _ in range(3):
|
||||||
try:
|
try:
|
||||||
response = requests.post(
|
response = requests.post(
|
||||||
url=Config.get(Config.notify_CompanyWebHookBotUrl),
|
url=webhook_url,
|
||||||
json=data,
|
json=data,
|
||||||
timeout=10,
|
timeout=10,
|
||||||
)
|
)
|
||||||
info = response.json()
|
info = response.json()
|
||||||
break
|
break
|
||||||
except Exception as e:
|
except requests.RequestException as e:
|
||||||
err = e
|
err = e
|
||||||
|
logger.warning(f"推送企业微信群机器人图片第{_+1}次失败:{e}")
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
else:
|
else:
|
||||||
logger.error(f"推送企业微信群机器人时出错:{err}")
|
logger.error(f"推送企业微信群机器人图片时出错:{err}")
|
||||||
self.push_info_bar.emit(
|
self.push_info_bar.emit(
|
||||||
"error",
|
"error",
|
||||||
"企业微信群机器人通知推送失败",
|
"企业微信群机器人图片推送失败",
|
||||||
f'使用企业微信群机器人推送通知时出错:{info["errmsg"]}',
|
f"使用企业微信群机器人推送图片时出错:{err}",
|
||||||
-1,
|
-1,
|
||||||
)
|
)
|
||||||
return None
|
return False
|
||||||
|
|
||||||
if info["errcode"] == 0:
|
if info.get("errcode") == 0:
|
||||||
logger.info("企业微信群机器人推送通知成功")
|
logger.info("企业微信群机器人推送图片成功")
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
logger.error(f"企业微信群机器人推送通知失败:{info}")
|
logger.error(f"企业微信群机器人推送图片失败:{info}")
|
||||||
self.push_info_bar.emit(
|
self.push_info_bar.emit(
|
||||||
"error",
|
"error",
|
||||||
"企业微信群机器人通知推送失败",
|
"企业微信群机器人图片推送失败",
|
||||||
f'使用企业微信群机器人推送通知时出错:{info["errmsg"]}',
|
f"使用企业微信群机器人推送图片时出错:{info}",
|
||||||
-1,
|
-1,
|
||||||
)
|
)
|
||||||
return f'使用企业微信群机器人推送通知时出错:{info["errmsg"]}'
|
return False
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"推送企业微信群机器人图片时发生未知异常:{e}")
|
||||||
|
self.push_info_bar.emit(
|
||||||
|
"error",
|
||||||
|
"企业微信群机器人图片推送失败",
|
||||||
|
f"发生未知异常:{e}",
|
||||||
|
-1,
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
def send_test_notification(self):
|
def send_test_notification(self):
|
||||||
"""发送测试通知到所有已启用的通知渠道"""
|
"""发送测试通知到所有已启用的通知渠道"""
|
||||||
@@ -282,6 +383,7 @@ class Notification(QWidget):
|
|||||||
"文本",
|
"文本",
|
||||||
"AUTO_MAA测试通知",
|
"AUTO_MAA测试通知",
|
||||||
"这是 AUTO_MAA 外部通知测试信息。如果你看到了这段内容,说明 AUTO_MAA 的通知功能已经正确配置且可以正常工作!",
|
"这是 AUTO_MAA 外部通知测试信息。如果你看到了这段内容,说明 AUTO_MAA 的通知功能已经正确配置且可以正常工作!",
|
||||||
|
Config.get(Config.notify_ToAddress),
|
||||||
)
|
)
|
||||||
|
|
||||||
# 发送Server酱通知
|
# 发送Server酱通知
|
||||||
@@ -289,6 +391,9 @@ class Notification(QWidget):
|
|||||||
self.ServerChanPush(
|
self.ServerChanPush(
|
||||||
"AUTO_MAA测试通知",
|
"AUTO_MAA测试通知",
|
||||||
"这是 AUTO_MAA 外部通知测试信息。如果你看到了这段内容,说明 AUTO_MAA 的通知功能已经正确配置且可以正常工作!",
|
"这是 AUTO_MAA 外部通知测试信息。如果你看到了这段内容,说明 AUTO_MAA 的通知功能已经正确配置且可以正常工作!",
|
||||||
|
Config.get(Config.notify_ServerChanKey),
|
||||||
|
Config.get(Config.notify_ServerChanTag),
|
||||||
|
Config.get(Config.notify_ServerChanChannel),
|
||||||
)
|
)
|
||||||
|
|
||||||
# 发送企业微信机器人通知
|
# 发送企业微信机器人通知
|
||||||
@@ -296,6 +401,11 @@ class Notification(QWidget):
|
|||||||
self.CompanyWebHookBotPush(
|
self.CompanyWebHookBotPush(
|
||||||
"AUTO_MAA测试通知",
|
"AUTO_MAA测试通知",
|
||||||
"这是 AUTO_MAA 外部通知测试信息。如果你看到了这段内容,说明 AUTO_MAA 的通知功能已经正确配置且可以正常工作!",
|
"这是 AUTO_MAA 外部通知测试信息。如果你看到了这段内容,说明 AUTO_MAA 的通知功能已经正确配置且可以正常工作!",
|
||||||
|
Config.get(Config.notify_CompanyWebHookBotUrl),
|
||||||
|
)
|
||||||
|
Notify.CompanyWebHookBotPushImage(
|
||||||
|
Config.app_path / "resources/images/notification/test_notify.png",
|
||||||
|
Config.get(Config.notify_CompanyWebHookBotUrl),
|
||||||
)
|
)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|||||||
241
app/services/skland.py
Normal file
241
app/services/skland.py
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
# 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[:3]}******{token[-3:]} 获得认证代码失败:{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}
|
||||||
@@ -26,7 +26,7 @@ v4.3
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from PySide6.QtWidgets import QApplication, QWidget
|
from PySide6.QtWidgets import QApplication
|
||||||
import sys
|
import sys
|
||||||
import ctypes
|
import ctypes
|
||||||
import win32gui
|
import win32gui
|
||||||
@@ -44,9 +44,7 @@ class _SystemHandler:
|
|||||||
ES_CONTINUOUS = 0x80000000
|
ES_CONTINUOUS = 0x80000000
|
||||||
ES_SYSTEM_REQUIRED = 0x00000001
|
ES_SYSTEM_REQUIRED = 0x00000001
|
||||||
|
|
||||||
def __init__(self, main_window: QWidget = None):
|
def __init__(self):
|
||||||
|
|
||||||
self.main_window = main_window
|
|
||||||
|
|
||||||
self.set_Sleep()
|
self.set_Sleep()
|
||||||
self.set_SelfStart()
|
self.set_SelfStart()
|
||||||
@@ -89,7 +87,7 @@ class _SystemHandler:
|
|||||||
|
|
||||||
if sys.platform.startswith("win"):
|
if sys.platform.startswith("win"):
|
||||||
|
|
||||||
if mode == "None":
|
if mode == "NoAction":
|
||||||
|
|
||||||
logger.info("不执行系统电源操作")
|
logger.info("不执行系统电源操作")
|
||||||
|
|
||||||
@@ -112,12 +110,13 @@ class _SystemHandler:
|
|||||||
|
|
||||||
elif mode == "KillSelf":
|
elif mode == "KillSelf":
|
||||||
|
|
||||||
self.main_window.close()
|
Config.main_window.close()
|
||||||
QApplication.quit()
|
QApplication.quit()
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
elif sys.platform.startswith("linux"):
|
elif sys.platform.startswith("linux"):
|
||||||
|
|
||||||
if mode == "None":
|
if mode == "NoAction":
|
||||||
|
|
||||||
logger.info("不执行系统电源操作")
|
logger.info("不执行系统电源操作")
|
||||||
|
|
||||||
@@ -138,8 +137,9 @@ class _SystemHandler:
|
|||||||
|
|
||||||
elif mode == "KillSelf":
|
elif mode == "KillSelf":
|
||||||
|
|
||||||
self.main_window.close()
|
Config.main_window.close()
|
||||||
QApplication.quit()
|
QApplication.quit()
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
def is_startup(self) -> bool:
|
def is_startup(self) -> bool:
|
||||||
"""判断程序是否已经开机自启"""
|
"""判断程序是否已经开机自启"""
|
||||||
|
|||||||
961
app/ui/Widget.py
961
app/ui/Widget.py
File diff suppressed because it is too large
Load Diff
@@ -33,8 +33,8 @@ from PySide6.QtWidgets import (
|
|||||||
QHBoxLayout,
|
QHBoxLayout,
|
||||||
)
|
)
|
||||||
from qfluentwidgets import (
|
from qfluentwidgets import (
|
||||||
|
BodyLabel,
|
||||||
CardWidget,
|
CardWidget,
|
||||||
Pivot,
|
|
||||||
ScrollArea,
|
ScrollArea,
|
||||||
FluentIcon,
|
FluentIcon,
|
||||||
HeaderCardWidget,
|
HeaderCardWidget,
|
||||||
@@ -44,13 +44,12 @@ from qfluentwidgets import (
|
|||||||
SubtitleLabel,
|
SubtitleLabel,
|
||||||
PushButton,
|
PushButton,
|
||||||
)
|
)
|
||||||
from PySide6.QtCore import Qt
|
|
||||||
from PySide6.QtGui import QTextCursor
|
from PySide6.QtGui import QTextCursor
|
||||||
from typing import List, Dict
|
from typing import List, Dict
|
||||||
|
|
||||||
|
|
||||||
from app.core import Config, TaskManager, Task, MainInfoBar
|
from app.core import Config, TaskManager, Task, MainInfoBar, SoundPlayer
|
||||||
from .Widget import StatefulItemCard, ComboBoxMessageBox
|
from .Widget import StatefulItemCard, ComboBoxMessageBox, PivotArea
|
||||||
|
|
||||||
|
|
||||||
class DispatchCenter(QWidget):
|
class DispatchCenter(QWidget):
|
||||||
@@ -60,13 +59,29 @@ class DispatchCenter(QWidget):
|
|||||||
|
|
||||||
self.setObjectName("调度中枢")
|
self.setObjectName("调度中枢")
|
||||||
|
|
||||||
self.pivot = Pivot(self)
|
self.multi_button = PushButton(FluentIcon.ADD, "添加任务", self)
|
||||||
|
self.multi_button.setToolTip("添加任务")
|
||||||
|
self.multi_button.clicked.connect(self.start_multi_task)
|
||||||
|
|
||||||
|
self.power_combox = ComboBox()
|
||||||
|
self.power_combox.addItem("无动作", userData="NoAction")
|
||||||
|
self.power_combox.addItem("退出软件", userData="KillSelf")
|
||||||
|
self.power_combox.addItem("睡眠", userData="Sleep")
|
||||||
|
self.power_combox.addItem("休眠", userData="Hibernate")
|
||||||
|
self.power_combox.addItem("关机", userData="Shutdown")
|
||||||
|
self.power_combox.setCurrentText("无动作")
|
||||||
|
self.power_combox.currentIndexChanged.connect(self.set_power_sign)
|
||||||
|
|
||||||
|
self.pivotArea = PivotArea(self)
|
||||||
|
self.pivot = self.pivotArea.pivot
|
||||||
|
|
||||||
self.stackedWidget = QStackedWidget(self)
|
self.stackedWidget = QStackedWidget(self)
|
||||||
self.Layout = QVBoxLayout(self)
|
self.stackedWidget.setContentsMargins(0, 0, 0, 0)
|
||||||
|
self.stackedWidget.setStyleSheet("background: transparent; border: none;")
|
||||||
|
|
||||||
self.script_list: Dict[str, DispatchBox] = {}
|
self.script_list: Dict[str, DispatchCenter.DispatchBox] = {}
|
||||||
|
|
||||||
dispatch_box = DispatchBox("主调度台", self)
|
dispatch_box = self.DispatchBox("主调度台", self)
|
||||||
self.script_list["主调度台"] = dispatch_box
|
self.script_list["主调度台"] = dispatch_box
|
||||||
self.stackedWidget.addWidget(self.script_list["主调度台"])
|
self.stackedWidget.addWidget(self.script_list["主调度台"])
|
||||||
self.pivot.addItem(
|
self.pivot.addItem(
|
||||||
@@ -76,7 +91,15 @@ class DispatchCenter(QWidget):
|
|||||||
icon=FluentIcon.CAFE,
|
icon=FluentIcon.CAFE,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.Layout.addWidget(self.pivot, 0, Qt.AlignHCenter)
|
h_layout = QHBoxLayout()
|
||||||
|
h_layout.addWidget(self.multi_button)
|
||||||
|
h_layout.addWidget(self.pivotArea)
|
||||||
|
h_layout.addWidget(BodyLabel("全部完成后", self))
|
||||||
|
h_layout.addWidget(self.power_combox)
|
||||||
|
h_layout.setContentsMargins(11, 5, 11, 0)
|
||||||
|
|
||||||
|
self.Layout = QVBoxLayout(self)
|
||||||
|
self.Layout.addLayout(h_layout)
|
||||||
self.Layout.addWidget(self.stackedWidget)
|
self.Layout.addWidget(self.stackedWidget)
|
||||||
self.Layout.setContentsMargins(0, 0, 0, 0)
|
self.Layout.setContentsMargins(0, 0, 0, 0)
|
||||||
|
|
||||||
@@ -87,7 +110,7 @@ class DispatchCenter(QWidget):
|
|||||||
def add_board(self, task: Task) -> None:
|
def add_board(self, task: Task) -> None:
|
||||||
"""添加一个调度台界面"""
|
"""添加一个调度台界面"""
|
||||||
|
|
||||||
dispatch_box = DispatchBox(task.name, self)
|
dispatch_box = self.DispatchBox(task.name, self)
|
||||||
|
|
||||||
dispatch_box.top_bar.main_button.clicked.connect(
|
dispatch_box.top_bar.main_button.clicked.connect(
|
||||||
lambda: TaskManager.stop_task(task.name)
|
lambda: TaskManager.stop_task(task.name)
|
||||||
@@ -123,7 +146,6 @@ class DispatchCenter(QWidget):
|
|||||||
self.script_list["主调度台"].top_bar.Lable.show()
|
self.script_list["主调度台"].top_bar.Lable.show()
|
||||||
self.script_list["主调度台"].top_bar.object.hide()
|
self.script_list["主调度台"].top_bar.object.hide()
|
||||||
self.script_list["主调度台"].top_bar.mode.hide()
|
self.script_list["主调度台"].top_bar.mode.hide()
|
||||||
self.script_list["主调度台"].top_bar.multi_button.show()
|
|
||||||
self.script_list["主调度台"].top_bar.main_button.clicked.disconnect()
|
self.script_list["主调度台"].top_bar.main_button.clicked.disconnect()
|
||||||
self.script_list["主调度台"].top_bar.main_button.setText("中止任务")
|
self.script_list["主调度台"].top_bar.main_button.setText("中止任务")
|
||||||
self.script_list["主调度台"].top_bar.main_button.clicked.connect(
|
self.script_list["主调度台"].top_bar.main_button.clicked.connect(
|
||||||
@@ -154,7 +176,6 @@ class DispatchCenter(QWidget):
|
|||||||
self.script_list["主调度台"].top_bar.Lable.hide()
|
self.script_list["主调度台"].top_bar.Lable.hide()
|
||||||
self.script_list["主调度台"].top_bar.object.show()
|
self.script_list["主调度台"].top_bar.object.show()
|
||||||
self.script_list["主调度台"].top_bar.mode.show()
|
self.script_list["主调度台"].top_bar.mode.show()
|
||||||
self.script_list["主调度台"].top_bar.multi_button.hide()
|
|
||||||
self.script_list["主调度台"].top_bar.main_button.clicked.disconnect()
|
self.script_list["主调度台"].top_bar.main_button.clicked.disconnect()
|
||||||
self.script_list["主调度台"].top_bar.main_button.setText("开始任务")
|
self.script_list["主调度台"].top_bar.main_button.setText("开始任务")
|
||||||
self.script_list["主调度台"].top_bar.main_button.clicked.connect(
|
self.script_list["主调度台"].top_bar.main_button.clicked.connect(
|
||||||
@@ -208,301 +229,323 @@ class DispatchCenter(QWidget):
|
|||||||
self.script_list["主调度台"].top_bar.mode.addItems(["自动代理", "人工排查"])
|
self.script_list["主调度台"].top_bar.mode.addItems(["自动代理", "人工排查"])
|
||||||
self.script_list["主调度台"].top_bar.mode.setCurrentIndex(0)
|
self.script_list["主调度台"].top_bar.mode.setCurrentIndex(0)
|
||||||
|
|
||||||
|
def update_power_sign(self) -> None:
|
||||||
|
"""更新电源设置"""
|
||||||
|
|
||||||
class DispatchBox(QWidget):
|
mode_book = {
|
||||||
|
"NoAction": "无动作",
|
||||||
|
"KillSelf": "退出软件",
|
||||||
|
"Sleep": "睡眠",
|
||||||
|
"Hibernate": "休眠",
|
||||||
|
"Shutdown": "关机",
|
||||||
|
}
|
||||||
|
self.power_combox.currentIndexChanged.disconnect()
|
||||||
|
self.power_combox.setCurrentText(mode_book[Config.power_sign])
|
||||||
|
self.power_combox.currentIndexChanged.connect(self.set_power_sign)
|
||||||
|
|
||||||
def __init__(self, name: str, parent=None):
|
def set_power_sign(self) -> None:
|
||||||
super().__init__(parent)
|
"""设置所有任务完成后动作"""
|
||||||
|
|
||||||
self.setObjectName(name)
|
if not Config.running_list:
|
||||||
|
|
||||||
layout = QVBoxLayout()
|
self.power_combox.currentIndexChanged.disconnect()
|
||||||
|
self.power_combox.setCurrentText("无动作")
|
||||||
scrollArea = ScrollArea()
|
self.power_combox.currentIndexChanged.connect(self.set_power_sign)
|
||||||
scrollArea.setWidgetResizable(True)
|
logger.warning("没有正在运行的任务,无法设置任务完成后动作")
|
||||||
|
MainInfoBar.push_info_bar(
|
||||||
content_widget = QWidget()
|
"warning",
|
||||||
content_layout = QVBoxLayout(content_widget)
|
"没有正在运行的任务",
|
||||||
|
"无法设置任务完成后动作",
|
||||||
self.top_bar = self.DispatchTopBar(self, name)
|
5000,
|
||||||
self.info = self.DispatchInfoCard(self)
|
|
||||||
|
|
||||||
content_layout.addWidget(self.top_bar)
|
|
||||||
content_layout.addWidget(self.info)
|
|
||||||
|
|
||||||
scrollArea.setWidget(content_widget)
|
|
||||||
|
|
||||||
layout.addWidget(scrollArea)
|
|
||||||
|
|
||||||
self.setLayout(layout)
|
|
||||||
|
|
||||||
class DispatchTopBar(CardWidget):
|
|
||||||
|
|
||||||
def __init__(self, parent=None, name: str = None):
|
|
||||||
super().__init__(parent)
|
|
||||||
|
|
||||||
Layout = QHBoxLayout(self)
|
|
||||||
|
|
||||||
if name == "主调度台":
|
|
||||||
|
|
||||||
self.Lable = SubtitleLabel("", self)
|
|
||||||
self.Lable.hide()
|
|
||||||
self.object = ComboBox()
|
|
||||||
self.object.setPlaceholderText("请选择调度对象")
|
|
||||||
self.mode = ComboBox()
|
|
||||||
self.mode.setPlaceholderText("请选择调度模式")
|
|
||||||
|
|
||||||
self.multi_button = PushButton("添加任务")
|
|
||||||
self.multi_button.clicked.connect(self.start_multi_task)
|
|
||||||
self.main_button = PushButton("开始任务")
|
|
||||||
self.main_button.clicked.connect(self.start_main_task)
|
|
||||||
self.multi_button.hide()
|
|
||||||
|
|
||||||
Layout.addWidget(self.Lable)
|
|
||||||
Layout.addWidget(self.object)
|
|
||||||
Layout.addWidget(self.mode)
|
|
||||||
Layout.addStretch(1)
|
|
||||||
Layout.addWidget(self.multi_button)
|
|
||||||
Layout.addWidget(self.main_button)
|
|
||||||
|
|
||||||
else:
|
|
||||||
|
|
||||||
self.Lable = SubtitleLabel(name, self)
|
|
||||||
self.main_button = PushButton("中止任务")
|
|
||||||
|
|
||||||
Layout.addWidget(self.Lable)
|
|
||||||
Layout.addStretch(1)
|
|
||||||
Layout.addWidget(self.main_button)
|
|
||||||
|
|
||||||
def start_main_task(self):
|
|
||||||
"""开始任务"""
|
|
||||||
|
|
||||||
if self.object.currentIndex() == -1:
|
|
||||||
logger.warning("未选择调度对象")
|
|
||||||
MainInfoBar.push_info_bar(
|
|
||||||
"warning", "未选择调度对象", "请选择后再开始任务", 5000
|
|
||||||
)
|
|
||||||
return None
|
|
||||||
|
|
||||||
if self.mode.currentIndex() == -1:
|
|
||||||
logger.warning("未选择调度模式")
|
|
||||||
MainInfoBar.push_info_bar(
|
|
||||||
"warning", "未选择调度模式", "请选择后再开始任务", 5000
|
|
||||||
)
|
|
||||||
return None
|
|
||||||
|
|
||||||
if self.object.currentData() in Config.running_list:
|
|
||||||
logger.warning(f"任务已存在:{self.object.currentData()}")
|
|
||||||
MainInfoBar.push_info_bar(
|
|
||||||
"warning", "任务已存在", self.object.currentData(), 5000
|
|
||||||
)
|
|
||||||
return None
|
|
||||||
|
|
||||||
if "调度队列" in self.object.currentData():
|
|
||||||
|
|
||||||
logger.info(f"用户添加任务:{self.object.currentData()}")
|
|
||||||
TaskManager.add_task(
|
|
||||||
f"{self.mode.currentText()}_主调度台",
|
|
||||||
self.object.currentData(),
|
|
||||||
Config.queue_dict[self.object.currentData()]["Config"].toDict(),
|
|
||||||
)
|
|
||||||
|
|
||||||
elif "脚本" in self.object.currentData():
|
|
||||||
|
|
||||||
if Config.member_dict[self.object.currentData()]["Type"] == "Maa":
|
|
||||||
|
|
||||||
logger.info(f"用户添加任务:{self.object.currentData()}")
|
|
||||||
TaskManager.add_task(
|
|
||||||
f"{self.mode.currentText()}_主调度台",
|
|
||||||
"自定义队列",
|
|
||||||
{"Queue": {"Member_1": self.object.currentData()}},
|
|
||||||
)
|
|
||||||
|
|
||||||
def start_multi_task(self):
|
|
||||||
"""开始任务"""
|
|
||||||
|
|
||||||
# 获取所有可用的队列和实例
|
|
||||||
text_list = []
|
|
||||||
data_list = []
|
|
||||||
for name, info in Config.queue_dict.items():
|
|
||||||
if name in Config.running_list:
|
|
||||||
continue
|
|
||||||
text_list.append(
|
|
||||||
"队列"
|
|
||||||
if info["Config"].get(info["Config"].queueSet_Name) == ""
|
|
||||||
else f"队列 - {info["Config"].get(info["Config"].queueSet_Name)}"
|
|
||||||
)
|
|
||||||
data_list.append(name)
|
|
||||||
|
|
||||||
for name, info in Config.member_dict.items():
|
|
||||||
if name in Config.running_list:
|
|
||||||
continue
|
|
||||||
text_list.append(
|
|
||||||
f"实例 - {info['Type']}"
|
|
||||||
if info["Config"].get(info["Config"].MaaSet_Name) == ""
|
|
||||||
else f"实例 - {info['Type']} - {info["Config"].get(info["Config"].MaaSet_Name)}"
|
|
||||||
)
|
|
||||||
data_list.append(name)
|
|
||||||
|
|
||||||
choice = ComboBoxMessageBox(
|
|
||||||
self.window(),
|
|
||||||
"选择一个对象以添加相应多开任务",
|
|
||||||
["选择调度对象"],
|
|
||||||
[text_list],
|
|
||||||
[data_list],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if choice.exec() and choice.input[0].currentIndex() != -1:
|
else:
|
||||||
|
|
||||||
if choice.input[0].currentData() in Config.running_list:
|
Config.set_power_sign(self.power_combox.currentData())
|
||||||
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():
|
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()}")
|
logger.info(f"用户添加任务:{choice.input[0].currentData()}")
|
||||||
TaskManager.add_task(
|
TaskManager.add_task(
|
||||||
"自动代理_新调度台",
|
"自动代理_新调度台",
|
||||||
choice.input[0].currentData(),
|
f"自定义队列 - {choice.input[0].currentData()}",
|
||||||
Config.queue_dict[choice.input[0].currentData()][
|
{"Queue": {"Member_1": choice.input[0].currentData()}},
|
||||||
"Config"
|
|
||||||
].toDict(),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
elif "脚本" in choice.input[0].currentData():
|
class DispatchBox(QWidget):
|
||||||
|
|
||||||
if (
|
def __init__(self, name: str, parent=None):
|
||||||
Config.member_dict[choice.input[0].currentData()]["Type"]
|
|
||||||
== "Maa"
|
|
||||||
):
|
|
||||||
|
|
||||||
logger.info(f"用户添加任务:{choice.input[0].currentData()}")
|
|
||||||
TaskManager.add_task(
|
|
||||||
"自动代理_新调度台",
|
|
||||||
f"自定义队列 - {choice.input[0].currentData()}",
|
|
||||||
{"Queue": {"Member_1": choice.input[0].currentData()}},
|
|
||||||
)
|
|
||||||
|
|
||||||
class DispatchInfoCard(HeaderCardWidget):
|
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
|
|
||||||
self.setTitle("调度信息")
|
self.setObjectName(name)
|
||||||
|
|
||||||
self.task = self.TaskInfoCard(self)
|
self.top_bar = self.DispatchTopBar(self, name)
|
||||||
self.user = self.UserInfoCard(self)
|
self.info = self.DispatchInfoCard(self)
|
||||||
self.log_text = self.LogCard(self)
|
|
||||||
|
|
||||||
self.viewLayout.addWidget(self.task)
|
content_widget = QWidget()
|
||||||
self.viewLayout.addWidget(self.user)
|
content_layout = QVBoxLayout(content_widget)
|
||||||
self.viewLayout.addWidget(self.log_text)
|
content_layout.setContentsMargins(0, 0, 0, 0)
|
||||||
|
content_layout.addWidget(self.top_bar)
|
||||||
|
content_layout.addWidget(self.info)
|
||||||
|
|
||||||
self.viewLayout.setStretch(0, 1)
|
scrollArea = ScrollArea()
|
||||||
self.viewLayout.setStretch(1, 1)
|
scrollArea.setWidgetResizable(True)
|
||||||
self.viewLayout.setStretch(2, 5)
|
scrollArea.setContentsMargins(0, 0, 0, 0)
|
||||||
|
scrollArea.setStyleSheet("background: transparent; border: none;")
|
||||||
|
scrollArea.setWidget(content_widget)
|
||||||
|
|
||||||
def update_board(self, task_list: list, user_list: list, log: str):
|
layout = QVBoxLayout(self)
|
||||||
"""更新调度信息"""
|
layout.addWidget(scrollArea)
|
||||||
|
|
||||||
self.task.update_task(task_list)
|
class DispatchTopBar(CardWidget):
|
||||||
self.user.update_user(user_list)
|
|
||||||
self.log_text.text.setText(log)
|
|
||||||
|
|
||||||
class TaskInfoCard(HeaderCardWidget):
|
def __init__(self, parent=None, name: str = None):
|
||||||
|
super().__init__(parent)
|
||||||
|
|
||||||
|
Layout = QHBoxLayout(self)
|
||||||
|
|
||||||
|
if name == "主调度台":
|
||||||
|
|
||||||
|
self.Lable = SubtitleLabel("", self)
|
||||||
|
self.Lable.hide()
|
||||||
|
self.object = ComboBox()
|
||||||
|
self.object.setPlaceholderText("请选择调度对象")
|
||||||
|
self.mode = ComboBox()
|
||||||
|
self.mode.setPlaceholderText("请选择调度模式")
|
||||||
|
|
||||||
|
self.main_button = PushButton("开始任务")
|
||||||
|
self.main_button.clicked.connect(self.start_main_task)
|
||||||
|
|
||||||
|
Layout.addWidget(self.Lable)
|
||||||
|
Layout.addWidget(self.object)
|
||||||
|
Layout.addWidget(self.mode)
|
||||||
|
Layout.addStretch(1)
|
||||||
|
Layout.addWidget(self.main_button)
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
self.Lable = SubtitleLabel(name, self)
|
||||||
|
self.main_button = PushButton("中止任务")
|
||||||
|
|
||||||
|
Layout.addWidget(self.Lable)
|
||||||
|
Layout.addStretch(1)
|
||||||
|
Layout.addWidget(self.main_button)
|
||||||
|
|
||||||
|
def start_main_task(self):
|
||||||
|
"""开始任务"""
|
||||||
|
|
||||||
|
if self.object.currentIndex() == -1:
|
||||||
|
logger.warning("未选择调度对象")
|
||||||
|
MainInfoBar.push_info_bar(
|
||||||
|
"warning", "未选择调度对象", "请选择后再开始任务", 5000
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
|
if self.mode.currentIndex() == -1:
|
||||||
|
logger.warning("未选择调度模式")
|
||||||
|
MainInfoBar.push_info_bar(
|
||||||
|
"warning", "未选择调度模式", "请选择后再开始任务", 5000
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
|
if self.object.currentData() in Config.running_list:
|
||||||
|
logger.warning(f"任务已存在:{self.object.currentData()}")
|
||||||
|
MainInfoBar.push_info_bar(
|
||||||
|
"warning", "任务已存在", self.object.currentData(), 5000
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
|
if "调度队列" in self.object.currentData():
|
||||||
|
|
||||||
|
logger.info(f"用户添加任务:{self.object.currentData()}")
|
||||||
|
TaskManager.add_task(
|
||||||
|
f"{self.mode.currentText()}_主调度台",
|
||||||
|
self.object.currentData(),
|
||||||
|
Config.queue_dict[self.object.currentData()]["Config"].toDict(),
|
||||||
|
)
|
||||||
|
|
||||||
|
elif "脚本" in self.object.currentData():
|
||||||
|
|
||||||
|
if Config.member_dict[self.object.currentData()]["Type"] == "Maa":
|
||||||
|
|
||||||
|
logger.info(f"用户添加任务:{self.object.currentData()}")
|
||||||
|
TaskManager.add_task(
|
||||||
|
f"{self.mode.currentText()}_主调度台",
|
||||||
|
"自定义队列",
|
||||||
|
{"Queue": {"Member_1": self.object.currentData()}},
|
||||||
|
)
|
||||||
|
|
||||||
|
class DispatchInfoCard(HeaderCardWidget):
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.setTitle("任务队列")
|
|
||||||
|
|
||||||
self.Layout = QVBoxLayout()
|
self.setTitle("调度信息")
|
||||||
self.viewLayout.addLayout(self.Layout)
|
|
||||||
self.viewLayout.setContentsMargins(3, 0, 3, 3)
|
|
||||||
|
|
||||||
self.task_cards: List[StatefulItemCard] = []
|
self.task = self.TaskInfoCard(self)
|
||||||
|
self.user = self.UserInfoCard(self)
|
||||||
|
self.log_text = self.LogCard(self)
|
||||||
|
|
||||||
def create_task(self, task_list: list):
|
self.viewLayout.addWidget(self.task)
|
||||||
"""创建任务队列"""
|
self.viewLayout.addWidget(self.user)
|
||||||
|
self.viewLayout.addWidget(self.log_text)
|
||||||
|
|
||||||
while self.Layout.count() > 0:
|
self.viewLayout.setStretch(0, 1)
|
||||||
item = self.Layout.takeAt(0)
|
self.viewLayout.setStretch(1, 1)
|
||||||
if item.spacerItem():
|
self.viewLayout.setStretch(2, 5)
|
||||||
self.Layout.removeItem(item.spacerItem())
|
|
||||||
elif item.widget():
|
|
||||||
item.widget().deleteLater()
|
|
||||||
|
|
||||||
self.task_cards = []
|
def update_board(self, task_list: list, user_list: list, log: str):
|
||||||
|
"""更新调度信息"""
|
||||||
|
|
||||||
for task in task_list:
|
self.task.update_task(task_list)
|
||||||
|
self.user.update_user(user_list)
|
||||||
|
self.log_text.text.setText(log)
|
||||||
|
|
||||||
self.task_cards.append(StatefulItemCard(task))
|
class TaskInfoCard(HeaderCardWidget):
|
||||||
self.Layout.addWidget(self.task_cards[-1])
|
|
||||||
|
|
||||||
self.Layout.addStretch(1)
|
def __init__(self, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.setTitle("任务队列")
|
||||||
|
|
||||||
def update_task(self, task_list: list):
|
self.Layout = QVBoxLayout()
|
||||||
"""更新任务队列"""
|
self.viewLayout.addLayout(self.Layout)
|
||||||
|
self.viewLayout.setContentsMargins(3, 0, 3, 3)
|
||||||
|
|
||||||
for i in range(len(task_list)):
|
self.task_cards: List[StatefulItemCard] = []
|
||||||
|
|
||||||
self.task_cards[i].update_status(task_list[i][1])
|
def create_task(self, task_list: list):
|
||||||
|
"""创建任务队列"""
|
||||||
|
|
||||||
class UserInfoCard(HeaderCardWidget):
|
while self.Layout.count() > 0:
|
||||||
|
item = self.Layout.takeAt(0)
|
||||||
|
if item.spacerItem():
|
||||||
|
self.Layout.removeItem(item.spacerItem())
|
||||||
|
elif item.widget():
|
||||||
|
item.widget().deleteLater()
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
self.task_cards = []
|
||||||
super().__init__(parent)
|
|
||||||
self.setTitle("用户队列")
|
|
||||||
|
|
||||||
self.Layout = QVBoxLayout()
|
for task in task_list:
|
||||||
self.viewLayout.addLayout(self.Layout)
|
|
||||||
self.viewLayout.setContentsMargins(3, 0, 3, 3)
|
|
||||||
|
|
||||||
self.user_cards: List[StatefulItemCard] = []
|
self.task_cards.append(StatefulItemCard(task))
|
||||||
|
self.Layout.addWidget(self.task_cards[-1])
|
||||||
|
|
||||||
def create_user(self, user_list: list):
|
self.Layout.addStretch(1)
|
||||||
"""创建用户队列"""
|
|
||||||
|
|
||||||
while self.Layout.count() > 0:
|
def update_task(self, task_list: list):
|
||||||
item = self.Layout.takeAt(0)
|
"""更新任务队列"""
|
||||||
if item.spacerItem():
|
|
||||||
self.Layout.removeItem(item.spacerItem())
|
|
||||||
elif item.widget():
|
|
||||||
item.widget().deleteLater()
|
|
||||||
|
|
||||||
self.user_cards = []
|
for i in range(len(task_list)):
|
||||||
|
|
||||||
for user in user_list:
|
self.task_cards[i].update_status(task_list[i][1])
|
||||||
|
|
||||||
self.user_cards.append(StatefulItemCard(user))
|
class UserInfoCard(HeaderCardWidget):
|
||||||
self.Layout.addWidget(self.user_cards[-1])
|
|
||||||
|
|
||||||
self.Layout.addStretch(1)
|
def __init__(self, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.setTitle("用户队列")
|
||||||
|
|
||||||
def update_user(self, user_list: list):
|
self.Layout = QVBoxLayout()
|
||||||
"""更新用户队列"""
|
self.viewLayout.addLayout(self.Layout)
|
||||||
|
self.viewLayout.setContentsMargins(3, 0, 3, 3)
|
||||||
|
|
||||||
for i in range(len(user_list)):
|
self.user_cards: List[StatefulItemCard] = []
|
||||||
|
|
||||||
self.user_cards[i].Label.setText(user_list[i][0])
|
def create_user(self, user_list: list):
|
||||||
self.user_cards[i].update_status(user_list[i][1])
|
"""创建用户队列"""
|
||||||
|
|
||||||
class LogCard(HeaderCardWidget):
|
while self.Layout.count() > 0:
|
||||||
|
item = self.Layout.takeAt(0)
|
||||||
|
if item.spacerItem():
|
||||||
|
self.Layout.removeItem(item.spacerItem())
|
||||||
|
elif item.widget():
|
||||||
|
item.widget().deleteLater()
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
self.user_cards = []
|
||||||
super().__init__(parent)
|
|
||||||
self.setTitle("日志")
|
|
||||||
|
|
||||||
self.text = TextBrowser()
|
for user in user_list:
|
||||||
self.viewLayout.setContentsMargins(3, 0, 3, 3)
|
|
||||||
self.viewLayout.addWidget(self.text)
|
|
||||||
|
|
||||||
self.text.textChanged.connect(self.to_end)
|
self.user_cards.append(StatefulItemCard(user))
|
||||||
|
self.Layout.addWidget(self.user_cards[-1])
|
||||||
|
|
||||||
def to_end(self):
|
self.Layout.addStretch(1)
|
||||||
"""滚动到底部"""
|
|
||||||
|
|
||||||
self.text.moveCursor(QTextCursor.End)
|
def update_user(self, user_list: list):
|
||||||
self.text.ensureCursorVisible()
|
"""更新用户队列"""
|
||||||
|
|
||||||
|
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()
|
||||||
|
|||||||
@@ -21,24 +21,20 @@
|
|||||||
"""
|
"""
|
||||||
AUTO_MAA
|
AUTO_MAA
|
||||||
AUTO_MAA更新器
|
AUTO_MAA更新器
|
||||||
v1.2
|
v4.3
|
||||||
作者:DLmaster_361
|
作者:DLmaster_361
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sys
|
from loguru import logger
|
||||||
import json
|
|
||||||
import zipfile
|
import zipfile
|
||||||
import requests
|
import requests
|
||||||
import subprocess
|
import subprocess
|
||||||
import time
|
import time
|
||||||
import psutil
|
import psutil
|
||||||
import win32crypt
|
|
||||||
import base64
|
|
||||||
from packaging import version
|
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from PySide6.QtWidgets import QApplication, QDialog, QVBoxLayout
|
from PySide6.QtWidgets import QDialog, QVBoxLayout
|
||||||
from qfluentwidgets import (
|
from qfluentwidgets import (
|
||||||
ProgressBar,
|
ProgressBar,
|
||||||
IndeterminateProgressBar,
|
IndeterminateProgressBar,
|
||||||
@@ -46,7 +42,7 @@ from qfluentwidgets import (
|
|||||||
setTheme,
|
setTheme,
|
||||||
Theme,
|
Theme,
|
||||||
)
|
)
|
||||||
from PySide6.QtGui import QIcon, QCloseEvent
|
from PySide6.QtGui import QCloseEvent
|
||||||
from PySide6.QtCore import QThread, Signal, QTimer, QEventLoop
|
from PySide6.QtCore import QThread, Signal, QTimer, QEventLoop
|
||||||
|
|
||||||
from typing import List, Dict, Union
|
from typing import List, Dict, Union
|
||||||
@@ -83,12 +79,15 @@ class DownloadProcess(QThread):
|
|||||||
) -> None:
|
) -> None:
|
||||||
super(DownloadProcess, self).__init__()
|
super(DownloadProcess, self).__init__()
|
||||||
|
|
||||||
|
self.setObjectName(f"DownloadProcess-{url}-{start_byte}-{end_byte}")
|
||||||
|
|
||||||
self.url = url
|
self.url = url
|
||||||
self.start_byte = start_byte
|
self.start_byte = start_byte
|
||||||
self.end_byte = end_byte
|
self.end_byte = end_byte
|
||||||
self.download_path = download_path
|
self.download_path = download_path
|
||||||
self.check_times = check_times
|
self.check_times = check_times
|
||||||
|
|
||||||
|
@logger.catch
|
||||||
def run(self) -> None:
|
def run(self) -> None:
|
||||||
|
|
||||||
# 清理可能存在的临时文件
|
# 清理可能存在的临时文件
|
||||||
@@ -162,10 +161,13 @@ class ZipExtractProcess(QThread):
|
|||||||
def __init__(self, name: str, app_path: Path, download_path: Path) -> None:
|
def __init__(self, name: str, app_path: Path, download_path: Path) -> None:
|
||||||
super(ZipExtractProcess, self).__init__()
|
super(ZipExtractProcess, self).__init__()
|
||||||
|
|
||||||
|
self.setObjectName(f"ZipExtractProcess-{name}")
|
||||||
|
|
||||||
self.name = name
|
self.name = name
|
||||||
self.app_path = app_path
|
self.app_path = app_path
|
||||||
self.download_path = download_path
|
self.download_path = download_path
|
||||||
|
|
||||||
|
@logger.catch
|
||||||
def run(self) -> None:
|
def run(self) -> None:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -237,14 +239,10 @@ class DownloadManager(QDialog):
|
|||||||
self.version = version
|
self.version = version
|
||||||
self.config = config
|
self.config = config
|
||||||
self.download_path = app_path / "DOWNLOAD_TEMP.zip" # 临时下载文件的路径
|
self.download_path = app_path / "DOWNLOAD_TEMP.zip" # 临时下载文件的路径
|
||||||
self.version_path = app_path / "resources/version.json"
|
|
||||||
self.download_process_dict: Dict[str, DownloadProcess] = {}
|
self.download_process_dict: Dict[str, DownloadProcess] = {}
|
||||||
self.timer_dict: Dict[str, QTimer] = {}
|
self.timer_dict: Dict[str, QTimer] = {}
|
||||||
|
self.if_speed_test_accomplish = False
|
||||||
|
|
||||||
self.setWindowTitle("AUTO_MAA更新器")
|
|
||||||
self.setWindowIcon(
|
|
||||||
QIcon(str(app_path / "resources/icons/AUTO_MAA_Updater.ico"))
|
|
||||||
)
|
|
||||||
self.resize(700, 70)
|
self.resize(700, 70)
|
||||||
|
|
||||||
setTheme(Theme.AUTO, lazy=True)
|
setTheme(Theme.AUTO, lazy=True)
|
||||||
@@ -266,14 +264,14 @@ class DownloadManager(QDialog):
|
|||||||
|
|
||||||
def run(self) -> None:
|
def run(self) -> None:
|
||||||
|
|
||||||
if self.name == "MAA":
|
if self.name == "AUTO_MAA":
|
||||||
self.download_task1()
|
|
||||||
elif self.name == "AUTO_MAA":
|
|
||||||
if self.config["mode"] == "Proxy":
|
if self.config["mode"] == "Proxy":
|
||||||
self.test_speed_task1()
|
self.test_speed_task1()
|
||||||
self.speed_test_accomplish.connect(self.download_task1)
|
self.speed_test_accomplish.connect(self.download_task1)
|
||||||
elif self.config["mode"] == "MirrorChyan":
|
elif self.config["mode"] == "MirrorChyan":
|
||||||
self.download_task1()
|
self.download_task1()
|
||||||
|
elif self.config["mode"] == "MirrorChyan":
|
||||||
|
self.download_task1()
|
||||||
|
|
||||||
def get_download_url(self, mode: str) -> Union[str, Dict[str, str]]:
|
def get_download_url(self, mode: str) -> Union[str, Dict[str, str]]:
|
||||||
"""获取下载链接"""
|
"""获取下载链接"""
|
||||||
@@ -300,9 +298,6 @@ class DownloadManager(QDialog):
|
|||||||
|
|
||||||
elif mode == "下载":
|
elif mode == "下载":
|
||||||
|
|
||||||
if self.name == "MAA":
|
|
||||||
return f"https://jp-download.fearr.xyz/MAA/MAA-{version_text(self.version)}-win-x64.zip"
|
|
||||||
|
|
||||||
if self.name == "AUTO_MAA":
|
if self.name == "AUTO_MAA":
|
||||||
|
|
||||||
if self.config["mode"] == "Proxy":
|
if self.config["mode"] == "Proxy":
|
||||||
@@ -325,6 +320,7 @@ class DownloadManager(QDialog):
|
|||||||
return f"{selected_url}https://github.com/DLmaster361/AUTO_MAA/releases/download/{version_text(self.version)}/AUTO_MAA_{version_text(self.version)}.zip"
|
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":
|
elif self.config["mode"] == "MirrorChyan":
|
||||||
|
|
||||||
with requests.get(
|
with requests.get(
|
||||||
self.config["url"],
|
self.config["url"],
|
||||||
allow_redirects=True,
|
allow_redirects=True,
|
||||||
@@ -334,6 +330,14 @@ class DownloadManager(QDialog):
|
|||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
return response.url
|
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:
|
def test_speed_task1(self) -> None:
|
||||||
|
|
||||||
if self.isInterruptionRequested:
|
if self.isInterruptionRequested:
|
||||||
@@ -409,6 +413,15 @@ class DownloadManager(QDialog):
|
|||||||
if not self.download_process_dict:
|
if not self.download_process_dict:
|
||||||
self.download_process_clear.emit()
|
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()):
|
if any(speed == -1 for _, speed in self.test_speed_result.items()):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -533,41 +546,25 @@ class DownloadManager(QDialog):
|
|||||||
self.zip_extract.start()
|
self.zip_extract.start()
|
||||||
self.zip_loop.exec()
|
self.zip_loop.exec()
|
||||||
|
|
||||||
self.update_info("正在删除已弃用的文件")
|
|
||||||
if (self.app_path / "changes.json").exists():
|
|
||||||
|
|
||||||
with (self.app_path / "changes.json").open(mode="r", encoding="utf-8") as f:
|
|
||||||
info: Dict[str, List[str]] = json.load(f)
|
|
||||||
|
|
||||||
if "deleted" in info:
|
|
||||||
for file_path in info["deleted"]:
|
|
||||||
if (self.app_path / file_path).exists():
|
|
||||||
(self.app_path / file_path).unlink()
|
|
||||||
|
|
||||||
(self.app_path / "changes.json").unlink()
|
|
||||||
|
|
||||||
self.update_info("正在删除临时文件")
|
self.update_info("正在删除临时文件")
|
||||||
self.update_progress(0, 0, 0)
|
self.update_progress(0, 0, 0)
|
||||||
|
if (self.app_path / "changes.json").exists():
|
||||||
|
(self.app_path / "changes.json").unlink()
|
||||||
if self.download_path.exists():
|
if self.download_path.exists():
|
||||||
self.download_path.unlink()
|
self.download_path.unlink()
|
||||||
|
|
||||||
# 主程序更新完成后打开对应程序
|
# 下载完成后打开对应程序
|
||||||
if not self.isInterruptionRequested and self.name == "AUTO_MAA":
|
if not self.isInterruptionRequested and self.name == "MAA":
|
||||||
subprocess.Popen(
|
|
||||||
[self.app_path / "AUTO_MAA.exe"],
|
|
||||||
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP
|
|
||||||
| subprocess.DETACHED_PROCESS
|
|
||||||
| subprocess.CREATE_NO_WINDOW,
|
|
||||||
)
|
|
||||||
elif not self.isInterruptionRequested and self.name == "MAA":
|
|
||||||
subprocess.Popen(
|
subprocess.Popen(
|
||||||
[self.app_path / "MAA.exe"],
|
[self.app_path / "MAA.exe"],
|
||||||
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP
|
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP
|
||||||
| subprocess.DETACHED_PROCESS
|
| subprocess.DETACHED_PROCESS
|
||||||
| subprocess.CREATE_NO_WINDOW,
|
| subprocess.CREATE_NO_WINDOW,
|
||||||
)
|
)
|
||||||
|
if self.name == "AUTO_MAA":
|
||||||
self.update_info(f"{self.name}更新成功!")
|
self.update_info(f"即将安装{self.name}")
|
||||||
|
else:
|
||||||
|
self.update_info(f"{self.name}下载成功!")
|
||||||
self.update_progress(0, 100, 100)
|
self.update_progress(0, 100, 100)
|
||||||
self.download_accomplish.emit()
|
self.download_accomplish.emit()
|
||||||
|
|
||||||
@@ -609,151 +606,3 @@ class DownloadManager(QDialog):
|
|||||||
self.requestInterruption()
|
self.requestInterruption()
|
||||||
|
|
||||||
event.accept()
|
event.accept()
|
||||||
|
|
||||||
|
|
||||||
class AUTO_MAA_Downloader(QApplication):
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
app_path: Path,
|
|
||||||
name: str,
|
|
||||||
main_version: list,
|
|
||||||
config: dict,
|
|
||||||
) -> None:
|
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
self.main = DownloadManager(app_path, name, main_version, config)
|
|
||||||
self.main.show()
|
|
||||||
self.main.run()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
|
|
||||||
# 获取软件自身的路径
|
|
||||||
app_path = Path(sys.argv[0]).resolve().parent
|
|
||||||
|
|
||||||
# 从本地版本信息文件获取当前版本信息
|
|
||||||
if (app_path / "resources/version.json").exists():
|
|
||||||
with (app_path / "resources/version.json").open(
|
|
||||||
mode="r", encoding="utf-8"
|
|
||||||
) as f:
|
|
||||||
current_version_info = json.load(f)
|
|
||||||
current_version = list(
|
|
||||||
map(int, current_version_info["main_version"].split("."))
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
current_version = [0, 0, 0, 0]
|
|
||||||
|
|
||||||
# 从本地配置文件获取更新信息
|
|
||||||
if (app_path / "config/config.json").exists():
|
|
||||||
with (app_path / "config/config.json").open(mode="r", encoding="utf-8") as f:
|
|
||||||
config = json.load(f)
|
|
||||||
if "Update" in config:
|
|
||||||
|
|
||||||
if "UpdateType" in config["Update"]:
|
|
||||||
update_type = config["Update"]["UpdateType"]
|
|
||||||
else:
|
|
||||||
update_type = "stable"
|
|
||||||
if "ProxyUrlList" in config["Update"]:
|
|
||||||
proxy_list = config["Update"]["ProxyUrlList"]
|
|
||||||
else:
|
|
||||||
proxy_list = []
|
|
||||||
if "ThreadNumb" in config["Update"]:
|
|
||||||
thread_numb = config["Update"]["ThreadNumb"]
|
|
||||||
else:
|
|
||||||
thread_numb = 8
|
|
||||||
if "MirrorChyanCDK" in config["Update"]:
|
|
||||||
mirrorchyan_CDK = (
|
|
||||||
win32crypt.CryptUnprotectData(
|
|
||||||
base64.b64decode(config["Update"]["MirrorChyanCDK"]),
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
0,
|
|
||||||
)[1].decode("utf-8")
|
|
||||||
if config["Update"]["MirrorChyanCDK"]
|
|
||||||
else ""
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
mirrorchyan_CDK = ""
|
|
||||||
|
|
||||||
else:
|
|
||||||
update_type = "stable"
|
|
||||||
proxy_list = []
|
|
||||||
thread_numb = 8
|
|
||||||
mirrorchyan_CDK = ""
|
|
||||||
else:
|
|
||||||
update_type = "stable"
|
|
||||||
proxy_list = []
|
|
||||||
thread_numb = 8
|
|
||||||
mirrorchyan_CDK = ""
|
|
||||||
|
|
||||||
# 从远程服务器获取最新版本信息
|
|
||||||
for _ in range(3):
|
|
||||||
try:
|
|
||||||
response = requests.get(
|
|
||||||
f"https://mirrorchyan.com/api/resources/AUTO_MAA/latest?user_agent=AutoMaaDownloader¤t_version={version_text(current_version)}&cdk={mirrorchyan_CDK}&channel={update_type}",
|
|
||||||
timeout=10,
|
|
||||||
)
|
|
||||||
version_info: Dict[str, Union[int, str, Dict[str, str]]] = response.json()
|
|
||||||
break
|
|
||||||
except Exception as e:
|
|
||||||
err = e
|
|
||||||
time.sleep(0.1)
|
|
||||||
else:
|
|
||||||
sys.exit(f"获取版本信息时出错:\n{err}")
|
|
||||||
|
|
||||||
if version_info["code"] == 0:
|
|
||||||
|
|
||||||
if "url" in version_info["data"]:
|
|
||||||
download_config = {
|
|
||||||
"mode": "MirrorChyan",
|
|
||||||
"thread_numb": 1,
|
|
||||||
"url": version_info["data"]["url"],
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
|
|
||||||
download_config = {"mode": "Proxy", "thread_numb": thread_numb}
|
|
||||||
else:
|
|
||||||
sys.exit(f"获取版本信息时出错:{version_info["msg"]}")
|
|
||||||
|
|
||||||
remote_version = list(
|
|
||||||
map(
|
|
||||||
int,
|
|
||||||
version_info["data"]["version_name"][1:].replace("-beta", "").split("."),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if download_config["mode"] == "Proxy":
|
|
||||||
for _ in range(3):
|
|
||||||
try:
|
|
||||||
response = requests.get(
|
|
||||||
"https://gitee.com/DLmaster_361/AUTO_MAA/raw/server/download_info.json",
|
|
||||||
timeout=10,
|
|
||||||
)
|
|
||||||
download_info = response.json()
|
|
||||||
|
|
||||||
download_config["proxy_list"] = list(
|
|
||||||
set(proxy_list + download_info["proxy_list"])
|
|
||||||
)
|
|
||||||
download_config["download_dict"] = download_info["download_dict"]
|
|
||||||
break
|
|
||||||
except Exception as e:
|
|
||||||
err = e
|
|
||||||
time.sleep(0.1)
|
|
||||||
else:
|
|
||||||
sys.exit(f"获取代理信息时出错:{err}")
|
|
||||||
|
|
||||||
if (app_path / "changes.json").exists():
|
|
||||||
(app_path / "changes.json").unlink()
|
|
||||||
|
|
||||||
# 启动更新线程
|
|
||||||
if version.parse(version_text(remote_version)) > version.parse(
|
|
||||||
version_text(current_version)
|
|
||||||
):
|
|
||||||
app = AUTO_MAA_Downloader(
|
|
||||||
app_path,
|
|
||||||
"AUTO_MAA",
|
|
||||||
remote_version,
|
|
||||||
download_config,
|
|
||||||
)
|
|
||||||
sys.exit(app.exec())
|
|
||||||
@@ -51,7 +51,7 @@ from pathlib import Path
|
|||||||
from typing import Union, List, Dict
|
from typing import Union, List, Dict
|
||||||
|
|
||||||
|
|
||||||
from app.core import Config
|
from app.core import Config, SoundPlayer
|
||||||
from .Widget import StatefulItemCard, QuantifiedItemCard, QuickExpandGroupCard
|
from .Widget import StatefulItemCard, QuantifiedItemCard, QuickExpandGroupCard
|
||||||
|
|
||||||
|
|
||||||
@@ -61,25 +61,30 @@ class History(QWidget):
|
|||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.setObjectName("历史记录")
|
self.setObjectName("历史记录")
|
||||||
|
|
||||||
|
self.history_top_bar = self.HistoryTopBar(self)
|
||||||
|
self.history_top_bar.search_history.connect(self.reload_history)
|
||||||
|
|
||||||
content_widget = QWidget()
|
content_widget = QWidget()
|
||||||
self.content_layout = QVBoxLayout(content_widget)
|
self.content_layout = QVBoxLayout(content_widget)
|
||||||
self.history_top_bar = self.HistoryTopBar(self)
|
self.content_layout.setContentsMargins(0, 0, 11, 0)
|
||||||
|
|
||||||
self.history_top_bar.search_history.connect(self.reload_history)
|
|
||||||
|
|
||||||
scrollArea = ScrollArea()
|
scrollArea = ScrollArea()
|
||||||
scrollArea.setWidgetResizable(True)
|
scrollArea.setWidgetResizable(True)
|
||||||
|
scrollArea.setContentsMargins(0, 0, 0, 0)
|
||||||
|
scrollArea.setStyleSheet("background: transparent; border: none;")
|
||||||
scrollArea.setWidget(content_widget)
|
scrollArea.setWidget(content_widget)
|
||||||
layout = QVBoxLayout()
|
|
||||||
|
layout = QVBoxLayout(self)
|
||||||
layout.addWidget(self.history_top_bar)
|
layout.addWidget(self.history_top_bar)
|
||||||
layout.addWidget(scrollArea)
|
layout.addWidget(scrollArea)
|
||||||
self.setLayout(layout)
|
|
||||||
|
|
||||||
self.history_card_list = []
|
self.history_card_list = []
|
||||||
|
|
||||||
def reload_history(self, mode: str, start_date: QDate, end_date: QDate) -> None:
|
def reload_history(self, mode: str, start_date: QDate, end_date: QDate) -> None:
|
||||||
"""加载历史记录界面"""
|
"""加载历史记录界面"""
|
||||||
|
|
||||||
|
SoundPlayer.play("历史记录查询")
|
||||||
|
|
||||||
while self.content_layout.count() > 0:
|
while self.content_layout.count() > 0:
|
||||||
item = self.content_layout.takeAt(0)
|
item = self.content_layout.takeAt(0)
|
||||||
if item.spacerItem():
|
if item.spacerItem():
|
||||||
|
|||||||
@@ -62,14 +62,6 @@ class Home(QWidget):
|
|||||||
self.banner = Banner()
|
self.banner = Banner()
|
||||||
self.banner_text = TextBrowser()
|
self.banner_text = TextBrowser()
|
||||||
|
|
||||||
widget = QWidget()
|
|
||||||
Layout = QVBoxLayout(widget)
|
|
||||||
|
|
||||||
Layout.addWidget(self.banner)
|
|
||||||
Layout.addWidget(self.banner_text)
|
|
||||||
Layout.setStretch(0, 2)
|
|
||||||
Layout.setStretch(1, 3)
|
|
||||||
|
|
||||||
v_layout = QVBoxLayout(self.banner)
|
v_layout = QVBoxLayout(self.banner)
|
||||||
v_layout.setContentsMargins(0, 0, 0, 15)
|
v_layout.setContentsMargins(0, 0, 0, 15)
|
||||||
v_layout.setSpacing(5)
|
v_layout.setSpacing(5)
|
||||||
@@ -146,12 +138,22 @@ class Home(QWidget):
|
|||||||
# 将底部水平布局添加到垂直布局
|
# 将底部水平布局添加到垂直布局
|
||||||
v_layout.addLayout(h2_layout)
|
v_layout.addLayout(h2_layout)
|
||||||
|
|
||||||
layout = QVBoxLayout()
|
content_widget = QWidget()
|
||||||
|
content_layout = QVBoxLayout(content_widget)
|
||||||
|
content_layout.setContentsMargins(0, 0, 0, 0)
|
||||||
|
content_layout.addWidget(self.banner)
|
||||||
|
content_layout.addWidget(self.banner_text)
|
||||||
|
content_layout.setStretch(0, 2)
|
||||||
|
content_layout.setStretch(1, 3)
|
||||||
|
|
||||||
scrollArea = ScrollArea()
|
scrollArea = ScrollArea()
|
||||||
scrollArea.setWidgetResizable(True)
|
scrollArea.setWidgetResizable(True)
|
||||||
scrollArea.setWidget(widget)
|
scrollArea.setContentsMargins(0, 0, 0, 0)
|
||||||
|
scrollArea.setStyleSheet("background: transparent; border: none;")
|
||||||
|
scrollArea.setWidget(content_widget)
|
||||||
|
|
||||||
|
layout = QVBoxLayout(self)
|
||||||
layout.addWidget(scrollArea)
|
layout.addWidget(scrollArea)
|
||||||
self.setLayout(layout)
|
|
||||||
|
|
||||||
self.set_banner()
|
self.set_banner()
|
||||||
|
|
||||||
@@ -197,20 +199,22 @@ class Home(QWidget):
|
|||||||
elif Config.get(Config.function_HomeImageMode) == "主题图像":
|
elif Config.get(Config.function_HomeImageMode) == "主题图像":
|
||||||
|
|
||||||
# 从远程服务器获取最新主题图像
|
# 从远程服务器获取最新主题图像
|
||||||
Network.set_info(
|
network = Network.add_task(
|
||||||
mode="get",
|
mode="get",
|
||||||
url="https://gitee.com/DLmaster_361/AUTO_MAA/raw/server/theme_image.json",
|
url="https://gitee.com/DLmaster_361/AUTO_MAA/raw/server/theme_image.json",
|
||||||
)
|
)
|
||||||
Network.start()
|
network.loop.exec()
|
||||||
Network.loop.exec()
|
network_result = Network.get_result(network)
|
||||||
if Network.stutus_code == 200:
|
if network_result["status_code"] == 200:
|
||||||
theme_image = Network.response_json
|
theme_image = network_result["response_json"]
|
||||||
else:
|
else:
|
||||||
logger.warning(f"获取最新主题图像时出错:{Network.error_message}")
|
logger.warning(
|
||||||
|
f"获取最新主题图像时出错:{network_result['error_message']}"
|
||||||
|
)
|
||||||
MainInfoBar.push_info_bar(
|
MainInfoBar.push_info_bar(
|
||||||
"warning",
|
"warning",
|
||||||
"获取最新主题图像时出错",
|
"获取最新主题图像时出错",
|
||||||
f"网络错误:{Network.stutus_code}",
|
f"网络错误:{network_result['status_code']}",
|
||||||
5000,
|
5000,
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
@@ -234,15 +238,15 @@ class Home(QWidget):
|
|||||||
> time_local
|
> time_local
|
||||||
):
|
):
|
||||||
|
|
||||||
Network.set_info(
|
network = Network.add_task(
|
||||||
mode="get_file",
|
mode="get_file",
|
||||||
url=theme_image["url"],
|
url=theme_image["url"],
|
||||||
path=Config.app_path / "resources/images/Home/BannerTheme.jpg",
|
path=Config.app_path / "resources/images/Home/BannerTheme.jpg",
|
||||||
)
|
)
|
||||||
Network.start()
|
network.loop.exec()
|
||||||
Network.loop.exec()
|
network_result = Network.get_result(network)
|
||||||
|
|
||||||
if Network.stutus_code == 200:
|
if network_result["status_code"] == 200:
|
||||||
|
|
||||||
with (Config.app_path / "resources/theme_image.json").open(
|
with (Config.app_path / "resources/theme_image.json").open(
|
||||||
mode="w", encoding="utf-8"
|
mode="w", encoding="utf-8"
|
||||||
@@ -259,11 +263,13 @@ class Home(QWidget):
|
|||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
logger.warning(f"下载最新主题图像时出错:{Network.error_message}")
|
logger.warning(
|
||||||
|
f"下载最新主题图像时出错:{network_result['error_message']}"
|
||||||
|
)
|
||||||
MainInfoBar.push_info_bar(
|
MainInfoBar.push_info_bar(
|
||||||
"warning",
|
"warning",
|
||||||
"下载最新主题图像时出错",
|
"下载最新主题图像时出错",
|
||||||
f"网络错误:{Network.stutus_code}",
|
f"网络错误:{network_result['status_code']}",
|
||||||
5000,
|
5000,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -399,4 +405,6 @@ class ButtonGroup(SimpleCardWidget):
|
|||||||
|
|
||||||
def open_sales(self):
|
def open_sales(self):
|
||||||
"""打开 MirrorChyan 链接"""
|
"""打开 MirrorChyan 链接"""
|
||||||
QDesktopServices.openUrl(QUrl("https://mirrorchyan.com/"))
|
QDesktopServices.openUrl(
|
||||||
|
QUrl("https://mirrorchyan.com/zh/get-start?source=auto_maa-home")
|
||||||
|
)
|
||||||
|
|||||||
@@ -26,9 +26,8 @@ v4.3
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from PySide6.QtWidgets import QSystemTrayIcon
|
from PySide6.QtWidgets import QApplication, QSystemTrayIcon
|
||||||
from qfluentwidgets import (
|
from qfluentwidgets import (
|
||||||
qconfig,
|
|
||||||
Action,
|
Action,
|
||||||
SystemTrayMenu,
|
SystemTrayMenu,
|
||||||
SplashScreen,
|
SplashScreen,
|
||||||
@@ -46,10 +45,11 @@ from datetime import datetime, timedelta
|
|||||||
import shutil
|
import shutil
|
||||||
import darkdetect
|
import darkdetect
|
||||||
|
|
||||||
from app.core import Config, TaskManager, MainTimer, MainInfoBar
|
from app.core import Config, TaskManager, MainTimer, MainInfoBar, SoundPlayer
|
||||||
from app.services import Notify, Crypto, System
|
from app.services import Notify, Crypto, System
|
||||||
from .home import Home
|
from .home import Home
|
||||||
from .member_manager import MemberManager
|
from .member_manager import MemberManager
|
||||||
|
from .plan_manager import PlanManager
|
||||||
from .queue_manager import QueueManager
|
from .queue_manager import QueueManager
|
||||||
from .dispatch_center import DispatchCenter
|
from .dispatch_center import DispatchCenter
|
||||||
from .history import History
|
from .history import History
|
||||||
@@ -62,19 +62,26 @@ class AUTO_MAA(MSFluentWindow):
|
|||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
self.setWindowIcon(QIcon(str(Config.app_path / "resources/icons/AUTO_MAA.ico")))
|
self.setWindowIcon(QIcon(str(Config.app_path / "resources/icons/AUTO_MAA.ico")))
|
||||||
self.setWindowTitle("AUTO_MAA")
|
|
||||||
|
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.switch_theme()
|
||||||
|
|
||||||
self.splashScreen = SplashScreen(self.windowIcon(), self)
|
self.splashScreen = SplashScreen(self.windowIcon(), self)
|
||||||
self.show_ui("显示主窗口", if_quick=True)
|
self.show_ui("显示主窗口", if_quick=True)
|
||||||
|
|
||||||
TaskManager.main_window = self.window()
|
Config.main_window = self.window()
|
||||||
MainInfoBar.main_window = self.window()
|
|
||||||
System.main_window = self.window()
|
|
||||||
|
|
||||||
# 创建主窗口
|
# 创建主窗口
|
||||||
self.home = Home(self)
|
self.home = Home(self)
|
||||||
|
self.plan_manager = PlanManager(self)
|
||||||
self.member_manager = MemberManager(self)
|
self.member_manager = MemberManager(self)
|
||||||
self.queue_manager = QueueManager(self)
|
self.queue_manager = QueueManager(self)
|
||||||
self.dispatch_center = DispatchCenter(self)
|
self.dispatch_center = DispatchCenter(self)
|
||||||
@@ -95,6 +102,13 @@ class AUTO_MAA(MSFluentWindow):
|
|||||||
FluentIcon.ROBOT,
|
FluentIcon.ROBOT,
|
||||||
NavigationItemPosition.TOP,
|
NavigationItemPosition.TOP,
|
||||||
)
|
)
|
||||||
|
self.addSubInterface(
|
||||||
|
self.plan_manager,
|
||||||
|
FluentIcon.CALENDAR,
|
||||||
|
"计划管理",
|
||||||
|
FluentIcon.CALENDAR,
|
||||||
|
NavigationItemPosition.TOP,
|
||||||
|
)
|
||||||
self.addSubInterface(
|
self.addSubInterface(
|
||||||
self.queue_manager,
|
self.queue_manager,
|
||||||
FluentIcon.BOOK_SHELF,
|
FluentIcon.BOOK_SHELF,
|
||||||
@@ -123,23 +137,7 @@ class AUTO_MAA(MSFluentWindow):
|
|||||||
FluentIcon.SETTING,
|
FluentIcon.SETTING,
|
||||||
NavigationItemPosition.BOTTOM,
|
NavigationItemPosition.BOTTOM,
|
||||||
)
|
)
|
||||||
self.stackedWidget.currentChanged.connect(
|
self.stackedWidget.currentChanged.connect(self.__currentChanged)
|
||||||
lambda index: (
|
|
||||||
self.queue_manager.reload_member_name() if index == 2 else None
|
|
||||||
)
|
|
||||||
)
|
|
||||||
self.stackedWidget.currentChanged.connect(
|
|
||||||
lambda index: (
|
|
||||||
self.dispatch_center.pivot.setCurrentItem("主调度台")
|
|
||||||
if index == 3
|
|
||||||
else None
|
|
||||||
)
|
|
||||||
)
|
|
||||||
self.stackedWidget.currentChanged.connect(
|
|
||||||
lambda index: (
|
|
||||||
self.dispatch_center.update_top_bar() if index == 3 else None
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
# 创建系统托盘及其菜单
|
# 创建系统托盘及其菜单
|
||||||
self.tray = QSystemTrayIcon(
|
self.tray = QSystemTrayIcon(
|
||||||
@@ -173,14 +171,21 @@ class AUTO_MAA(MSFluentWindow):
|
|||||||
|
|
||||||
# 退出主程序菜单项
|
# 退出主程序菜单项
|
||||||
self.tray_menu.addAction(
|
self.tray_menu.addAction(
|
||||||
Action(FluentIcon.POWER_BUTTON, "退出主程序", triggered=self.window().close)
|
Action(
|
||||||
|
FluentIcon.POWER_BUTTON,
|
||||||
|
"退出主程序",
|
||||||
|
triggered=lambda: System.set_power("KillSelf"),
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
# 设置托盘菜单
|
# 设置托盘菜单
|
||||||
self.tray.setContextMenu(self.tray_menu)
|
self.tray.setContextMenu(self.tray_menu)
|
||||||
self.tray.activated.connect(self.on_tray_activated)
|
self.tray.activated.connect(self.on_tray_activated)
|
||||||
|
|
||||||
|
self.set_min_method()
|
||||||
|
|
||||||
Config.user_info_changed.connect(self.member_manager.refresh_dashboard)
|
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.create_gui.connect(self.dispatch_center.add_board)
|
||||||
TaskManager.connect_gui.connect(self.dispatch_center.connect_main_board)
|
TaskManager.connect_gui.connect(self.dispatch_center.connect_main_board)
|
||||||
Notify.push_info_bar.connect(MainInfoBar.push_info_bar)
|
Notify.push_info_bar.connect(MainInfoBar.push_info_bar)
|
||||||
@@ -229,43 +234,6 @@ class AUTO_MAA(MSFluentWindow):
|
|||||||
else:
|
else:
|
||||||
self.setStyleSheet("background-color: #ffffff;")
|
self.setStyleSheet("background-color: #ffffff;")
|
||||||
|
|
||||||
def start_up_task(self) -> None:
|
|
||||||
"""启动时任务"""
|
|
||||||
|
|
||||||
# 清理旧日志
|
|
||||||
self.clean_old_logs()
|
|
||||||
|
|
||||||
# 清理临时更新器
|
|
||||||
if (Config.app_path / "AUTO_Updater.active.exe").exists():
|
|
||||||
try:
|
|
||||||
(Config.app_path / "AUTO_Updater.active.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_show=False)
|
|
||||||
|
|
||||||
# 检查更新
|
|
||||||
if Config.get(Config.update_IfAutoUpdate):
|
|
||||||
self.setting.check_update()
|
|
||||||
|
|
||||||
# 直接最小化
|
|
||||||
if Config.get(Config.start_IfMinimizeDirectly):
|
|
||||||
|
|
||||||
self.titleBar.minBtn.click()
|
|
||||||
|
|
||||||
def set_min_method(self) -> None:
|
def set_min_method(self) -> None:
|
||||||
"""设置最小化方法"""
|
"""设置最小化方法"""
|
||||||
|
|
||||||
@@ -284,6 +252,171 @@ class AUTO_MAA(MSFluentWindow):
|
|||||||
if reason == QSystemTrayIcon.DoubleClick:
|
if reason == QSystemTrayIcon.DoubleClick:
|
||||||
self.show_ui("显示主窗口")
|
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):
|
def clean_old_logs(self):
|
||||||
"""
|
"""
|
||||||
删除超过用户设定天数的日志文件(基于目录日期)
|
删除超过用户设定天数的日志文件(基于目录日期)
|
||||||
@@ -339,64 +472,16 @@ class AUTO_MAA(MSFluentWindow):
|
|||||||
"warning", "启动主任务失败", "“调度队列_1”与“脚本_1”均不存在", -1
|
"warning", "启动主任务失败", "“调度队列_1”与“脚本_1”均不存在", -1
|
||||||
)
|
)
|
||||||
|
|
||||||
def show_ui(self, mode: str, if_quick: bool = False) -> None:
|
def __currentChanged(self, index: int) -> None:
|
||||||
"""配置窗口状态"""
|
"""切换界面时任务"""
|
||||||
|
|
||||||
self.switch_theme()
|
if index == 1:
|
||||||
|
self.member_manager.reload_plan_name()
|
||||||
if mode == "显示主窗口":
|
elif index == 3:
|
||||||
|
self.queue_manager.reload_member_name()
|
||||||
# 配置主窗口
|
elif index == 4:
|
||||||
size = list(
|
self.dispatch_center.pivot.setCurrentItem("主调度台")
|
||||||
map(
|
self.dispatch_center.update_top_bar()
|
||||||
int,
|
|
||||||
Config.get(Config.ui_size).split("x"),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
location = list(
|
|
||||||
map(
|
|
||||||
int,
|
|
||||||
Config.get(Config.ui_location).split("x"),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
self.window().setGeometry(location[0], location[1], size[0], size[1])
|
|
||||||
self.window().show()
|
|
||||||
self.window().raise_()
|
|
||||||
self.window().activateWindow()
|
|
||||||
if not if_quick:
|
|
||||||
if Config.get(Config.ui_maximized):
|
|
||||||
self.window().showMaximized()
|
|
||||||
self.set_min_method()
|
|
||||||
self.show_ui("配置托盘")
|
|
||||||
|
|
||||||
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 closeEvent(self, event: QCloseEvent):
|
def closeEvent(self, event: QCloseEvent):
|
||||||
"""清理残余进程"""
|
"""清理残余进程"""
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
497
app/ui/plan_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"],
|
||||||
|
)
|
||||||
@@ -34,17 +34,15 @@ from PySide6.QtWidgets import (
|
|||||||
)
|
)
|
||||||
from qfluentwidgets import (
|
from qfluentwidgets import (
|
||||||
Action,
|
Action,
|
||||||
Pivot,
|
|
||||||
ScrollArea,
|
ScrollArea,
|
||||||
FluentIcon,
|
FluentIcon,
|
||||||
MessageBox,
|
MessageBox,
|
||||||
HeaderCardWidget,
|
HeaderCardWidget,
|
||||||
CommandBar,
|
CommandBar,
|
||||||
)
|
)
|
||||||
from PySide6.QtCore import Qt
|
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from app.core import QueueConfig, Config, MainInfoBar
|
from app.core import QueueConfig, Config, MainInfoBar, SoundPlayer
|
||||||
from .Widget import (
|
from .Widget import (
|
||||||
SwitchSettingCard,
|
SwitchSettingCard,
|
||||||
ComboBoxSettingCard,
|
ComboBoxSettingCard,
|
||||||
@@ -52,6 +50,7 @@ from .Widget import (
|
|||||||
TimeEditSettingCard,
|
TimeEditSettingCard,
|
||||||
NoOptionComboBoxSettingCard,
|
NoOptionComboBoxSettingCard,
|
||||||
HistoryCard,
|
HistoryCard,
|
||||||
|
PivotArea,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -117,13 +116,14 @@ class QueueManager(QWidget):
|
|||||||
|
|
||||||
logger.success(f"调度队列_{index} 添加成功")
|
logger.success(f"调度队列_{index} 添加成功")
|
||||||
MainInfoBar.push_info_bar("success", "操作成功", f"添加 调度队列_{index}", 3000)
|
MainInfoBar.push_info_bar("success", "操作成功", f"添加 调度队列_{index}", 3000)
|
||||||
|
SoundPlayer.play("添加调度队列")
|
||||||
|
|
||||||
def del_setting_box(self):
|
def del_setting_box(self):
|
||||||
"""删除一个调度队列实例"""
|
"""删除一个调度队列实例"""
|
||||||
|
|
||||||
name = self.queue_manager.pivot.currentRouteKey()
|
name = self.queue_manager.pivot.currentRouteKey()
|
||||||
|
|
||||||
if name == None:
|
if name is None:
|
||||||
logger.warning("未选择调度队列")
|
logger.warning("未选择调度队列")
|
||||||
MainInfoBar.push_info_bar(
|
MainInfoBar.push_info_bar(
|
||||||
"warning", "未选择调度队列", "请先选择一个调度队列", 5000
|
"warning", "未选择调度队列", "请先选择一个调度队列", 5000
|
||||||
@@ -137,11 +137,7 @@ class QueueManager(QWidget):
|
|||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
choice = MessageBox(
|
choice = MessageBox("确认", f"确定要删除 {name} 吗?", self.window())
|
||||||
"确认",
|
|
||||||
f"确定要删除 {name} 吗?",
|
|
||||||
self.window(),
|
|
||||||
)
|
|
||||||
if choice.exec():
|
if choice.exec():
|
||||||
|
|
||||||
self.queue_manager.clear_SettingBox()
|
self.queue_manager.clear_SettingBox()
|
||||||
@@ -159,13 +155,14 @@ class QueueManager(QWidget):
|
|||||||
|
|
||||||
logger.success(f"{name} 删除成功")
|
logger.success(f"{name} 删除成功")
|
||||||
MainInfoBar.push_info_bar("success", "操作成功", f"删除 {name}", 3000)
|
MainInfoBar.push_info_bar("success", "操作成功", f"删除 {name}", 3000)
|
||||||
|
SoundPlayer.play("删除调度队列")
|
||||||
|
|
||||||
def left_setting_box(self):
|
def left_setting_box(self):
|
||||||
"""向左移动调度队列实例"""
|
"""向左移动调度队列实例"""
|
||||||
|
|
||||||
name = self.queue_manager.pivot.currentRouteKey()
|
name = self.queue_manager.pivot.currentRouteKey()
|
||||||
|
|
||||||
if name == None:
|
if name is None:
|
||||||
logger.warning("未选择调度队列")
|
logger.warning("未选择调度队列")
|
||||||
MainInfoBar.push_info_bar(
|
MainInfoBar.push_info_bar(
|
||||||
"warning", "未选择调度队列", "请先选择一个调度队列", 5000
|
"warning", "未选择调度队列", "请先选择一个调度队列", 5000
|
||||||
@@ -210,7 +207,7 @@ class QueueManager(QWidget):
|
|||||||
|
|
||||||
name = self.queue_manager.pivot.currentRouteKey()
|
name = self.queue_manager.pivot.currentRouteKey()
|
||||||
|
|
||||||
if name == None:
|
if name is None:
|
||||||
logger.warning("未选择调度队列")
|
logger.warning("未选择调度队列")
|
||||||
MainInfoBar.push_info_bar(
|
MainInfoBar.push_info_bar(
|
||||||
"warning", "未选择调度队列", "请先选择一个调度队列", 5000
|
"warning", "未选择调度队列", "请先选择一个调度队列", 5000
|
||||||
@@ -305,15 +302,19 @@ class QueueManager(QWidget):
|
|||||||
|
|
||||||
self.setObjectName("调度队列管理")
|
self.setObjectName("调度队列管理")
|
||||||
|
|
||||||
self.pivot = Pivot(self)
|
self.pivotArea = PivotArea()
|
||||||
|
self.pivot = self.pivotArea.pivot
|
||||||
|
|
||||||
self.stackedWidget = QStackedWidget(self)
|
self.stackedWidget = QStackedWidget(self)
|
||||||
self.Layout = QVBoxLayout(self)
|
self.stackedWidget.setContentsMargins(0, 0, 0, 0)
|
||||||
|
self.stackedWidget.setStyleSheet("background: transparent; border: none;")
|
||||||
|
|
||||||
self.script_list: List[
|
self.script_list: List[
|
||||||
QueueManager.QueueSettingBox.QueueMemberSettingBox
|
QueueManager.QueueSettingBox.QueueMemberSettingBox
|
||||||
] = []
|
] = []
|
||||||
|
|
||||||
self.Layout.addWidget(self.pivot, 0, Qt.AlignHCenter)
|
self.Layout = QVBoxLayout(self)
|
||||||
|
self.Layout.addWidget(self.pivotArea)
|
||||||
self.Layout.addWidget(self.stackedWidget)
|
self.Layout.addWidget(self.stackedWidget)
|
||||||
self.Layout.setContentsMargins(0, 0, 0, 0)
|
self.Layout.setContentsMargins(0, 0, 0, 0)
|
||||||
|
|
||||||
@@ -376,14 +377,6 @@ class QueueManager(QWidget):
|
|||||||
self.setObjectName(f"调度队列_{uid}")
|
self.setObjectName(f"调度队列_{uid}")
|
||||||
self.config = Config.queue_dict[f"调度队列_{uid}"]["Config"]
|
self.config = Config.queue_dict[f"调度队列_{uid}"]["Config"]
|
||||||
|
|
||||||
layout = QVBoxLayout()
|
|
||||||
|
|
||||||
scrollArea = ScrollArea()
|
|
||||||
scrollArea.setWidgetResizable(True)
|
|
||||||
|
|
||||||
content_widget = QWidget()
|
|
||||||
content_layout = QVBoxLayout(content_widget)
|
|
||||||
|
|
||||||
self.queue_set = self.QueueSetSettingCard(self.config, self)
|
self.queue_set = self.QueueSetSettingCard(self.config, self)
|
||||||
self.time = self.TimeSettingCard(self.config, self)
|
self.time = self.TimeSettingCard(self.config, self)
|
||||||
self.task = self.TaskSettingCard(self.config, self)
|
self.task = self.TaskSettingCard(self.config, self)
|
||||||
@@ -393,18 +386,24 @@ class QueueManager(QWidget):
|
|||||||
parent=self,
|
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.queue_set)
|
||||||
content_layout.addWidget(self.time)
|
content_layout.addWidget(self.time)
|
||||||
content_layout.addWidget(self.task)
|
content_layout.addWidget(self.task)
|
||||||
content_layout.addWidget(self.history)
|
content_layout.addWidget(self.history)
|
||||||
content_layout.addStretch(1)
|
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)
|
scrollArea.setWidget(content_widget)
|
||||||
|
|
||||||
|
layout = QVBoxLayout(self)
|
||||||
layout.addWidget(scrollArea)
|
layout.addWidget(scrollArea)
|
||||||
|
|
||||||
self.setLayout(layout)
|
|
||||||
|
|
||||||
class QueueSetSettingCard(HeaderCardWidget):
|
class QueueSetSettingCard(HeaderCardWidget):
|
||||||
|
|
||||||
def __init__(self, config: QueueConfig, parent=None):
|
def __init__(self, config: QueueConfig, parent=None):
|
||||||
|
|||||||
@@ -26,7 +26,8 @@ v4.3
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from PySide6.QtWidgets import QApplication, QWidget, QVBoxLayout
|
from PySide6.QtWidgets import QWidget, QVBoxLayout
|
||||||
|
from PySide6.QtGui import QIcon
|
||||||
from PySide6.QtCore import Qt
|
from PySide6.QtCore import Qt
|
||||||
from qfluentwidgets import (
|
from qfluentwidgets import (
|
||||||
ScrollArea,
|
ScrollArea,
|
||||||
@@ -48,8 +49,9 @@ from packaging import version
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict, Union
|
from typing import Dict, Union
|
||||||
|
|
||||||
from app.core import Config, MainInfoBar, Network
|
from app.core import Config, MainInfoBar, Network, SoundPlayer
|
||||||
from app.services import Crypto, System, Notify
|
from app.services import Crypto, System, Notify
|
||||||
|
from .downloader import DownloadManager
|
||||||
from .Widget import (
|
from .Widget import (
|
||||||
SwitchSettingCard,
|
SwitchSettingCard,
|
||||||
RangeSettingCard,
|
RangeSettingCard,
|
||||||
@@ -68,10 +70,8 @@ class Setting(QWidget):
|
|||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.setObjectName("设置")
|
self.setObjectName("设置")
|
||||||
|
|
||||||
content_widget = QWidget()
|
|
||||||
content_layout = QVBoxLayout(content_widget)
|
|
||||||
|
|
||||||
self.function = FunctionSettingCard(self)
|
self.function = FunctionSettingCard(self)
|
||||||
|
self.voice = VoiceSettingCard(self)
|
||||||
self.start = StartSettingCard(self)
|
self.start = StartSettingCard(self)
|
||||||
self.ui = UiSettingCard(self)
|
self.ui = UiSettingCard(self)
|
||||||
self.notification = NotifySettingCard(self)
|
self.notification = NotifySettingCard(self)
|
||||||
@@ -87,11 +87,15 @@ class Setting(QWidget):
|
|||||||
self.start.card_IfSelfStart.checkedChanged.connect(System.set_SelfStart)
|
self.start.card_IfSelfStart.checkedChanged.connect(System.set_SelfStart)
|
||||||
self.security.card_changePASSWORD.clicked.connect(self.change_PASSWORD)
|
self.security.card_changePASSWORD.clicked.connect(self.change_PASSWORD)
|
||||||
self.updater.card_CheckUpdate.clicked.connect(
|
self.updater.card_CheckUpdate.clicked.connect(
|
||||||
lambda: self.check_update(if_click=True)
|
lambda: self.check_update(if_show=True)
|
||||||
)
|
)
|
||||||
self.other.card_Notice.clicked.connect(self.show_notice)
|
self.other.card_Notice.clicked.connect(lambda: self.show_notice(if_show=True))
|
||||||
|
|
||||||
|
content_widget = QWidget()
|
||||||
|
content_layout = QVBoxLayout(content_widget)
|
||||||
|
content_layout.setContentsMargins(0, 0, 11, 0)
|
||||||
content_layout.addWidget(self.function)
|
content_layout.addWidget(self.function)
|
||||||
|
content_layout.addWidget(self.voice)
|
||||||
content_layout.addWidget(self.start)
|
content_layout.addWidget(self.start)
|
||||||
content_layout.addWidget(self.ui)
|
content_layout.addWidget(self.ui)
|
||||||
content_layout.addWidget(self.notification)
|
content_layout.addWidget(self.notification)
|
||||||
@@ -101,10 +105,12 @@ class Setting(QWidget):
|
|||||||
|
|
||||||
scrollArea = ScrollArea()
|
scrollArea = ScrollArea()
|
||||||
scrollArea.setWidgetResizable(True)
|
scrollArea.setWidgetResizable(True)
|
||||||
|
scrollArea.setContentsMargins(0, 0, 0, 0)
|
||||||
|
scrollArea.setStyleSheet("background: transparent; border: none;")
|
||||||
scrollArea.setWidget(content_widget)
|
scrollArea.setWidget(content_widget)
|
||||||
layout = QVBoxLayout()
|
|
||||||
|
layout = QVBoxLayout(self)
|
||||||
layout.addWidget(scrollArea)
|
layout.addWidget(scrollArea)
|
||||||
self.setLayout(layout)
|
|
||||||
|
|
||||||
def agree_bilibili(self) -> None:
|
def agree_bilibili(self) -> None:
|
||||||
"""授权bilibili游戏隐私政策"""
|
"""授权bilibili游戏隐私政策"""
|
||||||
@@ -177,10 +183,7 @@ class Setting(QWidget):
|
|||||||
while True:
|
while True:
|
||||||
|
|
||||||
choice = LineEditMessageBox(
|
choice = LineEditMessageBox(
|
||||||
self.window(),
|
self.window(), "请设置您的管理密钥", "管理密钥", "密码"
|
||||||
"未检测到管理密钥,请设置您的管理密钥",
|
|
||||||
"管理密钥",
|
|
||||||
"密码",
|
|
||||||
)
|
)
|
||||||
if choice.exec() and choice.input.text() != "":
|
if choice.exec() and choice.input.text() != "":
|
||||||
Crypto.get_PASSWORD(choice.input.text())
|
Crypto.get_PASSWORD(choice.input.text())
|
||||||
@@ -251,79 +254,77 @@ class Setting(QWidget):
|
|||||||
choice.exec()
|
choice.exec()
|
||||||
else:
|
else:
|
||||||
choice = MessageBox(
|
choice = MessageBox(
|
||||||
"确认",
|
"确认", "您没有输入管理密钥,是否取消修改管理密钥?", self.window()
|
||||||
"您没有输入管理密钥,是否取消修改管理密钥?",
|
|
||||||
self.window(),
|
|
||||||
)
|
)
|
||||||
if choice.exec():
|
if choice.exec():
|
||||||
break
|
break
|
||||||
|
|
||||||
def check_update(self, if_click: bool = False) -> None:
|
def check_update(self, if_show: bool = False, if_first: bool = False) -> None:
|
||||||
"""检查版本更新,调起文件下载进程"""
|
"""检查版本更新,调起文件下载进程"""
|
||||||
|
|
||||||
current_version = list(map(int, Config.VERSION.split(".")))
|
current_version = list(map(int, Config.VERSION.split(".")))
|
||||||
|
|
||||||
if Network.if_running and if_click:
|
|
||||||
MainInfoBar.push_info_bar(
|
|
||||||
"warning", "请求速度过快", "上个网络请求还未结束,请稍等片刻", 5000
|
|
||||||
)
|
|
||||||
return None
|
|
||||||
# 从远程服务器获取最新版本信息
|
# 从远程服务器获取最新版本信息
|
||||||
Network.set_info(
|
network = Network.add_task(
|
||||||
mode="get",
|
mode="get",
|
||||||
url=f"https://mirrorchyan.com/api/resources/AUTO_MAA/latest?user_agent=AutoMaaGui¤t_version={version_text(current_version)}&cdk={Crypto.win_decryptor(Config.get(Config.update_MirrorChyanCDK))}&channel={Config.get(Config.update_UpdateType)}",
|
url=f"https://mirrorchyan.com/api/resources/AUTO_MAA/latest?user_agent=AutoMaaGui¤t_version={version_text(current_version)}&cdk={Crypto.win_decryptor(Config.get(Config.update_MirrorChyanCDK))}&channel={Config.get(Config.update_UpdateType)}",
|
||||||
)
|
)
|
||||||
Network.start()
|
network.loop.exec()
|
||||||
Network.loop.exec()
|
network_result = Network.get_result(network)
|
||||||
if Network.stutus_code == 200:
|
if network_result["status_code"] == 200:
|
||||||
version_info: Dict[str, Union[int, str, Dict[str, str]]] = (
|
version_info: Dict[str, Union[int, str, Dict[str, str]]] = network_result[
|
||||||
Network.response_json
|
"response_json"
|
||||||
)
|
]
|
||||||
else:
|
else:
|
||||||
logger.warning(f"获取版本信息时出错:{Network.error_message}")
|
|
||||||
|
if network_result["response_json"]:
|
||||||
|
|
||||||
|
version_info = network_result["response_json"]
|
||||||
|
|
||||||
|
if version_info["code"] != 0:
|
||||||
|
|
||||||
|
logger.error(f"获取版本信息时出错:{version_info['msg']}")
|
||||||
|
|
||||||
|
error_remark_dict = {
|
||||||
|
1001: "获取版本信息的URL参数不正确",
|
||||||
|
7001: "填入的 CDK 已过期",
|
||||||
|
7002: "填入的 CDK 错误",
|
||||||
|
7003: "填入的 CDK 今日下载次数已达上限",
|
||||||
|
7004: "填入的 CDK 类型和待下载的资源不匹配",
|
||||||
|
7005: "填入的 CDK 已被封禁",
|
||||||
|
8001: "对应架构和系统下的资源不存在",
|
||||||
|
8002: "错误的系统参数",
|
||||||
|
8003: "错误的架构参数",
|
||||||
|
8004: "错误的更新通道参数",
|
||||||
|
1: version_info["msg"],
|
||||||
|
}
|
||||||
|
|
||||||
|
if version_info["code"] in error_remark_dict:
|
||||||
|
MainInfoBar.push_info_bar(
|
||||||
|
"error",
|
||||||
|
"获取版本信息时出错",
|
||||||
|
error_remark_dict[version_info["code"]],
|
||||||
|
-1,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
MainInfoBar.push_info_bar(
|
||||||
|
"error",
|
||||||
|
"获取版本信息时出错",
|
||||||
|
"意料之外的错误,请及时联系项目组以获取来自 Mirror 酱的技术支持",
|
||||||
|
-1,
|
||||||
|
)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
logger.warning(f"获取版本信息时出错:{network_result['error_message']}")
|
||||||
MainInfoBar.push_info_bar(
|
MainInfoBar.push_info_bar(
|
||||||
"warning",
|
"warning",
|
||||||
"获取版本信息时出错",
|
"获取版本信息时出错",
|
||||||
f"网络错误:{Network.stutus_code}",
|
f"网络错误:{network_result['status_code']}",
|
||||||
5000,
|
5000,
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if version_info["code"] != 0:
|
|
||||||
|
|
||||||
logger.error(f"获取版本信息时出错:{version_info["msg"]}")
|
|
||||||
|
|
||||||
error_remark_dict = {
|
|
||||||
1001: "获取版本信息的URL参数不正确",
|
|
||||||
7001: "填入的 CDK 已过期",
|
|
||||||
7002: "填入的 CDK 错误",
|
|
||||||
7003: "填入的 CDK 今日下载次数已达上限",
|
|
||||||
7004: "填入的 CDK 类型和待下载的资源不匹配",
|
|
||||||
7005: "填入的 CDK 已被封禁",
|
|
||||||
8001: "对应架构和系统下的资源不存在",
|
|
||||||
8002: "错误的系统参数",
|
|
||||||
8003: "错误的架构参数",
|
|
||||||
8004: "错误的更新通道参数",
|
|
||||||
1: version_info["msg"],
|
|
||||||
}
|
|
||||||
|
|
||||||
if version_info["code"] in error_remark_dict:
|
|
||||||
MainInfoBar.push_info_bar(
|
|
||||||
"error",
|
|
||||||
"获取版本信息时出错",
|
|
||||||
error_remark_dict[version_info["code"]],
|
|
||||||
-1,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
MainInfoBar.push_info_bar(
|
|
||||||
"error",
|
|
||||||
"获取版本信息时出错",
|
|
||||||
"意料之外的错误,请及时联系项目组以获取来自 Mirror 酱的技术支持",
|
|
||||||
-1,
|
|
||||||
)
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
remote_version = list(
|
remote_version = list(
|
||||||
map(
|
map(
|
||||||
int,
|
int,
|
||||||
@@ -333,8 +334,14 @@ class Setting(QWidget):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
# 有版本更新
|
if (
|
||||||
if version.parse(version_text(remote_version)) > version.parse(
|
if_show
|
||||||
|
or (
|
||||||
|
not if_show
|
||||||
|
and if_first
|
||||||
|
and not Config.get(Config.function_UnattendedMode)
|
||||||
|
)
|
||||||
|
) and version.parse(version_text(remote_version)) > version.parse(
|
||||||
version_text(current_version)
|
version_text(current_version)
|
||||||
):
|
):
|
||||||
|
|
||||||
@@ -370,68 +377,133 @@ class Setting(QWidget):
|
|||||||
else:
|
else:
|
||||||
all_version_info[key] = value.copy()
|
all_version_info[key] = value.copy()
|
||||||
|
|
||||||
version_info = {
|
|
||||||
"更新总览": f"{main_version_info}\n\n{version_info_markdown(update_version_info)}",
|
|
||||||
"ALL~版本信息": version_info_markdown(all_version_info),
|
|
||||||
**{
|
|
||||||
version_text(list(map(int, k.split(".")))): version_info_markdown(v)
|
|
||||||
for k, v in version_info_json.items()
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
# 询问是否开始版本更新
|
# 询问是否开始版本更新
|
||||||
choice = NoticeMessageBox(self.window(), "版本更新", version_info)
|
SoundPlayer.play("有新版本")
|
||||||
|
choice = NoticeMessageBox(
|
||||||
|
self.window(),
|
||||||
|
"版本更新",
|
||||||
|
{
|
||||||
|
"更新总览": f"{main_version_info}\n\n{version_info_markdown(update_version_info)}",
|
||||||
|
"ALL~版本信息": version_info_markdown(all_version_info),
|
||||||
|
**{
|
||||||
|
version_text(
|
||||||
|
list(map(int, k.split(".")))
|
||||||
|
): version_info_markdown(v)
|
||||||
|
for k, v in version_info_json.items()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
if choice.exec():
|
if choice.exec():
|
||||||
|
|
||||||
with Config.version_path.open(mode="r", encoding="utf-8") as f:
|
if "url" in version_info["data"]:
|
||||||
version_info = json.load(f)
|
download_config = {
|
||||||
version_info["main_version"] = Config.VERSION
|
"mode": "MirrorChyan",
|
||||||
with Config.version_path.open(mode="w", encoding="utf-8") as f:
|
"thread_numb": 1,
|
||||||
json.dump(version_info, f, ensure_ascii=False, indent=4)
|
"url": version_info["data"]["url"],
|
||||||
|
}
|
||||||
if (Config.app_path / "AUTO_Updater.exe").exists():
|
|
||||||
shutil.copy(
|
|
||||||
Config.app_path / "AUTO_Updater.exe",
|
|
||||||
Config.app_path / "AUTO_Updater.active.exe",
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
logger.error("更新器文件不存在")
|
|
||||||
MainInfoBar.push_info_bar(
|
# 从远程服务器获取代理信息
|
||||||
"error", "更新器不存在", "请手动前往 GitHub 获取最新版本", -1
|
network = Network.add_task(
|
||||||
|
mode="get",
|
||||||
|
url="https://gitee.com/DLmaster_361/AUTO_MAA/raw/server/download_info.json",
|
||||||
)
|
)
|
||||||
return None
|
network.loop.exec()
|
||||||
|
network_result = Network.get_result(network)
|
||||||
|
if network_result["status_code"] == 200:
|
||||||
|
download_info = network_result["response_json"]
|
||||||
|
else:
|
||||||
|
logger.warning(
|
||||||
|
f"获取应用列表时出错:{network_result['error_message']}"
|
||||||
|
)
|
||||||
|
MainInfoBar.push_info_bar(
|
||||||
|
"warning",
|
||||||
|
"获取应用列表时出错",
|
||||||
|
f"网络错误:{network_result['status_code']}",
|
||||||
|
5000,
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
subprocess.Popen(
|
download_config = {
|
||||||
[Config.app_path / "AUTO_Updater.active.exe"],
|
"mode": "Proxy",
|
||||||
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP
|
"thread_numb": Config.get(Config.update_ThreadNumb),
|
||||||
| subprocess.DETACHED_PROCESS
|
"proxy_list": list(
|
||||||
| subprocess.CREATE_NO_WINDOW,
|
set(
|
||||||
|
Config.get(Config.update_ProxyUrlList)
|
||||||
|
+ download_info["proxy_list"]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
"download_dict": download_info["download_dict"],
|
||||||
|
}
|
||||||
|
|
||||||
|
self.downloader = DownloadManager(
|
||||||
|
Config.app_path, "AUTO_MAA", remote_version, download_config
|
||||||
)
|
)
|
||||||
self.window().close()
|
self.downloader.setWindowTitle("AUTO_MAA更新器")
|
||||||
QApplication.quit()
|
self.downloader.setWindowIcon(
|
||||||
|
QIcon(str(Config.app_path / "resources/icons/AUTO_MAA_Updater.ico"))
|
||||||
|
)
|
||||||
|
self.downloader.download_accomplish.connect(self.start_setup)
|
||||||
|
self.downloader.show()
|
||||||
|
self.downloader.run()
|
||||||
|
|
||||||
# 无版本更新
|
elif (
|
||||||
else:
|
if_show
|
||||||
MainInfoBar.push_info_bar("success", "更新检查", "已是最新版本~", 3000)
|
or if_first
|
||||||
|
or version.parse(version_text(remote_version))
|
||||||
|
> version.parse(version_text(current_version))
|
||||||
|
):
|
||||||
|
|
||||||
def show_notice(self, if_show: bool = True) -> None:
|
if version.parse(version_text(remote_version)) > version.parse(
|
||||||
|
version_text(current_version)
|
||||||
|
):
|
||||||
|
MainInfoBar.push_info_bar(
|
||||||
|
"info",
|
||||||
|
"发现新版本",
|
||||||
|
f"{version_text(current_version)} --> {version_text(remote_version)}",
|
||||||
|
3600000,
|
||||||
|
if_force=True,
|
||||||
|
)
|
||||||
|
SoundPlayer.play("有新版本")
|
||||||
|
else:
|
||||||
|
MainInfoBar.push_info_bar("success", "更新检查", "已是最新版本~", 3000)
|
||||||
|
SoundPlayer.play("无新版本")
|
||||||
|
|
||||||
|
def start_setup(self) -> None:
|
||||||
|
subprocess.Popen(
|
||||||
|
[
|
||||||
|
Config.app_path / "AUTO_MAA-Setup.exe",
|
||||||
|
"/SP-",
|
||||||
|
"/SILENT",
|
||||||
|
"/NOCANCEL",
|
||||||
|
"/FORCECLOSEAPPLICATIONS",
|
||||||
|
"/LANG=Chinese",
|
||||||
|
f"/DIR={Config.app_path}",
|
||||||
|
],
|
||||||
|
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP
|
||||||
|
| subprocess.DETACHED_PROCESS
|
||||||
|
| subprocess.CREATE_NO_WINDOW,
|
||||||
|
)
|
||||||
|
System.set_power("KillSelf")
|
||||||
|
|
||||||
|
def show_notice(self, if_show: bool = False, if_first: bool = False) -> None:
|
||||||
"""显示公告"""
|
"""显示公告"""
|
||||||
|
|
||||||
# 从远程服务器获取最新公告
|
# 从远程服务器获取最新公告
|
||||||
Network.set_info(
|
network = Network.add_task(
|
||||||
mode="get",
|
mode="get",
|
||||||
url="https://gitee.com/DLmaster_361/AUTO_MAA/raw/server/notice.json",
|
url="https://gitee.com/DLmaster_361/AUTO_MAA/raw/server/notice.json",
|
||||||
)
|
)
|
||||||
Network.start()
|
network.loop.exec()
|
||||||
Network.loop.exec()
|
network_result = Network.get_result(network)
|
||||||
if Network.stutus_code == 200:
|
if network_result["status_code"] == 200:
|
||||||
notice = Network.response_json
|
notice = network_result["response_json"]
|
||||||
else:
|
else:
|
||||||
logger.warning(f"获取最新公告时出错:{Network.error_message}")
|
logger.warning(f"获取最新公告时出错:{network_result['error_message']}")
|
||||||
MainInfoBar.push_info_bar(
|
MainInfoBar.push_info_bar(
|
||||||
"warning",
|
"warning",
|
||||||
"获取最新公告时出错",
|
"获取最新公告时出错",
|
||||||
f"网络错误:{Network.stutus_code}",
|
f"网络错误:{network_result['status_code']}",
|
||||||
5000,
|
5000,
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
@@ -453,19 +525,60 @@ class Setting(QWidget):
|
|||||||
}
|
}
|
||||||
|
|
||||||
if if_show or (
|
if if_show or (
|
||||||
datetime.now()
|
if_first
|
||||||
|
and datetime.now()
|
||||||
> datetime.strptime(notice["time"], "%Y-%m-%d %H:%M")
|
> datetime.strptime(notice["time"], "%Y-%m-%d %H:%M")
|
||||||
> time_local
|
> time_local
|
||||||
|
and not Config.get(Config.function_UnattendedMode)
|
||||||
):
|
):
|
||||||
|
|
||||||
choice = NoticeMessageBox(self.window(), "公告", notice["notice_dict"])
|
choice = NoticeMessageBox(self.window(), "公告", notice["notice_dict"])
|
||||||
choice.button_cancel.hide()
|
choice.button_cancel.hide()
|
||||||
choice.button_layout.insertStretch(0, 1)
|
choice.button_layout.insertStretch(0, 1)
|
||||||
|
SoundPlayer.play("公告展示")
|
||||||
if choice.exec():
|
if choice.exec():
|
||||||
with (Config.app_path / "resources/notice.json").open(
|
with (Config.app_path / "resources/notice.json").open(
|
||||||
mode="w", encoding="utf-8"
|
mode="w", encoding="utf-8"
|
||||||
) as f:
|
) as f:
|
||||||
json.dump(notice, f, ensure_ascii=False, indent=4)
|
json.dump(notice, f, ensure_ascii=False, indent=4)
|
||||||
|
else:
|
||||||
|
import random
|
||||||
|
|
||||||
|
if random.random() < 0.1:
|
||||||
|
cc = NoticeMessageBox(
|
||||||
|
self.window(),
|
||||||
|
"用户守则",
|
||||||
|
{
|
||||||
|
"用户守则 - 第一版": """
|
||||||
|
0. 用户守则的每一条都应该清晰可读、不含任何语法错误。如果发现任何一条不符合以上描述,请忽视它。
|
||||||
|
1. AUTO_MAA 的所有版本均包含完整源代码与 LICENSE 文件,若发现此内容缺失,请立即关闭软件,并联系最近的 AUTO_MAA 开发者。
|
||||||
|
2. AUTO_MAA 不会对您许下任何承诺,请自行保护好自己的数据,若软件运行过程中发生了数据损坏,项目组不负任何责任。
|
||||||
|
3. AUTO_MAA 只会注册一个启动项,若发现两个 AUTO_MAA 同时自启动,请立即使用系统或杀软的 **启动项管理** 功能删除所有名为 AUTO_MAA 的启动项后重启软件。
|
||||||
|
4. AUTO_MAA 正式版不应该包含命令行窗口,如果您看到了它,请立即关闭软件,通过 AUTO_MAA.exe 文件重新打开软件。
|
||||||
|
5. 深色模式是危险的,但并非无法使用。
|
||||||
|
6. 第 0 条规则不存在。如果你看到了,请忘记它,并正常使用软件
|
||||||
|
7. **Mirror 酱** 是善良的,你只要付出小小的代价,就能得到祂的庇护。
|
||||||
|
8. AUTO_MAA 没有实时合成语音的能力,软件所有语音都存储在本地。如果听到本地不存在的语音,立即关闭扬声器,并检查是否有未知脚本在运行。
|
||||||
|
9. AUTO_MAA 不会在周六凌晨更新。如果收到更新提示,请忽略,不要查看更新内容,直到第二天天亮。
|
||||||
|
10. 用户守则仅有一页""",
|
||||||
|
"--- 标记文档中止 ---": "xdfv-serfcx-jiol,m: !1 $bad food of do $5b 9630-300 $daad 100-1\n\n// 0 == o //\n\n∠( °ω°)/",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
cc.button_cancel.hide()
|
||||||
|
cc.button_layout.insertStretch(0, 1)
|
||||||
|
cc.exec()
|
||||||
|
|
||||||
|
elif (
|
||||||
|
datetime.now()
|
||||||
|
> datetime.strptime(notice["time"], "%Y-%m-%d %H:%M")
|
||||||
|
> time_local
|
||||||
|
):
|
||||||
|
|
||||||
|
MainInfoBar.push_info_bar(
|
||||||
|
"info", "有新公告", "请前往设置界面查看公告", 3600000, if_force=True
|
||||||
|
)
|
||||||
|
SoundPlayer.play("公告通知")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
class FunctionSettingCard(HeaderCardWidget):
|
class FunctionSettingCard(HeaderCardWidget):
|
||||||
@@ -501,6 +614,14 @@ class FunctionSettingCard(HeaderCardWidget):
|
|||||||
parent=self,
|
parent=self,
|
||||||
)
|
)
|
||||||
self.card_IfSilence = self.SilenceSettingCard(self)
|
self.card_IfSilence = self.SilenceSettingCard(self)
|
||||||
|
self.card_UnattendedMode = SwitchSettingCard(
|
||||||
|
icon=FluentIcon.PAGE_RIGHT,
|
||||||
|
title="无人值守模式",
|
||||||
|
content="开启后AUTO_MAA不再主动弹出对话框,以免影响代理任务运行",
|
||||||
|
qconfig=Config,
|
||||||
|
configItem=Config.function_UnattendedMode,
|
||||||
|
parent=self,
|
||||||
|
)
|
||||||
self.card_IfAgreeBilibili = SwitchSettingCard(
|
self.card_IfAgreeBilibili = SwitchSettingCard(
|
||||||
icon=FluentIcon.PAGE_RIGHT,
|
icon=FluentIcon.PAGE_RIGHT,
|
||||||
title="托管bilibili游戏隐私政策",
|
title="托管bilibili游戏隐私政策",
|
||||||
@@ -523,6 +644,7 @@ class FunctionSettingCard(HeaderCardWidget):
|
|||||||
Layout.addWidget(self.card_HistoryRetentionTime)
|
Layout.addWidget(self.card_HistoryRetentionTime)
|
||||||
Layout.addWidget(self.card_IfAllowSleep)
|
Layout.addWidget(self.card_IfAllowSleep)
|
||||||
Layout.addWidget(self.card_IfSilence)
|
Layout.addWidget(self.card_IfSilence)
|
||||||
|
Layout.addWidget(self.card_UnattendedMode)
|
||||||
Layout.addWidget(self.card_IfAgreeBilibili)
|
Layout.addWidget(self.card_IfAgreeBilibili)
|
||||||
Layout.addWidget(self.card_IfSkipMumuSplashAds)
|
Layout.addWidget(self.card_IfSkipMumuSplashAds)
|
||||||
self.viewLayout.addLayout(Layout)
|
self.viewLayout.addLayout(Layout)
|
||||||
@@ -564,6 +686,36 @@ class FunctionSettingCard(HeaderCardWidget):
|
|||||||
self.addGroupWidget(widget)
|
self.addGroupWidget(widget)
|
||||||
|
|
||||||
|
|
||||||
|
class VoiceSettingCard(HeaderCardWidget):
|
||||||
|
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.setTitle("音效")
|
||||||
|
|
||||||
|
self.card_Enabled = SwitchSettingCard(
|
||||||
|
icon=FluentIcon.PAGE_RIGHT,
|
||||||
|
title="音效开关",
|
||||||
|
content="是否启用音效",
|
||||||
|
qconfig=Config,
|
||||||
|
configItem=Config.voice_Enabled,
|
||||||
|
parent=self,
|
||||||
|
)
|
||||||
|
self.card_Type = ComboBoxSettingCard(
|
||||||
|
icon=FluentIcon.PAGE_RIGHT,
|
||||||
|
title="音效模式",
|
||||||
|
content="选择音效的播放模式",
|
||||||
|
texts=["简洁", "聒噪"],
|
||||||
|
qconfig=Config,
|
||||||
|
configItem=Config.voice_Type,
|
||||||
|
parent=self,
|
||||||
|
)
|
||||||
|
|
||||||
|
Layout = QVBoxLayout()
|
||||||
|
Layout.addWidget(self.card_Enabled)
|
||||||
|
Layout.addWidget(self.card_Type)
|
||||||
|
self.viewLayout.addLayout(Layout)
|
||||||
|
|
||||||
|
|
||||||
class StartSettingCard(HeaderCardWidget):
|
class StartSettingCard(HeaderCardWidget):
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
@@ -967,7 +1119,9 @@ class UpdaterSettingCard(HeaderCardWidget):
|
|||||||
parent=self,
|
parent=self,
|
||||||
)
|
)
|
||||||
mirrorchyan_url = HyperlinkButton(
|
mirrorchyan_url = HyperlinkButton(
|
||||||
"https://mirrorchyan.com/", "获取Mirror酱CDK", self
|
"https://mirrorchyan.com/zh/get-start?source=auto_maa-setting_card",
|
||||||
|
"获取Mirror酱CDK",
|
||||||
|
self,
|
||||||
)
|
)
|
||||||
self.card_MirrorChyanCDK.hBoxLayout.insertWidget(
|
self.card_MirrorChyanCDK.hBoxLayout.insertWidget(
|
||||||
5, mirrorchyan_url, 0, Qt.AlignRight
|
5, mirrorchyan_url, 0, Qt.AlignRight
|
||||||
@@ -998,9 +1152,9 @@ class OtherSettingCard(HeaderCardWidget):
|
|||||||
)
|
)
|
||||||
self.card_UserDocs = HyperlinkCard(
|
self.card_UserDocs = HyperlinkCard(
|
||||||
url="https://clozya.github.io/AUTOMAA_docs",
|
url="https://clozya.github.io/AUTOMAA_docs",
|
||||||
text="访问",
|
text="查看指南",
|
||||||
icon=FluentIcon.PAGE_RIGHT,
|
icon=FluentIcon.PAGE_RIGHT,
|
||||||
title="AUTO_MAA官方文档站",
|
title="用户指南",
|
||||||
content="访问AUTO_MAA的官方文档站,获取使用指南和项目相关信息",
|
content="访问AUTO_MAA的官方文档站,获取使用指南和项目相关信息",
|
||||||
parent=self,
|
parent=self,
|
||||||
)
|
)
|
||||||
|
|||||||
88
app/utils/AUTO_MAA.iss
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;
|
||||||
67
app/utils/ImageUtils.py
Normal file
67
app/utils/ImageUtils.py
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import base64
|
||||||
|
import hashlib
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
class ImageUtils:
|
||||||
|
@staticmethod
|
||||||
|
def get_base64_from_file(image_path):
|
||||||
|
"""从本地文件读取并返回base64编码字符串"""
|
||||||
|
with open(image_path, "rb") as f:
|
||||||
|
return base64.b64encode(f.read()).decode("utf-8")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def calculate_md5_from_file(image_path):
|
||||||
|
"""从本地文件读取并返回md5值(hex字符串)"""
|
||||||
|
with open(image_path, "rb") as f:
|
||||||
|
return hashlib.md5(f.read()).hexdigest()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def calculate_md5_from_base64(base64_content):
|
||||||
|
"""从base64字符串计算md5"""
|
||||||
|
image_data = base64.b64decode(base64_content)
|
||||||
|
return hashlib.md5(image_data).hexdigest()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def compress_image_if_needed(image_path: Path, max_size_mb=2) -> Path:
|
||||||
|
"""
|
||||||
|
如果图片大于max_size_mb,则压缩并覆盖原文件,返回原始路径(Path对象)
|
||||||
|
"""
|
||||||
|
if hasattr(Image, "Resampling"): # Pillow 9.1.0及以后
|
||||||
|
RESAMPLE = Image.Resampling.LANCZOS
|
||||||
|
else:
|
||||||
|
RESAMPLE = Image.ANTIALIAS
|
||||||
|
|
||||||
|
max_size = max_size_mb * 1024 * 1024
|
||||||
|
if image_path.stat().st_size <= max_size:
|
||||||
|
return image_path
|
||||||
|
|
||||||
|
img = Image.open(image_path)
|
||||||
|
suffix = image_path.suffix.lower()
|
||||||
|
quality = 90 if suffix in [".jpg", ".jpeg"] else None
|
||||||
|
step = 5
|
||||||
|
|
||||||
|
if suffix in [".jpg", ".jpeg"]:
|
||||||
|
while True:
|
||||||
|
img.save(image_path, quality=quality, optimize=True)
|
||||||
|
if image_path.stat().st_size <= max_size or quality <= 10:
|
||||||
|
break
|
||||||
|
quality -= step
|
||||||
|
elif suffix == ".png":
|
||||||
|
width, height = img.size
|
||||||
|
while True:
|
||||||
|
img.save(image_path, optimize=True)
|
||||||
|
if (
|
||||||
|
image_path.stat().st_size <= max_size
|
||||||
|
or width <= 200
|
||||||
|
or height <= 200
|
||||||
|
):
|
||||||
|
break
|
||||||
|
width = int(width * 0.95)
|
||||||
|
height = int(height * 0.95)
|
||||||
|
img = img.resize((width, height), RESAMPLE)
|
||||||
|
else:
|
||||||
|
raise ValueError("仅支持JPG/JPEG和PNG格式图片的压缩。")
|
||||||
|
|
||||||
|
return image_path
|
||||||
@@ -29,6 +29,4 @@ __version__ = "4.2.0"
|
|||||||
__author__ = "DLmaster361 <DLmaster_361@163.com>"
|
__author__ = "DLmaster361 <DLmaster_361@163.com>"
|
||||||
__license__ = "GPL-3.0 license"
|
__license__ = "GPL-3.0 license"
|
||||||
|
|
||||||
from .downloader import DownloadManager
|
__all__ = []
|
||||||
|
|
||||||
__all__ = ["DownloadManager"]
|
|
||||||
|
|||||||
@@ -66,18 +66,17 @@ if __name__ == "__main__":
|
|||||||
version = json.load(f)
|
version = json.load(f)
|
||||||
|
|
||||||
main_version_numb = list(map(int, version["main_version"].split(".")))
|
main_version_numb = list(map(int, version["main_version"].split(".")))
|
||||||
updater_version_numb = list(map(int, version["updater_version"].split(".")))
|
|
||||||
|
|
||||||
print("Packaging AUTO_MAA main program ...")
|
print("Packaging AUTO_MAA main program ...")
|
||||||
|
|
||||||
os.system(
|
os.system(
|
||||||
"powershell -Command python -m nuitka --standalone --onefile --mingw64"
|
"powershell -Command python -m nuitka --standalone --onefile --mingw64"
|
||||||
" --enable-plugins=pyside6 --windows-console-mode=disable"
|
" --enable-plugins=pyside6 --windows-console-mode=attach"
|
||||||
" --onefile-tempdir-spec='{TEMP}\\AUTO_MAA'"
|
" --onefile-tempdir-spec='{TEMP}\\AUTO_MAA'"
|
||||||
" --windows-icon-from-ico=resources\\icons\\AUTO_MAA.ico"
|
" --windows-icon-from-ico=resources\\icons\\AUTO_MAA.ico"
|
||||||
" --company-name='AUTO_MAA Team' --product-name=AUTO_MAA"
|
" --company-name='AUTO_MAA Team' --product-name=AUTO_MAA"
|
||||||
f" --file-version={version["main_version"]}"
|
f" --file-version={version['main_version']}"
|
||||||
f" --product-version={version["main_version"]}"
|
f" --product-version={version['main_version']}"
|
||||||
" --file-description='AUTO_MAA Component'"
|
" --file-description='AUTO_MAA Component'"
|
||||||
" --copyright='Copyright © 2024-2025 DLmaster361'"
|
" --copyright='Copyright © 2024-2025 DLmaster361'"
|
||||||
" --assume-yes-for-downloads --output-filename=AUTO_MAA"
|
" --assume-yes-for-downloads --output-filename=AUTO_MAA"
|
||||||
@@ -86,35 +85,10 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
print("AUTO_MAA main program packaging completed !")
|
print("AUTO_MAA main program packaging completed !")
|
||||||
|
|
||||||
print("Packaging AUTO_MAA update program ...")
|
print("start to create setup program ...")
|
||||||
|
|
||||||
shutil.copy(root_path / "app/utils/downloader.py", root_path)
|
|
||||||
os.system(
|
|
||||||
"powershell -Command python -m nuitka --standalone --onefile --mingw64"
|
|
||||||
" --enable-plugins=pyside6 --windows-console-mode=disable"
|
|
||||||
" --onefile-tempdir-spec='{TEMP}\\AUTO_MAA_Updater'"
|
|
||||||
" --windows-icon-from-ico=resources\\icons\\AUTO_MAA_Updater.ico"
|
|
||||||
" --company-name='AUTO_MAA Team' --product-name=AUTO_MAA"
|
|
||||||
f" --file-version={version["updater_version"]}"
|
|
||||||
f" --product-version={version["main_version"]}"
|
|
||||||
" --file-description='AUTO_MAA Component'"
|
|
||||||
" --copyright='Copyright © 2024-2025 DLmaster361'"
|
|
||||||
" --assume-yes-for-downloads --output-filename=AUTO_Updater"
|
|
||||||
" --remove-output downloader.py"
|
|
||||||
)
|
|
||||||
(root_path / "downloader.py").unlink()
|
|
||||||
|
|
||||||
print("AUTO_MAA update program packaging completed !")
|
|
||||||
|
|
||||||
(root_path / "AUTO_MAA").mkdir(parents=True, exist_ok=True)
|
(root_path / "AUTO_MAA").mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
print("Start to move AUTO_MAA program ...")
|
|
||||||
|
|
||||||
shutil.move(root_path / "AUTO_MAA.exe", root_path / "AUTO_MAA/")
|
shutil.move(root_path / "AUTO_MAA.exe", root_path / "AUTO_MAA/")
|
||||||
shutil.move(root_path / "AUTO_Updater.exe", root_path / "AUTO_MAA/")
|
|
||||||
|
|
||||||
print("Start to copy rescourses ...")
|
|
||||||
|
|
||||||
shutil.copytree(root_path / "app", root_path / "AUTO_MAA/app")
|
shutil.copytree(root_path / "app", root_path / "AUTO_MAA/app")
|
||||||
shutil.copytree(root_path / "resources", root_path / "AUTO_MAA/resources")
|
shutil.copytree(root_path / "resources", root_path / "AUTO_MAA/resources")
|
||||||
shutil.copy(root_path / "main.py", root_path / "AUTO_MAA/")
|
shutil.copy(root_path / "main.py", root_path / "AUTO_MAA/")
|
||||||
@@ -122,17 +96,38 @@ if __name__ == "__main__":
|
|||||||
shutil.copy(root_path / "README.md", root_path / "AUTO_MAA/")
|
shutil.copy(root_path / "README.md", root_path / "AUTO_MAA/")
|
||||||
shutil.copy(root_path / "LICENSE", root_path / "AUTO_MAA/")
|
shutil.copy(root_path / "LICENSE", root_path / "AUTO_MAA/")
|
||||||
|
|
||||||
print("Start to compress ...")
|
with (root_path / "app/utils/AUTO_MAA.iss").open(mode="r", encoding="utf-8") as f:
|
||||||
|
iss = f.read()
|
||||||
|
iss = (
|
||||||
|
iss.replace(
|
||||||
|
'#define MyAppVersion ""',
|
||||||
|
f'#define MyAppVersion "{version["main_version"]}"',
|
||||||
|
)
|
||||||
|
.replace(
|
||||||
|
'#define MyAppPath ""', f'#define MyAppPath "{root_path / "AUTO_MAA"}"'
|
||||||
|
)
|
||||||
|
.replace('#define OutputDir ""', f'#define OutputDir "{root_path}"')
|
||||||
|
)
|
||||||
|
with (root_path / "AUTO_MAA.iss").open(mode="w", encoding="utf-8") as f:
|
||||||
|
f.write(iss)
|
||||||
|
|
||||||
|
os.system(f'ISCC "{root_path / "AUTO_MAA.iss"}"')
|
||||||
|
|
||||||
|
(root_path / "AUTO_MAA_Setup").mkdir(parents=True, exist_ok=True)
|
||||||
|
shutil.move(root_path / "AUTO_MAA-Setup.exe", root_path / "AUTO_MAA_Setup")
|
||||||
|
|
||||||
shutil.make_archive(
|
shutil.make_archive(
|
||||||
base_name=root_path / f"AUTO_MAA_{version_text(main_version_numb)}",
|
base_name=root_path / f"AUTO_MAA_{version_text(main_version_numb)}",
|
||||||
format="zip",
|
format="zip",
|
||||||
root_dir=root_path / "AUTO_MAA",
|
root_dir=root_path / "AUTO_MAA_Setup",
|
||||||
base_dir=".",
|
base_dir=".",
|
||||||
)
|
)
|
||||||
shutil.rmtree(root_path / "AUTO_MAA")
|
|
||||||
|
|
||||||
print("compress completed !")
|
print("setup program created !")
|
||||||
|
|
||||||
|
(root_path / "AUTO_MAA.iss").unlink(missing_ok=True)
|
||||||
|
shutil.rmtree(root_path / "AUTO_MAA")
|
||||||
|
shutil.rmtree(root_path / "AUTO_MAA_Setup")
|
||||||
|
|
||||||
all_version_info = {}
|
all_version_info = {}
|
||||||
for v_i in version["version_info"].values():
|
for v_i in version["version_info"].values():
|
||||||
@@ -143,6 +138,6 @@ if __name__ == "__main__":
|
|||||||
all_version_info[key] = value.copy()
|
all_version_info[key] = value.copy()
|
||||||
|
|
||||||
(root_path / "version_info.txt").write_text(
|
(root_path / "version_info.txt").write_text(
|
||||||
f"{version_text(main_version_numb)}\n{version_text(updater_version_numb)}\n<!--{json.dumps(version["version_info"], ensure_ascii=False)}-->\n{version_info_markdown(all_version_info)}",
|
f"{version_text(main_version_numb)}\n\n<!--{json.dumps(version["version_info"], ensure_ascii=False)}-->\n{version_info_markdown(all_version_info)}",
|
||||||
encoding="utf-8",
|
encoding="utf-8",
|
||||||
)
|
)
|
||||||
|
|||||||
21
main.py
21
main.py
@@ -25,6 +25,25 @@ v4.3
|
|||||||
作者:DLmaster_361
|
作者:DLmaster_361
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# 屏蔽广告
|
||||||
|
import builtins
|
||||||
|
|
||||||
|
original_print = builtins.print
|
||||||
|
|
||||||
|
|
||||||
|
def no_print(*args, **kwargs):
|
||||||
|
if (
|
||||||
|
args
|
||||||
|
and isinstance(args[0], str)
|
||||||
|
and "QFluentWidgets Pro is now released." in args[0]
|
||||||
|
):
|
||||||
|
return
|
||||||
|
return original_print(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
builtins.print = no_print
|
||||||
|
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from PySide6.QtWidgets import QApplication
|
from PySide6.QtWidgets import QApplication
|
||||||
from qfluentwidgets import FluentTranslator
|
from qfluentwidgets import FluentTranslator
|
||||||
@@ -42,7 +61,7 @@ def main():
|
|||||||
from app.ui.main_window import AUTO_MAA
|
from app.ui.main_window import AUTO_MAA
|
||||||
|
|
||||||
window = AUTO_MAA()
|
window = AUTO_MAA()
|
||||||
window.show_ui("显示主窗口")
|
window.show_ui("显示主窗口", if_start=True)
|
||||||
window.start_up_task()
|
window.start_up_task()
|
||||||
sys.exit(application.exec())
|
sys.exit(application.exec())
|
||||||
|
|
||||||
|
|||||||
@@ -3,12 +3,11 @@ plyer
|
|||||||
PySide6
|
PySide6
|
||||||
PySide6-Fluent-Widgets[full]
|
PySide6-Fluent-Widgets[full]
|
||||||
psutil
|
psutil
|
||||||
opencv-python
|
|
||||||
pywin32
|
pywin32
|
||||||
pyautogui
|
keyboard
|
||||||
pycryptodome
|
pycryptodome
|
||||||
requests
|
requests
|
||||||
markdown
|
markdown
|
||||||
Jinja2
|
Jinja2
|
||||||
serverchan_sdk
|
nuitka
|
||||||
nuitka
|
pillow
|
||||||
403
resources/docs/ChineseSimplified.isl
Normal file
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您要继续吗?
|
||||||
@@ -6,10 +6,18 @@
|
|||||||
"TaskQueue.Recruiting.IsChecked": "True" #自动公招
|
"TaskQueue.Recruiting.IsChecked": "True" #自动公招
|
||||||
"TaskQueue.Base.IsChecked": "True" #基建换班
|
"TaskQueue.Base.IsChecked": "True" #基建换班
|
||||||
"TaskQueue.Combat.IsChecked": "True" #刷理智
|
"TaskQueue.Combat.IsChecked": "True" #刷理智
|
||||||
"TaskQueue.Mission.IsChecked": "True" #领取奖励
|
|
||||||
"TaskQueue.Mall.IsChecked": "True" #获取信用及购物
|
"TaskQueue.Mall.IsChecked": "True" #获取信用及购物
|
||||||
|
"TaskQueue.Mission.IsChecked": "True" #领取奖励
|
||||||
"TaskQueue.AutoRoguelike.IsChecked": "False" #自动肉鸽
|
"TaskQueue.AutoRoguelike.IsChecked": "False" #自动肉鸽
|
||||||
"TaskQueue.Reclamation.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": "True" #吃理智药
|
||||||
"MainFunction.UseMedicine.Quantity": "999" #吃理智药数量
|
"MainFunction.UseMedicine.Quantity": "999" #吃理智药数量
|
||||||
@@ -30,8 +38,8 @@
|
|||||||
"Penguin.EnablePenguin": "True" #上报企鹅物流
|
"Penguin.EnablePenguin": "True" #上报企鹅物流
|
||||||
"Yituliu.EnableYituliu": "True" #上报一图流
|
"Yituliu.EnableYituliu": "True" #上报一图流
|
||||||
#基建换班
|
#基建换班
|
||||||
"Infrast.CustomInfrastEnabled": "True" #启用自定义基建配置
|
"Infrast.InfrastMode": "Normal"、"Rotation"、"Custom" #基建模式
|
||||||
"Infrast.CustomInfrastPlanIndex": "1" #自定义基建配置索引
|
"Infrast.CustomInfrastPlanIndex": "1" #自定义基建配置索引号
|
||||||
"Infrast.DefaultInfrast": "user_defined" #内置配置
|
"Infrast.DefaultInfrast": "user_defined" #内置配置
|
||||||
"Infrast.IsCustomInfrastFileReadOnly": "False" #自定义基建配置文件只读
|
"Infrast.IsCustomInfrastFileReadOnly": "False" #自定义基建配置文件只读
|
||||||
"Infrast.CustomInfrastFile": "" #自定义基建配置文件地址
|
"Infrast.CustomInfrastFile": "" #自定义基建配置文件地址
|
||||||
@@ -49,4 +57,6 @@ G"Start.MinimizeDirectly": "True" #启动MAA后直接最小化
|
|||||||
G"GUI.UseTray": "True" #显示托盘图标
|
G"GUI.UseTray": "True" #显示托盘图标
|
||||||
G"GUI.MinimizeToTray": "False" #最小化时隐藏至托盘
|
G"GUI.MinimizeToTray": "False" #最小化时隐藏至托盘
|
||||||
"Start.EmulatorPath" #模拟器路径
|
"Start.EmulatorPath" #模拟器路径
|
||||||
"Start.EmulatorAddCommand": "-v 2" #附加命令
|
"Start.EmulatorAddCommand": "-v 2" #附加命令
|
||||||
|
"Start.EmulatorWaitSeconds": "10" #等待模拟器启动时间
|
||||||
|
G"VersionUpdate.package": "MirrorChyanAppv5.15.6.zip" #更新包标识
|
||||||
File diff suppressed because one or more lines are too long
BIN
resources/icons/MirrorChyan.ico
Normal file
BIN
resources/icons/MirrorChyan.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 2.8 MiB After Width: | Height: | Size: 1.7 MiB |
BIN
resources/images/notification/six_star.png
Normal file
BIN
resources/images/notification/six_star.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 222 KiB |
BIN
resources/images/notification/test_notify.png
Normal file
BIN
resources/images/notification/test_notify.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
BIN
resources/sounds/both/删除用户.wav
Normal file
BIN
resources/sounds/both/删除用户.wav
Normal file
Binary file not shown.
BIN
resources/sounds/both/删除脚本实例.wav
Normal file
BIN
resources/sounds/both/删除脚本实例.wav
Normal file
Binary file not shown.
BIN
resources/sounds/both/删除计划表.wav
Normal file
BIN
resources/sounds/both/删除计划表.wav
Normal file
Binary file not shown.
BIN
resources/sounds/both/删除调度队列.wav
Normal file
BIN
resources/sounds/both/删除调度队列.wav
Normal file
Binary file not shown.
BIN
resources/sounds/both/欢迎回来.wav
Normal file
BIN
resources/sounds/both/欢迎回来.wav
Normal file
Binary file not shown.
BIN
resources/sounds/both/添加用户.wav
Normal file
BIN
resources/sounds/both/添加用户.wav
Normal file
Binary file not shown.
BIN
resources/sounds/both/添加脚本实例.wav
Normal file
BIN
resources/sounds/both/添加脚本实例.wav
Normal file
Binary file not shown.
BIN
resources/sounds/both/添加计划表.wav
Normal file
BIN
resources/sounds/both/添加计划表.wav
Normal file
Binary file not shown.
BIN
resources/sounds/both/添加调度队列.wav
Normal file
BIN
resources/sounds/both/添加调度队列.wav
Normal file
Binary file not shown.
BIN
resources/sounds/noisy/ADB失败.wav
Normal file
BIN
resources/sounds/noisy/ADB失败.wav
Normal file
Binary file not shown.
BIN
resources/sounds/noisy/ADB成功.wav
Normal file
BIN
resources/sounds/noisy/ADB成功.wav
Normal file
Binary file not shown.
BIN
resources/sounds/noisy/MAA在完成任务前中止.wav
Normal file
BIN
resources/sounds/noisy/MAA在完成任务前中止.wav
Normal file
Binary file not shown.
BIN
resources/sounds/noisy/MAA在完成任务前退出.wav
Normal file
BIN
resources/sounds/noisy/MAA在完成任务前退出.wav
Normal file
Binary file not shown.
BIN
resources/sounds/noisy/MAA更新.wav
Normal file
BIN
resources/sounds/noisy/MAA更新.wav
Normal file
Binary file not shown.
BIN
resources/sounds/noisy/MAA未检测到任何模拟器.wav
Normal file
BIN
resources/sounds/noisy/MAA未检测到任何模拟器.wav
Normal file
Binary file not shown.
BIN
resources/sounds/noisy/MAA未能正确登录PRTS.wav
Normal file
BIN
resources/sounds/noisy/MAA未能正确登录PRTS.wav
Normal file
Binary file not shown.
BIN
resources/sounds/noisy/MAA的ADB连接异常.wav
Normal file
BIN
resources/sounds/noisy/MAA的ADB连接异常.wav
Normal file
Binary file not shown.
BIN
resources/sounds/noisy/MAA进程超时.wav
Normal file
BIN
resources/sounds/noisy/MAA进程超时.wav
Normal file
Binary file not shown.
BIN
resources/sounds/noisy/MAA部分任务执行失败.wav
Normal file
BIN
resources/sounds/noisy/MAA部分任务执行失败.wav
Normal file
Binary file not shown.
BIN
resources/sounds/noisy/任务开始.wav
Normal file
BIN
resources/sounds/noisy/任务开始.wav
Normal file
Binary file not shown.
BIN
resources/sounds/noisy/任务结束.wav
Normal file
BIN
resources/sounds/noisy/任务结束.wav
Normal file
Binary file not shown.
BIN
resources/sounds/noisy/公告展示.wav
Normal file
BIN
resources/sounds/noisy/公告展示.wav
Normal file
Binary file not shown.
BIN
resources/sounds/noisy/公告通知.wav
Normal file
BIN
resources/sounds/noisy/公告通知.wav
Normal file
Binary file not shown.
BIN
resources/sounds/noisy/六星喜报.wav
Normal file
BIN
resources/sounds/noisy/六星喜报.wav
Normal file
Binary file not shown.
BIN
resources/sounds/noisy/历史记录查询.wav
Normal file
BIN
resources/sounds/noisy/历史记录查询.wav
Normal file
Binary file not shown.
BIN
resources/sounds/noisy/发生异常.wav
Normal file
BIN
resources/sounds/noisy/发生异常.wav
Normal file
Binary file not shown.
BIN
resources/sounds/noisy/发生错误.wav
Normal file
BIN
resources/sounds/noisy/发生错误.wav
Normal file
Binary file not shown.
BIN
resources/sounds/noisy/子任务失败.wav
Normal file
BIN
resources/sounds/noisy/子任务失败.wav
Normal file
Binary file not shown.
BIN
resources/sounds/noisy/排查录入.wav
Normal file
BIN
resources/sounds/noisy/排查录入.wav
Normal file
Binary file not shown.
BIN
resources/sounds/noisy/排查重试.wav
Normal file
BIN
resources/sounds/noisy/排查重试.wav
Normal file
Binary file not shown.
BIN
resources/sounds/noisy/无新版本.wav
Normal file
BIN
resources/sounds/noisy/无新版本.wav
Normal file
Binary file not shown.
BIN
resources/sounds/noisy/有新版本.wav
Normal file
BIN
resources/sounds/noisy/有新版本.wav
Normal file
Binary file not shown.
BIN
resources/sounds/noisy/森空岛签到失败.wav
Normal file
BIN
resources/sounds/noisy/森空岛签到失败.wav
Normal file
Binary file not shown.
BIN
resources/sounds/noisy/森空岛签到成功.wav
Normal file
BIN
resources/sounds/noisy/森空岛签到成功.wav
Normal file
Binary file not shown.
BIN
resources/sounds/simple/任务开始.wav
Normal file
BIN
resources/sounds/simple/任务开始.wav
Normal file
Binary file not shown.
BIN
resources/sounds/simple/任务结束.wav
Normal file
BIN
resources/sounds/simple/任务结束.wav
Normal file
Binary file not shown.
BIN
resources/sounds/simple/公告展示.wav
Normal file
BIN
resources/sounds/simple/公告展示.wav
Normal file
Binary file not shown.
BIN
resources/sounds/simple/公告通知.wav
Normal file
BIN
resources/sounds/simple/公告通知.wav
Normal file
Binary file not shown.
BIN
resources/sounds/simple/历史记录查询.wav
Normal file
BIN
resources/sounds/simple/历史记录查询.wav
Normal file
Binary file not shown.
BIN
resources/sounds/simple/发生异常.wav
Normal file
BIN
resources/sounds/simple/发生异常.wav
Normal file
Binary file not shown.
BIN
resources/sounds/simple/发生错误.wav
Normal file
BIN
resources/sounds/simple/发生错误.wav
Normal file
Binary file not shown.
BIN
resources/sounds/simple/无新版本.wav
Normal file
BIN
resources/sounds/simple/无新版本.wav
Normal file
Binary file not shown.
BIN
resources/sounds/simple/有新版本.wav
Normal file
BIN
resources/sounds/simple/有新版本.wav
Normal file
Binary file not shown.
@@ -1,106 +1,37 @@
|
|||||||
{
|
{
|
||||||
"main_version": "4.3.4.0",
|
"main_version": "4.3.10.0",
|
||||||
"updater_version": "1.0.0.0",
|
|
||||||
"announcement": "\n## 新增功能\n- 屏蔽MuMu模拟器开屏广告功能上线\n- 更新器支持多线程下载\n- 添加强制关闭ADB与模拟器等增强任务项\n## 修复BUG\n- 修复统计信息HTML模板公招匹配错误\n- 修复密码显示按钮动画异常\n- 修复`检测到MAA未能实际执行任务`报错被异常屏蔽\n- 修复MAA超时判定异常失效\n## 程序优化\n- 关机等电源操作添加100s倒计时\n- 人工排查弹窗方法优化\n- 人工排查时自动屏蔽静默操作\n- 公告样式优化",
|
|
||||||
"version_info": {
|
"version_info": {
|
||||||
"4.3.4.0": {
|
"4.3.10.0": {
|
||||||
"修复BUG": [
|
|
||||||
"同步到MAAv5.15.4的tasks目录结构变化"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"4.3.4.7": {
|
|
||||||
"新增功能": [
|
"新增功能": [
|
||||||
"主调度台支持直接启动多开调度台"
|
"更换全新默认主页图",
|
||||||
|
"适配 MAA 无`Default`配置情况 #52"
|
||||||
],
|
],
|
||||||
"程序优化": [
|
"程序优化": [
|
||||||
"历史记录保留时间选项扩展"
|
"静默模式控制时段延长至模拟器完成启动的10s后"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"4.3.4.6": {
|
"4.3.10.3": {
|
||||||
|
"程序优化": [
|
||||||
|
"使用 keyboard 模块替代 pyautogui 模块"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"4.3.10.2": {
|
||||||
"新增功能": [
|
"新增功能": [
|
||||||
"历史记录添加`按周合并`、`按月合并`功能",
|
"公招喜报模板优化",
|
||||||
"新增MAA稳定版更新提醒"
|
"支持使用命令行调用"
|
||||||
],
|
],
|
||||||
"修复BUG": [
|
"修复BUG": [
|
||||||
"修复历史记录选项卡无法回收的问题"
|
"修复更新动作重复执行问题"
|
||||||
],
|
],
|
||||||
"程序优化": [
|
"程序优化": [
|
||||||
"历史记录添加`选中最近一月`、`选中最近一周`选项与打开日志文件所在目录功能",
|
"Mirror 酱链接添加`source`字段,用于标识来源",
|
||||||
"MAA报错判定结果文案拆分优化"
|
"优化下载器测速中止条件"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"4.3.4.5": {
|
"4.3.10.1": {
|
||||||
"修复BUG": [
|
|
||||||
"修复启动时运行主任务的历史记录与完成后任务异常"
|
|
||||||
],
|
|
||||||
"程序优化": [
|
|
||||||
"历史记录转为自行搜索",
|
|
||||||
"历史记录时间条目排序规则优化"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"4.3.4.4": {
|
|
||||||
"修复BUG": [
|
|
||||||
"修复更新时主程序退出不彻底"
|
|
||||||
],
|
|
||||||
"程序优化": [
|
|
||||||
"ADB路径与模拟器路径添加报错提示",
|
|
||||||
"ADB路径兼容相对路径"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"4.3.4.3": {
|
|
||||||
"修复BUG": [
|
|
||||||
"修复更新器使用mirrorc下载时删除已弃用的文件卡死"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"4.3.4.2": {
|
|
||||||
"程序优化": [
|
|
||||||
"调度队列历史记录归入配置类管理",
|
|
||||||
"添加.gitignore",
|
|
||||||
"工作流删除冗余部分",
|
|
||||||
"自动代理与人工排查结束后MAA恢复到全局配置 #40",
|
|
||||||
"网络相关操作由子线程执行"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"4.3.4.1": {
|
|
||||||
"新增功能": [
|
"新增功能": [
|
||||||
"开始任务前自动释放ADB端口"
|
"森空岛签到功能上线"
|
||||||
],
|
|
||||||
"程序优化": [
|
|
||||||
"request 添加超时限制",
|
|
||||||
"用户任务运行流程改进",
|
|
||||||
"自动代理中模拟器改由AUTO_MAA控制",
|
|
||||||
"修正部分配置项文案"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"4.3.3.0": {
|
|
||||||
"修复BUG": [
|
|
||||||
"修复更新器无法下载MAA的异常"
|
|
||||||
],
|
|
||||||
"程序优化": [
|
|
||||||
"自动发版改为手动触发"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"4.3.2.0": {
|
|
||||||
"修复BUG": [
|
|
||||||
"修复更新器无法启动的异常"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"4.3.1.0": {
|
|
||||||
"修复BUG": [
|
|
||||||
"覆盖规避v4.3.0错误包"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"proxy_list": [
|
|
||||||
"https://gitproxy.click/",
|
|
||||||
"https://cdn.moran233.xyz/",
|
|
||||||
"https://gh.llkk.cc/",
|
|
||||||
"https://github.akams.cn/",
|
|
||||||
"https://www.ghproxy.cn/",
|
|
||||||
"https://ghfast.top/"
|
|
||||||
],
|
|
||||||
"download_dict": {
|
|
||||||
"官方下载站-jp": "https://jp-download.fearr.xyz/AUTO_MAA/",
|
|
||||||
"官方下载站-hw": "http://hwobs.fearr.xyz/releases/artifacts/"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user