diff --git a/app/__init__.py b/app/__init__.py
new file mode 100644
index 0000000..68967d1
--- /dev/null
+++ b/app/__init__.py
@@ -0,0 +1,29 @@
+# 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
+
+
+__version__ = "5.0.0"
+__author__ = "DLmaster361 "
+__license__ = "GPL-3.0 license"
+
+from .api import app
+from .core import Config, logger
+
+__all__ = ["app", "Config", "logger"]
diff --git a/app/api/__init__.py b/app/api/__init__.py
new file mode 100644
index 0000000..035a4d7
--- /dev/null
+++ b/app/api/__init__.py
@@ -0,0 +1,27 @@
+# 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
+
+__version__ = "5.0.0"
+__author__ = "DLmaster361 "
+__license__ = "GPL-3.0 license"
+
+from .api import app
+
+__all__ = ["app"]
diff --git a/app/core/__init__.py b/app/core/__init__.py
new file mode 100644
index 0000000..a87134e
--- /dev/null
+++ b/app/core/__init__.py
@@ -0,0 +1,28 @@
+# 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
+
+__version__ = "5.0.0"
+__author__ = "DLmaster361 "
+__license__ = "GPL-3.0 license"
+
+from .config import Config
+from .logger import logger
+
+__all__ = ["Config", "logger"]
diff --git a/app/core/config.py b/app/core/config.py
new file mode 100644
index 0000000..03bb02c
--- /dev/null
+++ b/app/core/config.py
@@ -0,0 +1,1653 @@
+# 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 argparse
+import sqlite3
+import json
+import sys
+import shutil
+import re
+import base64
+import calendar
+from datetime import datetime, timedelta, date
+from collections import defaultdict
+from pathlib import Path
+
+
+from typing import Union, Dict, List
+
+from .logger import logger
+from app.models.ConfigBase import *
+
+
+class GlobalConfig(ConfigBase):
+ """全局配置"""
+
+ Function_HomeImageMode = ConfigItem(
+ "Function",
+ "HomeImageMode",
+ "默认",
+ OptionsValidator(["默认", "自定义", "主题图像"]),
+ )
+ Function_HistoryRetentionTime = ConfigItem(
+ "Function",
+ "HistoryRetentionTime",
+ 0,
+ OptionsValidator([7, 15, 30, 60, 90, 180, 365, 0]),
+ )
+ Function_IfAllowSleep = ConfigItem(
+ "Function", "IfAllowSleep", False, BoolValidator()
+ )
+ Function_IfSilence = ConfigItem("Function", "IfSilence", False, BoolValidator())
+ Function_BossKey = ConfigItem("Function", "BossKey", "")
+ Function_UnattendedMode = ConfigItem(
+ "Function", "UnattendedMode", False, BoolValidator()
+ )
+ Function_IfAgreeBilibili = ConfigItem(
+ "Function", "IfAgreeBilibili", False, BoolValidator()
+ )
+ Function_IfSkipMumuSplashAds = ConfigItem(
+ "Function", "IfSkipMumuSplashAds", False, BoolValidator()
+ )
+
+ Voice_Enabled = ConfigItem("Voice", "Enabled", False, BoolValidator())
+ Voice_Type = ConfigItem(
+ "Voice", "Type", "simple", OptionsValidator(["simple", "noisy"])
+ )
+
+ Start_IfSelfStart = ConfigItem("Start", "IfSelfStart", False, BoolValidator())
+ Start_IfMinimizeDirectly = ConfigItem(
+ "Start", "IfMinimizeDirectly", False, BoolValidator()
+ )
+
+ UI_IfShowTray = ConfigItem("UI", "IfShowTray", False, BoolValidator())
+ UI_IfToTray = ConfigItem("UI", "IfToTray", False, BoolValidator())
+ UI_Size = ConfigItem("UI", "size", "1200x700")
+ UI_Location = ConfigItem("UI", "location", "100x100")
+ UI_Maximized = ConfigItem("UI", "maximized", False, BoolValidator())
+
+ Notify_SendTaskResultTime = ConfigItem(
+ "Notify",
+ "SendTaskResultTime",
+ "不推送",
+ OptionsValidator(["不推送", "任何时刻", "仅失败时"]),
+ )
+ Notify_IfSendStatistic = ConfigItem(
+ "Notify", "IfSendStatistic", False, BoolValidator()
+ )
+ Notify_IfSendSixStar = ConfigItem("Notify", "IfSendSixStar", False, BoolValidator())
+ Notify_IfPushPlyer = ConfigItem("Notify", "IfPushPlyer", False, BoolValidator())
+ Notify_IfSendMail = ConfigItem("Notify", "IfSendMail", False, BoolValidator())
+ Notify_SMTPServerAddress = ConfigItem("Notify", "SMTPServerAddress", "")
+ Notify_AuthorizationCode = ConfigItem("Notify", "AuthorizationCode", "")
+ Notify_FromAddress = ConfigItem("Notify", "FromAddress", "")
+ Notify_ToAddress = ConfigItem("Notify", "ToAddress", "")
+ Notify_IfServerChan = ConfigItem("Notify", "IfServerChan", False, BoolValidator())
+ Notify_ServerChanKey = ConfigItem("Notify", "ServerChanKey", "")
+ Notify_ServerChanChannel = ConfigItem("Notify", "ServerChanChannel", "")
+ Notify_ServerChanTag = ConfigItem("Notify", "ServerChanTag", "")
+ Notify_IfCompanyWebHookBot = ConfigItem(
+ "Notify", "IfCompanyWebHookBot", False, BoolValidator()
+ )
+ Notify_CompanyWebHookBotUrl = ConfigItem("Notify", "CompanyWebHookBotUrl", "")
+
+ Notify_IfAutoUpdate = ConfigItem("Update", "IfAutoUpdate", False, BoolValidator())
+ Notify_UpdateType = ConfigItem(
+ "Update", "UpdateType", "stable", OptionsValidator(["stable", "beta"])
+ )
+ Notify_ThreadNumb = ConfigItem("Update", "ThreadNumb", 8, RangeValidator(1, 32))
+ Notify_ProxyAddress = ConfigItem("Update", "ProxyAddress", "")
+ Notify_ProxyUrlList = ConfigItem("Update", "ProxyUrlList", [])
+ Notify_MirrorChyanCDK = ConfigItem("Update", "MirrorChyanCDK", "")
+
+
+class QueueItem(ConfigBase):
+ """队列项配置"""
+
+ def __init__(self) -> None:
+ super().__init__()
+
+ self.Info_ScriptId = ConfigItem("Info", "ScriptId", "")
+
+
+class TimeSet(ConfigBase):
+ """时间设置配置"""
+
+ def __init__(self) -> None:
+ super().__init__()
+
+ self.Info_Enabled = ConfigItem("Info", "Enabled", False, BoolValidator())
+ self.Info_Time = ConfigItem("Info", "Set", "00:00")
+
+
+class QueueConfig(ConfigBase):
+ """队列配置"""
+
+ def __init__(self) -> None:
+ super().__init__()
+
+ self.Info_Name = ConfigItem("Info", "Name", "")
+ self.Info_TimeEnabled = ConfigItem(
+ "Info", "TimeEnabled", False, BoolValidator()
+ )
+ self.Info_StartUpEnabled = ConfigItem(
+ "Info", "StartUpEnabled", False, BoolValidator()
+ )
+ self.Info_AfterAccomplish = ConfigItem(
+ "Info",
+ "AfterAccomplish",
+ "NoAction",
+ OptionsValidator(
+ [
+ "NoAction",
+ "KillSelf",
+ "Sleep",
+ "Hibernate",
+ "Shutdown",
+ "ShutdownForce",
+ ]
+ ),
+ )
+
+ self.Data_LastProxyTime = ConfigItem(
+ "Data", "LastProxyTime", "2000-01-01 00:00:00"
+ )
+ self.Data_LastProxyHistory = ConfigItem(
+ "Data", "LastProxyHistory", "暂无历史运行记录"
+ )
+
+ self.TimeSet = MultipleConfig([TimeSet])
+ self.QueueItem = MultipleConfig([QueueItem])
+
+
+class MaaUserConfig(ConfigBase):
+ """MAA用户配置"""
+
+ def __init__(self) -> None:
+ super().__init__()
+
+ self.Info_Name = ConfigItem("Info", "Name", "新用户")
+ self.Info_Id = ConfigItem("Info", "Id", "")
+ self.Info_Mode = ConfigItem(
+ "Info", "Mode", "简洁", OptionsValidator(["简洁", "详细"])
+ )
+ self.Info_StageMode = ConfigItem("Info", "StageMode", "固定")
+ self.Info_Server = ConfigItem(
+ "Info",
+ "Server",
+ "Official",
+ OptionsValidator(
+ ["Official", "Bilibili", "YoStarEN", "YoStarJP", "YoStarKR", "txwy"]
+ ),
+ )
+ self.Info_Status = ConfigItem("Info", "Status", True, BoolValidator())
+ self.Info_RemainedDay = ConfigItem(
+ "Info", "RemainedDay", -1, RangeValidator(-1, 1024)
+ )
+ self.Info_Annihilation = ConfigItem(
+ "Info",
+ "Annihilation",
+ "Annihilation",
+ OptionsValidator(
+ [
+ "Close",
+ "Annihilation",
+ "Chernobog@Annihilation",
+ "LungmenOutskirts@Annihilation",
+ "LungmenDowntown@Annihilation",
+ ]
+ ),
+ )
+ self.Info_Routine = ConfigItem("Info", "Routine", True, BoolValidator())
+ self.Info_InfrastMode = ConfigItem(
+ "Info",
+ "InfrastMode",
+ "Normal",
+ OptionsValidator(["Normal", "Rotation", "Custom"]),
+ )
+ self.Info_Password = ConfigItem("Info", "Password", "")
+ self.Info_Notes = ConfigItem("Info", "Notes", "无")
+ self.Info_MedicineNumb = ConfigItem(
+ "Info", "MedicineNumb", 0, RangeValidator(0, 1024)
+ )
+ self.Info_SeriesNumb = ConfigItem(
+ "Info",
+ "SeriesNumb",
+ "0",
+ OptionsValidator(["0", "6", "5", "4", "3", "2", "1", "-1"]),
+ )
+ self.Info_Stage = ConfigItem("Info", "Stage", "-")
+ self.Info_Stage_1 = ConfigItem("Info", "Stage_1", "-")
+ self.Info_Stage_2 = ConfigItem("Info", "Stage_2", "-")
+ self.Info_Stage_3 = ConfigItem("Info", "Stage_3", "-")
+ self.Info_Stage_Remain = ConfigItem("Info", "Stage_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)
+ )
+ self.Data_IfPassCheck = ConfigItem("Data", "IfPassCheck", True, BoolValidator())
+ self.Data_CustomInfrastPlanIndex = ConfigItem(
+ "Data", "CustomInfrastPlanIndex", "0"
+ )
+
+ self.Task_IfWakeUp = ConfigItem("Task", "IfWakeUp", True, BoolValidator())
+ self.Task_IfRecruiting = ConfigItem(
+ "Task", "IfRecruiting", True, BoolValidator()
+ )
+ self.Task_IfBase = ConfigItem("Task", "IfBase", True, BoolValidator())
+ self.Task_IfCombat = ConfigItem("Task", "IfCombat", True, BoolValidator())
+ self.Task_IfMall = ConfigItem("Task", "IfMall", True, BoolValidator())
+ self.Task_IfMission = ConfigItem("Task", "IfMission", True, BoolValidator())
+ self.Task_IfAutoRoguelike = ConfigItem(
+ "Task", "IfAutoRoguelike", False, BoolValidator()
+ )
+ self.Task_IfReclamation = ConfigItem(
+ "Task", "IfReclamation", False, BoolValidator()
+ )
+
+ self.Notify_Enabled = ConfigItem("Notify", "Enabled", False, BoolValidator())
+ self.Notify_IfSendStatistic = ConfigItem(
+ "Notify", "IfSendStatistic", False, BoolValidator()
+ )
+ self.Notify_IfSendSixStar = ConfigItem(
+ "Notify", "IfSendSixStar", False, BoolValidator()
+ )
+ self.Notify_IfSendMail = ConfigItem(
+ "Notify", "IfSendMail", False, BoolValidator()
+ )
+ self.Notify_ToAddress = ConfigItem("Notify", "ToAddress", "")
+ self.Notify_IfServerChan = ConfigItem(
+ "Notify", "IfServerChan", False, BoolValidator()
+ )
+ self.Notify_ServerChanKey = ConfigItem("Notify", "ServerChanKey", "")
+ self.Notify_ServerChanChannel = ConfigItem("Notify", "ServerChanChannel", "")
+ self.Notify_ServerChanTag = ConfigItem("Notify", "ServerChanTag", "")
+ self.Notify_IfCompanyWebHookBot = ConfigItem(
+ "Notify", "IfCompanyWebHookBot", False, BoolValidator()
+ )
+ self.Notify_CompanyWebHookBotUrl = ConfigItem(
+ "Notify", "CompanyWebHookBotUrl", ""
+ )
+
+ # def get_plan_info(self) -> Dict[str, Union[str, int]]:
+ # """获取当前的计划下信息"""
+
+ # if self.get(self.Info_StageMode) == "固定":
+ # return {
+ # "MedicineNumb": self.get(self.Info_MedicineNumb),
+ # "SeriesNumb": self.get(self.Info_SeriesNumb),
+ # "Stage": self.get(self.Info_Stage),
+ # "Stage_1": self.get(self.Info_Stage_1),
+ # "Stage_2": self.get(self.Info_Stage_2),
+ # "Stage_3": self.get(self.Info_Stage_3),
+ # "Stage_Remain": self.get(self.Info_Stage_Remain),
+ # }
+ # elif "计划" in self.get(self.Info_StageMode):
+ # plan = Config.plan_dict[self.get(self.Info_StageMode)]["Config"]
+ # return {
+ # "MedicineNumb": plan.get(plan.get_current_info("MedicineNumb")),
+ # "SeriesNumb": plan.get(plan.get_current_info("SeriesNumb")),
+ # "Stage": plan.get(plan.get_current_info("Stage")),
+ # "Stage_1": plan.get(plan.get_current_info("Stage_1")),
+ # "Stage_2": plan.get(plan.get_current_info("Stage_2")),
+ # "Stage_3": plan.get(plan.get_current_info("Stage_3")),
+ # "Stage_Remain": plan.get(plan.get_current_info("Stage_Remain")),
+ # }
+
+
+class MaaConfig(ConfigBase):
+ """MAA配置"""
+
+ def __init__(self) -> None:
+ super().__init__()
+
+ self.Info_Name = ConfigItem("Info", "Name", "")
+ self.Info_Path = ConfigItem("Info", "Path", ".", FolderValidator())
+
+ self.Run_TaskTransitionMethod = ConfigItem(
+ "Run",
+ "TaskTransitionMethod",
+ "ExitEmulator",
+ OptionsValidator(["NoAction", "ExitGame", "ExitEmulator"]),
+ )
+ self.Run_ProxyTimesLimit = ConfigItem(
+ "Run", "ProxyTimesLimit", 0, RangeValidator(0, 1024)
+ )
+ self.Run_ADBSearchRange = ConfigItem(
+ "Run", "ADBSearchRange", 0, RangeValidator(0, 3)
+ )
+ self.Run_RunTimesLimit = ConfigItem(
+ "Run", "RunTimesLimit", 3, RangeValidator(1, 1024)
+ )
+ self.Run_AnnihilationTimeLimit = ConfigItem(
+ "Run", "AnnihilationTimeLimit", 40, RangeValidator(1, 1024)
+ )
+ self.Run_RoutineTimeLimit = ConfigItem(
+ "Run", "RoutineTimeLimit", 10, RangeValidator(1, 1024)
+ )
+ self.Run_AnnihilationWeeklyLimit = ConfigItem(
+ "Run", "AnnihilationWeeklyLimit", True, BoolValidator()
+ )
+
+ self.UserData = MultipleConfig([MaaUserConfig])
+
+ # def get_name(self) -> str:
+ # return self.get(self.MaaSet_Name)
+
+
+class MaaPlanConfig(ConfigBase):
+ """MAA计划表配置"""
+
+ def __init__(self) -> None:
+ super().__init__()
+
+ self.Info_Name = ConfigItem("Info", "Name", "")
+ self.Info_Mode = ConfigItem(
+ "Info", "Mode", "ALL", OptionsValidator(["ALL", "Weekly"])
+ )
+
+ self.config_item_dict: dict[str, Dict[str, ConfigItem]] = {}
+
+ for group in [
+ "ALL",
+ "Monday",
+ "Tuesday",
+ "Wednesday",
+ "Thursday",
+ "Friday",
+ "Saturday",
+ "Sunday",
+ ]:
+ self.config_item_dict[group] = {}
+
+ self.config_item_dict[group]["MedicineNumb"] = ConfigItem(
+ group, "MedicineNumb", 0, RangeValidator(0, 1024)
+ )
+ self.config_item_dict[group]["SeriesNumb"] = ConfigItem(
+ group,
+ "SeriesNumb",
+ "0",
+ OptionsValidator(["0", "6", "5", "4", "3", "2", "1", "-1"]),
+ )
+ self.config_item_dict[group]["Stage"] = ConfigItem(group, "Stage", "-")
+ self.config_item_dict[group]["Stage_1"] = ConfigItem(group, "Stage_1", "-")
+ self.config_item_dict[group]["Stage_2"] = ConfigItem(group, "Stage_2", "-")
+ self.config_item_dict[group]["Stage_3"] = ConfigItem(group, "Stage_3", "-")
+ self.config_item_dict[group]["Stage_Remain"] = ConfigItem(
+ group, "Stage_Remain", "-"
+ )
+
+ for name in [
+ "MedicineNumb",
+ "SeriesNumb",
+ "Stage",
+ "Stage_1",
+ "Stage_2",
+ "Stage_3",
+ "Stage_Remain",
+ ]:
+ setattr(self, f"{group}_{name}", self.config_item_dict[group][name])
+
+ # def get_current_info(self, name: str) -> ConfigItem:
+ # """获取当前的计划表配置项"""
+
+ # if self.get(self.Info_Mode) == "ALL":
+
+ # return self.config_item_dict["ALL"][name]
+
+ # elif self.get(self.Info_Mode) == "Weekly":
+
+ # dt = datetime.now()
+ # if dt.time() < datetime.min.time().replace(hour=4):
+ # dt = dt - timedelta(days=1)
+ # today = dt.strftime("%A")
+
+ # if today in self.config_item_dict:
+ # return self.config_item_dict[today][name]
+ # else:
+ # return self.config_item_dict["ALL"][name]
+
+
+class GeneralUserConfig(ConfigBase):
+ """通用子配置"""
+
+ def __init__(self) -> None:
+ super().__init__()
+
+ self.Info_Name = ConfigItem("Info", "Name", "新配置")
+ self.Info_Status = ConfigItem("Info", "Status", True, BoolValidator())
+ self.Info_RemainedDay = ConfigItem(
+ "Info", "RemainedDay", -1, RangeValidator(-1, 1024)
+ )
+ self.Info_IfScriptBeforeTask = ConfigItem(
+ "Info", "IfScriptBeforeTask", False, BoolValidator()
+ )
+ self.Info_ScriptBeforeTask = ConfigItem(
+ "Info", "ScriptBeforeTask", "", FileValidator()
+ )
+ self.Info_IfScriptAfterTask = ConfigItem(
+ "Info", "IfScriptAfterTask", False, BoolValidator()
+ )
+ self.Info_ScriptAfterTask = ConfigItem(
+ "Info", "ScriptAfterTask", "", FileValidator()
+ )
+ self.Info_Notes = ConfigItem("Info", "Notes", "无")
+
+ self.Data_LastProxyDate = ConfigItem("Data", "LastProxyDate", "2000-01-01")
+ self.Data_ProxyTimes = ConfigItem(
+ "Data", "ProxyTimes", 0, RangeValidator(0, 1024)
+ )
+
+ self.Notify_Enabled = ConfigItem("Notify", "Enabled", False, BoolValidator())
+ self.Notify_IfSendStatistic = ConfigItem(
+ "Notify", "IfSendStatistic", False, BoolValidator()
+ )
+ self.Notify_IfSendMail = ConfigItem(
+ "Notify", "IfSendMail", False, BoolValidator()
+ )
+ self.Notify_ToAddress = ConfigItem("Notify", "ToAddress", "")
+ self.Notify_IfServerChan = ConfigItem(
+ "Notify", "IfServerChan", False, BoolValidator()
+ )
+ self.Notify_ServerChanKey = ConfigItem("Notify", "ServerChanKey", "")
+ self.Notify_ServerChanChannel = ConfigItem("Notify", "ServerChanChannel", "")
+ self.Notify_ServerChanTag = ConfigItem("Notify", "ServerChanTag", "")
+ self.Notify_IfCompanyWebHookBot = ConfigItem(
+ "Notify", "IfCompanyWebHookBot", False, BoolValidator()
+ )
+ self.Notify_CompanyWebHookBotUrl = ConfigItem(
+ "Notify", "CompanyWebHookBotUrl", ""
+ )
+
+
+class GeneralConfig(ConfigBase):
+ """通用配置"""
+
+ def __init__(self) -> None:
+ super().__init__()
+
+ self.Info_Name = ConfigItem("Info", "Name", "")
+ self.Info_RootPath = ConfigItem("Info", "RootPath", ".", FileValidator())
+
+ self.Script_ScriptPath = ConfigItem(
+ "Script", "ScriptPath", ".", FileValidator()
+ )
+ self.Script_Arguments = ConfigItem("Script", "Arguments", "")
+ self.Script_IfTrackProcess = ConfigItem(
+ "Script", "IfTrackProcess", False, BoolValidator()
+ )
+ self.Script_ConfigPath = ConfigItem(
+ "Script", "ConfigPath", ".", FileValidator()
+ )
+ self.Script_ConfigPathMode = ConfigItem(
+ "Script",
+ "ConfigPathMode",
+ "所有文件 (*)",
+ OptionsValidator(["所有文件 (*)", "文件夹"]),
+ )
+ self.Script_UpdateConfigMode = ConfigItem(
+ "Script",
+ "UpdateConfigMode",
+ "Never",
+ OptionsValidator(["Never", "Success", "Failure", "Always"]),
+ )
+ self.Script_LogPath = ConfigItem("Script", "LogPath", ".", FileValidator())
+ self.Script_LogPathFormat = ConfigItem("Script", "LogPathFormat", "%Y-%m-%d")
+ self.Script_LogTimeStart = ConfigItem(
+ "Script", "LogTimeStart", 1, RangeValidator(1, 1024)
+ )
+ self.Script_LogTimeEnd = ConfigItem(
+ "Script", "LogTimeEnd", 1, RangeValidator(1, 1024)
+ )
+ self.Script_LogTimeFormat = ConfigItem(
+ "Script", "LogTimeFormat", "%Y-%m-%d %H:%M:%S"
+ )
+ self.Script_SuccessLog = ConfigItem("Script", "SuccessLog", "")
+ self.Script_ErrorLog = ConfigItem("Script", "ErrorLog", "")
+
+ self.Game_Enabled = ConfigItem("Game", "Enabled", False, BoolValidator())
+ self.Game_Style = ConfigItem(
+ "Game", "Style", "Emulator", OptionsValidator(["Emulator", "Client"])
+ )
+ self.Game_Path = ConfigItem("Game", "Path", ".", FileValidator())
+ self.Game_Arguments = ConfigItem("Game", "Arguments", "")
+ self.Game_WaitTime = ConfigItem("Game", "WaitTime", 0, RangeValidator(0, 1024))
+ self.Game_IfForceClose = ConfigItem(
+ "Game", "IfForceClose", False, BoolValidator()
+ )
+
+ self.Run_ProxyTimesLimit = ConfigItem(
+ "Run", "ProxyTimesLimit", 0, RangeValidator(0, 1024)
+ )
+ self.Run_RunTimesLimit = ConfigItem(
+ "Run", "RunTimesLimit", 3, RangeValidator(1, 1024)
+ )
+ self.Run_RunTimeLimit = ConfigItem(
+ "Run", "RunTimeLimit", 10, RangeValidator(1, 1024)
+ )
+
+ self.UserData = MultipleConfig([GeneralUserConfig])
+
+ # def get_name(self) -> str:
+ # return self.get(self.Script_Name)
+
+
+class AppConfig(GlobalConfig):
+
+ VERSION = "4.5.0.1"
+
+ def __init__(self) -> None:
+ super().__init__()
+
+ # self.app_path = Path(sys.argv[0]).resolve().parent
+ # self.app_path_sys = Path(sys.argv[0]).resolve()
+
+ self.root_path = Path.cwd()
+
+ self.log_path = self.root_path / "debug/app.log"
+ # self.database_path = self.root_path / "data/data.db"
+ self.config_path = self.root_path / "config"
+ # self.key_path = self.root_path / "data/key"
+
+ # self.main_window = None
+ # self.PASSWORD = ""
+ # self.running_list = []
+ # self.silence_dict: Dict[Path, datetime] = {}
+ # self.info_bar_list = []
+ # self.stage_dict = {
+ # "ALL": {"value": [], "text": []},
+ # "Monday": {"value": [], "text": []},
+ # "Tuesday": {"value": [], "text": []},
+ # "Wednesday": {"value": [], "text": []},
+ # "Thursday": {"value": [], "text": []},
+ # "Friday": {"value": [], "text": []},
+ # "Saturday": {"value": [], "text": []},
+ # "Sunday": {"value": [], "text": []},
+ # }
+ # self.power_sign = "NoAction"
+ # self.if_ignore_silence = False
+ # self.if_database_opened = False
+
+ self.init_logger()
+
+ # 检查目录
+ self.log_path.parent.mkdir(parents=True, exist_ok=True)
+ self.config_path.mkdir(parents=True, exist_ok=True)
+
+ self.ScriptConfig = MultipleConfig([MaaConfig, GeneralConfig])
+ self.PlanConfig = MultipleConfig([MaaPlanConfig])
+ self.QueueConfig = MultipleConfig([QueueConfig])
+ self.connect(self.config_path / "Config.json")
+ self.ScriptConfig.connect(self.config_path / "ScriptConfig.json")
+ self.PlanConfig.connect(self.config_path / "PlanConfig.json")
+ self.QueueConfig.connect(self.config_path / "QueueConfig.json")
+
+ # self.check_data()
+ logger.info("程序初始化完成", module="配置管理")
+
+ def init_logger(self) -> None:
+ """初始化日志记录器"""
+
+ logger.add(
+ sink=self.log_path,
+ level="DEBUG",
+ format="{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <8} | {extra[module]} | {message}",
+ enqueue=True,
+ backtrace=True,
+ diagnose=True,
+ rotation="1 week",
+ retention="1 month",
+ compression="zip",
+ )
+ logger.add(
+ sink=sys.stderr,
+ level="DEBUG",
+ format="{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <8} | {extra[module]} | {message}",
+ enqueue=True,
+ backtrace=True,
+ diagnose=True,
+ colorize=True,
+ )
+
+ logger.info("", module="配置管理")
+ logger.info("===================================", module="配置管理")
+ logger.info("AUTO_MAA 后端", module="配置管理")
+ logger.info(f"版本号: v{self.VERSION}", module="配置管理")
+ logger.info(f"根目录: {self.root_path}", module="配置管理")
+ logger.info("===================================", module="配置管理")
+
+ # def check_data(self) -> None:
+ # """检查用户数据文件并处理数据文件版本更新"""
+
+ # # 生成主数据库
+ # if not self.database_path.exists():
+ # db = sqlite3.connect(self.database_path)
+ # cur = db.cursor()
+ # cur.execute("CREATE TABLE version(v text)")
+ # cur.execute("INSERT INTO version VALUES(?)", ("v1.8",))
+ # db.commit()
+ # cur.close()
+ # db.close()
+
+ # # 数据文件版本更新
+ # db = sqlite3.connect(self.database_path)
+ # cur = db.cursor()
+ # cur.execute("SELECT * FROM version WHERE True")
+ # version = cur.fetchall()
+
+ # if version[0][0] != "v1.8":
+ # logger.info("数据文件版本更新开始", module="配置管理")
+ # if_streaming = False
+ # # v1.4-->v1.5
+ # if version[0][0] == "v1.4" or if_streaming:
+ # logger.info("数据文件版本更新:v1.4-->v1.5", module="配置管理")
+ # if_streaming = True
+
+ # member_dict: Dict[str, Dict[str, Union[str, Path]]] = {}
+ # if (self.app_path / "config/MaaConfig").exists():
+ # for maa_dir in (self.app_path / "config/MaaConfig").iterdir():
+ # if maa_dir.is_dir():
+ # member_dict[maa_dir.name] = {
+ # "Type": "Maa",
+ # "Path": maa_dir,
+ # }
+
+ # member_dict = dict(
+ # sorted(member_dict.items(), key=lambda x: int(x[0][3:]))
+ # )
+
+ # for name, config in member_dict.items():
+ # if config["Type"] == "Maa":
+
+ # _db = sqlite3.connect(config["Path"] / "user_data.db")
+ # _cur = _db.cursor()
+ # _cur.execute("SELECT * FROM adminx WHERE True")
+ # data = _cur.fetchall()
+ # data = [list(row) for row in data]
+ # data = sorted(data, key=lambda x: (-len(x[15]), x[16]))
+ # _cur.close()
+ # _db.close()
+
+ # (config["Path"] / "user_data.db").unlink()
+
+ # (config["Path"] / f"UserData").mkdir(
+ # parents=True, exist_ok=True
+ # )
+
+ # for i in range(len(data)):
+
+ # info = {
+ # "Data": {
+ # "IfPassCheck": True,
+ # "LastAnnihilationDate": "2000-01-01",
+ # "LastProxyDate": data[i][5],
+ # "ProxyTimes": data[i][14],
+ # },
+ # "Info": {
+ # "Annihilation": bool(data[i][10] == "y"),
+ # "GameId": data[i][6],
+ # "GameIdMode": "固定",
+ # "GameId_1": data[i][7],
+ # "GameId_2": data[i][8],
+ # "Id": data[i][1],
+ # "Infrastructure": bool(data[i][11] == "y"),
+ # "MedicineNumb": 0,
+ # "Mode": (
+ # "简洁" if data[i][15] == "simple" else "详细"
+ # ),
+ # "Name": data[i][0],
+ # "Notes": data[i][13],
+ # "Password": base64.b64encode(data[i][12]).decode(
+ # "utf-8"
+ # ),
+ # "RemainedDay": data[i][3],
+ # "Routine": bool(data[i][9] == "y"),
+ # "Server": data[i][2],
+ # "Status": bool(data[i][4] == "y"),
+ # },
+ # }
+
+ # (config["Path"] / f"UserData/用户_{i + 1}").mkdir(
+ # parents=True, exist_ok=True
+ # )
+ # with (
+ # config["Path"] / f"UserData/用户_{i + 1}/config.json"
+ # ).open(mode="w", encoding="utf-8") as f:
+ # json.dump(info, f, ensure_ascii=False, indent=4)
+
+ # if (
+ # self.app_path
+ # / f"config/MaaConfig/{name}/{data[i][15]}/{data[i][16]}/annihilation/gui.json"
+ # ).exists():
+ # (
+ # config["Path"]
+ # / f"UserData/用户_{i + 1}/Annihilation"
+ # ).mkdir(parents=True, exist_ok=True)
+ # shutil.move(
+ # self.app_path
+ # / f"config/MaaConfig/{name}/{data[i][15]}/{data[i][16]}/annihilation/gui.json",
+ # config["Path"]
+ # / f"UserData/用户_{i + 1}/Annihilation/gui.json",
+ # )
+ # if (
+ # self.app_path
+ # / f"config/MaaConfig/{name}/{data[i][15]}/{data[i][16]}/routine/gui.json"
+ # ).exists():
+ # (
+ # config["Path"] / f"UserData/用户_{i + 1}/Routine"
+ # ).mkdir(parents=True, exist_ok=True)
+ # shutil.move(
+ # self.app_path
+ # / f"config/MaaConfig/{name}/{data[i][15]}/{data[i][16]}/routine/gui.json",
+ # config["Path"]
+ # / f"UserData/用户_{i + 1}/Routine/gui.json",
+ # )
+ # if (
+ # self.app_path
+ # / f"config/MaaConfig/{name}/{data[i][15]}/{data[i][16]}/infrastructure/infrastructure.json"
+ # ).exists():
+ # (
+ # config["Path"]
+ # / f"UserData/用户_{i + 1}/Infrastructure"
+ # ).mkdir(parents=True, exist_ok=True)
+ # shutil.move(
+ # self.app_path
+ # / f"config/MaaConfig/{name}/{data[i][15]}/{data[i][16]}/infrastructure/infrastructure.json",
+ # config["Path"]
+ # / f"UserData/用户_{i + 1}/Infrastructure/infrastructure.json",
+ # )
+
+ # if (config["Path"] / f"simple").exists():
+ # shutil.rmtree(config["Path"] / f"simple")
+ # if (config["Path"] / f"beta").exists():
+ # shutil.rmtree(config["Path"] / f"beta")
+
+ # cur.execute("DELETE FROM version WHERE v = ?", ("v1.4",))
+ # cur.execute("INSERT INTO version VALUES(?)", ("v1.5",))
+ # db.commit()
+ # # v1.5-->v1.6
+ # if version[0][0] == "v1.5" or if_streaming:
+ # logger.info("数据文件版本更新:v1.5-->v1.6", module="配置管理")
+ # if_streaming = True
+ # cur.execute("DELETE FROM version WHERE v = ?", ("v1.5",))
+ # cur.execute("INSERT INTO version VALUES(?)", ("v1.6",))
+ # db.commit()
+ # # 删除旧的注册表项
+ # import winreg
+
+ # key = winreg.OpenKey(
+ # winreg.HKEY_CURRENT_USER,
+ # r"Software\Microsoft\Windows\CurrentVersion\Run",
+ # 0,
+ # winreg.KEY_READ,
+ # )
+
+ # try:
+ # value, _ = winreg.QueryValueEx(key, "AUTO_MAA")
+ # winreg.CloseKey(key)
+ # key = winreg.OpenKey(
+ # winreg.HKEY_CURRENT_USER,
+ # r"Software\Microsoft\Windows\CurrentVersion\Run",
+ # winreg.KEY_SET_VALUE,
+ # winreg.KEY_ALL_ACCESS
+ # | winreg.KEY_WRITE
+ # | winreg.KEY_CREATE_SUB_KEY,
+ # )
+ # winreg.DeleteValue(key, "AUTO_MAA")
+ # winreg.CloseKey(key)
+ # except FileNotFoundError:
+ # pass
+ # # v1.6-->v1.7
+ # if version[0][0] == "v1.6" or if_streaming:
+ # logger.info("数据文件版本更新:v1.6-->v1.7", module="配置管理")
+ # if_streaming = True
+
+ # if (self.app_path / "config/MaaConfig").exists():
+
+ # for MaaConfig in (self.app_path / "config/MaaConfig").iterdir():
+ # if MaaConfig.is_dir():
+ # for user in (MaaConfig / "UserData").iterdir():
+ # if user.is_dir():
+ # if (user / "config.json").exists():
+ # with (user / "config.json").open(
+ # encoding="utf-8"
+ # ) as f:
+ # user_config = json.load(f)
+ # user_config["Info"]["Stage"] = user_config[
+ # "Info"
+ # ]["GameId"]
+ # user_config["Info"]["StageMode"] = user_config[
+ # "Info"
+ # ]["GameIdMode"]
+ # user_config["Info"]["Stage_1"] = user_config[
+ # "Info"
+ # ]["GameId_1"]
+ # user_config["Info"]["Stage_2"] = user_config[
+ # "Info"
+ # ]["GameId_2"]
+ # user_config["Info"]["Stage_Remain"] = (
+ # user_config["Info"]["GameId_Remain"]
+ # )
+ # with (user / "config.json").open(
+ # "w", encoding="utf-8"
+ # ) as f:
+ # json.dump(
+ # user_config,
+ # f,
+ # ensure_ascii=False,
+ # indent=4,
+ # )
+
+ # if (self.app_path / "config/MaaPlanConfig").exists():
+ # for MaaPlanConfig in (
+ # self.app_path / "config/MaaPlanConfig"
+ # ).iterdir():
+ # if (
+ # MaaPlanConfig.is_dir()
+ # and (MaaPlanConfig / "config.json").exists()
+ # ):
+ # with (MaaPlanConfig / "config.json").open(
+ # encoding="utf-8"
+ # ) as f:
+ # plan_config = json.load(f)
+
+ # for k in self.stage_dict.keys():
+ # plan_config[k]["Stage"] = plan_config[k]["GameId"]
+ # plan_config[k]["Stage_1"] = plan_config[k]["GameId_1"]
+ # plan_config[k]["Stage_2"] = plan_config[k]["GameId_2"]
+ # plan_config[k]["Stage_Remain"] = plan_config[k][
+ # "GameId_Remain"
+ # ]
+ # with (MaaPlanConfig / "config.json").open(
+ # "w", encoding="utf-8"
+ # ) as f:
+ # json.dump(plan_config, f, ensure_ascii=False, indent=4)
+
+ # cur.execute("DELETE FROM version WHERE v = ?", ("v1.6",))
+ # cur.execute("INSERT INTO version VALUES(?)", ("v1.7",))
+ # db.commit()
+ # # v1.7-->v1.8
+ # if version[0][0] == "v1.7" or if_streaming:
+ # logger.info("数据文件版本更新:v1.7-->v1.8", module="配置管理")
+ # if_streaming = True
+
+ # if (self.app_path / "config/QueueConfig").exists():
+ # for QueueConfig in (self.app_path / "config/QueueConfig").glob(
+ # "*.json"
+ # ):
+ # with QueueConfig.open(encoding="utf-8") as f:
+ # queue_config = json.load(f)
+
+ # queue_config["QueueSet"]["TimeEnabled"] = queue_config[
+ # "QueueSet"
+ # ]["Enabled"]
+
+ # for i in range(10):
+ # queue_config["Queue"][f"Script_{i}"] = queue_config[
+ # "Queue"
+ # ][f"Member_{i + 1}"]
+ # queue_config["Time"][f"Enabled_{i}"] = queue_config["Time"][
+ # f"TimeEnabled_{i}"
+ # ]
+ # queue_config["Time"][f"Set_{i}"] = queue_config["Time"][
+ # f"TimeSet_{i}"
+ # ]
+
+ # with QueueConfig.open("w", encoding="utf-8") as f:
+ # json.dump(queue_config, f, ensure_ascii=False, indent=4)
+
+ # cur.execute("DELETE FROM version WHERE v = ?", ("v1.7",))
+ # cur.execute("INSERT INTO version VALUES(?)", ("v1.8",))
+ # db.commit()
+
+ # cur.close()
+ # db.close()
+ # logger.success("数据文件版本更新完成", module="配置管理")
+
+ # def get_stage(self) -> None:
+ # """从MAA服务器更新活动关卡信息"""
+
+ # logger.info("开始获取活动关卡信息", module="配置管理")
+ # network = Network.add_task(
+ # mode="get",
+ # url="https://api.maa.plus/MaaAssistantArknights/api/gui/StageActivity.json",
+ # )
+ # network.loop.exec()
+ # network_result = Network.get_result(network)
+ # if network_result["status_code"] == 200:
+ # stage_infos: List[Dict[str, Union[str, Dict[str, Union[str, int]]]]] = (
+ # network_result["response_json"]["Official"]["sideStoryStage"]
+ # )
+ # else:
+ # logger.warning(
+ # f"无法从MAA服务器获取活动关卡信息:{network_result['error_message']}",
+ # module="配置管理",
+ # )
+ # stage_infos = []
+
+ # ss_stage_dict = {"value": [], "text": []}
+
+ # for stage_info in stage_infos:
+
+ # if (
+ # datetime.strptime(
+ # stage_info["Activity"]["UtcStartTime"], "%Y/%m/%d %H:%M:%S"
+ # )
+ # < datetime.now()
+ # < datetime.strptime(
+ # stage_info["Activity"]["UtcExpireTime"], "%Y/%m/%d %H:%M:%S"
+ # )
+ # ):
+ # ss_stage_dict["value"].append(stage_info["Value"])
+ # ss_stage_dict["text"].append(stage_info["Value"])
+
+ # # 生成每日关卡信息
+ # stage_daily_info = [
+ # {"value": "-", "text": "当前/上次", "days": [1, 2, 3, 4, 5, 6, 7]},
+ # {"value": "1-7", "text": "1-7", "days": [1, 2, 3, 4, 5, 6, 7]},
+ # {"value": "R8-11", "text": "R8-11", "days": [1, 2, 3, 4, 5, 6, 7]},
+ # {
+ # "value": "12-17-HARD",
+ # "text": "12-17-HARD",
+ # "days": [1, 2, 3, 4, 5, 6, 7],
+ # },
+ # {"value": "CE-6", "text": "龙门币-6/5", "days": [2, 4, 6, 7]},
+ # {"value": "AP-5", "text": "红票-5", "days": [1, 4, 6, 7]},
+ # {"value": "CA-5", "text": "技能-5", "days": [2, 3, 5, 7]},
+ # {"value": "LS-6", "text": "经验-6/5", "days": [1, 2, 3, 4, 5, 6, 7]},
+ # {"value": "SK-5", "text": "碳-5", "days": [1, 3, 5, 6]},
+ # {"value": "PR-A-1", "text": "奶/盾芯片", "days": [1, 4, 5, 7]},
+ # {"value": "PR-A-2", "text": "奶/盾芯片组", "days": [1, 4, 5, 7]},
+ # {"value": "PR-B-1", "text": "术/狙芯片", "days": [1, 2, 5, 6]},
+ # {"value": "PR-B-2", "text": "术/狙芯片组", "days": [1, 2, 5, 6]},
+ # {"value": "PR-C-1", "text": "先/辅芯片", "days": [3, 4, 6, 7]},
+ # {"value": "PR-C-2", "text": "先/辅芯片组", "days": [3, 4, 6, 7]},
+ # {"value": "PR-D-1", "text": "近/特芯片", "days": [2, 3, 6, 7]},
+ # {"value": "PR-D-2", "text": "近/特芯片组", "days": [2, 3, 6, 7]},
+ # ]
+
+ # for day in range(0, 8):
+
+ # today_stage_dict = {"value": [], "text": []}
+
+ # for stage_info in stage_daily_info:
+
+ # if day in stage_info["days"] or day == 0:
+ # today_stage_dict["value"].append(stage_info["value"])
+ # today_stage_dict["text"].append(stage_info["text"])
+
+ # self.stage_dict[calendar.day_name[day - 1] if day > 0 else "ALL"] = {
+ # "value": today_stage_dict["value"] + ss_stage_dict["value"],
+ # "text": today_stage_dict["text"] + ss_stage_dict["text"],
+ # }
+
+ # self.stage_refreshed.emit()
+
+ # logger.success("活动关卡信息更新完成", module="配置管理")
+
+ def server_date(self) -> date:
+ """
+ 获取当前的服务器日期
+
+ :return: 当前的服务器日期
+ :rtype: date
+ """
+
+ dt = datetime.now()
+ if dt.time() < datetime.min.time().replace(hour=4):
+ dt = dt - timedelta(days=1)
+ return dt.date()
+
+
+# def search_maa_user(self, name: str) -> None:
+# """
+# 更新指定 MAA 脚本实例的用户信息
+
+# :param name: 脚本实例名称
+# :type name: str
+# """
+
+# logger.info(f"开始搜索并读入 MAA 脚本实例 {name} 的用户信息", module="配置管理")
+
+# user_dict: Dict[str, Dict[str, Union[Path, MaaUserConfig]]] = {}
+# for user_dir in (Config.script_dict[name]["Path"] / "UserData").iterdir():
+# if user_dir.is_dir():
+
+# user_config = MaaUserConfig()
+# user_config.load(user_dir / "config.json", user_config)
+# user_config.save()
+
+# user_dict[user_dir.stem] = {"Path": user_dir, "Config": user_config}
+
+# self.script_dict[name]["UserData"] = dict(
+# sorted(user_dict.items(), key=lambda x: int(x[0][3:]))
+# )
+
+# logger.success(
+# f"MAA 脚本实例 {name} 的用户信息搜索完成,共找到 {len(user_dict)} 个用户",
+# module="配置管理",
+# )
+
+# def search_general_sub(self, name: str) -> None:
+# """
+# 更新指定通用脚本实例的子配置信息
+
+# :param name: 脚本实例名称
+# :type name: str
+# """
+
+# logger.info(
+# f"开始搜索并读入通用脚本实例 {name} 的子配置信息", module="配置管理"
+# )
+
+# user_dict: Dict[str, Dict[str, Union[Path, GeneralSubConfig]]] = {}
+# for sub_dir in (Config.script_dict[name]["Path"] / "SubData").iterdir():
+# if sub_dir.is_dir():
+
+# sub_config = GeneralSubConfig()
+# sub_config.load(sub_dir / "config.json", sub_config)
+# sub_config.save()
+
+# user_dict[sub_dir.stem] = {"Path": sub_dir, "Config": sub_config}
+
+# self.script_dict[name]["SubData"] = dict(
+# sorted(user_dict.items(), key=lambda x: int(x[0][3:]))
+# )
+
+# logger.success(
+# f"通用脚本实例 {name} 的子配置信息搜索完成,共找到 {len(user_dict)} 个子配置",
+# module="配置管理",
+# )
+
+# def search_plan(self) -> None:
+# """更新计划表配置信息"""
+
+# logger.info("开始搜索并读入计划表配置", module="配置管理")
+
+# self.plan_dict: Dict[str, Dict[str, Union[str, Path, MaaPlanConfig]]] = {}
+# if (self.app_path / "config/MaaPlanConfig").exists():
+# for maa_plan_dir in (self.app_path / "config/MaaPlanConfig").iterdir():
+# if maa_plan_dir.is_dir():
+
+# maa_plan_config = MaaPlanConfig()
+# maa_plan_config.load(maa_plan_dir / "config.json", maa_plan_config)
+# maa_plan_config.save()
+
+# self.plan_dict[maa_plan_dir.name] = {
+# "Type": "Maa",
+# "Path": maa_plan_dir,
+# "Config": maa_plan_config,
+# }
+
+# self.plan_dict = dict(
+# sorted(self.plan_dict.items(), key=lambda x: int(x[0][3:]))
+# )
+
+# logger.success(
+# f"计划表配置搜索完成,共找到 {len(self.plan_dict)} 个计划表",
+# module="配置管理",
+# )
+
+# def search_queue(self):
+# """更新调度队列实例配置信息"""
+
+# logger.info("开始搜索并读入调度队列配置", module="配置管理")
+
+# self.queue_dict: Dict[str, Dict[str, Union[Path, QueueConfig]]] = {}
+
+# if (self.app_path / "config/QueueConfig").exists():
+# for json_file in (self.app_path / "config/QueueConfig").glob("*.json"):
+
+# queue_config = QueueConfig()
+# queue_config.load(json_file, queue_config)
+# queue_config.save()
+
+# self.queue_dict[json_file.stem] = {
+# "Path": json_file,
+# "Config": queue_config,
+# }
+
+# self.queue_dict = dict(
+# sorted(self.queue_dict.items(), key=lambda x: int(x[0][5:]))
+# )
+
+# logger.success(
+# f"调度队列配置搜索完成,共找到 {len(self.queue_dict)} 个调度队列",
+# module="配置管理",
+# )
+
+# def change_queue(self, old: str, new: str) -> None:
+# """
+# 修改调度队列配置文件的队列参数
+
+# :param old: 旧脚本名
+# :param new: 新脚本名
+# """
+
+# logger.info(f"开始修改调度队列参数:{old} -> {new}", module="配置管理")
+
+# for queue in self.queue_dict.values():
+
+# for i in range(10):
+
+# if (
+# queue["Config"].get(
+# queue["Config"].config_item_dict["Queue"][f"Script_{i}"]
+# )
+# == old
+# ):
+# queue["Config"].set(
+# queue["Config"].config_item_dict["Queue"][f"Script_{i}"], new
+# )
+
+# logger.success(f"调度队列参数修改完成:{old} -> {new}", module="配置管理")
+
+# def change_plan(self, old: str, new: str) -> None:
+# """
+# 修改脚本管理所有下属用户的计划表配置参数
+
+# :param old: 旧计划表名
+# :param new: 新计划表名
+# """
+
+# logger.info(f"开始修改计划表参数:{old} -> {new}", module="配置管理")
+
+# for script in self.script_dict.values():
+
+# for user in script["UserData"].values():
+
+# if user["Config"].get(user["Config"].Info_StageMode) == old:
+# user["Config"].set(user["Config"].Info_StageMode, new)
+
+# logger.success(f"计划表参数修改完成:{old} -> {new}", module="配置管理")
+
+# def change_maa_user_info(
+# self, name: str, user_data: Dict[str, Dict[str, Union[str, Path, dict]]]
+# ) -> None:
+# """
+# 保存代理完成后发生改动的用户信息
+
+# :param name: 脚本实例名称
+# :type name: str
+# :param user_data: 用户信息字典,包含用户名称和对应的配置信息
+# :type user_data: Dict[str, Dict[str, Union[str, Path, dict]]]
+# """
+
+# logger.info(f"开始保存 MAA 脚本实例 {name} 的用户信息变动", module="配置管理")
+
+# for user, info in user_data.items():
+
+# user_config = self.script_dict[name]["UserData"][user]["Config"]
+
+# user_config.set(
+# user_config.Info_RemainedDay, info["Config"]["Info"]["RemainedDay"]
+# )
+# user_config.set(
+# user_config.Data_LastProxyDate, info["Config"]["Data"]["LastProxyDate"]
+# )
+# user_config.set(
+# 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"]
+# )
+# user_config.set(
+# user_config.Data_IfPassCheck, info["Config"]["Data"]["IfPassCheck"]
+# )
+# user_config.set(
+# user_config.Data_CustomInfrastPlanIndex,
+# info["Config"]["Data"]["CustomInfrastPlanIndex"],
+# )
+
+# self.sub_info_changed.emit()
+
+# logger.success(f"MAA 脚本实例 {name} 的用户信息变动保存完成", module="配置管理")
+
+# def change_general_sub_info(
+# self, name: str, sub_data: Dict[str, Dict[str, Union[str, Path, dict]]]
+# ) -> None:
+# """
+# 保存代理完成后发生改动的配置信息
+
+# :param name: 脚本实例名称
+# :type name: str
+# :param sub_data: 子配置信息字典,包含子配置名称和对应的配置信息
+# :type sub_data: Dict[str, Dict[str, Union[str, Path, dict]]]
+# """
+
+# logger.info(f"开始保存通用脚本实例 {name} 的子配置信息变动", module="配置管理")
+
+# for sub, info in sub_data.items():
+
+# sub_config = self.script_dict[name]["SubData"][sub]["Config"]
+
+# sub_config.set(
+# sub_config.Info_RemainedDay, info["Config"]["Info"]["RemainedDay"]
+# )
+# sub_config.set(
+# sub_config.Data_LastProxyDate, info["Config"]["Data"]["LastProxyDate"]
+# )
+# sub_config.set(
+# sub_config.Data_ProxyTimes, info["Config"]["Data"]["ProxyTimes"]
+# )
+
+# self.sub_info_changed.emit()
+
+# logger.success(
+# f"通用脚本实例 {name} 的子配置信息变动保存完成", module="配置管理"
+# )
+
+# def set_power_sign(self, sign: str) -> None:
+# """
+# 设置当前电源状态
+
+# :param sign: 电源状态标志
+# """
+
+# self.power_sign = sign
+# self.power_sign_changed.emit()
+
+# logger.info(f"电源状态已更改为: {sign}", module="配置管理")
+
+# def save_history(self, key: str, content: dict) -> None:
+# """
+# 保存历史记录
+
+# :param key: 调度队列的键
+# :type key: str
+# :param content: 包含时间和历史记录内容的字典
+# :type content: dict
+# """
+
+# if key in self.queue_dict:
+# logger.info(f"保存调度队列 {key} 的历史记录", module="配置管理")
+# self.queue_dict[key]["Config"].set(
+# self.queue_dict[key]["Config"].Data_LastProxyTime, content["Time"]
+# )
+# self.queue_dict[key]["Config"].set(
+# self.queue_dict[key]["Config"].Data_LastProxyHistory, content["History"]
+# )
+# logger.success(f"调度队列 {key} 的历史记录已保存", module="配置管理")
+# else:
+# logger.warning(f"保存历史记录时未找到调度队列: {key}")
+
+# def save_maa_log(self, log_path: Path, logs: list, maa_result: str) -> bool:
+# """
+# 保存MAA日志并生成对应统计数据
+
+# :param log_path: 日志文件保存路径
+# :type log_path: Path
+# :param logs: 日志内容列表
+# :type logs: list
+# :param maa_result: MAA 结果
+# :type maa_result: str
+# :return: 是否包含6★招募
+# :rtype: bool
+# """
+
+# logger.info(
+# f"开始处理 MAA 日志,日志长度: {len(logs)},日志标记:{maa_result}",
+# module="配置管理",
+# )
+
+# data: Dict[str, Union[str, Dict[str, Union[int, dict]]]] = {
+# "recruit_statistics": defaultdict(int),
+# "drop_statistics": defaultdict(dict),
+# "maa_result": maa_result,
+# }
+
+# if_six_star = False
+
+# # 公招统计(仅统计招募到的)
+# confirmed_recruit = False
+# current_star_level = None
+# i = 0
+# while i < len(logs):
+# if "公招识别结果:" in logs[i]:
+# current_star_level = None # 每次识别公招时清空之前的星级
+# i += 1
+# while i < len(logs) and "Tags" not in logs[i]: # 读取所有公招标签
+# i += 1
+
+# if i < len(logs) and "Tags" in logs[i]: # 识别星级
+# star_match = re.search(r"(\d+)\s*★ Tags", logs[i])
+# if star_match:
+# current_star_level = f"{star_match.group(1)}★"
+# if current_star_level == "6★":
+# if_six_star = True
+
+# if "已确认招募" in logs[i]: # 只有确认招募后才统计
+# confirmed_recruit = True
+
+# if confirmed_recruit and current_star_level:
+# data["recruit_statistics"][current_star_level] += 1
+# confirmed_recruit = False # 重置,等待下一次公招
+# current_star_level = None # 清空已处理的星级
+
+# i += 1
+
+# # 掉落统计
+# # 存储所有关卡的掉落统计
+# all_stage_drops = {}
+
+# # 查找所有Fight任务的开始和结束位置
+# fight_tasks = []
+# for i, line in enumerate(logs):
+# if "开始任务: Fight" in line or "开始任务: 刷理智" in line:
+# # 查找对应的任务结束位置
+# end_index = -1
+# for j in range(i + 1, len(logs)):
+# if "完成任务: Fight" in logs[j] or "完成任务: 刷理智" in logs[j]:
+# end_index = j
+# break
+# # 如果遇到新的Fight任务开始,则当前任务没有正常结束
+# if j < len(logs) and (
+# "开始任务: Fight" in logs[j] or "开始任务: 刷理智" in logs[j]
+# ):
+# break
+
+# # 如果找到了结束位置,记录这个任务的范围
+# if end_index != -1:
+# fight_tasks.append((i, end_index))
+
+# # 处理每个Fight任务
+# for start_idx, end_idx in fight_tasks:
+# # 提取当前任务的日志
+# task_logs = logs[start_idx : end_idx + 1]
+
+# # 查找任务中的最后一次掉落统计
+# last_drop_stats = {}
+# current_stage = None
+
+# for line in task_logs:
+# # 匹配掉落统计行,如"1-7 掉落统计:"
+# drop_match = re.search(r"([A-Za-z0-9\-]+) 掉落统计:", line)
+# if drop_match:
+# # 发现新的掉落统计,重置当前关卡的掉落数据
+# current_stage = drop_match.group(1)
+# last_drop_stats = {}
+# continue
+
+# # 如果已经找到了关卡,处理掉落物
+# if current_stage:
+# item_match: List[str] = re.findall(
+# r"^(?!\[)(\S+?)\s*:\s*([\d,]+)(?:\s*\(\+[\d,]+\))?",
+# line,
+# re.M,
+# )
+# for item, total in item_match:
+# # 解析数值时去掉逗号 (如 2,160 -> 2160)
+# total = int(total.replace(",", ""))
+
+# # 黑名单
+# if item not in [
+# "当前次数",
+# "理智",
+# "最快截图耗时",
+# "专精等级",
+# "剩余时间",
+# ]:
+# last_drop_stats[item] = total
+
+# # 如果任务中有掉落统计,更新总统计
+# if current_stage and last_drop_stats:
+# if current_stage not in all_stage_drops:
+# all_stage_drops[current_stage] = {}
+
+# # 累加掉落数据
+# for item, count in last_drop_stats.items():
+# all_stage_drops[current_stage].setdefault(item, 0)
+# all_stage_drops[current_stage][item] += count
+
+# # 将累加后的掉落数据保存到结果中
+# data["drop_statistics"] = all_stage_drops
+
+# # 保存日志
+# log_path.parent.mkdir(parents=True, exist_ok=True)
+# with log_path.open("w", encoding="utf-8") as f:
+# f.writelines(logs)
+# with log_path.with_suffix(".json").open("w", encoding="utf-8") as f:
+# json.dump(data, f, ensure_ascii=False, indent=4)
+
+# logger.success(f"MAA 日志统计完成,日志路径:{log_path}", module="配置管理")
+
+# return if_six_star
+
+# def save_general_log(self, log_path: Path, logs: list, general_result: str) -> None:
+# """
+# 保存通用日志并生成对应统计数据
+
+# :param log_path: 日志文件保存路径
+# :param logs: 日志内容列表
+# :param general_result: 待保存的日志结果信息
+# """
+
+# logger.info(
+# f"开始处理通用日志,日志长度: {len(logs)},日志标记:{general_result}",
+# module="配置管理",
+# )
+
+# data: Dict[str, str] = {"general_result": general_result}
+
+# # 保存日志
+# log_path.parent.mkdir(parents=True, exist_ok=True)
+# with log_path.with_suffix(".log").open("w", encoding="utf-8") as f:
+# f.writelines(logs)
+# with log_path.with_suffix(".json").open("w", encoding="utf-8") as f:
+# json.dump(data, f, ensure_ascii=False, indent=4)
+
+# logger.success(
+# f"通用日志统计完成,日志路径:{log_path.with_suffix('.log')}",
+# module="配置管理",
+# )
+
+# def merge_statistic_info(self, statistic_path_list: List[Path]) -> dict:
+# """
+# 合并指定数据统计信息文件
+
+# :param statistic_path_list: 需要合并的统计信息文件路径列表
+# :return: 合并后的统计信息字典
+# """
+
+# logger.info(
+# f"开始合并统计信息文件,共计 {len(statistic_path_list)} 个文件",
+# module="配置管理",
+# )
+
+# data = {"index": {}}
+
+# for json_file in statistic_path_list:
+
+# with json_file.open("r", encoding="utf-8") as f:
+# single_data: Dict[str, Union[str, Dict[str, Union[int, dict]]]] = (
+# json.load(f)
+# )
+
+# for key in single_data.keys():
+
+# if key not in data:
+# data[key] = {}
+
+# # 合并公招统计
+# if key == "recruit_statistics":
+
+# for star_level, count in single_data[key].items():
+# if star_level not in data[key]:
+# data[key][star_level] = 0
+# data[key][star_level] += count
+
+# # 合并掉落统计
+# elif key == "drop_statistics":
+
+# for stage, drops in single_data[key].items():
+# if stage not in data[key]:
+# data[key][stage] = {} # 初始化关卡
+
+# for item, count in drops.items():
+
+# if item not in data[key][stage]:
+# data[key][stage][item] = 0
+# data[key][stage][item] += count
+
+# # 录入运行结果
+# elif key in ["maa_result", "general_result"]:
+
+# actual_date = datetime.strptime(
+# f"{json_file.parent.parent.name} {json_file.stem}",
+# "%Y-%m-%d %H-%M-%S",
+# ) + timedelta(
+# days=(
+# 1
+# if datetime.strptime(json_file.stem, "%H-%M-%S").time()
+# < datetime.min.time().replace(hour=4)
+# else 0
+# )
+# )
+
+# if single_data[key] != "Success!":
+# if "error_info" not in data:
+# data["error_info"] = {}
+# data["error_info"][actual_date.strftime("%d日 %H:%M:%S")] = (
+# single_data[key]
+# )
+
+# data["index"][actual_date] = [
+# actual_date.strftime("%d日 %H:%M:%S"),
+# ("完成" if single_data[key] == "Success!" else "异常"),
+# json_file,
+# ]
+
+# data["index"] = [data["index"][_] for _ in sorted(data["index"])]
+
+# logger.success(
+# f"统计信息合并完成,共计 {len(data['index'])} 条记录", module="配置管理"
+# )
+
+# return {k: v for k, v in data.items() if v}
+
+# def search_history(
+# self, mode: str, start_date: datetime, end_date: datetime
+# ) -> dict:
+# """
+# 搜索指定范围内的历史记录
+
+# :param mode: 合并模式(按日合并、按周合并、按月合并)
+# :param start_date: 开始日期
+# :param end_date: 结束日期
+# :return: 搜索到的历史记录字典
+# """
+
+# logger.info(
+# f"开始搜索历史记录,合并模式:{mode},日期范围:{start_date} 至 {end_date}",
+# module="配置管理",
+# )
+
+# history_dict = {}
+
+# for date_folder in (Config.app_path / "history").iterdir():
+# if not date_folder.is_dir():
+# continue # 只处理日期文件夹
+
+# try:
+
+# date = datetime.strptime(date_folder.name, "%Y-%m-%d")
+
+# if not (start_date <= date <= end_date):
+# continue # 只统计在范围内的日期
+
+# if mode == "按日合并":
+# date_name = date.strftime("%Y年 %m月 %d日")
+# elif mode == "按周合并":
+# year, week, _ = date.isocalendar()
+# date_name = f"{year}年 第{week}周"
+# elif mode == "按月合并":
+# date_name = date.strftime("%Y年 %m月")
+
+# if date_name not in history_dict:
+# history_dict[date_name] = {}
+
+# for user_folder in date_folder.iterdir():
+# if not user_folder.is_dir():
+# continue # 只处理用户文件夹
+
+# if user_folder.stem not in history_dict[date_name]:
+# history_dict[date_name][user_folder.stem] = list(
+# user_folder.with_suffix("").glob("*.json")
+# )
+# else:
+# history_dict[date_name][user_folder.stem] += list(
+# user_folder.with_suffix("").glob("*.json")
+# )
+
+# except ValueError:
+# logger.warning(f"非日期格式的目录: {date_folder}")
+
+# logger.success(
+# f"历史记录搜索完成,共计 {len(history_dict)} 条记录", module="配置管理"
+# )
+
+# return {
+# k: v
+# for k, v in sorted(history_dict.items(), key=lambda x: x[0], reverse=True)
+# }
+
+# def clean_old_history(self):
+# """删除超过用户设定天数的历史记录文件(基于目录日期)"""
+
+# if self.get(self.function_HistoryRetentionTime) == 0:
+# logger.info("历史记录永久保留,跳过历史记录清理", module="配置管理")
+# return
+
+# logger.info("开始清理超过设定天数的历史记录", module="配置管理")
+
+# deleted_count = 0
+
+# for date_folder in (self.app_path / "history").iterdir():
+# if not date_folder.is_dir():
+# continue # 只处理日期文件夹
+
+# try:
+# # 只检查 `YYYY-MM-DD` 格式的文件夹
+# folder_date = datetime.strptime(date_folder.name, "%Y-%m-%d")
+# if datetime.now() - folder_date > timedelta(
+# days=self.get(self.function_HistoryRetentionTime)
+# ):
+# shutil.rmtree(date_folder, ignore_errors=True)
+# deleted_count += 1
+# logger.info(f"已删除超期日志目录: {date_folder}", module="配置管理")
+# except ValueError:
+# logger.warning(f"非日期格式的目录: {date_folder}", module="配置管理")
+
+# logger.success(f"清理完成: {deleted_count} 个日期目录", module="配置管理")
+
+Config = AppConfig()
diff --git a/app/core/logger.py b/app/core/logger.py
new file mode 100644
index 0000000..be168d4
--- /dev/null
+++ b/app/core/logger.py
@@ -0,0 +1,25 @@
+# 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
+
+
+from loguru import logger as _logger
+
+logger = _logger.patch(lambda record: record["extra"].setdefault("module", "未知模块"))
+logger.remove(0)
diff --git a/app/core/user_config.py b/app/core/user_config.py
deleted file mode 100644
index 4ec51cb..0000000
--- a/app/core/user_config.py
+++ /dev/null
@@ -1,358 +0,0 @@
-import json
-import secrets
-import string
-import asyncio
-import aiofiles
-from pathlib import Path
-from typing import Any, TypeVar, Generic, cast
-from pydantic import BaseModel
-
-from app.utils.logger import get_logger
-
-T = TypeVar('T', bound=BaseModel)
-
-class ConfigManager(Generic[T]):
- """
- 异步配置管理基类,支持自动保存和Pydantic数据验证
- 子类需定义具体的配置模型类型
- """
-
- def __init__(self, file_path: str, log_name: str, model_type: type[T]):
- """
- 初始化配置管理器
-
- Args:
- file_path: 配置文件路径
- log_name: 日志名称
- model_type: 配置项的Pydantic模型类型
- """
- self.file_path = Path(file_path)
- self.logger = get_logger(log_name)
- self.model_type = model_type
- self.data: dict[str, Any] = {
- "instance_order": [],
- "instances": {}
- }
- self._lock = asyncio.Lock()
- self._save_task: asyncio.Task|None = None
- self._pending_save = False
- self._load_task = asyncio.create_task(self._load_async())
-
- async def _load_async(self) -> None:
- """异步加载配置文件 - 带健壮的错误处理"""
- async with self._lock:
- try:
- # 检查文件是否存在
- if not self.file_path.exists():
- self.logger.info(f"配置文件 {self.file_path} 不存在,创建新配置")
- self.file_path.parent.mkdir(parents=True, exist_ok=True)
- # 初始化空配置
- self.data = {
- "instance_order": [],
- "instances": {}
- }
- return
-
- # 检查文件是否为空
- if self.file_path.stat().st_size == 0:
- self.logger.warning(f"配置文件 {self.file_path} 为空,初始化新配置")
- self.data = {
- "instance_order": [],
- "instances": {}
- }
- return
-
- # 读取并解析配置
- async with aiofiles.open(self.file_path, 'r', encoding='utf-8') as f:
- content = await f.read()
-
- # 尝试解析JSON
- try:
- raw_data = json.loads(content)
- except json.JSONDecodeError as e:
- self.logger.error(f"配置文件 {self.file_path} JSON解析失败: {e}")
- # 尝试备份损坏的配置文件
- await self._backup_corrupted_config()
- # 初始化空配置
- self.data = {
- "instance_order": [],
- "instances": {}
- }
- return
-
- # 验证并加载实例
- instance_order = raw_data.get("instance_order", [])
- instances_raw = raw_data.get("instances", {})
-
- instances = {}
- for uid, config_data in instances_raw.items():
- try:
- # 使用Pydantic验证配置数据
- instances[uid] = self.model_type(**config_data)
- except Exception as e:
- self.logger.error(f"配置项 {uid} 验证失败: {e}")
- # 不中断整个加载过程,跳过无效配置
- continue
-
- # 确保instance_order与现有实例匹配
- valid_order = [uid for uid in instance_order if uid in instances]
- self.data = {
- "instance_order": valid_order,
- "instances": instances
- }
- self.logger.info(f"成功加载 {len(instances)} 个配置实例")
-
- except Exception as e:
- self.logger.error(f"配置加载失败: {e}", exc_info=True)
- # 初始化空配置作为安全措施
- self.data = {
- "instance_order": [],
- "instances": {}
- }
- # 尝试备份损坏的配置文件
- await self._backup_corrupted_config()
-
- async def _backup_corrupted_config(self) -> None:
- """备份损坏的配置文件"""
- try:
- backup_path = self.file_path.with_suffix(f"{self.file_path.suffix}.bak")
- counter = 1
- while backup_path.exists():
- backup_path = self.file_path.with_suffix(f"{self.file_path.suffix}.bak{counter}")
- counter += 1
-
- if self.file_path.exists():
- async with aiofiles.open(self.file_path, 'rb') as src, \
- aiofiles.open(backup_path, 'wb') as dst:
- content = await src.read()
- await dst.write(content)
-
- self.logger.warning(f"已备份损坏的配置文件到: {backup_path}")
- except Exception as e:
- self.logger.error(f"备份损坏配置失败: {e}")
-
- async def _save_async(self) -> None:
- """异步保存配置到文件"""
- async with self._lock:
- try:
- serializable = {
- "instance_order": self.data["instance_order"],
- "instances": {
- uid: instance.model_dump(mode='json')
- for uid, instance in self.data["instances"].items()
- }
- }
-
- # 确保目录存在
- self.file_path.parent.mkdir(parents=True, exist_ok=True)
-
- async with aiofiles.open(self.file_path, 'w', encoding='utf-8') as f:
- await f.write(json.dumps(serializable, indent=2, ensure_ascii=False))
-
- self.logger.debug(f"配置已异步保存到: {self.file_path}")
- except Exception as e:
- self.logger.error(f"配置保存失败: {e}", exc_info=True)
- raise
- finally:
- self._save_task = None
- self._pending_save = False
-
- def _schedule_save(self) -> None:
- """
- 调度配置保存(避免频繁保存)
- 使用防抖技术,确保短时间内多次修改只保存一次
- """
- if self._save_task and not self._save_task.done():
- # 已有保存任务在运行,标记需要再次保存
- self._pending_save = True
- return
-
- async def save_with_debounce():
- # 等待短暂时间,合并多次修改
- await asyncio.sleep(0.1)
-
- # 如果有新的保存请求,递归处理
- if self._pending_save:
- self._pending_save = False
- await save_with_debounce()
- return
-
- await self._save_async()
-
- self._save_task = asyncio.create_task(save_with_debounce())
-
- @staticmethod
- def generate_uid(length: int = 8) -> str:
- """生成8位随机UID"""
- alphabet = string.ascii_letters + string.digits
- return ''.join(secrets.choice(alphabet) for _ in range(length))
-
- async def create(self, **kwargs) -> str:
- """创建新的配置实例"""
- async with self._lock:
- # 确保配置已加载完成
- if not self._load_task.done():
- await self._load_task
-
- # 生成唯一UID
- uid = self.generate_uid()
- while uid in self.data["instances"]:
- uid = self.generate_uid()
-
- try:
- # 使用Pydantic模型验证数据
- new_config = self.model_type(**kwargs)
- except Exception as e:
- self.logger.error(f"无效的配置数据: {e}")
- raise
-
- self.data["instances"][uid] = new_config
- self.data["instance_order"].append(uid)
- self._schedule_save()
- self.logger.info(f"创建新的配置实例: {uid}")
- return uid
-
- # 实现所需魔法方法(同步方法)
- def __getitem__(self, uid: str) -> T:
- """获取配置项(同步)"""
- # 确保配置已加载完成
- if not self._load_task.done():
- raise RuntimeError("配置尚未加载完成,请等待初始化完成")
-
- return cast(T, self.data["instances"][uid])
-
- def __setitem__(self, uid: str, value: T | dict[str, Any]) -> None:
- """
- 设置配置项(同步)
- 注意:此方法是同步的,但会触发异步保存
-
- 支持两种用法:
- 1. config[uid] = config_model_instance
- 2. config[uid] = {"name": "value", ...} # 字典形式
- """
- # 确保配置已加载完成
- if not self._load_task.done():
- raise RuntimeError("配置尚未加载完成,请等待初始化完成")
-
- # 如果传入的是字典,转换为模型实例
- if isinstance(value, dict):
- try:
- value = self.model_type(**value)
- except Exception as e:
- self.logger.error(f"配置数据转换失败: {e}")
- raise ValueError("无效的配置数据") from e
-
- if not isinstance(value, self.model_type):
- raise TypeError(f"值必须是 {self.model_type.__name__} 类型或字典")
-
- # 更新内存数据
- if uid not in self.data["instances"]:
- self.data["instance_order"].append(uid)
-
- self.data["instances"][uid] = value
- self._schedule_save()
-
- def __delitem__(self, uid: str) -> None:
- """删除配置项(同步)"""
- # 确保配置已加载完成
- if not self._load_task.done():
- raise RuntimeError("配置尚未加载完成,请等待初始化完成")
-
- if uid in self.data["instances"]:
- del self.data["instances"][uid]
- if uid in self.data["instance_order"]:
- self.data["instance_order"].remove(uid)
- self._schedule_save()
- else:
- raise KeyError(uid)
-
- def __contains__(self, uid: str) -> bool:
- """检查UID是否存在(同步)"""
- # 确保配置已加载完成
- if not self._load_task.done():
- return False # 配置未加载完成时,认为不存在
-
- return uid in self.data["instances"]
-
- def __len__(self) -> int:
- """返回配置实例数量(同步)"""
- # 确保配置已加载完成
- if not self._load_task.done():
- return 0
-
- return len(self.data["instance_order"])
-
- def get_instance_order(self) -> list[str]:
- """获取实例顺序列表(同步)"""
- # 确保配置已加载完成
- if not self._load_task.done():
- return []
-
- return self.data["instance_order"].copy()
-
- def get_all_instances(self) -> dict[str, T]:
- """获取所有配置实例(同步)"""
- # 确保配置已加载完成
- if not self._load_task.done():
- return {}
-
- return cast(dict[str, T], self.data["instances"].copy())
-
- async def wait_until_ready(self) -> None:
- """等待配置加载完成"""
- await self._load_task
-
- async def save_now(self) -> None:
- """立即保存配置(等待保存完成)"""
- if self._save_task:
- await self._save_task
- else:
- await self._save_async()
-
- def is_ready(self) -> bool:
- """检查配置是否已加载完成"""
- return self._load_task.done() and not self._load_task.cancelled()
-
-
-
-'''
-初始化
-ConfigManager(file_path: str, log_name: str, model_type: type[T])
- file_path: 配置文件路径
- log_name: 日志记录器名称
- model_type: 配置模型类型(继承自 Pydantic BaseModel)
-主要方法
- create(**kwargs) -> str
- 异步创建新的配置实例,返回唯一标识符(UID)
-
- wait_until_ready() -> None
- 异步等待配置加载完成
-
- save_now() -> None
- 立即保存配置到文件
-
- is_ready() -> bool
- 检查配置是否已加载完成
-
- get_instance_order() -> list[str]
- 获取配置实例的顺序列表
-
- get_all_instances() -> dict[str, T]
- 获取所有配置实例
-
-魔法方法
- getitem(uid: str) -> T
- 通过 UID 获取配置实例
-
- setitem(uid: str, value: T | dict[str, Any]) -> None
- 通过 UID 设置配置实例
-
- delitem(uid: str) -> None
- 通过 UID 删除配置实例
-
- contains(uid: str) -> bool
- 检查是否存在指定 UID 的配置实例
-
- len() -> int
- 获取配置实例数量
-'''
\ No newline at end of file
diff --git a/app/models/ConfigBase.py b/app/models/ConfigBase.py
index 063711b..e9d1a7a 100644
--- a/app/models/ConfigBase.py
+++ b/app/models/ConfigBase.py
@@ -390,6 +390,11 @@ class MultipleConfig:
配置文件路径,必须为 JSON 文件,如果不存在则会创建
"""
+ if path.suffix != ".json":
+ raise ValueError(
+ "The config file must be a JSON file with '.json' extension."
+ )
+
self.file = path
if not self.file.exists():
diff --git a/app/models/__init__.py b/app/models/__init__.py
new file mode 100644
index 0000000..345fc7f
--- /dev/null
+++ b/app/models/__init__.py
@@ -0,0 +1,27 @@
+# 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
+
+__version__ = "5.0.0"
+__author__ = "DLmaster361 "
+__license__ = "GPL-3.0 license"
+
+from .ConfigBase import *
+
+__all__ = ["ConfigBase"]
diff --git a/config/data_config.json b/config/data_config.json
deleted file mode 100644
index 2d6259c..0000000
--- a/config/data_config.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "instance_order": [],
- "instances": {}
-}
\ No newline at end of file
diff --git a/main.py b/main.py
index e69de29..5bb9a84 100644
--- a/main.py
+++ b/main.py
@@ -0,0 +1,57 @@
+# 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 os
+import sys
+import ctypes
+
+from app.core.logger import logger
+
+
+def is_admin() -> bool:
+ """检查当前程序是否以管理员身份运行"""
+ try:
+ return ctypes.windll.shell32.IsUserAnAdmin()
+ except:
+ return False
+
+
+@logger.catch
+def main():
+
+ if is_admin():
+
+ import uvicorn
+ from app.api import app
+
+ uvicorn.run(app, host="0.0.0.0", port=8000)
+
+ else:
+
+ ctypes.windll.shell32.ShellExecuteW(
+ None, "runas", sys.executable, os.path.realpath(sys.argv[0]), None, 1
+ )
+ sys.exit(0)
+
+
+if __name__ == "__main__":
+
+ main()