Compare commits

...

28 Commits

Author SHA1 Message Date
DLmaster361
85f3b4f607 Merge branch 'dev' 2025-06-10 18:49:00 +08:00
DLmaster361
916396f855 refactor: 使用 keyboard 模块替代 pyautogui 模块 2025-06-10 18:48:41 +08:00
DLmaster361
211c8d2b04 Merge branch 'dev' 2025-06-10 14:10:01 +08:00
DLmaster361
92e274d3fd ci: 移除pyscreeze 2025-06-10 14:09:40 +08:00
DLmaster361
d511ea48d5 Merge branch 'main' into dev 2025-06-09 23:45:03 +08:00
DLmaster361
1aa4da1adf feat: 支持使用命令行调用 2025-06-09 23:43:36 +08:00
DLmaster361
0e8b6b0b6b feat: 添加用户守则 2025-06-08 23:41:18 +08:00
DLmaster361
1a2c1b976f fix(maa): 更新动作执行后移除相应标记 2025-06-07 15:59:06 +08:00
DLmaster361
1cc242fa51 feat: 优化下载器测速中止条件 2025-06-06 22:38:37 +08:00
DLmaster361
18dfdba15d ci: 测试完整工作流 2025-06-06 00:09:01 +08:00
DLmaster361
b04ac4eec6 ci: 使用预设配置 2025-06-05 23:50:56 +08:00
DLmaster361
c009f0c891 ci: 测试证书注册 2025-06-05 23:39:09 +08:00
DLmaster361
d2dc0bd295 ci: 上传测试之二 2025-06-05 23:28:15 +08:00
DLmaster361
ddbb5b7f19 ci: 名字作出区分 2025-06-05 23:25:48 +08:00
DLmaster361
954c25090b ci: 测试上传工作 2025-06-05 23:25:08 +08:00
DLmaster361
0b6cc59de1 ci: 构建部分测试流程 2025-06-05 22:38:24 +08:00
DLmaster361
2271b5741d ci: 移除不支持的参数 2025-06-05 22:08:41 +08:00
DLmaster361
8a438b041f ci: 修正signpath证书参数 2025-06-05 21:09:03 +08:00
DLmaster361
dd92fcc4d8 ci: 添加证书测试工作流 2025-06-05 20:02:17 +08:00
DLmaster361
8f66ca0e16 Merge branch 'dev' 2025-06-05 19:10:49 +08:00
DLmaster361
895ba1d24a feat(res): 公招喜报模板优化 2025-06-04 11:21:34 +08:00
e49b807bef feat(ui): 完善森空岛签到功能的提示信息 2025-06-02 14:26:25 +08:00
DLmaster361
73c15b5e93 refactor(skland): 森空岛签到功能拆分独立 2025-06-02 14:05:31 +08:00
DLmaster361
e505ea8c51 feat(maa): 森空岛签到功能上线 2025-06-02 02:35:01 +08:00
DLmaster361
21e7df7c3e Merge branch 'dev' 2025-06-01 20:04:16 +08:00
DLmaster361
2d72ca66a4 fix(core): 修复网络模块子线程未及时销毁导致的程序崩溃 2025-06-01 19:52:22 +08:00
DLmaster361
4725a30165 fix(ui): 修复语音包禁忌二重奏 2025-06-01 04:31:29 +08:00
DLmaster361
f3c977f1b3 Merge branch 'main' into dev 2025-06-01 03:18:39 +08:00
24 changed files with 787 additions and 187 deletions

View File

@@ -28,9 +28,11 @@ permissions:
actions: write
jobs:
pre_check:
name: Pre Checks
runs-on: ubuntu-latest
steps:
- name: Repo Check
id: repo_check
@@ -40,67 +42,198 @@ jobs:
exit 1
fi
exit 0
build_AUTO_MAA:
runs-on: windows-latest
needs: pre_check
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python 3.12
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
python-version: '3.12'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install flake8 pytest
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
run: |
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
- name: Package
id: package
- name: Get version
id: get_version
run: |
copy app\utils\package.py .\
python package.py
- name: Read version
id: read_version
$version = (Get-Content resources/version.json | ConvertFrom-Json).main_version
echo "main_version=$version" >> $env:GITHUB_OUTPUT
- name: Nuitka build main program
uses: Nuitka/Nuitka-Action@main
with:
script-name: main.py
mode: app
enable-plugins: pyside6
onefile-tempdir-spec: "{TEMP}/AUTO_MAA"
windows-console-mode: attach
windows-icon-from-ico: resources/icons/AUTO_MAA.ico
company-name: AUTO_MAA Team
product-name: AUTO_MAA
file-version: ${{ steps.get_version.outputs.main_version }}
product-version: ${{ steps.get_version.outputs.main_version }}
file-description: AUTO_MAA Component
copyright: Copyright © 2024-2025 DLmaster361
assume-yes-for-downloads: true
output-file: AUTO_MAA
output-dir: AUTO_MAA
- name: Upload unsigned main program
id: upload-unsigned-main-program
uses: actions/upload-artifact@v4
with:
name: AUTO_MAA
path: AUTO_MAA/AUTO_MAA.exe
- name: Sign main program
id: sign_main_program
uses: signpath/github-action-submit-signing-request@v1.2
with:
api-token: '${{ secrets.SIGNPATH_API_TOKEN }}'
organization-id: '787a1d5f-6177-4f30-9559-d2646473584a'
project-slug: 'AUTO_MAA_'
signing-policy-slug: 'test-signing'
artifact-configuration-slug: "AUTO_MAA"
github-artifact-id: '${{ steps.upload-unsigned-main-program.outputs.artifact-id }}'
wait-for-completion: true
output-artifact-directory: 'AUTO_MAA'
- name: Add other resources
shell: pwsh
run: |
$MAIN_VERSION=(Get-Content -Path "version_info.txt" -TotalCount 1).Trim()
"AUTO_MAA_version=$MAIN_VERSION" | Out-File -FilePath $env:GITHUB_ENV -Append
$root = "${{ github.workspace }}"
$ver = "${{ steps.get_version.outputs.main_version }}"
Copy-Item "$root/app" "$root/AUTO_MAA/app" -Recurse
Copy-Item "$root/resources" "$root/AUTO_MAA/resources" -Recurse
Copy-Item "$root/main.py" "$root/AUTO_MAA/"
Copy-Item "$root/requirements.txt" "$root/AUTO_MAA/"
Copy-Item "$root/README.md" "$root/AUTO_MAA/"
Copy-Item "$root/LICENSE" "$root/AUTO_MAA/"
- name: Create Inno Setup script
shell: pwsh
run: |
$root = "${{ github.workspace }}"
$ver = "${{ steps.get_version.outputs.main_version }}"
$iss = Get-Content "$root/app/utils/AUTO_MAA.iss" -Raw
$iss = $iss -replace '#define MyAppVersion ""', "#define MyAppVersion `"$ver`""
$iss = $iss -replace '#define MyAppPath ""', "#define MyAppPath `"$root/AUTO_MAA`""
$iss = $iss -replace '#define OutputDir ""', "#define OutputDir `"$root`""
Set-Content -Path "$root/AUTO_MAA.iss" -Value $iss
- name: Build setup program
uses: Minionguyjpro/Inno-Setup-Action@v1.2.5
with:
path: AUTO_MAA.iss
- name: Upload unsigned setup program
id: upload-unsigned-setup-program
uses: actions/upload-artifact@v4
with:
name: AUTO_MAA-Setup
path: AUTO_MAA-Setup.exe
- name: Sign setup program
id: sign_setup_program
uses: signpath/github-action-submit-signing-request@v1.2
with:
api-token: '${{ secrets.SIGNPATH_API_TOKEN }}'
organization-id: '787a1d5f-6177-4f30-9559-d2646473584a'
project-slug: 'AUTO_MAA_'
signing-policy-slug: 'test-signing'
artifact-configuration-slug: "AUTO_MAA-Setup"
github-artifact-id: '${{ steps.upload-unsigned-setup-program.outputs.artifact-id }}'
wait-for-completion: true
output-artifact-directory: 'AUTO_MAA_Setup'
- name: Compress setup exe
shell: pwsh
run: Compress-Archive -Path AUTO_MAA_Setup/* -DestinationPath AUTO_MAA_${{ steps.get_version.outputs.main_version }}.zip
- name: Generate version info
shell: python
run: |
import json
from pathlib import Path
def version_text(version_numb):
while len(version_numb) < 4:
version_numb.append(0)
if version_numb[3] == 0:
return f"v{'.'.join(str(_) for _ in version_numb[0:3])}"
else:
return f"v{'.'.join(str(_) for _ in version_numb[0:3])}-beta.{version_numb[3]}"
def version_info_markdown(info):
version_info = ""
for key, value in info.items():
version_info += f"## {key}\n"
for v in value:
version_info += f"- {v}\n"
return version_info
root_path = Path(".")
version = json.loads((root_path / "resources/version.json").read_text(encoding="utf-8"))
main_version_numb = list(map(int, version["main_version"].split(".")))
all_version_info = {}
for v_i in version["version_info"].values():
for key, value in v_i.items():
if key in all_version_info:
all_version_info[key] += value.copy()
else:
all_version_info[key] = value.copy()
(root_path / "version_info.txt").write_text(
f"{version_text(main_version_numb)}\n\n<!--{json.dumps(version['version_info'], ensure_ascii=False)}-->\n{version_info_markdown(all_version_info)}",
encoding="utf-8",
)
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: AUTO_MAA_${{ env.AUTO_MAA_version }}
path: AUTO_MAA_${{ env.AUTO_MAA_version }}.zip
name: AUTO_MAA_${{ steps.get_version.outputs.main_version }}
path: AUTO_MAA_${{ steps.get_version.outputs.main_version }}.zip
- name: Upload Version_Info Artifact
uses: actions/upload-artifact@v4
with:
name: version_info
path: version_info.txt
publish_release:
name: Publish release
needs: build_AUTO_MAA
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Download artifacts
uses: actions/download-artifact@v4
with:
pattern: AUTO_MAA_*
merge-multiple: true
path: artifacts
- name: Download Version_Info
uses: actions/download-artifact@v4
with:
name: version_info
path: ./
- name: Create release
id: create_release
run: |
@@ -111,7 +244,7 @@ jobs:
NOTES_MAIN="$(sed 's/\r$//g' <(tail -n +3 version_info.txt))"
NOTES="$NOTES_MAIN
[已有 Mirror酱 CDK ?前往 Mirror酱 高速下载](https://mirrorchyan.com/zh/projects?rid=AUTO_MAA)
[已有 Mirror酱 CDK ?前往 Mirror酱 高速下载](https://mirrorchyan.com/zh/projects?rid=AUTO_MAA&source=auto_maa-release)
\`\`\`本release通过GitHub Actions自动构建\`\`\`"
if [ "${{ github.ref_name }}" == "main" ]; then
@@ -122,6 +255,7 @@ jobs:
gh release create "$TAGNAME" --target "main" --title "$NAME" --notes "$NOTES" $PRERELEASE_FLAG artifacts/*
env:
GITHUB_TOKEN: ${{ secrets.WORKFLOW_TOKEN }}
- name: Trigger MirrorChyanUploading
run: |
gh workflow run --repo $GITHUB_REPOSITORY mirrorchyan

View File

@@ -14,7 +14,7 @@
<a href="https://github.com/DLmaster361/AUTO_MAA/graphs/contributors"><img alt="GitHub Contributors" src="https://img.shields.io/github/contributors/DLmaster361/AUTO_MAA?style=flat-square"></a>
<a href="https://github.com/DLmaster361/AUTO_MAA/blob/main/LICENSE"><img alt="GitHub License" src="https://img.shields.io/github/license/DLmaster361/AUTO_MAA?style=flat-square"></a>
<a href="https://deepwiki.com/DLmaster361/AUTO_MAA"><img alt="DeepWiki" src="https://deepwiki.com/badge.svg"></a>
<a href="https://mirrorchyan.com/zh/projects?rid=AUTO_MAA"><img alt="mirrorc" src="https://img.shields.io/badge/Mirror%E9%85%B1-%239af3f6?logo=countingworkspro&logoColor=4f46e5"></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>
## 软件介绍
@@ -75,6 +75,12 @@
可在[《AUTO_MAA开发者协作文档》](https://docs.qq.com/aio/DQ3Z5eHNxdmxFQmZX)的`开发任务`页面中查看开发进度。
## 特别鸣谢
- 下载服务器:由[AoXuan (@ClozyA)](https://github.com/ClozyA) 个人为项目赞助。
- EXE签名: Free code signing provided by [SignPath.io](https://signpath.io/), certificate by [SignPath Foundation](https://signpath.org/)
## 贡献者
感谢以下贡献者对本项目做出的贡献
@@ -87,8 +93,6 @@
![Alt](https://repobeats.axiom.co/api/embed/6c2f834141eff1ac297db70d12bd11c6236a58a5.svg "Repobeats analytics image")
感谢 [AoXuan (@ClozyA)](https://github.com/ClozyA) 为本项目提供的下载服务器
## Star History
[![Star History Chart](https://api.star-history.com/svg?repos=DLmaster361/AUTO_MAA&type=Date)](https://star-history.com/#DLmaster361/AUTO_MAA&Date)

View File

@@ -27,6 +27,7 @@ v4.3
from loguru import logger
from PySide6.QtCore import Signal
import argparse
import sqlite3
import json
import sys
@@ -426,11 +427,14 @@ class MaaUserConfig(LQConfig):
self.Info_GameId_1 = ConfigItem("Info", "GameId_1", "-")
self.Info_GameId_2 = ConfigItem("Info", "GameId_2", "-")
self.Info_GameId_Remain = ConfigItem("Info", "GameId_Remain", "-")
self.Info_IfSkland = ConfigItem("Info", "IfSkland", False, BoolValidator())
self.Info_SklandToken = ConfigItem("Info", "SklandToken", "")
self.Data_LastProxyDate = ConfigItem("Data", "LastProxyDate", "2000-01-01")
self.Data_LastAnnihilationDate = ConfigItem(
"Data", "LastAnnihilationDate", "2000-01-01"
)
self.Data_LastSklandDate = ConfigItem("Data", "LastSklandDate", "2000-01-01")
self.Data_ProxyTimes = ConfigItem(
"Data", "ProxyTimes", 0, RangeValidator(0, 1024)
)
@@ -572,7 +576,7 @@ class MaaPlanConfig(LQConfig):
class AppConfig(GlobalConfig):
VERSION = "4.3.9.1"
VERSION = "4.3.10.3"
gameid_refreshed = Signal()
PASSWORD_refreshed = Signal()
@@ -611,6 +615,27 @@ class AppConfig(GlobalConfig):
self.if_ignore_silence = 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()
def initialize(self) -> None:
@@ -633,7 +658,8 @@ class AppConfig(GlobalConfig):
def init_logger(self) -> None:
"""初始化日志记录器"""
logger.remove(0)
if self.args.mode != "cli":
logger.remove(0)
logger.add(
sink=self.log_path,
@@ -646,10 +672,14 @@ class AppConfig(GlobalConfig):
retention="1 month",
compression="zip",
)
logger.info("")
logger.info("===================================")
logger.info("AUTO_MAA 主程序")
logger.info(f"版本号: v{self.VERSION}")
logger.info(f"根目录: {self.app_path}")
logger.info(
f"运行模式: {'图形化界面' if self.args.mode == 'gui' else '命令行界面'}"
)
logger.info("===================================")
logger.info("日志记录器初始化完成")
@@ -1256,6 +1286,10 @@ class AppConfig(GlobalConfig):
user_config.Data_LastAnnihilationDate,
info["Config"]["Data"]["LastAnnihilationDate"],
)
user_config.set(
user_config.Data_LastSklandDate,
info["Config"]["Data"]["LastSklandDate"],
)
user_config.set(
user_config.Data_ProxyTimes, info["Config"]["Data"]["ProxyTimes"]
)

View File

@@ -27,6 +27,7 @@ v4.3
from loguru import logger
from PySide6.QtCore import QObject, QThread, QEventLoop
import re
import time
import requests
from pathlib import Path
@@ -42,6 +43,10 @@ class NetworkThread(QThread):
def __init__(self, mode: str, url: str, path: Path = None) -> None:
super().__init__()
self.setObjectName(
f"NetworkThread-{mode}-{re.sub(r'(&cdk=)[^&]+(&)', r'\1******\2', url)}"
)
self.mode = mode
self.url = url
self.path = path
@@ -125,13 +130,22 @@ class _Network(QObject):
def get_result(self, network_thread: NetworkThread) -> dict:
"""获取网络请求结果"""
self.task_queue.remove(network_thread)
return {
result = {
"status_code": network_thread.status_code,
"response_json": network_thread.response_json,
"error_message": network_thread.error_message,
"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()

View File

@@ -41,27 +41,26 @@ class _SoundPlayer(QObject):
self.sounds_path = Config.app_path / "resources/sounds"
def play(self, sound_name: str, parent: QObject = None):
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", parent)
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",
parent,
self.sounds_path / Config.get(Config.voice_Type) / f"{sound_name}.wav"
)
def play_voice(self, sound_path: Path, parent: QObject):
def play_voice(self, sound_path: Path):
effect = QSoundEffect(self if parent is None else parent)
effect = QSoundEffect(self)
effect.setVolume(1)
effect.setSource(QUrl.fromLocalFile(sound_path))
effect.play()

View File

@@ -45,6 +45,7 @@ class Task(QThread):
check_maa_version = Signal(str)
push_info_bar = Signal(str, str, str, int)
play_sound = Signal(str)
question = Signal(str, str)
question_response = Signal(bool)
update_user_info = Signal(str, dict)
@@ -60,6 +61,8 @@ class Task(QThread):
):
super(Task, self).__init__()
self.setObjectName(f"Task-{mode}-{name}")
self.mode = mode
self.name = name
self.info = info
@@ -83,6 +86,7 @@ class Task(QThread):
)
self.task.check_maa_version.connect(self.check_maa_version.emit)
self.task.push_info_bar.connect(self.push_info_bar.emit)
self.task.play_sound.connect(self.play_sound.emit)
self.task.accomplish.connect(lambda: self.accomplish.emit([]))
self.task.run()
@@ -142,6 +146,7 @@ class Task(QThread):
self.question_response.disconnect()
self.question_response.connect(self.task.question_response.emit)
self.task.push_info_bar.connect(self.push_info_bar.emit)
self.task.play_sound.connect(self.play_sound.emit)
self.task.create_user_list.connect(self.create_user_list.emit)
self.task.update_user_list.connect(self.update_user_list.emit)
self.task.update_log_text.connect(self.update_log_text.emit)
@@ -201,6 +206,7 @@ class _TaskManager(QObject):
lambda title, content: self.push_dialog(name, title, content)
)
self.task_dict[name].push_info_bar.connect(MainInfoBar.push_info_bar)
self.task_dict[name].play_sound.connect(SoundPlayer.play)
self.task_dict[name].update_user_info.connect(Config.change_user_info)
self.task_dict[name].accomplish.connect(
lambda logs: self.remove_task(mode, name, logs)
@@ -277,6 +283,9 @@ 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):
"""检查MAA版本"""

View File

@@ -29,7 +29,7 @@ from loguru import logger
from PySide6.QtCore import QObject, QTimer
from datetime import datetime
from pathlib import Path
import pyautogui
import keyboard
from .config import Config
from .task_manager import TaskManager
@@ -41,8 +41,6 @@ class _MainTimer(QObject):
def __init__(self, parent=None):
super().__init__(parent)
self.if_FailSafeException = False
self.Timer = QTimer()
self.Timer.timeout.connect(self.timed_start)
self.Timer.timeout.connect(self.set_silence)
@@ -113,16 +111,14 @@ class _MainTimer(QObject):
for emulator_path in Config.silence_list
):
try:
pyautogui.hotkey(
*[
keyboard.press_and_release(
"+".join(
_.strip().lower()
for _ in Config.get(Config.function_BossKey).split("+")
]
)
)
except pyautogui.FailSafeException as e:
if not self.if_FailSafeException:
logger.warning(f"FailSafeException: {e}")
self.if_FailSafeException = True
except Exception as e:
logger.error(f"模拟按键时出错:{e}")
def check_power(self):

View File

@@ -30,7 +30,6 @@ from PySide6.QtCore import QObject, Signal, QEventLoop, QFileSystemWatcher, QTim
import json
import subprocess
import shutil
import time
import re
import win32com.client
from datetime import datetime, timedelta
@@ -38,8 +37,8 @@ from pathlib import Path
from jinja2 import Environment, FileSystemLoader
from typing import Union, List, Dict
from app.core import Config, MaaConfig, MaaUserConfig, SoundPlayer
from app.services import Notify, System
from app.core import Config, MaaConfig, MaaUserConfig
from app.services import Notify, Crypto, System, skland_sign_in
class MaaManager(QObject):
@@ -50,6 +49,7 @@ class MaaManager(QObject):
question_response = Signal(bool)
update_user_info = Signal(str, dict)
push_info_bar = Signal(str, str, str, int)
play_sound = Signal(str)
create_user_list = Signal(list)
update_user_list = Signal(list)
update_log_text = Signal(str)
@@ -219,6 +219,51 @@ class MaaManager(QObject):
user_logs_list = []
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 (
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"]:
@@ -564,9 +609,9 @@ class MaaManager(QObject):
1,
)
if i == self.set["RunSet"]["RunTimesLimit"] - 1:
SoundPlayer.play("子任务失败", self)
self.play_sound.emit("子任务失败")
else:
SoundPlayer.play(self.maa_result, self)
self.play_sound.emit(self.maa_result)
self.sleep(10)
# 任务结束后释放ADB
@@ -619,7 +664,7 @@ class MaaManager(QObject):
},
user_data,
)
SoundPlayer.play("六星喜报", self)
self.play_sound.emit("六星喜报")
# 执行MAA解压更新动作
if self.maa_update_package:
@@ -631,7 +676,7 @@ class MaaManager(QObject):
self.update_log_text.emit(
f"检测到MAA存在更新\nMAA正在执行更新动作\n请等待10s"
)
SoundPlayer.play("MAA更新", self)
self.play_sound.emit("MAA更新")
self.set_maa("更新MAA", None)
subprocess.Popen(
[self.maa_exe_path],
@@ -640,6 +685,8 @@ class MaaManager(QObject):
self.sleep(10)
System.kill_process(self.maa_exe_path)
self.maa_update_package = ""
logger.info(f"{self.name} | 更新动作结束")
# 发送统计信息
@@ -752,7 +799,7 @@ class MaaManager(QObject):
# 登录失败,询问是否结束循环
elif not self.isInterruptionRequested:
SoundPlayer.play("排查重试", self)
self.play_sound.emit("排查重试")
if not self.push_question(
"操作提示", "MAA未能正确登录到PRTS是否重试"
):
@@ -761,7 +808,7 @@ class MaaManager(QObject):
# 登录成功,录入人工排查情况
if run_book[0] and not self.isInterruptionRequested:
SoundPlayer.play("排查录入", self)
self.play_sound.emit("排查录入")
if self.push_question(
"操作提示", "请检查用户代理情况,该用户是否正确完成代理任务?"
):
@@ -909,6 +956,9 @@ class MaaManager(QObject):
self.sleep(self.wait_time)
if self.isInterruptionRequested:
return None
# 移除静默进程标记
Config.silence_list.remove(self.emulator_path)
@@ -932,6 +982,7 @@ class MaaManager(QObject):
connect_result = subprocess.run(
[self.ADB_path, "connect", ADB_address],
creationflags=subprocess.CREATE_NO_WINDOW,
stdin=subprocess.DEVNULL,
capture_output=True,
text=True,
encoding="utf-8",
@@ -943,6 +994,7 @@ class MaaManager(QObject):
devices_result = subprocess.run(
[self.ADB_path, "devices"],
creationflags=subprocess.CREATE_NO_WINDOW,
stdin=subprocess.DEVNULL,
capture_output=True,
text=True,
encoding="utf-8",
@@ -970,7 +1022,7 @@ class MaaManager(QObject):
with self.maa_set_path.open(mode="w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=4)
SoundPlayer.play("ADB成功", self)
self.play_sound.emit("ADB成功")
return None
else:
@@ -978,7 +1030,8 @@ class MaaManager(QObject):
else:
logger.info(f"{self.name} | 无法连接到ADB地址{ADB_address}")
SoundPlayer.play("ADB失败", self)
if not self.isInterruptionRequested:
self.play_sound.emit("ADB失败")
def refresh_maa_log(self) -> None:
"""刷新MAA日志"""

View File

@@ -32,5 +32,6 @@ __license__ = "GPL-3.0 license"
from .notification import Notify
from .security import Crypto
from .system import System
from .skland import skland_sign_in
__all__ = ["Notify", "Crypto", "System"]
__all__ = ["Notify", "Crypto", "System", "skland_sign_in"]

239
app/services/skland.py Normal file
View File

@@ -0,0 +1,239 @@
# AUTO_MAA:A MAA Multi Account Management and Automation Tool
# Copyright © 2024-2025 DLmaster361
# This file incorporates work covered by the following copyright and
# permission notice:
#
# skland-checkin-ghaction Copyright © 2023 Yanstory
# https://github.com/Yanstory/skland-checkin-ghaction
# This file is part of AUTO_MAA.
# AUTO_MAA is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published
# by the Free Software Foundation, either version 3 of the License,
# or (at your option) any later version.
# AUTO_MAA is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
# the GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with AUTO_MAA. If not, see <https://www.gnu.org/licenses/>.
# Contact: DLmaster_361@163.com
"""
AUTO_MAA
AUTO_MAA森空岛服务
v4.3
作者DLmaster_361、ClozyA
"""
from loguru import logger
import time
import json
import hmac
import hashlib
import requests
from urllib import parse
def skland_sign_in(token) -> dict:
"""森空岛签到"""
app_code = "4ca99fa6b56cc2ba"
# 用于获取grant code
grant_code_url = "https://as.hypergryph.com/user/oauth2/v2/grant"
# 用于获取cred
cred_code_url = "https://zonai.skland.com/api/v1/user/auth/generate_cred_by_code"
# 查询角色绑定
binding_url = "https://zonai.skland.com/api/v1/game/player/binding"
# 签到接口
sign_url = "https://zonai.skland.com/api/v1/game/attendance"
# 基础请求头
header = {
"cred": "",
"User-Agent": "Skland/1.5.1 (com.hypergryph.skland; build:100501001; Android 34;) Okhttp/4.11.0",
"Accept-Encoding": "gzip",
"Connection": "close",
}
header_login = header.copy()
header_for_sign = {
"platform": "1",
"timestamp": "",
"dId": "",
"vName": "1.5.1",
}
# 生成签名
def generate_signature(token_for_sign: str, path, body_or_query):
"""
生成请求签名
:param token_for_sign: 用于加密的token
:param path: 请求路径(如 /api/v1/game/player/binding
:param body_or_query: GET用query字符串POST用body字符串
:return: (sign, 新的header_for_sign字典)
"""
t = str(int(time.time()) - 2) # 时间戳,-2秒以防服务器时间不一致
token_bytes = token_for_sign.encode("utf-8")
header_ca = dict(header_for_sign)
header_ca["timestamp"] = t
header_ca_str = json.dumps(header_ca, separators=(",", ":"))
s = path + body_or_query + t + header_ca_str # 拼接原始字符串
# HMAC-SHA256 + MD5得到最终sign
hex_s = hmac.new(token_bytes, s.encode("utf-8"), hashlib.sha256).hexdigest()
md5 = hashlib.md5(hex_s.encode("utf-8")).hexdigest()
return md5, header_ca
# 获取带签名的header
def get_sign_header(url: str, method, body, old_header, sign_token):
"""
获取带签名的请求头
:param url: 请求完整url
:param method: 请求方式 GET/POST
:param body: POST请求体或GET时为None
:param old_header: 原始请求头
:param sign_token: 当前会话的签名token
:return: 新请求头
"""
h = json.loads(json.dumps(old_header))
p = parse.urlparse(url)
if method.lower() == "get":
sign, header_ca = generate_signature(sign_token, p.path, p.query)
else:
sign, header_ca = generate_signature(
sign_token, p.path, json.dumps(body) if body else ""
)
h["sign"] = sign
for i in header_ca:
h[i] = header_ca[i]
return h
# 复制请求头并添加cred
def copy_header(cred):
v = json.loads(json.dumps(header))
v["cred"] = cred
return v
# 使用token一步步拿到cred和sign_token
def login_by_token(token_code):
"""
:param token_code: 你的skyland token
:return: (cred, sign_token)
"""
try:
# token为json对象时提取data.content
t = json.loads(token_code)
token_code = t["data"]["content"]
except:
pass
grant_code = get_grant_code(token_code)
return get_cred(grant_code)
# 通过grant code换cred和sign_token
def get_cred(grant):
rsp = requests.post(
cred_code_url, json={"code": grant, "kind": 1}, headers=header_login
).json()
if rsp["code"] != 0:
raise Exception(f'获得cred失败{rsp.get("messgae")}')
sign_token = rsp["data"]["token"]
cred = rsp["data"]["cred"]
return cred, sign_token
# 通过token换grant code
def get_grant_code(token):
rsp = requests.post(
grant_code_url,
json={"appCode": app_code, "token": token, "type": 0},
headers=header_login,
).json()
if rsp["status"] != 0:
raise Exception(f'使用token: {token} 获得认证代码失败:{rsp.get("msg")}')
return rsp["data"]["code"]
# 获取已绑定的角色列表
def get_binding_list(cred, sign_token):
"""
查询绑定的角色
:param cred: 当前cred
:param sign_token: 当前sign_token
:return: 角色列表
"""
v = []
rsp = requests.get(
binding_url,
headers=get_sign_header(
binding_url, "get", None, copy_header(cred), sign_token
),
).json()
if rsp["code"] != 0:
logger.error(f"森空岛服务 | 请求角色列表出现问题:{rsp['message']}")
if rsp.get("message") == "用户未登录":
logger.error(f"森空岛服务 | 用户登录可能失效了,请重新登录!")
return v
# 只取明日方舟arknights的绑定账号
for i in rsp["data"]["list"]:
if i.get("appCode") != "arknights":
continue
v.extend(i.get("bindingList"))
return v
# 执行签到
def do_sign(cred, sign_token) -> dict:
"""
对所有绑定的角色进行签到
:param cred: 当前cred
:param sign_token: 当前sign_token
:return: 签到结果字典
"""
characters = get_binding_list(cred, sign_token)
result = {"成功": [], "重复": [], "失败": [], "总计": len(characters)}
for character in characters:
body = {
"uid": character.get("uid"),
"gameId": character.get("channelMasterId"),
}
rsp = requests.post(
sign_url,
headers=get_sign_header(
sign_url, "post", body, copy_header(cred), sign_token
),
json=body,
).json()
if rsp["code"] != 0:
result[
"重复" if rsp.get("message") == "请勿重复签到!" else "失败"
].append(
f"{character.get("nickName")}{character.get("channelName")}"
)
else:
result["成功"].append(
f"{character.get("nickName")}{character.get("channelName")}"
)
time.sleep(3)
return result
# 主流程
try:
# 拿到cred和sign_token
cred, sign_token = login_by_token(token)
time.sleep(1)
# 依次签到
return do_sign(cred, sign_token)
except Exception as e:
logger.error(f"森空岛服务 | 森空岛签到失败: {e}")
return {"成功": [], "重复": [], "失败": [], "总计": 0}

View File

@@ -112,6 +112,7 @@ class _SystemHandler:
Config.main_window.close()
QApplication.quit()
sys.exit(0)
elif sys.platform.startswith("linux"):
@@ -138,6 +139,7 @@ class _SystemHandler:
Config.main_window.close()
QApplication.quit()
sys.exit(0)
def is_startup(self) -> bool:
"""判断程序是否已经开机自启"""

View File

@@ -1,6 +1,12 @@
# 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:
#
# ZenlessZoneZero-OneDragon Copyright © 2024-2025 DoctorReid
# https://github.com/DoctorReid/ZenlessZoneZero-OneDragon
# This file is part of AUTO_MAA.
# AUTO_MAA is free software: you can redistribute it and/or modify
@@ -156,7 +162,7 @@ class ProgressRingMessageBox(MessageBoxBase):
super().__init__(parent)
self.title = SubtitleLabel(title)
self.time = 100
self.time = 100 if Config.args.mode == "gui" else 1
Widget = QWidget()
Layout = QHBoxLayout(Widget)
self.ring = ProgressRing()
@@ -608,6 +614,83 @@ class PushAndSwitchButtonSettingCard(SettingCard):
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):
"""Setting card with push & combo box"""
@@ -1129,6 +1212,13 @@ class UserLableSettingCard(SettingCard):
== Config.server_date().isocalendar()[:2]
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))

View File

@@ -25,6 +25,7 @@ v4.3
作者DLmaster_361
"""
from loguru import logger
import zipfile
import requests
import subprocess
@@ -78,12 +79,15 @@ class DownloadProcess(QThread):
) -> None:
super(DownloadProcess, self).__init__()
self.setObjectName(f"DownloadProcess-{url}-{start_byte}-{end_byte}")
self.url = url
self.start_byte = start_byte
self.end_byte = end_byte
self.download_path = download_path
self.check_times = check_times
@logger.catch
def run(self) -> None:
# 清理可能存在的临时文件
@@ -157,10 +161,13 @@ class ZipExtractProcess(QThread):
def __init__(self, name: str, app_path: Path, download_path: Path) -> None:
super(ZipExtractProcess, self).__init__()
self.setObjectName(f"ZipExtractProcess-{name}")
self.name = name
self.app_path = app_path
self.download_path = download_path
@logger.catch
def run(self) -> None:
try:
@@ -234,6 +241,7 @@ class DownloadManager(QDialog):
self.download_path = app_path / "DOWNLOAD_TEMP.zip" # 临时下载文件的路径
self.download_process_dict: Dict[str, DownloadProcess] = {}
self.timer_dict: Dict[str, QTimer] = {}
self.if_speed_test_accomplish = False
self.resize(700, 70)
@@ -405,6 +413,15 @@ class DownloadManager(QDialog):
if not self.download_process_dict:
self.download_process_clear.emit()
# 当有速度大于1 MB/s的链接或存在3个即以上链接测速完成时停止其他测速
if not self.if_speed_test_accomplish and (
sum(1 for speed in self.test_speed_result.values() if speed > 0) >= 3
or any(speed > 1 for speed in self.test_speed_result.values())
):
self.if_speed_test_accomplish = True
for timer in self.timer_dict.values():
timer.timeout.emit()
if any(speed == -1 for _, speed in self.test_speed_result.items()):
return None

View File

@@ -405,4 +405,6 @@ class ButtonGroup(SimpleCardWidget):
def open_sales(self):
"""打开 MirrorChyan 链接"""
QDesktopServices.openUrl(QUrl("https://mirrorchyan.com/"))
QDesktopServices.openUrl(
QUrl("https://mirrorchyan.com/zh/get-start?source=auto_maa-home")
)

View File

@@ -257,6 +257,9 @@ class AUTO_MAA(MSFluentWindow):
) -> None:
"""配置窗口状态"""
if Config.args.mode != "gui":
return None
self.switch_theme()
if mode == "显示主窗口":
@@ -379,7 +382,40 @@ class AUTO_MAA(MSFluentWindow):
self.titleBar.minBtn.click()
SoundPlayer.play("MAA在完成任务前退出")
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):
"""

View File

@@ -80,6 +80,7 @@ from .Widget import (
EditableComboBoxWithPlanSettingCard,
SpinBoxWithPlanSettingCard,
PasswordLineEditSettingCard,
PasswordLineAndSwitchButtonSettingCard,
UserLableSettingCard,
UserTaskSettingCard,
ComboBoxSettingCard,
@@ -1585,6 +1586,18 @@ class MemberManager(QWidget):
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(
icon=FluentIcon.INFO,
@@ -1596,6 +1609,8 @@ class MemberManager(QWidget):
"LastAnnihilationDate": self.config.Data_LastAnnihilationDate,
"ProxyTimes": self.config.Data_ProxyTimes,
"IfPassCheck": self.config.Data_IfPassCheck,
"IfSkland": self.config.Info_IfSkland,
"LastSklandDate": self.config.Data_LastSklandDate,
},
parent=self,
)
@@ -1778,6 +1793,7 @@ class MemberManager(QWidget):
Layout.addLayout(h6_layout)
Layout.addLayout(h7_layout)
Layout.addLayout(h8_layout)
Layout.addWidget(self.card_Skland)
Layout.addWidget(self.card_TaskSet)
Layout.addWidget(self.card_NotifySet)

View File

@@ -541,6 +541,32 @@ class Setting(QWidget):
mode="w", encoding="utf-8"
) as f:
json.dump(notice, f, ensure_ascii=False, indent=4)
else:
import random
if random.random() < 0.1:
cc = NoticeMessageBox(
self.window(),
"用户守则",
{
"用户守则 - 第一版": """
0. 用户守则的每一条都应该清晰可读、不含任何语法错误。如果发现任何一条不符合以上描述,请忽视它。
1. AUTO_MAA 的所有版本均包含完整源代码与 LICENSE 文件,若发现此内容缺失,请立即关闭软件,并联系最近的 AUTO_MAA 开发者。
2. AUTO_MAA 不会对您许下任何承诺,请自行保护好自己的数据,若软件运行过程中发生了数据损坏,项目组不负任何责任。
3. AUTO_MAA 只会注册一个启动项,若发现两个 AUTO_MAA 同时自启动,请立即使用系统或杀软的 **启动项管理** 功能删除所有名为 AUTO_MAA 的启动项后重启软件。
4. AUTO_MAA 正式版不应该包含命令行窗口,如果您看到了它,请立即关闭软件,通过 AUTO_MAA.exe 文件重新打开软件。
5. 深色模式是危险的,但并非无法使用。
6. 第 0 条规则不存在。如果你看到了,请忘记它,并正常使用软件
7. **Mirror 酱** 是善良的,你只要付出小小的代价,就能得到祂的庇护。
8. AUTO_MAA 没有实时合成语音的能力,软件所有语音都存储在本地。如果听到本地不存在的语音,立即关闭扬声器,并检查是否有未知脚本在运行。
9. AUTO_MAA 不会在周六凌晨更新。如果收到更新提示,请忽略,不要查看更新内容,直到第二天天亮。
10. 用户守则仅有一页""",
"--- 标记文档中止 ---": "xdfv-serfcx-jiol,m: !1 $bad food of do $5b 9630-300 $daad 100-1\n\n// 0 == o //\n\n∠( °ω°)/",
},
)
cc.button_cancel.hide()
cc.button_layout.insertStretch(0, 1)
cc.exec()
elif (
datetime.now()
@@ -1093,7 +1119,9 @@ class UpdaterSettingCard(HeaderCardWidget):
parent=self,
)
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(
5, mirrorchyan_url, 0, Qt.AlignRight

View File

@@ -71,12 +71,12 @@ if __name__ == "__main__":
os.system(
"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'"
" --windows-icon-from-ico=resources\\icons\\AUTO_MAA.ico"
" --company-name='AUTO_MAA Team' --product-name=AUTO_MAA"
f" --file-version={version["main_version"]}"
f" --product-version={version["main_version"]}"
f" --file-version={version['main_version']}"
f" --product-version={version['main_version']}"
" --file-description='AUTO_MAA Component'"
" --copyright='Copyright © 2024-2025 DLmaster361'"
" --assume-yes-for-downloads --output-filename=AUTO_MAA"

19
main.py
View File

@@ -25,6 +25,25 @@ v4.3
作者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 PySide6.QtWidgets import QApplication
from qfluentwidgets import FluentTranslator

View File

@@ -3,9 +3,8 @@ plyer
PySide6
PySide6-Fluent-Widgets[full]
psutil
opencv-python
pywin32
pyautogui
keyboard
pycryptodome
requests
markdown

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

View File

@@ -1,16 +1,27 @@
{
"main_version": "4.3.9.1",
"main_version": "4.3.10.3",
"version_info": {
"4.3.9.1": {
"4.3.10.3": {
"程序优化": [
"使用 keyboard 模块替代 pyautogui 模块"
]
},
"4.3.10.2": {
"新增功能": [
"语音功能上线"
"公招喜报模板优化",
"支持使用命令行调用"
],
"修复bug": [
"网络模块支持并发请求",
"修复中止任务时程序异常卡顿"
"修复BUG": [
"修复更新动作重复执行问题"
],
"程序优化": [
"非UI组件转为QObject类"
"Mirror 酱链接添加`source`字段,用于标识来源",
"优化下载器测速中止条件"
]
},
"4.3.10.1": {
"新增功能": [
"森空岛签到功能上线"
]
}
}