diff --git a/app/api/info.py b/app/api/info.py index e7194ac..a184a6e 100644 --- a/app/api/info.py +++ b/app/api/info.py @@ -28,6 +28,34 @@ from app.models.schema import * router = APIRouter(prefix="/api/info", tags=["信息获取"]) +@router.post( + "/version", + summary="获取后端git版本信息", + response_model=VersionOut, + status_code=200, +) +async def get_git_version() -> VersionOut: + + try: + is_latest, commit_hash, commit_time = await Config.get_git_version() + except Exception as e: + return VersionOut( + code=500, + status="error", + message=f"{type(e).__name__}: {str(e)}", + if_latest=False, + current_hash="", + current_time="", + current_version="", + ) + return VersionOut( + if_latest=is_latest, + current_hash=commit_hash, + current_time=commit_time, + current_version=Config.version(), + ) + + @router.post( "/combox/stage", summary="获取关卡号下拉框信息", diff --git a/app/core/config.py b/app/core/config.py index ae3d21e..6569e9c 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -28,11 +28,11 @@ import sqlite3 import calendar import requests import truststore +from git import Repo from pathlib import Path from fastapi import WebSocket -from urllib.parse import quote from collections import defaultdict -from datetime import datetime, timedelta, date, timezone +from datetime import datetime, timedelta, date from typing import Literal, Optional from app.models.ConfigBase import * @@ -597,6 +597,7 @@ class AppConfig(GlobalConfig): self.config_path.mkdir(parents=True, exist_ok=True) self.history_path.mkdir(parents=True, exist_ok=True) + self.repo = Repo(Path.cwd()) self.server: Optional[uvicorn.Server] = None self.websocket: Optional[WebSocket] = None self.silence_dict: Dict[Path, datetime] = {} @@ -908,6 +909,26 @@ class AppConfig(GlobalConfig): else: await Config.websocket.send_json(data) + async def get_git_version(self) -> tuple[bool, str, str]: + + # 获取当前 commit + current_commit = self.repo.head.commit + + # 获取 commit 哈希 + commit_hash = current_commit.hexsha + + # 获取 commit 时间 + commit_time = datetime.fromtimestamp(current_commit.committed_date) + + # 检查是否为最新 commit + # 获取远程分支的最新 commit + origin = self.repo.remotes.origin + origin.fetch() # 拉取最新信息 + remote_commit = self.repo.commit(f"origin/{self.repo.active_branch.name}") + is_latest = bool(current_commit.hexsha == remote_commit.hexsha) + + return is_latest, commit_hash, commit_time.strftime("%Y-%m-%d %H:%M:%S") + async def add_script( self, script: Literal["MAA", "General"] ) -> tuple[uuid.UUID, ConfigBase]: diff --git a/app/models/schema.py b/app/models/schema.py index b520343..827c683 100644 --- a/app/models/schema.py +++ b/app/models/schema.py @@ -34,6 +34,13 @@ class InfoOut(OutBase): data: Dict[str, Any] = Field(..., description="收到的服务器数据") +class VersionOut(OutBase): + if_latest: bool = Field(..., description="后端代码是否为最新") + current_hash: str = Field(..., description="后端代码当前哈希值") + current_time: str = Field(..., description="后端代码当前时间戳") + current_version: str = Field(..., description="后端当前版本号") + + class NoticeOut(OutBase): if_need_show: bool = Field(..., description="是否需要显示公告") data: Dict[str, str] = Field( diff --git a/app/services/update.py b/app/services/update.py index e92e092..6526c37 100644 --- a/app/services/update.py +++ b/app/services/update.py @@ -22,6 +22,7 @@ import re import time import json +import asyncio import zipfile import requests import subprocess @@ -209,7 +210,7 @@ class _UpdateHandler: f"连接失败: {download_url}, 状态码: {response.status_code}, 剩余重试次数: {check_times}" ) - time.sleep(1) + await asyncio.sleep(1) continue logger.info(f"连接成功: {download_url}, 状态码: {response.status_code}") @@ -225,17 +226,6 @@ class _UpdateHandler: f.write(chunk) downloaded_size += len(chunk) - await Config.send_json( - WebSocketMessage( - id="Update", - type="Update", - data={ - "downloaded_size": downloaded_size, - "file_size": file_size, - "speed": speed, - }, - ).model_dump() - ) # 更新指定线程的下载进度, 每秒更新一次 if time.time() - last_time >= 1.0: @@ -247,6 +237,18 @@ class _UpdateHandler: last_download_size = downloaded_size last_time = time.time() + await Config.send_json( + WebSocketMessage( + id="Update", + type="Update", + data={ + "downloaded_size": downloaded_size, + "file_size": file_size, + "speed": speed, + }, + ).model_dump() + ) + (Path.cwd() / "download.temp").rename( Path.cwd() / f"UpdatePack_{self.remote_version}.zip" ) @@ -276,7 +278,7 @@ class _UpdateHandler: logger.info( f"下载出错: {download_url}, 错误信息: {e}, 剩余重试次数: {check_times}" ) - time.sleep(1) + await asyncio.sleep(1) else: diff --git a/app/task/skland.py b/app/task/skland.py index 2ecf8ea..18799c3 100644 --- a/app/task/skland.py +++ b/app/task/skland.py @@ -249,7 +249,7 @@ async def skland_sign_in(token) -> dict: f"{character.get("nickName")}({character.get("channelName")})" ) - time.sleep(3) + await asyncio.sleep(3) return result diff --git a/frontend/package.json b/frontend/package.json index f3c0b5e..23e073c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "frontend", "private": true, - "version": "0.0.1", + "version": "1.0.1", "main": "dist-electron/main.js", "scripts": { "dev": "concurrently \"vite\" \"yarn watch:main\" \"yarn electron-dev\"", diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts index 8ec29e6..8bdd32b 100644 --- a/frontend/src/api/index.ts +++ b/frontend/src/api/index.ts @@ -98,6 +98,8 @@ export type { TimeSetGetOut } from './models/TimeSetGetOut'; export type { TimeSetIndexItem } from './models/TimeSetIndexItem'; export type { TimeSetReorderIn } from './models/TimeSetReorderIn'; export type { TimeSetUpdateIn } from './models/TimeSetUpdateIn'; +export type { UpdateCheckIn } from './models/UpdateCheckIn'; +export type { UpdateCheckOut } from './models/UpdateCheckOut'; export type { UserConfig_Notify } from './models/UserConfig_Notify'; export type { UserCreateOut } from './models/UserCreateOut'; export type { UserDeleteIn } from './models/UserDeleteIn'; @@ -109,5 +111,6 @@ export type { UserReorderIn } from './models/UserReorderIn'; export type { UserSetIn } from './models/UserSetIn'; export type { UserUpdateIn } from './models/UserUpdateIn'; export type { ValidationError } from './models/ValidationError'; +export type { VersionOut } from './models/VersionOut'; export { Service } from './services/Service'; diff --git a/frontend/src/api/models/HistoryIndexItem.ts b/frontend/src/api/models/HistoryIndexItem.ts index 5181e54..84fbf6b 100644 --- a/frontend/src/api/models/HistoryIndexItem.ts +++ b/frontend/src/api/models/HistoryIndexItem.ts @@ -3,26 +3,25 @@ /* tslint:disable */ /* eslint-disable */ export type HistoryIndexItem = { - /** - * 日期 - */ - date: string; - /** - * 状态 - */ - status: HistoryIndexItem.status; - /** - * 对应JSON文件 - */ - jsonFile: string; -}; -export namespace HistoryIndexItem { - /** - * 状态 - */ - export enum status { - DONE = '完成', - ERROR = '异常', - } + /** + * 日期 + */ + date: string + /** + * 状态 + */ + status: HistoryIndexItem.status + /** + * 对应JSON文件 + */ + jsonFile: string +} +export namespace HistoryIndexItem { + /** + * 状态 + */ + export enum status { + DONE = '完成', + ERROR = '异常', + } } - diff --git a/frontend/src/api/models/UpdateCheckIn.ts b/frontend/src/api/models/UpdateCheckIn.ts new file mode 100644 index 0000000..50588e2 --- /dev/null +++ b/frontend/src/api/models/UpdateCheckIn.ts @@ -0,0 +1,11 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type UpdateCheckIn = { + /** + * 当前前端版本号 + */ + current_version: string; +}; + diff --git a/frontend/src/api/models/UpdateCheckOut.ts b/frontend/src/api/models/UpdateCheckOut.ts new file mode 100644 index 0000000..52264d3 --- /dev/null +++ b/frontend/src/api/models/UpdateCheckOut.ts @@ -0,0 +1,31 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type UpdateCheckOut = { + /** + * 状态码 + */ + code?: number; + /** + * 操作状态 + */ + status?: string; + /** + * 操作消息 + */ + message?: string; + /** + * 是否需要更新前端 + */ + if_need_update: boolean; + /** + * 最新前端版本号 + */ + latest_version: string; + /** + * 版本更新信息字典 + */ + update_info: Record>; +}; + diff --git a/frontend/src/api/models/VersionOut.ts b/frontend/src/api/models/VersionOut.ts new file mode 100644 index 0000000..ff9c990 --- /dev/null +++ b/frontend/src/api/models/VersionOut.ts @@ -0,0 +1,35 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export type VersionOut = { + /** + * 状态码 + */ + code?: number; + /** + * 操作状态 + */ + status?: string; + /** + * 操作消息 + */ + message?: string; + /** + * 后端代码是否为最新 + */ + if_latest: boolean; + /** + * 后端代码当前哈希值 + */ + current_hash: string; + /** + * 后端代码当前时间戳 + */ + current_time: string; + /** + * 后端当前版本号 + */ + current_version: string; +}; + diff --git a/frontend/src/api/services/Service.ts b/frontend/src/api/services/Service.ts index 5cd28bf..44d7598 100644 --- a/frontend/src/api/services/Service.ts +++ b/frontend/src/api/services/Service.ts @@ -53,6 +53,8 @@ import type { TimeSetGetIn } from '../models/TimeSetGetIn'; import type { TimeSetGetOut } from '../models/TimeSetGetOut'; import type { TimeSetReorderIn } from '../models/TimeSetReorderIn'; import type { TimeSetUpdateIn } from '../models/TimeSetUpdateIn'; +import type { UpdateCheckIn } from '../models/UpdateCheckIn'; +import type { UpdateCheckOut } from '../models/UpdateCheckOut'; import type { UserCreateOut } from '../models/UserCreateOut'; import type { UserDeleteIn } from '../models/UserDeleteIn'; import type { UserGetIn } from '../models/UserGetIn'; @@ -61,10 +63,22 @@ import type { UserInBase } from '../models/UserInBase'; import type { UserReorderIn } from '../models/UserReorderIn'; import type { UserSetIn } from '../models/UserSetIn'; import type { UserUpdateIn } from '../models/UserUpdateIn'; +import type { VersionOut } from '../models/VersionOut'; import type { CancelablePromise } from '../core/CancelablePromise'; import { OpenAPI } from '../core/OpenAPI'; import { request as __request } from '../core/request'; export class Service { + /** + * 获取后端git版本信息 + * @returns VersionOut Successful Response + * @throws ApiError + */ + public static getGitVersionApiInfoVersionPost(): CancelablePromise { + return __request(OpenAPI, { + method: 'POST', + url: '/api/info/version', + }); + } /** * 获取关卡号下拉框信息 * @param requestBody @@ -956,4 +970,45 @@ export class Service { }, }); } + /** + * 检查更新 + * @param requestBody + * @returns UpdateCheckOut Successful Response + * @throws ApiError + */ + public static checkUpdateApiUpdateCheckPost( + requestBody: UpdateCheckIn, + ): CancelablePromise { + return __request(OpenAPI, { + method: 'POST', + url: '/api/update/check', + body: requestBody, + mediaType: 'application/json', + errors: { + 422: `Validation Error`, + }, + }); + } + /** + * 下载更新 + * @returns OutBase Successful Response + * @throws ApiError + */ + public static downloadUpdateApiUpdateDownloadPost(): CancelablePromise { + return __request(OpenAPI, { + method: 'POST', + url: '/api/update/download', + }); + } + /** + * 安装更新 + * @returns OutBase Successful Response + * @throws ApiError + */ + public static installUpdateApiUpdateInstallPost(): CancelablePromise { + return __request(OpenAPI, { + method: 'POST', + url: '/api/update/install', + }); + } } diff --git a/frontend/src/components/TitleBar.vue b/frontend/src/components/TitleBar.vue index 05e0af8..ef20330 100644 --- a/frontend/src/components/TitleBar.vue +++ b/frontend/src/components/TitleBar.vue @@ -7,6 +7,12 @@ AUTO-MAS + + v{{ version }} + + 检测到更新 {{ updateInfo.latest_version }} 请尽快更新 + + @@ -16,26 +22,18 @@
- - -
@@ -47,10 +45,47 @@ import { ref, onMounted } from 'vue' import { MinusOutlined, BorderOutlined, CopyOutlined, CloseOutlined } from '@ant-design/icons-vue' import { useTheme } from '@/composables/useTheme' +import { Service } from '@/api' +import type { UpdateCheckOut } from '@/api' const { isDark } = useTheme() const isMaximized = ref(false) +// 使用 import.meta.env 或直接定义版本号,确保打包后可用 +const version = import.meta.env.VITE_APP_VERSION || '获取版本失败!' +const updateInfo = ref(null) + +// 获取是否有更新 +const getAppVersion = async () => { + try { + const ver = await Service.checkUpdateApiUpdateCheckPost({ + current_version: version, + }) + updateInfo.value = ver + return ver || '获取版本失败!' + } catch (error) { + console.error('Failed to get app version:', error) + return '获取版本失败!' + } +} + +// 生成更新提示的详细信息 +const getUpdateTooltip = () => { + if (!updateInfo.value?.update_info) return '' + + const updateDetails = [] + for (const [category, items] of Object.entries(updateInfo.value.update_info)) { + if (items && items.length > 0) { + updateDetails.push(`${category}:`) + items.forEach(item => { + updateDetails.push(`• ${item}`) + }) + updateDetails.push('') + } + } + return updateDetails.join('\n') +} + const minimizeWindow = async () => { try { await window.electronAPI?.windowMinimize() @@ -62,7 +97,7 @@ const minimizeWindow = async () => { const toggleMaximize = async () => { try { await window.electronAPI?.windowMaximize() - isMaximized.value = await window.electronAPI?.windowIsMaximized() || false + isMaximized.value = (await window.electronAPI?.windowIsMaximized()) || false } catch (error) { console.error('Failed to toggle maximize:', error) } @@ -78,10 +113,11 @@ const closeWindow = async () => { onMounted(async () => { try { - isMaximized.value = await window.electronAPI?.windowIsMaximized() || false + isMaximized.value = (await window.electronAPI?.windowIsMaximized()) || false } catch (error) { console.error('Failed to get window state:', error) } + await getAppVersion() }) @@ -124,11 +160,11 @@ onMounted(async () => { left: 55px; /* 调整:更贴近图标 */ top: 50%; transform: translate(-50%, -50%); - width: 200px; /* 缩小尺寸以适配 32px 高度 */ + width: 200px; /* 缩小尺寸以适配 32px 高度 */ height: 100px; pointer-events: none; border-radius: 50%; - background: radial-gradient(circle at 50% 50%, var(--ant-color-primary) 0%, rgba(0,0,0,0) 70%); + background: radial-gradient(circle at 50% 50%, var(--ant-color-primary) 0%, rgba(0, 0, 0, 0) 70%); filter: blur(24px); /* 降低模糊避免越界过多 */ opacity: 0.4; z-index: 0; @@ -153,10 +189,23 @@ onMounted(async () => { z-index: 1; } +.version-text { + font-size: 13px; + font-weight: 400; + opacity: 0.8; + position: relative; + z-index: 1; + margin-left: 4px; +} + .title-bar-dark .title-text { color: #fff; } +.title-bar-dark .version-text { + color: #ffffff; +} + .title-bar-center { flex: 1; height: 100%; @@ -218,4 +267,79 @@ onMounted(async () => { .title-bar-dark .maximize-button:hover { background: rgba(255, 255, 255, 0.15); } - \ No newline at end of file + +.update-hint { + font-weight: 500; + margin-left: 4px; + cursor: help; + background: linear-gradient(45deg, #ff0000, #ff7f00, #ffff00, #00ff00, #8b00ff, #ff0000); + background-size: 400% 400%; + -webkit-background-clip: text; + background-clip: text; + -webkit-text-fill-color: transparent; + animation: + rainbow-flow 3s ease-in-out infinite, + glow-pulse 2s ease-in-out infinite; + position: relative; +} + +.update-hint::before { + content: ''; + position: absolute; + top: -2px; + left: -2px; + right: -2px; + bottom: -2px; + background: linear-gradient(45deg, #ff0000, #ff7f00, #ffff00, #00ff00, #8b00ff, #ff0000); + background-size: 400% 400%; + border-radius: 4px; + z-index: -1; + opacity: 0.3; + filter: blur(8px); + animation: rainbow-flow 3s ease-in-out infinite; +} + +.title-bar-dark .update-hint::before { + opacity: 0.5; + filter: blur(10px); +} + +@keyframes rainbow-flow { + 0% { + background-position: 0% 50%; + } + 50% { + background-position: 100% 50%; + } + 100% { + background-position: 0% 50%; + } +} + +@keyframes glow-pulse { + 0% { + filter: brightness(1) saturate(1); + transform: scale(1); + } + 50% { + filter: brightness(1.2) saturate(1.3); + transform: scale(1.02); + } + 100% { + filter: brightness(1) saturate(1); + transform: scale(1); + } +} + +@keyframes pulse { + 0% { + opacity: 1; + } + 50% { + opacity: 0.7; + } + 100% { + opacity: 1; + } +} + diff --git a/frontend/src/types/electron.ts b/frontend/src/types/electron.ts index 11f134f..571c95c 100644 --- a/frontend/src/types/electron.ts +++ b/frontend/src/types/electron.ts @@ -5,9 +5,18 @@ export interface ElectronAPI { selectFolder: () => Promise selectFile: (filters?: Array<{ name: string; extensions: string[] }>) => Promise + // 窗口控制 + windowMinimize: () => Promise + windowMaximize: () => Promise + windowClose: () => Promise + windowIsMaximized: () => Promise + // 管理员权限检查 checkAdmin: () => Promise + // 重启为管理员 + restartAsAdmin: () => Promise + // 环境检查 checkEnvironment: () => Promise<{ pythonExists: boolean @@ -53,7 +62,6 @@ export interface ElectronAPI { callback: (progress: { progress: number; status: string; message: string }) => void ) => void removeDownloadProgressListener: () => void - restartAsAdmin: () => Promise } declare global { diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 0611bc6..aedce55 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -2,6 +2,9 @@ import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import path from 'path' +// 读取package.json中的版本号 +const packageJson = require('./package.json') + // https://vite.dev/config/ export default defineConfig({ plugins: [vue()], @@ -12,4 +15,8 @@ export default defineConfig({ '@': path.resolve(__dirname, './src'), }, }, + define: { + // 在编译时将版本号注入到环境变量中 + 'import.meta.env.VITE_APP_VERSION': JSON.stringify(packageJson.version) + } }) diff --git a/pyproject.toml b/pyproject.toml index 80986e6..aa8773e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,6 +12,7 @@ dependencies = [ "websockets==15.0.1", "aiofiles==24.1.0", "aiohttp==3.12.15", + "gitpython==3.1.45", "plyer==2.1.0", "psutil==7.0.0", "jinja2==3.1.6", diff --git a/requirements.txt b/requirements.txt index 19e4b88..6bb246b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,6 +5,7 @@ uvicorn==0.35.0 websockets==15.0.1 aiofiles==24.1.0 aiohttp==3.12.15 +gitpython==3.1.45 plyer==2.1.0 psutil==7.0.0 jinja2==3.1.6 diff --git a/updater.py b/updater.py deleted file mode 100644 index 1904ea9..0000000 --- a/updater.py +++ /dev/null @@ -1,379 +0,0 @@ -# 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 . - -# Contact: DLmaster_361@163.com - - -import re -import sys -import time -import json -import psutil -import base64 -import zipfile -import requests -import argparse -import truststore -import subprocess -import win32crypt - -from packaging import version -from pathlib import Path -from typing import List, Dict - -current_dir = Path(__file__).resolve().parent -if str(current_dir) not in sys.path: - sys.path.insert(0, str(current_dir)) - - -MIRROR_ERROR_INFO = { - 1001: "获取版本信息的URL参数不正确", - 7001: "填入的 CDK 已过期", - 7002: "填入的 CDK 错误", - 7003: "填入的 CDK 今日下载次数已达上限", - 7004: "填入的 CDK 类型和待下载的资源不匹配", - 7005: "填入的 CDK 已被封禁", - 8001: "对应架构和系统下的资源不存在", - 8002: "错误的系统参数", - 8003: "错误的架构参数", - 8004: "错误的更新通道参数", - 1: "未知错误类型", -} - - -def dpapi_decrypt(note: str, entropy: None | bytes = None) -> str: - """ - 使用Windows DPAPI解密数据 - - :param note: 数据密文 - :type note: str - :param entropy: 随机熵 - :type entropy: bytes - :return: 解密后的明文 - :rtype: str - """ - - if note == "": - return "" - - decrypted = win32crypt.CryptUnprotectData( - base64.b64decode(note), entropy, None, None, 0 - ) - return decrypted[1].decode("utf-8") - - -def kill_process(path: Path) -> None: - """ - 根据路径中止进程 - - :param path: 进程路径 - """ - - print(f"开始中止进程: {path}") - - for pid in search_pids(path): - killprocess = subprocess.Popen( - f"taskkill /F /T /PID {pid}", - shell=True, - creationflags=subprocess.CREATE_NO_WINDOW, - ) - killprocess.wait() - - print(f"进程已中止: {path}") - - -def search_pids(path: Path) -> list: - """ - 根据路径查找进程PID - - :param path: 进程路径 - :return: 匹配的进程PID列表 - """ - - print(f"开始查找进程 PID: {path}") - - pids = [] - for proc in psutil.process_iter(["pid", "exe"]): - try: - if proc.info["exe"] and proc.info["exe"].lower() == str(path).lower(): - pids.append(proc.info["pid"]) - except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess): - # 进程可能在此期间已结束或无法访问, 忽略这些异常 - pass - return pids - - -truststore.inject_into_ssl() -parser = argparse.ArgumentParser( - prog="AUTO-MAS更新器", description="为AUTO-MAS前端提供更新服务" -) -parser.add_argument( - "--version", "-v", type=str, required=False, default=None, help="前端程序版本号" -) -args = parser.parse_args() - -if (Path.cwd() / "config/Config.json").exists(): - config = json.loads( - (Path.cwd() / "config/Config.json").read_text(encoding="utf-8") - ).get( - "Update", - { - "MirrorChyanCDK": "", - "ProxyAddress": "", - "Source": "GitHub", - "UpdateType": "stable", - }, - ) -else: - config = { - "MirrorChyanCDK": "", - "ProxyAddress": "", - "Source": "GitHub", - "UpdateType": "stable", - } - -if ( - config.get("Source", "GitHub") == "MirrorChyan" - and dpapi_decrypt(config.get("MirrorChyanCDK", "")) == "" -): - print("使用 MirrorChyan源但未填写 MirrorChyanCDK, 转用 GitHub 源") - config["Source"] = "GitHub" - config["MirrorChyanCDK"] = "" - -print(f"当前配置: {config}") - - -download_source = config.get("Source", "GitHub") -proxies = { - "http": config.get("ProxyAddress", ""), - "https": config.get("ProxyAddress", ""), -} - -if args.version: - current_version = args.version -else: - current_version = "v0.0.0" - -print(f"当前版本: {current_version}") - - -response = requests.get( - f"https://mirrorchyan.com/api/resources/AUTO_MAA/latest?user_agent=AutoMaaGui¤t_version={current_version}&cdk={dpapi_decrypt(config.get('MirrorChyanCDK', ''))}&channel={config.get('UpdateType', 'stable')}", - timeout=10, - proxies=proxies, -) -if response.status_code == 200: - version_info = response.json() -else: - try: - result = response.json() - - if result["code"] != 0: - if result["code"] in MIRROR_ERROR_INFO: - print(f"获取版本信息时出错: {MIRROR_ERROR_INFO[result['code']]}") - else: - print( - "获取版本信息时出错: 意料之外的错误, 请及时联系项目组以获取来自 Mirror 酱的技术支持" - ) - print(f" {result['msg']}") - sys.exit(1) - except Exception: - print(f"获取版本信息时出错: {response.text}") - - sys.exit(1) - - -remote_version = version_info["data"]["version_name"] - -if version.parse(remote_version) > version.parse(current_version): - - # 版本更新信息 - print(f"发现新版本: {remote_version}, 当前版本: {current_version}") - - version_info_json: Dict[str, Dict[str, List[str]]] = json.loads( - re.sub( - r"^$", - r"\1", - version_info["data"]["release_note"].splitlines()[0], - ) - ) - - update_version_info = {} - for v_i in [ - info - for ver, info in version_info_json.items() - if version.parse(ver) > version.parse(current_version) - ]: - - for key, value in v_i.items(): - if key not in update_version_info: - update_version_info[key] = [] - update_version_info[key] += value - - for key, value in update_version_info.items(): - print(f"{key}: ") - for v in value: - print(f" - {v}") - - if download_source == "GitHub": - - download_url = f"https://github.com/DLmaster361/AUTO_MAA/releases/download/{remote_version}/AUTO_MAA_{remote_version}.zip" - - elif download_source == "MirrorChyan": - if "url" in version_info["data"]: - with requests.get( - version_info["data"]["url"], - allow_redirects=True, - timeout=10, - stream=True, - proxies=proxies, - ) as response: - if response.status_code == 200: - download_url = response.url - else: - print(f"MirrorChyan 未返回下载链接, 使用自建下载站") - download_url = f"https://download.auto-mas.top/d/AUTO_MAA/AUTO_MAA_{remote_version}.zip" - - elif download_source == "AutoSite": - download_url = ( - f"https://download.auto-mas.top/d/AUTO_MAA/AUTO_MAA_{remote_version}.zip" - ) - - else: - print(f"未知的下载源: {download_source}, 请检查配置文件") - sys.exit(1) - - print(f"开始下载: {download_url}") - - # 清理可能存在的临时文件 - if (Path.cwd() / "download.temp").exists(): - (Path.cwd() / "download.temp").unlink() - - check_times = 3 - while check_times != 0: - - try: - - start_time = time.time() - - response = requests.get( - download_url, timeout=10, stream=True, proxies=proxies - ) - - if response.status_code not in [200, 206]: - - if check_times != -1: - check_times -= 1 - - print( - f"连接失败: {download_url}, 状态码: {response.status_code}, 剩余重试次数: {check_times}", - ) - - time.sleep(1) - continue - - print(f"连接成功: {download_url}, 状态码: {response.status_code}") - - file_size = int(response.headers.get("content-length", 0)) - downloaded_size = 0 - last_download_size = 0 - last_time = time.time() - with (Path.cwd() / "download.temp").open(mode="wb") as f: - - for chunk in response.iter_content(chunk_size=8192): - - f.write(chunk) - downloaded_size += len(chunk) - - # 更新指定线程的下载进度, 每秒更新一次 - if time.time() - last_time >= 1.0: - speed = ( - (downloaded_size - last_download_size) - / (time.time() - last_time) - / 1024 - ) - last_download_size = downloaded_size - last_time = time.time() - - if speed >= 1024: - print( - f"正在下载: AUTO-MAS 已下载: {downloaded_size / 1048576:.2f}/{file_size / 1048576:.2f} MB ({downloaded_size / file_size * 100:.2f}%) 下载速度: {speed / 1024:.2f} MB/s", - ) - else: - print( - f"正在下载: AUTO-MAS 已下载: {downloaded_size / 1048576:.2f}/{file_size / 1048576:.2f} MB ({downloaded_size / file_size * 100:.2f}%) 下载速度: {speed:.2f} KB/s", - ) - - print( - f"下载完成: {download_url}, 实际下载大小: {downloaded_size} 字节, 耗时: {time.time() - start_time:.2f} 秒", - ) - - break - - except Exception as e: - - if check_times != -1: - check_times -= 1 - - print( - f"下载出错: {download_url}, 错误信息: {e}, 剩余重试次数: {check_times}", - ) - time.sleep(1) - - else: - - if (Path.cwd() / "download.temp").exists(): - (Path.cwd() / "download.temp").unlink() - print(f"下载失败: {download_url}") - sys.exit(1) - - print(f"开始解压: {Path.cwd() / 'download.temp'} 到 {Path.cwd()}") - - while True: - - try: - with zipfile.ZipFile(Path.cwd() / "download.temp", "r") as zip_ref: - zip_ref.extractall(Path.cwd()) - print(f"解压完成: {Path.cwd() / 'download.temp'} 到 {Path.cwd()}") - break - except PermissionError: - print(f"解压出错: AUTO_MAA正在运行, 正在尝试将其关闭") - kill_process(Path.cwd() / "AUTO_MAA.exe") - time.sleep(1) - - print("正在删除临时文件") - if (Path.cwd() / "changes.json").exists(): - (Path.cwd() / "changes.json").unlink() - if (Path.cwd() / "download.temp").exists(): - (Path.cwd() / "download.temp").unlink() - - print("正在启动AUTO_MAA") - subprocess.Popen( - [Path.cwd() / "AUTO_MAA.exe"], - creationflags=subprocess.CREATE_NEW_PROCESS_GROUP - | subprocess.DETACHED_PROCESS - | subprocess.CREATE_NO_WINDOW, - ) - - print("更新完成") - sys.exit(0) - -else: - - print(f"当前版本为最新版本: {current_version}") - sys.exit(0)