Compare commits
60 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0572caa528 | ||
|
|
4233040585 | ||
|
|
c27dc8e380 | ||
|
|
e746756e56 | ||
|
|
1829d1cd0b | ||
|
|
fb979e5639 | ||
|
|
e7d0a85ad5 | ||
|
|
a384711327 | ||
|
|
3fd4778a48 | ||
|
|
4841dc09b3 | ||
|
|
b3aa4fc776 | ||
|
|
a9b3b8b6f4 | ||
|
|
56ef196695 | ||
| 242238d341 | |||
| f66f6d38fe | |||
| d58077f58b | |||
| 4d4d6dbedf | |||
| f60b276916 | |||
| 87857fd499 | |||
| 3c371cd079 | |||
|
|
428b849bcc | ||
|
|
85f3b4f607 | ||
|
|
916396f855 | ||
|
|
211c8d2b04 | ||
|
|
92e274d3fd | ||
|
|
d511ea48d5 | ||
|
|
1aa4da1adf | ||
|
|
0e8b6b0b6b | ||
|
|
1a2c1b976f | ||
|
|
1cc242fa51 | ||
|
|
18dfdba15d | ||
|
|
b04ac4eec6 | ||
|
|
c009f0c891 | ||
|
|
d2dc0bd295 | ||
|
|
ddbb5b7f19 | ||
|
|
954c25090b | ||
|
|
0b6cc59de1 | ||
|
|
2271b5741d | ||
|
|
8a438b041f | ||
|
|
dd92fcc4d8 | ||
|
|
8f66ca0e16 | ||
|
|
895ba1d24a | ||
| e49b807bef | |||
|
|
73c15b5e93 | ||
|
|
e505ea8c51 | ||
|
|
21e7df7c3e | ||
|
|
2d72ca66a4 | ||
|
|
4725a30165 | ||
|
|
f3c977f1b3 | ||
|
|
9a0e7265c6 | ||
|
|
3f8e2fbe6b | ||
|
|
590b13e916 | ||
|
|
0f6aee56e5 | ||
|
|
daf18e7295 | ||
|
|
9bcc87f663 | ||
|
|
e7205ce0aa | ||
|
|
e3c4b2edc8 | ||
|
|
222a3b35a2 | ||
|
|
cd5dfd56b2 | ||
|
|
7d5c6b8222 |
178
.github/workflows/build-app.yml
vendored
178
.github/workflows/build-app.yml
vendored
@@ -28,9 +28,11 @@ permissions:
|
|||||||
actions: write
|
actions: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
pre_check:
|
pre_check:
|
||||||
name: Pre Checks
|
name: Pre Checks
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Repo Check
|
- name: Repo Check
|
||||||
id: repo_check
|
id: repo_check
|
||||||
@@ -40,67 +42,198 @@ jobs:
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
exit 0
|
exit 0
|
||||||
|
|
||||||
build_AUTO_MAA:
|
build_AUTO_MAA:
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
needs: pre_check
|
needs: pre_check
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
- name: Set up Python 3.12
|
|
||||||
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: "3.12"
|
python-version: '3.12'
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
pip install flake8 pytest
|
pip install flake8 pytest
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
choco install innosetup
|
|
||||||
echo "C:\Program Files (x86)\Inno Setup 6" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
|
||||||
- name: Lint with flake8
|
- name: Lint with flake8
|
||||||
run: |
|
run: |
|
||||||
# stop the build if there are Python syntax errors or undefined names
|
# stop the build if there are Python syntax errors or undefined names
|
||||||
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
|
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
|
||||||
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
|
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
|
||||||
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
|
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
|
||||||
- name: Package
|
|
||||||
id: package
|
- name: Get version
|
||||||
|
id: get_version
|
||||||
run: |
|
run: |
|
||||||
copy app\utils\package.py .\
|
$version = (Get-Content resources/version.json | ConvertFrom-Json).main_version
|
||||||
python package.py
|
echo "main_version=$version" >> $env:GITHUB_OUTPUT
|
||||||
- name: Read version
|
|
||||||
id: read_version
|
- name: Nuitka build main program
|
||||||
|
uses: Nuitka/Nuitka-Action@main
|
||||||
|
with:
|
||||||
|
script-name: main.py
|
||||||
|
mode: app
|
||||||
|
enable-plugins: pyside6
|
||||||
|
onefile-tempdir-spec: "{TEMP}/AUTO_MAA"
|
||||||
|
windows-console-mode: attach
|
||||||
|
windows-icon-from-ico: resources/icons/AUTO_MAA.ico
|
||||||
|
company-name: AUTO_MAA Team
|
||||||
|
product-name: AUTO_MAA
|
||||||
|
file-version: ${{ steps.get_version.outputs.main_version }}
|
||||||
|
product-version: ${{ steps.get_version.outputs.main_version }}
|
||||||
|
file-description: AUTO_MAA Component
|
||||||
|
copyright: Copyright © 2024-2025 DLmaster361
|
||||||
|
assume-yes-for-downloads: true
|
||||||
|
output-file: AUTO_MAA
|
||||||
|
output-dir: AUTO_MAA
|
||||||
|
|
||||||
|
- name: Upload unsigned main program
|
||||||
|
id: upload-unsigned-main-program
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: AUTO_MAA
|
||||||
|
path: AUTO_MAA/AUTO_MAA.exe
|
||||||
|
|
||||||
|
- name: Sign main program
|
||||||
|
id: sign_main_program
|
||||||
|
uses: signpath/github-action-submit-signing-request@v1.2
|
||||||
|
with:
|
||||||
|
api-token: '${{ secrets.SIGNPATH_API_TOKEN }}'
|
||||||
|
organization-id: '787a1d5f-6177-4f30-9559-d2646473584a'
|
||||||
|
project-slug: 'AUTO_MAA'
|
||||||
|
signing-policy-slug: 'release-signing'
|
||||||
|
artifact-configuration-slug: "AUTO_MAA"
|
||||||
|
github-artifact-id: '${{ steps.upload-unsigned-main-program.outputs.artifact-id }}'
|
||||||
|
wait-for-completion: true
|
||||||
|
output-artifact-directory: 'AUTO_MAA'
|
||||||
|
|
||||||
|
- name: Add other resources
|
||||||
|
shell: pwsh
|
||||||
run: |
|
run: |
|
||||||
$MAIN_VERSION=(Get-Content -Path "version_info.txt" -TotalCount 1).Trim()
|
$root = "${{ github.workspace }}"
|
||||||
"AUTO_MAA_version=$MAIN_VERSION" | Out-File -FilePath $env:GITHUB_ENV -Append
|
$ver = "${{ steps.get_version.outputs.main_version }}"
|
||||||
|
Copy-Item "$root/app" "$root/AUTO_MAA/app" -Recurse
|
||||||
|
Copy-Item "$root/resources" "$root/AUTO_MAA/resources" -Recurse
|
||||||
|
Copy-Item "$root/main.py" "$root/AUTO_MAA/"
|
||||||
|
Copy-Item "$root/requirements.txt" "$root/AUTO_MAA/"
|
||||||
|
Copy-Item "$root/README.md" "$root/AUTO_MAA/"
|
||||||
|
Copy-Item "$root/LICENSE" "$root/AUTO_MAA/"
|
||||||
|
|
||||||
|
- name: Create Inno Setup script
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
$root = "${{ github.workspace }}"
|
||||||
|
$ver = "${{ steps.get_version.outputs.main_version }}"
|
||||||
|
$iss = Get-Content "$root/app/utils/AUTO_MAA.iss" -Raw
|
||||||
|
$iss = $iss -replace '#define MyAppVersion ""', "#define MyAppVersion `"$ver`""
|
||||||
|
$iss = $iss -replace '#define MyAppPath ""', "#define MyAppPath `"$root/AUTO_MAA`""
|
||||||
|
$iss = $iss -replace '#define OutputDir ""', "#define OutputDir `"$root`""
|
||||||
|
Set-Content -Path "$root/AUTO_MAA.iss" -Value $iss
|
||||||
|
|
||||||
|
- name: Build setup program
|
||||||
|
uses: Minionguyjpro/Inno-Setup-Action@v1.2.5
|
||||||
|
with:
|
||||||
|
path: AUTO_MAA.iss
|
||||||
|
|
||||||
|
- name: Upload unsigned setup program
|
||||||
|
id: upload-unsigned-setup-program
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: AUTO_MAA-Setup
|
||||||
|
path: AUTO_MAA-Setup.exe
|
||||||
|
|
||||||
|
- name: Sign setup program
|
||||||
|
id: sign_setup_program
|
||||||
|
uses: signpath/github-action-submit-signing-request@v1.2
|
||||||
|
with:
|
||||||
|
api-token: '${{ secrets.SIGNPATH_API_TOKEN }}'
|
||||||
|
organization-id: '787a1d5f-6177-4f30-9559-d2646473584a'
|
||||||
|
project-slug: 'AUTO_MAA'
|
||||||
|
signing-policy-slug: 'release-signing'
|
||||||
|
artifact-configuration-slug: "AUTO_MAA-Setup"
|
||||||
|
github-artifact-id: '${{ steps.upload-unsigned-setup-program.outputs.artifact-id }}'
|
||||||
|
wait-for-completion: true
|
||||||
|
output-artifact-directory: 'AUTO_MAA_Setup'
|
||||||
|
|
||||||
|
- name: Compress setup exe
|
||||||
|
shell: pwsh
|
||||||
|
run: Compress-Archive -Path AUTO_MAA_Setup/* -DestinationPath AUTO_MAA_${{ steps.get_version.outputs.main_version }}.zip
|
||||||
|
|
||||||
|
- name: Generate version info
|
||||||
|
shell: python
|
||||||
|
run: |
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
def version_text(version_numb):
|
||||||
|
while len(version_numb) < 4:
|
||||||
|
version_numb.append(0)
|
||||||
|
if version_numb[3] == 0:
|
||||||
|
return f"v{'.'.join(str(_) for _ in version_numb[0:3])}"
|
||||||
|
else:
|
||||||
|
return f"v{'.'.join(str(_) for _ in version_numb[0:3])}-beta.{version_numb[3]}"
|
||||||
|
def version_info_markdown(info):
|
||||||
|
version_info = ""
|
||||||
|
for key, value in info.items():
|
||||||
|
version_info += f"## {key}\n"
|
||||||
|
for v in value:
|
||||||
|
version_info += f"- {v}\n"
|
||||||
|
return version_info
|
||||||
|
root_path = Path(".")
|
||||||
|
version = json.loads((root_path / "resources/version.json").read_text(encoding="utf-8"))
|
||||||
|
main_version_numb = list(map(int, version["main_version"].split(".")))
|
||||||
|
all_version_info = {}
|
||||||
|
for v_i in version["version_info"].values():
|
||||||
|
for key, value in v_i.items():
|
||||||
|
if key in all_version_info:
|
||||||
|
all_version_info[key] += value.copy()
|
||||||
|
else:
|
||||||
|
all_version_info[key] = value.copy()
|
||||||
|
(root_path / "version_info.txt").write_text(
|
||||||
|
f"{version_text(main_version_numb)}\n\n<!--{json.dumps(version['version_info'], ensure_ascii=False)}-->\n{version_info_markdown(all_version_info)}",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
- name: Upload Artifact
|
- name: Upload Artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: AUTO_MAA_${{ env.AUTO_MAA_version }}
|
name: AUTO_MAA_${{ steps.get_version.outputs.main_version }}
|
||||||
path: AUTO_MAA_${{ env.AUTO_MAA_version }}.zip
|
path: AUTO_MAA_${{ steps.get_version.outputs.main_version }}.zip
|
||||||
|
|
||||||
- name: Upload Version_Info Artifact
|
- name: Upload Version_Info Artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: version_info
|
name: version_info
|
||||||
path: version_info.txt
|
path: version_info.txt
|
||||||
|
|
||||||
publish_release:
|
publish_release:
|
||||||
name: Publish release
|
name: Publish release
|
||||||
needs: build_AUTO_MAA
|
needs: build_AUTO_MAA
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Download artifacts
|
- name: Download artifacts
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
pattern: AUTO_MAA_*
|
pattern: AUTO_MAA_*
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
path: artifacts
|
path: artifacts
|
||||||
|
|
||||||
- name: Download Version_Info
|
- name: Download Version_Info
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: version_info
|
name: version_info
|
||||||
path: ./
|
path: ./
|
||||||
|
|
||||||
- name: Create release
|
- name: Create release
|
||||||
id: create_release
|
id: create_release
|
||||||
run: |
|
run: |
|
||||||
@@ -110,8 +243,20 @@ jobs:
|
|||||||
TAGNAME="$(sed 's/\r$//g' <(head -n 1 version_info.txt))"
|
TAGNAME="$(sed 's/\r$//g' <(head -n 1 version_info.txt))"
|
||||||
NOTES_MAIN="$(sed 's/\r$//g' <(tail -n +3 version_info.txt))"
|
NOTES_MAIN="$(sed 's/\r$//g' <(tail -n +3 version_info.txt))"
|
||||||
NOTES="$NOTES_MAIN
|
NOTES="$NOTES_MAIN
|
||||||
|
|
||||||
[已有 Mirror酱 CDK ?前往 Mirror酱 高速下载](https://mirrorchyan.com/zh/projects?rid=AUTO_MAA)
|
## 代码签名策略(Code signing policy)
|
||||||
|
|
||||||
|
Free code signing provided by [SignPath.io](https://signpath.io/), certificate by [SignPath Foundation](https://signpath.org/)
|
||||||
|
|
||||||
|
- 审批人(Approvers): [DLmaster (@DLmaster361)](https://github.com/DLmaster361)
|
||||||
|
|
||||||
|
## 隐私政策(Privacy policy)
|
||||||
|
|
||||||
|
除非用户、安装者或使用者特别要求,否则本程序不会将任何信息传输到其他网络系统。
|
||||||
|
|
||||||
|
This program will not transfer any information to other networked systems unless specifically requested by the user or the person installing or operating it.
|
||||||
|
|
||||||
|
[已有 Mirror酱 CDK ?前往 Mirror酱 高速下载](https://mirrorchyan.com/zh/projects?rid=AUTO_MAA&source=auto_maa-release)
|
||||||
|
|
||||||
\`\`\`本release通过GitHub Actions自动构建\`\`\`"
|
\`\`\`本release通过GitHub Actions自动构建\`\`\`"
|
||||||
if [ "${{ github.ref_name }}" == "main" ]; then
|
if [ "${{ github.ref_name }}" == "main" ]; then
|
||||||
@@ -122,6 +267,7 @@ jobs:
|
|||||||
gh release create "$TAGNAME" --target "main" --title "$NAME" --notes "$NOTES" $PRERELEASE_FLAG artifacts/*
|
gh release create "$TAGNAME" --target "main" --title "$NAME" --notes "$NOTES" $PRERELEASE_FLAG artifacts/*
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.WORKFLOW_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.WORKFLOW_TOKEN }}
|
||||||
|
|
||||||
- name: Trigger MirrorChyanUploading
|
- name: Trigger MirrorChyanUploading
|
||||||
run: |
|
run: |
|
||||||
gh workflow run --repo $GITHUB_REPOSITORY mirrorchyan
|
gh workflow run --repo $GITHUB_REPOSITORY mirrorchyan
|
||||||
|
|||||||
28
README.md
28
README.md
@@ -13,7 +13,8 @@
|
|||||||
<a href="https://github.com/DLmaster361/AUTO_MAA/issues"><img alt="GitHub Issues" src="https://img.shields.io/github/issues/DLmaster361/AUTO_MAA?style=flat-square"></a>
|
<a href="https://github.com/DLmaster361/AUTO_MAA/issues"><img alt="GitHub Issues" src="https://img.shields.io/github/issues/DLmaster361/AUTO_MAA?style=flat-square"></a>
|
||||||
<a href="https://github.com/DLmaster361/AUTO_MAA/graphs/contributors"><img alt="GitHub Contributors" src="https://img.shields.io/github/contributors/DLmaster361/AUTO_MAA?style=flat-square"></a>
|
<a href="https://github.com/DLmaster361/AUTO_MAA/graphs/contributors"><img alt="GitHub Contributors" src="https://img.shields.io/github/contributors/DLmaster361/AUTO_MAA?style=flat-square"></a>
|
||||||
<a href="https://github.com/DLmaster361/AUTO_MAA/blob/main/LICENSE"><img alt="GitHub License" src="https://img.shields.io/github/license/DLmaster361/AUTO_MAA?style=flat-square"></a>
|
<a href="https://github.com/DLmaster361/AUTO_MAA/blob/main/LICENSE"><img alt="GitHub License" src="https://img.shields.io/github/license/DLmaster361/AUTO_MAA?style=flat-square"></a>
|
||||||
<a href="https://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>
|
<a href="https://deepwiki.com/DLmaster361/AUTO_MAA"><img alt="DeepWiki" src="https://deepwiki.com/badge.svg"></a>
|
||||||
|
<a href="https://mirrorchyan.com/zh/projects?rid=AUTO_MAA&source=auto_maa-readme"><img alt="mirrorc" src="https://img.shields.io/badge/Mirror%E9%85%B1-%239af3f6?logo=countingworkspro&logoColor=4f46e5"></a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
## 软件介绍
|
## 软件介绍
|
||||||
@@ -51,13 +52,10 @@
|
|||||||
- **传播:** AUTO_MAA原则上允许传播者自由传播本软件,但无论在何种传播过程中,不得删除项目作者与开发者所留版权声明,不得隐瞒项目作者与相关开发者的存在。由于软件性质,项目组不希望发现任何人在明日方舟官方媒体(包括官方媒体账号与森空岛社区等)或明日方舟游戏相关内容(包括同好群、线下活动与游戏内容讨论等)下提及AUTO_MAA或MAA,希望各位理解。
|
- **传播:** AUTO_MAA原则上允许传播者自由传播本软件,但无论在何种传播过程中,不得删除项目作者与开发者所留版权声明,不得隐瞒项目作者与相关开发者的存在。由于软件性质,项目组不希望发现任何人在明日方舟官方媒体(包括官方媒体账号与森空岛社区等)或明日方舟游戏相关内容(包括同好群、线下活动与游戏内容讨论等)下提及AUTO_MAA或MAA,希望各位理解。
|
||||||
- **衍生:** AUTO_MAA允许任何人对软件本体或软件部分代码进行二次开发或利用。但依据GPL,相关成果再次分发时也必须使用GPL或兼容的协议开源。
|
- **衍生:** AUTO_MAA允许任何人对软件本体或软件部分代码进行二次开发或利用。但依据GPL,相关成果再次分发时也必须使用GPL或兼容的协议开源。
|
||||||
- **贡献:** 不论是直接参与软件的维护编写,或是撰写文档、测试、反馈BUG、给出建议、参与讨论,都为AUTO_MAA项目的发展完善做出了不可忽视的贡献。项目组提倡各位贡献者遵照GitHub开源社区惯例,发布Issues参与项目。避免私信或私发邮件(安全性漏洞或敏感问题除外),以帮助更多用户。
|
- **贡献:** 不论是直接参与软件的维护编写,或是撰写文档、测试、反馈BUG、给出建议、参与讨论,都为AUTO_MAA项目的发展完善做出了不可忽视的贡献。项目组提倡各位贡献者遵照GitHub开源社区惯例,发布Issues参与项目。避免私信或私发邮件(安全性漏洞或敏感问题除外),以帮助更多用户。
|
||||||
|
- **图像:** `AUTO_MAA主页默认图像` 并不适用开源协议,著作权归 [NARINpopo](https://space.bilibili.com/1877154) 画师所有,商业使用权归 [DLmaster (@DLmaster361)](https://github.com/DLmaster361) 所有,软件用户仅拥有非商业使用权。不得以开源协议已授权为由在未经授权的情况下使用 `AUTO_MAA主页默认图像`,不得在未经授权的情况下将 `AUTO_MAA主页默认图像` 用于任何商业用途。
|
||||||
|
|
||||||
以上细则是本项目对GPL的相关补充与强调。未提及的以GPL为准,发生冲突的以本细则为准。如有不清楚的部分,请发Issues询问。若发生纠纷,相关内容也没有在Issues上提及的,项目组拥有最终解释权。
|
以上细则是本项目对GPL的相关补充与强调。未提及的以GPL为准,发生冲突的以本细则为准。如有不清楚的部分,请发Issues询问。若发生纠纷,相关内容也没有在Issues上提及的,项目组拥有最终解释权。
|
||||||
|
|
||||||
**注意**
|
|
||||||
|
|
||||||
- 由于本软件有修改其它目录JSON文件等行为,使用前请将AUTO_MAA添加入Windows Defender信任区以及防病毒软件的信任区或开发者目录,避免被误杀。
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# 使用方法
|
# 使用方法
|
||||||
@@ -74,6 +72,24 @@
|
|||||||
|
|
||||||
可在[《AUTO_MAA开发者协作文档》](https://docs.qq.com/aio/DQ3Z5eHNxdmxFQmZX)的`开发任务`页面中查看开发进度。
|
可在[《AUTO_MAA开发者协作文档》](https://docs.qq.com/aio/DQ3Z5eHNxdmxFQmZX)的`开发任务`页面中查看开发进度。
|
||||||
|
|
||||||
|
## 代码签名策略(Code signing policy)
|
||||||
|
|
||||||
|
Free code signing provided by [SignPath.io](https://signpath.io/), certificate by [SignPath Foundation](https://signpath.org/)
|
||||||
|
|
||||||
|
- 审批人(Approvers): [DLmaster (@DLmaster361)](https://github.com/DLmaster361)
|
||||||
|
|
||||||
|
## 隐私政策(Privacy policy)
|
||||||
|
|
||||||
|
除非用户、安装者或使用者特别要求,否则本程序不会将任何信息传输到其他网络系统。
|
||||||
|
|
||||||
|
This program will not transfer any information to other networked systems unless specifically requested by the user or the person installing or operating it.
|
||||||
|
|
||||||
|
## 特别鸣谢
|
||||||
|
|
||||||
|
- 下载服务器:由[AoXuan (@ClozyA)](https://github.com/ClozyA) 个人为项目赞助。
|
||||||
|
|
||||||
|
- EXE签名: 由 [SignPath.io](https://signpath.io/)提供免费代码签名,签名来自[SignPath Foundation](https://signpath.org/)。
|
||||||
|
|
||||||
## 贡献者
|
## 贡献者
|
||||||
|
|
||||||
感谢以下贡献者对本项目做出的贡献
|
感谢以下贡献者对本项目做出的贡献
|
||||||
@@ -86,8 +102,6 @@
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
感谢 [AoXuan (@ClozyA)](https://github.com/ClozyA) 为本项目提供的下载服务器
|
|
||||||
|
|
||||||
## Star History
|
## Star History
|
||||||
|
|
||||||
[](https://star-history.com/#DLmaster361/AUTO_MAA&Date)
|
[](https://star-history.com/#DLmaster361/AUTO_MAA&Date)
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ __license__ = "GPL-3.0 license"
|
|||||||
from .config import QueueConfig, MaaConfig, MaaUserConfig, MaaPlanConfig, Config
|
from .config import QueueConfig, MaaConfig, MaaUserConfig, MaaPlanConfig, Config
|
||||||
from .main_info_bar import MainInfoBar
|
from .main_info_bar import MainInfoBar
|
||||||
from .network import Network
|
from .network import Network
|
||||||
|
from .sound_player import SoundPlayer
|
||||||
from .task_manager import Task, TaskManager
|
from .task_manager import Task, TaskManager
|
||||||
from .timer import MainTimer
|
from .timer import MainTimer
|
||||||
|
|
||||||
@@ -43,6 +44,7 @@ __all__ = [
|
|||||||
"MaaPlanConfig",
|
"MaaPlanConfig",
|
||||||
"MainInfoBar",
|
"MainInfoBar",
|
||||||
"Network",
|
"Network",
|
||||||
|
"SoundPlayer",
|
||||||
"Task",
|
"Task",
|
||||||
"TaskManager",
|
"TaskManager",
|
||||||
"MainTimer",
|
"MainTimer",
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ v4.3
|
|||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from PySide6.QtCore import Signal
|
from PySide6.QtCore import Signal
|
||||||
|
import argparse
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import json
|
import json
|
||||||
import sys
|
import sys
|
||||||
@@ -185,6 +186,11 @@ class GlobalConfig(LQConfig):
|
|||||||
"Function", "IfSkipMumuSplashAds", False, BoolValidator()
|
"Function", "IfSkipMumuSplashAds", False, BoolValidator()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.voice_Enabled = ConfigItem("Voice", "Enabled", False, BoolValidator())
|
||||||
|
self.voice_Type = OptionsConfigItem(
|
||||||
|
"Voice", "Type", "simple", OptionsValidator(["simple", "noisy"])
|
||||||
|
)
|
||||||
|
|
||||||
self.start_IfSelfStart = ConfigItem(
|
self.start_IfSelfStart = ConfigItem(
|
||||||
"Start", "IfSelfStart", False, BoolValidator()
|
"Start", "IfSelfStart", False, BoolValidator()
|
||||||
)
|
)
|
||||||
@@ -421,11 +427,14 @@ class MaaUserConfig(LQConfig):
|
|||||||
self.Info_GameId_1 = ConfigItem("Info", "GameId_1", "-")
|
self.Info_GameId_1 = ConfigItem("Info", "GameId_1", "-")
|
||||||
self.Info_GameId_2 = ConfigItem("Info", "GameId_2", "-")
|
self.Info_GameId_2 = ConfigItem("Info", "GameId_2", "-")
|
||||||
self.Info_GameId_Remain = ConfigItem("Info", "GameId_Remain", "-")
|
self.Info_GameId_Remain = ConfigItem("Info", "GameId_Remain", "-")
|
||||||
|
self.Info_IfSkland = ConfigItem("Info", "IfSkland", False, BoolValidator())
|
||||||
|
self.Info_SklandToken = ConfigItem("Info", "SklandToken", "")
|
||||||
|
|
||||||
self.Data_LastProxyDate = ConfigItem("Data", "LastProxyDate", "2000-01-01")
|
self.Data_LastProxyDate = ConfigItem("Data", "LastProxyDate", "2000-01-01")
|
||||||
self.Data_LastAnnihilationDate = ConfigItem(
|
self.Data_LastAnnihilationDate = ConfigItem(
|
||||||
"Data", "LastAnnihilationDate", "2000-01-01"
|
"Data", "LastAnnihilationDate", "2000-01-01"
|
||||||
)
|
)
|
||||||
|
self.Data_LastSklandDate = ConfigItem("Data", "LastSklandDate", "2000-01-01")
|
||||||
self.Data_ProxyTimes = ConfigItem(
|
self.Data_ProxyTimes = ConfigItem(
|
||||||
"Data", "ProxyTimes", 0, RangeValidator(0, 1024)
|
"Data", "ProxyTimes", 0, RangeValidator(0, 1024)
|
||||||
)
|
)
|
||||||
@@ -567,7 +576,7 @@ class MaaPlanConfig(LQConfig):
|
|||||||
|
|
||||||
class AppConfig(GlobalConfig):
|
class AppConfig(GlobalConfig):
|
||||||
|
|
||||||
VERSION = "4.3.8.0"
|
VERSION = "4.3.12.0"
|
||||||
|
|
||||||
gameid_refreshed = Signal()
|
gameid_refreshed = Signal()
|
||||||
PASSWORD_refreshed = Signal()
|
PASSWORD_refreshed = Signal()
|
||||||
@@ -606,6 +615,27 @@ class AppConfig(GlobalConfig):
|
|||||||
self.if_ignore_silence = False
|
self.if_ignore_silence = False
|
||||||
self.if_database_opened = False
|
self.if_database_opened = False
|
||||||
|
|
||||||
|
self.search_member()
|
||||||
|
self.search_queue()
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
prog="AUTO_MAA",
|
||||||
|
description="A MAA Multi Account Management and Automation Tool",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--mode",
|
||||||
|
choices=["gui", "cli"],
|
||||||
|
default="gui",
|
||||||
|
help="使用UI界面或命令行模式运行程序",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--config",
|
||||||
|
nargs="+",
|
||||||
|
choices=list(self.member_dict.keys()) + list(self.queue_dict.keys()),
|
||||||
|
help="指定需要运行哪些配置项",
|
||||||
|
)
|
||||||
|
self.args = parser.parse_args()
|
||||||
|
|
||||||
self.initialize()
|
self.initialize()
|
||||||
|
|
||||||
def initialize(self) -> None:
|
def initialize(self) -> None:
|
||||||
@@ -628,7 +658,8 @@ class AppConfig(GlobalConfig):
|
|||||||
def init_logger(self) -> None:
|
def init_logger(self) -> None:
|
||||||
"""初始化日志记录器"""
|
"""初始化日志记录器"""
|
||||||
|
|
||||||
logger.remove(0)
|
if self.args.mode != "cli":
|
||||||
|
logger.remove(0)
|
||||||
|
|
||||||
logger.add(
|
logger.add(
|
||||||
sink=self.log_path,
|
sink=self.log_path,
|
||||||
@@ -641,10 +672,14 @@ class AppConfig(GlobalConfig):
|
|||||||
retention="1 month",
|
retention="1 month",
|
||||||
compression="zip",
|
compression="zip",
|
||||||
)
|
)
|
||||||
|
logger.info("")
|
||||||
logger.info("===================================")
|
logger.info("===================================")
|
||||||
logger.info("AUTO_MAA 主程序")
|
logger.info("AUTO_MAA 主程序")
|
||||||
logger.info(f"版本号: v{self.VERSION}")
|
logger.info(f"版本号: v{self.VERSION}")
|
||||||
logger.info(f"根目录: {self.app_path}")
|
logger.info(f"根目录: {self.app_path}")
|
||||||
|
logger.info(
|
||||||
|
f"运行模式: {'图形化界面' if self.args.mode == 'gui' else '命令行界面'}"
|
||||||
|
)
|
||||||
logger.info("===================================")
|
logger.info("===================================")
|
||||||
|
|
||||||
logger.info("日志记录器初始化完成")
|
logger.info("日志记录器初始化完成")
|
||||||
@@ -652,18 +687,20 @@ class AppConfig(GlobalConfig):
|
|||||||
def get_gameid(self) -> None:
|
def get_gameid(self) -> None:
|
||||||
|
|
||||||
# 从MAA服务器获取活动关卡信息
|
# 从MAA服务器获取活动关卡信息
|
||||||
Network.set_info(
|
network = Network.add_task(
|
||||||
mode="get",
|
mode="get",
|
||||||
url="https://api.maa.plus/MaaAssistantArknights/api/gui/StageActivity.json",
|
url="https://api.maa.plus/MaaAssistantArknights/api/gui/StageActivity.json",
|
||||||
)
|
)
|
||||||
Network.start()
|
network.loop.exec()
|
||||||
Network.loop.exec()
|
network_result = Network.get_result(network)
|
||||||
if Network.stutus_code == 200:
|
if network_result["status_code"] == 200:
|
||||||
gameid_infos: List[Dict[str, Union[str, Dict[str, Union[str, int]]]]] = (
|
gameid_infos: List[Dict[str, Union[str, Dict[str, Union[str, int]]]]] = (
|
||||||
Network.response_json["Official"]["sideStoryStage"]
|
network_result["response_json"]["Official"]["sideStoryStage"]
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
logger.warning(f"无法从MAA服务器获取活动关卡信息:{Network.error_message}")
|
logger.warning(
|
||||||
|
f"无法从MAA服务器获取活动关卡信息:{network_result['error_message']}"
|
||||||
|
)
|
||||||
gameid_infos = []
|
gameid_infos = []
|
||||||
|
|
||||||
ss_gameid_dict = {"value": [], "text": []}
|
ss_gameid_dict = {"value": [], "text": []}
|
||||||
@@ -1249,6 +1286,10 @@ class AppConfig(GlobalConfig):
|
|||||||
user_config.Data_LastAnnihilationDate,
|
user_config.Data_LastAnnihilationDate,
|
||||||
info["Config"]["Data"]["LastAnnihilationDate"],
|
info["Config"]["Data"]["LastAnnihilationDate"],
|
||||||
)
|
)
|
||||||
|
user_config.set(
|
||||||
|
user_config.Data_LastSklandDate,
|
||||||
|
info["Config"]["Data"]["LastSklandDate"],
|
||||||
|
)
|
||||||
user_config.set(
|
user_config.set(
|
||||||
user_config.Data_ProxyTimes, info["Config"]["Data"]["ProxyTimes"]
|
user_config.Data_ProxyTimes, info["Config"]["Data"]["ProxyTimes"]
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ from PySide6.QtCore import Qt
|
|||||||
from qfluentwidgets import InfoBar, InfoBarPosition
|
from qfluentwidgets import InfoBar, InfoBarPosition
|
||||||
|
|
||||||
from .config import Config
|
from .config import Config
|
||||||
|
from .sound_player import SoundPlayer
|
||||||
|
|
||||||
|
|
||||||
class _MainInfoBar:
|
class _MainInfoBar:
|
||||||
@@ -79,5 +80,10 @@ class _MainInfoBar:
|
|||||||
if info_bar_item not in Config.info_bar_list:
|
if info_bar_item not in Config.info_bar_list:
|
||||||
Config.info_bar_list.append(info_bar_item)
|
Config.info_bar_list.append(info_bar_item)
|
||||||
|
|
||||||
|
if mode == "warning":
|
||||||
|
SoundPlayer.play("发生异常")
|
||||||
|
if mode == "error":
|
||||||
|
SoundPlayer.play("发生错误")
|
||||||
|
|
||||||
|
|
||||||
MainInfoBar = _MainInfoBar()
|
MainInfoBar = _MainInfoBar()
|
||||||
|
|||||||
@@ -26,55 +26,46 @@ v4.3
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from PySide6.QtCore import QThread, QEventLoop, QTimer
|
from PySide6.QtCore import QObject, QThread, QEventLoop
|
||||||
|
import re
|
||||||
import time
|
import time
|
||||||
import requests
|
import requests
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
class _Network(QThread):
|
class NetworkThread(QThread):
|
||||||
|
"""网络请求线程类"""
|
||||||
|
|
||||||
max_retries = 3
|
max_retries = 3
|
||||||
timeout = 10
|
timeout = 10
|
||||||
backoff_factor = 0.1
|
backoff_factor = 0.1
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self, mode: str, url: str, path: Path = None) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
self.if_running = False
|
self.setObjectName(
|
||||||
self.mode = None
|
f"NetworkThread-{mode}-{re.sub(r'(&cdk=)[^&]+(&)', r'\1******\2', url)}"
|
||||||
self.url = None
|
)
|
||||||
self.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.mode = mode
|
||||||
self.url = url
|
self.url = url
|
||||||
self.path = path
|
self.path = path
|
||||||
|
|
||||||
self.stutus_code = None
|
self.status_code = None
|
||||||
self.response_json = None
|
self.response_json = None
|
||||||
self.error_message = None
|
self.error_message = None
|
||||||
|
|
||||||
|
self.loop = QEventLoop()
|
||||||
|
|
||||||
|
@logger.catch
|
||||||
|
def run(self) -> None:
|
||||||
|
"""运行网络请求线程"""
|
||||||
|
|
||||||
|
if self.mode == "get":
|
||||||
|
self.get_json(self.url)
|
||||||
|
elif self.mode == "get_file":
|
||||||
|
self.get_file(self.url, self.path)
|
||||||
|
|
||||||
def get_json(self, url: str) -> None:
|
def get_json(self, url: str) -> None:
|
||||||
"""通过get方法获取json数据"""
|
"""通过get方法获取json数据"""
|
||||||
|
|
||||||
@@ -83,12 +74,12 @@ class _Network(QThread):
|
|||||||
for _ in range(self.max_retries):
|
for _ in range(self.max_retries):
|
||||||
try:
|
try:
|
||||||
response = requests.get(url, timeout=self.timeout)
|
response = requests.get(url, timeout=self.timeout)
|
||||||
self.stutus_code = response.status_code
|
self.status_code = response.status_code
|
||||||
self.response_json = response.json()
|
self.response_json = response.json()
|
||||||
self.error_message = None
|
self.error_message = None
|
||||||
break
|
break
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.stutus_code = response.status_code if response else None
|
self.status_code = response.status_code if response else None
|
||||||
self.response_json = None
|
self.response_json = None
|
||||||
self.error_message = str(e)
|
self.error_message = str(e)
|
||||||
time.sleep(self.backoff_factor)
|
time.sleep(self.backoff_factor)
|
||||||
@@ -105,16 +96,56 @@ class _Network(QThread):
|
|||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
with open(path, "wb") as file:
|
with open(path, "wb") as file:
|
||||||
file.write(response.content)
|
file.write(response.content)
|
||||||
self.stutus_code = response.status_code
|
self.status_code = response.status_code
|
||||||
else:
|
else:
|
||||||
self.stutus_code = response.status_code
|
self.status_code = response.status_code
|
||||||
self.error_message = "下载失败"
|
self.error_message = "下载失败"
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.stutus_code = response.status_code if response else None
|
self.status_code = response.status_code if response else None
|
||||||
self.error_message = str(e)
|
self.error_message = str(e)
|
||||||
|
|
||||||
self.loop.quit()
|
self.loop.quit()
|
||||||
|
|
||||||
|
|
||||||
|
class _Network(QObject):
|
||||||
|
"""网络请求线程类"""
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
self.task_queue = []
|
||||||
|
|
||||||
|
def add_task(self, mode: str, url: str, path: Path = None) -> NetworkThread:
|
||||||
|
"""添加网络请求任务"""
|
||||||
|
|
||||||
|
network_thread = NetworkThread(mode, url, path)
|
||||||
|
|
||||||
|
self.task_queue.append(network_thread)
|
||||||
|
|
||||||
|
network_thread.start()
|
||||||
|
|
||||||
|
return network_thread
|
||||||
|
|
||||||
|
def get_result(self, network_thread: NetworkThread) -> dict:
|
||||||
|
"""获取网络请求结果"""
|
||||||
|
|
||||||
|
result = {
|
||||||
|
"status_code": network_thread.status_code,
|
||||||
|
"response_json": network_thread.response_json,
|
||||||
|
"error_message": (
|
||||||
|
re.sub(r"(&cdk=)[^&]+(&)", r"\1******\2", network_thread.error_message)
|
||||||
|
if network_thread.error_message
|
||||||
|
else None
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
network_thread.quit()
|
||||||
|
network_thread.wait()
|
||||||
|
self.task_queue.remove(network_thread)
|
||||||
|
network_thread.deleteLater()
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
Network = _Network()
|
Network = _Network()
|
||||||
|
|||||||
69
app/core/sound_player.py
Normal file
69
app/core/sound_player.py
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
# AUTO_MAA:A MAA Multi Account Management and Automation Tool
|
||||||
|
# Copyright © 2024-2025 DLmaster361
|
||||||
|
|
||||||
|
# This file is part of AUTO_MAA.
|
||||||
|
|
||||||
|
# AUTO_MAA is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published
|
||||||
|
# by the Free Software Foundation, either version 3 of the License,
|
||||||
|
# or (at your option) any later version.
|
||||||
|
|
||||||
|
# AUTO_MAA is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||||
|
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
|
||||||
|
# the GNU General Public License for more details.
|
||||||
|
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
# Contact: DLmaster_361@163.com
|
||||||
|
|
||||||
|
"""
|
||||||
|
AUTO_MAA
|
||||||
|
AUTO_MAA音效播放器
|
||||||
|
v4.3
|
||||||
|
作者:DLmaster_361
|
||||||
|
"""
|
||||||
|
|
||||||
|
from loguru import logger
|
||||||
|
from PySide6.QtCore import QObject, QUrl
|
||||||
|
from PySide6.QtMultimedia import QSoundEffect
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
from .config import Config
|
||||||
|
|
||||||
|
|
||||||
|
class _SoundPlayer(QObject):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
self.sounds_path = Config.app_path / "resources/sounds"
|
||||||
|
|
||||||
|
def play(self, sound_name: str):
|
||||||
|
|
||||||
|
if not Config.get(Config.voice_Enabled):
|
||||||
|
return
|
||||||
|
|
||||||
|
if (self.sounds_path / f"both/{sound_name}.wav").exists():
|
||||||
|
|
||||||
|
self.play_voice(self.sounds_path / f"both/{sound_name}.wav")
|
||||||
|
|
||||||
|
elif (
|
||||||
|
self.sounds_path / Config.get(Config.voice_Type) / f"{sound_name}.wav"
|
||||||
|
).exists():
|
||||||
|
|
||||||
|
self.play_voice(
|
||||||
|
self.sounds_path / Config.get(Config.voice_Type) / f"{sound_name}.wav"
|
||||||
|
)
|
||||||
|
|
||||||
|
def play_voice(self, sound_path: Path):
|
||||||
|
|
||||||
|
effect = QSoundEffect(self)
|
||||||
|
effect.setVolume(1)
|
||||||
|
effect.setSource(QUrl.fromLocalFile(sound_path))
|
||||||
|
effect.play()
|
||||||
|
|
||||||
|
|
||||||
|
SoundPlayer = _SoundPlayer()
|
||||||
@@ -35,6 +35,7 @@ from typing import Dict, Union
|
|||||||
from .config import Config
|
from .config import Config
|
||||||
from .main_info_bar import MainInfoBar
|
from .main_info_bar import MainInfoBar
|
||||||
from .network import Network
|
from .network import Network
|
||||||
|
from .sound_player import SoundPlayer
|
||||||
from app.models import MaaManager
|
from app.models import MaaManager
|
||||||
from app.services import System
|
from app.services import System
|
||||||
|
|
||||||
@@ -44,6 +45,7 @@ class Task(QThread):
|
|||||||
|
|
||||||
check_maa_version = Signal(str)
|
check_maa_version = Signal(str)
|
||||||
push_info_bar = Signal(str, str, str, int)
|
push_info_bar = Signal(str, str, str, int)
|
||||||
|
play_sound = Signal(str)
|
||||||
question = Signal(str, str)
|
question = Signal(str, str)
|
||||||
question_response = Signal(bool)
|
question_response = Signal(bool)
|
||||||
update_user_info = Signal(str, dict)
|
update_user_info = Signal(str, dict)
|
||||||
@@ -59,6 +61,8 @@ class Task(QThread):
|
|||||||
):
|
):
|
||||||
super(Task, self).__init__()
|
super(Task, self).__init__()
|
||||||
|
|
||||||
|
self.setObjectName(f"Task-{mode}-{name}")
|
||||||
|
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
self.name = name
|
self.name = name
|
||||||
self.info = info
|
self.info = info
|
||||||
@@ -82,6 +86,7 @@ class Task(QThread):
|
|||||||
)
|
)
|
||||||
self.task.check_maa_version.connect(self.check_maa_version.emit)
|
self.task.check_maa_version.connect(self.check_maa_version.emit)
|
||||||
self.task.push_info_bar.connect(self.push_info_bar.emit)
|
self.task.push_info_bar.connect(self.push_info_bar.emit)
|
||||||
|
self.task.play_sound.connect(self.play_sound.emit)
|
||||||
self.task.accomplish.connect(lambda: self.accomplish.emit([]))
|
self.task.accomplish.connect(lambda: self.accomplish.emit([]))
|
||||||
|
|
||||||
self.task.run()
|
self.task.run()
|
||||||
@@ -141,6 +146,7 @@ class Task(QThread):
|
|||||||
self.question_response.disconnect()
|
self.question_response.disconnect()
|
||||||
self.question_response.connect(self.task.question_response.emit)
|
self.question_response.connect(self.task.question_response.emit)
|
||||||
self.task.push_info_bar.connect(self.push_info_bar.emit)
|
self.task.push_info_bar.connect(self.push_info_bar.emit)
|
||||||
|
self.task.play_sound.connect(self.play_sound.emit)
|
||||||
self.task.create_user_list.connect(self.create_user_list.emit)
|
self.task.create_user_list.connect(self.create_user_list.emit)
|
||||||
self.task.update_user_list.connect(self.update_user_list.emit)
|
self.task.update_user_list.connect(self.update_user_list.emit)
|
||||||
self.task.update_log_text.connect(self.update_log_text.emit)
|
self.task.update_log_text.connect(self.update_log_text.emit)
|
||||||
@@ -191,6 +197,7 @@ class _TaskManager(QObject):
|
|||||||
|
|
||||||
logger.info(f"任务开始:{name}")
|
logger.info(f"任务开始:{name}")
|
||||||
MainInfoBar.push_info_bar("info", "任务开始", name, 3000)
|
MainInfoBar.push_info_bar("info", "任务开始", name, 3000)
|
||||||
|
SoundPlayer.play("任务开始")
|
||||||
|
|
||||||
Config.running_list.append(name)
|
Config.running_list.append(name)
|
||||||
self.task_dict[name] = Task(mode, name, info)
|
self.task_dict[name] = Task(mode, name, info)
|
||||||
@@ -199,6 +206,7 @@ class _TaskManager(QObject):
|
|||||||
lambda title, content: self.push_dialog(name, title, content)
|
lambda title, content: self.push_dialog(name, title, content)
|
||||||
)
|
)
|
||||||
self.task_dict[name].push_info_bar.connect(MainInfoBar.push_info_bar)
|
self.task_dict[name].push_info_bar.connect(MainInfoBar.push_info_bar)
|
||||||
|
self.task_dict[name].play_sound.connect(SoundPlayer.play)
|
||||||
self.task_dict[name].update_user_info.connect(Config.change_user_info)
|
self.task_dict[name].update_user_info.connect(Config.change_user_info)
|
||||||
self.task_dict[name].accomplish.connect(
|
self.task_dict[name].accomplish.connect(
|
||||||
lambda logs: self.remove_task(mode, name, logs)
|
lambda logs: self.remove_task(mode, name, logs)
|
||||||
@@ -239,6 +247,7 @@ class _TaskManager(QObject):
|
|||||||
|
|
||||||
logger.info(f"任务结束:{name}")
|
logger.info(f"任务结束:{name}")
|
||||||
MainInfoBar.push_info_bar("info", "任务结束", name, 3000)
|
MainInfoBar.push_info_bar("info", "任务结束", name, 3000)
|
||||||
|
SoundPlayer.play("任务结束")
|
||||||
|
|
||||||
self.task_dict[name].deleteLater()
|
self.task_dict[name].deleteLater()
|
||||||
self.task_dict.pop(name)
|
self.task_dict.pop(name)
|
||||||
@@ -274,23 +283,26 @@ class _TaskManager(QObject):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if Config.args.mode == "cli" and Config.power_sign == "NoAction":
|
||||||
|
Config.set_power_sign("KillSelf")
|
||||||
|
|
||||||
def check_maa_version(self, v: str):
|
def check_maa_version(self, v: str):
|
||||||
"""检查MAA版本"""
|
"""检查MAA版本"""
|
||||||
|
|
||||||
Network.set_info(
|
network = Network.add_task(
|
||||||
mode="get",
|
mode="get",
|
||||||
url="https://mirrorchyan.com/api/resources/MAA/latest?user_agent=AutoMaaGui&os=win&arch=x64&channel=stable",
|
url="https://mirrorchyan.com/api/resources/MAA/latest?user_agent=AutoMaaGui&os=win&arch=x64&channel=stable",
|
||||||
)
|
)
|
||||||
Network.start()
|
network.loop.exec()
|
||||||
Network.loop.exec()
|
network_result = Network.get_result(network)
|
||||||
if Network.stutus_code == 200:
|
if network_result["status_code"] == 200:
|
||||||
maa_info = Network.response_json
|
maa_info = network_result["response_json"]
|
||||||
else:
|
else:
|
||||||
logger.warning(f"获取MAA版本信息时出错:{Network.error_message}")
|
logger.warning(f"获取MAA版本信息时出错:{network_result['error_message']}")
|
||||||
MainInfoBar.push_info_bar(
|
MainInfoBar.push_info_bar(
|
||||||
"warning",
|
"warning",
|
||||||
"获取MAA版本信息时出错",
|
"获取MAA版本信息时出错",
|
||||||
f"网络错误:{Network.stutus_code}",
|
f"网络错误:{network_result['status_code']}",
|
||||||
5000,
|
5000,
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -26,24 +26,21 @@ v4.3
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from PySide6.QtWidgets import QWidget
|
from PySide6.QtCore import QObject, QTimer
|
||||||
from PySide6.QtCore import QTimer
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import pyautogui
|
import keyboard
|
||||||
|
|
||||||
from .config import Config
|
from .config import Config
|
||||||
from .task_manager import TaskManager
|
from .task_manager import TaskManager
|
||||||
from app.services import System
|
from app.services import System
|
||||||
|
|
||||||
|
|
||||||
class _MainTimer(QWidget):
|
class _MainTimer(QObject):
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
|
|
||||||
self.if_FailSafeException = False
|
|
||||||
|
|
||||||
self.Timer = QTimer()
|
self.Timer = QTimer()
|
||||||
self.Timer.timeout.connect(self.timed_start)
|
self.Timer.timeout.connect(self.timed_start)
|
||||||
self.Timer.timeout.connect(self.set_silence)
|
self.Timer.timeout.connect(self.set_silence)
|
||||||
@@ -99,31 +96,21 @@ class _MainTimer(QWidget):
|
|||||||
|
|
||||||
windows = System.get_window_info()
|
windows = System.get_window_info()
|
||||||
|
|
||||||
# 排除雷电名为新通知的窗口
|
# 此处排除雷电名为新通知的窗口
|
||||||
windows = [
|
|
||||||
window
|
|
||||||
for window in windows
|
|
||||||
if not (
|
|
||||||
window[0] == "新通知" and Path(window[1]) in Config.silence_list
|
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
if any(
|
if any(
|
||||||
str(emulator_path) in window
|
str(emulator_path) in window and window[0] != "新通知"
|
||||||
for window in windows
|
for window in windows
|
||||||
for emulator_path in Config.silence_list
|
for emulator_path in Config.silence_list
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
pyautogui.hotkey(
|
keyboard.press_and_release(
|
||||||
*[
|
"+".join(
|
||||||
_.strip().lower()
|
_.strip().lower()
|
||||||
for _ in Config.get(Config.function_BossKey).split("+")
|
for _ in Config.get(Config.function_BossKey).split("+")
|
||||||
]
|
)
|
||||||
)
|
)
|
||||||
except pyautogui.FailSafeException as e:
|
except Exception as e:
|
||||||
if not self.if_FailSafeException:
|
logger.error(f"模拟按键时出错:{e}")
|
||||||
logger.warning(f"FailSafeException: {e}")
|
|
||||||
self.if_FailSafeException = True
|
|
||||||
|
|
||||||
def check_power(self):
|
def check_power(self):
|
||||||
|
|
||||||
|
|||||||
@@ -30,16 +30,17 @@ from PySide6.QtCore import QObject, Signal, QEventLoop, QFileSystemWatcher, QTim
|
|||||||
import json
|
import json
|
||||||
import subprocess
|
import subprocess
|
||||||
import shutil
|
import shutil
|
||||||
import time
|
|
||||||
import re
|
import re
|
||||||
import win32com.client
|
import win32com.client
|
||||||
|
from functools import partial
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from jinja2 import Environment, FileSystemLoader
|
from jinja2 import Environment, FileSystemLoader
|
||||||
from typing import Union, List, Dict
|
from typing import Union, List, Dict
|
||||||
|
|
||||||
from app.core import Config, MaaConfig, MaaUserConfig
|
from app.core import Config, MaaConfig, MaaUserConfig
|
||||||
from app.services import Notify, System
|
from app.services import Notify, Crypto, System, skland_sign_in
|
||||||
|
from app.utils.ImageUtils import ImageUtils
|
||||||
|
|
||||||
|
|
||||||
class MaaManager(QObject):
|
class MaaManager(QObject):
|
||||||
@@ -50,6 +51,7 @@ class MaaManager(QObject):
|
|||||||
question_response = Signal(bool)
|
question_response = Signal(bool)
|
||||||
update_user_info = Signal(str, dict)
|
update_user_info = Signal(str, dict)
|
||||||
push_info_bar = Signal(str, str, str, int)
|
push_info_bar = Signal(str, str, str, int)
|
||||||
|
play_sound = Signal(str)
|
||||||
create_user_list = Signal(list)
|
create_user_list = Signal(list)
|
||||||
update_user_list = Signal(list)
|
update_user_list = Signal(list)
|
||||||
update_log_text = Signal(str)
|
update_log_text = Signal(str)
|
||||||
@@ -88,6 +90,8 @@ class MaaManager(QObject):
|
|||||||
self.question_response.connect(self.__capture_response)
|
self.question_response.connect(self.__capture_response)
|
||||||
self.question_response.connect(self.question_loop.quit)
|
self.question_response.connect(self.question_loop.quit)
|
||||||
|
|
||||||
|
self.wait_loop = QEventLoop()
|
||||||
|
|
||||||
self.interrupt.connect(self.quit_monitor)
|
self.interrupt.connect(self.quit_monitor)
|
||||||
|
|
||||||
self.maa_version = None
|
self.maa_version = None
|
||||||
@@ -217,6 +221,59 @@ class MaaManager(QObject):
|
|||||||
user_logs_list = []
|
user_logs_list = []
|
||||||
user_start_time = datetime.now()
|
user_start_time = datetime.now()
|
||||||
|
|
||||||
|
if user_data["Info"]["IfSkland"] and user_data["Info"]["SklandToken"]:
|
||||||
|
|
||||||
|
if user_data["Data"]["LastSklandDate"] != datetime.now().strftime(
|
||||||
|
"%Y-%m-%d"
|
||||||
|
):
|
||||||
|
|
||||||
|
self.update_log_text.emit("正在执行森空岛签到中\n请稍候~")
|
||||||
|
|
||||||
|
skland_result = skland_sign_in(
|
||||||
|
Crypto.win_decryptor(user_data["Info"]["SklandToken"])
|
||||||
|
)
|
||||||
|
|
||||||
|
for type, user_list in skland_result.items():
|
||||||
|
|
||||||
|
if type != "总计" and len(user_list) > 0:
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
f"{self.name} | 用户: {user[0]} - 森空岛签到{type}: {'、'.join(user_list)}"
|
||||||
|
)
|
||||||
|
self.push_info_bar.emit(
|
||||||
|
"info",
|
||||||
|
f"森空岛签到{type}",
|
||||||
|
"、".join(user_list),
|
||||||
|
-1 if type == "失败" else 5000,
|
||||||
|
)
|
||||||
|
|
||||||
|
if skland_result["总计"] == 0:
|
||||||
|
self.push_info_bar.emit(
|
||||||
|
"info",
|
||||||
|
"森空岛签到失败",
|
||||||
|
user[0],
|
||||||
|
-1,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (
|
||||||
|
skland_result["总计"] > 0
|
||||||
|
and len(skland_result["失败"]) == 0
|
||||||
|
):
|
||||||
|
user_data["Data"][
|
||||||
|
"LastSklandDate"
|
||||||
|
] = datetime.now().strftime("%Y-%m-%d")
|
||||||
|
self.play_sound.emit("森空岛签到成功")
|
||||||
|
else:
|
||||||
|
self.play_sound.emit("森空岛签到失败")
|
||||||
|
|
||||||
|
elif user_data["Info"]["IfSkland"]:
|
||||||
|
logger.warning(
|
||||||
|
f"{self.name} | 用户: {user[0]} - 未配置森空岛签到Token,跳过森空岛签到"
|
||||||
|
)
|
||||||
|
self.push_info_bar.emit(
|
||||||
|
"warning", "森空岛签到失败", "未配置鹰角网络通行证登录凭证", -1
|
||||||
|
)
|
||||||
|
|
||||||
# 剿灭-日常模式循环
|
# 剿灭-日常模式循环
|
||||||
for mode in ["Annihilation", "Routine"]:
|
for mode in ["Annihilation", "Routine"]:
|
||||||
|
|
||||||
@@ -426,11 +483,11 @@ class MaaManager(QObject):
|
|||||||
|
|
||||||
# 任务开始前释放ADB
|
# 任务开始前释放ADB
|
||||||
try:
|
try:
|
||||||
|
logger.info(f"{self.name} | 释放ADB:{self.ADB_address}")
|
||||||
subprocess.run(
|
subprocess.run(
|
||||||
[self.ADB_path, "disconnect", self.ADB_address],
|
[self.ADB_path, "disconnect", self.ADB_address],
|
||||||
creationflags=subprocess.CREATE_NO_WINDOW,
|
creationflags=subprocess.CREATE_NO_WINDOW,
|
||||||
)
|
)
|
||||||
logger.info(f"{self.name} | 释放ADB:{self.ADB_address}")
|
|
||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError as e:
|
||||||
# 忽略错误,因为可能本来就没有连接
|
# 忽略错误,因为可能本来就没有连接
|
||||||
logger.warning(f"{self.name} | 释放ADB时出现异常:{e}")
|
logger.warning(f"{self.name} | 释放ADB时出现异常:{e}")
|
||||||
@@ -445,13 +502,13 @@ class MaaManager(QObject):
|
|||||||
|
|
||||||
if self.if_open_emulator_process:
|
if self.if_open_emulator_process:
|
||||||
try:
|
try:
|
||||||
|
logger.info(
|
||||||
|
f"{self.name} | 启动模拟器:{self.emulator_path},参数:{self.emulator_arguments}"
|
||||||
|
)
|
||||||
self.emulator_process = subprocess.Popen(
|
self.emulator_process = subprocess.Popen(
|
||||||
[self.emulator_path, *self.emulator_arguments],
|
[self.emulator_path, *self.emulator_arguments],
|
||||||
creationflags=subprocess.CREATE_NO_WINDOW,
|
creationflags=subprocess.CREATE_NO_WINDOW,
|
||||||
)
|
)
|
||||||
logger.info(
|
|
||||||
f"{self.name} | 启动模拟器:{self.emulator_path},参数:{self.emulator_arguments}"
|
|
||||||
)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"{self.name} | 启动模拟器时出现异常:{e}")
|
logger.error(f"{self.name} | 启动模拟器时出现异常:{e}")
|
||||||
self.push_info_bar.emit(
|
self.push_info_bar.emit(
|
||||||
@@ -511,10 +568,7 @@ class MaaManager(QObject):
|
|||||||
"检测到MAA进程完成代理任务\n正在等待相关程序结束\n请等待10s"
|
"检测到MAA进程完成代理任务\n正在等待相关程序结束\n请等待10s"
|
||||||
)
|
)
|
||||||
|
|
||||||
for _ in range(10):
|
self.sleep(10)
|
||||||
if self.isInterruptionRequested:
|
|
||||||
break
|
|
||||||
time.sleep(1)
|
|
||||||
else:
|
else:
|
||||||
logger.error(
|
logger.error(
|
||||||
f"{self.name} | 用户: {user[0]} - 代理任务异常: {self.maa_result}"
|
f"{self.name} | 用户: {user[0]} - 代理任务异常: {self.maa_result}"
|
||||||
@@ -564,18 +618,19 @@ class MaaManager(QObject):
|
|||||||
f"{user[0].replace("_", " ")}的{mode_book[mode][5:7]}出现异常",
|
f"{user[0].replace("_", " ")}的{mode_book[mode][5:7]}出现异常",
|
||||||
1,
|
1,
|
||||||
)
|
)
|
||||||
for _ in range(10):
|
if i == self.set["RunSet"]["RunTimesLimit"] - 1:
|
||||||
if self.isInterruptionRequested:
|
self.play_sound.emit("子任务失败")
|
||||||
break
|
else:
|
||||||
time.sleep(1)
|
self.play_sound.emit(self.maa_result)
|
||||||
|
self.sleep(10)
|
||||||
|
|
||||||
# 任务结束后释放ADB
|
# 任务结束后释放ADB
|
||||||
try:
|
try:
|
||||||
|
logger.info(f"{self.name} | 释放ADB:{self.ADB_address}")
|
||||||
subprocess.run(
|
subprocess.run(
|
||||||
[self.ADB_path, "disconnect", self.ADB_address],
|
[self.ADB_path, "disconnect", self.ADB_address],
|
||||||
creationflags=subprocess.CREATE_NO_WINDOW,
|
creationflags=subprocess.CREATE_NO_WINDOW,
|
||||||
)
|
)
|
||||||
logger.info(f"{self.name} | 释放ADB:{self.ADB_address}")
|
|
||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError as e:
|
||||||
# 忽略错误,因为可能本来就没有连接
|
# 忽略错误,因为可能本来就没有连接
|
||||||
logger.warning(f"{self.name} | 释放ADB时出现异常:{e}")
|
logger.warning(f"{self.name} | 释放ADB时出现异常:{e}")
|
||||||
@@ -619,6 +674,7 @@ class MaaManager(QObject):
|
|||||||
},
|
},
|
||||||
user_data,
|
user_data,
|
||||||
)
|
)
|
||||||
|
self.play_sound.emit("六星喜报")
|
||||||
|
|
||||||
# 执行MAA解压更新动作
|
# 执行MAA解压更新动作
|
||||||
if self.maa_update_package:
|
if self.maa_update_package:
|
||||||
@@ -630,17 +686,17 @@ class MaaManager(QObject):
|
|||||||
self.update_log_text.emit(
|
self.update_log_text.emit(
|
||||||
f"检测到MAA存在更新\nMAA正在执行更新动作\n请等待10s"
|
f"检测到MAA存在更新\nMAA正在执行更新动作\n请等待10s"
|
||||||
)
|
)
|
||||||
|
self.play_sound.emit("MAA更新")
|
||||||
self.set_maa("更新MAA", None)
|
self.set_maa("更新MAA", None)
|
||||||
subprocess.Popen(
|
subprocess.Popen(
|
||||||
[self.maa_exe_path],
|
[self.maa_exe_path],
|
||||||
creationflags=subprocess.CREATE_NO_WINDOW,
|
creationflags=subprocess.CREATE_NO_WINDOW,
|
||||||
)
|
)
|
||||||
for _ in range(10):
|
self.sleep(10)
|
||||||
if self.isInterruptionRequested:
|
|
||||||
break
|
|
||||||
time.sleep(1)
|
|
||||||
System.kill_process(self.maa_exe_path)
|
System.kill_process(self.maa_exe_path)
|
||||||
|
|
||||||
|
self.maa_update_package = ""
|
||||||
|
|
||||||
logger.info(f"{self.name} | 更新动作结束")
|
logger.info(f"{self.name} | 更新动作结束")
|
||||||
|
|
||||||
# 发送统计信息
|
# 发送统计信息
|
||||||
@@ -745,10 +801,7 @@ class MaaManager(QObject):
|
|||||||
# 无命令行中止MAA与其子程序
|
# 无命令行中止MAA与其子程序
|
||||||
System.kill_process(self.maa_exe_path)
|
System.kill_process(self.maa_exe_path)
|
||||||
self.if_open_emulator = True
|
self.if_open_emulator = True
|
||||||
for _ in range(10):
|
self.sleep(10)
|
||||||
if self.isInterruptionRequested:
|
|
||||||
break
|
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
# 登录成功,结束循环
|
# 登录成功,结束循环
|
||||||
if run_book[0]:
|
if run_book[0]:
|
||||||
@@ -756,6 +809,7 @@ class MaaManager(QObject):
|
|||||||
# 登录失败,询问是否结束循环
|
# 登录失败,询问是否结束循环
|
||||||
elif not self.isInterruptionRequested:
|
elif not self.isInterruptionRequested:
|
||||||
|
|
||||||
|
self.play_sound.emit("排查重试")
|
||||||
if not self.push_question(
|
if not self.push_question(
|
||||||
"操作提示", "MAA未能正确登录到PRTS,是否重试?"
|
"操作提示", "MAA未能正确登录到PRTS,是否重试?"
|
||||||
):
|
):
|
||||||
@@ -764,6 +818,7 @@ class MaaManager(QObject):
|
|||||||
# 登录成功,录入人工排查情况
|
# 登录成功,录入人工排查情况
|
||||||
if run_book[0] and not self.isInterruptionRequested:
|
if run_book[0] and not self.isInterruptionRequested:
|
||||||
|
|
||||||
|
self.play_sound.emit("排查录入")
|
||||||
if self.push_question(
|
if self.push_question(
|
||||||
"操作提示", "请检查用户代理情况,该用户是否正确完成代理任务?"
|
"操作提示", "请检查用户代理情况,该用户是否正确完成代理任务?"
|
||||||
):
|
):
|
||||||
@@ -885,6 +940,7 @@ class MaaManager(QObject):
|
|||||||
|
|
||||||
self.maa_result = "任务被手动中止"
|
self.maa_result = "任务被手动中止"
|
||||||
self.isInterruptionRequested = True
|
self.isInterruptionRequested = True
|
||||||
|
self.wait_loop.quit()
|
||||||
|
|
||||||
def push_question(self, title: str, message: str) -> bool:
|
def push_question(self, title: str, message: str) -> bool:
|
||||||
|
|
||||||
@@ -895,6 +951,12 @@ class MaaManager(QObject):
|
|||||||
def __capture_response(self, response: bool) -> None:
|
def __capture_response(self, response: bool) -> None:
|
||||||
self.response = response
|
self.response = response
|
||||||
|
|
||||||
|
def sleep(self, time: int) -> None:
|
||||||
|
"""非阻塞型等待"""
|
||||||
|
|
||||||
|
QTimer.singleShot(time * 1000, self.wait_loop.quit)
|
||||||
|
self.wait_loop.exec()
|
||||||
|
|
||||||
def search_ADB_address(self) -> None:
|
def search_ADB_address(self) -> None:
|
||||||
"""搜索ADB实际地址"""
|
"""搜索ADB实际地址"""
|
||||||
|
|
||||||
@@ -902,13 +964,15 @@ class MaaManager(QObject):
|
|||||||
f"即将搜索ADB实际地址\n正在等待模拟器完成启动\n请等待{self.wait_time}s"
|
f"即将搜索ADB实际地址\n正在等待模拟器完成启动\n请等待{self.wait_time}s"
|
||||||
)
|
)
|
||||||
|
|
||||||
for _ in range(self.wait_time):
|
self.sleep(self.wait_time)
|
||||||
if self.isInterruptionRequested:
|
|
||||||
break
|
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
# 移除静默进程标记
|
if self.isInterruptionRequested:
|
||||||
Config.silence_list.remove(self.emulator_path)
|
return None
|
||||||
|
|
||||||
|
# 10s后移除静默进程标记
|
||||||
|
QTimer.singleShot(
|
||||||
|
10000, partial(Config.silence_list.remove, self.emulator_path)
|
||||||
|
)
|
||||||
|
|
||||||
if "-" in self.ADB_address:
|
if "-" in self.ADB_address:
|
||||||
ADB_ip = f"{self.ADB_address.split("-")[0]}-"
|
ADB_ip = f"{self.ADB_address.split("-")[0]}-"
|
||||||
@@ -930,6 +994,7 @@ class MaaManager(QObject):
|
|||||||
connect_result = subprocess.run(
|
connect_result = subprocess.run(
|
||||||
[self.ADB_path, "connect", ADB_address],
|
[self.ADB_path, "connect", ADB_address],
|
||||||
creationflags=subprocess.CREATE_NO_WINDOW,
|
creationflags=subprocess.CREATE_NO_WINDOW,
|
||||||
|
stdin=subprocess.DEVNULL,
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
text=True,
|
text=True,
|
||||||
encoding="utf-8",
|
encoding="utf-8",
|
||||||
@@ -941,6 +1006,7 @@ class MaaManager(QObject):
|
|||||||
devices_result = subprocess.run(
|
devices_result = subprocess.run(
|
||||||
[self.ADB_path, "devices"],
|
[self.ADB_path, "devices"],
|
||||||
creationflags=subprocess.CREATE_NO_WINDOW,
|
creationflags=subprocess.CREATE_NO_WINDOW,
|
||||||
|
stdin=subprocess.DEVNULL,
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
text=True,
|
text=True,
|
||||||
encoding="utf-8",
|
encoding="utf-8",
|
||||||
@@ -968,6 +1034,7 @@ class MaaManager(QObject):
|
|||||||
with self.maa_set_path.open(mode="w", encoding="utf-8") as f:
|
with self.maa_set_path.open(mode="w", encoding="utf-8") as f:
|
||||||
json.dump(data, f, ensure_ascii=False, indent=4)
|
json.dump(data, f, ensure_ascii=False, indent=4)
|
||||||
|
|
||||||
|
self.play_sound.emit("ADB成功")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
else:
|
else:
|
||||||
@@ -975,6 +1042,9 @@ class MaaManager(QObject):
|
|||||||
else:
|
else:
|
||||||
logger.info(f"{self.name} | 无法连接到ADB地址:{ADB_address}")
|
logger.info(f"{self.name} | 无法连接到ADB地址:{ADB_address}")
|
||||||
|
|
||||||
|
if not self.isInterruptionRequested:
|
||||||
|
self.play_sound.emit("ADB失败")
|
||||||
|
|
||||||
def refresh_maa_log(self) -> None:
|
def refresh_maa_log(self) -> None:
|
||||||
"""刷新MAA日志"""
|
"""刷新MAA日志"""
|
||||||
|
|
||||||
@@ -1211,10 +1281,15 @@ class MaaManager(QObject):
|
|||||||
else:
|
else:
|
||||||
self.agree_bilibili(False)
|
self.agree_bilibili(False)
|
||||||
|
|
||||||
|
# 切换配置
|
||||||
|
if data["Current"] != "Default":
|
||||||
|
|
||||||
|
data["Configurations"]["Default"] = data["Configurations"][data["Current"]]
|
||||||
|
data["Current"] = "Default"
|
||||||
|
|
||||||
# 自动代理配置
|
# 自动代理配置
|
||||||
if "自动代理" in mode:
|
if "自动代理" in mode:
|
||||||
|
|
||||||
data["Current"] = "Default" # 切换配置
|
|
||||||
for i in range(1, 9):
|
for i in range(1, 9):
|
||||||
data["Global"][f"Timer.Timer{i}"] = "False" # 时间设置
|
data["Global"][f"Timer.Timer{i}"] = "False" # 时间设置
|
||||||
|
|
||||||
@@ -1520,7 +1595,6 @@ class MaaManager(QObject):
|
|||||||
# 人工排查配置
|
# 人工排查配置
|
||||||
elif "人工排查" in mode:
|
elif "人工排查" in mode:
|
||||||
|
|
||||||
data["Current"] = "Default" # 切换配置
|
|
||||||
for i in range(1, 9):
|
for i in range(1, 9):
|
||||||
data["Global"][f"Timer.Timer{i}"] = "False" # 时间设置
|
data["Global"][f"Timer.Timer{i}"] = "False" # 时间设置
|
||||||
data["Configurations"]["Default"][
|
data["Configurations"]["Default"][
|
||||||
@@ -1593,7 +1667,6 @@ class MaaManager(QObject):
|
|||||||
# 设置MAA配置
|
# 设置MAA配置
|
||||||
elif "设置MAA" in mode:
|
elif "设置MAA" in mode:
|
||||||
|
|
||||||
data["Current"] = "Default" # 切换配置
|
|
||||||
for i in range(1, 9):
|
for i in range(1, 9):
|
||||||
data["Global"][f"Timer.Timer{i}"] = "False" # 时间设置
|
data["Global"][f"Timer.Timer{i}"] = "False" # 时间设置
|
||||||
data["Configurations"]["Default"][
|
data["Configurations"]["Default"][
|
||||||
@@ -1649,7 +1722,6 @@ class MaaManager(QObject):
|
|||||||
|
|
||||||
elif mode == "更新MAA":
|
elif mode == "更新MAA":
|
||||||
|
|
||||||
data["Current"] = "Default" # 切换配置
|
|
||||||
for i in range(1, 9):
|
for i in range(1, 9):
|
||||||
data["Global"][f"Timer.Timer{i}"] = "False" # 时间设置
|
data["Global"][f"Timer.Timer{i}"] = "False" # 时间设置
|
||||||
data["Configurations"]["Default"][
|
data["Configurations"]["Default"][
|
||||||
@@ -1937,6 +2009,10 @@ class MaaManager(QObject):
|
|||||||
"好羡慕~\n\nAUTO_MAA 敬上",
|
"好羡慕~\n\nAUTO_MAA 敬上",
|
||||||
Config.get(Config.notify_CompanyWebHookBotUrl),
|
Config.get(Config.notify_CompanyWebHookBotUrl),
|
||||||
)
|
)
|
||||||
|
Notify.CompanyWebHookBotPushImage(
|
||||||
|
Config.app_path / "resources/images/notification/six_star.png",
|
||||||
|
Config.get(Config.notify_CompanyWebHookBotUrl),
|
||||||
|
)
|
||||||
|
|
||||||
# 发送用户单独通知
|
# 发送用户单独通知
|
||||||
if user_data["Notify"]["Enabled"] and user_data["Notify"]["IfSendSixStar"]:
|
if user_data["Notify"]["Enabled"] and user_data["Notify"]["IfSendSixStar"]:
|
||||||
@@ -1979,8 +2055,14 @@ class MaaManager(QObject):
|
|||||||
"好羡慕~\n\nAUTO_MAA 敬上",
|
"好羡慕~\n\nAUTO_MAA 敬上",
|
||||||
user_data["Notify"]["CompanyWebHookBotUrl"],
|
user_data["Notify"]["CompanyWebHookBotUrl"],
|
||||||
)
|
)
|
||||||
|
Notify.CompanyWebHookBotPushImage(
|
||||||
|
Config.app_path
|
||||||
|
/ "resources/images/notification/six_star.png",
|
||||||
|
Config.get(Config.notify_CompanyWebHookBotUrl),
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
logger.error(
|
logger.error(
|
||||||
f"{self.name} |用户CompanyWebHookBot密钥为空,无法发送用户单独的CompanyWebHookBot通知"
|
f"{self.name} |用户CompanyWebHookBot密钥为空,无法发送用户单独的CompanyWebHookBot通知"
|
||||||
)
|
)
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -32,5 +32,6 @@ __license__ = "GPL-3.0 license"
|
|||||||
from .notification import Notify
|
from .notification import Notify
|
||||||
from .security import Crypto
|
from .security import Crypto
|
||||||
from .system import System
|
from .system import System
|
||||||
|
from .skland import skland_sign_in
|
||||||
|
|
||||||
__all__ = ["Notify", "Crypto", "System"]
|
__all__ = ["Notify", "Crypto", "System", "skland_sign_in"]
|
||||||
|
|||||||
@@ -32,18 +32,19 @@ from email.header import Header
|
|||||||
from email.mime.multipart import MIMEMultipart
|
from email.mime.multipart import MIMEMultipart
|
||||||
from email.mime.text import MIMEText
|
from email.mime.text import MIMEText
|
||||||
from email.utils import formataddr
|
from email.utils import formataddr
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
from PySide6.QtCore import Signal
|
from PySide6.QtCore import QObject, Signal
|
||||||
from PySide6.QtWidgets import QWidget
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from plyer import notification
|
from plyer import notification
|
||||||
|
|
||||||
from app.core import Config
|
from app.core import Config
|
||||||
from app.services.security import Crypto
|
from app.services.security import Crypto
|
||||||
|
from app.utils.ImageUtils import ImageUtils
|
||||||
|
|
||||||
|
|
||||||
class Notification(QWidget):
|
class Notification(QObject):
|
||||||
|
|
||||||
push_info_bar = Signal(str, str, str, int)
|
push_info_bar = Signal(str, str, str, int)
|
||||||
|
|
||||||
@@ -272,6 +273,100 @@ class Notification(QWidget):
|
|||||||
)
|
)
|
||||||
return f"使用企业微信群机器人推送通知时出错:{err}"
|
return f"使用企业微信群机器人推送通知时出错:{err}"
|
||||||
|
|
||||||
|
def CompanyWebHookBotPushImage(self, image_path: Path, webhook_url: str) -> bool:
|
||||||
|
"""使用企业微信群机器人推送图片通知"""
|
||||||
|
try:
|
||||||
|
# 压缩图片
|
||||||
|
ImageUtils.compress_image_if_needed(image_path)
|
||||||
|
|
||||||
|
# 检查图片是否存在
|
||||||
|
if not image_path.exists():
|
||||||
|
logger.error(
|
||||||
|
"图片推送异常 | 图片不存在或者压缩失败,请检查图片路径是否正确"
|
||||||
|
)
|
||||||
|
self.push_info_bar.emit(
|
||||||
|
"error",
|
||||||
|
"企业微信群机器人通知推送异常",
|
||||||
|
"图片不存在或者压缩失败,请检查图片路径是否正确",
|
||||||
|
-1,
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not webhook_url:
|
||||||
|
logger.error("请正确设置企业微信群机器人的WebHook地址")
|
||||||
|
self.push_info_bar.emit(
|
||||||
|
"error",
|
||||||
|
"企业微信群机器人通知推送异常",
|
||||||
|
"请正确设置企业微信群机器人的WebHook地址",
|
||||||
|
-1,
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 获取图片base64和md5
|
||||||
|
try:
|
||||||
|
image_base64 = ImageUtils.get_base64_from_file(str(image_path))
|
||||||
|
image_md5 = ImageUtils.calculate_md5_from_file(str(image_path))
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"图片编码或MD5计算失败:{e}")
|
||||||
|
self.push_info_bar.emit(
|
||||||
|
"error",
|
||||||
|
"企业微信群机器人通知推送异常",
|
||||||
|
f"图片编码或MD5计算失败:{e}",
|
||||||
|
-1,
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"msgtype": "image",
|
||||||
|
"image": {"base64": image_base64, "md5": image_md5},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _ in range(3):
|
||||||
|
try:
|
||||||
|
response = requests.post(
|
||||||
|
url=webhook_url,
|
||||||
|
json=data,
|
||||||
|
timeout=10,
|
||||||
|
)
|
||||||
|
info = response.json()
|
||||||
|
break
|
||||||
|
except requests.RequestException as e:
|
||||||
|
err = e
|
||||||
|
logger.warning(f"推送企业微信群机器人图片第{_+1}次失败:{e}")
|
||||||
|
time.sleep(0.1)
|
||||||
|
else:
|
||||||
|
logger.error(f"推送企业微信群机器人图片时出错:{err}")
|
||||||
|
self.push_info_bar.emit(
|
||||||
|
"error",
|
||||||
|
"企业微信群机器人图片推送失败",
|
||||||
|
f"使用企业微信群机器人推送图片时出错:{err}",
|
||||||
|
-1,
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
if info.get("errcode") == 0:
|
||||||
|
logger.info("企业微信群机器人推送图片成功")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
logger.error(f"企业微信群机器人推送图片失败:{info}")
|
||||||
|
self.push_info_bar.emit(
|
||||||
|
"error",
|
||||||
|
"企业微信群机器人图片推送失败",
|
||||||
|
f"使用企业微信群机器人推送图片时出错:{info}",
|
||||||
|
-1,
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"推送企业微信群机器人图片时发生未知异常:{e}")
|
||||||
|
self.push_info_bar.emit(
|
||||||
|
"error",
|
||||||
|
"企业微信群机器人图片推送失败",
|
||||||
|
f"发生未知异常:{e}",
|
||||||
|
-1,
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
def send_test_notification(self):
|
def send_test_notification(self):
|
||||||
"""发送测试通知到所有已启用的通知渠道"""
|
"""发送测试通知到所有已启用的通知渠道"""
|
||||||
# 发送系统通知
|
# 发送系统通知
|
||||||
@@ -308,6 +403,10 @@ class Notification(QWidget):
|
|||||||
"这是 AUTO_MAA 外部通知测试信息。如果你看到了这段内容,说明 AUTO_MAA 的通知功能已经正确配置且可以正常工作!",
|
"这是 AUTO_MAA 外部通知测试信息。如果你看到了这段内容,说明 AUTO_MAA 的通知功能已经正确配置且可以正常工作!",
|
||||||
Config.get(Config.notify_CompanyWebHookBotUrl),
|
Config.get(Config.notify_CompanyWebHookBotUrl),
|
||||||
)
|
)
|
||||||
|
Notify.CompanyWebHookBotPushImage(
|
||||||
|
Config.app_path / "resources/images/notification/test_notify.png",
|
||||||
|
Config.get(Config.notify_CompanyWebHookBotUrl),
|
||||||
|
)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|||||||
241
app/services/skland.py
Normal file
241
app/services/skland.py
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
# AUTO_MAA:A MAA Multi Account Management and Automation Tool
|
||||||
|
# Copyright © 2024-2025 DLmaster361
|
||||||
|
|
||||||
|
# This file incorporates work covered by the following copyright and
|
||||||
|
# permission notice:
|
||||||
|
#
|
||||||
|
# skland-checkin-ghaction Copyright © 2023 Yanstory
|
||||||
|
# https://github.com/Yanstory/skland-checkin-ghaction
|
||||||
|
|
||||||
|
# This file is part of AUTO_MAA.
|
||||||
|
|
||||||
|
# AUTO_MAA is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published
|
||||||
|
# by the Free Software Foundation, either version 3 of the License,
|
||||||
|
# or (at your option) any later version.
|
||||||
|
|
||||||
|
# AUTO_MAA is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||||
|
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
|
||||||
|
# the GNU General Public License for more details.
|
||||||
|
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
# Contact: DLmaster_361@163.com
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
AUTO_MAA
|
||||||
|
AUTO_MAA森空岛服务
|
||||||
|
v4.3
|
||||||
|
作者:DLmaster_361、ClozyA
|
||||||
|
"""
|
||||||
|
|
||||||
|
from loguru import logger
|
||||||
|
import time
|
||||||
|
import json
|
||||||
|
import hmac
|
||||||
|
import hashlib
|
||||||
|
import requests
|
||||||
|
from urllib import parse
|
||||||
|
|
||||||
|
|
||||||
|
def skland_sign_in(token) -> dict:
|
||||||
|
"""森空岛签到"""
|
||||||
|
|
||||||
|
app_code = "4ca99fa6b56cc2ba"
|
||||||
|
# 用于获取grant code
|
||||||
|
grant_code_url = "https://as.hypergryph.com/user/oauth2/v2/grant"
|
||||||
|
# 用于获取cred
|
||||||
|
cred_code_url = "https://zonai.skland.com/api/v1/user/auth/generate_cred_by_code"
|
||||||
|
# 查询角色绑定
|
||||||
|
binding_url = "https://zonai.skland.com/api/v1/game/player/binding"
|
||||||
|
# 签到接口
|
||||||
|
sign_url = "https://zonai.skland.com/api/v1/game/attendance"
|
||||||
|
|
||||||
|
# 基础请求头
|
||||||
|
header = {
|
||||||
|
"cred": "",
|
||||||
|
"User-Agent": "Skland/1.5.1 (com.hypergryph.skland; build:100501001; Android 34;) Okhttp/4.11.0",
|
||||||
|
"Accept-Encoding": "gzip",
|
||||||
|
"Connection": "close",
|
||||||
|
}
|
||||||
|
header_login = header.copy()
|
||||||
|
header_for_sign = {
|
||||||
|
"platform": "1",
|
||||||
|
"timestamp": "",
|
||||||
|
"dId": "",
|
||||||
|
"vName": "1.5.1",
|
||||||
|
}
|
||||||
|
|
||||||
|
# 生成签名
|
||||||
|
def generate_signature(token_for_sign: str, path, body_or_query):
|
||||||
|
"""
|
||||||
|
生成请求签名
|
||||||
|
:param token_for_sign: 用于加密的token
|
||||||
|
:param path: 请求路径(如 /api/v1/game/player/binding)
|
||||||
|
:param body_or_query: GET用query字符串,POST用body字符串
|
||||||
|
:return: (sign, 新的header_for_sign字典)
|
||||||
|
"""
|
||||||
|
t = str(int(time.time()) - 2) # 时间戳,-2秒以防服务器时间不一致
|
||||||
|
token_bytes = token_for_sign.encode("utf-8")
|
||||||
|
header_ca = dict(header_for_sign)
|
||||||
|
header_ca["timestamp"] = t
|
||||||
|
header_ca_str = json.dumps(header_ca, separators=(",", ":"))
|
||||||
|
s = path + body_or_query + t + header_ca_str # 拼接原始字符串
|
||||||
|
# HMAC-SHA256 + MD5得到最终sign
|
||||||
|
hex_s = hmac.new(token_bytes, s.encode("utf-8"), hashlib.sha256).hexdigest()
|
||||||
|
md5 = hashlib.md5(hex_s.encode("utf-8")).hexdigest()
|
||||||
|
return md5, header_ca
|
||||||
|
|
||||||
|
# 获取带签名的header
|
||||||
|
def get_sign_header(url: str, method, body, old_header, sign_token):
|
||||||
|
"""
|
||||||
|
获取带签名的请求头
|
||||||
|
:param url: 请求完整url
|
||||||
|
:param method: 请求方式 GET/POST
|
||||||
|
:param body: POST请求体或GET时为None
|
||||||
|
:param old_header: 原始请求头
|
||||||
|
:param sign_token: 当前会话的签名token
|
||||||
|
:return: 新请求头
|
||||||
|
"""
|
||||||
|
h = json.loads(json.dumps(old_header))
|
||||||
|
p = parse.urlparse(url)
|
||||||
|
if method.lower() == "get":
|
||||||
|
sign, header_ca = generate_signature(sign_token, p.path, p.query)
|
||||||
|
else:
|
||||||
|
sign, header_ca = generate_signature(
|
||||||
|
sign_token, p.path, json.dumps(body) if body else ""
|
||||||
|
)
|
||||||
|
h["sign"] = sign
|
||||||
|
for i in header_ca:
|
||||||
|
h[i] = header_ca[i]
|
||||||
|
return h
|
||||||
|
|
||||||
|
# 复制请求头并添加cred
|
||||||
|
def copy_header(cred):
|
||||||
|
v = json.loads(json.dumps(header))
|
||||||
|
v["cred"] = cred
|
||||||
|
return v
|
||||||
|
|
||||||
|
# 使用token一步步拿到cred和sign_token
|
||||||
|
def login_by_token(token_code):
|
||||||
|
"""
|
||||||
|
:param token_code: 你的skyland token
|
||||||
|
:return: (cred, sign_token)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# token为json对象时提取data.content
|
||||||
|
t = json.loads(token_code)
|
||||||
|
token_code = t["data"]["content"]
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
grant_code = get_grant_code(token_code)
|
||||||
|
return get_cred(grant_code)
|
||||||
|
|
||||||
|
# 通过grant code换cred和sign_token
|
||||||
|
def get_cred(grant):
|
||||||
|
rsp = requests.post(
|
||||||
|
cred_code_url, json={"code": grant, "kind": 1}, headers=header_login
|
||||||
|
).json()
|
||||||
|
if rsp["code"] != 0:
|
||||||
|
raise Exception(f'获得cred失败:{rsp.get("messgae")}')
|
||||||
|
sign_token = rsp["data"]["token"]
|
||||||
|
cred = rsp["data"]["cred"]
|
||||||
|
return cred, sign_token
|
||||||
|
|
||||||
|
# 通过token换grant code
|
||||||
|
def get_grant_code(token):
|
||||||
|
rsp = requests.post(
|
||||||
|
grant_code_url,
|
||||||
|
json={"appCode": app_code, "token": token, "type": 0},
|
||||||
|
headers=header_login,
|
||||||
|
).json()
|
||||||
|
if rsp["status"] != 0:
|
||||||
|
raise Exception(
|
||||||
|
f'使用token: {token[:3]}******{token[-3:]} 获得认证代码失败:{rsp.get("msg")}'
|
||||||
|
)
|
||||||
|
return rsp["data"]["code"]
|
||||||
|
|
||||||
|
# 获取已绑定的角色列表
|
||||||
|
def get_binding_list(cred, sign_token):
|
||||||
|
"""
|
||||||
|
查询绑定的角色
|
||||||
|
:param cred: 当前cred
|
||||||
|
:param sign_token: 当前sign_token
|
||||||
|
:return: 角色列表
|
||||||
|
"""
|
||||||
|
v = []
|
||||||
|
rsp = requests.get(
|
||||||
|
binding_url,
|
||||||
|
headers=get_sign_header(
|
||||||
|
binding_url, "get", None, copy_header(cred), sign_token
|
||||||
|
),
|
||||||
|
).json()
|
||||||
|
if rsp["code"] != 0:
|
||||||
|
logger.error(f"森空岛服务 | 请求角色列表出现问题:{rsp['message']}")
|
||||||
|
if rsp.get("message") == "用户未登录":
|
||||||
|
logger.error(f"森空岛服务 | 用户登录可能失效了,请重新登录!")
|
||||||
|
return v
|
||||||
|
# 只取明日方舟(arknights)的绑定账号
|
||||||
|
for i in rsp["data"]["list"]:
|
||||||
|
if i.get("appCode") != "arknights":
|
||||||
|
continue
|
||||||
|
v.extend(i.get("bindingList"))
|
||||||
|
return v
|
||||||
|
|
||||||
|
# 执行签到
|
||||||
|
def do_sign(cred, sign_token) -> dict:
|
||||||
|
"""
|
||||||
|
对所有绑定的角色进行签到
|
||||||
|
:param cred: 当前cred
|
||||||
|
:param sign_token: 当前sign_token
|
||||||
|
:return: 签到结果字典
|
||||||
|
"""
|
||||||
|
|
||||||
|
characters = get_binding_list(cred, sign_token)
|
||||||
|
result = {"成功": [], "重复": [], "失败": [], "总计": len(characters)}
|
||||||
|
|
||||||
|
for character in characters:
|
||||||
|
|
||||||
|
body = {
|
||||||
|
"uid": character.get("uid"),
|
||||||
|
"gameId": character.get("channelMasterId"),
|
||||||
|
}
|
||||||
|
rsp = requests.post(
|
||||||
|
sign_url,
|
||||||
|
headers=get_sign_header(
|
||||||
|
sign_url, "post", body, copy_header(cred), sign_token
|
||||||
|
),
|
||||||
|
json=body,
|
||||||
|
).json()
|
||||||
|
|
||||||
|
if rsp["code"] != 0:
|
||||||
|
|
||||||
|
result[
|
||||||
|
"重复" if rsp.get("message") == "请勿重复签到!" else "失败"
|
||||||
|
].append(
|
||||||
|
f"{character.get("nickName")}({character.get("channelName")})"
|
||||||
|
)
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
result["成功"].append(
|
||||||
|
f"{character.get("nickName")}({character.get("channelName")})"
|
||||||
|
)
|
||||||
|
|
||||||
|
time.sleep(3)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
# 主流程
|
||||||
|
try:
|
||||||
|
# 拿到cred和sign_token
|
||||||
|
cred, sign_token = login_by_token(token)
|
||||||
|
time.sleep(1)
|
||||||
|
# 依次签到
|
||||||
|
return do_sign(cred, sign_token)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"森空岛服务 | 森空岛签到失败: {e}")
|
||||||
|
return {"成功": [], "重复": [], "失败": [], "总计": 0}
|
||||||
@@ -112,6 +112,7 @@ class _SystemHandler:
|
|||||||
|
|
||||||
Config.main_window.close()
|
Config.main_window.close()
|
||||||
QApplication.quit()
|
QApplication.quit()
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
elif sys.platform.startswith("linux"):
|
elif sys.platform.startswith("linux"):
|
||||||
|
|
||||||
@@ -138,6 +139,7 @@ class _SystemHandler:
|
|||||||
|
|
||||||
Config.main_window.close()
|
Config.main_window.close()
|
||||||
QApplication.quit()
|
QApplication.quit()
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
def is_startup(self) -> bool:
|
def is_startup(self) -> bool:
|
||||||
"""判断程序是否已经开机自启"""
|
"""判断程序是否已经开机自启"""
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
# AUTO_MAA:A MAA Multi Account Management and Automation Tool
|
# AUTO_MAA:A MAA Multi Account Management and Automation Tool
|
||||||
# Copyright © 2024-2025 DLmaster361
|
# Copyright © 2024-2025 DLmaster361
|
||||||
|
|
||||||
|
# This file incorporates work covered by the following copyright and
|
||||||
|
# permission notice:
|
||||||
|
#
|
||||||
|
# ZenlessZoneZero-OneDragon Copyright © 2024-2025 DoctorReid
|
||||||
|
# https://github.com/DoctorReid/ZenlessZoneZero-OneDragon
|
||||||
|
|
||||||
# This file is part of AUTO_MAA.
|
# This file is part of AUTO_MAA.
|
||||||
|
|
||||||
# AUTO_MAA is free software: you can redistribute it and/or modify
|
# AUTO_MAA is free software: you can redistribute it and/or modify
|
||||||
@@ -156,7 +162,7 @@ class ProgressRingMessageBox(MessageBoxBase):
|
|||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.title = SubtitleLabel(title)
|
self.title = SubtitleLabel(title)
|
||||||
|
|
||||||
self.time = 100
|
self.time = 100 if Config.args.mode == "gui" else 1
|
||||||
Widget = QWidget()
|
Widget = QWidget()
|
||||||
Layout = QHBoxLayout(Widget)
|
Layout = QHBoxLayout(Widget)
|
||||||
self.ring = ProgressRing()
|
self.ring = ProgressRing()
|
||||||
@@ -608,6 +614,83 @@ class PushAndSwitchButtonSettingCard(SettingCard):
|
|||||||
self.switchButton.setText("开" if isChecked else "关")
|
self.switchButton.setText("开" if isChecked else "关")
|
||||||
|
|
||||||
|
|
||||||
|
class PasswordLineAndSwitchButtonSettingCard(SettingCard):
|
||||||
|
"""Setting card with PasswordLineEdit and SwitchButton"""
|
||||||
|
|
||||||
|
textChanged = Signal()
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
icon: Union[str, QIcon, FluentIconBase],
|
||||||
|
title: str,
|
||||||
|
content: Union[str, None],
|
||||||
|
text: str,
|
||||||
|
algorithm: str,
|
||||||
|
qconfig: QConfig,
|
||||||
|
configItem_bool: ConfigItem,
|
||||||
|
configItem_info: ConfigItem,
|
||||||
|
parent=None,
|
||||||
|
):
|
||||||
|
|
||||||
|
super().__init__(icon, title, content, parent)
|
||||||
|
self.algorithm = algorithm
|
||||||
|
self.qconfig = qconfig
|
||||||
|
self.configItem_bool = configItem_bool
|
||||||
|
self.configItem_info = configItem_info
|
||||||
|
self.LineEdit = PasswordLineEdit(self)
|
||||||
|
self.LineEdit.setMinimumWidth(200)
|
||||||
|
self.LineEdit.setPlaceholderText(text)
|
||||||
|
if algorithm == "AUTO":
|
||||||
|
self.LineEdit.setViewPasswordButtonVisible(False)
|
||||||
|
self.SwitchButton = SwitchButton(self)
|
||||||
|
|
||||||
|
self.hBoxLayout.addWidget(self.LineEdit, 0, Qt.AlignRight)
|
||||||
|
self.hBoxLayout.addSpacing(16)
|
||||||
|
self.hBoxLayout.addWidget(self.SwitchButton, 0, Qt.AlignRight)
|
||||||
|
self.hBoxLayout.addSpacing(16)
|
||||||
|
|
||||||
|
self.configItem_info.valueChanged.connect(self.setInfo)
|
||||||
|
self.LineEdit.textChanged.connect(self.__textChanged)
|
||||||
|
self.configItem_bool.valueChanged.connect(self.SwitchButton.setChecked)
|
||||||
|
self.SwitchButton.checkedChanged.connect(
|
||||||
|
lambda isChecked: self.qconfig.set(self.configItem_bool, isChecked)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.setInfo(self.qconfig.get(configItem_info))
|
||||||
|
self.SwitchButton.setChecked(self.qconfig.get(configItem_bool))
|
||||||
|
|
||||||
|
def __textChanged(self, content: str):
|
||||||
|
|
||||||
|
self.configItem_info.valueChanged.disconnect(self.setInfo)
|
||||||
|
if self.algorithm == "DPAPI":
|
||||||
|
self.qconfig.set(self.configItem_info, Crypto.win_encryptor(content))
|
||||||
|
elif self.algorithm == "AUTO":
|
||||||
|
self.qconfig.set(self.configItem_info, Crypto.AUTO_encryptor(content))
|
||||||
|
self.configItem_info.valueChanged.connect(self.setInfo)
|
||||||
|
|
||||||
|
self.textChanged.emit()
|
||||||
|
|
||||||
|
def setInfo(self, content: str):
|
||||||
|
|
||||||
|
self.LineEdit.textChanged.disconnect(self.__textChanged)
|
||||||
|
if self.algorithm == "DPAPI":
|
||||||
|
self.LineEdit.setText(Crypto.win_decryptor(content))
|
||||||
|
elif self.algorithm == "AUTO":
|
||||||
|
if Crypto.check_PASSWORD(Config.PASSWORD):
|
||||||
|
self.LineEdit.setText(Crypto.AUTO_decryptor(content, Config.PASSWORD))
|
||||||
|
self.LineEdit.setPasswordVisible(True)
|
||||||
|
self.LineEdit.setReadOnly(False)
|
||||||
|
elif Config.PASSWORD:
|
||||||
|
self.LineEdit.setText("管理密钥错误")
|
||||||
|
self.LineEdit.setPasswordVisible(True)
|
||||||
|
self.LineEdit.setReadOnly(True)
|
||||||
|
else:
|
||||||
|
self.LineEdit.setText("************")
|
||||||
|
self.LineEdit.setPasswordVisible(False)
|
||||||
|
self.LineEdit.setReadOnly(True)
|
||||||
|
self.LineEdit.textChanged.connect(self.__textChanged)
|
||||||
|
|
||||||
|
|
||||||
class PushAndComboBoxSettingCard(SettingCard):
|
class PushAndComboBoxSettingCard(SettingCard):
|
||||||
"""Setting card with push & combo box"""
|
"""Setting card with push & combo box"""
|
||||||
|
|
||||||
@@ -1129,6 +1212,13 @@ class UserLableSettingCard(SettingCard):
|
|||||||
== Config.server_date().isocalendar()[:2]
|
== Config.server_date().isocalendar()[:2]
|
||||||
else "本周剿灭未完成"
|
else "本周剿灭未完成"
|
||||||
)
|
)
|
||||||
|
if self.qconfig.get(self.configItems["IfSkland"]):
|
||||||
|
text_list.append(
|
||||||
|
"森空岛已签到"
|
||||||
|
if datetime.now().strftime("%Y-%m-%d")
|
||||||
|
== self.qconfig.get(self.configItems["LastSklandDate"])
|
||||||
|
else "森空岛未签到"
|
||||||
|
)
|
||||||
|
|
||||||
self.Lable.setText(" | ".join(text_list))
|
self.Lable.setText(" | ".join(text_list))
|
||||||
|
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ from PySide6.QtGui import QTextCursor
|
|||||||
from typing import List, Dict
|
from typing import List, Dict
|
||||||
|
|
||||||
|
|
||||||
from app.core import Config, TaskManager, Task, MainInfoBar
|
from app.core import Config, TaskManager, Task, MainInfoBar, SoundPlayer
|
||||||
from .Widget import StatefulItemCard, ComboBoxMessageBox, PivotArea
|
from .Widget import StatefulItemCard, ComboBoxMessageBox, PivotArea
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ v4.3
|
|||||||
作者:DLmaster_361
|
作者:DLmaster_361
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from loguru import logger
|
||||||
import zipfile
|
import zipfile
|
||||||
import requests
|
import requests
|
||||||
import subprocess
|
import subprocess
|
||||||
@@ -78,12 +79,15 @@ class DownloadProcess(QThread):
|
|||||||
) -> None:
|
) -> None:
|
||||||
super(DownloadProcess, self).__init__()
|
super(DownloadProcess, self).__init__()
|
||||||
|
|
||||||
|
self.setObjectName(f"DownloadProcess-{url}-{start_byte}-{end_byte}")
|
||||||
|
|
||||||
self.url = url
|
self.url = url
|
||||||
self.start_byte = start_byte
|
self.start_byte = start_byte
|
||||||
self.end_byte = end_byte
|
self.end_byte = end_byte
|
||||||
self.download_path = download_path
|
self.download_path = download_path
|
||||||
self.check_times = check_times
|
self.check_times = check_times
|
||||||
|
|
||||||
|
@logger.catch
|
||||||
def run(self) -> None:
|
def run(self) -> None:
|
||||||
|
|
||||||
# 清理可能存在的临时文件
|
# 清理可能存在的临时文件
|
||||||
@@ -157,10 +161,13 @@ class ZipExtractProcess(QThread):
|
|||||||
def __init__(self, name: str, app_path: Path, download_path: Path) -> None:
|
def __init__(self, name: str, app_path: Path, download_path: Path) -> None:
|
||||||
super(ZipExtractProcess, self).__init__()
|
super(ZipExtractProcess, self).__init__()
|
||||||
|
|
||||||
|
self.setObjectName(f"ZipExtractProcess-{name}")
|
||||||
|
|
||||||
self.name = name
|
self.name = name
|
||||||
self.app_path = app_path
|
self.app_path = app_path
|
||||||
self.download_path = download_path
|
self.download_path = download_path
|
||||||
|
|
||||||
|
@logger.catch
|
||||||
def run(self) -> None:
|
def run(self) -> None:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -234,6 +241,7 @@ class DownloadManager(QDialog):
|
|||||||
self.download_path = app_path / "DOWNLOAD_TEMP.zip" # 临时下载文件的路径
|
self.download_path = app_path / "DOWNLOAD_TEMP.zip" # 临时下载文件的路径
|
||||||
self.download_process_dict: Dict[str, DownloadProcess] = {}
|
self.download_process_dict: Dict[str, DownloadProcess] = {}
|
||||||
self.timer_dict: Dict[str, QTimer] = {}
|
self.timer_dict: Dict[str, QTimer] = {}
|
||||||
|
self.if_speed_test_accomplish = False
|
||||||
|
|
||||||
self.resize(700, 70)
|
self.resize(700, 70)
|
||||||
|
|
||||||
@@ -405,6 +413,15 @@ class DownloadManager(QDialog):
|
|||||||
if not self.download_process_dict:
|
if not self.download_process_dict:
|
||||||
self.download_process_clear.emit()
|
self.download_process_clear.emit()
|
||||||
|
|
||||||
|
# 当有速度大于1 MB/s的链接或存在3个即以上链接测速完成时,停止其他测速
|
||||||
|
if not self.if_speed_test_accomplish and (
|
||||||
|
sum(1 for speed in self.test_speed_result.values() if speed > 0) >= 3
|
||||||
|
or any(speed > 1 for speed in self.test_speed_result.values())
|
||||||
|
):
|
||||||
|
self.if_speed_test_accomplish = True
|
||||||
|
for timer in self.timer_dict.values():
|
||||||
|
timer.timeout.emit()
|
||||||
|
|
||||||
if any(speed == -1 for _, speed in self.test_speed_result.items()):
|
if any(speed == -1 for _, speed in self.test_speed_result.items()):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ from pathlib import Path
|
|||||||
from typing import Union, List, Dict
|
from typing import Union, List, Dict
|
||||||
|
|
||||||
|
|
||||||
from app.core import Config
|
from app.core import Config, SoundPlayer
|
||||||
from .Widget import StatefulItemCard, QuantifiedItemCard, QuickExpandGroupCard
|
from .Widget import StatefulItemCard, QuantifiedItemCard, QuickExpandGroupCard
|
||||||
|
|
||||||
|
|
||||||
@@ -83,6 +83,8 @@ class History(QWidget):
|
|||||||
def reload_history(self, mode: str, start_date: QDate, end_date: QDate) -> None:
|
def reload_history(self, mode: str, start_date: QDate, end_date: QDate) -> None:
|
||||||
"""加载历史记录界面"""
|
"""加载历史记录界面"""
|
||||||
|
|
||||||
|
SoundPlayer.play("历史记录查询")
|
||||||
|
|
||||||
while self.content_layout.count() > 0:
|
while self.content_layout.count() > 0:
|
||||||
item = self.content_layout.takeAt(0)
|
item = self.content_layout.takeAt(0)
|
||||||
if item.spacerItem():
|
if item.spacerItem():
|
||||||
|
|||||||
@@ -199,20 +199,22 @@ class Home(QWidget):
|
|||||||
elif Config.get(Config.function_HomeImageMode) == "主题图像":
|
elif Config.get(Config.function_HomeImageMode) == "主题图像":
|
||||||
|
|
||||||
# 从远程服务器获取最新主题图像
|
# 从远程服务器获取最新主题图像
|
||||||
Network.set_info(
|
network = Network.add_task(
|
||||||
mode="get",
|
mode="get",
|
||||||
url="https://gitee.com/DLmaster_361/AUTO_MAA/raw/server/theme_image.json",
|
url="https://gitee.com/DLmaster_361/AUTO_MAA/raw/server/theme_image.json",
|
||||||
)
|
)
|
||||||
Network.start()
|
network.loop.exec()
|
||||||
Network.loop.exec()
|
network_result = Network.get_result(network)
|
||||||
if Network.stutus_code == 200:
|
if network_result["status_code"] == 200:
|
||||||
theme_image = Network.response_json
|
theme_image = network_result["response_json"]
|
||||||
else:
|
else:
|
||||||
logger.warning(f"获取最新主题图像时出错:{Network.error_message}")
|
logger.warning(
|
||||||
|
f"获取最新主题图像时出错:{network_result['error_message']}"
|
||||||
|
)
|
||||||
MainInfoBar.push_info_bar(
|
MainInfoBar.push_info_bar(
|
||||||
"warning",
|
"warning",
|
||||||
"获取最新主题图像时出错",
|
"获取最新主题图像时出错",
|
||||||
f"网络错误:{Network.stutus_code}",
|
f"网络错误:{network_result['status_code']}",
|
||||||
5000,
|
5000,
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
@@ -236,15 +238,15 @@ class Home(QWidget):
|
|||||||
> time_local
|
> time_local
|
||||||
):
|
):
|
||||||
|
|
||||||
Network.set_info(
|
network = Network.add_task(
|
||||||
mode="get_file",
|
mode="get_file",
|
||||||
url=theme_image["url"],
|
url=theme_image["url"],
|
||||||
path=Config.app_path / "resources/images/Home/BannerTheme.jpg",
|
path=Config.app_path / "resources/images/Home/BannerTheme.jpg",
|
||||||
)
|
)
|
||||||
Network.start()
|
network.loop.exec()
|
||||||
Network.loop.exec()
|
network_result = Network.get_result(network)
|
||||||
|
|
||||||
if Network.stutus_code == 200:
|
if network_result["status_code"] == 200:
|
||||||
|
|
||||||
with (Config.app_path / "resources/theme_image.json").open(
|
with (Config.app_path / "resources/theme_image.json").open(
|
||||||
mode="w", encoding="utf-8"
|
mode="w", encoding="utf-8"
|
||||||
@@ -261,11 +263,13 @@ class Home(QWidget):
|
|||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
logger.warning(f"下载最新主题图像时出错:{Network.error_message}")
|
logger.warning(
|
||||||
|
f"下载最新主题图像时出错:{network_result['error_message']}"
|
||||||
|
)
|
||||||
MainInfoBar.push_info_bar(
|
MainInfoBar.push_info_bar(
|
||||||
"warning",
|
"warning",
|
||||||
"下载最新主题图像时出错",
|
"下载最新主题图像时出错",
|
||||||
f"网络错误:{Network.stutus_code}",
|
f"网络错误:{network_result['status_code']}",
|
||||||
5000,
|
5000,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -401,4 +405,6 @@ class ButtonGroup(SimpleCardWidget):
|
|||||||
|
|
||||||
def open_sales(self):
|
def open_sales(self):
|
||||||
"""打开 MirrorChyan 链接"""
|
"""打开 MirrorChyan 链接"""
|
||||||
QDesktopServices.openUrl(QUrl("https://mirrorchyan.com/"))
|
QDesktopServices.openUrl(
|
||||||
|
QUrl("https://mirrorchyan.com/zh/get-start?source=auto_maa-home")
|
||||||
|
)
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ from datetime import datetime, timedelta
|
|||||||
import shutil
|
import shutil
|
||||||
import darkdetect
|
import darkdetect
|
||||||
|
|
||||||
from app.core import Config, TaskManager, MainTimer, MainInfoBar
|
from app.core import Config, TaskManager, MainTimer, MainInfoBar, SoundPlayer
|
||||||
from app.services import Notify, Crypto, System
|
from app.services import Notify, Crypto, System
|
||||||
from .home import Home
|
from .home import Home
|
||||||
from .member_manager import MemberManager
|
from .member_manager import MemberManager
|
||||||
@@ -257,6 +257,9 @@ class AUTO_MAA(MSFluentWindow):
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""配置窗口状态"""
|
"""配置窗口状态"""
|
||||||
|
|
||||||
|
if Config.args.mode != "gui":
|
||||||
|
return None
|
||||||
|
|
||||||
self.switch_theme()
|
self.switch_theme()
|
||||||
|
|
||||||
if mode == "显示主窗口":
|
if mode == "显示主窗口":
|
||||||
@@ -285,6 +288,7 @@ class AUTO_MAA(MSFluentWindow):
|
|||||||
and not self.window().isMaximized()
|
and not self.window().isMaximized()
|
||||||
):
|
):
|
||||||
self.titleBar.maxBtn.click()
|
self.titleBar.maxBtn.click()
|
||||||
|
SoundPlayer.play("欢迎回来")
|
||||||
self.show_ui("配置托盘")
|
self.show_ui("配置托盘")
|
||||||
elif if_start:
|
elif if_start:
|
||||||
if Config.get(Config.ui_maximized) and not self.window().isMaximized():
|
if Config.get(Config.ui_maximized) and not self.window().isMaximized():
|
||||||
@@ -378,6 +382,41 @@ class AUTO_MAA(MSFluentWindow):
|
|||||||
|
|
||||||
self.titleBar.minBtn.click()
|
self.titleBar.minBtn.click()
|
||||||
|
|
||||||
|
if Config.args.config:
|
||||||
|
|
||||||
|
for config in [_ for _ in Config.args.config if _ in Config.queue_dict]:
|
||||||
|
|
||||||
|
TaskManager.add_task(
|
||||||
|
"自动代理_新调度台",
|
||||||
|
config,
|
||||||
|
Config.queue_dict["调度队列_1"]["Config"].toDict(),
|
||||||
|
)
|
||||||
|
|
||||||
|
for config in [_ for _ in Config.args.config if _ in Config.member_dict]:
|
||||||
|
|
||||||
|
TaskManager.add_task(
|
||||||
|
"自动代理_新调度台",
|
||||||
|
"自定义队列",
|
||||||
|
{"Queue": {"Member_1": config}},
|
||||||
|
)
|
||||||
|
|
||||||
|
if not any(
|
||||||
|
_ in (list(Config.member_dict.keys()) + list(Config.queue_dict.keys()))
|
||||||
|
for _ in Config.args.config
|
||||||
|
):
|
||||||
|
|
||||||
|
logger.warning(
|
||||||
|
"当前运行模式为命令行模式,由于您使用了错误的 --config 参数进行配置,程序自动退出"
|
||||||
|
)
|
||||||
|
System.set_power("KillSelf")
|
||||||
|
|
||||||
|
elif Config.args.mode == "cli":
|
||||||
|
|
||||||
|
logger.warning(
|
||||||
|
"当前运行模式为命令行模式,由于您未使用 --config 参数进行配置,程序自动退出"
|
||||||
|
)
|
||||||
|
System.set_power("KillSelf")
|
||||||
|
|
||||||
def clean_old_logs(self):
|
def clean_old_logs(self):
|
||||||
"""
|
"""
|
||||||
删除超过用户设定天数的日志文件(基于目录日期)
|
删除超过用户设定天数的日志文件(基于目录日期)
|
||||||
|
|||||||
@@ -58,7 +58,15 @@ from typing import List
|
|||||||
import shutil
|
import shutil
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from app.core import Config, MainInfoBar, TaskManager, MaaConfig, MaaUserConfig, Network
|
from app.core import (
|
||||||
|
Config,
|
||||||
|
MainInfoBar,
|
||||||
|
TaskManager,
|
||||||
|
MaaConfig,
|
||||||
|
MaaUserConfig,
|
||||||
|
Network,
|
||||||
|
SoundPlayer,
|
||||||
|
)
|
||||||
from app.services import Crypto
|
from app.services import Crypto
|
||||||
from .downloader import DownloadManager
|
from .downloader import DownloadManager
|
||||||
from .Widget import (
|
from .Widget import (
|
||||||
@@ -72,6 +80,7 @@ from .Widget import (
|
|||||||
EditableComboBoxWithPlanSettingCard,
|
EditableComboBoxWithPlanSettingCard,
|
||||||
SpinBoxWithPlanSettingCard,
|
SpinBoxWithPlanSettingCard,
|
||||||
PasswordLineEditSettingCard,
|
PasswordLineEditSettingCard,
|
||||||
|
PasswordLineAndSwitchButtonSettingCard,
|
||||||
UserLableSettingCard,
|
UserLableSettingCard,
|
||||||
UserTaskSettingCard,
|
UserTaskSettingCard,
|
||||||
ComboBoxSettingCard,
|
ComboBoxSettingCard,
|
||||||
@@ -181,6 +190,7 @@ class MemberManager(QWidget):
|
|||||||
MainInfoBar.push_info_bar(
|
MainInfoBar.push_info_bar(
|
||||||
"success", "操作成功", f"添加脚本实例 脚本_{index}", 3000
|
"success", "操作成功", f"添加脚本实例 脚本_{index}", 3000
|
||||||
)
|
)
|
||||||
|
SoundPlayer.play("添加脚本实例")
|
||||||
|
|
||||||
def del_setting_box(self):
|
def del_setting_box(self):
|
||||||
"""删除一个脚本实例"""
|
"""删除一个脚本实例"""
|
||||||
@@ -221,6 +231,7 @@ class MemberManager(QWidget):
|
|||||||
MainInfoBar.push_info_bar(
|
MainInfoBar.push_info_bar(
|
||||||
"success", "操作成功", f"删除脚本实例 {name}", 3000
|
"success", "操作成功", f"删除脚本实例 {name}", 3000
|
||||||
)
|
)
|
||||||
|
SoundPlayer.play("删除脚本实例")
|
||||||
|
|
||||||
def left_setting_box(self):
|
def left_setting_box(self):
|
||||||
"""向左移动脚本实例"""
|
"""向左移动脚本实例"""
|
||||||
@@ -333,20 +344,20 @@ class MemberManager(QWidget):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
# 从远程服务器获取应用列表
|
# 从远程服务器获取应用列表
|
||||||
Network.set_info(
|
network = Network.add_task(
|
||||||
mode="get",
|
mode="get",
|
||||||
url="https://gitee.com/DLmaster_361/AUTO_MAA/raw/server/apps_info.json",
|
url="https://gitee.com/DLmaster_361/AUTO_MAA/raw/server/apps_info.json",
|
||||||
)
|
)
|
||||||
Network.start()
|
network.loop.exec()
|
||||||
Network.loop.exec()
|
network_result = Network.get_result(network)
|
||||||
if Network.stutus_code == 200:
|
if network_result["status_code"] == 200:
|
||||||
apps_info = Network.response_json
|
apps_info = network_result["response_json"]
|
||||||
else:
|
else:
|
||||||
logger.warning(f"获取应用列表时出错:{Network.error_message}")
|
logger.warning(f"获取应用列表时出错:{network_result['error_message']}")
|
||||||
MainInfoBar.push_info_bar(
|
MainInfoBar.push_info_bar(
|
||||||
"warning",
|
"warning",
|
||||||
"获取应用列表时出错",
|
"获取应用列表时出错",
|
||||||
f"网络错误:{Network.stutus_code}",
|
f"网络错误:{network_result['status_code']}",
|
||||||
5000,
|
5000,
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
@@ -376,19 +387,19 @@ class MemberManager(QWidget):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
# 从mirrorc服务器获取最新版本信息
|
# 从mirrorc服务器获取最新版本信息
|
||||||
Network.set_info(
|
network = Network.add_task(
|
||||||
mode="get",
|
mode="get",
|
||||||
url=f"https://mirrorchyan.com/api/resources/{app_rid}/latest?user_agent=AutoMaaGui&cdk={Crypto.win_decryptor(Config.get(Config.update_MirrorChyanCDK))}&os={apps_info[app_name]["os"]}&arch={apps_info[app_name]["arch"]}&channel=stable",
|
url=f"https://mirrorchyan.com/api/resources/{app_rid}/latest?user_agent=AutoMaaGui&cdk={Crypto.win_decryptor(Config.get(Config.update_MirrorChyanCDK))}&os={apps_info[app_name]["os"]}&arch={apps_info[app_name]["arch"]}&channel=stable",
|
||||||
)
|
)
|
||||||
Network.start()
|
network.loop.exec()
|
||||||
Network.loop.exec()
|
network_result = Network.get_result(network)
|
||||||
if Network.stutus_code == 200:
|
if network_result["status_code"] == 200:
|
||||||
app_info = Network.response_json
|
app_info = network_result["response_json"]
|
||||||
else:
|
else:
|
||||||
|
|
||||||
if Network.response_json:
|
if network_result["response_json"]:
|
||||||
|
|
||||||
app_info = Network.response_json
|
app_info = network_result["response_json"]
|
||||||
|
|
||||||
if app_info["code"] != 0:
|
if app_info["code"] != 0:
|
||||||
|
|
||||||
@@ -425,11 +436,11 @@ class MemberManager(QWidget):
|
|||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
logger.warning(f"获取版本信息时出错:{Network.error_message}")
|
logger.warning(f"获取版本信息时出错:{network_result['error_message']}")
|
||||||
MainInfoBar.push_info_bar(
|
MainInfoBar.push_info_bar(
|
||||||
"warning",
|
"warning",
|
||||||
"获取版本信息时出错",
|
"获取版本信息时出错",
|
||||||
f"网络错误:{Network.stutus_code}",
|
f"网络错误:{network_result['status_code']}",
|
||||||
5000,
|
5000,
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
@@ -885,6 +896,7 @@ class MemberManager(QWidget):
|
|||||||
MainInfoBar.push_info_bar(
|
MainInfoBar.push_info_bar(
|
||||||
"success", "操作成功", f"{self.name} 添加 用户_{index}", 3000
|
"success", "操作成功", f"{self.name} 添加 用户_{index}", 3000
|
||||||
)
|
)
|
||||||
|
SoundPlayer.play("添加用户")
|
||||||
|
|
||||||
def del_user(self):
|
def del_user(self):
|
||||||
"""删除一个用户"""
|
"""删除一个用户"""
|
||||||
@@ -944,6 +956,7 @@ class MemberManager(QWidget):
|
|||||||
MainInfoBar.push_info_bar(
|
MainInfoBar.push_info_bar(
|
||||||
"success", "操作成功", f"{self.name} 删除 {name}", 3000
|
"success", "操作成功", f"{self.name} 删除 {name}", 3000
|
||||||
)
|
)
|
||||||
|
SoundPlayer.play("删除用户")
|
||||||
|
|
||||||
def left_user(self):
|
def left_user(self):
|
||||||
"""向前移动用户"""
|
"""向前移动用户"""
|
||||||
@@ -1573,6 +1586,18 @@ class MemberManager(QWidget):
|
|||||||
parent=self,
|
parent=self,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
self.card_Skland = PasswordLineAndSwitchButtonSettingCard(
|
||||||
|
icon=FluentIcon.CERTIFICATE,
|
||||||
|
title="森空岛签到",
|
||||||
|
content="此功能具有一定风险,请谨慎使用!获取登录凭证请查阅「文档-进阶功能」。",
|
||||||
|
text="鹰角网络通行证登录凭证",
|
||||||
|
algorithm="DPAPI",
|
||||||
|
qconfig=self.config,
|
||||||
|
configItem_bool=self.config.Info_IfSkland,
|
||||||
|
configItem_info=self.config.Info_SklandToken,
|
||||||
|
parent=self,
|
||||||
|
)
|
||||||
|
self.card_Skland.LineEdit.setMinimumWidth(250)
|
||||||
|
|
||||||
self.card_UserLable = UserLableSettingCard(
|
self.card_UserLable = UserLableSettingCard(
|
||||||
icon=FluentIcon.INFO,
|
icon=FluentIcon.INFO,
|
||||||
@@ -1584,6 +1609,8 @@ class MemberManager(QWidget):
|
|||||||
"LastAnnihilationDate": self.config.Data_LastAnnihilationDate,
|
"LastAnnihilationDate": self.config.Data_LastAnnihilationDate,
|
||||||
"ProxyTimes": self.config.Data_ProxyTimes,
|
"ProxyTimes": self.config.Data_ProxyTimes,
|
||||||
"IfPassCheck": self.config.Data_IfPassCheck,
|
"IfPassCheck": self.config.Data_IfPassCheck,
|
||||||
|
"IfSkland": self.config.Info_IfSkland,
|
||||||
|
"LastSklandDate": self.config.Data_LastSklandDate,
|
||||||
},
|
},
|
||||||
parent=self,
|
parent=self,
|
||||||
)
|
)
|
||||||
@@ -1766,6 +1793,7 @@ class MemberManager(QWidget):
|
|||||||
Layout.addLayout(h6_layout)
|
Layout.addLayout(h6_layout)
|
||||||
Layout.addLayout(h7_layout)
|
Layout.addLayout(h7_layout)
|
||||||
Layout.addLayout(h8_layout)
|
Layout.addLayout(h8_layout)
|
||||||
|
Layout.addWidget(self.card_Skland)
|
||||||
Layout.addWidget(self.card_TaskSet)
|
Layout.addWidget(self.card_TaskSet)
|
||||||
Layout.addWidget(self.card_NotifySet)
|
Layout.addWidget(self.card_NotifySet)
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ from qfluentwidgets import (
|
|||||||
from typing import List, Dict, Union
|
from typing import List, Dict, Union
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
from app.core import Config, MainInfoBar, MaaPlanConfig
|
from app.core import Config, MainInfoBar, MaaPlanConfig, SoundPlayer
|
||||||
from .Widget import (
|
from .Widget import (
|
||||||
ComboBoxMessageBox,
|
ComboBoxMessageBox,
|
||||||
LineEditSettingCard,
|
LineEditSettingCard,
|
||||||
@@ -129,6 +129,7 @@ class PlanManager(QWidget):
|
|||||||
MainInfoBar.push_info_bar(
|
MainInfoBar.push_info_bar(
|
||||||
"success", "操作成功", f"添加计划表 计划_{index}", 3000
|
"success", "操作成功", f"添加计划表 计划_{index}", 3000
|
||||||
)
|
)
|
||||||
|
SoundPlayer.play("添加计划表")
|
||||||
|
|
||||||
def del_setting_box(self):
|
def del_setting_box(self):
|
||||||
"""删除一个计划表"""
|
"""删除一个计划表"""
|
||||||
@@ -155,7 +156,7 @@ class PlanManager(QWidget):
|
|||||||
self.plan_manager.clear_SettingBox()
|
self.plan_manager.clear_SettingBox()
|
||||||
|
|
||||||
shutil.rmtree(Config.plan_dict[name]["Path"])
|
shutil.rmtree(Config.plan_dict[name]["Path"])
|
||||||
Config.change_plan(name, "禁用")
|
Config.change_plan(name, "固定")
|
||||||
for i in range(int(name[3:]) + 1, len(Config.plan_dict) + 1):
|
for i in range(int(name[3:]) + 1, len(Config.plan_dict) + 1):
|
||||||
if Config.plan_dict[f"计划_{i}"]["Path"].exists():
|
if Config.plan_dict[f"计划_{i}"]["Path"].exists():
|
||||||
Config.plan_dict[f"计划_{i}"]["Path"].rename(
|
Config.plan_dict[f"计划_{i}"]["Path"].rename(
|
||||||
@@ -167,6 +168,7 @@ class PlanManager(QWidget):
|
|||||||
|
|
||||||
logger.success(f"计划表 {name} 删除成功")
|
logger.success(f"计划表 {name} 删除成功")
|
||||||
MainInfoBar.push_info_bar("success", "操作成功", f"删除计划表 {name}", 3000)
|
MainInfoBar.push_info_bar("success", "操作成功", f"删除计划表 {name}", 3000)
|
||||||
|
SoundPlayer.play("删除计划表")
|
||||||
|
|
||||||
def left_setting_box(self):
|
def left_setting_box(self):
|
||||||
"""向左移动计划表"""
|
"""向左移动计划表"""
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ from qfluentwidgets import (
|
|||||||
)
|
)
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from app.core import QueueConfig, Config, MainInfoBar
|
from app.core import QueueConfig, Config, MainInfoBar, SoundPlayer
|
||||||
from .Widget import (
|
from .Widget import (
|
||||||
SwitchSettingCard,
|
SwitchSettingCard,
|
||||||
ComboBoxSettingCard,
|
ComboBoxSettingCard,
|
||||||
@@ -116,6 +116,7 @@ class QueueManager(QWidget):
|
|||||||
|
|
||||||
logger.success(f"调度队列_{index} 添加成功")
|
logger.success(f"调度队列_{index} 添加成功")
|
||||||
MainInfoBar.push_info_bar("success", "操作成功", f"添加 调度队列_{index}", 3000)
|
MainInfoBar.push_info_bar("success", "操作成功", f"添加 调度队列_{index}", 3000)
|
||||||
|
SoundPlayer.play("添加调度队列")
|
||||||
|
|
||||||
def del_setting_box(self):
|
def del_setting_box(self):
|
||||||
"""删除一个调度队列实例"""
|
"""删除一个调度队列实例"""
|
||||||
@@ -154,6 +155,7 @@ class QueueManager(QWidget):
|
|||||||
|
|
||||||
logger.success(f"{name} 删除成功")
|
logger.success(f"{name} 删除成功")
|
||||||
MainInfoBar.push_info_bar("success", "操作成功", f"删除 {name}", 3000)
|
MainInfoBar.push_info_bar("success", "操作成功", f"删除 {name}", 3000)
|
||||||
|
SoundPlayer.play("删除调度队列")
|
||||||
|
|
||||||
def left_setting_box(self):
|
def left_setting_box(self):
|
||||||
"""向左移动调度队列实例"""
|
"""向左移动调度队列实例"""
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ from packaging import version
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict, Union
|
from typing import Dict, Union
|
||||||
|
|
||||||
from app.core import Config, MainInfoBar, Network
|
from app.core import Config, MainInfoBar, Network, SoundPlayer
|
||||||
from app.services import Crypto, System, Notify
|
from app.services import Crypto, System, Notify
|
||||||
from .downloader import DownloadManager
|
from .downloader import DownloadManager
|
||||||
from .Widget import (
|
from .Widget import (
|
||||||
@@ -71,6 +71,7 @@ class Setting(QWidget):
|
|||||||
self.setObjectName("设置")
|
self.setObjectName("设置")
|
||||||
|
|
||||||
self.function = FunctionSettingCard(self)
|
self.function = FunctionSettingCard(self)
|
||||||
|
self.voice = VoiceSettingCard(self)
|
||||||
self.start = StartSettingCard(self)
|
self.start = StartSettingCard(self)
|
||||||
self.ui = UiSettingCard(self)
|
self.ui = UiSettingCard(self)
|
||||||
self.notification = NotifySettingCard(self)
|
self.notification = NotifySettingCard(self)
|
||||||
@@ -94,6 +95,7 @@ class Setting(QWidget):
|
|||||||
content_layout = QVBoxLayout(content_widget)
|
content_layout = QVBoxLayout(content_widget)
|
||||||
content_layout.setContentsMargins(0, 0, 11, 0)
|
content_layout.setContentsMargins(0, 0, 11, 0)
|
||||||
content_layout.addWidget(self.function)
|
content_layout.addWidget(self.function)
|
||||||
|
content_layout.addWidget(self.voice)
|
||||||
content_layout.addWidget(self.start)
|
content_layout.addWidget(self.start)
|
||||||
content_layout.addWidget(self.ui)
|
content_layout.addWidget(self.ui)
|
||||||
content_layout.addWidget(self.notification)
|
content_layout.addWidget(self.notification)
|
||||||
@@ -262,31 +264,26 @@ class Setting(QWidget):
|
|||||||
|
|
||||||
current_version = list(map(int, Config.VERSION.split(".")))
|
current_version = list(map(int, Config.VERSION.split(".")))
|
||||||
|
|
||||||
if Network.if_running and if_show:
|
|
||||||
MainInfoBar.push_info_bar(
|
|
||||||
"warning", "请求速度过快", "上个网络请求还未结束,请稍等片刻", 5000
|
|
||||||
)
|
|
||||||
return None
|
|
||||||
# 从远程服务器获取最新版本信息
|
# 从远程服务器获取最新版本信息
|
||||||
Network.set_info(
|
network = Network.add_task(
|
||||||
mode="get",
|
mode="get",
|
||||||
url=f"https://mirrorchyan.com/api/resources/AUTO_MAA/latest?user_agent=AutoMaaGui¤t_version={version_text(current_version)}&cdk={Crypto.win_decryptor(Config.get(Config.update_MirrorChyanCDK))}&channel={Config.get(Config.update_UpdateType)}",
|
url=f"https://mirrorchyan.com/api/resources/AUTO_MAA/latest?user_agent=AutoMaaGui¤t_version={version_text(current_version)}&cdk={Crypto.win_decryptor(Config.get(Config.update_MirrorChyanCDK))}&channel={Config.get(Config.update_UpdateType)}",
|
||||||
)
|
)
|
||||||
Network.start()
|
network.loop.exec()
|
||||||
Network.loop.exec()
|
network_result = Network.get_result(network)
|
||||||
if Network.stutus_code == 200:
|
if network_result["status_code"] == 200:
|
||||||
version_info: Dict[str, Union[int, str, Dict[str, str]]] = (
|
version_info: Dict[str, Union[int, str, Dict[str, str]]] = network_result[
|
||||||
Network.response_json
|
"response_json"
|
||||||
)
|
]
|
||||||
else:
|
else:
|
||||||
|
|
||||||
if Network.response_json:
|
if network_result["response_json"]:
|
||||||
|
|
||||||
version_info = Network.response_json
|
version_info = network_result["response_json"]
|
||||||
|
|
||||||
if version_info["code"] != 0:
|
if version_info["code"] != 0:
|
||||||
|
|
||||||
logger.error(f"获取版本信息时出错:{version_info["msg"]}")
|
logger.error(f"获取版本信息时出错:{version_info['msg']}")
|
||||||
|
|
||||||
error_remark_dict = {
|
error_remark_dict = {
|
||||||
1001: "获取版本信息的URL参数不正确",
|
1001: "获取版本信息的URL参数不正确",
|
||||||
@@ -319,11 +316,11 @@ class Setting(QWidget):
|
|||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
logger.warning(f"获取版本信息时出错:{Network.error_message}")
|
logger.warning(f"获取版本信息时出错:{network_result['error_message']}")
|
||||||
MainInfoBar.push_info_bar(
|
MainInfoBar.push_info_bar(
|
||||||
"warning",
|
"warning",
|
||||||
"获取版本信息时出错",
|
"获取版本信息时出错",
|
||||||
f"网络错误:{Network.stutus_code}",
|
f"网络错误:{network_result['status_code']}",
|
||||||
5000,
|
5000,
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
@@ -381,6 +378,7 @@ class Setting(QWidget):
|
|||||||
all_version_info[key] = value.copy()
|
all_version_info[key] = value.copy()
|
||||||
|
|
||||||
# 询问是否开始版本更新
|
# 询问是否开始版本更新
|
||||||
|
SoundPlayer.play("有新版本")
|
||||||
choice = NoticeMessageBox(
|
choice = NoticeMessageBox(
|
||||||
self.window(),
|
self.window(),
|
||||||
"版本更新",
|
"版本更新",
|
||||||
@@ -406,20 +404,22 @@ class Setting(QWidget):
|
|||||||
else:
|
else:
|
||||||
|
|
||||||
# 从远程服务器获取代理信息
|
# 从远程服务器获取代理信息
|
||||||
Network.set_info(
|
network = Network.add_task(
|
||||||
mode="get",
|
mode="get",
|
||||||
url="https://gitee.com/DLmaster_361/AUTO_MAA/raw/server/download_info.json",
|
url="https://gitee.com/DLmaster_361/AUTO_MAA/raw/server/download_info.json",
|
||||||
)
|
)
|
||||||
Network.start()
|
network.loop.exec()
|
||||||
Network.loop.exec()
|
network_result = Network.get_result(network)
|
||||||
if Network.stutus_code == 200:
|
if network_result["status_code"] == 200:
|
||||||
download_info = Network.response_json
|
download_info = network_result["response_json"]
|
||||||
else:
|
else:
|
||||||
logger.warning(f"获取应用列表时出错:{Network.error_message}")
|
logger.warning(
|
||||||
|
f"获取应用列表时出错:{network_result['error_message']}"
|
||||||
|
)
|
||||||
MainInfoBar.push_info_bar(
|
MainInfoBar.push_info_bar(
|
||||||
"warning",
|
"warning",
|
||||||
"获取应用列表时出错",
|
"获取应用列表时出错",
|
||||||
f"网络错误:{Network.stutus_code}",
|
f"网络错误:{network_result['status_code']}",
|
||||||
5000,
|
5000,
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
@@ -464,8 +464,10 @@ class Setting(QWidget):
|
|||||||
3600000,
|
3600000,
|
||||||
if_force=True,
|
if_force=True,
|
||||||
)
|
)
|
||||||
|
SoundPlayer.play("有新版本")
|
||||||
else:
|
else:
|
||||||
MainInfoBar.push_info_bar("success", "更新检查", "已是最新版本~", 3000)
|
MainInfoBar.push_info_bar("success", "更新检查", "已是最新版本~", 3000)
|
||||||
|
SoundPlayer.play("无新版本")
|
||||||
|
|
||||||
def start_setup(self) -> None:
|
def start_setup(self) -> None:
|
||||||
subprocess.Popen(
|
subprocess.Popen(
|
||||||
@@ -488,20 +490,20 @@ class Setting(QWidget):
|
|||||||
"""显示公告"""
|
"""显示公告"""
|
||||||
|
|
||||||
# 从远程服务器获取最新公告
|
# 从远程服务器获取最新公告
|
||||||
Network.set_info(
|
network = Network.add_task(
|
||||||
mode="get",
|
mode="get",
|
||||||
url="https://gitee.com/DLmaster_361/AUTO_MAA/raw/server/notice.json",
|
url="https://gitee.com/DLmaster_361/AUTO_MAA/raw/server/notice.json",
|
||||||
)
|
)
|
||||||
Network.start()
|
network.loop.exec()
|
||||||
Network.loop.exec()
|
network_result = Network.get_result(network)
|
||||||
if Network.stutus_code == 200:
|
if network_result["status_code"] == 200:
|
||||||
notice = Network.response_json
|
notice = network_result["response_json"]
|
||||||
else:
|
else:
|
||||||
logger.warning(f"获取最新公告时出错:{Network.error_message}")
|
logger.warning(f"获取最新公告时出错:{network_result['error_message']}")
|
||||||
MainInfoBar.push_info_bar(
|
MainInfoBar.push_info_bar(
|
||||||
"warning",
|
"warning",
|
||||||
"获取最新公告时出错",
|
"获取最新公告时出错",
|
||||||
f"网络错误:{Network.stutus_code}",
|
f"网络错误:{network_result['status_code']}",
|
||||||
5000,
|
5000,
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
@@ -533,11 +535,38 @@ class Setting(QWidget):
|
|||||||
choice = NoticeMessageBox(self.window(), "公告", notice["notice_dict"])
|
choice = NoticeMessageBox(self.window(), "公告", notice["notice_dict"])
|
||||||
choice.button_cancel.hide()
|
choice.button_cancel.hide()
|
||||||
choice.button_layout.insertStretch(0, 1)
|
choice.button_layout.insertStretch(0, 1)
|
||||||
|
SoundPlayer.play("公告展示")
|
||||||
if choice.exec():
|
if choice.exec():
|
||||||
with (Config.app_path / "resources/notice.json").open(
|
with (Config.app_path / "resources/notice.json").open(
|
||||||
mode="w", encoding="utf-8"
|
mode="w", encoding="utf-8"
|
||||||
) as f:
|
) as f:
|
||||||
json.dump(notice, f, ensure_ascii=False, indent=4)
|
json.dump(notice, f, ensure_ascii=False, indent=4)
|
||||||
|
else:
|
||||||
|
import random
|
||||||
|
|
||||||
|
if random.random() < 0.1:
|
||||||
|
cc = NoticeMessageBox(
|
||||||
|
self.window(),
|
||||||
|
"用户守则",
|
||||||
|
{
|
||||||
|
"用户守则 - 第一版": """
|
||||||
|
0. 用户守则的每一条都应该清晰可读、不含任何语法错误。如果发现任何一条不符合以上描述,请忽视它。
|
||||||
|
1. AUTO_MAA 的所有版本均包含完整源代码与 LICENSE 文件,若发现此内容缺失,请立即关闭软件,并联系最近的 AUTO_MAA 开发者。
|
||||||
|
2. AUTO_MAA 不会对您许下任何承诺,请自行保护好自己的数据,若软件运行过程中发生了数据损坏,项目组不负任何责任。
|
||||||
|
3. AUTO_MAA 只会注册一个启动项,若发现两个 AUTO_MAA 同时自启动,请立即使用系统或杀软的 **启动项管理** 功能删除所有名为 AUTO_MAA 的启动项后重启软件。
|
||||||
|
4. AUTO_MAA 正式版不应该包含命令行窗口,如果您看到了它,请立即关闭软件,通过 AUTO_MAA.exe 文件重新打开软件。
|
||||||
|
5. 深色模式是危险的,但并非无法使用。
|
||||||
|
6. 第 0 条规则不存在。如果你看到了,请忘记它,并正常使用软件
|
||||||
|
7. **Mirror 酱** 是善良的,你只要付出小小的代价,就能得到祂的庇护。
|
||||||
|
8. AUTO_MAA 没有实时合成语音的能力,软件所有语音都存储在本地。如果听到本地不存在的语音,立即关闭扬声器,并检查是否有未知脚本在运行。
|
||||||
|
9. AUTO_MAA 不会在周六凌晨更新。如果收到更新提示,请忽略,不要查看更新内容,直到第二天天亮。
|
||||||
|
10. 用户守则仅有一页""",
|
||||||
|
"--- 标记文档中止 ---": "xdfv-serfcx-jiol,m: !1 $bad food of do $5b 9630-300 $daad 100-1\n\n// 0 == o //\n\n∠( °ω°)/",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
cc.button_cancel.hide()
|
||||||
|
cc.button_layout.insertStretch(0, 1)
|
||||||
|
cc.exec()
|
||||||
|
|
||||||
elif (
|
elif (
|
||||||
datetime.now()
|
datetime.now()
|
||||||
@@ -548,6 +577,7 @@ class Setting(QWidget):
|
|||||||
MainInfoBar.push_info_bar(
|
MainInfoBar.push_info_bar(
|
||||||
"info", "有新公告", "请前往设置界面查看公告", 3600000, if_force=True
|
"info", "有新公告", "请前往设置界面查看公告", 3600000, if_force=True
|
||||||
)
|
)
|
||||||
|
SoundPlayer.play("公告通知")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@@ -656,6 +686,36 @@ class FunctionSettingCard(HeaderCardWidget):
|
|||||||
self.addGroupWidget(widget)
|
self.addGroupWidget(widget)
|
||||||
|
|
||||||
|
|
||||||
|
class VoiceSettingCard(HeaderCardWidget):
|
||||||
|
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.setTitle("音效")
|
||||||
|
|
||||||
|
self.card_Enabled = SwitchSettingCard(
|
||||||
|
icon=FluentIcon.PAGE_RIGHT,
|
||||||
|
title="音效开关",
|
||||||
|
content="是否启用音效",
|
||||||
|
qconfig=Config,
|
||||||
|
configItem=Config.voice_Enabled,
|
||||||
|
parent=self,
|
||||||
|
)
|
||||||
|
self.card_Type = ComboBoxSettingCard(
|
||||||
|
icon=FluentIcon.PAGE_RIGHT,
|
||||||
|
title="音效模式",
|
||||||
|
content="选择音效的播放模式",
|
||||||
|
texts=["简洁", "聒噪"],
|
||||||
|
qconfig=Config,
|
||||||
|
configItem=Config.voice_Type,
|
||||||
|
parent=self,
|
||||||
|
)
|
||||||
|
|
||||||
|
Layout = QVBoxLayout()
|
||||||
|
Layout.addWidget(self.card_Enabled)
|
||||||
|
Layout.addWidget(self.card_Type)
|
||||||
|
self.viewLayout.addLayout(Layout)
|
||||||
|
|
||||||
|
|
||||||
class StartSettingCard(HeaderCardWidget):
|
class StartSettingCard(HeaderCardWidget):
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
@@ -1059,7 +1119,9 @@ class UpdaterSettingCard(HeaderCardWidget):
|
|||||||
parent=self,
|
parent=self,
|
||||||
)
|
)
|
||||||
mirrorchyan_url = HyperlinkButton(
|
mirrorchyan_url = HyperlinkButton(
|
||||||
"https://mirrorchyan.com/", "获取Mirror酱CDK", self
|
"https://mirrorchyan.com/zh/get-start?source=auto_maa-setting_card",
|
||||||
|
"获取Mirror酱CDK",
|
||||||
|
self,
|
||||||
)
|
)
|
||||||
self.card_MirrorChyanCDK.hBoxLayout.insertWidget(
|
self.card_MirrorChyanCDK.hBoxLayout.insertWidget(
|
||||||
5, mirrorchyan_url, 0, Qt.AlignRight
|
5, mirrorchyan_url, 0, Qt.AlignRight
|
||||||
|
|||||||
67
app/utils/ImageUtils.py
Normal file
67
app/utils/ImageUtils.py
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import base64
|
||||||
|
import hashlib
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
class ImageUtils:
|
||||||
|
@staticmethod
|
||||||
|
def get_base64_from_file(image_path):
|
||||||
|
"""从本地文件读取并返回base64编码字符串"""
|
||||||
|
with open(image_path, "rb") as f:
|
||||||
|
return base64.b64encode(f.read()).decode("utf-8")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def calculate_md5_from_file(image_path):
|
||||||
|
"""从本地文件读取并返回md5值(hex字符串)"""
|
||||||
|
with open(image_path, "rb") as f:
|
||||||
|
return hashlib.md5(f.read()).hexdigest()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def calculate_md5_from_base64(base64_content):
|
||||||
|
"""从base64字符串计算md5"""
|
||||||
|
image_data = base64.b64decode(base64_content)
|
||||||
|
return hashlib.md5(image_data).hexdigest()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def compress_image_if_needed(image_path: Path, max_size_mb=2) -> Path:
|
||||||
|
"""
|
||||||
|
如果图片大于max_size_mb,则压缩并覆盖原文件,返回原始路径(Path对象)
|
||||||
|
"""
|
||||||
|
if hasattr(Image, "Resampling"): # Pillow 9.1.0及以后
|
||||||
|
RESAMPLE = Image.Resampling.LANCZOS
|
||||||
|
else:
|
||||||
|
RESAMPLE = Image.ANTIALIAS
|
||||||
|
|
||||||
|
max_size = max_size_mb * 1024 * 1024
|
||||||
|
if image_path.stat().st_size <= max_size:
|
||||||
|
return image_path
|
||||||
|
|
||||||
|
img = Image.open(image_path)
|
||||||
|
suffix = image_path.suffix.lower()
|
||||||
|
quality = 90 if suffix in [".jpg", ".jpeg"] else None
|
||||||
|
step = 5
|
||||||
|
|
||||||
|
if suffix in [".jpg", ".jpeg"]:
|
||||||
|
while True:
|
||||||
|
img.save(image_path, quality=quality, optimize=True)
|
||||||
|
if image_path.stat().st_size <= max_size or quality <= 10:
|
||||||
|
break
|
||||||
|
quality -= step
|
||||||
|
elif suffix == ".png":
|
||||||
|
width, height = img.size
|
||||||
|
while True:
|
||||||
|
img.save(image_path, optimize=True)
|
||||||
|
if (
|
||||||
|
image_path.stat().st_size <= max_size
|
||||||
|
or width <= 200
|
||||||
|
or height <= 200
|
||||||
|
):
|
||||||
|
break
|
||||||
|
width = int(width * 0.95)
|
||||||
|
height = int(height * 0.95)
|
||||||
|
img = img.resize((width, height), RESAMPLE)
|
||||||
|
else:
|
||||||
|
raise ValueError("仅支持JPG/JPEG和PNG格式图片的压缩。")
|
||||||
|
|
||||||
|
return image_path
|
||||||
@@ -71,12 +71,12 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
os.system(
|
os.system(
|
||||||
"powershell -Command python -m nuitka --standalone --onefile --mingw64"
|
"powershell -Command python -m nuitka --standalone --onefile --mingw64"
|
||||||
" --enable-plugins=pyside6 --windows-console-mode=disable"
|
" --enable-plugins=pyside6 --windows-console-mode=attach"
|
||||||
" --onefile-tempdir-spec='{TEMP}\\AUTO_MAA'"
|
" --onefile-tempdir-spec='{TEMP}\\AUTO_MAA'"
|
||||||
" --windows-icon-from-ico=resources\\icons\\AUTO_MAA.ico"
|
" --windows-icon-from-ico=resources\\icons\\AUTO_MAA.ico"
|
||||||
" --company-name='AUTO_MAA Team' --product-name=AUTO_MAA"
|
" --company-name='AUTO_MAA Team' --product-name=AUTO_MAA"
|
||||||
f" --file-version={version["main_version"]}"
|
f" --file-version={version['main_version']}"
|
||||||
f" --product-version={version["main_version"]}"
|
f" --product-version={version['main_version']}"
|
||||||
" --file-description='AUTO_MAA Component'"
|
" --file-description='AUTO_MAA Component'"
|
||||||
" --copyright='Copyright © 2024-2025 DLmaster361'"
|
" --copyright='Copyright © 2024-2025 DLmaster361'"
|
||||||
" --assume-yes-for-downloads --output-filename=AUTO_MAA"
|
" --assume-yes-for-downloads --output-filename=AUTO_MAA"
|
||||||
|
|||||||
19
main.py
19
main.py
@@ -25,6 +25,25 @@ v4.3
|
|||||||
作者:DLmaster_361
|
作者:DLmaster_361
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# 屏蔽广告
|
||||||
|
import builtins
|
||||||
|
|
||||||
|
original_print = builtins.print
|
||||||
|
|
||||||
|
|
||||||
|
def no_print(*args, **kwargs):
|
||||||
|
if (
|
||||||
|
args
|
||||||
|
and isinstance(args[0], str)
|
||||||
|
and "QFluentWidgets Pro is now released." in args[0]
|
||||||
|
):
|
||||||
|
return
|
||||||
|
return original_print(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
builtins.print = no_print
|
||||||
|
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from PySide6.QtWidgets import QApplication
|
from PySide6.QtWidgets import QApplication
|
||||||
from qfluentwidgets import FluentTranslator
|
from qfluentwidgets import FluentTranslator
|
||||||
|
|||||||
@@ -3,11 +3,12 @@ plyer
|
|||||||
PySide6
|
PySide6
|
||||||
PySide6-Fluent-Widgets[full]
|
PySide6-Fluent-Widgets[full]
|
||||||
psutil
|
psutil
|
||||||
opencv-python
|
|
||||||
pywin32
|
pywin32
|
||||||
pyautogui
|
keyboard
|
||||||
pycryptodome
|
pycryptodome
|
||||||
|
certifi==2025.4.26
|
||||||
requests
|
requests
|
||||||
markdown
|
markdown
|
||||||
Jinja2
|
Jinja2
|
||||||
nuitka
|
nuitka
|
||||||
|
pillow
|
||||||
File diff suppressed because one or more lines are too long
Binary file not shown.
|
Before Width: | Height: | Size: 2.8 MiB After Width: | Height: | Size: 1.7 MiB |
BIN
resources/images/notification/six_star.png
Normal file
BIN
resources/images/notification/six_star.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 222 KiB |
BIN
resources/images/notification/test_notify.png
Normal file
BIN
resources/images/notification/test_notify.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
BIN
resources/sounds/both/删除用户.wav
Normal file
BIN
resources/sounds/both/删除用户.wav
Normal file
Binary file not shown.
BIN
resources/sounds/both/删除脚本实例.wav
Normal file
BIN
resources/sounds/both/删除脚本实例.wav
Normal file
Binary file not shown.
BIN
resources/sounds/both/删除计划表.wav
Normal file
BIN
resources/sounds/both/删除计划表.wav
Normal file
Binary file not shown.
BIN
resources/sounds/both/删除调度队列.wav
Normal file
BIN
resources/sounds/both/删除调度队列.wav
Normal file
Binary file not shown.
BIN
resources/sounds/both/欢迎回来.wav
Normal file
BIN
resources/sounds/both/欢迎回来.wav
Normal file
Binary file not shown.
BIN
resources/sounds/both/添加用户.wav
Normal file
BIN
resources/sounds/both/添加用户.wav
Normal file
Binary file not shown.
BIN
resources/sounds/both/添加脚本实例.wav
Normal file
BIN
resources/sounds/both/添加脚本实例.wav
Normal file
Binary file not shown.
BIN
resources/sounds/both/添加计划表.wav
Normal file
BIN
resources/sounds/both/添加计划表.wav
Normal file
Binary file not shown.
BIN
resources/sounds/both/添加调度队列.wav
Normal file
BIN
resources/sounds/both/添加调度队列.wav
Normal file
Binary file not shown.
BIN
resources/sounds/noisy/ADB失败.wav
Normal file
BIN
resources/sounds/noisy/ADB失败.wav
Normal file
Binary file not shown.
BIN
resources/sounds/noisy/ADB成功.wav
Normal file
BIN
resources/sounds/noisy/ADB成功.wav
Normal file
Binary file not shown.
BIN
resources/sounds/noisy/MAA在完成任务前中止.wav
Normal file
BIN
resources/sounds/noisy/MAA在完成任务前中止.wav
Normal file
Binary file not shown.
BIN
resources/sounds/noisy/MAA在完成任务前退出.wav
Normal file
BIN
resources/sounds/noisy/MAA在完成任务前退出.wav
Normal file
Binary file not shown.
BIN
resources/sounds/noisy/MAA更新.wav
Normal file
BIN
resources/sounds/noisy/MAA更新.wav
Normal file
Binary file not shown.
BIN
resources/sounds/noisy/MAA未检测到任何模拟器.wav
Normal file
BIN
resources/sounds/noisy/MAA未检测到任何模拟器.wav
Normal file
Binary file not shown.
BIN
resources/sounds/noisy/MAA未能正确登录PRTS.wav
Normal file
BIN
resources/sounds/noisy/MAA未能正确登录PRTS.wav
Normal file
Binary file not shown.
BIN
resources/sounds/noisy/MAA的ADB连接异常.wav
Normal file
BIN
resources/sounds/noisy/MAA的ADB连接异常.wav
Normal file
Binary file not shown.
BIN
resources/sounds/noisy/MAA进程超时.wav
Normal file
BIN
resources/sounds/noisy/MAA进程超时.wav
Normal file
Binary file not shown.
BIN
resources/sounds/noisy/MAA部分任务执行失败.wav
Normal file
BIN
resources/sounds/noisy/MAA部分任务执行失败.wav
Normal file
Binary file not shown.
BIN
resources/sounds/noisy/任务开始.wav
Normal file
BIN
resources/sounds/noisy/任务开始.wav
Normal file
Binary file not shown.
BIN
resources/sounds/noisy/任务结束.wav
Normal file
BIN
resources/sounds/noisy/任务结束.wav
Normal file
Binary file not shown.
BIN
resources/sounds/noisy/公告展示.wav
Normal file
BIN
resources/sounds/noisy/公告展示.wav
Normal file
Binary file not shown.
BIN
resources/sounds/noisy/公告通知.wav
Normal file
BIN
resources/sounds/noisy/公告通知.wav
Normal file
Binary file not shown.
BIN
resources/sounds/noisy/六星喜报.wav
Normal file
BIN
resources/sounds/noisy/六星喜报.wav
Normal file
Binary file not shown.
BIN
resources/sounds/noisy/历史记录查询.wav
Normal file
BIN
resources/sounds/noisy/历史记录查询.wav
Normal file
Binary file not shown.
BIN
resources/sounds/noisy/发生异常.wav
Normal file
BIN
resources/sounds/noisy/发生异常.wav
Normal file
Binary file not shown.
BIN
resources/sounds/noisy/发生错误.wav
Normal file
BIN
resources/sounds/noisy/发生错误.wav
Normal file
Binary file not shown.
BIN
resources/sounds/noisy/子任务失败.wav
Normal file
BIN
resources/sounds/noisy/子任务失败.wav
Normal file
Binary file not shown.
BIN
resources/sounds/noisy/排查录入.wav
Normal file
BIN
resources/sounds/noisy/排查录入.wav
Normal file
Binary file not shown.
BIN
resources/sounds/noisy/排查重试.wav
Normal file
BIN
resources/sounds/noisy/排查重试.wav
Normal file
Binary file not shown.
BIN
resources/sounds/noisy/无新版本.wav
Normal file
BIN
resources/sounds/noisy/无新版本.wav
Normal file
Binary file not shown.
BIN
resources/sounds/noisy/有新版本.wav
Normal file
BIN
resources/sounds/noisy/有新版本.wav
Normal file
Binary file not shown.
BIN
resources/sounds/noisy/森空岛签到失败.wav
Normal file
BIN
resources/sounds/noisy/森空岛签到失败.wav
Normal file
Binary file not shown.
BIN
resources/sounds/noisy/森空岛签到成功.wav
Normal file
BIN
resources/sounds/noisy/森空岛签到成功.wav
Normal file
Binary file not shown.
BIN
resources/sounds/simple/任务开始.wav
Normal file
BIN
resources/sounds/simple/任务开始.wav
Normal file
Binary file not shown.
BIN
resources/sounds/simple/任务结束.wav
Normal file
BIN
resources/sounds/simple/任务结束.wav
Normal file
Binary file not shown.
BIN
resources/sounds/simple/公告展示.wav
Normal file
BIN
resources/sounds/simple/公告展示.wav
Normal file
Binary file not shown.
BIN
resources/sounds/simple/公告通知.wav
Normal file
BIN
resources/sounds/simple/公告通知.wav
Normal file
Binary file not shown.
BIN
resources/sounds/simple/历史记录查询.wav
Normal file
BIN
resources/sounds/simple/历史记录查询.wav
Normal file
Binary file not shown.
BIN
resources/sounds/simple/发生异常.wav
Normal file
BIN
resources/sounds/simple/发生异常.wav
Normal file
Binary file not shown.
BIN
resources/sounds/simple/发生错误.wav
Normal file
BIN
resources/sounds/simple/发生错误.wav
Normal file
Binary file not shown.
BIN
resources/sounds/simple/无新版本.wav
Normal file
BIN
resources/sounds/simple/无新版本.wav
Normal file
Binary file not shown.
BIN
resources/sounds/simple/有新版本.wav
Normal file
BIN
resources/sounds/simple/有新版本.wav
Normal file
Binary file not shown.
@@ -1,53 +1,46 @@
|
|||||||
{
|
{
|
||||||
"main_version": "4.3.8.0",
|
"main_version": "4.3.12.0",
|
||||||
"version_info": {
|
"version_info": {
|
||||||
"4.3.8.0": {
|
"4.3.12.0": {
|
||||||
"新增功能": [
|
"修复BUG": [
|
||||||
"吐司通知在主窗口隐藏时不再弹出"
|
"固定certifi版本号"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"4.3.8.4": {
|
"4.3.11.0": {
|
||||||
"新增功能": [
|
"修复BUG": [
|
||||||
"支持为每一个用户执行独立通知",
|
"修复删除计划表引发的错误"
|
||||||
"输入文本框适配文本插入操作",
|
|
||||||
"计划表功能上线",
|
|
||||||
"静默控制时长从全任务内缩短至搜索ADB时段内",
|
|
||||||
"UI界面添加自动日常代理任务序列设置项"
|
|
||||||
],
|
|
||||||
"修复bug": [
|
|
||||||
"修复雷电模拟器静默模式无法正常识别模拟器是否隐藏相关问题"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"4.3.8.3": {
|
"4.3.10.0": {
|
||||||
"新增功能": [
|
"新增功能": [
|
||||||
"用户仪表盘支持直接控制用户状态"
|
"更换全新默认主页图",
|
||||||
],
|
"适配 MAA 无`Default`配置情况 #52"
|
||||||
"修复bug": [
|
|
||||||
"修复雷电ADB端口号相关问题"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"4.3.8.2": {
|
|
||||||
"新增功能": [
|
|
||||||
"添加ADB端口号宽幅适配能力"
|
|
||||||
],
|
|
||||||
"修复bug": [
|
|
||||||
"日志分析忽略MAA超时提示"
|
|
||||||
],
|
],
|
||||||
"程序优化": [
|
"程序优化": [
|
||||||
"配置类定义方法优化"
|
"静默模式控制时段延长至模拟器完成启动的10s后"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"4.3.8.1": {
|
"4.3.10.3": {
|
||||||
|
"程序优化": [
|
||||||
|
"使用 keyboard 模块替代 pyautogui 模块"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"4.3.10.2": {
|
||||||
"新增功能": [
|
"新增功能": [
|
||||||
"自定义基建显示配置名称 #46",
|
"公招喜报模板优化",
|
||||||
"主调度台添加仅一次电源任务"
|
"支持使用命令行调用"
|
||||||
],
|
],
|
||||||
"修复bug": [
|
"修复BUG": [
|
||||||
"电源相关选项改为所有任务完成后生效",
|
"修复更新动作重复执行问题"
|
||||||
"适配MAAv5.16.3的ADB报错信息更改"
|
|
||||||
],
|
],
|
||||||
"程序优化": [
|
"程序优化": [
|
||||||
"UI样式优化,进一步适配win10主题"
|
"Mirror 酱链接添加`source`字段,用于标识来源",
|
||||||
|
"优化下载器测速中止条件"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"4.3.10.1": {
|
||||||
|
"新增功能": [
|
||||||
|
"森空岛签到功能上线"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user