diff --git a/app/core/config.py b/app/core/config.py index fc12c83..f71d213 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -116,6 +116,10 @@ class GlobalConfig(ConfigBase): "Update", "MirrorChyanCDK", "", EncryptValidator() ) + Data_UID = ConfigItem("Data", "UID", str(uuid.uuid4()), UUIDValidator()) + Data_LastStatisticsUpload = ConfigItem( + "Data", "LastStatisticsUpload", "2000-01-01 00:00:00" + ) Data_LastStageUpdated = ConfigItem( "Data", "LastStageUpdated", "2000-01-01 00:00:00" ) @@ -571,7 +575,7 @@ TYPE_BOOK = {"MaaConfig": "MAA", "GeneralConfig": "通用"} class AppConfig(GlobalConfig): - VERSION = "5.0.0.1" + VERSION = [5, 0, 0, 1] def __init__(self) -> None: super().__init__(if_save_multi_config=False) @@ -579,7 +583,7 @@ class AppConfig(GlobalConfig): logger.info("") logger.info("===================================") logger.info("AUTO_MAA 后端应用程序") - logger.info(f"版本号: v{self.VERSION}") + logger.info(f"版本号: {self.version()}") logger.info(f"工作目录: {Path.cwd()}") logger.info("===================================") @@ -605,6 +609,16 @@ class AppConfig(GlobalConfig): truststore.inject_into_ssl() + def version(self) -> str: + """获取版本号字符串""" + + if self.VERSION[3] == 0: + return f"v{'.'.join(str(_) for _ in self.VERSION[0:3])}" + else: + return ( + f"v{'.'.join(str(_) for _ in self.VERSION[0:3])}-beta.{self.VERSION[3]}" + ) + async def init_config(self) -> None: """初始化配置管理""" diff --git a/app/core/timer.py b/app/core/timer.py index 55ad9a6..3995f8c 100644 --- a/app/core/timer.py +++ b/app/core/timer.py @@ -22,7 +22,7 @@ import asyncio import keyboard from datetime import datetime -from app.services import System +from app.services import Matomo, System from app.utils import get_logger from .config import Config @@ -42,6 +42,33 @@ class _MainTimer: await asyncio.sleep(1) + async def hour_task(self): + """每小时定期任务""" + + logger.info("每小时定期任务启动") + + while True: + + if ( + datetime.strptime( + Config.get("Data", "LastStatisticsUpload"), "%Y-%m-%d %H:%M:%S" + ).date() + != datetime.now().date() + ): + await Matomo.send_event( + "App", + "Version", + Config.version(), + 1 if "beta" in Config.version() else 0, + ) + await Config.set( + "Data", + "LastStatisticsUpload", + datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + ) + + await asyncio.sleep(3600) + async def set_silence(self): """静默模式通过模拟老板键来隐藏模拟器窗口""" diff --git a/app/models/ConfigBase.py b/app/models/ConfigBase.py index 2367372..bf10aa5 100644 --- a/app/models/ConfigBase.py +++ b/app/models/ConfigBase.py @@ -97,6 +97,20 @@ class UidValidator(ConfigValidator): return value if self.validate(value) else None +class UUIDValidator(ConfigValidator): + """UUID验证器""" + + def validate(self, value: Any) -> bool: + try: + uuid.UUID(value) + return True + except (TypeError, ValueError): + return False + + def correct(self, value: Any) -> Any: + return value if self.validate(value) else str(uuid.uuid4()) + + class EncryptValidator(ConfigValidator): """加数据验证器""" diff --git a/app/services/__init__.py b/app/services/__init__.py index bab395d..c55fcd6 100644 --- a/app/services/__init__.py +++ b/app/services/__init__.py @@ -22,6 +22,7 @@ __version__ = "5.0.0" __author__ = "DLmaster361 " __license__ = "GPL-3.0 license" +from .matomo import Matomo from .notification import Notify from .system import System diff --git a/app/services/matomo.py b/app/services/matomo.py new file mode 100644 index 0000000..701f5b1 --- /dev/null +++ b/app/services/matomo.py @@ -0,0 +1,125 @@ +# 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 asyncio +import aiohttp +import json +import uuid +import psutil +import platform +import time +from typing import Dict, Any, Optional + +from app.core import Config +from app.utils.logger import get_logger + +logger = get_logger("信息上报") + + +class _MatomoHandler: + """Matomo统计上报服务""" + + base_url = "https://statistics.auto-mas.top/matomo.php" + site_id = "3" + + def __init__(self): + + self.session = None + + async def _get_session(self): + """获取HTTP会话""" + + if self.session is None or self.session.closed: + timeout = aiohttp.ClientTimeout(total=10) + self.session = aiohttp.ClientSession(timeout=timeout) + return self.session + + async def close(self): + """关闭HTTP会话""" + if self.session and not self.session.closed: + await self.session.close() + + def _build_base_params(self, custom_vars: Optional[Dict[str, Any]] = None): + """构建基础参数""" + params = { + "idsite": self.site_id, + "rec": "1", + "action_name": "AUTO-MAS后端", + "_id": Config.get("Data", "UID")[:16], + "uid": Config.get("Data", "UID"), + "rand": str(uuid.uuid4().int)[:10], + "apiv": "1", + "h": time.strftime("%H"), + "m": time.strftime("%M"), + "s": time.strftime("%S"), + "ua": f"AUTO-MAS/{Config.version()} ({platform.system()} {platform.release()})", + } + + # 添加自定义变量 + if custom_vars is not None: + cvar = {} + for i, (key, value) in enumerate(custom_vars.items(), 1): + if i <= 5: + cvar[str(i)] = [str(key), str(value)] + if cvar: + params["_cvar"] = json.dumps(cvar) + + return params + + async def send_event( + self, + category: str, + action: str, + name: Optional[str] = None, + value: Optional[float] = None, + custom_vars: Optional[Dict[str, Any]] = None, + ): + """发送事件数据到Matomo + + Args: + category: 事件类别,如 "Script", "Config", "User" + action: 事件动作,如 "Execute", "Update", "Login" + name: 事件名称,如具体的脚本名称 + value: 事件值,如执行时长、文件大小等数值 + custom_vars: 自定义变量字典 + """ + try: + session = await self._get_session() + if session is None: + return + + params = self._build_base_params(custom_vars) + params.update({"e_c": category, "e_a": action, "e_n": name, "e_v": value}) + params = {k: v for k, v in params.items() if v is not None} + + async with session.get(self.base_url, params=params) as response: + if response.status == 200: + logger.debug(f"Matomo事件上报成功: {category}/{action}") + else: + logger.warning(f"Matomo事件上报失败: {response.status}") + + except asyncio.TimeoutError: + logger.warning("Matomo事件上报超时") + except Exception as e: + logger.error(f"Matomo事件上报错误: {e}") + + +Matomo = _MatomoHandler() diff --git a/main.py b/main.py index d52b73d..7836824 100644 --- a/main.py +++ b/main.py @@ -81,19 +81,26 @@ def main(): await Config.init_config() await Config.get_stage(if_start=True) await Config.clean_old_history() - main_timer = asyncio.create_task(MainTimer.second_task()) + second_timer = asyncio.create_task(MainTimer.second_task()) + hour_timer = asyncio.create_task(MainTimer.hour_task()) await System.set_Sleep() await System.set_SelfStart() yield await TaskManager.stop_task("ALL") - main_timer.cancel() + second_timer.cancel() + hour_timer.cancel() try: - await main_timer + await second_timer + await hour_timer except asyncio.CancelledError: logger.info("主业务定时器已关闭") + from app.services import Matomo + + await Matomo.close() + logger.info("AUTO_MAA 后端程序关闭") from fastapi.middleware.cors import CORSMiddleware diff --git a/pyproject.toml b/pyproject.toml index 9e08ffc..80986e6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,12 +3,15 @@ name = "AUTO_MAA" version = "4.0.0.1" description = "AUTO_MAA~" readme = "README.md" -requires-python = ">=3.13" +requires-python = ">=3.12" dependencies = [ "loguru==0.7.3", "fastapi==0.116.1", "pydantic==2.11.7", "uvicorn==0.35.0", + "websockets==15.0.1", + "aiofiles==24.1.0", + "aiohttp==3.12.15", "plyer==2.1.0", "psutil==7.0.0", "jinja2==3.1.6", diff --git a/requirements.txt b/requirements.txt index e987640..19e4b88 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,6 +4,7 @@ pydantic==2.11.7 uvicorn==0.35.0 websockets==15.0.1 aiofiles==24.1.0 +aiohttp==3.12.15 plyer==2.1.0 psutil==7.0.0 jinja2==3.1.6