Compare commits
111 Commits
v4.2.5-bet
...
v4.3.6-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fba5395bf0 | ||
|
|
2c4508ee16 | ||
| d239443555 | |||
| e45ad08fab | |||
| ddf5d26c4b | |||
|
|
ce74dcf912 | ||
|
|
41412e1ef4 | ||
|
|
1395d48cd0 | ||
|
|
418c3d4742 | ||
|
|
17ec962a22 | ||
|
|
989ee73549 | ||
|
|
7e452e1253 | ||
|
|
5bdb5c8025 | ||
|
|
924a5fea0b | ||
|
|
b51a57a6ee | ||
|
|
4079188881 | ||
|
|
174163e305 | ||
|
|
0886439685 | ||
|
|
34bf5a4fe8 | ||
|
|
e6a97f2b17 | ||
|
|
fecff625a3 | ||
|
|
6f540036a0 | ||
|
|
86d72aec39 | ||
|
|
39876832f3 | ||
|
|
f3af6ddbbc | ||
|
|
ba7299e20c | ||
|
|
5db9d934b2 | ||
|
|
5c8eebf12c | ||
|
|
e725f6d2b2 | ||
|
|
494b655156 | ||
|
|
2940f2557c | ||
|
|
5e4660670f | ||
| e8d592ae76 | |||
|
|
97ea51df59 | ||
|
|
986061dc97 | ||
|
|
fe1910d16f | ||
|
|
63cb1aaa74 | ||
| 49ebd50077 | |||
|
|
4a6f874210 | ||
|
|
9394c7a9c5 | ||
|
|
7e502420fa | ||
|
|
12f4b764de | ||
|
|
4da4b7d552 | ||
|
|
d38abbbaa0 | ||
|
|
67bf7f649e | ||
|
|
acb35403b0 | ||
|
|
7d5dccc649 | ||
|
|
a7e0e7b217 | ||
|
|
9ce75b2dda | ||
|
|
d2022819f6 | ||
|
|
c8b342ba01 | ||
|
|
63823d5c89 | ||
|
|
cb17cc32da | ||
|
|
14d0e6d438 | ||
|
|
878fbad06a | ||
|
|
deb0506163 | ||
|
|
c4aeb673fd | ||
|
|
915ee59643 | ||
|
|
1568e120be | ||
|
|
d19dd3496d | ||
|
|
62c86ce477 | ||
|
|
c727eddc54 | ||
|
|
9c946ef6dc | ||
|
|
38a04fc4b2 | ||
| bded794647 | |||
|
|
539cb1de99 | ||
| 2e9ff47dbb | |||
|
|
c01079af1b | ||
|
|
cca1acb6f6 | ||
|
|
d7e502e22f | ||
|
|
bbeab360bc | ||
|
|
a78b7fdb29 | ||
| 273fbe2261 | |||
| ba9855c616 | |||
| c54f894f4f | |||
|
|
9f88f92ec0 | ||
|
|
a80e96c2cd | ||
|
|
7774612810 | ||
| 088ea1817c | |||
|
|
f362c8f7ef | ||
|
|
648f42b7e0 | ||
|
|
9a56cc350d | ||
|
|
50cd49217f | ||
|
|
7ed4b7db57 | ||
| b359cd623b | |||
|
|
a363e8dc34 | ||
|
|
52affc0d76 | ||
|
|
fe26f29f93 | ||
|
|
67b8725156 | ||
|
|
2a235b2bc9 | ||
|
|
dd022cf356 | ||
|
|
62e5bb30e2 | ||
|
|
675e11960a | ||
|
|
0c274ecbe0 | ||
|
|
2dfcd3f131 | ||
|
|
053acd138f | ||
|
|
3f20ae62be | ||
|
|
d342c7c827 | ||
|
|
3da0cfd0d0 | ||
|
|
acc4045580 | ||
|
|
6ee577302f | ||
|
|
d52856180a | ||
|
|
d4d479ca20 | ||
|
|
364af4b9c5 | ||
|
|
9e0d81fb1d | ||
|
|
2ee2c37479 | ||
|
|
528925b969 | ||
| 4851b40777 | |||
|
|
6372ad4e0a | ||
|
|
465bc9137e | ||
|
|
e8b6f5d893 |
57
.github/workflows/build-app.yml
vendored
57
.github/workflows/build-app.yml
vendored
@@ -1,5 +1,5 @@
|
|||||||
# <AUTO_MAA:A MAA Multi Account Management and Automation Tool>
|
# AUTO_MAA:A MAA Multi Account Management and Automation Tool
|
||||||
# Copyright © <2024> <DLmaster361>
|
# Copyright © 2024-2025 DLmaster361
|
||||||
|
|
||||||
# This file is part of AUTO_MAA.
|
# This file is part of AUTO_MAA.
|
||||||
|
|
||||||
@@ -16,19 +16,16 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
# DLmaster_361@163.com
|
# Contact: DLmaster_361@163.com
|
||||||
|
|
||||||
name: Build AUTO_MAA
|
name: Build AUTO_MAA
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
workflow_dispatch:
|
||||||
branches: [ "main" ]
|
|
||||||
paths-ignore:
|
|
||||||
- '**.md'
|
|
||||||
- 'LICENSE'
|
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: write
|
||||||
|
actions: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
pre_check:
|
pre_check:
|
||||||
@@ -76,18 +73,12 @@ jobs:
|
|||||||
"AUTO_MAA_version=$MAIN_VERSION" | Out-File -FilePath $env:GITHUB_ENV -Append
|
"AUTO_MAA_version=$MAIN_VERSION" | Out-File -FilePath $env:GITHUB_ENV -Append
|
||||||
$UPDATER_VERSION=(Get-Content -Path "version_info.txt" -TotalCount 2 | Select-Object -Index 1).Trim()
|
$UPDATER_VERSION=(Get-Content -Path "version_info.txt" -TotalCount 2 | Select-Object -Index 1).Trim()
|
||||||
"updater_version=$UPDATER_VERSION" | Out-File -FilePath $env:GITHUB_ENV -Append
|
"updater_version=$UPDATER_VERSION" | Out-File -FilePath $env:GITHUB_ENV -Append
|
||||||
- name: Create Zip
|
|
||||||
id: create_zip
|
|
||||||
run: |
|
|
||||||
Compress-Archive -Path app,resources,main.py,AUTO_MAA.exe,requirements.txt,README.md,LICENSE -DestinationPath AUTO_MAA_${{ env.AUTO_MAA_version }}.zip
|
|
||||||
Compress-Archive -Path Updater.exe -DestinationPath Updater_${{ env.updater_version }}.zip
|
|
||||||
- name: Upload Artifact
|
- 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_${{ env.AUTO_MAA_version }}
|
||||||
path: |
|
path: |
|
||||||
AUTO_MAA_${{ env.AUTO_MAA_version }}.zip
|
AUTO_MAA_${{ env.AUTO_MAA_version }}.zip
|
||||||
Updater_${{ env.updater_version }}.zip
|
|
||||||
- name: Upload Version_Info Artifact
|
- name: Upload Version_Info Artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
@@ -111,20 +102,8 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
name: version_info
|
name: version_info
|
||||||
path: ./
|
path: ./
|
||||||
- name: Check if release exists
|
|
||||||
id: check_if_release_exists
|
|
||||||
run: |
|
|
||||||
release_id=$(gh release view $(sed 's/\r$//g' <(head -n 1 version_info.txt)) --json id --jq .id || true)
|
|
||||||
if [[ -z $release_id ]]; then
|
|
||||||
echo "release_exists=false" >> $GITHUB_OUTPUT
|
|
||||||
else
|
|
||||||
echo "release_exists=true" >> $GITHUB_OUTPUT
|
|
||||||
fi
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.WORKFLOW_TOKEN }}
|
|
||||||
- name: Create release
|
- name: Create release
|
||||||
id: create_release
|
id: create_release
|
||||||
if: steps.check_if_release_exists.outputs.release_exists == 'false'
|
|
||||||
run: |
|
run: |
|
||||||
set -xe
|
set -xe
|
||||||
shopt -s nullglob
|
shopt -s nullglob
|
||||||
@@ -133,24 +112,20 @@ jobs:
|
|||||||
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_TAIL="\`\`\`本release通过GitHub Actions自动构建\`\`\`"
|
||||||
NOTES="$NOTES_MAIN<br><br>$NOTES_TAIL"
|
NOTES="$NOTES_MAIN<br><br>$NOTES_TAIL"
|
||||||
gh release create "$TAGNAME" --target "main" --title "$NAME" --notes "$NOTES" artifacts/*
|
if [ "${{ github.ref_name }}" == "main" ]; then
|
||||||
|
PRERELEASE_FLAG=""
|
||||||
|
else
|
||||||
|
PRERELEASE_FLAG="--prerelease"
|
||||||
|
fi
|
||||||
|
gh release create "$TAGNAME" --target "main" --title "$NAME" --notes "$NOTES" $PRERELEASE_FLAG artifacts/*
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.WORKFLOW_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.WORKFLOW_TOKEN }}
|
||||||
- name: Update release
|
- name: Trigger MirrorChyanUploading
|
||||||
id: update_release
|
|
||||||
if: steps.check_if_release_exists.outputs.release_exists == 'true'
|
|
||||||
run: |
|
run: |
|
||||||
set -xe
|
gh workflow run --repo $GITHUB_REPOSITORY mirrorchyan
|
||||||
shopt -s nullglob
|
gh workflow run --repo $GITHUB_REPOSITORY mirrorchyan_release_note
|
||||||
NAME="$(sed 's/\r$//g' <(head -n 1 version_info.txt))"
|
|
||||||
TAGNAME="$(sed 's/\r$//g' <(head -n 1 version_info.txt))"
|
|
||||||
NOTES_MAIN="$(sed 's/\r$//g' <(tail -n +3 version_info.txt))"
|
|
||||||
NOTES_TAIL="\`\`\`本release通过GitHub Actions自动构建\`\`\`"
|
|
||||||
NOTES="$NOTES_MAIN<br><br>$NOTES_TAIL"
|
|
||||||
gh release delete "$TAGNAME" --yes
|
|
||||||
gh release create "$TAGNAME" --target "main" --title "$NAME" --notes "$NOTES" artifacts/*
|
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.WORKFLOW_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Setup SSH Key
|
- name: Setup SSH Key
|
||||||
run: |
|
run: |
|
||||||
mkdir -p ~/.ssh
|
mkdir -p ~/.ssh
|
||||||
|
|||||||
162
.github/workflows/build-pre.yml
vendored
162
.github/workflows/build-pre.yml
vendored
@@ -1,162 +0,0 @@
|
|||||||
# <AUTO_MAA:A MAA Multi Account Management and Automation Tool>
|
|
||||||
# Copyright © <2024> <DLmaster361>
|
|
||||||
|
|
||||||
# This file is part of AUTO_MAA.
|
|
||||||
|
|
||||||
# AUTO_MAA is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published
|
|
||||||
# by the Free Software Foundation, either version 3 of the License,
|
|
||||||
# or (at your option) any later version.
|
|
||||||
|
|
||||||
# AUTO_MAA is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty
|
|
||||||
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
|
|
||||||
# the GNU General Public License for more details.
|
|
||||||
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
# DLmaster_361@163.com
|
|
||||||
|
|
||||||
name: Build AUTO_MAA_Pre
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ "dev" ]
|
|
||||||
paths-ignore:
|
|
||||||
- '**.md'
|
|
||||||
- 'LICENSE'
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
pre_check:
|
|
||||||
name: Pre Checks
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Repo Check
|
|
||||||
id: repo_check
|
|
||||||
run: |
|
|
||||||
if [[ "$GITHUB_REPOSITORY" != "DLmaster361/AUTO_MAA" ]]; then
|
|
||||||
echo "When forking this repository to make your own builds, you have to adjust this check."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
exit 0
|
|
||||||
build_AUTO_MAA:
|
|
||||||
runs-on: windows-latest
|
|
||||||
needs: pre_check
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
- name: Set up Python 3.12
|
|
||||||
uses: actions/setup-python@v5
|
|
||||||
with:
|
|
||||||
python-version: "3.12"
|
|
||||||
- name: Install dependencies
|
|
||||||
run: |
|
|
||||||
python -m pip install --upgrade pip
|
|
||||||
pip install flake8 pytest
|
|
||||||
pip install -r requirements.txt
|
|
||||||
- name: Lint with flake8
|
|
||||||
run: |
|
|
||||||
# stop the build if there are Python syntax errors or undefined names
|
|
||||||
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
|
|
||||||
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
|
|
||||||
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
|
|
||||||
- name: Package
|
|
||||||
id: package
|
|
||||||
run: |
|
|
||||||
copy app\utils\package.py .\
|
|
||||||
python package.py
|
|
||||||
- name: Read version
|
|
||||||
id: read_version
|
|
||||||
run: |
|
|
||||||
$MAIN_VERSION=(Get-Content -Path "version_info.txt" -TotalCount 1).Trim()
|
|
||||||
"AUTO_MAA_version=$MAIN_VERSION" | Out-File -FilePath $env:GITHUB_ENV -Append
|
|
||||||
$UPDATER_VERSION=(Get-Content -Path "version_info.txt" -TotalCount 2 | Select-Object -Index 1).Trim()
|
|
||||||
"updater_version=$UPDATER_VERSION" | Out-File -FilePath $env:GITHUB_ENV -Append
|
|
||||||
- name: Create Zip
|
|
||||||
id: create_zip
|
|
||||||
run: |
|
|
||||||
Compress-Archive -Path app,resources,main.py,AUTO_MAA.exe,requirements.txt,README.md,LICENSE -DestinationPath AUTO_MAA_${{ env.AUTO_MAA_version }}.zip
|
|
||||||
Compress-Archive -Path Updater.exe -DestinationPath Updater_${{ env.updater_version }}.zip
|
|
||||||
- name: Upload Artifact
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: AUTO_MAA_${{ env.AUTO_MAA_version }}
|
|
||||||
path: |
|
|
||||||
AUTO_MAA_${{ env.AUTO_MAA_version }}.zip
|
|
||||||
Updater_${{ env.updater_version }}.zip
|
|
||||||
- name: Upload Version_Info Artifact
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: version_info
|
|
||||||
path: version_info.txt
|
|
||||||
publish_prerelease:
|
|
||||||
name: Publish prerelease
|
|
||||||
needs: build_AUTO_MAA
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
- name: Download artifacts
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
pattern: AUTO_MAA_*
|
|
||||||
merge-multiple: true
|
|
||||||
path: artifacts
|
|
||||||
- name: Download Version_Info
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: version_info
|
|
||||||
path: ./
|
|
||||||
- name: Check if release exists
|
|
||||||
id: check_if_release_exists
|
|
||||||
run: |
|
|
||||||
release_id=$(gh release view $(sed 's/\r$//g' <(head -n 1 version_info.txt)) --json id --jq .id || true)
|
|
||||||
if [[ -z $release_id ]]; then
|
|
||||||
echo "release_exists=false" >> $GITHUB_OUTPUT
|
|
||||||
else
|
|
||||||
echo "release_exists=true" >> $GITHUB_OUTPUT
|
|
||||||
fi
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.WORKFLOW_TOKEN }}
|
|
||||||
- name: Create prerelease
|
|
||||||
id: create_prerelease
|
|
||||||
if: steps.check_if_release_exists.outputs.release_exists == 'false'
|
|
||||||
run: |
|
|
||||||
set -xe
|
|
||||||
shopt -s nullglob
|
|
||||||
NAME="$(sed 's/\r$//g' <(head -n 1 version_info.txt))"
|
|
||||||
TAGNAME="$(sed 's/\r$//g' <(head -n 1 version_info.txt))"
|
|
||||||
NOTES_MAIN="$(sed 's/\r$//g' <(tail -n +3 version_info.txt))"
|
|
||||||
NOTES_TAIL="\`\`\`本release通过GitHub Actions自动构建\`\`\`"
|
|
||||||
NOTES="$NOTES_MAIN<br><br>$NOTES_TAIL"
|
|
||||||
gh release create "$TAGNAME" --target "main" --title "$NAME" --notes "$NOTES" --prerelease artifacts/*
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.WORKFLOW_TOKEN }}
|
|
||||||
- name: Update prerelease
|
|
||||||
id: update_prerelease
|
|
||||||
if: steps.check_if_release_exists.outputs.release_exists == 'true'
|
|
||||||
run: |
|
|
||||||
set -xe
|
|
||||||
shopt -s nullglob
|
|
||||||
NAME="$(sed 's/\r$//g' <(head -n 1 version_info.txt))"
|
|
||||||
TAGNAME="$(sed 's/\r$//g' <(head -n 1 version_info.txt))"
|
|
||||||
NOTES_MAIN="$(sed 's/\r$//g' <(tail -n +3 version_info.txt))"
|
|
||||||
NOTES_TAIL="\`\`\`本release通过GitHub Actions自动构建\`\`\`"
|
|
||||||
NOTES="$NOTES_MAIN<br><br>$NOTES_TAIL"
|
|
||||||
gh release delete "$TAGNAME" --yes
|
|
||||||
gh release create "$TAGNAME" --target "main" --title "$NAME" --notes "$NOTES" --prerelease artifacts/*
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.WORKFLOW_TOKEN }}
|
|
||||||
- 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/
|
|
||||||
21
.github/workflows/mirrorchyan.yml
vendored
Normal file
21
.github/workflows/mirrorchyan.yml
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
name: mirrorchyan
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
mirrorchyan:
|
||||||
|
runs-on: macos-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- id: uploading
|
||||||
|
uses: MirrorChyan/uploading-action@v1
|
||||||
|
with:
|
||||||
|
filetype: latest-release
|
||||||
|
filename: "AUTO_MAA*.zip"
|
||||||
|
mirrorchyan_rid: AUTO_MAA
|
||||||
|
|
||||||
|
owner: DLmaster361
|
||||||
|
repo: AUTO_MAA
|
||||||
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
upload_token: ${{ secrets.MirrorChyanUploadToken }}
|
||||||
19
.github/workflows/mirrorchyan_release_note.yml
vendored
Normal file
19
.github/workflows/mirrorchyan_release_note.yml
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
name: mirrorchyan_release_note
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
release:
|
||||||
|
types: [edited]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
mirrorchyan:
|
||||||
|
runs-on: macos-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- id: uploading
|
||||||
|
uses: MirrorChyan/release-note-action@v1
|
||||||
|
with:
|
||||||
|
mirrorchyan_rid: AUTO_MAA
|
||||||
|
|
||||||
|
upload_token: ${{ secrets.MirrorChyanUploadToken }}
|
||||||
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
__pycache__/
|
||||||
|
config/
|
||||||
|
data/
|
||||||
|
debug/
|
||||||
|
history/
|
||||||
|
resources/notice.json
|
||||||
|
resources/theme_image.json
|
||||||
|
resources/images/Home/BannerTheme.jpg
|
||||||
29
README.md
29
README.md
@@ -1,19 +1,20 @@
|
|||||||
# 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/issues)
|
<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/graphs/contributors)
|
<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/blob/main/LICENSE)
|
<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>
|
||||||
</div>
|
<a href="https://mirrorchyan.com/zh/projects?rid=AUTO_MAA"><img alt="mirrorc" src="https://img.shields.io/badge/Mirror%E9%85%B1-%239af3f6?logo=countingworkspro&logoColor=4f46e5"></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
## 软件介绍
|
## 软件介绍
|
||||||
|
|
||||||
@@ -98,4 +99,4 @@ MAA多账号管理与自动化软件
|
|||||||
|
|
||||||
如果喜欢这个项目的话,给作者来杯咖啡吧!
|
如果喜欢这个项目的话,给作者来杯咖啡吧!
|
||||||
|
|
||||||

|

|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# <AUTO_MAA:A MAA Multi Account Management and Automation Tool>
|
# AUTO_MAA:A MAA Multi Account Management and Automation Tool
|
||||||
# Copyright © <2024> <DLmaster361>
|
# Copyright © 2024-2025 DLmaster361
|
||||||
|
|
||||||
# This file is part of AUTO_MAA.
|
# This file is part of AUTO_MAA.
|
||||||
|
|
||||||
@@ -16,12 +16,12 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
# DLmaster_361@163.com
|
# Contact: DLmaster_361@163.com
|
||||||
|
|
||||||
"""
|
"""
|
||||||
AUTO_MAA
|
AUTO_MAA
|
||||||
AUTO_MAA主程序包
|
AUTO_MAA主程序包
|
||||||
v4.2
|
v4.3
|
||||||
作者:DLmaster_361
|
作者:DLmaster_361
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -29,16 +29,16 @@ __version__ = "4.2.0"
|
|||||||
__author__ = "DLmaster361 <DLmaster_361@163.com>"
|
__author__ = "DLmaster361 <DLmaster_361@163.com>"
|
||||||
__license__ = "GPL-3.0 license"
|
__license__ = "GPL-3.0 license"
|
||||||
|
|
||||||
from .core import AppConfig, QueueConfig, MaaConfig, Task, TaskManager, MainTimer
|
from .core import QueueConfig, MaaConfig, MaaUserConfig, Task, TaskManager, MainTimer
|
||||||
from .models import MaaManager
|
from .models import MaaManager
|
||||||
from .services import Notify, Crypto, System
|
from .services import Notify, Crypto, System
|
||||||
from .ui import AUTO_MAA
|
from .ui import AUTO_MAA
|
||||||
from .utils import Updater
|
from .utils import DownloadManager
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"AppConfig",
|
|
||||||
"QueueConfig",
|
"QueueConfig",
|
||||||
"MaaConfig",
|
"MaaConfig",
|
||||||
|
"MaaUserConfig",
|
||||||
"Task",
|
"Task",
|
||||||
"TaskManager",
|
"TaskManager",
|
||||||
"MainTimer",
|
"MainTimer",
|
||||||
@@ -47,5 +47,5 @@ __all__ = [
|
|||||||
"Crypto",
|
"Crypto",
|
||||||
"System",
|
"System",
|
||||||
"AUTO_MAA",
|
"AUTO_MAA",
|
||||||
"Updater",
|
"DownloadManager",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# <AUTO_MAA:A MAA Multi Account Management and Automation Tool>
|
# AUTO_MAA:A MAA Multi Account Management and Automation Tool
|
||||||
# Copyright © <2024> <DLmaster361>
|
# Copyright © 2024-2025 DLmaster361
|
||||||
|
|
||||||
# This file is part of AUTO_MAA.
|
# This file is part of AUTO_MAA.
|
||||||
|
|
||||||
@@ -16,12 +16,12 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
# DLmaster_361@163.com
|
# Contact: DLmaster_361@163.com
|
||||||
|
|
||||||
"""
|
"""
|
||||||
AUTO_MAA
|
AUTO_MAA
|
||||||
AUTO_MAA核心组件包
|
AUTO_MAA核心组件包
|
||||||
v4.2
|
v4.3
|
||||||
作者:DLmaster_361
|
作者:DLmaster_361
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -29,17 +29,19 @@ __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 AppConfig, QueueConfig, MaaConfig, Config
|
from .config import QueueConfig, MaaConfig, MaaUserConfig, Config
|
||||||
from .main_info_bar import MainInfoBar
|
from .main_info_bar import MainInfoBar
|
||||||
|
from .network import Network
|
||||||
from .task_manager import Task, TaskManager
|
from .task_manager import Task, TaskManager
|
||||||
from .timer import MainTimer
|
from .timer import MainTimer
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"AppConfig",
|
|
||||||
"Config",
|
"Config",
|
||||||
"QueueConfig",
|
"QueueConfig",
|
||||||
"MaaConfig",
|
"MaaConfig",
|
||||||
|
"MaaUserConfig",
|
||||||
"MainInfoBar",
|
"MainInfoBar",
|
||||||
|
"Network",
|
||||||
"Task",
|
"Task",
|
||||||
"TaskManager",
|
"TaskManager",
|
||||||
"MainTimer",
|
"MainTimer",
|
||||||
|
|||||||
1470
app/core/config.py
1470
app/core/config.py
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
|||||||
# <AUTO_MAA:A MAA Multi Account Management and Automation Tool>
|
# AUTO_MAA:A MAA Multi Account Management and Automation Tool
|
||||||
# Copyright © <2024> <DLmaster361>
|
# Copyright © 2024-2025 DLmaster361
|
||||||
|
|
||||||
# This file is part of AUTO_MAA.
|
# This file is part of AUTO_MAA.
|
||||||
|
|
||||||
@@ -16,77 +16,53 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
# DLmaster_361@163.com
|
# Contact: DLmaster_361@163.com
|
||||||
|
|
||||||
"""
|
"""
|
||||||
AUTO_MAA
|
AUTO_MAA
|
||||||
AUTO_MAA信息通知栏
|
AUTO_MAA信息通知栏
|
||||||
v4.2
|
v4.3
|
||||||
作者:DLmaster_361
|
作者:DLmaster_361
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from PySide6.QtCore import Qt
|
from PySide6.QtCore import Qt
|
||||||
from qfluentwidgets import (
|
from qfluentwidgets import InfoBar, InfoBarPosition
|
||||||
InfoBar,
|
|
||||||
InfoBarPosition,
|
from .config import Config
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class _MainInfoBar:
|
class _MainInfoBar:
|
||||||
"""信息通知栏"""
|
"""信息通知栏"""
|
||||||
|
|
||||||
def __init__(self, main_window=None):
|
|
||||||
|
|
||||||
self.main_window = main_window
|
|
||||||
|
|
||||||
def push_info_bar(self, mode: str, title: str, content: str, time: int):
|
def push_info_bar(self, mode: str, title: str, content: str, time: int):
|
||||||
"""推送到信息通知栏"""
|
"""推送到信息通知栏"""
|
||||||
|
if Config.main_window is None:
|
||||||
if self.main_window is None:
|
|
||||||
logger.error("信息通知栏未设置父窗口")
|
logger.error("信息通知栏未设置父窗口")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if mode == "success":
|
# 定义模式到 InfoBar 方法的映射
|
||||||
InfoBar.success(
|
mode_mapping = {
|
||||||
|
"success": InfoBar.success,
|
||||||
|
"warning": InfoBar.warning,
|
||||||
|
"error": InfoBar.error,
|
||||||
|
"info": InfoBar.info,
|
||||||
|
}
|
||||||
|
|
||||||
|
# 根据 mode 获取对应的 InfoBar 方法
|
||||||
|
info_bar_method = mode_mapping.get(mode)
|
||||||
|
if info_bar_method:
|
||||||
|
info_bar_method(
|
||||||
title=title,
|
title=title,
|
||||||
content=content,
|
content=content,
|
||||||
orient=Qt.Horizontal,
|
orient=Qt.Horizontal,
|
||||||
isClosable=True,
|
isClosable=True,
|
||||||
position=InfoBarPosition.TOP_RIGHT,
|
position=InfoBarPosition.TOP_RIGHT,
|
||||||
duration=time,
|
duration=time,
|
||||||
parent=self.main_window,
|
parent=Config.main_window,
|
||||||
)
|
|
||||||
elif mode == "warning":
|
|
||||||
InfoBar.warning(
|
|
||||||
title=title,
|
|
||||||
content=content,
|
|
||||||
orient=Qt.Horizontal,
|
|
||||||
isClosable=True,
|
|
||||||
position=InfoBarPosition.TOP_RIGHT,
|
|
||||||
duration=time,
|
|
||||||
parent=self.main_window,
|
|
||||||
)
|
|
||||||
elif mode == "error":
|
|
||||||
InfoBar.error(
|
|
||||||
title=title,
|
|
||||||
content=content,
|
|
||||||
orient=Qt.Horizontal,
|
|
||||||
isClosable=True,
|
|
||||||
position=InfoBarPosition.TOP_RIGHT,
|
|
||||||
duration=time,
|
|
||||||
parent=self.main_window,
|
|
||||||
)
|
|
||||||
elif mode == "info":
|
|
||||||
InfoBar.info(
|
|
||||||
title=title,
|
|
||||||
content=content,
|
|
||||||
orient=Qt.Horizontal,
|
|
||||||
isClosable=True,
|
|
||||||
position=InfoBarPosition.TOP_RIGHT,
|
|
||||||
duration=time,
|
|
||||||
parent=self.main_window,
|
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
logger.error(f"未知的通知栏模式: {mode}")
|
||||||
|
|
||||||
|
|
||||||
MainInfoBar = _MainInfoBar()
|
MainInfoBar = _MainInfoBar()
|
||||||
|
|||||||
120
app/core/network.py
Normal file
120
app/core/network.py
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
# AUTO_MAA:A MAA Multi Account Management and Automation Tool
|
||||||
|
# Copyright © 2024-2025 DLmaster361
|
||||||
|
|
||||||
|
# This file is part of AUTO_MAA.
|
||||||
|
|
||||||
|
# AUTO_MAA is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published
|
||||||
|
# by the Free Software Foundation, either version 3 of the License,
|
||||||
|
# or (at your option) any later version.
|
||||||
|
|
||||||
|
# AUTO_MAA is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||||
|
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
|
||||||
|
# the GNU General Public License for more details.
|
||||||
|
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
# Contact: DLmaster_361@163.com
|
||||||
|
|
||||||
|
"""
|
||||||
|
AUTO_MAA
|
||||||
|
AUTO_MAA网络请求线程
|
||||||
|
v4.3
|
||||||
|
作者:DLmaster_361
|
||||||
|
"""
|
||||||
|
|
||||||
|
from loguru import logger
|
||||||
|
from PySide6.QtCore import QThread, QEventLoop, QTimer
|
||||||
|
import time
|
||||||
|
import requests
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
class _Network(QThread):
|
||||||
|
|
||||||
|
max_retries = 3
|
||||||
|
timeout = 10
|
||||||
|
backoff_factor = 0.1
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
self.if_running = False
|
||||||
|
self.mode = None
|
||||||
|
self.url = None
|
||||||
|
self.loop = QEventLoop()
|
||||||
|
self.wait_loop = QEventLoop()
|
||||||
|
|
||||||
|
@logger.catch
|
||||||
|
def run(self) -> None:
|
||||||
|
"""运行网络请求线程"""
|
||||||
|
|
||||||
|
self.if_running = True
|
||||||
|
|
||||||
|
if self.mode == "get":
|
||||||
|
self.get_json(self.url)
|
||||||
|
elif self.mode == "get_file":
|
||||||
|
self.get_file(self.url, self.path)
|
||||||
|
|
||||||
|
self.if_running = False
|
||||||
|
|
||||||
|
def set_info(self, mode: str, url: str, path: Path = None) -> None:
|
||||||
|
"""设置网络请求信息"""
|
||||||
|
|
||||||
|
while self.if_running:
|
||||||
|
QTimer.singleShot(self.backoff_factor * 1000, self.wait_loop.quit)
|
||||||
|
self.wait_loop.exec()
|
||||||
|
|
||||||
|
self.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:
|
||||||
|
"""通过get方法获取json数据"""
|
||||||
|
|
||||||
|
response = None
|
||||||
|
|
||||||
|
for _ in range(self.max_retries):
|
||||||
|
try:
|
||||||
|
response = requests.get(url, timeout=self.timeout)
|
||||||
|
self.stutus_code = response.status_code
|
||||||
|
self.response_json = response.json()
|
||||||
|
self.error_message = None
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
self.stutus_code = response.status_code if response else None
|
||||||
|
self.response_json = None
|
||||||
|
self.error_message = str(e)
|
||||||
|
time.sleep(self.backoff_factor)
|
||||||
|
|
||||||
|
self.loop.quit()
|
||||||
|
|
||||||
|
def get_file(self, url: str, path: Path) -> None:
|
||||||
|
"""通过get方法下载文件"""
|
||||||
|
|
||||||
|
response = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.get(url, timeout=10)
|
||||||
|
if response.status_code == 200:
|
||||||
|
with open(path, "wb") as file:
|
||||||
|
file.write(response.content)
|
||||||
|
self.stutus_code = response.status_code
|
||||||
|
else:
|
||||||
|
self.stutus_code = response.status_code
|
||||||
|
self.error_message = "下载失败"
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.stutus_code = response.status_code if response else None
|
||||||
|
self.error_message = str(e)
|
||||||
|
|
||||||
|
self.loop.quit()
|
||||||
|
|
||||||
|
|
||||||
|
Network = _Network()
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
# <AUTO_MAA:A MAA Multi Account Management and Automation Tool>
|
# AUTO_MAA:A MAA Multi Account Management and Automation Tool
|
||||||
# Copyright © <2024> <DLmaster361>
|
# Copyright © 2024-2025 DLmaster361
|
||||||
|
|
||||||
# This file is part of AUTO_MAA.
|
# This file is part of AUTO_MAA.
|
||||||
|
|
||||||
@@ -16,25 +16,25 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
# DLmaster_361@163.com
|
# Contact: DLmaster_361@163.com
|
||||||
|
|
||||||
"""
|
"""
|
||||||
AUTO_MAA
|
AUTO_MAA
|
||||||
AUTO_MAA业务调度器
|
AUTO_MAA业务调度器
|
||||||
v4.2
|
v4.3
|
||||||
作者:DLmaster_361
|
作者:DLmaster_361
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from PySide6.QtCore import QThread, QObject, Signal
|
from PySide6.QtCore import QThread, QObject, Signal
|
||||||
from qfluentwidgets import Dialog
|
from qfluentwidgets import MessageBox
|
||||||
import json
|
|
||||||
from pathlib import Path
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from packaging import version
|
||||||
from typing import Dict, Union
|
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 app.models import MaaManager
|
from app.models import MaaManager
|
||||||
from app.services import System
|
from app.services import System
|
||||||
|
|
||||||
@@ -42,10 +42,11 @@ from app.services import System
|
|||||||
class Task(QThread):
|
class Task(QThread):
|
||||||
"""业务线程"""
|
"""业务线程"""
|
||||||
|
|
||||||
|
check_maa_version = Signal(str)
|
||||||
push_info_bar = Signal(str, str, str, int)
|
push_info_bar = Signal(str, str, str, int)
|
||||||
question = Signal(str, str)
|
question = Signal(str, str)
|
||||||
question_response = Signal(bool)
|
question_response = Signal(bool)
|
||||||
update_user_info = Signal(Path, list, list, list, list, list, list)
|
update_user_info = Signal(str, dict)
|
||||||
create_task_list = Signal(list)
|
create_task_list = Signal(list)
|
||||||
create_user_list = Signal(list)
|
create_user_list = Signal(list)
|
||||||
update_task_list = Signal(list)
|
update_task_list = Signal(list)
|
||||||
@@ -66,6 +67,7 @@ class Task(QThread):
|
|||||||
|
|
||||||
self.question_response.connect(lambda: print("response"))
|
self.question_response.connect(lambda: print("response"))
|
||||||
|
|
||||||
|
@logger.catch
|
||||||
def run(self):
|
def run(self):
|
||||||
|
|
||||||
if "设置MAA" in self.mode:
|
if "设置MAA" in self.mode:
|
||||||
@@ -75,14 +77,10 @@ class Task(QThread):
|
|||||||
|
|
||||||
self.task = MaaManager(
|
self.task = MaaManager(
|
||||||
self.mode,
|
self.mode,
|
||||||
Config.app_path / f"config/MaaConfig/{self.name}",
|
Config.member_dict[self.name],
|
||||||
(
|
(None if "全局" in self.mode else self.info["SetMaaInfo"]["Path"]),
|
||||||
None
|
|
||||||
if "全局" in self.mode
|
|
||||||
else Config.app_path
|
|
||||||
/ f"config/MaaConfig/{self.name}/beta/{self.info["SetMaaInfo"]["UserId"]}/{self.info["SetMaaInfo"]["SetType"]}"
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
self.task.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.accomplish.connect(lambda: self.accomplish.emit([]))
|
self.task.accomplish.connect(lambda: self.accomplish.emit([]))
|
||||||
|
|
||||||
@@ -90,44 +88,55 @@ class Task(QThread):
|
|||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
self.member_dict = self.search_member()
|
self.task_list = [
|
||||||
self.task_dict = [
|
[
|
||||||
[value, "等待"]
|
(
|
||||||
for _, value in self.info["Queue"].items()
|
value
|
||||||
|
if Config.member_dict[value]["Config"].get(
|
||||||
|
Config.member_dict[value]["Config"].MaaSet_Name
|
||||||
|
)
|
||||||
|
== ""
|
||||||
|
else f"{value} - {Config.member_dict[value]["Config"].get(Config.member_dict[value]["Config"].MaaSet_Name)}"
|
||||||
|
),
|
||||||
|
"等待",
|
||||||
|
value,
|
||||||
|
]
|
||||||
|
for _, value in sorted(
|
||||||
|
self.info["Queue"].items(), key=lambda x: int(x[0][7:])
|
||||||
|
)
|
||||||
if value != "禁用"
|
if value != "禁用"
|
||||||
]
|
]
|
||||||
|
|
||||||
self.create_task_list.emit(self.task_dict)
|
self.create_task_list.emit(self.task_list)
|
||||||
|
|
||||||
for i in range(len(self.task_dict)):
|
for task in self.task_list:
|
||||||
|
|
||||||
if self.isInterruptionRequested():
|
if self.isInterruptionRequested():
|
||||||
break
|
break
|
||||||
|
|
||||||
self.task_dict[i][1] = "运行"
|
task[1] = "运行"
|
||||||
self.update_task_list.emit(self.task_dict)
|
self.update_task_list.emit(self.task_list)
|
||||||
|
|
||||||
if self.task_dict[i][0] in Config.running_list:
|
if task[2] in Config.running_list:
|
||||||
|
|
||||||
self.task_dict[i][1] = "跳过"
|
task[1] = "跳过"
|
||||||
self.update_task_list.emit(self.task_dict)
|
self.update_task_list.emit(self.task_list)
|
||||||
logger.info(f"跳过任务:{self.task_dict[i][0]}")
|
logger.info(f"跳过任务:{task[0]}")
|
||||||
self.push_info_bar.emit(
|
self.push_info_bar.emit("info", "跳过任务", task[0], 3000)
|
||||||
"info", "跳过任务", self.task_dict[i][0], 3000
|
|
||||||
)
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
Config.running_list.append(self.task_dict[i][0])
|
Config.running_list.append(task[2])
|
||||||
logger.info(f"任务开始:{self.task_dict[i][0]}")
|
logger.info(f"任务开始:{task[0]}")
|
||||||
self.push_info_bar.emit("info", "任务开始", self.task_dict[i][0], 3000)
|
self.push_info_bar.emit("info", "任务开始", task[0], 3000)
|
||||||
|
|
||||||
if self.member_dict[self.task_dict[i][0]][0] == "Maa":
|
if Config.member_dict[task[2]]["Type"] == "Maa":
|
||||||
|
|
||||||
self.task = MaaManager(
|
self.task = MaaManager(
|
||||||
self.mode[0:4],
|
self.mode[0:4],
|
||||||
self.member_dict[self.task_dict[i][0]][1],
|
Config.member_dict[task[2]],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.task.check_maa_version.connect(self.check_maa_version.emit)
|
||||||
self.task.question.connect(self.question.emit)
|
self.task.question.connect(self.question.emit)
|
||||||
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)
|
||||||
@@ -135,44 +144,22 @@ class Task(QThread):
|
|||||||
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)
|
||||||
self.task.update_user_info.connect(
|
self.task.update_user_info.connect(self.update_user_info.emit)
|
||||||
lambda modes, uids, days, lasts, notes, numbs: self.update_user_info.emit(
|
|
||||||
self.member_dict[self.task_dict[i][0]][1],
|
|
||||||
modes,
|
|
||||||
uids,
|
|
||||||
days,
|
|
||||||
lasts,
|
|
||||||
notes,
|
|
||||||
numbs,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
self.task.accomplish.connect(
|
self.task.accomplish.connect(
|
||||||
lambda log: self.task_accomplish(self.task_dict[i][0], log)
|
lambda log: self.task_accomplish(task[2], log)
|
||||||
)
|
)
|
||||||
|
|
||||||
self.task.run()
|
self.task.run()
|
||||||
|
|
||||||
Config.running_list.remove(self.task_dict[i][0])
|
Config.running_list.remove(task[2])
|
||||||
|
|
||||||
self.task_dict[i][1] = "完成"
|
task[1] = "完成"
|
||||||
logger.info(f"任务完成:{self.task_dict[i][0]}")
|
self.update_task_list.emit(self.task_list)
|
||||||
self.push_info_bar.emit("info", "任务完成", self.task_dict[i][0], 3000)
|
logger.info(f"任务完成:{task[0]}")
|
||||||
|
self.push_info_bar.emit("info", "任务完成", task[0], 3000)
|
||||||
|
|
||||||
self.accomplish.emit(self.logs)
|
self.accomplish.emit(self.logs)
|
||||||
|
|
||||||
def search_member(self) -> dict:
|
|
||||||
"""搜索所有脚本实例及其路径"""
|
|
||||||
|
|
||||||
member_dict = {}
|
|
||||||
|
|
||||||
if (Config.app_path / "config/MaaConfig").exists():
|
|
||||||
for subdir in (Config.app_path / "config/MaaConfig").iterdir():
|
|
||||||
if subdir.is_dir():
|
|
||||||
|
|
||||||
member_dict[subdir.name] = ["Maa", subdir]
|
|
||||||
|
|
||||||
return member_dict
|
|
||||||
|
|
||||||
def task_accomplish(self, name: str, log: dict):
|
def task_accomplish(self, name: str, log: dict):
|
||||||
"""保存保存任务结果"""
|
"""保存保存任务结果"""
|
||||||
|
|
||||||
@@ -185,7 +172,6 @@ class _TaskManager(QObject):
|
|||||||
|
|
||||||
create_gui = Signal(Task)
|
create_gui = Signal(Task)
|
||||||
connect_gui = Signal(Task)
|
connect_gui = Signal(Task)
|
||||||
push_info_bar = Signal(str, str, str, int)
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(_TaskManager, self).__init__()
|
super(_TaskManager, self).__init__()
|
||||||
@@ -208,6 +194,7 @@ class _TaskManager(QObject):
|
|||||||
|
|
||||||
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)
|
||||||
|
self.task_dict[name].check_maa_version.connect(self.check_maa_version)
|
||||||
self.task_dict[name].question.connect(
|
self.task_dict[name].question.connect(
|
||||||
lambda title, content: self.push_dialog(name, title, content)
|
lambda title, content: self.push_dialog(name, title, content)
|
||||||
)
|
)
|
||||||
@@ -247,50 +234,101 @@ class _TaskManager(QObject):
|
|||||||
self.task_dict[name].quit()
|
self.task_dict[name].quit()
|
||||||
self.task_dict[name].wait()
|
self.task_dict[name].wait()
|
||||||
|
|
||||||
def remove_task(self, mode: str, name: str, logs: str):
|
def remove_task(self, mode: str, name: str, logs: list):
|
||||||
"""任务结束后的处理"""
|
"""任务结束后的处理"""
|
||||||
|
|
||||||
logger.info(f"任务结束:{name}")
|
logger.info(f"任务结束:{name}")
|
||||||
MainInfoBar.push_info_bar("info", "任务结束", name, 3000)
|
MainInfoBar.push_info_bar("info", "任务结束", name, 3000)
|
||||||
|
|
||||||
self.task_dict[name].deleteLater()
|
self.task_dict[name].deleteLater()
|
||||||
|
|
||||||
if len(logs) > 0:
|
|
||||||
time = logs[0][1]["Time"]
|
|
||||||
history = ""
|
|
||||||
for log in logs:
|
|
||||||
Config.save_history(log[0], log[1])
|
|
||||||
history += (
|
|
||||||
f"任务名称:{log[0]},{log[1]["History"].replace("\n","\n ")}\n"
|
|
||||||
)
|
|
||||||
Config.save_history(name, {"Time": time, "History": history})
|
|
||||||
else:
|
|
||||||
Config.save_history(
|
|
||||||
name,
|
|
||||||
{
|
|
||||||
"Time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
|
||||||
"History": "没有任务被执行",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
self.task_dict.pop(name)
|
self.task_dict.pop(name)
|
||||||
Config.running_list.remove(name)
|
Config.running_list.remove(name)
|
||||||
|
|
||||||
if "调度队列" in name and "人工排查" not in mode:
|
if "调度队列" in name and "人工排查" not in mode:
|
||||||
with (Config.app_path / f"config/QueueConfig/{name}.json").open(
|
|
||||||
"r", encoding="utf-8"
|
if len(logs) > 0:
|
||||||
) as f:
|
time = logs[0][1]["Time"]
|
||||||
info = json.load(f)
|
history = ""
|
||||||
System.set_power(info["QueueSet"]["AfterAccomplish"])
|
for log in logs:
|
||||||
|
history += f"任务名称:{log[0]},{log[1]["History"].replace("\n","\n ")}\n"
|
||||||
|
Config.save_history(name, {"Time": time, "History": history})
|
||||||
|
else:
|
||||||
|
Config.save_history(
|
||||||
|
name,
|
||||||
|
{
|
||||||
|
"Time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
|
"History": "没有任务被执行",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if (
|
||||||
|
Config.queue_dict[name]["Config"].get(
|
||||||
|
Config.queue_dict[name]["Config"].queueSet_AfterAccomplish
|
||||||
|
)
|
||||||
|
!= "None"
|
||||||
|
):
|
||||||
|
|
||||||
|
from app.ui import ProgressRingMessageBox
|
||||||
|
|
||||||
|
mode_book = {
|
||||||
|
"Shutdown": "关机",
|
||||||
|
"Hibernate": "休眠",
|
||||||
|
"Sleep": "睡眠",
|
||||||
|
"KillSelf": "关闭AUTO_MAA",
|
||||||
|
}
|
||||||
|
|
||||||
|
choice = ProgressRingMessageBox(
|
||||||
|
Config.main_window,
|
||||||
|
f"{mode_book[Config.queue_dict[name]["Config"].get(Config.queue_dict[name]["Config"].queueSet_AfterAccomplish)]}倒计时",
|
||||||
|
)
|
||||||
|
if choice.exec():
|
||||||
|
System.set_power(
|
||||||
|
Config.queue_dict[name]["Config"].get(
|
||||||
|
Config.queue_dict[name]["Config"].queueSet_AfterAccomplish
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def check_maa_version(self, v: str):
|
||||||
|
"""检查MAA版本"""
|
||||||
|
|
||||||
|
Network.set_info(
|
||||||
|
mode="get",
|
||||||
|
url="https://mirrorchyan.com/api/resources/MAA/latest?user_agent=AutoMaaGui&os=win&arch=x64&channel=stable",
|
||||||
|
)
|
||||||
|
Network.start()
|
||||||
|
Network.loop.exec()
|
||||||
|
if Network.stutus_code == 200:
|
||||||
|
maa_info = Network.response_json
|
||||||
|
else:
|
||||||
|
logger.warning(f"获取MAA版本信息时出错:{Network.error_message}")
|
||||||
|
MainInfoBar.push_info_bar(
|
||||||
|
"warning",
|
||||||
|
"获取MAA版本信息时出错",
|
||||||
|
f"网络错误:{Network.stutus_code}",
|
||||||
|
5000,
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
|
if version.parse(maa_info["data"]["version_name"]) > version.parse(v):
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
f"检测到MAA版本过低:{v},最新版本:{maa_info['data']['version_name']}"
|
||||||
|
)
|
||||||
|
MainInfoBar.push_info_bar(
|
||||||
|
"info",
|
||||||
|
"MAA版本过低",
|
||||||
|
f"当前版本:{v},最新稳定版:{maa_info['data']['version_name']}",
|
||||||
|
-1,
|
||||||
|
)
|
||||||
|
|
||||||
def push_dialog(self, name: str, title: str, content: str):
|
def push_dialog(self, name: str, title: str, content: str):
|
||||||
"""推送对话框"""
|
"""推送对话框"""
|
||||||
|
|
||||||
choice = Dialog(title, content, None)
|
choice = MessageBox(title, content, Config.main_window)
|
||||||
choice.yesButton.setText("是")
|
choice.yesButton.setText("是")
|
||||||
choice.cancelButton.setText("否")
|
choice.cancelButton.setText("否")
|
||||||
|
|
||||||
self.task_dict[name].question_response.emit(bool(choice.exec_()))
|
self.task_dict[name].question_response.emit(bool(choice.exec()))
|
||||||
|
|
||||||
|
|
||||||
TaskManager = _TaskManager()
|
TaskManager = _TaskManager()
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# <AUTO_MAA:A MAA Multi Account Management and Automation Tool>
|
# AUTO_MAA:A MAA Multi Account Management and Automation Tool
|
||||||
# Copyright © <2024> <DLmaster361>
|
# Copyright © 2024-2025 DLmaster361
|
||||||
|
|
||||||
# This file is part of AUTO_MAA.
|
# This file is part of AUTO_MAA.
|
||||||
|
|
||||||
@@ -16,19 +16,18 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
# DLmaster_361@163.com
|
# Contact: DLmaster_361@163.com
|
||||||
|
|
||||||
"""
|
"""
|
||||||
AUTO_MAA
|
AUTO_MAA
|
||||||
AUTO_MAA主业务定时器
|
AUTO_MAA主业务定时器
|
||||||
v4.2
|
v4.3
|
||||||
作者:DLmaster_361
|
作者:DLmaster_361
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from PySide6.QtWidgets import QWidget
|
from PySide6.QtWidgets import QWidget
|
||||||
from PySide6.QtCore import QTimer
|
from PySide6.QtCore import QTimer
|
||||||
import json
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import pyautogui
|
import pyautogui
|
||||||
|
|
||||||
@@ -48,44 +47,52 @@ class _MainTimer(QWidget):
|
|||||||
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.start(1000)
|
self.Timer.start(1000)
|
||||||
|
self.LongTimer = QTimer()
|
||||||
|
self.LongTimer.timeout.connect(self.long_timed_task)
|
||||||
|
self.LongTimer.start(3600000)
|
||||||
|
|
||||||
|
def long_timed_task(self):
|
||||||
|
"""长时间定期检定任务"""
|
||||||
|
|
||||||
|
Config.get_gameid()
|
||||||
|
Config.main_window.setting.show_notice()
|
||||||
|
if Config.get(Config.update_IfAutoUpdate):
|
||||||
|
Config.main_window.setting.check_update()
|
||||||
|
|
||||||
def timed_start(self):
|
def timed_start(self):
|
||||||
"""定时启动代理任务"""
|
"""定时启动代理任务"""
|
||||||
|
|
||||||
# 获取定时列表
|
for name, info in Config.queue_dict.items():
|
||||||
queue_list = self.search_queue()
|
|
||||||
|
|
||||||
for i in queue_list:
|
if not info["Config"].get(info["Config"].queueSet_Enabled):
|
||||||
|
|
||||||
name, info = i
|
|
||||||
|
|
||||||
if not info["QueueSet"]["Enabled"]:
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
history = Config.get_history(name)
|
data = info["Config"].toDict()
|
||||||
|
|
||||||
time_set = [
|
time_set = [
|
||||||
info["Time"][f"TimeSet_{_}"]
|
data["Time"][f"TimeSet_{_}"]
|
||||||
for _ in range(10)
|
for _ in range(10)
|
||||||
if info["Time"][f"TimeEnabled_{_}"]
|
if data["Time"][f"TimeEnabled_{_}"]
|
||||||
]
|
]
|
||||||
# 按时间调起代理任务
|
# 按时间调起代理任务
|
||||||
curtime = datetime.now().strftime("%Y-%m-%d %H:%M")
|
curtime = datetime.now().strftime("%Y-%m-%d %H:%M")
|
||||||
if (
|
if (
|
||||||
curtime[11:16] in time_set
|
curtime[11:16] in time_set
|
||||||
and curtime != history["Time"][:16]
|
and curtime
|
||||||
|
!= info["Config"].get(info["Config"].Data_LastProxyTime)[:16]
|
||||||
and name not in Config.running_list
|
and name not in Config.running_list
|
||||||
):
|
):
|
||||||
|
|
||||||
logger.info(f"定时任务:{name}")
|
logger.info(f"定时任务:{name}")
|
||||||
TaskManager.add_task("自动代理_新调度台", name, info)
|
TaskManager.add_task("自动代理_新调度台", name, data)
|
||||||
|
|
||||||
def set_silence(self):
|
def set_silence(self):
|
||||||
"""设置静默模式"""
|
"""设置静默模式"""
|
||||||
|
|
||||||
if (
|
if (
|
||||||
Config.global_config.get(Config.global_config.function_IfSilence)
|
not Config.if_ignore_silence
|
||||||
and Config.global_config.get(Config.global_config.function_BossKey) != ""
|
and Config.get(Config.function_IfSilence)
|
||||||
|
and Config.get(Config.function_BossKey) != ""
|
||||||
):
|
):
|
||||||
|
|
||||||
windows = System.get_window_info()
|
windows = System.get_window_info()
|
||||||
@@ -98,9 +105,7 @@ class _MainTimer(QWidget):
|
|||||||
pyautogui.hotkey(
|
pyautogui.hotkey(
|
||||||
*[
|
*[
|
||||||
_.strip().lower()
|
_.strip().lower()
|
||||||
for _ in Config.global_config.get(
|
for _ in Config.get(Config.function_BossKey).split("+")
|
||||||
Config.global_config.function_BossKey
|
|
||||||
).split("+")
|
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
except pyautogui.FailSafeException as e:
|
except pyautogui.FailSafeException as e:
|
||||||
@@ -108,18 +113,5 @@ class _MainTimer(QWidget):
|
|||||||
logger.warning(f"FailSafeException: {e}")
|
logger.warning(f"FailSafeException: {e}")
|
||||||
self.if_FailSafeException = True
|
self.if_FailSafeException = True
|
||||||
|
|
||||||
def search_queue(self) -> list:
|
|
||||||
"""搜索所有调度队列实例"""
|
|
||||||
|
|
||||||
queue_list = []
|
|
||||||
|
|
||||||
if (Config.app_path / "config/QueueConfig").exists():
|
|
||||||
for json_file in (Config.app_path / "config/QueueConfig").glob("*.json"):
|
|
||||||
with json_file.open("r", encoding="utf-8") as f:
|
|
||||||
info = json.load(f)
|
|
||||||
queue_list.append([json_file.stem, info])
|
|
||||||
|
|
||||||
return queue_list
|
|
||||||
|
|
||||||
|
|
||||||
MainTimer = _MainTimer()
|
MainTimer = _MainTimer()
|
||||||
|
|||||||
1201
app/models/MAA.py
1201
app/models/MAA.py
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
|||||||
# <AUTO_MAA:A MAA Multi Account Management and Automation Tool>
|
# AUTO_MAA:A MAA Multi Account Management and Automation Tool
|
||||||
# Copyright © <2024> <DLmaster361>
|
# Copyright © 2024-2025 DLmaster361
|
||||||
|
|
||||||
# This file is part of AUTO_MAA.
|
# This file is part of AUTO_MAA.
|
||||||
|
|
||||||
@@ -16,12 +16,12 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
# DLmaster_361@163.com
|
# Contact: DLmaster_361@163.com
|
||||||
|
|
||||||
"""
|
"""
|
||||||
AUTO_MAA
|
AUTO_MAA
|
||||||
AUTO_MAA模组包
|
AUTO_MAA模组包
|
||||||
v4.2
|
v4.3
|
||||||
作者:DLmaster_361
|
作者:DLmaster_361
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# <AUTO_MAA:A MAA Multi Account Management and Automation Tool>
|
# AUTO_MAA:A MAA Multi Account Management and Automation Tool
|
||||||
# Copyright © <2024> <DLmaster361>
|
# Copyright © 2024-2025 DLmaster361
|
||||||
|
|
||||||
# This file is part of AUTO_MAA.
|
# This file is part of AUTO_MAA.
|
||||||
|
|
||||||
@@ -16,12 +16,12 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
# DLmaster_361@163.com
|
# Contact: DLmaster_361@163.com
|
||||||
|
|
||||||
"""
|
"""
|
||||||
AUTO_MAA
|
AUTO_MAA
|
||||||
AUTO_MAA服务包
|
AUTO_MAA服务包
|
||||||
v4.2
|
v4.3
|
||||||
作者:DLmaster_361
|
作者:DLmaster_361
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# <AUTO_MAA:A MAA Multi Account Management and Automation Tool>
|
# AUTO_MAA:A MAA Multi Account Management and Automation Tool
|
||||||
# Copyright © <2024> <DLmaster361>
|
# Copyright © 2024-2025 DLmaster361
|
||||||
|
|
||||||
# This file is part of AUTO_MAA.
|
# This file is part of AUTO_MAA.
|
||||||
|
|
||||||
@@ -16,18 +16,19 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
# DLmaster_361@163.com
|
# Contact: DLmaster_361@163.com
|
||||||
|
|
||||||
"""
|
"""
|
||||||
AUTO_MAA
|
AUTO_MAA
|
||||||
AUTO_MAA通知服务
|
AUTO_MAA通知服务
|
||||||
v4.2
|
v4.3
|
||||||
作者:DLmaster_361
|
作者:DLmaster_361
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from PySide6.QtWidgets import QWidget
|
from PySide6.QtWidgets import QWidget
|
||||||
from PySide6.QtCore import Signal
|
from PySide6.QtCore import Signal
|
||||||
import requests
|
import requests
|
||||||
|
import time
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from plyer import notification
|
from plyer import notification
|
||||||
import re
|
import re
|
||||||
@@ -36,9 +37,6 @@ from email.mime.text import MIMEText
|
|||||||
from email.mime.multipart import MIMEMultipart
|
from email.mime.multipart import MIMEMultipart
|
||||||
from email.header import Header
|
from email.header import Header
|
||||||
from email.utils import formataddr
|
from email.utils import formataddr
|
||||||
|
|
||||||
from serverchan_sdk import sc_send
|
|
||||||
|
|
||||||
from app.core import Config
|
from app.core import Config
|
||||||
from app.services.security import Crypto
|
from app.services.security import Crypto
|
||||||
|
|
||||||
@@ -53,7 +51,7 @@ class Notification(QWidget):
|
|||||||
def push_plyer(self, title, message, ticker, t):
|
def push_plyer(self, title, message, ticker, t):
|
||||||
"""推送系统通知"""
|
"""推送系统通知"""
|
||||||
|
|
||||||
if Config.global_config.get(Config.global_config.notify_IfPushPlyer):
|
if Config.get(Config.notify_IfPushPlyer):
|
||||||
|
|
||||||
notification.notify(
|
notification.notify(
|
||||||
title=title,
|
title=title,
|
||||||
@@ -70,27 +68,21 @@ class Notification(QWidget):
|
|||||||
def send_mail(self, mode, title, content) -> None:
|
def send_mail(self, mode, title, content) -> None:
|
||||||
"""推送邮件通知"""
|
"""推送邮件通知"""
|
||||||
|
|
||||||
if Config.global_config.get(Config.global_config.notify_IfSendMail):
|
if Config.get(Config.notify_IfSendMail):
|
||||||
|
|
||||||
if (
|
if (
|
||||||
Config.global_config.get(Config.global_config.notify_SMTPServerAddress)
|
Config.get(Config.notify_SMTPServerAddress) == ""
|
||||||
== ""
|
or Config.get(Config.notify_AuthorizationCode) == ""
|
||||||
or Config.global_config.get(
|
|
||||||
Config.global_config.notify_AuthorizationCode
|
|
||||||
)
|
|
||||||
== ""
|
|
||||||
or not bool(
|
or not bool(
|
||||||
re.match(
|
re.match(
|
||||||
r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$",
|
r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$",
|
||||||
Config.global_config.get(
|
Config.get(Config.notify_FromAddress),
|
||||||
Config.global_config.notify_FromAddress
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
or not bool(
|
or not bool(
|
||||||
re.match(
|
re.match(
|
||||||
r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$",
|
r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$",
|
||||||
Config.global_config.get(Config.global_config.notify_ToAddress),
|
Config.get(Config.notify_ToAddress),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
):
|
):
|
||||||
@@ -114,15 +106,13 @@ class Notification(QWidget):
|
|||||||
message["From"] = formataddr(
|
message["From"] = formataddr(
|
||||||
(
|
(
|
||||||
Header("AUTO_MAA通知服务", "utf-8").encode(),
|
Header("AUTO_MAA通知服务", "utf-8").encode(),
|
||||||
Config.global_config.get(
|
Config.get(Config.notify_FromAddress),
|
||||||
Config.global_config.notify_FromAddress
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
) # 发件人显示的名字
|
) # 发件人显示的名字
|
||||||
message["To"] = formataddr(
|
message["To"] = formataddr(
|
||||||
(
|
(
|
||||||
Header("AUTO_MAA用户", "utf-8").encode(),
|
Header("AUTO_MAA用户", "utf-8").encode(),
|
||||||
Config.global_config.get(Config.global_config.notify_ToAddress),
|
Config.get(Config.notify_ToAddress),
|
||||||
)
|
)
|
||||||
) # 收件人显示的名字
|
) # 收件人显示的名字
|
||||||
message["Subject"] = Header(title, "utf-8")
|
message["Subject"] = Header(title, "utf-8")
|
||||||
@@ -131,22 +121,16 @@ class Notification(QWidget):
|
|||||||
message.attach(MIMEText(content, "html", "utf-8"))
|
message.attach(MIMEText(content, "html", "utf-8"))
|
||||||
|
|
||||||
smtpObj = smtplib.SMTP_SSL(
|
smtpObj = smtplib.SMTP_SSL(
|
||||||
Config.global_config.get(
|
Config.get(Config.notify_SMTPServerAddress),
|
||||||
Config.global_config.notify_SMTPServerAddress
|
|
||||||
),
|
|
||||||
465,
|
465,
|
||||||
)
|
)
|
||||||
smtpObj.login(
|
smtpObj.login(
|
||||||
Config.global_config.get(Config.global_config.notify_FromAddress),
|
Config.get(Config.notify_FromAddress),
|
||||||
Crypto.win_decryptor(
|
Crypto.win_decryptor(Config.get(Config.notify_AuthorizationCode)),
|
||||||
Config.global_config.get(
|
|
||||||
Config.global_config.notify_AuthorizationCode
|
|
||||||
)
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
smtpObj.sendmail(
|
smtpObj.sendmail(
|
||||||
Config.global_config.get(Config.global_config.notify_FromAddress),
|
Config.get(Config.notify_FromAddress),
|
||||||
Config.global_config.get(Config.global_config.notify_ToAddress),
|
Config.get(Config.notify_ToAddress),
|
||||||
message.as_string(),
|
message.as_string(),
|
||||||
)
|
)
|
||||||
smtpObj.quit()
|
smtpObj.quit()
|
||||||
@@ -156,92 +140,177 @@ class Notification(QWidget):
|
|||||||
self.push_info_bar.emit("error", "发送邮件时出错", f"{e}", -1)
|
self.push_info_bar.emit("error", "发送邮件时出错", f"{e}", -1)
|
||||||
|
|
||||||
def ServerChanPush(self, title, content):
|
def ServerChanPush(self, title, content):
|
||||||
"""使用Server酱推送通知"""
|
"""使用Server酱推送通知(支持 tag 和 channel,避免使用SDK)"""
|
||||||
|
if Config.get(Config.notify_IfServerChan):
|
||||||
|
send_key = Config.get(Config.notify_ServerChanKey)
|
||||||
|
|
||||||
if Config.global_config.get(Config.global_config.notify_IfServerChan):
|
if not send_key:
|
||||||
send_key = Config.global_config.get(
|
logger.error("请正确设置Server酱的SendKey")
|
||||||
Config.global_config.notify_ServerChanKey
|
|
||||||
)
|
|
||||||
option = {}
|
|
||||||
is_valid = lambda s: s == "" or (
|
|
||||||
s == "|".join(s.split("|")) and (s.count("|") == 0 or all(s.split("|")))
|
|
||||||
)
|
|
||||||
"""
|
|
||||||
is_valid => True, 如果启用的话需要正确设置Tag和Channel。
|
|
||||||
允许空的Tag和Channel即不启用,但不允许例如a||b,|a|b,a|b|,||||
|
|
||||||
"""
|
|
||||||
send_tag = Config.global_config.get(
|
|
||||||
Config.global_config.notify_ServerChanTag
|
|
||||||
)
|
|
||||||
send_channel = Config.global_config.get(
|
|
||||||
Config.global_config.notify_ServerChanChannel
|
|
||||||
)
|
|
||||||
|
|
||||||
if is_valid(send_tag):
|
|
||||||
option["tags"] = send_tag
|
|
||||||
else:
|
|
||||||
option["tags"] = ""
|
|
||||||
logger.warning("请正确设置Auto_MAA中ServerChan的Tag。")
|
|
||||||
self.push_info_bar.emit(
|
self.push_info_bar.emit(
|
||||||
"warning",
|
"error", "Server酱通知推送异常", "请正确设置Server酱的SendKey", -1
|
||||||
"Server酱通知推送异常",
|
)
|
||||||
"请正确设置Auto_MAA中ServerChan的Tag。",
|
return None
|
||||||
-1,
|
|
||||||
|
try:
|
||||||
|
# 构造 URL
|
||||||
|
if send_key.startswith("sctp"):
|
||||||
|
match = re.match(r"^sctp(\d+)t", send_key)
|
||||||
|
if match:
|
||||||
|
url = f"https://{match.group(1)}.push.ft07.com/send/{send_key}.send"
|
||||||
|
else:
|
||||||
|
raise ValueError("SendKey 格式错误(sctp)")
|
||||||
|
else:
|
||||||
|
url = f"https://sctapi.ftqq.com/{send_key}.send"
|
||||||
|
|
||||||
|
# 构建 tags 和 channel
|
||||||
|
def is_valid(s):
|
||||||
|
return s == "" or (
|
||||||
|
s == "|".join(s.split("|"))
|
||||||
|
and (s.count("|") == 0 or all(s.split("|")))
|
||||||
|
)
|
||||||
|
|
||||||
|
tags = "|".join(
|
||||||
|
_.strip()
|
||||||
|
for _ in Config.get(Config.notify_ServerChanTag).split("|")
|
||||||
|
)
|
||||||
|
channels = "|".join(
|
||||||
|
_.strip()
|
||||||
|
for _ in Config.get(Config.notify_ServerChanChannel).split("|")
|
||||||
)
|
)
|
||||||
|
|
||||||
if is_valid(send_channel):
|
options = {}
|
||||||
option["channel"] = send_channel
|
if is_valid(tags):
|
||||||
else:
|
options["tags"] = tags
|
||||||
option["channel"] = ""
|
else:
|
||||||
logger.warning("请正确设置Auto_MAA中ServerChan的Channel。")
|
logger.warning("Server酱 Tag 配置不正确,将被忽略")
|
||||||
self.push_info_bar.emit(
|
self.push_info_bar.emit(
|
||||||
"warning",
|
"warning",
|
||||||
"Server酱通知推送异常",
|
"Server酱通知推送异常",
|
||||||
"请正确设置Auto_MAA中ServerChan的Channel。",
|
"请正确设置 ServerChan 的 Tag",
|
||||||
-1,
|
-1,
|
||||||
)
|
)
|
||||||
|
|
||||||
response = sc_send(send_key, title, content, option)
|
if is_valid(channels):
|
||||||
if response["code"] == 0:
|
options["channel"] = channels
|
||||||
logger.info("Server酱推送通知成功")
|
else:
|
||||||
return True
|
logger.warning("Server酱 Channel 配置不正确,将被忽略")
|
||||||
else:
|
self.push_info_bar.emit(
|
||||||
logger.info("Server酱推送通知失败")
|
"warning",
|
||||||
logger.error(response)
|
"Server酱通知推送异常",
|
||||||
|
"请正确设置 ServerChan 的 Channel",
|
||||||
|
-1,
|
||||||
|
)
|
||||||
|
|
||||||
|
# 请求发送
|
||||||
|
params = {"title": title, "desp": content, **options}
|
||||||
|
headers = {"Content-Type": "application/json;charset=utf-8"}
|
||||||
|
|
||||||
|
response = requests.post(url, json=params, headers=headers, timeout=10)
|
||||||
|
result = response.json()
|
||||||
|
|
||||||
|
if result.get("code") == 0:
|
||||||
|
logger.info("Server酱推送通知成功")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
error_code = result.get("code", "-1")
|
||||||
|
logger.error(f"Server酱通知推送失败:响应码:{error_code}")
|
||||||
|
self.push_info_bar.emit(
|
||||||
|
"error", "Server酱通知推送失败", f"响应码:{error_code}", -1
|
||||||
|
)
|
||||||
|
return f"Server酱通知推送失败:{error_code}"
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception("Server酱通知推送异常")
|
||||||
self.push_info_bar.emit(
|
self.push_info_bar.emit(
|
||||||
"error",
|
"error", "Server酱通知推送异常", f"请检查相关设置,如还有问题可联系开发者", -1
|
||||||
"Server酱通知推送失败",
|
|
||||||
f'使用Server酱推送通知时出错:\n{response["data"]['error']}',
|
|
||||||
-1,
|
|
||||||
)
|
)
|
||||||
return f'使用Server酱推送通知时出错:\n{response["data"]['error']}'
|
return f"Server酱通知推送异常:{str(e)}"
|
||||||
|
|
||||||
def CompanyWebHookBotPush(self, title, content):
|
def CompanyWebHookBotPush(self, title, content):
|
||||||
"""使用企业微信群机器人推送通知"""
|
"""使用企业微信群机器人推送通知"""
|
||||||
if Config.global_config.get(Config.global_config.notify_IfCompanyWebHookBot):
|
if Config.get(Config.notify_IfCompanyWebHookBot):
|
||||||
|
|
||||||
|
if Config.get(Config.notify_CompanyWebHookBotUrl) == "":
|
||||||
|
logger.error("请正确设置企业微信群机器人的WebHook地址")
|
||||||
|
self.push_info_bar.emit(
|
||||||
|
"error",
|
||||||
|
"企业微信群机器人通知推送异常",
|
||||||
|
"请正确设置企业微信群机器人的WebHook地址",
|
||||||
|
-1,
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
content = f"{title}\n{content}"
|
content = f"{title}\n{content}"
|
||||||
data = {"msgtype": "text", "text": {"content": content}}
|
data = {"msgtype": "text", "text": {"content": content}}
|
||||||
response = requests.post(
|
# 从远程服务器获取最新主题图像
|
||||||
url=Config.global_config.get(
|
for _ in range(3):
|
||||||
Config.global_config.notify_CompanyWebHookBotUrl
|
try:
|
||||||
),
|
response = requests.post(
|
||||||
json=data,
|
url=Config.get(Config.notify_CompanyWebHookBotUrl),
|
||||||
)
|
json=data,
|
||||||
if response.json()["errcode"] == 0:
|
timeout=10,
|
||||||
logger.info("企业微信群机器人推送通知成功")
|
)
|
||||||
return True
|
info = response.json()
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
err = e
|
||||||
|
time.sleep(0.1)
|
||||||
else:
|
else:
|
||||||
logger.info("企业微信群机器人推送通知失败")
|
logger.error(f"推送企业微信群机器人时出错:{err}")
|
||||||
logger.error(response.json())
|
|
||||||
self.push_info_bar.emit(
|
self.push_info_bar.emit(
|
||||||
"error",
|
"error",
|
||||||
"企业微信群机器人通知推送失败",
|
"企业微信群机器人通知推送失败",
|
||||||
f'使用企业微信群机器人推送通知时出错:\n{response.json()["errmsg"]}',
|
f'使用企业微信群机器人推送通知时出错:{info["errmsg"]}',
|
||||||
-1,
|
-1,
|
||||||
)
|
)
|
||||||
return (
|
return None
|
||||||
f'使用企业微信群机器人推送通知时出错:\n{response.json()["errmsg"]}'
|
|
||||||
|
if info["errcode"] == 0:
|
||||||
|
logger.info("企业微信群机器人推送通知成功")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
logger.error(f"企业微信群机器人推送通知失败:{info}")
|
||||||
|
self.push_info_bar.emit(
|
||||||
|
"error",
|
||||||
|
"企业微信群机器人通知推送失败",
|
||||||
|
f'使用企业微信群机器人推送通知时出错:{info["errmsg"]}',
|
||||||
|
-1,
|
||||||
)
|
)
|
||||||
|
return f'使用企业微信群机器人推送通知时出错:{info["errmsg"]}'
|
||||||
|
|
||||||
|
def send_test_notification(self):
|
||||||
|
"""发送测试通知到所有已启用的通知渠道"""
|
||||||
|
# 发送系统通知
|
||||||
|
self.push_plyer(
|
||||||
|
"测试通知",
|
||||||
|
"这是 AUTO_MAA 外部通知测试信息。如果你看到了这段内容,说明 AUTO_MAA 的通知功能已经正确配置且可以正常工作!",
|
||||||
|
"测试通知",
|
||||||
|
3,
|
||||||
|
)
|
||||||
|
|
||||||
|
# 发送邮件通知
|
||||||
|
if Config.get(Config.notify_IfSendMail):
|
||||||
|
self.send_mail(
|
||||||
|
"文本",
|
||||||
|
"AUTO_MAA测试通知",
|
||||||
|
"这是 AUTO_MAA 外部通知测试信息。如果你看到了这段内容,说明 AUTO_MAA 的通知功能已经正确配置且可以正常工作!",
|
||||||
|
)
|
||||||
|
|
||||||
|
# 发送Server酱通知
|
||||||
|
if Config.get(Config.notify_IfServerChan):
|
||||||
|
self.ServerChanPush(
|
||||||
|
"AUTO_MAA测试通知",
|
||||||
|
"这是 AUTO_MAA 外部通知测试信息。如果你看到了这段内容,说明 AUTO_MAA 的通知功能已经正确配置且可以正常工作!",
|
||||||
|
)
|
||||||
|
|
||||||
|
# 发送企业微信机器人通知
|
||||||
|
if Config.get(Config.notify_IfCompanyWebHookBot):
|
||||||
|
self.CompanyWebHookBotPush(
|
||||||
|
"AUTO_MAA测试通知",
|
||||||
|
"这是 AUTO_MAA 外部通知测试信息。如果你看到了这段内容,说明 AUTO_MAA 的通知功能已经正确配置且可以正常工作!",
|
||||||
|
)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
Notify = Notification()
|
Notify = Notification()
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# <AUTO_MAA:A MAA Multi Account Management and Automation Tool>
|
# AUTO_MAA:A MAA Multi Account Management and Automation Tool
|
||||||
# Copyright © <2024> <DLmaster361>
|
# Copyright © 2024-2025 DLmaster361
|
||||||
|
|
||||||
# This file is part of AUTO_MAA.
|
# This file is part of AUTO_MAA.
|
||||||
|
|
||||||
@@ -16,17 +16,16 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
# DLmaster_361@163.com
|
# Contact: DLmaster_361@163.com
|
||||||
|
|
||||||
"""
|
"""
|
||||||
AUTO_MAA
|
AUTO_MAA
|
||||||
AUTO_MAA安全服务
|
AUTO_MAA安全服务
|
||||||
v4.2
|
v4.3
|
||||||
作者:DLmaster_361
|
作者:DLmaster_361
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
import sqlite3
|
|
||||||
import hashlib
|
import hashlib
|
||||||
import random
|
import random
|
||||||
import secrets
|
import secrets
|
||||||
@@ -85,9 +84,12 @@ class CryptoHandler:
|
|||||||
private_key_local = AES_key.encrypt(pad(private_key.exportKey(), 32))
|
private_key_local = AES_key.encrypt(pad(private_key.exportKey(), 32))
|
||||||
(Config.app_path / "data/key/private_key.bin").write_bytes(private_key_local)
|
(Config.app_path / "data/key/private_key.bin").write_bytes(private_key_local)
|
||||||
|
|
||||||
def AUTO_encryptor(self, note: str) -> bytes:
|
def AUTO_encryptor(self, note: str) -> str:
|
||||||
"""使用AUTO_MAA的算法加密数据"""
|
"""使用AUTO_MAA的算法加密数据"""
|
||||||
|
|
||||||
|
if note == "":
|
||||||
|
return ""
|
||||||
|
|
||||||
# 读取RSA公钥
|
# 读取RSA公钥
|
||||||
public_key_local = RSA.import_key(
|
public_key_local = RSA.import_key(
|
||||||
(Config.app_path / "data/key/public_key.pem").read_bytes()
|
(Config.app_path / "data/key/public_key.pem").read_bytes()
|
||||||
@@ -95,11 +97,14 @@ class CryptoHandler:
|
|||||||
# 使用RSA公钥对数据进行加密
|
# 使用RSA公钥对数据进行加密
|
||||||
cipher = PKCS1_OAEP.new(public_key_local)
|
cipher = PKCS1_OAEP.new(public_key_local)
|
||||||
encrypted = cipher.encrypt(note.encode("utf-8"))
|
encrypted = cipher.encrypt(note.encode("utf-8"))
|
||||||
return encrypted
|
return base64.b64encode(encrypted).decode("utf-8")
|
||||||
|
|
||||||
def AUTO_decryptor(self, note: bytes, PASSWORD: str) -> str:
|
def AUTO_decryptor(self, note: str, PASSWORD: str) -> str:
|
||||||
"""使用AUTO_MAA的算法解密数据"""
|
"""使用AUTO_MAA的算法解密数据"""
|
||||||
|
|
||||||
|
if note == "":
|
||||||
|
return ""
|
||||||
|
|
||||||
# 读入RSA私钥密文、盐与校验哈希值
|
# 读入RSA私钥密文、盐与校验哈希值
|
||||||
private_key_local = (
|
private_key_local = (
|
||||||
(Config.app_path / "data/key/private_key.bin").read_bytes().strip()
|
(Config.app_path / "data/key/private_key.bin").read_bytes().strip()
|
||||||
@@ -133,63 +138,40 @@ class CryptoHandler:
|
|||||||
private_key = RSA.import_key(private_key_pem)
|
private_key = RSA.import_key(private_key_pem)
|
||||||
# 使用RSA私钥解密数据
|
# 使用RSA私钥解密数据
|
||||||
decrypter = PKCS1_OAEP.new(private_key)
|
decrypter = PKCS1_OAEP.new(private_key)
|
||||||
note = decrypter.decrypt(note)
|
note = decrypter.decrypt(base64.b64decode(note)).decode("utf-8")
|
||||||
return note.decode("utf-8")
|
return note
|
||||||
|
|
||||||
def change_PASSWORD(self, PASSWORD_old: str, PASSWORD_new: str) -> None:
|
def change_PASSWORD(self, PASSWORD_old: str, PASSWORD_new: str) -> None:
|
||||||
"""修改管理密钥"""
|
"""修改管理密钥"""
|
||||||
|
|
||||||
member_list = self.search_member()
|
for member in Config.member_dict.values():
|
||||||
|
|
||||||
for user_data in member_list:
|
|
||||||
|
|
||||||
# 读取用户数据
|
|
||||||
db = sqlite3.connect(user_data["Path"])
|
|
||||||
cur = db.cursor()
|
|
||||||
cur.execute("SELECT * FROM adminx WHERE True")
|
|
||||||
data = cur.fetchall()
|
|
||||||
|
|
||||||
# 使用旧管理密钥解密
|
# 使用旧管理密钥解密
|
||||||
user_data["Password"] = []
|
for user in member["UserData"].values():
|
||||||
for i in range(len(data)):
|
user["Password"] = self.AUTO_decryptor(
|
||||||
user_data["Password"].append(
|
user["Config"].get(user["Config"].Info_Password), PASSWORD_old
|
||||||
self.AUTO_decryptor(data[i][12], PASSWORD_old)
|
|
||||||
)
|
)
|
||||||
cur.close()
|
|
||||||
db.close()
|
|
||||||
|
|
||||||
self.get_PASSWORD(PASSWORD_new)
|
self.get_PASSWORD(PASSWORD_new)
|
||||||
|
|
||||||
for user_data in member_list:
|
for member in Config.member_dict.values():
|
||||||
|
|
||||||
# 读取用户数据
|
|
||||||
db = sqlite3.connect(user_data["Path"])
|
|
||||||
cur = db.cursor()
|
|
||||||
cur.execute("SELECT * FROM adminx WHERE True")
|
|
||||||
data = cur.fetchall()
|
|
||||||
|
|
||||||
# 使用新管理密钥重新加密
|
# 使用新管理密钥重新加密
|
||||||
for i in range(len(data)):
|
for user in member["UserData"].values():
|
||||||
cur.execute(
|
user["Config"].set(
|
||||||
"UPDATE adminx SET password = ? WHERE mode = ? AND uid = ?",
|
user["Config"].Info_Password, self.AUTO_encryptor(user["Password"])
|
||||||
(
|
|
||||||
self.AUTO_encryptor(user_data["Password"][i]),
|
|
||||||
data[i][15],
|
|
||||||
data[i][16],
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
db.commit()
|
user["Password"] = None
|
||||||
user_data["Password"][i] = None
|
del user["Password"]
|
||||||
del user_data["Password"]
|
|
||||||
|
|
||||||
cur.close()
|
|
||||||
db.close()
|
|
||||||
|
|
||||||
def win_encryptor(
|
def win_encryptor(
|
||||||
self, note: str, description: str = None, entropy: bytes = None
|
self, note: str, description: str = None, entropy: bytes = None
|
||||||
) -> str:
|
) -> str:
|
||||||
"""使用Windows DPAPI加密数据"""
|
"""使用Windows DPAPI加密数据"""
|
||||||
|
|
||||||
|
if note == "":
|
||||||
|
return ""
|
||||||
|
|
||||||
encrypted = win32crypt.CryptProtectData(
|
encrypted = win32crypt.CryptProtectData(
|
||||||
note.encode("utf-8"), description, entropy, None, None, 0
|
note.encode("utf-8"), description, entropy, None, None, 0
|
||||||
)
|
)
|
||||||
@@ -223,7 +205,7 @@ class CryptoHandler:
|
|||||||
"""验证管理密钥"""
|
"""验证管理密钥"""
|
||||||
|
|
||||||
return bool(
|
return bool(
|
||||||
self.AUTO_decryptor(self.AUTO_encryptor(""), PASSWORD) != "管理密钥错误"
|
self.AUTO_decryptor(self.AUTO_encryptor("-"), PASSWORD) != "管理密钥错误"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# <AUTO_MAA:A MAA Multi Account Management and Automation Tool>
|
# AUTO_MAA:A MAA Multi Account Management and Automation Tool
|
||||||
# Copyright © <2024> <DLmaster361>
|
# Copyright © 2024-2025 DLmaster361
|
||||||
|
|
||||||
# This file is part of AUTO_MAA.
|
# This file is part of AUTO_MAA.
|
||||||
|
|
||||||
@@ -16,17 +16,17 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
# DLmaster_361@163.com
|
# Contact: DLmaster_361@163.com
|
||||||
|
|
||||||
"""
|
"""
|
||||||
AUTO_MAA
|
AUTO_MAA
|
||||||
AUTO_MAA系统服务
|
AUTO_MAA系统服务
|
||||||
v4.2
|
v4.3
|
||||||
作者:DLmaster_361
|
作者:DLmaster_361
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from PySide6.QtWidgets import QWidget
|
from PySide6.QtWidgets import QApplication, QWidget
|
||||||
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()
|
||||||
@@ -54,7 +52,7 @@ class _SystemHandler:
|
|||||||
def set_Sleep(self) -> None:
|
def set_Sleep(self) -> None:
|
||||||
"""同步系统休眠状态"""
|
"""同步系统休眠状态"""
|
||||||
|
|
||||||
if Config.global_config.get(Config.global_config.function_IfAllowSleep):
|
if Config.get(Config.function_IfAllowSleep):
|
||||||
# 设置系统电源状态
|
# 设置系统电源状态
|
||||||
ctypes.windll.kernel32.SetThreadExecutionState(
|
ctypes.windll.kernel32.SetThreadExecutionState(
|
||||||
self.ES_CONTINUOUS | self.ES_SYSTEM_REQUIRED
|
self.ES_CONTINUOUS | self.ES_SYSTEM_REQUIRED
|
||||||
@@ -66,10 +64,7 @@ class _SystemHandler:
|
|||||||
def set_SelfStart(self) -> None:
|
def set_SelfStart(self) -> None:
|
||||||
"""同步开机自启"""
|
"""同步开机自启"""
|
||||||
|
|
||||||
if (
|
if Config.get(Config.start_IfSelfStart) and not self.is_startup():
|
||||||
Config.global_config.get(Config.global_config.start_IfSelfStart)
|
|
||||||
and not self.is_startup()
|
|
||||||
):
|
|
||||||
key = winreg.OpenKey(
|
key = winreg.OpenKey(
|
||||||
winreg.HKEY_CURRENT_USER,
|
winreg.HKEY_CURRENT_USER,
|
||||||
r"Software\Microsoft\Windows\CurrentVersion\Run",
|
r"Software\Microsoft\Windows\CurrentVersion\Run",
|
||||||
@@ -78,10 +73,7 @@ class _SystemHandler:
|
|||||||
)
|
)
|
||||||
winreg.SetValueEx(key, "AUTO_MAA", 0, winreg.REG_SZ, Config.app_path_sys)
|
winreg.SetValueEx(key, "AUTO_MAA", 0, winreg.REG_SZ, Config.app_path_sys)
|
||||||
winreg.CloseKey(key)
|
winreg.CloseKey(key)
|
||||||
elif (
|
elif not Config.get(Config.start_IfSelfStart) and self.is_startup():
|
||||||
not Config.global_config.get(Config.global_config.start_IfSelfStart)
|
|
||||||
and self.is_startup()
|
|
||||||
):
|
|
||||||
key = winreg.OpenKey(
|
key = winreg.OpenKey(
|
||||||
winreg.HKEY_CURRENT_USER,
|
winreg.HKEY_CURRENT_USER,
|
||||||
r"Software\Microsoft\Windows\CurrentVersion\Run",
|
r"Software\Microsoft\Windows\CurrentVersion\Run",
|
||||||
@@ -118,7 +110,8 @@ class _SystemHandler:
|
|||||||
|
|
||||||
elif mode == "KillSelf":
|
elif mode == "KillSelf":
|
||||||
|
|
||||||
self.main_window.close()
|
Config.main_window.close()
|
||||||
|
QApplication.quit()
|
||||||
|
|
||||||
elif sys.platform.startswith("linux"):
|
elif sys.platform.startswith("linux"):
|
||||||
|
|
||||||
@@ -143,7 +136,8 @@ class _SystemHandler:
|
|||||||
|
|
||||||
elif mode == "KillSelf":
|
elif mode == "KillSelf":
|
||||||
|
|
||||||
self.main_window.close()
|
Config.main_window.close()
|
||||||
|
QApplication.quit()
|
||||||
|
|
||||||
def is_startup(self) -> bool:
|
def is_startup(self) -> bool:
|
||||||
"""判断程序是否已经开机自启"""
|
"""判断程序是否已经开机自启"""
|
||||||
|
|||||||
887
app/ui/Widget.py
887
app/ui/Widget.py
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
|||||||
# <AUTO_MAA:A MAA Multi Account Management and Automation Tool>
|
# AUTO_MAA:A MAA Multi Account Management and Automation Tool
|
||||||
# Copyright © <2024> <DLmaster361>
|
# Copyright © 2024-2025 DLmaster361
|
||||||
|
|
||||||
# This file is part of AUTO_MAA.
|
# This file is part of AUTO_MAA.
|
||||||
|
|
||||||
@@ -16,12 +16,12 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
# DLmaster_361@163.com
|
# Contact: DLmaster_361@163.com
|
||||||
|
|
||||||
"""
|
"""
|
||||||
AUTO_MAA
|
AUTO_MAA
|
||||||
AUTO_MAA图形化界面包
|
AUTO_MAA图形化界面包
|
||||||
v4.2
|
v4.3
|
||||||
作者:DLmaster_361
|
作者:DLmaster_361
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -30,5 +30,6 @@ __author__ = "DLmaster361 <DLmaster_361@163.com>"
|
|||||||
__license__ = "GPL-3.0 license"
|
__license__ = "GPL-3.0 license"
|
||||||
|
|
||||||
from .main_window import AUTO_MAA
|
from .main_window import AUTO_MAA
|
||||||
|
from .Widget import ProgressRingMessageBox
|
||||||
|
|
||||||
__all__ = ["AUTO_MAA"]
|
__all__ = ["AUTO_MAA", "ProgressRingMessageBox"]
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# <AUTO_MAA:A MAA Multi Account Management and Automation Tool>
|
# AUTO_MAA:A MAA Multi Account Management and Automation Tool
|
||||||
# Copyright © <2024> <DLmaster361>
|
# Copyright © 2024-2025 DLmaster361
|
||||||
|
|
||||||
# This file is part of AUTO_MAA.
|
# This file is part of AUTO_MAA.
|
||||||
|
|
||||||
@@ -16,12 +16,12 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
# DLmaster_361@163.com
|
# Contact: DLmaster_361@163.com
|
||||||
|
|
||||||
"""
|
"""
|
||||||
AUTO_MAA
|
AUTO_MAA
|
||||||
AUTO_MAA调度中枢界面
|
AUTO_MAA调度中枢界面
|
||||||
v4.2
|
v4.3
|
||||||
作者:DLmaster_361
|
作者:DLmaster_361
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -47,11 +47,10 @@ from qfluentwidgets import (
|
|||||||
from PySide6.QtCore import Qt
|
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
|
||||||
import json
|
|
||||||
|
|
||||||
|
|
||||||
from app.core import Config, TaskManager, Task, MainInfoBar
|
from app.core import Config, TaskManager, Task, MainInfoBar
|
||||||
from .Widget import StatefulItemCard
|
from .Widget import StatefulItemCard, ComboBoxMessageBox
|
||||||
|
|
||||||
|
|
||||||
class DispatchCenter(QWidget):
|
class DispatchCenter(QWidget):
|
||||||
@@ -90,7 +89,7 @@ class DispatchCenter(QWidget):
|
|||||||
|
|
||||||
dispatch_box = DispatchBox(task.name, self)
|
dispatch_box = DispatchBox(task.name, self)
|
||||||
|
|
||||||
dispatch_box.top_bar.button.clicked.connect(
|
dispatch_box.top_bar.main_button.clicked.connect(
|
||||||
lambda: TaskManager.stop_task(task.name)
|
lambda: TaskManager.stop_task(task.name)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -124,9 +123,10 @@ 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.button.clicked.disconnect()
|
self.script_list["主调度台"].top_bar.multi_button.show()
|
||||||
self.script_list["主调度台"].top_bar.button.setText("中止任务")
|
self.script_list["主调度台"].top_bar.main_button.clicked.disconnect()
|
||||||
self.script_list["主调度台"].top_bar.button.clicked.connect(
|
self.script_list["主调度台"].top_bar.main_button.setText("中止任务")
|
||||||
|
self.script_list["主调度台"].top_bar.main_button.clicked.connect(
|
||||||
lambda: TaskManager.stop_task(task.name)
|
lambda: TaskManager.stop_task(task.name)
|
||||||
)
|
)
|
||||||
task.create_task_list.connect(
|
task.create_task_list.connect(
|
||||||
@@ -144,51 +144,68 @@ class DispatchCenter(QWidget):
|
|||||||
task.update_log_text.connect(
|
task.update_log_text.connect(
|
||||||
self.script_list["主调度台"].info.log_text.text.setText
|
self.script_list["主调度台"].info.log_text.text.setText
|
||||||
)
|
)
|
||||||
task.accomplish.connect(lambda: self.disconnect_main_board(task.name))
|
task.accomplish.connect(
|
||||||
|
lambda logs: self.disconnect_main_board(task.name, logs)
|
||||||
|
)
|
||||||
|
|
||||||
def disconnect_main_board(self, name: str) -> None:
|
def disconnect_main_board(self, name: str, logs: list) -> None:
|
||||||
"""断开主调度台"""
|
"""断开主调度台"""
|
||||||
|
|
||||||
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.button.clicked.disconnect()
|
self.script_list["主调度台"].top_bar.multi_button.hide()
|
||||||
self.script_list["主调度台"].top_bar.button.setText("开始任务")
|
self.script_list["主调度台"].top_bar.main_button.clicked.disconnect()
|
||||||
self.script_list["主调度台"].top_bar.button.clicked.connect(
|
self.script_list["主调度台"].top_bar.main_button.setText("开始任务")
|
||||||
self.script_list["主调度台"].top_bar.start_task
|
self.script_list["主调度台"].top_bar.main_button.clicked.connect(
|
||||||
)
|
self.script_list["主调度台"].top_bar.start_main_task
|
||||||
self.script_list["主调度台"].info.log_text.text.setText(
|
|
||||||
Config.get_history(name)["History"]
|
|
||||||
)
|
)
|
||||||
|
if len(logs) > 0:
|
||||||
|
history = ""
|
||||||
|
for log in logs:
|
||||||
|
history += (
|
||||||
|
f"任务名称:{log[0]},{log[1]["History"].replace("\n","\n ")}\n"
|
||||||
|
)
|
||||||
|
self.script_list["主调度台"].info.log_text.text.setText(history)
|
||||||
|
else:
|
||||||
|
self.script_list["主调度台"].info.log_text.text.setText("没有任务被执行")
|
||||||
|
|
||||||
def update_top_bar(self):
|
def update_top_bar(self):
|
||||||
"""更新顶栏"""
|
"""更新顶栏"""
|
||||||
|
|
||||||
list = []
|
|
||||||
queue_numb, member_numb = 0, 0
|
|
||||||
|
|
||||||
if (Config.app_path / "config/QueueConfig").exists():
|
|
||||||
for json_file in (Config.app_path / "config/QueueConfig").glob("*.json"):
|
|
||||||
list.append(f"队列 - {json_file.stem}")
|
|
||||||
queue_numb += 1
|
|
||||||
|
|
||||||
if (Config.app_path / "config/MaaConfig").exists():
|
|
||||||
for subdir in (Config.app_path / "config/MaaConfig").iterdir():
|
|
||||||
if subdir.is_dir():
|
|
||||||
list.append(f"实例 - Maa - {subdir.name}")
|
|
||||||
member_numb += 1
|
|
||||||
|
|
||||||
self.script_list["主调度台"].top_bar.object.clear()
|
self.script_list["主调度台"].top_bar.object.clear()
|
||||||
self.script_list["主调度台"].top_bar.object.addItems(list)
|
|
||||||
self.script_list["主调度台"].top_bar.mode.clear()
|
|
||||||
self.script_list["主调度台"].top_bar.mode.addItems(["自动代理", "人工排查"])
|
|
||||||
|
|
||||||
if queue_numb == 1:
|
for name, info in Config.queue_dict.items():
|
||||||
|
self.script_list["主调度台"].top_bar.object.addItem(
|
||||||
|
(
|
||||||
|
"队列"
|
||||||
|
if info["Config"].get(info["Config"].queueSet_Name) == ""
|
||||||
|
else f"队列 - {info["Config"].get(info["Config"].queueSet_Name)}"
|
||||||
|
),
|
||||||
|
userData=name,
|
||||||
|
)
|
||||||
|
|
||||||
|
for name, info in Config.member_dict.items():
|
||||||
|
self.script_list["主调度台"].top_bar.object.addItem(
|
||||||
|
(
|
||||||
|
f"实例 - {info['Type']}"
|
||||||
|
if info["Config"].get(info["Config"].MaaSet_Name) == ""
|
||||||
|
else f"实例 - {info['Type']} - {info["Config"].get(info["Config"].MaaSet_Name)}"
|
||||||
|
),
|
||||||
|
userData=name,
|
||||||
|
)
|
||||||
|
|
||||||
|
if len(Config.queue_dict) == 1:
|
||||||
self.script_list["主调度台"].top_bar.object.setCurrentIndex(0)
|
self.script_list["主调度台"].top_bar.object.setCurrentIndex(0)
|
||||||
elif member_numb == 1:
|
elif len(Config.member_dict) == 1:
|
||||||
self.script_list["主调度台"].top_bar.object.setCurrentIndex(queue_numb)
|
self.script_list["主调度台"].top_bar.object.setCurrentIndex(
|
||||||
|
len(Config.queue_dict)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self.script_list["主调度台"].top_bar.object.setCurrentIndex(-1)
|
self.script_list["主调度台"].top_bar.object.setCurrentIndex(-1)
|
||||||
|
|
||||||
|
self.script_list["主调度台"].top_bar.mode.clear()
|
||||||
|
self.script_list["主调度台"].top_bar.mode.addItems(["自动代理", "人工排查"])
|
||||||
self.script_list["主调度台"].top_bar.mode.setCurrentIndex(0)
|
self.script_list["主调度台"].top_bar.mode.setCurrentIndex(0)
|
||||||
|
|
||||||
|
|
||||||
@@ -235,25 +252,29 @@ class DispatchBox(QWidget):
|
|||||||
self.mode = ComboBox()
|
self.mode = ComboBox()
|
||||||
self.mode.setPlaceholderText("请选择调度模式")
|
self.mode.setPlaceholderText("请选择调度模式")
|
||||||
|
|
||||||
self.button = PushButton("开始任务")
|
self.multi_button = PushButton("添加任务")
|
||||||
self.button.clicked.connect(self.start_task)
|
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.Lable)
|
||||||
Layout.addWidget(self.object)
|
Layout.addWidget(self.object)
|
||||||
Layout.addWidget(self.mode)
|
Layout.addWidget(self.mode)
|
||||||
Layout.addStretch(1)
|
Layout.addStretch(1)
|
||||||
Layout.addWidget(self.button)
|
Layout.addWidget(self.multi_button)
|
||||||
|
Layout.addWidget(self.main_button)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
self.Lable = SubtitleLabel(name, self)
|
self.Lable = SubtitleLabel(name, self)
|
||||||
self.button = PushButton("中止任务")
|
self.main_button = PushButton("中止任务")
|
||||||
|
|
||||||
Layout.addWidget(self.Lable)
|
Layout.addWidget(self.Lable)
|
||||||
Layout.addStretch(1)
|
Layout.addStretch(1)
|
||||||
Layout.addWidget(self.button)
|
Layout.addWidget(self.main_button)
|
||||||
|
|
||||||
def start_task(self):
|
def start_main_task(self):
|
||||||
"""开始任务"""
|
"""开始任务"""
|
||||||
|
|
||||||
if self.object.currentIndex() == -1:
|
if self.object.currentIndex() == -1:
|
||||||
@@ -270,34 +291,101 @@ class DispatchBox(QWidget):
|
|||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
name = self.object.currentText().split(" - ")[-1]
|
if self.object.currentData() in Config.running_list:
|
||||||
|
logger.warning(f"任务已存在:{self.object.currentData()}")
|
||||||
if name in Config.running_list:
|
MainInfoBar.push_info_bar(
|
||||||
logger.warning(f"任务已存在:{name}")
|
"warning", "任务已存在", self.object.currentData(), 5000
|
||||||
MainInfoBar.push_info_bar("warning", "任务已存在", name, 5000)
|
)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if self.object.currentText().split(" - ")[0] == "队列":
|
if "调度队列" in self.object.currentData():
|
||||||
|
|
||||||
with (Config.app_path / f"config/QueueConfig/{name}.json").open(
|
logger.info(f"用户添加任务:{self.object.currentData()}")
|
||||||
mode="r", encoding="utf-8"
|
TaskManager.add_task(
|
||||||
) as f:
|
f"{self.mode.currentText()}_主调度台",
|
||||||
info = json.load(f)
|
self.object.currentData(),
|
||||||
|
Config.queue_dict[self.object.currentData()]["Config"].toDict(),
|
||||||
|
)
|
||||||
|
|
||||||
logger.info(f"用户添加任务:{name}")
|
elif "脚本" in self.object.currentData():
|
||||||
TaskManager.add_task(f"{self.mode.currentText()}_主调度台", name, info)
|
|
||||||
|
|
||||||
elif self.object.currentText().split(" - ")[0] == "实例":
|
if Config.member_dict[self.object.currentData()]["Type"] == "Maa":
|
||||||
|
|
||||||
if self.object.currentText().split(" - ")[1] == "Maa":
|
logger.info(f"用户添加任务:{self.object.currentData()}")
|
||||||
|
|
||||||
info = {"Queue": {"Member_1": name}}
|
|
||||||
|
|
||||||
logger.info(f"用户添加任务:{name}")
|
|
||||||
TaskManager.add_task(
|
TaskManager.add_task(
|
||||||
f"{self.mode.currentText()}_主调度台", "自定义队列", info
|
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:
|
||||||
|
|
||||||
|
if choice.input[0].currentData() in Config.running_list:
|
||||||
|
logger.warning(f"任务已存在:{choice.input[0].currentData()}")
|
||||||
|
MainInfoBar.push_info_bar(
|
||||||
|
"warning", "任务已存在", choice.input[0].currentData(), 5000
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
|
if "调度队列" in choice.input[0].currentData():
|
||||||
|
|
||||||
|
logger.info(f"用户添加任务:{choice.input[0].currentData()}")
|
||||||
|
TaskManager.add_task(
|
||||||
|
"自动代理_新调度台",
|
||||||
|
choice.input[0].currentData(),
|
||||||
|
Config.queue_dict[choice.input[0].currentData()][
|
||||||
|
"Config"
|
||||||
|
].toDict(),
|
||||||
|
)
|
||||||
|
|
||||||
|
elif "脚本" in choice.input[0].currentData():
|
||||||
|
|
||||||
|
if (
|
||||||
|
Config.member_dict[choice.input[0].currentData()]["Type"]
|
||||||
|
== "Maa"
|
||||||
|
):
|
||||||
|
|
||||||
|
logger.info(f"用户添加任务:{choice.input[0].currentData()}")
|
||||||
|
TaskManager.add_task(
|
||||||
|
"自动代理_新调度台",
|
||||||
|
f"自定义队列 - {choice.input[0].currentData()}",
|
||||||
|
{"Queue": {"Member_1": choice.input[0].currentData()}},
|
||||||
|
)
|
||||||
|
|
||||||
class DispatchInfoCard(HeaderCardWidget):
|
class DispatchInfoCard(HeaderCardWidget):
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# <AUTO_MAA:A MAA Multi Account Management and Automation Tool>
|
# AUTO_MAA:A MAA Multi Account Management and Automation Tool
|
||||||
# Copyright © <2024> <DLmaster361>
|
# Copyright © 2024-2025 DLmaster361
|
||||||
|
|
||||||
# This file is part of AUTO_MAA.
|
# This file is part of AUTO_MAA.
|
||||||
|
|
||||||
@@ -16,12 +16,12 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
# DLmaster_361@163.com
|
# Contact: DLmaster_361@163.com
|
||||||
|
|
||||||
"""
|
"""
|
||||||
AUTO_MAA
|
AUTO_MAA
|
||||||
AUTO_MAA历史记录界面
|
AUTO_MAA历史记录界面
|
||||||
v4.2
|
v4.3
|
||||||
作者:DLmaster_361
|
作者:DLmaster_361
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -36,18 +36,23 @@ from qfluentwidgets import (
|
|||||||
FluentIcon,
|
FluentIcon,
|
||||||
HeaderCardWidget,
|
HeaderCardWidget,
|
||||||
PushButton,
|
PushButton,
|
||||||
ExpandGroupSettingCard,
|
|
||||||
TextBrowser,
|
TextBrowser,
|
||||||
|
CardWidget,
|
||||||
|
ComboBox,
|
||||||
|
ZhDatePicker,
|
||||||
|
SubtitleLabel,
|
||||||
)
|
)
|
||||||
from PySide6.QtCore import Signal
|
from PySide6.QtCore import Signal, QDate
|
||||||
import os
|
import os
|
||||||
|
import subprocess
|
||||||
|
from datetime import datetime, timedelta
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import List
|
from typing import Union, List, Dict
|
||||||
|
|
||||||
|
|
||||||
from app.core import Config
|
from app.core import Config
|
||||||
from .Widget import StatefulItemCard, QuantifiedItemCard
|
from .Widget import StatefulItemCard, QuantifiedItemCard, QuickExpandGroupCard
|
||||||
|
|
||||||
|
|
||||||
class History(QWidget):
|
class History(QWidget):
|
||||||
@@ -58,20 +63,22 @@ class History(QWidget):
|
|||||||
|
|
||||||
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.history_top_bar.search_history.connect(self.reload_history)
|
||||||
|
|
||||||
scrollArea = ScrollArea()
|
scrollArea = ScrollArea()
|
||||||
scrollArea.setWidgetResizable(True)
|
scrollArea.setWidgetResizable(True)
|
||||||
scrollArea.setWidget(content_widget)
|
scrollArea.setWidget(content_widget)
|
||||||
layout = QVBoxLayout()
|
layout = QVBoxLayout()
|
||||||
|
layout.addWidget(self.history_top_bar)
|
||||||
layout.addWidget(scrollArea)
|
layout.addWidget(scrollArea)
|
||||||
self.setLayout(layout)
|
self.setLayout(layout)
|
||||||
|
|
||||||
self.history_card_list = []
|
self.history_card_list = []
|
||||||
|
|
||||||
self.refresh()
|
def reload_history(self, mode: str, start_date: QDate, end_date: QDate) -> None:
|
||||||
|
"""加载历史记录界面"""
|
||||||
def refresh(self):
|
|
||||||
"""刷新脚本实例界面"""
|
|
||||||
|
|
||||||
while self.content_layout.count() > 0:
|
while self.content_layout.count() > 0:
|
||||||
item = self.content_layout.takeAt(0)
|
item = self.content_layout.takeAt(0)
|
||||||
@@ -82,177 +89,300 @@ class History(QWidget):
|
|||||||
|
|
||||||
self.history_card_list = []
|
self.history_card_list = []
|
||||||
|
|
||||||
history_dict = Config.search_history()
|
history_dict = Config.search_history(
|
||||||
|
mode,
|
||||||
|
datetime(start_date.year(), start_date.month(), start_date.day()),
|
||||||
|
datetime(end_date.year(), end_date.month(), end_date.day()),
|
||||||
|
)
|
||||||
|
|
||||||
for date, user_list in history_dict.items():
|
for date, user in history_dict.items():
|
||||||
|
|
||||||
self.history_card_list.append(HistoryCard(date, user_list, self))
|
self.history_card_list.append(self.HistoryCard(mode, date, user, self))
|
||||||
self.content_layout.addWidget(self.history_card_list[-1])
|
self.content_layout.addWidget(self.history_card_list[-1])
|
||||||
|
|
||||||
self.content_layout.addStretch(1)
|
self.content_layout.addStretch(1)
|
||||||
|
|
||||||
|
class HistoryTopBar(CardWidget):
|
||||||
|
"""历史记录顶部工具栏"""
|
||||||
|
|
||||||
class HistoryCard(ExpandGroupSettingCard):
|
search_history = Signal(str, QDate, QDate)
|
||||||
|
|
||||||
def __init__(self, date: str, user_list: List[Path], parent=None):
|
def __init__(self, parent=None):
|
||||||
super().__init__(
|
super().__init__(parent)
|
||||||
FluentIcon.HISTORY, date, f"{date}的历史运行记录与统计信息", parent
|
|
||||||
)
|
|
||||||
|
|
||||||
widget = QWidget()
|
Layout = QHBoxLayout(self)
|
||||||
Layout = QVBoxLayout(widget)
|
|
||||||
self.viewLayout.setContentsMargins(0, 0, 0, 0)
|
|
||||||
self.viewLayout.setSpacing(0)
|
|
||||||
self.addGroupWidget(widget)
|
|
||||||
|
|
||||||
self.user_history_card_list = []
|
self.lable_1 = SubtitleLabel("查询范围:")
|
||||||
|
self.start_date = ZhDatePicker()
|
||||||
|
self.start_date.setDate(QDate(2019, 5, 1))
|
||||||
|
self.lable_2 = SubtitleLabel("→")
|
||||||
|
self.end_date = ZhDatePicker()
|
||||||
|
server_date = Config.server_date()
|
||||||
|
self.end_date.setDate(
|
||||||
|
QDate(server_date.year, server_date.month, server_date.day)
|
||||||
|
)
|
||||||
|
self.mode = ComboBox()
|
||||||
|
self.mode.setPlaceholderText("请选择查询模式")
|
||||||
|
self.mode.addItems(["按日合并", "按周合并", "按月合并"])
|
||||||
|
|
||||||
for user_path in user_list:
|
self.select_month = PushButton(FluentIcon.TAG, "最近一月")
|
||||||
|
self.select_week = PushButton(FluentIcon.TAG, "最近一周")
|
||||||
|
self.search = PushButton(FluentIcon.SEARCH, "查询")
|
||||||
|
self.select_month.clicked.connect(lambda: self.select_date("month"))
|
||||||
|
self.select_week.clicked.connect(lambda: self.select_date("week"))
|
||||||
|
self.search.clicked.connect(
|
||||||
|
lambda: self.search_history.emit(
|
||||||
|
self.mode.currentText(),
|
||||||
|
self.start_date.getDate(),
|
||||||
|
self.end_date.getDate(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
self.user_history_card_list.append(self.UserHistoryCard(user_path, self))
|
Layout.addWidget(self.lable_1)
|
||||||
Layout.addWidget(self.user_history_card_list[-1])
|
Layout.addWidget(self.start_date)
|
||||||
|
Layout.addWidget(self.lable_2)
|
||||||
|
Layout.addWidget(self.end_date)
|
||||||
|
Layout.addWidget(self.mode)
|
||||||
|
Layout.addStretch(1)
|
||||||
|
Layout.addWidget(self.select_month)
|
||||||
|
Layout.addWidget(self.select_week)
|
||||||
|
Layout.addWidget(self.search)
|
||||||
|
|
||||||
class UserHistoryCard(HeaderCardWidget):
|
def select_date(self, date: str) -> None:
|
||||||
|
"""选中最近一段时间并启动查询"""
|
||||||
|
|
||||||
|
server_date = Config.server_date()
|
||||||
|
if date == "week":
|
||||||
|
begin_date = server_date - timedelta(weeks=1)
|
||||||
|
elif date == "month":
|
||||||
|
begin_date = server_date - timedelta(days=30)
|
||||||
|
|
||||||
|
self.start_date.setDate(
|
||||||
|
QDate(begin_date.year, begin_date.month, begin_date.day)
|
||||||
|
)
|
||||||
|
self.end_date.setDate(
|
||||||
|
QDate(server_date.year, server_date.month, server_date.day)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.search.clicked.emit()
|
||||||
|
|
||||||
|
class HistoryCard(QuickExpandGroupCard):
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
user_history_path: Path,
|
mode: str,
|
||||||
|
date: str,
|
||||||
|
user: Union[List[Path], Dict[str, List[Path]]],
|
||||||
parent=None,
|
parent=None,
|
||||||
):
|
):
|
||||||
super().__init__(parent)
|
super().__init__(
|
||||||
|
FluentIcon.HISTORY, date, f"{date}的历史运行记录与统计信息", parent
|
||||||
|
)
|
||||||
|
|
||||||
self.setTitle(user_history_path.name.replace(".json", ""))
|
widget = QWidget()
|
||||||
|
Layout = QVBoxLayout(widget)
|
||||||
self.user_history_path = user_history_path
|
|
||||||
self.main_history = Config.load_maa_logs("总览", user_history_path)
|
|
||||||
|
|
||||||
self.index_card = self.IndexCard(self.main_history["条目索引"], self)
|
|
||||||
self.statistics_card = QHBoxLayout()
|
|
||||||
self.log_card = self.LogCard(self)
|
|
||||||
|
|
||||||
self.index_card.index_changed.connect(self.update_info)
|
|
||||||
|
|
||||||
self.viewLayout.addWidget(self.index_card)
|
|
||||||
self.viewLayout.addLayout(self.statistics_card)
|
|
||||||
self.viewLayout.addWidget(self.log_card)
|
|
||||||
self.viewLayout.setContentsMargins(0, 0, 0, 0)
|
self.viewLayout.setContentsMargins(0, 0, 0, 0)
|
||||||
self.viewLayout.setSpacing(0)
|
self.viewLayout.setSpacing(0)
|
||||||
self.viewLayout.setStretch(0, 1)
|
self.addGroupWidget(widget)
|
||||||
self.viewLayout.setStretch(2, 4)
|
|
||||||
|
|
||||||
self.update_info("数据总览")
|
self.user_history_card_list = []
|
||||||
|
|
||||||
def update_info(self, index: str) -> None:
|
if mode == "按日合并":
|
||||||
"""更新信息"""
|
|
||||||
|
|
||||||
if index == "数据总览":
|
for user_path in user:
|
||||||
|
self.user_history_card_list.append(
|
||||||
while self.statistics_card.count() > 0:
|
self.UserHistoryCard(mode, user_path.stem, user_path, self)
|
||||||
item = self.statistics_card.takeAt(0)
|
|
||||||
if item.spacerItem():
|
|
||||||
self.statistics_card.removeItem(item.spacerItem())
|
|
||||||
elif item.widget():
|
|
||||||
item.widget().deleteLater()
|
|
||||||
|
|
||||||
for name, item_list in self.main_history["统计数据"].items():
|
|
||||||
|
|
||||||
statistics_card = self.StatisticsCard(name, item_list, self)
|
|
||||||
self.statistics_card.addWidget(statistics_card)
|
|
||||||
|
|
||||||
self.log_card.hide()
|
|
||||||
|
|
||||||
else:
|
|
||||||
|
|
||||||
single_history = Config.load_maa_logs(
|
|
||||||
"单项",
|
|
||||||
self.user_history_path.with_suffix("")
|
|
||||||
/ f"{index.replace(":","-")}.json",
|
|
||||||
)
|
|
||||||
|
|
||||||
while self.statistics_card.count() > 0:
|
|
||||||
item = self.statistics_card.takeAt(0)
|
|
||||||
if item.spacerItem():
|
|
||||||
self.statistics_card.removeItem(item.spacerItem())
|
|
||||||
elif item.widget():
|
|
||||||
item.widget().deleteLater()
|
|
||||||
|
|
||||||
for name, item_list in single_history["统计数据"].items():
|
|
||||||
|
|
||||||
statistics_card = self.StatisticsCard(name, item_list, self)
|
|
||||||
self.statistics_card.addWidget(statistics_card)
|
|
||||||
|
|
||||||
self.log_card.text.setText(single_history["日志信息"])
|
|
||||||
self.log_card.button.clicked.disconnect()
|
|
||||||
self.log_card.button.clicked.connect(
|
|
||||||
lambda: os.startfile(
|
|
||||||
self.user_history_path.with_suffix("")
|
|
||||||
/ f"{index.replace(":","-")}.log"
|
|
||||||
)
|
)
|
||||||
)
|
Layout.addWidget(self.user_history_card_list[-1])
|
||||||
self.log_card.show()
|
|
||||||
|
|
||||||
self.viewLayout.setStretch(1, self.statistics_card.count())
|
elif mode in ["按周合并", "按月合并"]:
|
||||||
|
|
||||||
self.setMinimumHeight(300)
|
for user, info in user.items():
|
||||||
|
self.user_history_card_list.append(
|
||||||
class IndexCard(HeaderCardWidget):
|
self.UserHistoryCard(mode, user, info, self)
|
||||||
|
|
||||||
index_changed = Signal(str)
|
|
||||||
|
|
||||||
def __init__(self, index_list: list, parent=None):
|
|
||||||
super().__init__(parent)
|
|
||||||
self.setTitle("记录条目")
|
|
||||||
|
|
||||||
self.Layout = QVBoxLayout()
|
|
||||||
self.viewLayout.addLayout(self.Layout)
|
|
||||||
self.viewLayout.setContentsMargins(3, 0, 3, 3)
|
|
||||||
|
|
||||||
self.index_cards: List[StatefulItemCard] = []
|
|
||||||
|
|
||||||
for index in index_list:
|
|
||||||
|
|
||||||
self.index_cards.append(StatefulItemCard(index))
|
|
||||||
self.index_cards[-1].clicked.connect(
|
|
||||||
partial(self.index_changed.emit, index[0])
|
|
||||||
)
|
)
|
||||||
self.Layout.addWidget(self.index_cards[-1])
|
Layout.addWidget(self.user_history_card_list[-1])
|
||||||
|
|
||||||
self.Layout.addStretch(1)
|
class UserHistoryCard(HeaderCardWidget):
|
||||||
|
"""用户历史记录卡片"""
|
||||||
|
|
||||||
class StatisticsCard(HeaderCardWidget):
|
def __init__(
|
||||||
|
self,
|
||||||
def __init__(self, name: str, item_list: list, parent=None):
|
mode: str,
|
||||||
|
name: str,
|
||||||
|
user_history: Union[Path, List[Path]],
|
||||||
|
parent=None,
|
||||||
|
):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
|
|
||||||
self.setTitle(name)
|
self.setTitle(name)
|
||||||
|
|
||||||
self.Layout = QVBoxLayout()
|
if mode == "按日合并":
|
||||||
self.viewLayout.addLayout(self.Layout)
|
|
||||||
self.viewLayout.setContentsMargins(3, 0, 3, 3)
|
|
||||||
|
|
||||||
self.item_cards: List[QuantifiedItemCard] = []
|
self.user_history_path = user_history
|
||||||
|
self.main_history = Config.load_maa_logs("总览", user_history)
|
||||||
|
|
||||||
for item in item_list:
|
self.index_card = self.IndexCard(
|
||||||
|
self.main_history["条目索引"], self
|
||||||
|
)
|
||||||
|
self.index_card.index_changed.connect(self.update_info)
|
||||||
|
self.viewLayout.addWidget(self.index_card)
|
||||||
|
|
||||||
self.item_cards.append(QuantifiedItemCard(item))
|
elif mode in ["按周合并", "按月合并"]:
|
||||||
self.Layout.addWidget(self.item_cards[-1])
|
|
||||||
|
|
||||||
if len(item_list) == 0:
|
history = Config.merge_maa_logs("指定项", user_history)
|
||||||
self.Layout.addWidget(QuantifiedItemCard(["暂无记录", ""]))
|
|
||||||
|
|
||||||
self.Layout.addStretch(1)
|
self.main_history = {}
|
||||||
|
self.main_history["统计数据"] = {
|
||||||
|
"公招统计": list(history["recruit_statistics"].items())
|
||||||
|
}
|
||||||
|
|
||||||
class LogCard(HeaderCardWidget):
|
for game_id, drops in history["drop_statistics"].items():
|
||||||
|
self.main_history["统计数据"][f"掉落统计:{game_id}"] = list(
|
||||||
|
drops.items()
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
self.statistics_card = QHBoxLayout()
|
||||||
super().__init__(parent)
|
self.log_card = self.LogCard(self)
|
||||||
self.setTitle("日志")
|
|
||||||
|
|
||||||
self.text = TextBrowser(self)
|
self.viewLayout.addLayout(self.statistics_card)
|
||||||
self.button = PushButton("打开日志文件", self)
|
self.viewLayout.addWidget(self.log_card)
|
||||||
self.button.clicked.connect(lambda: print("打开日志文件"))
|
self.viewLayout.setContentsMargins(0, 0, 0, 0)
|
||||||
|
self.viewLayout.setSpacing(0)
|
||||||
|
self.viewLayout.setStretch(0, 1)
|
||||||
|
self.viewLayout.setStretch(2, 4)
|
||||||
|
|
||||||
Layout = QVBoxLayout()
|
self.update_info("数据总览")
|
||||||
Layout.addWidget(self.text)
|
|
||||||
Layout.addWidget(self.button)
|
def update_info(self, index: str) -> None:
|
||||||
self.viewLayout.setContentsMargins(3, 0, 3, 3)
|
"""更新信息"""
|
||||||
self.viewLayout.addLayout(Layout)
|
|
||||||
|
if index == "数据总览":
|
||||||
|
|
||||||
|
while self.statistics_card.count() > 0:
|
||||||
|
item = self.statistics_card.takeAt(0)
|
||||||
|
if item.spacerItem():
|
||||||
|
self.statistics_card.removeItem(item.spacerItem())
|
||||||
|
elif item.widget():
|
||||||
|
item.widget().deleteLater()
|
||||||
|
|
||||||
|
for name, item_list in self.main_history["统计数据"].items():
|
||||||
|
|
||||||
|
statistics_card = self.StatisticsCard(name, item_list, self)
|
||||||
|
self.statistics_card.addWidget(statistics_card)
|
||||||
|
|
||||||
|
self.log_card.hide()
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
single_history = Config.load_maa_logs(
|
||||||
|
"单项",
|
||||||
|
self.user_history_path.with_suffix("")
|
||||||
|
/ f"{index.replace(":","-")}.json",
|
||||||
|
)
|
||||||
|
|
||||||
|
while self.statistics_card.count() > 0:
|
||||||
|
item = self.statistics_card.takeAt(0)
|
||||||
|
if item.spacerItem():
|
||||||
|
self.statistics_card.removeItem(item.spacerItem())
|
||||||
|
elif item.widget():
|
||||||
|
item.widget().deleteLater()
|
||||||
|
|
||||||
|
for name, item_list in single_history["统计数据"].items():
|
||||||
|
|
||||||
|
statistics_card = self.StatisticsCard(name, item_list, self)
|
||||||
|
self.statistics_card.addWidget(statistics_card)
|
||||||
|
|
||||||
|
self.log_card.text.setText(single_history["日志信息"])
|
||||||
|
self.log_card.open_file.clicked.disconnect()
|
||||||
|
self.log_card.open_file.clicked.connect(
|
||||||
|
lambda: os.startfile(
|
||||||
|
self.user_history_path.with_suffix("")
|
||||||
|
/ f"{index.replace(":","-")}.log"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.log_card.open_dir.clicked.disconnect()
|
||||||
|
self.log_card.open_dir.clicked.connect(
|
||||||
|
lambda: subprocess.Popen(
|
||||||
|
[
|
||||||
|
"explorer",
|
||||||
|
"/select,",
|
||||||
|
str(
|
||||||
|
self.user_history_path.with_suffix("")
|
||||||
|
/ f"{index.replace(":","-")}.log"
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.log_card.show()
|
||||||
|
|
||||||
|
self.viewLayout.setStretch(1, self.statistics_card.count())
|
||||||
|
|
||||||
|
self.setMinimumHeight(300)
|
||||||
|
|
||||||
|
class IndexCard(HeaderCardWidget):
|
||||||
|
|
||||||
|
index_changed = Signal(str)
|
||||||
|
|
||||||
|
def __init__(self, index_list: list, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.setTitle("记录条目")
|
||||||
|
|
||||||
|
self.Layout = QVBoxLayout()
|
||||||
|
self.viewLayout.addLayout(self.Layout)
|
||||||
|
self.viewLayout.setContentsMargins(3, 0, 3, 3)
|
||||||
|
|
||||||
|
self.index_cards: List[StatefulItemCard] = []
|
||||||
|
|
||||||
|
for index in index_list:
|
||||||
|
|
||||||
|
self.index_cards.append(StatefulItemCard(index))
|
||||||
|
self.index_cards[-1].clicked.connect(
|
||||||
|
partial(self.index_changed.emit, index[0])
|
||||||
|
)
|
||||||
|
self.Layout.addWidget(self.index_cards[-1])
|
||||||
|
|
||||||
|
self.Layout.addStretch(1)
|
||||||
|
|
||||||
|
class StatisticsCard(HeaderCardWidget):
|
||||||
|
|
||||||
|
def __init__(self, name: str, item_list: list, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.setTitle(name)
|
||||||
|
|
||||||
|
self.Layout = QVBoxLayout()
|
||||||
|
self.viewLayout.addLayout(self.Layout)
|
||||||
|
self.viewLayout.setContentsMargins(3, 0, 3, 3)
|
||||||
|
|
||||||
|
self.item_cards: List[QuantifiedItemCard] = []
|
||||||
|
|
||||||
|
for item in item_list:
|
||||||
|
|
||||||
|
self.item_cards.append(QuantifiedItemCard(item))
|
||||||
|
self.Layout.addWidget(self.item_cards[-1])
|
||||||
|
|
||||||
|
if len(item_list) == 0:
|
||||||
|
self.Layout.addWidget(QuantifiedItemCard(["暂无记录", ""]))
|
||||||
|
|
||||||
|
self.Layout.addStretch(1)
|
||||||
|
|
||||||
|
class LogCard(HeaderCardWidget):
|
||||||
|
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.setTitle("日志")
|
||||||
|
|
||||||
|
self.text = TextBrowser(self)
|
||||||
|
self.open_file = PushButton("打开日志文件", self)
|
||||||
|
self.open_file.clicked.connect(lambda: print("打开日志文件"))
|
||||||
|
self.open_dir = PushButton("打开所在目录", self)
|
||||||
|
self.open_dir.clicked.connect(lambda: print("打开所在文件"))
|
||||||
|
|
||||||
|
Layout = QVBoxLayout()
|
||||||
|
h_layout = QHBoxLayout()
|
||||||
|
h_layout.addWidget(self.open_file)
|
||||||
|
h_layout.addWidget(self.open_dir)
|
||||||
|
Layout.addWidget(self.text)
|
||||||
|
Layout.addLayout(h_layout)
|
||||||
|
self.viewLayout.setContentsMargins(3, 0, 3, 3)
|
||||||
|
self.viewLayout.addLayout(Layout)
|
||||||
|
|||||||
117
app/ui/home.py
117
app/ui/home.py
@@ -1,5 +1,5 @@
|
|||||||
# <AUTO_MAA:A MAA Multi Account Management and Automation Tool>
|
# AUTO_MAA:A MAA Multi Account Management and Automation Tool
|
||||||
# Copyright © <2024> <DLmaster361>
|
# Copyright © 2024-2025 DLmaster361
|
||||||
|
|
||||||
# This file is part of AUTO_MAA.
|
# This file is part of AUTO_MAA.
|
||||||
|
|
||||||
@@ -16,12 +16,12 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
# DLmaster_361@163.com
|
# Contact: DLmaster_361@163.com
|
||||||
|
|
||||||
"""
|
"""
|
||||||
AUTO_MAA
|
AUTO_MAA
|
||||||
AUTO_MAA主界面
|
AUTO_MAA主界面
|
||||||
v4.2
|
v4.3
|
||||||
作者:DLmaster_361
|
作者:DLmaster_361
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -45,13 +45,11 @@ from qfluentwidgets import (
|
|||||||
)
|
)
|
||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
import requests
|
|
||||||
import json
|
import json
|
||||||
import time
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from app.core import Config, MainInfoBar
|
from app.core import Config, MainInfoBar, Network
|
||||||
from .Widget import Banner, IconButton
|
from .Widget import Banner, IconButton
|
||||||
|
|
||||||
|
|
||||||
@@ -160,15 +158,9 @@ class Home(QWidget):
|
|||||||
def get_home_image(self) -> None:
|
def get_home_image(self) -> None:
|
||||||
"""获取主页图片"""
|
"""获取主页图片"""
|
||||||
|
|
||||||
if (
|
if Config.get(Config.function_HomeImageMode) == "默认":
|
||||||
Config.global_config.get(Config.global_config.function_HomeImageMode)
|
|
||||||
== "默认"
|
|
||||||
):
|
|
||||||
pass
|
pass
|
||||||
elif (
|
elif Config.get(Config.function_HomeImageMode) == "自定义":
|
||||||
Config.global_config.get(Config.global_config.function_HomeImageMode)
|
|
||||||
== "自定义"
|
|
||||||
):
|
|
||||||
|
|
||||||
file_path, _ = QFileDialog.getOpenFileName(
|
file_path, _ = QFileDialog.getOpenFileName(
|
||||||
self, "打开自定义主页图片", "", "图片文件 (*.png *.jpg *.bmp)"
|
self, "打开自定义主页图片", "", "图片文件 (*.png *.jpg *.bmp)"
|
||||||
@@ -202,29 +194,24 @@ class Home(QWidget):
|
|||||||
"未选择图片文件!",
|
"未选择图片文件!",
|
||||||
5000,
|
5000,
|
||||||
)
|
)
|
||||||
elif (
|
elif Config.get(Config.function_HomeImageMode) == "主题图像":
|
||||||
Config.global_config.get(Config.global_config.function_HomeImageMode)
|
|
||||||
== "主题图像"
|
|
||||||
):
|
|
||||||
|
|
||||||
# 从远程服务器获取最新主题图像
|
# 从远程服务器获取最新主题图像
|
||||||
for _ in range(3):
|
Network.set_info(
|
||||||
try:
|
mode="get",
|
||||||
response = requests.get(
|
url="https://gitee.com/DLmaster_361/AUTO_MAA/raw/server/theme_image.json",
|
||||||
"https://gitee.com/DLmaster_361/AUTO_MAA/raw/server/theme_image.json"
|
)
|
||||||
)
|
Network.start()
|
||||||
theme_image = response.json()
|
Network.loop.exec()
|
||||||
break
|
if Network.stutus_code == 200:
|
||||||
except Exception as e:
|
theme_image = Network.response_json
|
||||||
err = e
|
|
||||||
time.sleep(0.1)
|
|
||||||
else:
|
else:
|
||||||
logger.error(f"获取最新主题图像时出错:\n{err}")
|
logger.warning(f"获取最新主题图像时出错:{Network.error_message}")
|
||||||
MainInfoBar.push_info_bar(
|
MainInfoBar.push_info_bar(
|
||||||
"error",
|
"warning",
|
||||||
"主题图像获取失败",
|
"获取最新主题图像时出错",
|
||||||
f"获取最新主题图像信息时出错:\n{err}",
|
f"网络错误:{Network.stutus_code}",
|
||||||
-1,
|
5000,
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -244,19 +231,25 @@ class Home(QWidget):
|
|||||||
).exists() or (
|
).exists() or (
|
||||||
datetime.now()
|
datetime.now()
|
||||||
> datetime.strptime(theme_image["time"], "%Y-%m-%d %H:%M")
|
> datetime.strptime(theme_image["time"], "%Y-%m-%d %H:%M")
|
||||||
and datetime.strptime(theme_image["time"], "%Y-%m-%d %H:%M")
|
|
||||||
> time_local
|
> time_local
|
||||||
):
|
):
|
||||||
|
|
||||||
response = requests.get(theme_image["url"])
|
Network.set_info(
|
||||||
if response.status_code == 200:
|
mode="get_file",
|
||||||
|
url=theme_image["url"],
|
||||||
|
path=Config.app_path / "resources/images/Home/BannerTheme.jpg",
|
||||||
|
)
|
||||||
|
Network.start()
|
||||||
|
Network.loop.exec()
|
||||||
|
|
||||||
with open(
|
if Network.stutus_code == 200:
|
||||||
Config.app_path / "resources/images/Home/BannerTheme.jpg", "wb"
|
|
||||||
) as file:
|
|
||||||
file.write(response.content)
|
|
||||||
|
|
||||||
logger.info(f"主题图像「{theme_image["name"]}」下载成功")
|
with (Config.app_path / "resources/theme_image.json").open(
|
||||||
|
mode="w", encoding="utf-8"
|
||||||
|
) as f:
|
||||||
|
json.dump(theme_image, f, ensure_ascii=False, indent=4)
|
||||||
|
|
||||||
|
logger.success(f"主题图像「{theme_image["name"]}」下载成功")
|
||||||
MainInfoBar.push_info_bar(
|
MainInfoBar.push_info_bar(
|
||||||
"success",
|
"success",
|
||||||
"主题图像下载成功",
|
"主题图像下载成功",
|
||||||
@@ -266,19 +259,14 @@ class Home(QWidget):
|
|||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
logger.error("主题图像下载失败")
|
logger.warning(f"下载最新主题图像时出错:{Network.error_message}")
|
||||||
MainInfoBar.push_info_bar(
|
MainInfoBar.push_info_bar(
|
||||||
"error",
|
"warning",
|
||||||
"主题图像下载失败",
|
"下载最新主题图像时出错",
|
||||||
f"主题图像下载失败:{response.status_code}",
|
f"网络错误:{Network.stutus_code}",
|
||||||
-1,
|
5000,
|
||||||
)
|
)
|
||||||
|
|
||||||
with (Config.app_path / "resources/theme_image.json").open(
|
|
||||||
mode="w", encoding="utf-8"
|
|
||||||
) as f:
|
|
||||||
json.dump(theme_image, f, ensure_ascii=False, indent=4)
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
logger.info("主题图像已是最新")
|
logger.info("主题图像已是最新")
|
||||||
@@ -293,28 +281,19 @@ class Home(QWidget):
|
|||||||
|
|
||||||
def set_banner(self):
|
def set_banner(self):
|
||||||
"""设置主页图像"""
|
"""设置主页图像"""
|
||||||
if (
|
if Config.get(Config.function_HomeImageMode) == "默认":
|
||||||
Config.global_config.get(Config.global_config.function_HomeImageMode)
|
|
||||||
== "默认"
|
|
||||||
):
|
|
||||||
self.banner.set_banner_image(
|
self.banner.set_banner_image(
|
||||||
str(Config.app_path / "resources/images/Home/BannerDefault.png")
|
str(Config.app_path / "resources/images/Home/BannerDefault.png")
|
||||||
)
|
)
|
||||||
self.imageButton.hide()
|
self.imageButton.hide()
|
||||||
self.banner_text.setVisible(False)
|
self.banner_text.setVisible(False)
|
||||||
elif (
|
elif Config.get(Config.function_HomeImageMode) == "自定义":
|
||||||
Config.global_config.get(Config.global_config.function_HomeImageMode)
|
|
||||||
== "自定义"
|
|
||||||
):
|
|
||||||
for file in Config.app_path.glob("resources/images/Home/BannerCustomize.*"):
|
for file in Config.app_path.glob("resources/images/Home/BannerCustomize.*"):
|
||||||
self.banner.set_banner_image(str(file))
|
self.banner.set_banner_image(str(file))
|
||||||
break
|
break
|
||||||
self.imageButton.show()
|
self.imageButton.show()
|
||||||
self.banner_text.setVisible(False)
|
self.banner_text.setVisible(False)
|
||||||
elif (
|
elif Config.get(Config.function_HomeImageMode) == "主题图像":
|
||||||
Config.global_config.get(Config.global_config.function_HomeImageMode)
|
|
||||||
== "主题图像"
|
|
||||||
):
|
|
||||||
self.banner.set_banner_image(
|
self.banner.set_banner_image(
|
||||||
str(Config.app_path / "resources/images/Home/BannerTheme.jpg")
|
str(Config.app_path / "resources/images/Home/BannerTheme.jpg")
|
||||||
)
|
)
|
||||||
@@ -388,11 +367,11 @@ class ButtonGroup(SimpleCardWidget):
|
|||||||
doc_button.clicked.connect(self.open_chat)
|
doc_button.clicked.connect(self.open_chat)
|
||||||
layout.addWidget(doc_button)
|
layout.addWidget(doc_button)
|
||||||
|
|
||||||
# 创建 官方店铺 按钮 (当然没有)
|
# 创建 MirrorChyan 按钮
|
||||||
doc_button = IconButton(
|
doc_button = IconButton(
|
||||||
FluentIcon.SHOPPING_CART.icon(color=QColor("#fff")),
|
FluentIcon.SHOPPING_CART.icon(color=QColor("#fff")),
|
||||||
tip_title="官方店铺",
|
tip_title="非官方店铺",
|
||||||
tip_content="暂时没有官方店铺,但是可以加入官方群聊哦~",
|
tip_content="获取 MirrorChyan CDK,更新快人一步",
|
||||||
isTooltip=True,
|
isTooltip=True,
|
||||||
)
|
)
|
||||||
doc_button.setIconSize(QSize(32, 32))
|
doc_button.setIconSize(QSize(32, 32))
|
||||||
@@ -419,5 +398,5 @@ class ButtonGroup(SimpleCardWidget):
|
|||||||
QDesktopServices.openUrl(QUrl("https://clozya.github.io/AUTOMAA_docs"))
|
QDesktopServices.openUrl(QUrl("https://clozya.github.io/AUTOMAA_docs"))
|
||||||
|
|
||||||
def open_sales(self):
|
def open_sales(self):
|
||||||
"""其实还是打开 Q群 链接"""
|
"""打开 MirrorChyan 链接"""
|
||||||
QDesktopServices.openUrl(QUrl("https://qm.qq.com/q/bd9fISNoME"))
|
QDesktopServices.openUrl(QUrl("https://mirrorchyan.com/"))
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# <AUTO_MAA:A MAA Multi Account Management and Automation Tool>
|
# AUTO_MAA:A MAA Multi Account Management and Automation Tool
|
||||||
# Copyright © <2024> <DLmaster361>
|
# Copyright © 2024-2025 DLmaster361
|
||||||
|
|
||||||
# This file is part of AUTO_MAA.
|
# This file is part of AUTO_MAA.
|
||||||
|
|
||||||
@@ -16,38 +16,35 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
# DLmaster_361@163.com
|
# Contact: DLmaster_361@163.com
|
||||||
|
|
||||||
"""
|
"""
|
||||||
AUTO_MAA
|
AUTO_MAA
|
||||||
AUTO_MAA主界面
|
AUTO_MAA主界面
|
||||||
v4.2
|
v4.3
|
||||||
作者:DLmaster_361
|
作者:DLmaster_361
|
||||||
"""
|
"""
|
||||||
|
|
||||||
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,
|
||||||
PushButton,
|
|
||||||
SystemTrayMenu,
|
SystemTrayMenu,
|
||||||
SplashScreen,
|
SplashScreen,
|
||||||
FluentIcon,
|
FluentIcon,
|
||||||
InfoBar,
|
|
||||||
InfoBarPosition,
|
|
||||||
setTheme,
|
setTheme,
|
||||||
isDarkTheme,
|
isDarkTheme,
|
||||||
SystemThemeListener,
|
SystemThemeListener,
|
||||||
Theme,
|
Theme,
|
||||||
MSFluentWindow,
|
MSFluentWindow,
|
||||||
NavigationItemPosition,
|
NavigationItemPosition,
|
||||||
qconfig,
|
|
||||||
)
|
)
|
||||||
from PySide6.QtGui import QIcon, QCloseEvent
|
from PySide6.QtGui import QIcon, QCloseEvent
|
||||||
from PySide6.QtCore import Qt, QTimer
|
from PySide6.QtCore import QTimer
|
||||||
import json
|
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
import shutil
|
import shutil
|
||||||
|
import darkdetect
|
||||||
|
|
||||||
from app.core import Config, TaskManager, MainTimer, MainInfoBar
|
from app.core import Config, TaskManager, MainTimer, MainInfoBar
|
||||||
from app.services import Notify, Crypto, System
|
from app.services import Notify, Crypto, System
|
||||||
@@ -65,15 +62,22 @@ 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")
|
|
||||||
|
|
||||||
setTheme(Theme.AUTO, lazy=True)
|
version_numb = list(map(int, Config.VERSION.split(".")))
|
||||||
|
version_text = (
|
||||||
|
f"v{'.'.join(str(_) for _ in version_numb[0:3])}"
|
||||||
|
if version_numb[3] == 0
|
||||||
|
else f"v{'.'.join(str(_) for _ in version_numb[0:3])}-beta.{version_numb[3]}"
|
||||||
|
)
|
||||||
|
|
||||||
|
self.setWindowTitle(f"AUTO_MAA - {version_text}")
|
||||||
|
|
||||||
|
self.switch_theme()
|
||||||
|
|
||||||
self.splashScreen = SplashScreen(self.windowIcon(), self)
|
self.splashScreen = SplashScreen(self.windowIcon(), self)
|
||||||
self.show_ui("显示主窗口", if_quick=True)
|
self.show_ui("显示主窗口", if_quick=True)
|
||||||
|
|
||||||
MainInfoBar.main_window = self.window()
|
Config.main_window = self.window()
|
||||||
System.main_window = self.window()
|
|
||||||
|
|
||||||
# 创建主窗口
|
# 创建主窗口
|
||||||
self.home = Home(self)
|
self.home = Home(self)
|
||||||
@@ -126,10 +130,9 @@ class AUTO_MAA(MSFluentWindow):
|
|||||||
NavigationItemPosition.BOTTOM,
|
NavigationItemPosition.BOTTOM,
|
||||||
)
|
)
|
||||||
self.stackedWidget.currentChanged.connect(
|
self.stackedWidget.currentChanged.connect(
|
||||||
lambda index: (self.member_manager.refresh() if index == 1 else None)
|
lambda index: (
|
||||||
)
|
self.queue_manager.reload_member_name() if index == 2 else None
|
||||||
self.stackedWidget.currentChanged.connect(
|
)
|
||||||
lambda index: self.queue_manager.refresh() if index == 2 else None
|
|
||||||
)
|
)
|
||||||
self.stackedWidget.currentChanged.connect(
|
self.stackedWidget.currentChanged.connect(
|
||||||
lambda index: (
|
lambda index: (
|
||||||
@@ -143,9 +146,6 @@ class AUTO_MAA(MSFluentWindow):
|
|||||||
self.dispatch_center.update_top_bar() if index == 3 else None
|
self.dispatch_center.update_top_bar() if index == 3 else None
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.stackedWidget.currentChanged.connect(
|
|
||||||
lambda index: (self.history.refresh() if index == 4 else None)
|
|
||||||
)
|
|
||||||
|
|
||||||
# 创建系统托盘及其菜单
|
# 创建系统托盘及其菜单
|
||||||
self.tray = QSystemTrayIcon(
|
self.tray = QSystemTrayIcon(
|
||||||
@@ -179,13 +179,20 @@ 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: (self.window().close(), QApplication.quit()),
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
# 设置托盘菜单
|
# 设置托盘菜单
|
||||||
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)
|
||||||
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)
|
||||||
@@ -205,77 +212,76 @@ class AUTO_MAA(MSFluentWindow):
|
|||||||
self.themeListener.systemThemeChanged.connect(self.switch_theme)
|
self.themeListener.systemThemeChanged.connect(self.switch_theme)
|
||||||
self.themeListener.start()
|
self.themeListener.start()
|
||||||
|
|
||||||
def switch_theme(self):
|
def switch_theme(self) -> None:
|
||||||
"""切换主题"""
|
"""切换主题"""
|
||||||
|
|
||||||
setTheme(Theme.AUTO, lazy=True)
|
setTheme(
|
||||||
QTimer.singleShot(100, lambda: setTheme(Theme.AUTO, lazy=True))
|
Theme(darkdetect.theme()) if darkdetect.theme() else Theme.LIGHT, lazy=True
|
||||||
|
)
|
||||||
|
QTimer.singleShot(300, lambda: setTheme(Theme.AUTO, lazy=True))
|
||||||
|
|
||||||
# 云母特效启用时需要增加重试机制
|
# 云母特效启用时需要增加重试机制
|
||||||
|
# 云母特效不兼容Win10,如果True则通过云母进行主题转换,False则根据当前主题设置背景颜色
|
||||||
if self.isMicaEffectEnabled():
|
if self.isMicaEffectEnabled():
|
||||||
QTimer.singleShot(
|
QTimer.singleShot(
|
||||||
100,
|
300,
|
||||||
lambda: self.windowEffect.setMicaEffect(self.winId(), isDarkTheme()),
|
lambda: self.windowEffect.setMicaEffect(self.winId(), isDarkTheme()),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
else:
|
||||||
|
# 根据当前主题设置背景颜色
|
||||||
|
if isDarkTheme():
|
||||||
|
self.setStyleSheet(
|
||||||
|
"""
|
||||||
|
CardWidget {background-color: #313131;}
|
||||||
|
HeaderCardWidget {background-color: #313131;}
|
||||||
|
background-color: #313131;
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.setStyleSheet("background-color: #ffffff;")
|
||||||
|
|
||||||
def start_up_task(self) -> None:
|
def start_up_task(self) -> None:
|
||||||
"""启动时任务"""
|
"""启动时任务"""
|
||||||
|
|
||||||
# 加载配置
|
|
||||||
qconfig.load(Config.config_path, Config.global_config)
|
|
||||||
Config.global_config.save()
|
|
||||||
|
|
||||||
# 清理旧日志
|
# 清理旧日志
|
||||||
self.clean_old_logs()
|
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()
|
self.setting.check_PASSWORD()
|
||||||
|
|
||||||
# 获取主题图像
|
# 获取主题图像
|
||||||
if (
|
if Config.get(Config.function_HomeImageMode) == "主题图像":
|
||||||
Config.global_config.get(Config.global_config.function_HomeImageMode)
|
|
||||||
== "主题图像"
|
|
||||||
):
|
|
||||||
self.home.get_home_image()
|
self.home.get_home_image()
|
||||||
|
|
||||||
# 获取公告
|
|
||||||
self.setting.show_notice(if_show=False)
|
|
||||||
|
|
||||||
# 检查更新
|
|
||||||
if Config.global_config.get(Config.global_config.update_IfAutoUpdate):
|
|
||||||
result = self.setting.get_update_info()
|
|
||||||
if result == "已是最新版本~":
|
|
||||||
MainInfoBar.push_info_bar("success", "更新检查", result, 3000)
|
|
||||||
else:
|
|
||||||
info = InfoBar.info(
|
|
||||||
title="更新检查",
|
|
||||||
content=result,
|
|
||||||
orient=Qt.Horizontal,
|
|
||||||
isClosable=True,
|
|
||||||
position=InfoBarPosition.BOTTOM_LEFT,
|
|
||||||
duration=-1,
|
|
||||||
parent=self,
|
|
||||||
)
|
|
||||||
Up = PushButton("更新")
|
|
||||||
Up.clicked.connect(lambda: self.setting.get_update(if_question=False))
|
|
||||||
Up.clicked.connect(info.close)
|
|
||||||
info.addWidget(Up)
|
|
||||||
info.show()
|
|
||||||
|
|
||||||
# 直接运行主任务
|
# 直接运行主任务
|
||||||
if Config.global_config.get(Config.global_config.start_IfRunDirectly):
|
if Config.get(Config.start_IfRunDirectly):
|
||||||
|
|
||||||
self.start_main_task()
|
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.global_config.get(Config.global_config.start_IfMinimizeDirectly):
|
if Config.get(Config.start_IfMinimizeDirectly):
|
||||||
|
|
||||||
self.titleBar.minBtn.click()
|
self.titleBar.minBtn.click()
|
||||||
|
|
||||||
def set_min_method(self) -> None:
|
def set_min_method(self) -> None:
|
||||||
"""设置最小化方法"""
|
"""设置最小化方法"""
|
||||||
|
|
||||||
if Config.global_config.get(Config.global_config.ui_IfToTray):
|
if Config.get(Config.ui_IfToTray):
|
||||||
|
|
||||||
self.titleBar.minBtn.clicked.disconnect()
|
self.titleBar.minBtn.clicked.disconnect()
|
||||||
self.titleBar.minBtn.clicked.connect(lambda: self.show_ui("隐藏到托盘"))
|
self.titleBar.minBtn.clicked.connect(lambda: self.show_ui("隐藏到托盘"))
|
||||||
@@ -295,10 +301,7 @@ class AUTO_MAA(MSFluentWindow):
|
|||||||
删除超过用户设定天数的日志文件(基于目录日期)
|
删除超过用户设定天数的日志文件(基于目录日期)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if (
|
if Config.get(Config.function_HistoryRetentionTime) == 0:
|
||||||
Config.global_config.get(Config.global_config.function_HistoryRetentionTime)
|
|
||||||
== 0
|
|
||||||
):
|
|
||||||
logger.info("由于用户设置日志永久保留,跳过日志清理")
|
logger.info("由于用户设置日志永久保留,跳过日志清理")
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -312,9 +315,7 @@ class AUTO_MAA(MSFluentWindow):
|
|||||||
# 只检查 `YYYY-MM-DD` 格式的文件夹
|
# 只检查 `YYYY-MM-DD` 格式的文件夹
|
||||||
folder_date = datetime.strptime(date_folder.name, "%Y-%m-%d")
|
folder_date = datetime.strptime(date_folder.name, "%Y-%m-%d")
|
||||||
if datetime.now() - folder_date > timedelta(
|
if datetime.now() - folder_date > timedelta(
|
||||||
days=Config.global_config.get(
|
days=Config.get(Config.function_HistoryRetentionTime)
|
||||||
Config.global_config.function_HistoryRetentionTime
|
|
||||||
)
|
|
||||||
):
|
):
|
||||||
shutil.rmtree(date_folder, ignore_errors=True)
|
shutil.rmtree(date_folder, ignore_errors=True)
|
||||||
deleted_count += 1
|
deleted_count += 1
|
||||||
@@ -327,26 +328,25 @@ class AUTO_MAA(MSFluentWindow):
|
|||||||
def start_main_task(self) -> None:
|
def start_main_task(self) -> None:
|
||||||
"""启动主任务"""
|
"""启动主任务"""
|
||||||
|
|
||||||
if (Config.app_path / "config/QueueConfig/调度队列_1.json").exists():
|
if "调度队列_1" in Config.queue_dict:
|
||||||
|
|
||||||
with (Config.app_path / "config/QueueConfig/调度队列_1.json").open(
|
|
||||||
mode="r", encoding="utf-8"
|
|
||||||
) as f:
|
|
||||||
info = json.load(f)
|
|
||||||
|
|
||||||
logger.info("自动添加任务:调度队列_1")
|
logger.info("自动添加任务:调度队列_1")
|
||||||
TaskManager.add_task("自动代理_主调度台", "主任务队列", info)
|
TaskManager.add_task(
|
||||||
|
"自动代理_主调度台",
|
||||||
|
"调度队列_1",
|
||||||
|
Config.queue_dict["调度队列_1"]["Config"].toDict(),
|
||||||
|
)
|
||||||
|
|
||||||
elif (Config.app_path / "config/MaaConfig/脚本_1").exists():
|
elif "脚本_1" in Config.member_dict:
|
||||||
|
|
||||||
info = {"Queue": {"Member_1": "脚本_1"}}
|
|
||||||
|
|
||||||
logger.info("自动添加任务:脚本_1")
|
logger.info("自动添加任务:脚本_1")
|
||||||
TaskManager.add_task("自动代理_主调度台", "主任务队列", info)
|
TaskManager.add_task(
|
||||||
|
"自动代理_主调度台", "自定义队列", {"Queue": {"Member_1": "脚本_1"}}
|
||||||
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
logger.worning("启动主任务失败:未找到有效的主任务配置文件")
|
logger.warning("启动主任务失败:未找到有效的主任务配置文件")
|
||||||
MainInfoBar.push_info_bar(
|
MainInfoBar.push_info_bar(
|
||||||
"warning", "启动主任务失败", "“调度队列_1”与“脚本_1”均不存在", -1
|
"warning", "启动主任务失败", "“调度队列_1”与“脚本_1”均不存在", -1
|
||||||
)
|
)
|
||||||
@@ -354,36 +354,46 @@ class AUTO_MAA(MSFluentWindow):
|
|||||||
def show_ui(self, mode: str, if_quick: bool = False) -> None:
|
def show_ui(self, mode: str, if_quick: bool = False) -> None:
|
||||||
"""配置窗口状态"""
|
"""配置窗口状态"""
|
||||||
|
|
||||||
|
self.switch_theme()
|
||||||
|
|
||||||
if mode == "显示主窗口":
|
if mode == "显示主窗口":
|
||||||
|
|
||||||
# 配置主窗口
|
# 配置主窗口
|
||||||
size = list(
|
if not self.window().isVisible():
|
||||||
map(
|
size = list(
|
||||||
int,
|
map(
|
||||||
Config.global_config.get(Config.global_config.ui_size).split("x"),
|
int,
|
||||||
|
Config.get(Config.ui_size).split("x"),
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
location = list(
|
||||||
location = list(
|
map(
|
||||||
map(
|
int,
|
||||||
int,
|
Config.get(Config.ui_location).split("x"),
|
||||||
Config.global_config.get(Config.global_config.ui_location).split(
|
)
|
||||||
"x"
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
)
|
if self.window().isMaximized():
|
||||||
self.window().setGeometry(location[0], location[1], size[0], size[1])
|
self.window().showNormal()
|
||||||
self.window().show()
|
self.window().setGeometry(location[0], location[1], size[0], size[1])
|
||||||
|
self.window().show()
|
||||||
|
if not if_quick:
|
||||||
|
if Config.get(Config.ui_maximized):
|
||||||
|
self.titleBar.maxBtn.click()
|
||||||
|
self.show_ui("配置托盘")
|
||||||
|
|
||||||
|
if not any(
|
||||||
|
self.window().geometry().intersects(screen.availableGeometry())
|
||||||
|
for screen in QApplication.screens()
|
||||||
|
):
|
||||||
|
self.window().showNormal()
|
||||||
|
self.window().setGeometry(100, 100, 1200, 700)
|
||||||
|
|
||||||
self.window().raise_()
|
self.window().raise_()
|
||||||
self.window().activateWindow()
|
self.window().activateWindow()
|
||||||
if not if_quick:
|
|
||||||
if Config.global_config.get(Config.global_config.ui_maximized):
|
|
||||||
self.window().showMaximized()
|
|
||||||
self.set_min_method()
|
|
||||||
self.show_ui("配置托盘")
|
|
||||||
|
|
||||||
elif mode == "配置托盘":
|
elif mode == "配置托盘":
|
||||||
|
|
||||||
if Config.global_config.get(Config.global_config.ui_IfShowTray):
|
if Config.get(Config.ui_IfShowTray):
|
||||||
self.tray.show()
|
self.tray.show()
|
||||||
else:
|
else:
|
||||||
self.tray.hide()
|
self.tray.hide()
|
||||||
@@ -393,18 +403,17 @@ class AUTO_MAA(MSFluentWindow):
|
|||||||
# 保存窗口相关属性
|
# 保存窗口相关属性
|
||||||
if not self.window().isMaximized():
|
if not self.window().isMaximized():
|
||||||
|
|
||||||
Config.global_config.set(
|
Config.set(
|
||||||
Config.global_config.ui_size,
|
Config.ui_size,
|
||||||
f"{self.geometry().width()}x{self.geometry().height()}",
|
f"{self.geometry().width()}x{self.geometry().height()}",
|
||||||
)
|
)
|
||||||
Config.global_config.set(
|
Config.set(
|
||||||
Config.global_config.ui_location,
|
Config.ui_location,
|
||||||
f"{self.geometry().x()}x{self.geometry().y()}",
|
f"{self.geometry().x()}x{self.geometry().y()}",
|
||||||
)
|
)
|
||||||
Config.global_config.set(
|
|
||||||
Config.global_config.ui_maximized, self.window().isMaximized()
|
Config.set(Config.ui_maximized, self.window().isMaximized())
|
||||||
)
|
Config.save()
|
||||||
Config.global_config.save()
|
|
||||||
|
|
||||||
# 隐藏主窗口
|
# 隐藏主窗口
|
||||||
if not if_quick:
|
if not if_quick:
|
||||||
@@ -420,11 +429,10 @@ class AUTO_MAA(MSFluentWindow):
|
|||||||
# 清理各功能线程
|
# 清理各功能线程
|
||||||
MainTimer.Timer.stop()
|
MainTimer.Timer.stop()
|
||||||
MainTimer.Timer.deleteLater()
|
MainTimer.Timer.deleteLater()
|
||||||
|
MainTimer.LongTimer.stop()
|
||||||
|
MainTimer.LongTimer.deleteLater()
|
||||||
TaskManager.stop_task("ALL")
|
TaskManager.stop_task("ALL")
|
||||||
|
|
||||||
# 关闭数据库连接
|
|
||||||
Config.close_database()
|
|
||||||
|
|
||||||
# 关闭主题监听
|
# 关闭主题监听
|
||||||
self.themeListener.terminate()
|
self.themeListener.terminate()
|
||||||
self.themeListener.deleteLater()
|
self.themeListener.deleteLater()
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,490 +0,0 @@
|
|||||||
# <AUTO_MAA:A MAA Multi Account Management and Automation Tool>
|
|
||||||
# Copyright © <2024> <DLmaster361>
|
|
||||||
|
|
||||||
# This file is part of AUTO_MAA.
|
|
||||||
|
|
||||||
# AUTO_MAA is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published
|
|
||||||
# by the Free Software Foundation, either version 3 of the License,
|
|
||||||
# or (at your option) any later version.
|
|
||||||
|
|
||||||
# AUTO_MAA is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty
|
|
||||||
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
|
|
||||||
# the GNU General Public License for more details.
|
|
||||||
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
# DLmaster_361@163.com
|
|
||||||
|
|
||||||
"""
|
|
||||||
AUTO_MAA
|
|
||||||
AUTO_MAA更新器
|
|
||||||
v1.1
|
|
||||||
作者:DLmaster_361
|
|
||||||
"""
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import json
|
|
||||||
import zipfile
|
|
||||||
import requests
|
|
||||||
import subprocess
|
|
||||||
import time
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from PySide6.QtWidgets import QApplication, QDialog, QVBoxLayout, QHBoxLayout
|
|
||||||
from qfluentwidgets import (
|
|
||||||
ProgressBar,
|
|
||||||
IndeterminateProgressBar,
|
|
||||||
BodyLabel,
|
|
||||||
PushButton,
|
|
||||||
EditableComboBox,
|
|
||||||
)
|
|
||||||
from PySide6.QtGui import QIcon, QCloseEvent
|
|
||||||
from PySide6.QtCore import QThread, Signal, QEventLoop
|
|
||||||
|
|
||||||
|
|
||||||
def version_text(version_numb: list) -> str:
|
|
||||||
"""将版本号列表转为可读的文本信息"""
|
|
||||||
|
|
||||||
if version_numb[3] == 0:
|
|
||||||
version = f"v{'.'.join(str(_) for _ in version_numb[0:3])}"
|
|
||||||
else:
|
|
||||||
version = (
|
|
||||||
f"v{'.'.join(str(_) for _ in version_numb[0:3])}-beta.{version_numb[3]}"
|
|
||||||
)
|
|
||||||
return version
|
|
||||||
|
|
||||||
|
|
||||||
class UpdateProcess(QThread):
|
|
||||||
|
|
||||||
info = Signal(str)
|
|
||||||
progress = Signal(int, int, int)
|
|
||||||
question = Signal(dict)
|
|
||||||
question_response = Signal(str)
|
|
||||||
accomplish = Signal()
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self, app_path: Path, name: str, main_version: list, updater_version: list
|
|
||||||
) -> None:
|
|
||||||
super(UpdateProcess, self).__init__()
|
|
||||||
|
|
||||||
self.app_path = app_path
|
|
||||||
self.name = name
|
|
||||||
self.main_version = main_version
|
|
||||||
self.updater_version = updater_version
|
|
||||||
self.download_path = app_path / "DOWNLOAD_TEMP.zip" # 临时下载文件的路径
|
|
||||||
self.version_path = app_path / "resources/version.json"
|
|
||||||
self.response = None
|
|
||||||
|
|
||||||
self.question_response.connect(self._capture_response)
|
|
||||||
|
|
||||||
def run(self) -> None:
|
|
||||||
|
|
||||||
# 清理可能存在的临时文件
|
|
||||||
if self.download_path.exists():
|
|
||||||
self.download_path.unlink()
|
|
||||||
|
|
||||||
self.info.emit("正在获取下载链接")
|
|
||||||
url_list = self.get_download_url()
|
|
||||||
url_dict = {}
|
|
||||||
|
|
||||||
# 验证下载地址
|
|
||||||
for i, url in enumerate(url_list):
|
|
||||||
|
|
||||||
if self.isInterruptionRequested():
|
|
||||||
return None
|
|
||||||
|
|
||||||
self.progress.emit(0, len(url_list), i)
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.info.emit(f"正在验证下载地址:{url}")
|
|
||||||
response = requests.get(url, stream=True)
|
|
||||||
if response.status_code != 200:
|
|
||||||
self.info.emit(f"连接失败,错误代码 {response.status_code}")
|
|
||||||
time.sleep(1)
|
|
||||||
continue
|
|
||||||
url_dict[url] = response.elapsed.total_seconds()
|
|
||||||
except requests.RequestException:
|
|
||||||
self.info.emit(f"请求超时")
|
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
download_url = self.push_question(url_dict)
|
|
||||||
|
|
||||||
# 获取文件大小
|
|
||||||
try:
|
|
||||||
self.info.emit(f"正在连接下载地址:{download_url}")
|
|
||||||
self.progress.emit(0, 0, 0)
|
|
||||||
response = requests.get(download_url, stream=True)
|
|
||||||
if response.status_code != 200:
|
|
||||||
self.info.emit(f"连接失败,错误代码 {response.status_code}")
|
|
||||||
return None
|
|
||||||
file_size = response.headers.get("Content-Length")
|
|
||||||
except requests.RequestException:
|
|
||||||
self.info.emit(f"请求超时")
|
|
||||||
return None
|
|
||||||
|
|
||||||
if file_size is None:
|
|
||||||
file_size = 1
|
|
||||||
else:
|
|
||||||
file_size = int(file_size)
|
|
||||||
|
|
||||||
try:
|
|
||||||
# 下载文件
|
|
||||||
with open(self.download_path, "wb") as f:
|
|
||||||
|
|
||||||
downloaded_size = 0
|
|
||||||
last_download_size = 0
|
|
||||||
speed = 0
|
|
||||||
last_time = time.time()
|
|
||||||
|
|
||||||
for chunk in response.iter_content(chunk_size=8192):
|
|
||||||
|
|
||||||
if self.isInterruptionRequested():
|
|
||||||
break
|
|
||||||
|
|
||||||
# 写入已下载数据
|
|
||||||
f.write(chunk)
|
|
||||||
downloaded_size += len(chunk)
|
|
||||||
|
|
||||||
# 计算下载速度
|
|
||||||
if time.time() - last_time >= 1.0:
|
|
||||||
speed = (
|
|
||||||
(downloaded_size - last_download_size)
|
|
||||||
/ (time.time() - last_time)
|
|
||||||
/ 1024
|
|
||||||
)
|
|
||||||
last_download_size = downloaded_size
|
|
||||||
last_time = time.time()
|
|
||||||
|
|
||||||
# 更新下载进度
|
|
||||||
if speed >= 1024:
|
|
||||||
self.info.emit(
|
|
||||||
f"正在下载:{self.name} 已下载:{downloaded_size / 1048576:.2f}/{file_size / 1048576:.2f} MB ({downloaded_size / file_size * 100:.2f}%) 下载速度:{speed / 1024:.2f} MB/s",
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
self.info.emit(
|
|
||||||
f"正在下载:{self.name} 已下载:{downloaded_size / 1048576:.2f}/{file_size / 1048576:.2f} MB ({downloaded_size / file_size * 100:.2f}%) 下载速度:{speed:.2f} KB/s",
|
|
||||||
)
|
|
||||||
self.progress.emit(0, 100, int(downloaded_size / file_size * 100))
|
|
||||||
|
|
||||||
if self.isInterruptionRequested() and self.download_path.exists():
|
|
||||||
self.download_path.unlink()
|
|
||||||
return None
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
e = str(e)
|
|
||||||
e = "\n".join([e[_ : _ + 75] for _ in range(0, len(e), 75)])
|
|
||||||
self.info.emit(f"下载{self.name}时出错:\n{e}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
# 解压
|
|
||||||
try:
|
|
||||||
|
|
||||||
while True:
|
|
||||||
if self.isInterruptionRequested():
|
|
||||||
self.download_path.unlink()
|
|
||||||
return None
|
|
||||||
try:
|
|
||||||
self.info.emit("正在解压更新文件")
|
|
||||||
self.progress.emit(0, 0, 0)
|
|
||||||
with zipfile.ZipFile(self.download_path, "r") as zip_ref:
|
|
||||||
zip_ref.extractall(self.app_path)
|
|
||||||
break
|
|
||||||
except PermissionError:
|
|
||||||
self.info.emit(f"解压出错:{self.name}正在运行,正在等待其关闭")
|
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
self.info.emit("正在删除临时文件")
|
|
||||||
self.progress.emit(0, 0, 0)
|
|
||||||
self.download_path.unlink()
|
|
||||||
|
|
||||||
self.info.emit(f"{self.name}更新成功!")
|
|
||||||
self.progress.emit(0, 100, 100)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
|
|
||||||
e = str(e)
|
|
||||||
e = "\n".join([e[_ : _ + 75] for _ in range(0, len(e), 75)])
|
|
||||||
self.info.emit(f"解压更新时出错:\n{e}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
# 更新version文件
|
|
||||||
if not self.isInterruptionRequested and self.name in [
|
|
||||||
"AUTO_MAA主程序",
|
|
||||||
"AUTO_MAA更新器",
|
|
||||||
]:
|
|
||||||
with open(self.version_path, "r", encoding="utf-8") as f:
|
|
||||||
version_info = json.load(f)
|
|
||||||
if self.name == "AUTO_MAA主程序":
|
|
||||||
version_info["main_version"] = ".".join(map(str, self.main_version))
|
|
||||||
elif self.name == "AUTO_MAA更新器":
|
|
||||||
version_info["updater_version"] = ".".join(
|
|
||||||
map(str, self.updater_version)
|
|
||||||
)
|
|
||||||
with open(self.version_path, "w", encoding="utf-8") as f:
|
|
||||||
json.dump(version_info, f, ensure_ascii=False, indent=4)
|
|
||||||
|
|
||||||
# 主程序更新完成后打开AUTO_MAA
|
|
||||||
if not self.isInterruptionRequested and self.name == "AUTO_MAA主程序":
|
|
||||||
subprocess.Popen(
|
|
||||||
str(self.app_path / "AUTO_MAA.exe"),
|
|
||||||
shell=True,
|
|
||||||
creationflags=subprocess.CREATE_NO_WINDOW,
|
|
||||||
)
|
|
||||||
elif not self.isInterruptionRequested and self.name == "MAA":
|
|
||||||
subprocess.Popen(
|
|
||||||
str(self.app_path / "MAA.exe"),
|
|
||||||
shell=True,
|
|
||||||
creationflags=subprocess.CREATE_NO_WINDOW,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.accomplish.emit()
|
|
||||||
|
|
||||||
def get_download_url(self) -> list:
|
|
||||||
"""获取下载链接"""
|
|
||||||
|
|
||||||
try_num = 3
|
|
||||||
for i in range(try_num):
|
|
||||||
try:
|
|
||||||
response = requests.get(
|
|
||||||
"https://gitee.com/DLmaster_361/AUTO_MAA/raw/main/resources/version.json"
|
|
||||||
)
|
|
||||||
if response.status_code != 200:
|
|
||||||
self.info.emit(
|
|
||||||
f"连接失败,错误代码 {response.status_code} ,正在重试({i+1}/{try_num})"
|
|
||||||
)
|
|
||||||
time.sleep(0.1)
|
|
||||||
continue
|
|
||||||
version_remote = response.json()
|
|
||||||
PROXY_list = version_remote["proxy_list"]
|
|
||||||
break
|
|
||||||
except requests.RequestException:
|
|
||||||
self.info.emit(f"请求超时,正在重试({i+1}/{try_num})")
|
|
||||||
time.sleep(0.1)
|
|
||||||
except KeyError:
|
|
||||||
self.info.emit(f"未找到远端代理网址项,正在重试({i+1}/{try_num})")
|
|
||||||
time.sleep(0.1)
|
|
||||||
else:
|
|
||||||
self.info.emit("获取远端代理信息失败,将使用默认代理地址")
|
|
||||||
PROXY_list = [
|
|
||||||
"",
|
|
||||||
"https://gitproxy.click/",
|
|
||||||
"https://cdn.moran233.xyz/",
|
|
||||||
"https://gh.llkk.cc/",
|
|
||||||
"https://github.akams.cn/",
|
|
||||||
"https://www.ghproxy.cn/",
|
|
||||||
"https://ghfast.top/",
|
|
||||||
]
|
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
url_list = []
|
|
||||||
if self.name == "AUTO_MAA主程序":
|
|
||||||
url_list.append(
|
|
||||||
f"https://gitee.com/DLmaster_361/AUTO_MAA/releases/download/{version_text(self.main_version)}/AUTO_MAA_{version_text(self.main_version)}.zip"
|
|
||||||
)
|
|
||||||
url_list.append(
|
|
||||||
f"https://jp-download.fearr.xyz/AUTO_MAA/AUTO_MAA_{version_text(self.main_version)}.zip"
|
|
||||||
)
|
|
||||||
for i in range(len(PROXY_list)):
|
|
||||||
url_list.append(
|
|
||||||
f"{PROXY_list[i]}https://github.com/DLmaster361/AUTO_MAA/releases/download/{version_text(self.main_version)}/AUTO_MAA_{version_text(self.main_version)}.zip"
|
|
||||||
)
|
|
||||||
elif self.name == "AUTO_MAA更新器":
|
|
||||||
url_list.append(
|
|
||||||
f"https://gitee.com/DLmaster_361/AUTO_MAA/releases/download/{version_text(self.main_version)}/Updater_{version_text(self.updater_version)}.zip"
|
|
||||||
)
|
|
||||||
url_list.append(
|
|
||||||
f"https://jp-download.fearr.xyz/AUTO_MAA/Updater_{version_text(self.updater_version)}.zip"
|
|
||||||
)
|
|
||||||
for i in range(len(PROXY_list)):
|
|
||||||
url_list.append(
|
|
||||||
f"{PROXY_list[i]}https://github.com/DLmaster361/AUTO_MAA/releases/download/{version_text(self.main_version)}/Updater_{version_text(self.updater_version)}.zip"
|
|
||||||
)
|
|
||||||
elif self.name == "MAA":
|
|
||||||
url_list.append(
|
|
||||||
f"https://jp-download.fearr.xyz/MAA/MAA-{version_text(self.main_version)}-win-x64.zip"
|
|
||||||
)
|
|
||||||
for i in range(len(PROXY_list)):
|
|
||||||
url_list.append(
|
|
||||||
f"{PROXY_list[i]}https://github.com/MaaAssistantArknights/MaaAssistantArknights/releases/download/{version_text(self.main_version)}/MAA-{version_text(self.main_version)}-win-x64.zip"
|
|
||||||
)
|
|
||||||
return url_list
|
|
||||||
|
|
||||||
def push_question(self, url_dict: dict) -> str:
|
|
||||||
self.question.emit(url_dict)
|
|
||||||
loop = QEventLoop()
|
|
||||||
self.question_response.connect(loop.quit)
|
|
||||||
loop.exec()
|
|
||||||
return self.response
|
|
||||||
|
|
||||||
def _capture_response(self, response: str) -> None:
|
|
||||||
self.response = response
|
|
||||||
|
|
||||||
|
|
||||||
class Updater(QDialog):
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self, app_path: Path, name: str, main_version: list, updater_version: list
|
|
||||||
) -> None:
|
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
self.setWindowTitle("AUTO_MAA更新器")
|
|
||||||
self.setWindowIcon(
|
|
||||||
QIcon(
|
|
||||||
str(
|
|
||||||
Path(sys.argv[0]).resolve().parent
|
|
||||||
/ "resources/icons/AUTO_MAA_Updater.ico"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
# 创建垂直布局
|
|
||||||
self.Layout = QVBoxLayout(self)
|
|
||||||
|
|
||||||
self.info = BodyLabel("正在初始化", self)
|
|
||||||
self.progress_1 = IndeterminateProgressBar(self)
|
|
||||||
self.progress_2 = ProgressBar(self)
|
|
||||||
self.combo_box = EditableComboBox(self)
|
|
||||||
|
|
||||||
self.button = PushButton("继续", self)
|
|
||||||
self.h_layout = QHBoxLayout()
|
|
||||||
self.h_layout.addStretch(1)
|
|
||||||
self.h_layout.addWidget(self.button)
|
|
||||||
|
|
||||||
self.update_progress(0, 0, 0)
|
|
||||||
|
|
||||||
self.Layout.addWidget(self.info)
|
|
||||||
self.Layout.addStretch(1)
|
|
||||||
self.Layout.addWidget(self.progress_1)
|
|
||||||
self.Layout.addWidget(self.progress_2)
|
|
||||||
self.Layout.addWidget(self.combo_box)
|
|
||||||
self.Layout.addLayout(self.h_layout)
|
|
||||||
self.Layout.addStretch(1)
|
|
||||||
|
|
||||||
self.update_process = UpdateProcess(
|
|
||||||
app_path, name, main_version, updater_version
|
|
||||||
)
|
|
||||||
|
|
||||||
self.update_process.info.connect(self.update_info)
|
|
||||||
self.update_process.progress.connect(self.update_progress)
|
|
||||||
self.update_process.question.connect(self.question)
|
|
||||||
|
|
||||||
self.update_process.start()
|
|
||||||
|
|
||||||
def update_info(self, text: str) -> None:
|
|
||||||
self.info.setText(text)
|
|
||||||
|
|
||||||
def update_progress(
|
|
||||||
self, begin: int, end: int, current: int, if_show_combo_box: bool = False
|
|
||||||
) -> None:
|
|
||||||
|
|
||||||
self.combo_box.setVisible(if_show_combo_box)
|
|
||||||
self.button.setVisible(if_show_combo_box)
|
|
||||||
|
|
||||||
if if_show_combo_box:
|
|
||||||
self.progress_1.setVisible(False)
|
|
||||||
self.progress_2.setVisible(False)
|
|
||||||
self.resize(1000, 90)
|
|
||||||
elif begin == 0 and end == 0:
|
|
||||||
self.progress_2.setVisible(False)
|
|
||||||
self.progress_1.setVisible(True)
|
|
||||||
self.resize(700, 70)
|
|
||||||
else:
|
|
||||||
self.progress_1.setVisible(False)
|
|
||||||
self.progress_2.setVisible(True)
|
|
||||||
self.progress_2.setRange(begin, end)
|
|
||||||
self.progress_2.setValue(current)
|
|
||||||
self.resize(700, 70)
|
|
||||||
|
|
||||||
def question(self, url_dict: dict) -> None:
|
|
||||||
|
|
||||||
self.update_info("测速完成,请选择或自行输入一个合适下载地址:")
|
|
||||||
self.update_progress(0, 0, 0, True)
|
|
||||||
|
|
||||||
url_dict = dict(sorted(url_dict.items(), key=lambda item: item[1]))
|
|
||||||
|
|
||||||
for url, time in url_dict.items():
|
|
||||||
self.combo_box.addItem(f"{url} | 响应时间:{time:.3f}秒")
|
|
||||||
|
|
||||||
self.button.clicked.connect(
|
|
||||||
lambda: self.update_process.question_response.emit(
|
|
||||||
self.combo_box.currentText().split(" | ")[0]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
def closeEvent(self, event: QCloseEvent):
|
|
||||||
"""清理残余进程"""
|
|
||||||
|
|
||||||
self.update_process.requestInterruption()
|
|
||||||
self.update_process.quit()
|
|
||||||
self.update_process.wait()
|
|
||||||
|
|
||||||
event.accept()
|
|
||||||
|
|
||||||
|
|
||||||
class AUTO_MAA_Updater(QApplication):
|
|
||||||
def __init__(
|
|
||||||
self, app_path: Path, name: str, main_version: list, updater_version: list
|
|
||||||
) -> None:
|
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
self.main = Updater(app_path, name, main_version, updater_version)
|
|
||||||
self.main.show()
|
|
||||||
|
|
||||||
|
|
||||||
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:
|
|
||||||
version_current = json.load(f)
|
|
||||||
main_version_current = list(
|
|
||||||
map(int, version_current["main_version"].split("."))
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
main_version_current = [0, 0, 0, 0]
|
|
||||||
|
|
||||||
# 从本地配置文件获取更新类型
|
|
||||||
if (app_path / "config/config.json").exists():
|
|
||||||
with (app_path / "config/config.json").open(mode="r", encoding="utf-8") as f:
|
|
||||||
config = json.load(f)
|
|
||||||
if "Update" in config and "UpdateType" in config["Update"]:
|
|
||||||
update_type = config["Update"]["UpdateType"]
|
|
||||||
else:
|
|
||||||
update_type = "main"
|
|
||||||
else:
|
|
||||||
update_type = "main"
|
|
||||||
|
|
||||||
# 从远程服务器获取最新版本信息
|
|
||||||
for _ in range(3):
|
|
||||||
try:
|
|
||||||
response = requests.get(
|
|
||||||
f"https://gitee.com/DLmaster_361/AUTO_MAA/raw/{update_type}/resources/version.json"
|
|
||||||
)
|
|
||||||
version_remote = response.json()
|
|
||||||
main_version_remote = list(
|
|
||||||
map(int, version_remote["main_version"].split("."))
|
|
||||||
)
|
|
||||||
break
|
|
||||||
except Exception as e:
|
|
||||||
err = e
|
|
||||||
time.sleep(0.1)
|
|
||||||
else:
|
|
||||||
sys.exit(f"获取版本信息时出错:\n{err}")
|
|
||||||
|
|
||||||
# 启动更新线程
|
|
||||||
if main_version_remote > main_version_current:
|
|
||||||
app = AUTO_MAA_Updater(
|
|
||||||
app_path,
|
|
||||||
"AUTO_MAA主程序",
|
|
||||||
main_version_remote,
|
|
||||||
[],
|
|
||||||
)
|
|
||||||
sys.exit(app.exec())
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
# <AUTO_MAA:A MAA Multi Account Management and Automation Tool>
|
# AUTO_MAA:A MAA Multi Account Management and Automation Tool
|
||||||
# Copyright © <2024> <DLmaster361>
|
# Copyright © 2024-2025 DLmaster361
|
||||||
|
|
||||||
# This file is part of AUTO_MAA.
|
# This file is part of AUTO_MAA.
|
||||||
|
|
||||||
@@ -16,12 +16,12 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
# DLmaster_361@163.com
|
# Contact: DLmaster_361@163.com
|
||||||
|
|
||||||
"""
|
"""
|
||||||
AUTO_MAA
|
AUTO_MAA
|
||||||
AUTO_MAA工具包
|
AUTO_MAA工具包
|
||||||
v4.2
|
v4.3
|
||||||
作者:DLmaster_361
|
作者:DLmaster_361
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -29,6 +29,6 @@ __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 .Updater import Updater
|
from .downloader import DownloadManager
|
||||||
|
|
||||||
__all__ = ["Updater"]
|
__all__ = ["DownloadManager"]
|
||||||
|
|||||||
755
app/utils/downloader.py
Normal file
755
app/utils/downloader.py
Normal file
@@ -0,0 +1,755 @@
|
|||||||
|
# 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更新器
|
||||||
|
v1.2
|
||||||
|
作者:DLmaster_361
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import zipfile
|
||||||
|
import requests
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
|
import psutil
|
||||||
|
import win32crypt
|
||||||
|
import base64
|
||||||
|
from packaging import version
|
||||||
|
from functools import partial
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from PySide6.QtWidgets import QApplication, QDialog, QVBoxLayout
|
||||||
|
from qfluentwidgets import (
|
||||||
|
ProgressBar,
|
||||||
|
IndeterminateProgressBar,
|
||||||
|
BodyLabel,
|
||||||
|
setTheme,
|
||||||
|
Theme,
|
||||||
|
)
|
||||||
|
from PySide6.QtGui import QIcon, QCloseEvent
|
||||||
|
from PySide6.QtCore import QThread, Signal, QTimer, QEventLoop
|
||||||
|
|
||||||
|
from typing import List, Dict, Union
|
||||||
|
|
||||||
|
|
||||||
|
def version_text(version_numb: list) -> str:
|
||||||
|
"""将版本号列表转为可读的文本信息"""
|
||||||
|
|
||||||
|
while len(version_numb) < 4:
|
||||||
|
version_numb.append(0)
|
||||||
|
|
||||||
|
if version_numb[3] == 0:
|
||||||
|
version = f"v{'.'.join(str(_) for _ in version_numb[0:3])}"
|
||||||
|
else:
|
||||||
|
version = (
|
||||||
|
f"v{'.'.join(str(_) for _ in version_numb[0:3])}-beta.{version_numb[3]}"
|
||||||
|
)
|
||||||
|
return version
|
||||||
|
|
||||||
|
|
||||||
|
class DownloadProcess(QThread):
|
||||||
|
"""分段下载子线程"""
|
||||||
|
|
||||||
|
progress = Signal(int)
|
||||||
|
accomplish = Signal(float)
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
url: str,
|
||||||
|
start_byte: int,
|
||||||
|
end_byte: int,
|
||||||
|
download_path: Path,
|
||||||
|
check_times: int = -1,
|
||||||
|
) -> None:
|
||||||
|
super(DownloadProcess, self).__init__()
|
||||||
|
|
||||||
|
self.url = url
|
||||||
|
self.start_byte = start_byte
|
||||||
|
self.end_byte = end_byte
|
||||||
|
self.download_path = download_path
|
||||||
|
self.check_times = check_times
|
||||||
|
|
||||||
|
def run(self) -> None:
|
||||||
|
|
||||||
|
# 清理可能存在的临时文件
|
||||||
|
if self.download_path.exists():
|
||||||
|
self.download_path.unlink()
|
||||||
|
|
||||||
|
headers = {"Range": f"bytes={self.start_byte}-{self.end_byte}"}
|
||||||
|
|
||||||
|
while not self.isInterruptionRequested() and self.check_times != 0:
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
start_time = time.time()
|
||||||
|
|
||||||
|
response = requests.get(
|
||||||
|
self.url, headers=headers, timeout=10, stream=True
|
||||||
|
)
|
||||||
|
|
||||||
|
if response.status_code != 206:
|
||||||
|
|
||||||
|
if self.check_times != -1:
|
||||||
|
self.check_times -= 1
|
||||||
|
|
||||||
|
time.sleep(1)
|
||||||
|
continue
|
||||||
|
|
||||||
|
downloaded_size = 0
|
||||||
|
with self.download_path.open(mode="wb") as f:
|
||||||
|
|
||||||
|
for chunk in response.iter_content(chunk_size=8192):
|
||||||
|
|
||||||
|
if self.isInterruptionRequested():
|
||||||
|
break
|
||||||
|
|
||||||
|
f.write(chunk)
|
||||||
|
downloaded_size += len(chunk)
|
||||||
|
|
||||||
|
self.progress.emit(downloaded_size)
|
||||||
|
|
||||||
|
if self.isInterruptionRequested():
|
||||||
|
|
||||||
|
if self.download_path.exists():
|
||||||
|
self.download_path.unlink()
|
||||||
|
self.accomplish.emit(0)
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
self.accomplish.emit(time.time() - start_time)
|
||||||
|
|
||||||
|
break
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
|
||||||
|
if self.check_times != -1:
|
||||||
|
self.check_times -= 1
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
if self.download_path.exists():
|
||||||
|
self.download_path.unlink()
|
||||||
|
self.accomplish.emit(0)
|
||||||
|
|
||||||
|
|
||||||
|
class ZipExtractProcess(QThread):
|
||||||
|
"""解压子线程"""
|
||||||
|
|
||||||
|
info = Signal(str)
|
||||||
|
accomplish = Signal()
|
||||||
|
|
||||||
|
def __init__(self, name: str, app_path: Path, download_path: Path) -> None:
|
||||||
|
super(ZipExtractProcess, self).__init__()
|
||||||
|
|
||||||
|
self.name = name
|
||||||
|
self.app_path = app_path
|
||||||
|
self.download_path = download_path
|
||||||
|
|
||||||
|
def run(self) -> None:
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
while True:
|
||||||
|
|
||||||
|
if self.isInterruptionRequested():
|
||||||
|
self.download_path.unlink()
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
with zipfile.ZipFile(self.download_path, "r") as zip_ref:
|
||||||
|
zip_ref.extractall(self.app_path)
|
||||||
|
self.accomplish.emit()
|
||||||
|
break
|
||||||
|
except PermissionError:
|
||||||
|
if self.name == "AUTO_MAA":
|
||||||
|
self.info.emit(f"解压出错:AUTO_MAA正在运行,正在尝试将其关闭")
|
||||||
|
self.kill_process(self.app_path / "AUTO_MAA.exe")
|
||||||
|
else:
|
||||||
|
self.info.emit(f"解压出错:{self.name}正在运行,正在等待其关闭")
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
|
||||||
|
e = str(e)
|
||||||
|
e = "\n".join([e[_ : _ + 75] for _ in range(0, len(e), 75)])
|
||||||
|
self.info.emit(f"解压更新时出错:\n{e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def kill_process(self, path: Path) -> None:
|
||||||
|
"""根据路径中止进程"""
|
||||||
|
|
||||||
|
for pid in self.search_pids(path):
|
||||||
|
killprocess = subprocess.Popen(
|
||||||
|
f"taskkill /F /PID {pid}",
|
||||||
|
shell=True,
|
||||||
|
creationflags=subprocess.CREATE_NO_WINDOW,
|
||||||
|
)
|
||||||
|
killprocess.wait()
|
||||||
|
|
||||||
|
def search_pids(self, path: Path) -> list:
|
||||||
|
"""根据路径查找进程PID"""
|
||||||
|
|
||||||
|
pids = []
|
||||||
|
for proc in psutil.process_iter(["pid", "exe"]):
|
||||||
|
try:
|
||||||
|
if proc.info["exe"] and proc.info["exe"].lower() == str(path).lower():
|
||||||
|
pids.append(proc.info["pid"])
|
||||||
|
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
|
||||||
|
# 进程可能在此期间已结束或无法访问,忽略这些异常
|
||||||
|
pass
|
||||||
|
return pids
|
||||||
|
|
||||||
|
|
||||||
|
class DownloadManager(QDialog):
|
||||||
|
"""下载管理器"""
|
||||||
|
|
||||||
|
speed_test_accomplish = Signal()
|
||||||
|
download_accomplish = Signal()
|
||||||
|
download_process_clear = Signal()
|
||||||
|
|
||||||
|
isInterruptionRequested = False
|
||||||
|
|
||||||
|
def __init__(self, app_path: Path, name: str, version: list, config: dict) -> None:
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
self.app_path = app_path
|
||||||
|
self.name = name
|
||||||
|
self.version = version
|
||||||
|
self.config = config
|
||||||
|
self.download_path = app_path / "DOWNLOAD_TEMP.zip" # 临时下载文件的路径
|
||||||
|
self.version_path = app_path / "resources/version.json"
|
||||||
|
self.download_process_dict: Dict[str, DownloadProcess] = {}
|
||||||
|
self.timer_dict: Dict[str, QTimer] = {}
|
||||||
|
|
||||||
|
self.setWindowTitle("AUTO_MAA更新器")
|
||||||
|
self.setWindowIcon(
|
||||||
|
QIcon(str(app_path / "resources/icons/AUTO_MAA_Updater.ico"))
|
||||||
|
)
|
||||||
|
self.resize(700, 70)
|
||||||
|
|
||||||
|
setTheme(Theme.AUTO, lazy=True)
|
||||||
|
|
||||||
|
# 创建垂直布局
|
||||||
|
self.Layout = QVBoxLayout(self)
|
||||||
|
|
||||||
|
self.info = BodyLabel("正在初始化", self)
|
||||||
|
self.progress_1 = IndeterminateProgressBar(self)
|
||||||
|
self.progress_2 = ProgressBar(self)
|
||||||
|
|
||||||
|
self.update_progress(0, 0, 0)
|
||||||
|
|
||||||
|
self.Layout.addWidget(self.info)
|
||||||
|
self.Layout.addStretch(1)
|
||||||
|
self.Layout.addWidget(self.progress_1)
|
||||||
|
self.Layout.addWidget(self.progress_2)
|
||||||
|
self.Layout.addStretch(1)
|
||||||
|
|
||||||
|
def run(self) -> None:
|
||||||
|
|
||||||
|
if self.name == "MAA":
|
||||||
|
self.download_task1()
|
||||||
|
elif self.name == "AUTO_MAA":
|
||||||
|
if self.config["mode"] == "Proxy":
|
||||||
|
self.test_speed_task1()
|
||||||
|
self.speed_test_accomplish.connect(self.download_task1)
|
||||||
|
elif self.config["mode"] == "MirrorChyan":
|
||||||
|
self.download_task1()
|
||||||
|
|
||||||
|
def get_download_url(self, mode: str) -> Union[str, Dict[str, str]]:
|
||||||
|
"""获取下载链接"""
|
||||||
|
|
||||||
|
url_dict = {}
|
||||||
|
|
||||||
|
if mode == "测速":
|
||||||
|
|
||||||
|
url_dict["GitHub站"] = (
|
||||||
|
f"https://github.com/DLmaster361/AUTO_MAA/releases/download/{version_text(self.version)}/AUTO_MAA_{version_text(self.version)}.zip"
|
||||||
|
)
|
||||||
|
url_dict["官方镜像站"] = (
|
||||||
|
f"https://gitee.com/DLmaster_361/AUTO_MAA/releases/download/{version_text(self.version)}/AUTO_MAA_{version_text(self.version)}.zip"
|
||||||
|
)
|
||||||
|
for name, download_url_head in self.config["download_dict"].items():
|
||||||
|
url_dict[name] = (
|
||||||
|
f"{download_url_head}AUTO_MAA_{version_text(self.version)}.zip"
|
||||||
|
)
|
||||||
|
for proxy_url in self.config["proxy_list"]:
|
||||||
|
url_dict[proxy_url] = (
|
||||||
|
f"{proxy_url}https://github.com/DLmaster361/AUTO_MAA/releases/download/{version_text(self.version)}/AUTO_MAA_{version_text(self.version)}.zip"
|
||||||
|
)
|
||||||
|
return url_dict
|
||||||
|
|
||||||
|
elif mode == "下载":
|
||||||
|
|
||||||
|
if self.name == "MAA":
|
||||||
|
return f"https://jp-download.fearr.xyz/MAA/MAA-{version_text(self.version)}-win-x64.zip"
|
||||||
|
|
||||||
|
if self.name == "AUTO_MAA":
|
||||||
|
|
||||||
|
if self.config["mode"] == "Proxy":
|
||||||
|
|
||||||
|
if "selected" in self.config:
|
||||||
|
selected_url = self.config["selected"]
|
||||||
|
elif "speed_result" in self.config:
|
||||||
|
selected_url = max(
|
||||||
|
self.config["speed_result"],
|
||||||
|
key=self.config["speed_result"].get,
|
||||||
|
)
|
||||||
|
|
||||||
|
if selected_url == "GitHub站":
|
||||||
|
return f"https://github.com/DLmaster361/AUTO_MAA/releases/download/{version_text(self.version)}/AUTO_MAA_{version_text(self.version)}.zip"
|
||||||
|
elif selected_url == "官方镜像站":
|
||||||
|
return f"https://gitee.com/DLmaster_361/AUTO_MAA/releases/download/{version_text(self.version)}/AUTO_MAA_{version_text(self.version)}.zip"
|
||||||
|
elif selected_url in self.config["download_dict"].keys():
|
||||||
|
return f"{self.config["download_dict"][selected_url]}AUTO_MAA_{version_text(self.version)}.zip"
|
||||||
|
else:
|
||||||
|
return f"{selected_url}https://github.com/DLmaster361/AUTO_MAA/releases/download/{version_text(self.version)}/AUTO_MAA_{version_text(self.version)}.zip"
|
||||||
|
|
||||||
|
elif self.config["mode"] == "MirrorChyan":
|
||||||
|
with requests.get(
|
||||||
|
self.config["url"],
|
||||||
|
allow_redirects=True,
|
||||||
|
timeout=10,
|
||||||
|
stream=True,
|
||||||
|
) as response:
|
||||||
|
if response.status_code == 200:
|
||||||
|
return response.url
|
||||||
|
|
||||||
|
def test_speed_task1(self) -> None:
|
||||||
|
|
||||||
|
if self.isInterruptionRequested:
|
||||||
|
return None
|
||||||
|
|
||||||
|
url_dict = self.get_download_url("测速")
|
||||||
|
self.test_speed_result: Dict[str, float] = {}
|
||||||
|
|
||||||
|
for name, url in url_dict.items():
|
||||||
|
|
||||||
|
if self.isInterruptionRequested:
|
||||||
|
break
|
||||||
|
|
||||||
|
# 创建测速线程,下载4MB文件以测试下载速度
|
||||||
|
self.download_process_dict[name] = DownloadProcess(
|
||||||
|
url,
|
||||||
|
0,
|
||||||
|
4194304,
|
||||||
|
self.app_path / f"{name.replace('/','').replace(':','')}.zip",
|
||||||
|
10,
|
||||||
|
)
|
||||||
|
self.test_speed_result[name] = -1
|
||||||
|
self.download_process_dict[name].accomplish.connect(
|
||||||
|
partial(self.test_speed_task2, name)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.download_process_dict[name].start()
|
||||||
|
timer = QTimer(self)
|
||||||
|
timer.setSingleShot(True)
|
||||||
|
timer.timeout.connect(partial(self.kill_speed_test, name))
|
||||||
|
timer.start(30000)
|
||||||
|
self.timer_dict[name] = timer
|
||||||
|
|
||||||
|
self.update_info("正在测速,预计用时30秒")
|
||||||
|
self.update_progress(0, 1, 0)
|
||||||
|
|
||||||
|
def kill_speed_test(self, name: str) -> None:
|
||||||
|
|
||||||
|
if name in self.download_process_dict:
|
||||||
|
self.download_process_dict[name].requestInterruption()
|
||||||
|
|
||||||
|
def test_speed_task2(self, name: str, t: float) -> None:
|
||||||
|
|
||||||
|
# 计算下载速度
|
||||||
|
if self.isInterruptionRequested:
|
||||||
|
self.update_info(f"已中止测速进程:{name}")
|
||||||
|
self.test_speed_result[name] = 0
|
||||||
|
elif t != 0:
|
||||||
|
self.update_info(f"{name}:{ 4 / t:.2f} MB/s")
|
||||||
|
self.test_speed_result[name] = 4 / t
|
||||||
|
else:
|
||||||
|
self.update_info(f"{name}:{ 0:.2f} MB/s")
|
||||||
|
self.test_speed_result[name] = 0
|
||||||
|
self.update_progress(
|
||||||
|
0,
|
||||||
|
len(self.test_speed_result),
|
||||||
|
sum(1 for speed in self.test_speed_result.values() if speed != -1),
|
||||||
|
)
|
||||||
|
|
||||||
|
# 删除临时文件
|
||||||
|
if (self.app_path / f"{name.replace('/','').replace(':','')}.zip").exists():
|
||||||
|
(self.app_path / f"{name.replace('/','').replace(':','')}.zip").unlink()
|
||||||
|
|
||||||
|
# 清理下载线程
|
||||||
|
self.timer_dict[name].stop()
|
||||||
|
self.timer_dict[name].deleteLater()
|
||||||
|
self.timer_dict.pop(name)
|
||||||
|
self.download_process_dict[name].requestInterruption()
|
||||||
|
self.download_process_dict[name].quit()
|
||||||
|
self.download_process_dict[name].wait()
|
||||||
|
self.download_process_dict[name].deleteLater()
|
||||||
|
self.download_process_dict.pop(name)
|
||||||
|
if not self.download_process_dict:
|
||||||
|
self.download_process_clear.emit()
|
||||||
|
|
||||||
|
if any(speed == -1 for _, speed in self.test_speed_result.items()):
|
||||||
|
return None
|
||||||
|
|
||||||
|
# 保存测速结果
|
||||||
|
self.config["speed_result"] = self.test_speed_result
|
||||||
|
|
||||||
|
self.update_info("测速完成!")
|
||||||
|
self.speed_test_accomplish.emit()
|
||||||
|
|
||||||
|
def download_task1(self) -> None:
|
||||||
|
|
||||||
|
if self.isInterruptionRequested:
|
||||||
|
return None
|
||||||
|
|
||||||
|
url = self.get_download_url("下载")
|
||||||
|
self.downloaded_size_list: List[List[int, bool]] = []
|
||||||
|
|
||||||
|
response = requests.head(url, timeout=10)
|
||||||
|
|
||||||
|
self.file_size = int(response.headers.get("content-length", 0))
|
||||||
|
part_size = self.file_size // self.config["thread_numb"]
|
||||||
|
self.downloaded_size = 0
|
||||||
|
self.last_download_size = 0
|
||||||
|
self.last_time = time.time()
|
||||||
|
self.speed = 0
|
||||||
|
|
||||||
|
# 拆分下载任务,启用多线程下载
|
||||||
|
for i in range(self.config["thread_numb"]):
|
||||||
|
|
||||||
|
if self.isInterruptionRequested:
|
||||||
|
break
|
||||||
|
|
||||||
|
# 计算单任务下载范围
|
||||||
|
start_byte = i * part_size
|
||||||
|
end_byte = (
|
||||||
|
(i + 1) * part_size - 1
|
||||||
|
if (i != self.config["thread_numb"] - 1)
|
||||||
|
else self.file_size - 1
|
||||||
|
)
|
||||||
|
|
||||||
|
# 创建下载子线程
|
||||||
|
self.download_process_dict[f"part{i}"] = DownloadProcess(
|
||||||
|
url,
|
||||||
|
start_byte,
|
||||||
|
end_byte,
|
||||||
|
self.download_path.with_suffix(f".part{i}"),
|
||||||
|
1 if self.config["mode"] == "MirrorChyan" else -1,
|
||||||
|
)
|
||||||
|
self.downloaded_size_list.append([0, False])
|
||||||
|
self.download_process_dict[f"part{i}"].progress.connect(
|
||||||
|
partial(self.download_task2, i)
|
||||||
|
)
|
||||||
|
self.download_process_dict[f"part{i}"].accomplish.connect(
|
||||||
|
partial(self.download_task3, i)
|
||||||
|
)
|
||||||
|
self.download_process_dict[f"part{i}"].start()
|
||||||
|
|
||||||
|
def download_task2(self, index: str, current: int) -> None:
|
||||||
|
"""更新下载进度"""
|
||||||
|
|
||||||
|
self.downloaded_size_list[index][0] = current
|
||||||
|
self.downloaded_size = sum([_[0] for _ in self.downloaded_size_list])
|
||||||
|
self.update_progress(0, self.file_size, self.downloaded_size)
|
||||||
|
|
||||||
|
if time.time() - self.last_time >= 1.0:
|
||||||
|
self.speed = (
|
||||||
|
(self.downloaded_size - self.last_download_size)
|
||||||
|
/ (time.time() - self.last_time)
|
||||||
|
/ 1024
|
||||||
|
)
|
||||||
|
self.last_download_size = self.downloaded_size
|
||||||
|
self.last_time = time.time()
|
||||||
|
|
||||||
|
if self.speed >= 1024:
|
||||||
|
self.update_info(
|
||||||
|
f"正在下载:{self.name} 已下载:{self.downloaded_size / 1048576:.2f}/{self.file_size / 1048576:.2f} MB ({self.downloaded_size / self.file_size * 100:.2f}%) 下载速度:{self.speed / 1024:.2f} MB/s",
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.update_info(
|
||||||
|
f"正在下载:{self.name} 已下载:{self.downloaded_size / 1048576:.2f}/{self.file_size / 1048576:.2f} MB ({self.downloaded_size / self.file_size * 100:.2f}%) 下载速度:{self.speed:.2f} KB/s",
|
||||||
|
)
|
||||||
|
|
||||||
|
def download_task3(self, index: str, t: float) -> None:
|
||||||
|
|
||||||
|
# 标记下载线程完成
|
||||||
|
self.downloaded_size_list[index][1] = True
|
||||||
|
|
||||||
|
# 清理下载线程
|
||||||
|
self.download_process_dict[f"part{index}"].requestInterruption()
|
||||||
|
self.download_process_dict[f"part{index}"].quit()
|
||||||
|
self.download_process_dict[f"part{index}"].wait()
|
||||||
|
self.download_process_dict[f"part{index}"].deleteLater()
|
||||||
|
self.download_process_dict.pop(f"part{index}")
|
||||||
|
if not self.download_process_dict:
|
||||||
|
self.download_process_clear.emit()
|
||||||
|
|
||||||
|
if (
|
||||||
|
any([not _[1] for _ in self.downloaded_size_list])
|
||||||
|
or self.isInterruptionRequested
|
||||||
|
):
|
||||||
|
return None
|
||||||
|
|
||||||
|
# 合并下载的分段文件
|
||||||
|
with self.download_path.open(mode="wb") as outfile:
|
||||||
|
for i in range(self.config["thread_numb"]):
|
||||||
|
with self.download_path.with_suffix(f".part{i}").open(
|
||||||
|
mode="rb"
|
||||||
|
) as infile:
|
||||||
|
outfile.write(infile.read())
|
||||||
|
self.download_path.with_suffix(f".part{i}").unlink()
|
||||||
|
|
||||||
|
self.update_info("正在解压更新文件")
|
||||||
|
self.update_progress(0, 0, 0)
|
||||||
|
|
||||||
|
# 创建解压线程
|
||||||
|
self.zip_extract = ZipExtractProcess(
|
||||||
|
self.name, self.app_path, self.download_path
|
||||||
|
)
|
||||||
|
self.zip_loop = QEventLoop()
|
||||||
|
self.zip_extract.info.connect(self.update_info)
|
||||||
|
self.zip_extract.accomplish.connect(self.zip_loop.quit)
|
||||||
|
self.zip_extract.start()
|
||||||
|
self.zip_loop.exec()
|
||||||
|
|
||||||
|
self.update_info("正在删除已弃用的文件")
|
||||||
|
if (self.app_path / "changes.json").exists():
|
||||||
|
|
||||||
|
with (self.app_path / "changes.json").open(mode="r", encoding="utf-8") as f:
|
||||||
|
info: Dict[str, List[str]] = json.load(f)
|
||||||
|
|
||||||
|
if "deleted" in info:
|
||||||
|
for file_path in info["deleted"]:
|
||||||
|
if (self.app_path / file_path).exists():
|
||||||
|
(self.app_path / file_path).unlink()
|
||||||
|
|
||||||
|
(self.app_path / "changes.json").unlink()
|
||||||
|
|
||||||
|
self.update_info("正在删除临时文件")
|
||||||
|
self.update_progress(0, 0, 0)
|
||||||
|
if self.download_path.exists():
|
||||||
|
self.download_path.unlink()
|
||||||
|
|
||||||
|
# 主程序更新完成后打开对应程序
|
||||||
|
if not self.isInterruptionRequested and self.name == "AUTO_MAA":
|
||||||
|
subprocess.Popen(
|
||||||
|
[self.app_path / "AUTO_MAA.exe"],
|
||||||
|
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP
|
||||||
|
| subprocess.DETACHED_PROCESS
|
||||||
|
| subprocess.CREATE_NO_WINDOW,
|
||||||
|
)
|
||||||
|
elif not self.isInterruptionRequested and self.name == "MAA":
|
||||||
|
subprocess.Popen(
|
||||||
|
[self.app_path / "MAA.exe"],
|
||||||
|
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP
|
||||||
|
| subprocess.DETACHED_PROCESS
|
||||||
|
| subprocess.CREATE_NO_WINDOW,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.update_info(f"{self.name}更新成功!")
|
||||||
|
self.update_progress(0, 100, 100)
|
||||||
|
self.download_accomplish.emit()
|
||||||
|
|
||||||
|
def update_info(self, text: str) -> None:
|
||||||
|
self.info.setText(text)
|
||||||
|
|
||||||
|
def update_progress(self, begin: int, end: int, current: int) -> None:
|
||||||
|
|
||||||
|
if begin == 0 and end == 0:
|
||||||
|
self.progress_2.setVisible(False)
|
||||||
|
self.progress_1.setVisible(True)
|
||||||
|
else:
|
||||||
|
self.progress_1.setVisible(False)
|
||||||
|
self.progress_2.setVisible(True)
|
||||||
|
self.progress_2.setRange(begin, end)
|
||||||
|
self.progress_2.setValue(current)
|
||||||
|
|
||||||
|
def requestInterruption(self) -> None:
|
||||||
|
|
||||||
|
self.isInterruptionRequested = True
|
||||||
|
|
||||||
|
if hasattr(self, "zip_extract") and self.zip_extract:
|
||||||
|
self.zip_extract.requestInterruption()
|
||||||
|
|
||||||
|
if hasattr(self, "zip_loop") and self.zip_loop:
|
||||||
|
self.zip_loop.quit()
|
||||||
|
|
||||||
|
for process in self.download_process_dict.values():
|
||||||
|
process.requestInterruption()
|
||||||
|
|
||||||
|
if self.download_process_dict:
|
||||||
|
loop = QEventLoop()
|
||||||
|
self.download_process_clear.connect(loop.quit)
|
||||||
|
loop.exec()
|
||||||
|
|
||||||
|
def closeEvent(self, event: QCloseEvent):
|
||||||
|
"""清理残余进程"""
|
||||||
|
|
||||||
|
self.requestInterruption()
|
||||||
|
|
||||||
|
event.accept()
|
||||||
|
|
||||||
|
|
||||||
|
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())
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
# <AUTO_MAA:A MAA Multi Account Management and Automation Tool>
|
# AUTO_MAA:A MAA Multi Account Management and Automation Tool
|
||||||
# Copyright © <2024> <DLmaster361>
|
# Copyright © 2024-2025 DLmaster361
|
||||||
|
|
||||||
# This file is part of AUTO_MAA.
|
# This file is part of AUTO_MAA.
|
||||||
|
|
||||||
@@ -16,12 +16,12 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
# DLmaster_361@163.com
|
# Contact: DLmaster_361@163.com
|
||||||
|
|
||||||
"""
|
"""
|
||||||
AUTO_MAA
|
AUTO_MAA
|
||||||
AUTO_MAA打包程序
|
AUTO_MAA打包程序
|
||||||
v4.2
|
v4.3
|
||||||
作者:DLmaster_361
|
作者:DLmaster_361
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -35,6 +35,9 @@ from pathlib import Path
|
|||||||
def version_text(version_numb: list) -> str:
|
def version_text(version_numb: list) -> str:
|
||||||
"""将版本号列表转为可读的文本信息"""
|
"""将版本号列表转为可读的文本信息"""
|
||||||
|
|
||||||
|
while len(version_numb) < 4:
|
||||||
|
version_numb.append(0)
|
||||||
|
|
||||||
if version_numb[3] == 0:
|
if version_numb[3] == 0:
|
||||||
version = f"v{'.'.join(str(_) for _ in version_numb[0:3])}"
|
version = f"v{'.'.join(str(_) for _ in version_numb[0:3])}"
|
||||||
else:
|
else:
|
||||||
@@ -44,6 +47,17 @@ def version_text(version_numb: list) -> str:
|
|||||||
return version
|
return version
|
||||||
|
|
||||||
|
|
||||||
|
def version_info_markdown(info: dict) -> str:
|
||||||
|
"""将版本信息字典转为markdown信息"""
|
||||||
|
|
||||||
|
version_info = ""
|
||||||
|
for key, value in info.items():
|
||||||
|
version_info += f"## {key}\n"
|
||||||
|
for v in value:
|
||||||
|
version_info += f"- {v}\n"
|
||||||
|
return version_info
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
||||||
root_path = Path(sys.argv[0]).resolve().parent
|
root_path = Path(sys.argv[0]).resolve().parent
|
||||||
@@ -65,26 +79,16 @@ if __name__ == "__main__":
|
|||||||
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 DLmaster361'"
|
" --copyright='Copyright © 2024-2025 DLmaster361'"
|
||||||
" --assume-yes-for-downloads --output-filename=AUTO_MAA"
|
" --assume-yes-for-downloads --output-filename=AUTO_MAA"
|
||||||
" --remove-output main.py"
|
" --remove-output main.py"
|
||||||
)
|
)
|
||||||
|
|
||||||
print("AUTO_MAA main program packaging completed !")
|
print("AUTO_MAA main program packaging completed !")
|
||||||
|
|
||||||
shutil.copy(root_path / "app/utils/Updater.py", root_path)
|
|
||||||
|
|
||||||
file_content = (root_path / "Updater.py").read_text(encoding="utf-8")
|
|
||||||
|
|
||||||
(root_path / "Updater.py").write_text(
|
|
||||||
file_content.replace(
|
|
||||||
"from .version import version_text", "from app import version_text"
|
|
||||||
),
|
|
||||||
encoding="utf-8",
|
|
||||||
)
|
|
||||||
|
|
||||||
print("Packaging AUTO_MAA update program ...")
|
print("Packaging AUTO_MAA update program ...")
|
||||||
|
|
||||||
|
shutil.copy(root_path / "app/utils/downloader.py", root_path)
|
||||||
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=disable"
|
||||||
@@ -94,16 +98,51 @@ if __name__ == "__main__":
|
|||||||
f" --file-version={version["updater_version"]}"
|
f" --file-version={version["updater_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 DLmaster361'"
|
" --copyright='Copyright © 2024-2025 DLmaster361'"
|
||||||
" --assume-yes-for-downloads --output-filename=Updater"
|
" --assume-yes-for-downloads --output-filename=AUTO_Updater"
|
||||||
" --remove-output Updater.py"
|
" --remove-output downloader.py"
|
||||||
)
|
)
|
||||||
|
(root_path / "downloader.py").unlink()
|
||||||
|
|
||||||
print("AUTO_MAA update program packaging completed !")
|
print("AUTO_MAA update program packaging completed !")
|
||||||
|
|
||||||
(root_path / "Updater.py").unlink()
|
(root_path / "AUTO_MAA").mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
print("Start to move AUTO_MAA program ...")
|
||||||
|
|
||||||
|
shutil.move(root_path / "AUTO_MAA.exe", root_path / "AUTO_MAA/")
|
||||||
|
shutil.move(root_path / "AUTO_Updater.exe", root_path / "AUTO_MAA/")
|
||||||
|
|
||||||
|
print("Start to copy rescourses ...")
|
||||||
|
|
||||||
|
shutil.copytree(root_path / "app", root_path / "AUTO_MAA/app")
|
||||||
|
shutil.copytree(root_path / "resources", root_path / "AUTO_MAA/resources")
|
||||||
|
shutil.copy(root_path / "main.py", root_path / "AUTO_MAA/")
|
||||||
|
shutil.copy(root_path / "requirements.txt", root_path / "AUTO_MAA/")
|
||||||
|
shutil.copy(root_path / "README.md", root_path / "AUTO_MAA/")
|
||||||
|
shutil.copy(root_path / "LICENSE", root_path / "AUTO_MAA/")
|
||||||
|
|
||||||
|
print("Start to compress ...")
|
||||||
|
|
||||||
|
shutil.make_archive(
|
||||||
|
base_name=root_path / f"AUTO_MAA_{version_text(main_version_numb)}",
|
||||||
|
format="zip",
|
||||||
|
root_dir=root_path / "AUTO_MAA",
|
||||||
|
base_dir=".",
|
||||||
|
)
|
||||||
|
shutil.rmtree(root_path / "AUTO_MAA")
|
||||||
|
|
||||||
|
print("compress completed !")
|
||||||
|
|
||||||
|
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(
|
(root_path / "version_info.txt").write_text(
|
||||||
f"{version_text(main_version_numb)}\n{version_text(updater_version_numb)}{version["announcement"]}",
|
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)}",
|
||||||
encoding="utf-8",
|
encoding="utf-8",
|
||||||
)
|
)
|
||||||
|
|||||||
10
main.py
10
main.py
@@ -1,5 +1,5 @@
|
|||||||
# <AUTO_MAA:A MAA Multi Account Management and Automation Tool>
|
# AUTO_MAA:A MAA Multi Account Management and Automation Tool
|
||||||
# Copyright © <2024> <DLmaster361>
|
# Copyright © 2024-2025 DLmaster361
|
||||||
|
|
||||||
# This file is part of AUTO_MAA.
|
# This file is part of AUTO_MAA.
|
||||||
|
|
||||||
@@ -16,18 +16,17 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
# DLmaster_361@163.com
|
# Contact: DLmaster_361@163.com
|
||||||
|
|
||||||
"""
|
"""
|
||||||
AUTO_MAA
|
AUTO_MAA
|
||||||
AUTO_MAA主程序
|
AUTO_MAA主程序
|
||||||
v4.2
|
v4.3
|
||||||
作者:DLmaster_361
|
作者:DLmaster_361
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from PySide6.QtWidgets import QApplication
|
from PySide6.QtWidgets import QApplication
|
||||||
from PySide6.QtCore import Qt
|
|
||||||
from qfluentwidgets import FluentTranslator
|
from qfluentwidgets import FluentTranslator
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
@@ -36,7 +35,6 @@ import sys
|
|||||||
def main():
|
def main():
|
||||||
|
|
||||||
application = QApplication(sys.argv)
|
application = QApplication(sys.argv)
|
||||||
QApplication.setAttribute(Qt.AA_DontCreateNativeWidgetSiblings)
|
|
||||||
|
|
||||||
translator = FluentTranslator()
|
translator = FluentTranslator()
|
||||||
application.installTranslator(translator)
|
application.installTranslator(translator)
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ pywin32
|
|||||||
pyautogui
|
pyautogui
|
||||||
pycryptodome
|
pycryptodome
|
||||||
requests
|
requests
|
||||||
|
markdown
|
||||||
Jinja2
|
Jinja2
|
||||||
serverchan_sdk
|
serverchan_sdk
|
||||||
nuitka==2.6
|
nuitka
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"main_version": "4.2.0.0",
|
|
||||||
"updater_version": "1.1.0.0",
|
|
||||||
"announcement": "\n# 这是一个中转版本,此版本后更换程序架构方式。\n# 由于更新方法无法通用,您需要在完成本次更新后再次检查更新以获取最新版本。\n",
|
|
||||||
"proxy_list":[
|
|
||||||
"",
|
|
||||||
"https://gitproxy.click/",
|
|
||||||
"https://cdn.moran233.xyz/",
|
|
||||||
"https://gh.llkk.cc/",
|
|
||||||
"https://github.akams.cn/",
|
|
||||||
"https://www.ghproxy.cn/",
|
|
||||||
"https://ghfast.top/"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -6,11 +6,21 @@
|
|||||||
"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.Quantity": "999" #吃理智药数量
|
||||||
"MainFunction.Stage1": "" #主关卡
|
"MainFunction.Stage1": "" #主关卡
|
||||||
"MainFunction.Stage2": "" #备选关卡1
|
"MainFunction.Stage2": "" #备选关卡1
|
||||||
"MainFunction.Stage3": "" #备选关卡2
|
"MainFunction.Stage3": "" #备选关卡2
|
||||||
@@ -28,14 +38,16 @@
|
|||||||
"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": "" #自定义基建配置文件地址
|
||||||
#设置
|
#设置
|
||||||
"Start.ClientType": "Bilibili"、 "Official" #服务器
|
"Start.ClientType": "Bilibili"、 "Official" #服务器
|
||||||
G"Timer.Timer1": "False" #时间设置1
|
G"Timer.Timer1": "False" #时间设置1
|
||||||
|
"Connect.AdbPath" #ADB路径
|
||||||
|
"Connect.Address": "127.0.0.1:16448" #连接地址
|
||||||
G"VersionUpdate.ScheduledUpdateCheck": "True" #定时检查更新
|
G"VersionUpdate.ScheduledUpdateCheck": "True" #定时检查更新
|
||||||
G"VersionUpdate.AutoDownloadUpdatePackage": "True" #自动下载更新包
|
G"VersionUpdate.AutoDownloadUpdatePackage": "True" #自动下载更新包
|
||||||
G"VersionUpdate.AutoInstallUpdatePackage": "True" #自动安装更新包
|
G"VersionUpdate.AutoInstallUpdatePackage": "True" #自动安装更新包
|
||||||
@@ -44,4 +56,6 @@ G"Start.MinimizeDirectly": "True" #启动MAA后直接最小化
|
|||||||
"Start.OpenEmulatorAfterLaunch": "True" #启动MAA后自动开启模拟器
|
"Start.OpenEmulatorAfterLaunch": "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" #附加命令
|
||||||
|
G"VersionUpdate.package": "MirrorChyanAppv5.15.6.zip" #更新包标识
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 2.8 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 61 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 21 KiB |
@@ -1,14 +1,61 @@
|
|||||||
{
|
{
|
||||||
"main_version": "4.2.5.1",
|
"main_version": "4.3.6.2",
|
||||||
"updater_version": "1.1.2.1",
|
"updater_version": "1.0.0.0",
|
||||||
"announcement": "\n## 新增功能\n- 暂无\n## 修复BUG\n- 修复统计信息HTML模板公招匹配错误\n## 程序优化\n- 暂无",
|
"announcement": "\n## 新增功能\n- 屏蔽MuMu模拟器开屏广告功能上线\n- 更新器支持多线程下载\n- 添加强制关闭ADB与模拟器等增强任务项\n## 修复BUG\n- 修复统计信息HTML模板公招匹配错误\n- 修复密码显示按钮动画异常\n- 修复`检测到MAA未能实际执行任务`报错被异常屏蔽\n- 修复MAA超时判定异常失效\n## 程序优化\n- 关机等电源操作添加100s倒计时\n- 人工排查弹窗方法优化\n- 人工排查时自动屏蔽静默操作\n- 公告样式优化",
|
||||||
|
"version_info": {
|
||||||
|
"4.3.6.2": {
|
||||||
|
"新增功能": [
|
||||||
|
"新增`无人值守模式`"
|
||||||
|
],
|
||||||
|
"修复BUG": [
|
||||||
|
"修复软件窗口最大化异常问题",
|
||||||
|
"修复异常操作导致窗口离开屏幕后难以复原的问题",
|
||||||
|
"修正剩余理智关卡文案",
|
||||||
|
"修复隐藏到托盘时,托盘无法退出主程序的问题",
|
||||||
|
"修复Server酱网络异常导致的卡死问题"
|
||||||
|
],
|
||||||
|
"程序优化": [
|
||||||
|
"主窗口显示版本号"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"4.3.6.1": {
|
||||||
|
"新增功能": [
|
||||||
|
"单次自动代理任务中,已完成的子任务在重复执行时不再启用"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"4.3.5.0": {
|
||||||
|
"新增功能": [
|
||||||
|
"用户设置中新增连战次数与剩余理智关卡两项配置项",
|
||||||
|
"支持自动代理时更新MAA"
|
||||||
|
],
|
||||||
|
"修复BUG": [
|
||||||
|
"适配MAAv5.16.0基建模式",
|
||||||
|
"适配自定义基建自动轮换功能"
|
||||||
|
],
|
||||||
|
"程序优化": [
|
||||||
|
"移除增效任务"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"4.3.5.2": {
|
||||||
|
"修复BUG": [
|
||||||
|
"修复无法建立网络连接时软件卡死问题"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"4.3.5.1": {
|
||||||
|
"程序优化": [
|
||||||
|
"模拟器路径适配快捷方式"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"proxy_list": [
|
"proxy_list": [
|
||||||
"",
|
|
||||||
"https://gitproxy.click/",
|
"https://gitproxy.click/",
|
||||||
"https://cdn.moran233.xyz/",
|
"https://cdn.moran233.xyz/",
|
||||||
"https://gh.llkk.cc/",
|
"https://gh.llkk.cc/",
|
||||||
"https://github.akams.cn/",
|
"https://github.akams.cn/",
|
||||||
"https://www.ghproxy.cn/",
|
"https://www.ghproxy.cn/",
|
||||||
"https://ghfast.top/"
|
"https://ghfast.top/"
|
||||||
]
|
],
|
||||||
|
"download_dict": {
|
||||||
|
"官方下载站-jp": "https://jp-download.fearr.xyz/AUTO_MAA/"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user