feat: 初步完成后端自定义webhook适配;重构配置项管理体系
This commit is contained in:
@@ -100,141 +100,83 @@ async def test_notify() -> OutBase:
|
||||
|
||||
|
||||
@router.post(
|
||||
"/webhook/create",
|
||||
summary="创建自定义Webhook",
|
||||
response_model=OutBase,
|
||||
"/webhook/get",
|
||||
summary="查询 webhook 配置",
|
||||
response_model=WebhookGetOut,
|
||||
status_code=200,
|
||||
)
|
||||
async def create_webhook(webhook_data: dict = Body(...)) -> OutBase:
|
||||
"""创建自定义Webhook"""
|
||||
async def get_webhook(webhook: WebhookGetIn = Body(...)) -> WebhookGetOut:
|
||||
|
||||
try:
|
||||
# 生成唯一ID
|
||||
webhook_id = str(uuid.uuid4())
|
||||
|
||||
# 创建webhook配置
|
||||
webhook_config = {
|
||||
"id": webhook_id,
|
||||
"name": webhook_data.get("name", ""),
|
||||
"url": webhook_data.get("url", ""),
|
||||
"template": webhook_data.get("template", ""),
|
||||
"enabled": webhook_data.get("enabled", True),
|
||||
"headers": webhook_data.get("headers", {}),
|
||||
"method": webhook_data.get("method", "POST"),
|
||||
}
|
||||
|
||||
# 获取当前配置
|
||||
current_config = await Config.get_setting()
|
||||
custom_webhooks = current_config.get("Notify", {}).get("CustomWebhooks", [])
|
||||
|
||||
# 添加新webhook
|
||||
custom_webhooks.append(webhook_config)
|
||||
|
||||
# 更新配置
|
||||
update_data = {"Notify": {"CustomWebhooks": custom_webhooks}}
|
||||
await Config.update_setting(update_data)
|
||||
|
||||
return OutBase(message=f"Webhook '{webhook_config['name']}' 创建成功")
|
||||
|
||||
index, data = await Config.get_webhook(None, None, webhook.webhookId)
|
||||
index = [WebhookIndexItem(**_) for _ in index]
|
||||
data = {uid: Webhook(**cfg) for uid, cfg in data.items()}
|
||||
except Exception as e:
|
||||
return OutBase(
|
||||
code=500, status="error", message=f"{type(e).__name__}: {str(e)}"
|
||||
return WebhookGetOut(
|
||||
code=500,
|
||||
status="error",
|
||||
message=f"{type(e).__name__}: {str(e)}",
|
||||
index=[],
|
||||
data={},
|
||||
)
|
||||
return WebhookGetOut(index=index, data=data)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/webhook/update",
|
||||
summary="更新自定义Webhook",
|
||||
response_model=OutBase,
|
||||
"/webhook/add",
|
||||
summary="添加定时项",
|
||||
response_model=WebhookCreateOut,
|
||||
status_code=200,
|
||||
)
|
||||
async def update_webhook(webhook_data: dict = Body(...)) -> OutBase:
|
||||
"""更新自定义Webhook"""
|
||||
async def add_webhook() -> WebhookCreateOut:
|
||||
|
||||
try:
|
||||
webhook_id = webhook_data.get("id")
|
||||
if not webhook_id:
|
||||
return OutBase(code=400, status="error", message="缺少Webhook ID")
|
||||
|
||||
# 获取当前配置
|
||||
current_config = await Config.get_setting()
|
||||
custom_webhooks = current_config.get("Notify", {}).get("CustomWebhooks", [])
|
||||
|
||||
# 查找并更新webhook
|
||||
updated = False
|
||||
for i, webhook in enumerate(custom_webhooks):
|
||||
if webhook.get("id") == webhook_id:
|
||||
custom_webhooks[i].update(
|
||||
{
|
||||
"name": webhook_data.get("name", webhook.get("name", "")),
|
||||
"url": webhook_data.get("url", webhook.get("url", "")),
|
||||
"template": webhook_data.get(
|
||||
"template", webhook.get("template", "")
|
||||
),
|
||||
"enabled": webhook_data.get(
|
||||
"enabled", webhook.get("enabled", True)
|
||||
),
|
||||
"headers": webhook_data.get(
|
||||
"headers", webhook.get("headers", {})
|
||||
),
|
||||
"method": webhook_data.get(
|
||||
"method", webhook.get("method", "POST")
|
||||
),
|
||||
}
|
||||
)
|
||||
updated = True
|
||||
break
|
||||
|
||||
if not updated:
|
||||
return OutBase(code=404, status="error", message="Webhook不存在")
|
||||
|
||||
# 更新配置
|
||||
update_data = {"Notify": {"CustomWebhooks": custom_webhooks}}
|
||||
await Config.update_setting(update_data)
|
||||
|
||||
return OutBase(message="Webhook更新成功")
|
||||
|
||||
except Exception as e:
|
||||
return OutBase(
|
||||
code=500, status="error", message=f"{type(e).__name__}: {str(e)}"
|
||||
)
|
||||
uid, config = await Config.add_webhook(None, None)
|
||||
data = Webhook(**(await config.toDict()))
|
||||
return WebhookCreateOut(webhookId=str(uid), data=data)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/webhook/delete",
|
||||
summary="删除自定义Webhook",
|
||||
response_model=OutBase,
|
||||
status_code=200,
|
||||
"/webhook/update", summary="更新定时项", response_model=OutBase, status_code=200
|
||||
)
|
||||
async def delete_webhook(webhook_data: dict = Body(...)) -> OutBase:
|
||||
"""删除自定义Webhook"""
|
||||
async def update_webhook(webhook: WebhookUpdateIn = Body(...)) -> OutBase:
|
||||
|
||||
try:
|
||||
webhook_id = webhook_data.get("id")
|
||||
if not webhook_id:
|
||||
return OutBase(code=400, status="error", message="缺少Webhook ID")
|
||||
|
||||
# 获取当前配置
|
||||
current_config = await Config.get_setting()
|
||||
custom_webhooks = current_config.get("Notify", {}).get("CustomWebhooks", [])
|
||||
|
||||
# 查找并删除webhook
|
||||
original_length = len(custom_webhooks)
|
||||
custom_webhooks = [w for w in custom_webhooks if w.get("id") != webhook_id]
|
||||
|
||||
if len(custom_webhooks) == original_length:
|
||||
return OutBase(code=404, status="error", message="Webhook不存在")
|
||||
|
||||
# 更新配置
|
||||
update_data = {"Notify": {"CustomWebhooks": custom_webhooks}}
|
||||
await Config.update_setting(update_data)
|
||||
|
||||
return OutBase(message="Webhook删除成功")
|
||||
|
||||
await Config.update_webhook(
|
||||
None, None, webhook.webhookId, webhook.data.model_dump(exclude_unset=True)
|
||||
)
|
||||
except Exception as e:
|
||||
return OutBase(
|
||||
code=500, status="error", message=f"{type(e).__name__}: {str(e)}"
|
||||
)
|
||||
return OutBase()
|
||||
|
||||
|
||||
@router.post(
|
||||
"/webhook/delete", summary="删除定时项", response_model=OutBase, status_code=200
|
||||
)
|
||||
async def delete_webhook(webhook: WebhookDeleteIn = Body(...)) -> OutBase:
|
||||
|
||||
try:
|
||||
await Config.del_webhook(None, None, webhook.webhookId)
|
||||
except Exception as e:
|
||||
return OutBase(
|
||||
code=500, status="error", message=f"{type(e).__name__}: {str(e)}"
|
||||
)
|
||||
return OutBase()
|
||||
|
||||
|
||||
@router.post(
|
||||
"/webhook/order", summary="重新排序定时项", response_model=OutBase, status_code=200
|
||||
)
|
||||
async def reorder_webhook(webhook: WebhookReorderIn = Body(...)) -> OutBase:
|
||||
|
||||
try:
|
||||
await Config.reorder_webhook(None, None, webhook.indexList)
|
||||
except Exception as e:
|
||||
return OutBase(
|
||||
code=500, status="error", message=f"{type(e).__name__}: {str(e)}"
|
||||
)
|
||||
return OutBase()
|
||||
|
||||
|
||||
@router.post(
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -62,10 +62,7 @@ class _TaskManager:
|
||||
actual_id = None
|
||||
else:
|
||||
for script_id, script in Config.ScriptConfig.items():
|
||||
if (
|
||||
isinstance(script, (MaaConfig | GeneralConfig))
|
||||
and actual_id in script.UserData
|
||||
):
|
||||
if actual_id in script.UserData:
|
||||
task_id = script_id
|
||||
break
|
||||
else:
|
||||
@@ -142,31 +139,26 @@ class _TaskManager:
|
||||
# 初始化任务列表
|
||||
if task_id in Config.QueueConfig:
|
||||
|
||||
queue = Config.QueueConfig[task_id]
|
||||
if not isinstance(queue, QueueConfig):
|
||||
return
|
||||
|
||||
task_list = []
|
||||
for queue_item in queue.QueueItem.values():
|
||||
for queue_item in Config.QueueConfig[task_id].QueueItem.values():
|
||||
if queue_item.get("Info", "ScriptId") == "-":
|
||||
continue
|
||||
script_id = uuid.UUID(queue_item.get("Info", "ScriptId"))
|
||||
script = Config.ScriptConfig[script_id]
|
||||
if not isinstance(script, (MaaConfig | GeneralConfig)):
|
||||
logger.error(f"不支持的脚本类型: {type(script).__name__}")
|
||||
continue
|
||||
script_uid = uuid.UUID(queue_item.get("Info", "ScriptId"))
|
||||
|
||||
task_list.append(
|
||||
{
|
||||
"script_id": str(script_id),
|
||||
"script_id": str(script_uid),
|
||||
"status": "等待",
|
||||
"name": script.get("Info", "Name"),
|
||||
"name": Config.ScriptConfig[script_uid].get("Info", "Name"),
|
||||
"user_list": [
|
||||
{
|
||||
"user_id": str(user_id),
|
||||
"status": "等待",
|
||||
"name": config.get("Info", "Name"),
|
||||
}
|
||||
for user_id, config in script.UserData.items()
|
||||
for user_id, config in Config.ScriptConfig[
|
||||
script_uid
|
||||
].UserData.items()
|
||||
if config.get("Info", "Status")
|
||||
and config.get("Info", "RemainedDay") != 0
|
||||
],
|
||||
@@ -175,23 +167,20 @@ class _TaskManager:
|
||||
|
||||
elif actual_id is not None and actual_id in Config.ScriptConfig:
|
||||
|
||||
script = Config.ScriptConfig[actual_id]
|
||||
if not isinstance(script, (MaaConfig | GeneralConfig)):
|
||||
logger.error(f"不支持的脚本类型: {type(script).__name__}")
|
||||
return
|
||||
|
||||
task_list = [
|
||||
{
|
||||
"script_id": str(actual_id),
|
||||
"status": "等待",
|
||||
"name": script.get("Info", "Name"),
|
||||
"name": Config.ScriptConfig[actual_id].get("Info", "Name"),
|
||||
"user_list": [
|
||||
{
|
||||
"user_id": str(user_id),
|
||||
"status": "等待",
|
||||
"name": config.get("Info", "Name"),
|
||||
}
|
||||
for user_id, config in script.UserData.items()
|
||||
for user_id, config in Config.ScriptConfig[
|
||||
actual_id
|
||||
].UserData.items()
|
||||
if config.get("Info", "Status")
|
||||
and config.get("Info", "RemainedDay") != 0
|
||||
],
|
||||
|
||||
@@ -81,9 +81,7 @@ class _MainTimer:
|
||||
|
||||
for uid, queue in Config.QueueConfig.items():
|
||||
|
||||
if not isinstance(queue, QueueConfig) or not queue.get(
|
||||
"Info", "TimeEnabled"
|
||||
):
|
||||
if not queue.get("Info", "TimeEnabled"):
|
||||
continue
|
||||
|
||||
# 避免重复调起任务
|
||||
|
||||
@@ -25,9 +25,10 @@ import json
|
||||
import uuid
|
||||
import win32com.client
|
||||
from copy import deepcopy
|
||||
from urllib.parse import urlparse
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import List, Any, Dict, Union, Optional
|
||||
from typing import List, Any, Dict, Union, Optional, TypeVar, Generic, Type
|
||||
|
||||
|
||||
from app.utils import dpapi_encrypt, dpapi_decrypt
|
||||
@@ -127,17 +128,25 @@ class DateTimeValidator(ConfigValidator):
|
||||
|
||||
class JSONValidator(ConfigValidator):
|
||||
|
||||
def __init__(self, tpye: type[dict] | type[list] = dict) -> None:
|
||||
self.type = tpye
|
||||
|
||||
def validate(self, value: Any) -> bool:
|
||||
if not isinstance(value, str):
|
||||
return False
|
||||
try:
|
||||
json.loads(value)
|
||||
return True
|
||||
data = json.loads(value)
|
||||
if isinstance(data, self.type):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
except json.JSONDecodeError:
|
||||
return False
|
||||
|
||||
def correct(self, value: Any) -> str:
|
||||
return value if self.validate(value) else "{ }"
|
||||
return (
|
||||
value if self.validate(value) else ("{ }" if self.type == dict else "[ ]")
|
||||
)
|
||||
|
||||
|
||||
class EncryptValidator(ConfigValidator):
|
||||
@@ -246,6 +255,67 @@ class UserNameValidator(ConfigValidator):
|
||||
return value
|
||||
|
||||
|
||||
class URLValidator(ConfigValidator):
|
||||
"""URL格式验证器"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
schemes: list[str] | None = None,
|
||||
require_netloc: bool = True,
|
||||
default: str = "",
|
||||
):
|
||||
"""
|
||||
:param schemes: 允许的协议列表, 若为 None 则允许任意协议
|
||||
:param require_netloc: 是否要求必须包含网络位置, 如域名或IP
|
||||
"""
|
||||
self.schemes = [s.lower() for s in schemes] if schemes else None
|
||||
self.require_netloc = require_netloc
|
||||
self.default = default
|
||||
|
||||
def validate(self, value: Any) -> bool:
|
||||
|
||||
if value == self.default:
|
||||
return True
|
||||
|
||||
if not isinstance(value, str):
|
||||
return False
|
||||
|
||||
try:
|
||||
parsed = urlparse(value)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
# 检查协议
|
||||
if self.schemes is not None:
|
||||
if not parsed.scheme or parsed.scheme.lower() not in self.schemes:
|
||||
return False
|
||||
else:
|
||||
# 不限制协议仍要求有 scheme
|
||||
if not parsed.scheme:
|
||||
return False
|
||||
|
||||
# 检查是否包含网络位置
|
||||
if self.require_netloc and not parsed.netloc:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def correct(self, value: Any) -> str:
|
||||
|
||||
if self.validate(value):
|
||||
return value
|
||||
|
||||
if isinstance(value, str):
|
||||
# 简单尝试:若看起来像域名,加上 https://
|
||||
stripped = value.strip()
|
||||
if stripped and not stripped.startswith(("http://", "https://")):
|
||||
candidate = f"https://{stripped}"
|
||||
if self.validate(candidate):
|
||||
return candidate
|
||||
|
||||
return self.default
|
||||
|
||||
|
||||
class ConfigItem:
|
||||
"""配置项"""
|
||||
|
||||
@@ -537,7 +607,10 @@ class ConfigBase:
|
||||
await item.unlock()
|
||||
|
||||
|
||||
class MultipleConfig:
|
||||
T = TypeVar("T", bound="ConfigBase")
|
||||
|
||||
|
||||
class MultipleConfig(Generic[T]):
|
||||
"""
|
||||
多配置项管理类
|
||||
|
||||
@@ -550,7 +623,7 @@ class MultipleConfig:
|
||||
子配置项的类型列表, 必须是 ConfigBase 的子类
|
||||
"""
|
||||
|
||||
def __init__(self, sub_config_type: List[type]):
|
||||
def __init__(self, sub_config_type: List[Type[T]]):
|
||||
|
||||
if not sub_config_type:
|
||||
raise ValueError("子配置项类型列表不能为空")
|
||||
@@ -561,13 +634,13 @@ class MultipleConfig:
|
||||
f"配置类型 {config_type.__name__} 必须是 ConfigBase 的子类"
|
||||
)
|
||||
|
||||
self.sub_config_type = sub_config_type
|
||||
self.file: None | Path = None
|
||||
self.sub_config_type: List[Type[T]] = sub_config_type
|
||||
self.file: Path | None = None
|
||||
self.order: List[uuid.UUID] = []
|
||||
self.data: Dict[uuid.UUID, ConfigBase] = {}
|
||||
self.data: Dict[uuid.UUID, T] = {}
|
||||
self.is_locked = False
|
||||
|
||||
def __getitem__(self, key: uuid.UUID) -> ConfigBase:
|
||||
def __getitem__(self, key: uuid.UUID) -> T:
|
||||
"""允许通过 config[uuid] 访问配置项"""
|
||||
if key not in self.data:
|
||||
raise KeyError(f"配置项 '{key}' 不存在")
|
||||
@@ -665,7 +738,9 @@ class MultipleConfig:
|
||||
if self.file:
|
||||
await self.save()
|
||||
|
||||
async def toDict(self) -> Dict[str, Union[list, dict]]:
|
||||
async def toDict(
|
||||
self, ignore_multi_config: bool = False, if_decrypt: bool = True
|
||||
) -> Dict[str, Union[list, dict]]:
|
||||
"""
|
||||
将配置项转换为字典
|
||||
|
||||
@@ -678,7 +753,7 @@ class MultipleConfig:
|
||||
]
|
||||
}
|
||||
for uid, config in self.items():
|
||||
data[str(uid)] = await config.toDict()
|
||||
data[str(uid)] = await config.toDict(ignore_multi_config, if_decrypt)
|
||||
return data
|
||||
|
||||
async def get(self, uid: uuid.UUID) -> Dict[str, Union[list, dict]]:
|
||||
@@ -721,7 +796,7 @@ class MultipleConfig:
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
async def add(self, config_type: type) -> tuple[uuid.UUID, ConfigBase]:
|
||||
async def add(self, config_type: Type[T]) -> tuple[uuid.UUID, T]:
|
||||
"""
|
||||
添加一个新的配置项
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ __author__ = "DLmaster361 <DLmaster_361@163.com>"
|
||||
__license__ = "GPL-3.0 license"
|
||||
|
||||
from .ConfigBase import *
|
||||
from .config import *
|
||||
from .schema import *
|
||||
|
||||
__all__ = ["ConfigBase", "schema"]
|
||||
__all__ = ["ConfigBase", "config", "schema"]
|
||||
|
||||
569
app/models/config.py
Normal file
569
app/models/config.py
Normal file
@@ -0,0 +1,569 @@
|
||||
# AUTO-MAS: A Multi-Script, Multi-Config Management and Automation Software
|
||||
# Copyright © 2025 AUTO-MAS Team
|
||||
|
||||
# This file is part of AUTO-MAS.
|
||||
|
||||
# AUTO-MAS 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-MAS 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-MAS. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# Contact: DLmaster_361@163.com
|
||||
|
||||
from pathlib import Path
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from .ConfigBase import *
|
||||
|
||||
|
||||
class Webhook(ConfigBase):
|
||||
"""Webhook 配置"""
|
||||
|
||||
Info_Name = ConfigItem("Info", "Name", "")
|
||||
Info_Enabled = ConfigItem("Info", "Enabled", True, BoolValidator())
|
||||
|
||||
Data_Url = ConfigItem("Data", "Url", "", URLValidator())
|
||||
Data_Template = ConfigItem("Data", "Template", "")
|
||||
Data_Headers = ConfigItem("Data", "Headers", "{ }", JSONValidator())
|
||||
Data_Method = ConfigItem(
|
||||
"Data", "Method", "POST", OptionsValidator(["POST", "GET"])
|
||||
)
|
||||
|
||||
|
||||
class GlobalConfig(ConfigBase):
|
||||
"""全局配置"""
|
||||
|
||||
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_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())
|
||||
|
||||
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", "", EncryptValidator()
|
||||
)
|
||||
Notify_FromAddress = ConfigItem("Notify", "FromAddress", "")
|
||||
Notify_ToAddress = ConfigItem("Notify", "ToAddress", "")
|
||||
Notify_IfServerChan = ConfigItem("Notify", "IfServerChan", False, BoolValidator())
|
||||
Notify_ServerChanKey = ConfigItem("Notify", "ServerChanKey", "")
|
||||
Notify_CustomWebhooks = MultipleConfig([Webhook])
|
||||
|
||||
Update_IfAutoUpdate = ConfigItem("Update", "IfAutoUpdate", False, BoolValidator())
|
||||
Update_Source = ConfigItem(
|
||||
"Update",
|
||||
"Source",
|
||||
"GitHub",
|
||||
OptionsValidator(["GitHub", "MirrorChyan", "AutoSite"]),
|
||||
)
|
||||
Update_ProxyAddress = ConfigItem("Update", "ProxyAddress", "")
|
||||
Update_MirrorChyanCDK = ConfigItem(
|
||||
"Update", "MirrorChyanCDK", "", EncryptValidator()
|
||||
)
|
||||
|
||||
Data_UID = ConfigItem("Data", "UID", str(uuid.uuid4()), UUIDValidator())
|
||||
Data_LastStatisticsUpload = ConfigItem(
|
||||
"Data",
|
||||
"LastStatisticsUpload",
|
||||
"2000-01-01 00:00:00",
|
||||
DateTimeValidator("%Y-%m-%d %H:%M:%S"),
|
||||
)
|
||||
Data_LastStageUpdated = ConfigItem(
|
||||
"Data",
|
||||
"LastStageUpdated",
|
||||
"2000-01-01 00:00:00",
|
||||
DateTimeValidator("%Y-%m-%d %H:%M:%S"),
|
||||
)
|
||||
Data_StageTimeStamp = ConfigItem(
|
||||
"Data",
|
||||
"StageTimeStamp",
|
||||
"2000-01-01 00:00:00",
|
||||
DateTimeValidator("%Y-%m-%d %H:%M:%S"),
|
||||
)
|
||||
Data_Stage = ConfigItem("Data", "Stage", "{ }", JSONValidator())
|
||||
Data_LastNoticeUpdated = ConfigItem(
|
||||
"Data",
|
||||
"LastNoticeUpdated",
|
||||
"2000-01-01 00:00:00",
|
||||
DateTimeValidator("%Y-%m-%d %H:%M:%S"),
|
||||
)
|
||||
Data_IfShowNotice = ConfigItem("Data", "IfShowNotice", True, BoolValidator())
|
||||
Data_Notice = ConfigItem("Data", "Notice", "{ }", JSONValidator())
|
||||
Data_LastWebConfigUpdated = ConfigItem(
|
||||
"Data",
|
||||
"LastWebConfigUpdated",
|
||||
"2000-01-01 00:00:00",
|
||||
DateTimeValidator("%Y-%m-%d %H:%M:%S"),
|
||||
)
|
||||
Data_WebConfig = ConfigItem("Data", "WebConfig", "{ }", JSONValidator())
|
||||
|
||||
|
||||
class QueueItem(ConfigBase):
|
||||
"""队列项配置"""
|
||||
|
||||
related_config: dict[str, MultipleConfig] = {}
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
|
||||
self.Info_ScriptId = ConfigItem(
|
||||
"Info",
|
||||
"ScriptId",
|
||||
"-",
|
||||
MultipleUIDValidator("-", self.related_config, "ScriptConfig"),
|
||||
)
|
||||
|
||||
|
||||
class TimeSet(ConfigBase):
|
||||
"""时间设置配置"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
|
||||
self.Info_Enabled = ConfigItem("Info", "Enabled", False, BoolValidator())
|
||||
self.Info_Time = ConfigItem("Info", "Time", "00:00", DateTimeValidator("%H:%M"))
|
||||
|
||||
|
||||
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_LastTimedStart = ConfigItem(
|
||||
"Data",
|
||||
"LastTimedStart",
|
||||
"2000-01-01 00:00",
|
||||
DateTimeValidator("%Y-%m-%d %H:%M"),
|
||||
)
|
||||
|
||||
self.TimeSet = MultipleConfig([TimeSet])
|
||||
self.QueueItem = MultipleConfig([QueueItem])
|
||||
|
||||
|
||||
class MaaUserConfig(ConfigBase):
|
||||
"""MAA用户配置"""
|
||||
|
||||
related_config: dict[str, MultipleConfig] = {}
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
|
||||
self.Info_Name = ConfigItem("Info", "Name", "新用户", UserNameValidator())
|
||||
self.Info_Id = ConfigItem("Info", "Id", "")
|
||||
self.Info_Mode = ConfigItem(
|
||||
"Info", "Mode", "简洁", OptionsValidator(["简洁", "详细"])
|
||||
)
|
||||
self.Info_StageMode = ConfigItem(
|
||||
"Info",
|
||||
"StageMode",
|
||||
"Fixed",
|
||||
MultipleUIDValidator("Fixed", self.related_config, "PlanConfig"),
|
||||
)
|
||||
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, 9999)
|
||||
)
|
||||
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_InfrastPath = ConfigItem(
|
||||
"Info", "InfrastPath", str(Path.cwd()), FileValidator()
|
||||
)
|
||||
self.Info_Password = ConfigItem("Info", "Password", "", EncryptValidator())
|
||||
self.Info_Notes = ConfigItem("Info", "Notes", "无")
|
||||
self.Info_MedicineNumb = ConfigItem(
|
||||
"Info", "MedicineNumb", 0, RangeValidator(0, 9999)
|
||||
)
|
||||
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", "", EncryptValidator()
|
||||
)
|
||||
|
||||
self.Data_LastProxyDate = ConfigItem(
|
||||
"Data", "LastProxyDate", "2000-01-01", DateTimeValidator("%Y-%m-%d")
|
||||
)
|
||||
self.Data_LastAnnihilationDate = ConfigItem(
|
||||
"Data", "LastAnnihilationDate", "2000-01-01", DateTimeValidator("%Y-%m-%d")
|
||||
)
|
||||
self.Data_LastSklandDate = ConfigItem(
|
||||
"Data", "LastSklandDate", "2000-01-01", DateTimeValidator("%Y-%m-%d")
|
||||
)
|
||||
self.Data_ProxyTimes = ConfigItem(
|
||||
"Data", "ProxyTimes", 0, RangeValidator(0, 9999)
|
||||
)
|
||||
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_CustomWebhooks = MultipleConfig([Webhook])
|
||||
|
||||
|
||||
class MaaConfig(ConfigBase):
|
||||
"""MAA配置"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
|
||||
self.Info_Name = ConfigItem("Info", "Name", "新 MAA 脚本")
|
||||
self.Info_Path = ConfigItem("Info", "Path", str(Path.cwd()), FolderValidator())
|
||||
|
||||
self.Run_TaskTransitionMethod = ConfigItem(
|
||||
"Run",
|
||||
"TaskTransitionMethod",
|
||||
"ExitEmulator",
|
||||
OptionsValidator(["NoAction", "ExitGame", "ExitEmulator"]),
|
||||
)
|
||||
self.Run_ProxyTimesLimit = ConfigItem(
|
||||
"Run", "ProxyTimesLimit", 0, RangeValidator(0, 9999)
|
||||
)
|
||||
self.Run_ADBSearchRange = ConfigItem(
|
||||
"Run", "ADBSearchRange", 0, RangeValidator(0, 3)
|
||||
)
|
||||
self.Run_RunTimesLimit = ConfigItem(
|
||||
"Run", "RunTimesLimit", 3, RangeValidator(1, 9999)
|
||||
)
|
||||
self.Run_AnnihilationTimeLimit = ConfigItem(
|
||||
"Run", "AnnihilationTimeLimit", 40, RangeValidator(1, 9999)
|
||||
)
|
||||
self.Run_RoutineTimeLimit = ConfigItem(
|
||||
"Run", "RoutineTimeLimit", 10, RangeValidator(1, 9999)
|
||||
)
|
||||
self.Run_AnnihilationWeeklyLimit = ConfigItem(
|
||||
"Run", "AnnihilationWeeklyLimit", True, BoolValidator()
|
||||
)
|
||||
|
||||
self.UserData = MultipleConfig([MaaUserConfig])
|
||||
|
||||
|
||||
class MaaPlanConfig(ConfigBase):
|
||||
"""MAA计划表配置"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
|
||||
self.Info_Name = ConfigItem("Info", "Name", "新 MAA 计划表")
|
||||
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, 9999)
|
||||
)
|
||||
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("Info", "Mode") == "ALL":
|
||||
|
||||
return self.config_item_dict["ALL"][name]
|
||||
|
||||
elif self.get("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]
|
||||
|
||||
else:
|
||||
raise ValueError("非法的计划表模式")
|
||||
|
||||
|
||||
class GeneralUserConfig(ConfigBase):
|
||||
"""通用脚本用户配置"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
|
||||
self.Info_Name = ConfigItem("Info", "Name", "新用户", UserNameValidator())
|
||||
self.Info_Status = ConfigItem("Info", "Status", True, BoolValidator())
|
||||
self.Info_RemainedDay = ConfigItem(
|
||||
"Info", "RemainedDay", -1, RangeValidator(-1, 9999)
|
||||
)
|
||||
self.Info_IfScriptBeforeTask = ConfigItem(
|
||||
"Info", "IfScriptBeforeTask", False, BoolValidator()
|
||||
)
|
||||
self.Info_ScriptBeforeTask = ConfigItem(
|
||||
"Info", "ScriptBeforeTask", str(Path.cwd()), FileValidator()
|
||||
)
|
||||
self.Info_IfScriptAfterTask = ConfigItem(
|
||||
"Info", "IfScriptAfterTask", False, BoolValidator()
|
||||
)
|
||||
self.Info_ScriptAfterTask = ConfigItem(
|
||||
"Info", "ScriptAfterTask", str(Path.cwd()), FileValidator()
|
||||
)
|
||||
self.Info_Notes = ConfigItem("Info", "Notes", "无")
|
||||
|
||||
self.Data_LastProxyDate = ConfigItem(
|
||||
"Data", "LastProxyDate", "2000-01-01", DateTimeValidator("%Y-%m-%d")
|
||||
)
|
||||
self.Data_ProxyTimes = ConfigItem(
|
||||
"Data", "ProxyTimes", 0, RangeValidator(0, 9999)
|
||||
)
|
||||
|
||||
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_CustomWebhooks = MultipleConfig([Webhook])
|
||||
|
||||
|
||||
class GeneralConfig(ConfigBase):
|
||||
"""通用配置"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
|
||||
self.Info_Name = ConfigItem("Info", "Name", "新通用脚本")
|
||||
self.Info_RootPath = ConfigItem(
|
||||
"Info", "RootPath", str(Path.cwd()), FileValidator()
|
||||
)
|
||||
|
||||
self.Script_ScriptPath = ConfigItem(
|
||||
"Script", "ScriptPath", str(Path.cwd()), FileValidator()
|
||||
)
|
||||
self.Script_Arguments = ConfigItem("Script", "Arguments", "")
|
||||
self.Script_IfTrackProcess = ConfigItem(
|
||||
"Script", "IfTrackProcess", False, BoolValidator()
|
||||
)
|
||||
self.Script_ConfigPath = ConfigItem(
|
||||
"Script", "ConfigPath", str(Path.cwd()), FileValidator()
|
||||
)
|
||||
self.Script_ConfigPathMode = ConfigItem(
|
||||
"Script", "ConfigPathMode", "File", OptionsValidator(["File", "Folder"])
|
||||
)
|
||||
self.Script_UpdateConfigMode = ConfigItem(
|
||||
"Script",
|
||||
"UpdateConfigMode",
|
||||
"Never",
|
||||
OptionsValidator(["Never", "Success", "Failure", "Always"]),
|
||||
)
|
||||
self.Script_LogPath = ConfigItem(
|
||||
"Script", "LogPath", str(Path.cwd()), FileValidator()
|
||||
)
|
||||
self.Script_LogPathFormat = ConfigItem("Script", "LogPathFormat", "%Y-%m-%d")
|
||||
self.Script_LogTimeStart = ConfigItem(
|
||||
"Script", "LogTimeStart", 1, RangeValidator(1, 9999)
|
||||
)
|
||||
self.Script_LogTimeEnd = ConfigItem(
|
||||
"Script", "LogTimeEnd", 1, RangeValidator(1, 9999)
|
||||
)
|
||||
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_Type = ConfigItem(
|
||||
"Game", "Type", "Emulator", OptionsValidator(["Emulator", "Client"])
|
||||
)
|
||||
self.Game_Path = ConfigItem("Game", "Path", str(Path.cwd()), FileValidator())
|
||||
self.Game_Arguments = ConfigItem("Game", "Arguments", "")
|
||||
self.Game_WaitTime = ConfigItem("Game", "WaitTime", 0, RangeValidator(0, 9999))
|
||||
self.Game_IfForceClose = ConfigItem(
|
||||
"Game", "IfForceClose", False, BoolValidator()
|
||||
)
|
||||
|
||||
self.Run_ProxyTimesLimit = ConfigItem(
|
||||
"Run", "ProxyTimesLimit", 0, RangeValidator(0, 9999)
|
||||
)
|
||||
self.Run_RunTimesLimit = ConfigItem(
|
||||
"Run", "RunTimesLimit", 3, RangeValidator(1, 9999)
|
||||
)
|
||||
self.Run_RunTimeLimit = ConfigItem(
|
||||
"Run", "RunTimeLimit", 10, RangeValidator(1, 9999)
|
||||
)
|
||||
|
||||
self.UserData = MultipleConfig([GeneralUserConfig])
|
||||
|
||||
|
||||
CLASS_BOOK = {"MAA": MaaConfig, "MaaPlan": MaaPlanConfig, "General": GeneralConfig}
|
||||
"""配置类映射表"""
|
||||
@@ -75,6 +75,30 @@ class GetStageIn(BaseModel):
|
||||
)
|
||||
|
||||
|
||||
class WebhookIndexItem(BaseModel):
|
||||
uid: str = Field(..., description="唯一标识符")
|
||||
type: Literal["Webhook"] = Field(..., description="配置类型")
|
||||
|
||||
|
||||
class Webhook_Info(BaseModel):
|
||||
Name: Optional[str] = Field(default=None, description="Webhook名称")
|
||||
Enabled: Optional[bool] = Field(default=None, description="是否启用")
|
||||
|
||||
|
||||
class Webhook_Data(BaseModel):
|
||||
url: Optional[str] = Field(default=None, description="Webhook URL")
|
||||
template: Optional[str] = Field(default=None, description="消息模板")
|
||||
headers: Optional[Dict[str, str]] = Field(default=None, description="自定义请求头")
|
||||
method: Optional[Literal["POST", "GET"]] = Field(
|
||||
default=None, description="请求方法"
|
||||
)
|
||||
|
||||
|
||||
class Webhook(BaseModel):
|
||||
Info: Optional[Webhook_Info] = Field(default=None, description="Webhook基础信息")
|
||||
Data: Optional[Webhook_Data] = Field(default=None, description="Webhook配置数据")
|
||||
|
||||
|
||||
class GlobalConfig_Function(BaseModel):
|
||||
HistoryRetentionTime: Optional[Literal[7, 15, 30, 60, 90, 180, 365, 0]] = Field(
|
||||
None, description="历史记录保留时间, 0表示永久保存"
|
||||
@@ -111,18 +135,6 @@ class GlobalConfig_UI(BaseModel):
|
||||
IfToTray: Optional[bool] = Field(default=None, description="是否最小化到托盘")
|
||||
|
||||
|
||||
class CustomWebhook(BaseModel):
|
||||
id: str = Field(..., description="Webhook唯一标识")
|
||||
name: str = Field(..., description="Webhook名称")
|
||||
url: str = Field(..., description="Webhook URL")
|
||||
template: str = Field(..., description="消息模板")
|
||||
enabled: bool = Field(default=True, description="是否启用")
|
||||
headers: Optional[Dict[str, str]] = Field(default=None, description="自定义请求头")
|
||||
method: Optional[Literal["POST", "GET"]] = Field(
|
||||
default="POST", description="请求方法"
|
||||
)
|
||||
|
||||
|
||||
class GlobalConfig_Notify(BaseModel):
|
||||
SendTaskResultTime: Optional[Literal["不推送", "任何时刻", "仅失败时"]] = Field(
|
||||
default=None, description="任务结果推送时机"
|
||||
@@ -143,9 +155,6 @@ class GlobalConfig_Notify(BaseModel):
|
||||
default=None, description="是否使用ServerChan推送"
|
||||
)
|
||||
ServerChanKey: Optional[str] = Field(default=None, description="ServerChan推送密钥")
|
||||
CustomWebhooks: Optional[List[CustomWebhook]] = Field(
|
||||
default=None, description="自定义Webhook列表"
|
||||
)
|
||||
|
||||
|
||||
class GlobalConfig_Update(BaseModel):
|
||||
@@ -313,9 +322,6 @@ class MaaUserConfig_Notify(BaseModel):
|
||||
default=None, description="是否使用Server酱推送"
|
||||
)
|
||||
ServerChanKey: Optional[str] = Field(default=None, description="ServerChanKey")
|
||||
CustomWebhooks: Optional[List[CustomWebhook]] = Field(
|
||||
default=None, description="用户自定义Webhook列表"
|
||||
)
|
||||
|
||||
|
||||
class GeneralUserConfig_Notify(BaseModel):
|
||||
@@ -618,6 +624,46 @@ class UserSetIn(UserInBase):
|
||||
jsonFile: str = Field(..., description="JSON文件路径, 用于导入自定义基建文件")
|
||||
|
||||
|
||||
class WebhookInBase(BaseModel):
|
||||
scriptId: Optional[str] = Field(
|
||||
default=None, description="所属脚本ID, 获取全局设置的Webhook数据时无需携带"
|
||||
)
|
||||
userId: Optional[str] = Field(
|
||||
default=None, description="所属用户ID, 获取全局设置的Webhook数据时无需携带"
|
||||
)
|
||||
|
||||
|
||||
class WebhookGetIn(WebhookInBase):
|
||||
webhookId: Optional[str] = Field(
|
||||
default=None, description="Webhook ID, 未携带时表示获取所有Webhook数据"
|
||||
)
|
||||
|
||||
|
||||
class WebhookGetOut(OutBase):
|
||||
index: List[WebhookIndexItem] = Field(..., description="Webhook索引列表")
|
||||
data: Dict[str, Webhook] = Field(
|
||||
..., description="Webhook数据字典, key来自于index列表的uid"
|
||||
)
|
||||
|
||||
|
||||
class WebhookCreateOut(OutBase):
|
||||
webhookId: str = Field(..., description="新创建的Webhook ID")
|
||||
data: Webhook = Field(..., description="Webhook配置数据")
|
||||
|
||||
|
||||
class WebhookUpdateIn(WebhookInBase):
|
||||
webhookId: str = Field(..., description="Webhook ID")
|
||||
data: Webhook = Field(..., description="Webhook更新数据")
|
||||
|
||||
|
||||
class WebhookDeleteIn(WebhookInBase):
|
||||
webhookId: str = Field(..., description="Webhook ID")
|
||||
|
||||
|
||||
class WebhookReorderIn(WebhookInBase):
|
||||
indexList: List[str] = Field(..., description="Webhook ID列表, 按新顺序排列")
|
||||
|
||||
|
||||
class PlanCreateIn(BaseModel):
|
||||
type: Literal["MaaPlan"]
|
||||
|
||||
|
||||
@@ -21,17 +21,20 @@
|
||||
|
||||
|
||||
import re
|
||||
import json
|
||||
import smtplib
|
||||
import requests
|
||||
from datetime import datetime
|
||||
from plyer import notification
|
||||
from email.header import Header
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from email.mime.text import MIMEText
|
||||
from email.utils import formataddr
|
||||
from pathlib import Path
|
||||
|
||||
from plyer import notification
|
||||
from typing import Literal
|
||||
|
||||
from app.core import Config
|
||||
from app.models.config import Webhook
|
||||
from app.utils import get_logger, ImageUtils
|
||||
|
||||
logger = get_logger("通知服务")
|
||||
@@ -39,71 +42,76 @@ logger = get_logger("通知服务")
|
||||
|
||||
class Notification:
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
async def push_plyer(self, title, message, ticker, t) -> bool:
|
||||
async def push_plyer(self, title: str, message: str, ticker: str, t: int) -> None:
|
||||
"""
|
||||
推送系统通知
|
||||
|
||||
:param title: 通知标题
|
||||
:param message: 通知内容
|
||||
:param ticker: 通知横幅
|
||||
:param t: 通知持续时间
|
||||
:return: bool
|
||||
Parameters
|
||||
----------
|
||||
title: str
|
||||
通知标题
|
||||
message: str
|
||||
通知内容
|
||||
ticker: str
|
||||
通知横幅
|
||||
t: int
|
||||
通知持续时间
|
||||
"""
|
||||
|
||||
if Config.get("Notify", "IfPushPlyer"):
|
||||
if not Config.get("Notify", "IfPushPlyer"):
|
||||
return
|
||||
|
||||
logger.info(f"推送系统通知: {title}")
|
||||
logger.info(f"推送系统通知: {title}")
|
||||
|
||||
if notification.notify is not None:
|
||||
notification.notify(
|
||||
title=title,
|
||||
message=message,
|
||||
app_name="AUTO-MAS",
|
||||
app_icon=(Path.cwd() / "res/icons/AUTO-MAS.ico").as_posix(),
|
||||
timeout=t,
|
||||
ticker=ticker,
|
||||
toast=True,
|
||||
)
|
||||
else:
|
||||
logger.error("plyer.notification 未正确导入, 无法推送系统通知")
|
||||
if notification.notify is not None:
|
||||
notification.notify(
|
||||
title=title,
|
||||
message=message,
|
||||
app_name="AUTO-MAS",
|
||||
app_icon=(Path.cwd() / "res/icons/AUTO-MAS.ico").as_posix(),
|
||||
timeout=t,
|
||||
ticker=ticker,
|
||||
toast=True,
|
||||
)
|
||||
else:
|
||||
logger.error("plyer.notification 未正确导入, 无法推送系统通知")
|
||||
|
||||
return True
|
||||
|
||||
async def send_mail(self, mode, title, content, to_address) -> None:
|
||||
async def send_mail(
|
||||
self, mode: Literal["文本", "网页"], title: str, content: str, to_address: str
|
||||
) -> None:
|
||||
"""
|
||||
推送邮件通知
|
||||
|
||||
:param mode: 邮件内容模式, 支持 "文本" 和 "网页"
|
||||
:param title: 邮件标题
|
||||
:param content: 邮件内容
|
||||
:param to_address: 收件人地址
|
||||
Parameters
|
||||
----------
|
||||
mode: Literal["文本", "网页"]
|
||||
邮件内容模式, 支持 "文本" 和 "网页"
|
||||
title: str
|
||||
邮件标题
|
||||
content: str
|
||||
邮件内容
|
||||
to_address: str
|
||||
收件人地址
|
||||
"""
|
||||
|
||||
if (
|
||||
Config.get("Notify", "SMTPServerAddress") == ""
|
||||
or Config.get("Notify", "AuthorizationCode") == ""
|
||||
or not bool(
|
||||
re.match(
|
||||
r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$",
|
||||
Config.get("Notify", "FromAddress"),
|
||||
)
|
||||
)
|
||||
or not bool(
|
||||
re.match(
|
||||
r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$",
|
||||
to_address,
|
||||
)
|
||||
if Config.get("Notify", "SMTPServerAddress") == "":
|
||||
raise ValueError("邮件通知的SMTP服务器地址不能为空")
|
||||
if Config.get("Notify", "AuthorizationCode") == "":
|
||||
raise ValueError("邮件通知的授权码不能为空")
|
||||
if not bool(
|
||||
re.match(
|
||||
r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$",
|
||||
Config.get("Notify", "FromAddress"),
|
||||
)
|
||||
):
|
||||
logger.error(
|
||||
"请正确设置邮件通知的SMTP服务器地址、授权码、发件人地址和收件人地址"
|
||||
)
|
||||
raise ValueError(
|
||||
"邮件通知的SMTP服务器地址、授权码、发件人地址或收件人地址未正确配置"
|
||||
raise ValueError("邮件通知的发送邮箱格式错误或为空")
|
||||
if not bool(
|
||||
re.match(
|
||||
r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$",
|
||||
to_address,
|
||||
)
|
||||
):
|
||||
raise ValueError("邮件通知的接收邮箱格式错误或为空")
|
||||
|
||||
# 定义邮件正文
|
||||
if mode == "文本":
|
||||
@@ -135,16 +143,21 @@ class Notification:
|
||||
smtpObj.quit()
|
||||
logger.success(f"邮件发送成功: {title}")
|
||||
|
||||
async def ServerChanPush(self, title, content, send_key) -> None:
|
||||
async def ServerChanPush(self, title: str, content: str, send_key: str) -> None:
|
||||
"""
|
||||
使用Server酱推送通知
|
||||
|
||||
:param title: 通知标题
|
||||
:param content: 通知内容
|
||||
:param send_key: Server酱的SendKey
|
||||
Parameters
|
||||
----------
|
||||
title: str
|
||||
通知标题
|
||||
content: str
|
||||
通知内容
|
||||
send_key: str
|
||||
Server酱的SendKey
|
||||
"""
|
||||
|
||||
if not send_key:
|
||||
if send_key == "":
|
||||
raise ValueError("ServerChan SendKey 不能为空")
|
||||
|
||||
# 构造 URL
|
||||
@@ -171,33 +184,33 @@ class Notification:
|
||||
else:
|
||||
raise Exception(f"ServerChan 推送通知失败: {response.text}")
|
||||
|
||||
async def CustomWebhookPush(self, title, content, webhook_config) -> None:
|
||||
async def WebhookPush(self, title: str, content: str, webhook: Webhook) -> None:
|
||||
"""
|
||||
自定义 Webhook 推送通知
|
||||
Webhook 推送通知
|
||||
|
||||
:param title: 通知标题
|
||||
:param content: 通知内容
|
||||
:param webhook_config: Webhook配置对象
|
||||
Parameters
|
||||
----------
|
||||
title: str
|
||||
通知标题
|
||||
content: str
|
||||
通知内容
|
||||
webhook: Webhook
|
||||
Webhook配置对象
|
||||
"""
|
||||
|
||||
if not webhook_config.get("url"):
|
||||
raise ValueError("Webhook URL 不能为空")
|
||||
|
||||
if not webhook_config.get("enabled", True):
|
||||
logger.info(
|
||||
f"Webhook {webhook_config.get('name', 'Unknown')} 已禁用,跳过推送"
|
||||
)
|
||||
if not webhook.get("Info", "Enabled"):
|
||||
return
|
||||
|
||||
if webhook.get("Data", "Url") == "":
|
||||
raise ValueError("Webhook URL 不能为空")
|
||||
|
||||
# 解析模板
|
||||
template = webhook_config.get(
|
||||
"template", '{"title": "{title}", "content": "{content}"}'
|
||||
template = (
|
||||
webhook.get("Data", "Template")
|
||||
or '{"title": "{title}", "content": "{content}"}'
|
||||
)
|
||||
|
||||
# 替换模板变量
|
||||
try:
|
||||
import json
|
||||
from datetime import datetime
|
||||
|
||||
# 准备模板变量
|
||||
template_vars = {
|
||||
@@ -264,56 +277,55 @@ class Notification:
|
||||
|
||||
# 准备请求头
|
||||
headers = {"Content-Type": "application/json"}
|
||||
if webhook_config.get("headers"):
|
||||
headers.update(webhook_config["headers"])
|
||||
headers.update(json.loads(webhook.get("Data", "Headers")))
|
||||
|
||||
# 发送请求
|
||||
method = webhook_config.get("method", "POST").upper()
|
||||
|
||||
try:
|
||||
if method == "POST":
|
||||
if isinstance(data, dict):
|
||||
response = requests.post(
|
||||
url=webhook_config["url"],
|
||||
json=data,
|
||||
headers=headers,
|
||||
timeout=10,
|
||||
proxies=Config.get_proxies(),
|
||||
)
|
||||
else:
|
||||
response = requests.post(
|
||||
url=webhook_config["url"],
|
||||
data=data,
|
||||
headers=headers,
|
||||
timeout=10,
|
||||
proxies=Config.get_proxies(),
|
||||
)
|
||||
else: # GET
|
||||
params = data if isinstance(data, dict) else {"message": data}
|
||||
response = requests.get(
|
||||
url=webhook_config["url"],
|
||||
params=params,
|
||||
if webhook.get("Data", "Method") == "POST":
|
||||
if isinstance(data, dict):
|
||||
response = requests.post(
|
||||
url=webhook.get("Data", "Url"),
|
||||
json=data,
|
||||
headers=headers,
|
||||
timeout=10,
|
||||
proxies=Config.get_proxies(),
|
||||
)
|
||||
|
||||
# 检查响应
|
||||
if response.status_code == 200:
|
||||
logger.success(
|
||||
f"自定义Webhook推送成功: {webhook_config.get('name', 'Unknown')} - {title}"
|
||||
elif isinstance(data, str):
|
||||
response = requests.post(
|
||||
url=webhook.get("Data", "Url"),
|
||||
data=data,
|
||||
headers=headers,
|
||||
timeout=10,
|
||||
proxies=Config.get_proxies(),
|
||||
)
|
||||
elif webhook.get("Data", "Method") == "GET":
|
||||
if isinstance(data, dict):
|
||||
# Flatten params to ensure all values are str or list of str
|
||||
params = {}
|
||||
for k, v in data.items():
|
||||
if isinstance(v, (dict, list)):
|
||||
params[k] = json.dumps(v, ensure_ascii=False)
|
||||
else:
|
||||
params[k] = str(v)
|
||||
else:
|
||||
raise Exception(f"HTTP {response.status_code}: {response.text}")
|
||||
|
||||
except Exception as e:
|
||||
raise Exception(
|
||||
f"自定义Webhook推送失败 ({webhook_config.get('name', 'Unknown')}): {str(e)}"
|
||||
params = {"message": str(data)}
|
||||
response = requests.get(
|
||||
url=webhook.get("Data", "Url"),
|
||||
params=params,
|
||||
headers=headers,
|
||||
timeout=10,
|
||||
proxies=Config.get_proxies(),
|
||||
)
|
||||
|
||||
async def WebHookPush(self, title, content, webhook_url) -> None:
|
||||
# 检查响应
|
||||
if response.status_code == 200:
|
||||
logger.success(
|
||||
f"自定义Webhook推送成功: {webhook.get('Info', 'Name')} - {title}"
|
||||
)
|
||||
else:
|
||||
raise Exception(f"HTTP {response.status_code}: {response.text}")
|
||||
|
||||
async def _WebHookPush(self, title, content, webhook_url) -> None:
|
||||
"""
|
||||
WebHook 推送通知 (兼容旧版企业微信格式)
|
||||
WebHook 推送通知 (即将弃用)
|
||||
|
||||
:param title: 通知标题
|
||||
:param content: 通知内容
|
||||
@@ -340,7 +352,7 @@ class Notification:
|
||||
self, image_path: Path, webhook_url: str
|
||||
) -> None:
|
||||
"""
|
||||
使用企业微信群机器人推送图片通知
|
||||
使用企业微信群机器人推送图片通知(等待重新适配)
|
||||
|
||||
:param image_path: 图片文件路径
|
||||
:param webhook_url: 企业微信群机器人的WebHook地址
|
||||
@@ -406,23 +418,12 @@ class Notification:
|
||||
)
|
||||
|
||||
# 发送自定义Webhook通知
|
||||
try:
|
||||
custom_webhooks = Config.get("Notify", "CustomWebhooks")
|
||||
except AttributeError:
|
||||
custom_webhooks = []
|
||||
if custom_webhooks:
|
||||
for webhook in custom_webhooks:
|
||||
if webhook.get("enabled", True):
|
||||
try:
|
||||
await self.CustomWebhookPush(
|
||||
"AUTO-MAS测试通知",
|
||||
"这是 AUTO-MAS 外部通知测试信息。如果你看到了这段内容, 说明 AUTO-MAS 的通知功能已经正确配置且可以正常工作!",
|
||||
webhook,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"自定义Webhook测试失败 ({webhook.get('name', 'Unknown')}): {e}"
|
||||
)
|
||||
for webhook in Config.Notify_CustomWebhooks.values():
|
||||
await self.WebhookPush(
|
||||
"AUTO-MAS测试通知",
|
||||
"这是 AUTO-MAS 外部通知测试信息。如果你看到了这段内容, 说明 AUTO-MAS 的通知功能已经正确配置且可以正常工作!",
|
||||
webhook,
|
||||
)
|
||||
|
||||
logger.success("测试通知发送完成")
|
||||
|
||||
|
||||
1308
app/task/MAA.py
1308
app/task/MAA.py
File diff suppressed because it is too large
Load Diff
@@ -27,10 +27,9 @@ import shutil
|
||||
import asyncio
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from fastapi import WebSocket
|
||||
from datetime import datetime, timedelta
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
from typing import Union, List, Dict, Optional
|
||||
from typing import List, Dict, Optional
|
||||
|
||||
|
||||
from app.core import Config, GeneralConfig, GeneralUserConfig
|
||||
@@ -44,7 +43,7 @@ logger = get_logger("通用调度器")
|
||||
|
||||
|
||||
class GeneralManager:
|
||||
"""通用脚本通用控制器"""
|
||||
"""通用脚本调度器"""
|
||||
|
||||
def __init__(
|
||||
self, mode: str, script_id: uuid.UUID, user_id: Optional[uuid.UUID], ws_id: str
|
||||
@@ -69,9 +68,8 @@ class GeneralManager:
|
||||
await Config.ScriptConfig[self.script_id].lock()
|
||||
|
||||
self.script_config = Config.ScriptConfig[self.script_id]
|
||||
if isinstance(self.script_config, GeneralConfig):
|
||||
self.user_config = MultipleConfig([GeneralUserConfig])
|
||||
await self.user_config.load(await self.script_config.UserData.toDict())
|
||||
self.user_config = MultipleConfig([GeneralUserConfig])
|
||||
await self.user_config.load(await self.script_config.UserData.toDict())
|
||||
|
||||
self.script_root_path = Path(self.script_config.get("Info", "RootPath"))
|
||||
self.script_path = Path(self.script_config.get("Script", "ScriptPath"))
|
||||
@@ -143,6 +141,9 @@ class GeneralManager:
|
||||
def check_config(self) -> str:
|
||||
"""检查配置是否可用"""
|
||||
|
||||
if not isinstance(Config.ScriptConfig[self.script_id], GeneralConfig):
|
||||
return "脚本配置类型错误, 不是通用脚本类型"
|
||||
|
||||
if self.mode == "人工排查":
|
||||
return "通用脚本不支持人工排查模式"
|
||||
if self.mode == "设置脚本" and self.user_id is None:
|
||||
@@ -221,298 +222,319 @@ class GeneralManager:
|
||||
# 开始代理
|
||||
for self.index, user in enumerate(self.user_list):
|
||||
|
||||
self.cur_user_data = self.user_config[uuid.UUID(user["user_id"])]
|
||||
try:
|
||||
|
||||
if (self.script_config.get("Run", "ProxyTimesLimit") == 0) or (
|
||||
self.cur_user_data.get("Data", "ProxyTimes")
|
||||
< self.script_config.get("Run", "ProxyTimesLimit")
|
||||
):
|
||||
user["status"] = "运行"
|
||||
await Config.send_json(
|
||||
WebSocketMessage(
|
||||
id=self.ws_id,
|
||||
type="Update",
|
||||
data={"user_list": self.user_list},
|
||||
).model_dump()
|
||||
)
|
||||
else:
|
||||
user["status"] = "跳过"
|
||||
await Config.send_json(
|
||||
WebSocketMessage(
|
||||
id=self.ws_id,
|
||||
type="Update",
|
||||
data={"user_list": self.user_list},
|
||||
).model_dump()
|
||||
)
|
||||
continue
|
||||
self.cur_user_data = self.user_config[uuid.UUID(user["user_id"])]
|
||||
|
||||
logger.info(f"开始代理用户: {user['user_id']}")
|
||||
if (self.script_config.get("Run", "ProxyTimesLimit") == 0) or (
|
||||
self.cur_user_data.get("Data", "ProxyTimes")
|
||||
< self.script_config.get("Run", "ProxyTimesLimit")
|
||||
):
|
||||
user["status"] = "运行"
|
||||
await Config.send_json(
|
||||
WebSocketMessage(
|
||||
id=self.ws_id,
|
||||
type="Update",
|
||||
data={"user_list": self.user_list},
|
||||
).model_dump()
|
||||
)
|
||||
else:
|
||||
user["status"] = "跳过"
|
||||
await Config.send_json(
|
||||
WebSocketMessage(
|
||||
id=self.ws_id,
|
||||
type="Update",
|
||||
data={"user_list": self.user_list},
|
||||
).model_dump()
|
||||
)
|
||||
continue
|
||||
|
||||
self.user_start_time = datetime.now()
|
||||
logger.info(f"开始代理用户: {user['user_id']}")
|
||||
|
||||
self.run_book = False
|
||||
self.user_start_time = datetime.now()
|
||||
|
||||
if not (
|
||||
Path.cwd() / f"data/{self.script_id}/{user['user_id']}/ConfigFile"
|
||||
).exists():
|
||||
self.run_book = False
|
||||
|
||||
logger.error(f"用户: {user['user_id']} - 未找到配置文件")
|
||||
if not (
|
||||
Path.cwd()
|
||||
/ f"data/{self.script_id}/{user['user_id']}/ConfigFile"
|
||||
).exists():
|
||||
|
||||
logger.error(f"用户: {user['user_id']} - 未找到配置文件")
|
||||
await Config.send_json(
|
||||
WebSocketMessage(
|
||||
id=self.ws_id,
|
||||
type="Info",
|
||||
data={"Error": f"未找到 {user['user_id']} 的配置文件"},
|
||||
).model_dump()
|
||||
)
|
||||
self.run_book = False
|
||||
continue
|
||||
|
||||
# 尝试次数循环
|
||||
for i in range(self.script_config.get("Run", "RunTimesLimit")):
|
||||
|
||||
if self.run_book:
|
||||
break
|
||||
|
||||
logger.info(
|
||||
f"用户 {user['user_id']} - 尝试次数: {i + 1}/{self.script_config.get('Run','RunTimesLimit')}"
|
||||
)
|
||||
|
||||
# 配置脚本
|
||||
await self.set_general()
|
||||
# 记录当前时间
|
||||
self.log_start_time = datetime.now()
|
||||
|
||||
# 执行任务前脚本
|
||||
if (
|
||||
self.cur_user_data.get("Info", "IfScriptBeforeTask")
|
||||
and Path(
|
||||
self.cur_user_data.get("Info", "ScriptBeforeTask")
|
||||
).exists()
|
||||
):
|
||||
await self.execute_script_task(
|
||||
Path(
|
||||
self.cur_user_data.get("Info", "ScriptBeforeTask")
|
||||
),
|
||||
"脚本前任务",
|
||||
)
|
||||
|
||||
# 启动游戏/模拟器
|
||||
if self.script_config.get("Game", "Enabled"):
|
||||
|
||||
try:
|
||||
logger.info(
|
||||
f"启动游戏/模拟器: {self.game_path}, 参数: {self.script_config.get('Game','Arguments')}"
|
||||
)
|
||||
await self.game_process_manager.open_process(
|
||||
self.game_path,
|
||||
str(
|
||||
self.script_config.get("Game", "Arguments")
|
||||
).split(" "),
|
||||
0,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.exception(f"启动游戏/模拟器时出现异常: {e}")
|
||||
await Config.send_json(
|
||||
WebSocketMessage(
|
||||
id=self.ws_id,
|
||||
type="Info",
|
||||
data={
|
||||
"Error": f"启动游戏/模拟器时出现异常: {e}"
|
||||
},
|
||||
).model_dump()
|
||||
)
|
||||
self.general_result = "游戏/模拟器启动失败"
|
||||
break
|
||||
|
||||
# 更新静默进程标记有效时间
|
||||
if self.script_config.get("Game", "Type") == "Emulator":
|
||||
logger.info(
|
||||
f"更新静默进程标记: {self.game_path}, 标记有效时间: {datetime.now() + timedelta(seconds=self.script_config.get('Game', 'WaitTime') + 10)}"
|
||||
)
|
||||
Config.silence_dict[
|
||||
self.game_path
|
||||
] = datetime.now() + timedelta(
|
||||
seconds=self.script_config.get("Game", "WaitTime")
|
||||
+ 10
|
||||
)
|
||||
|
||||
await Config.send_json(
|
||||
WebSocketMessage(
|
||||
id=self.ws_id,
|
||||
type="Update",
|
||||
data={
|
||||
"log": f"正在等待游戏/模拟器完成启动\n请等待{self.script_config.get('Game', 'WaitTime')}s"
|
||||
},
|
||||
).model_dump()
|
||||
)
|
||||
await asyncio.sleep(
|
||||
self.script_config.get("Game", "WaitTime")
|
||||
)
|
||||
|
||||
# 运行脚本任务
|
||||
logger.info(
|
||||
f"运行脚本任务: {self.script_exe_path}, 参数: {self.script_arguments}"
|
||||
)
|
||||
await self.general_process_manager.open_process(
|
||||
self.script_exe_path,
|
||||
self.script_arguments,
|
||||
tracking_time=(
|
||||
60
|
||||
if self.script_config.get("Script", "IfTrackProcess")
|
||||
else 0
|
||||
),
|
||||
)
|
||||
|
||||
# 监测运行状态
|
||||
await self.general_log_monitor.start(
|
||||
self.script_log_path, self.log_start_time
|
||||
)
|
||||
self.wait_event.clear()
|
||||
await self.wait_event.wait()
|
||||
|
||||
await self.general_log_monitor.stop()
|
||||
|
||||
# 处理通用脚本结果
|
||||
if self.general_result == "Success!":
|
||||
|
||||
# 标记任务完成
|
||||
self.run_book = True
|
||||
|
||||
logger.info(
|
||||
f"用户: {user['user_id']} - 通用脚本进程完成代理任务"
|
||||
)
|
||||
await Config.send_json(
|
||||
WebSocketMessage(
|
||||
id=self.ws_id,
|
||||
type="Update",
|
||||
data={
|
||||
"log": "检测到通用脚本进程完成代理任务\n正在等待相关程序结束\n请等待10s"
|
||||
},
|
||||
).model_dump()
|
||||
)
|
||||
|
||||
# 中止相关程序
|
||||
logger.info(f"中止相关程序: {self.script_exe_path}")
|
||||
await self.general_process_manager.kill()
|
||||
await System.kill_process(self.script_exe_path)
|
||||
if self.script_config.get("Game", "Enabled"):
|
||||
logger.info(
|
||||
f"中止游戏/模拟器进程: {list(self.game_process_manager.tracked_pids)}"
|
||||
)
|
||||
await self.game_process_manager.kill()
|
||||
if self.script_config.get("Game", "IfForceClose"):
|
||||
await System.kill_process(self.game_path)
|
||||
|
||||
await asyncio.sleep(10)
|
||||
|
||||
# 更新脚本配置文件
|
||||
if self.script_config.get("Script", "UpdateConfigMode") in [
|
||||
"Success",
|
||||
"Always",
|
||||
]:
|
||||
|
||||
if (
|
||||
self.script_config.get("Script", "ConfigPathMode")
|
||||
== "Folder"
|
||||
):
|
||||
shutil.copytree(
|
||||
self.script_config_path,
|
||||
Path.cwd()
|
||||
/ f"data/{self.script_id}/{user['user_id']}/ConfigFile",
|
||||
dirs_exist_ok=True,
|
||||
)
|
||||
elif (
|
||||
self.script_config.get("Script", "ConfigPathMode")
|
||||
== "File"
|
||||
):
|
||||
shutil.copy(
|
||||
self.script_config_path,
|
||||
Path.cwd()
|
||||
/ f"data/{self.script_id}/{user['user_id']}/ConfigFile"
|
||||
/ self.script_config_path.name,
|
||||
)
|
||||
logger.success("通用脚本配置文件已更新")
|
||||
|
||||
else:
|
||||
logger.error(
|
||||
f"配置: {user['user_id']} - 代理任务异常: {self.general_result}"
|
||||
)
|
||||
# 打印中止信息
|
||||
# 此时, log变量内存储的就是出现异常的日志信息, 可以保存或发送用于问题排查
|
||||
await Config.send_json(
|
||||
WebSocketMessage(
|
||||
id=self.ws_id,
|
||||
type="Update",
|
||||
data={
|
||||
"log": f"{self.general_result}\n正在中止相关程序\n请等待10s"
|
||||
},
|
||||
).model_dump()
|
||||
)
|
||||
|
||||
# 中止相关程序
|
||||
logger.info(f"中止相关程序: {self.script_exe_path}")
|
||||
await self.general_process_manager.kill()
|
||||
await System.kill_process(self.script_exe_path)
|
||||
if self.script_config.get("Game", "Enabled"):
|
||||
logger.info(
|
||||
f"中止游戏/模拟器进程: {list(self.game_process_manager.tracked_pids)}"
|
||||
)
|
||||
await self.game_process_manager.kill()
|
||||
if self.script_config.get("Game", "IfForceClose"):
|
||||
await System.kill_process(self.game_path)
|
||||
|
||||
# 推送异常通知
|
||||
await Notify.push_plyer(
|
||||
"用户自动代理出现异常!",
|
||||
f"用户 {user['name']} 的自动代理出现一次异常",
|
||||
f"{user['name']} 的自动代理出现异常",
|
||||
3,
|
||||
)
|
||||
|
||||
await asyncio.sleep(10)
|
||||
|
||||
# 更新脚本配置文件
|
||||
if self.script_config.get("Script", "UpdateConfigMode") in [
|
||||
"Failure",
|
||||
"Always",
|
||||
]:
|
||||
|
||||
if (
|
||||
self.script_config.get("Script", "ConfigPathMode")
|
||||
== "Folder"
|
||||
):
|
||||
shutil.copytree(
|
||||
self.script_config_path,
|
||||
Path.cwd()
|
||||
/ f"data/{self.script_id}/{user['user_id']}/ConfigFile",
|
||||
dirs_exist_ok=True,
|
||||
)
|
||||
elif (
|
||||
self.script_config.get("Script", "ConfigPathMode")
|
||||
== "File"
|
||||
):
|
||||
shutil.copy(
|
||||
self.script_config_path,
|
||||
Path.cwd()
|
||||
/ f"data/{self.script_id}/{user['user_id']}/ConfigFile"
|
||||
/ self.script_config_path.name,
|
||||
)
|
||||
logger.success("通用脚本配置文件已更新")
|
||||
|
||||
# 执行任务后脚本
|
||||
if (
|
||||
self.cur_user_data.get("Info", "IfScriptAfterTask")
|
||||
and Path(
|
||||
self.cur_user_data.get("Info", "ScriptAfterTask")
|
||||
).exists()
|
||||
):
|
||||
await self.execute_script_task(
|
||||
Path(self.cur_user_data.get("Info", "ScriptAfterTask")),
|
||||
"脚本后任务",
|
||||
)
|
||||
|
||||
# 保存运行日志以及统计信息
|
||||
await Config.save_general_log(
|
||||
Path.cwd()
|
||||
/ f"history/{self.curdate}/{user['name']}/{self.log_start_time.strftime('%H-%M-%S')}.log",
|
||||
self.general_logs,
|
||||
self.general_result,
|
||||
)
|
||||
|
||||
await self.result_record()
|
||||
|
||||
except Exception as e:
|
||||
logger.exception(f"用户 {user['user_id']} 代理时出现异常: {e}")
|
||||
user["status"] = "异常"
|
||||
await Config.send_json(
|
||||
WebSocketMessage(
|
||||
id=self.ws_id,
|
||||
type="Info",
|
||||
data={"Error": f"未找到 {user['user_id']} 的配置文件"},
|
||||
data={"Error": f"代理用户 {user['name']} 时出现异常: {e}"},
|
||||
).model_dump()
|
||||
)
|
||||
self.run_book = False
|
||||
continue
|
||||
|
||||
# 尝试次数循环
|
||||
for i in range(self.script_config.get("Run", "RunTimesLimit")):
|
||||
|
||||
if self.run_book:
|
||||
break
|
||||
|
||||
logger.info(
|
||||
f"用户 {user['user_id']} - 尝试次数: {i + 1}/{self.script_config.get('Run','RunTimesLimit')}"
|
||||
)
|
||||
|
||||
# 配置脚本
|
||||
await self.set_general()
|
||||
# 记录当前时间
|
||||
self.log_start_time = datetime.now()
|
||||
|
||||
# 执行任务前脚本
|
||||
if (
|
||||
self.cur_user_data.get("Info", "IfScriptBeforeTask")
|
||||
and Path(
|
||||
self.cur_user_data.get("Info", "ScriptBeforeTask")
|
||||
).exists()
|
||||
):
|
||||
await self.execute_script_task(
|
||||
Path(self.cur_user_data.get("Info", "ScriptBeforeTask")),
|
||||
"脚本前任务",
|
||||
)
|
||||
|
||||
# 启动游戏/模拟器
|
||||
if self.script_config.get("Game", "Enabled"):
|
||||
|
||||
try:
|
||||
logger.info(
|
||||
f"启动游戏/模拟器: {self.game_path}, 参数: {self.script_config.get('Game','Arguments')}"
|
||||
)
|
||||
await self.game_process_manager.open_process(
|
||||
self.game_path,
|
||||
str(self.script_config.get("Game", "Arguments")).split(
|
||||
" "
|
||||
),
|
||||
0,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.exception(f"启动游戏/模拟器时出现异常: {e}")
|
||||
await Config.send_json(
|
||||
WebSocketMessage(
|
||||
id=self.ws_id,
|
||||
type="Info",
|
||||
data={"Error": f"启动游戏/模拟器时出现异常: {e}"},
|
||||
).model_dump()
|
||||
)
|
||||
self.general_result = "游戏/模拟器启动失败"
|
||||
break
|
||||
|
||||
# 更新静默进程标记有效时间
|
||||
if self.script_config.get("Game", "Type") == "Emulator":
|
||||
logger.info(
|
||||
f"更新静默进程标记: {self.game_path}, 标记有效时间: {datetime.now() + timedelta(seconds=self.script_config.get('Game', 'WaitTime') + 10)}"
|
||||
)
|
||||
Config.silence_dict[
|
||||
self.game_path
|
||||
] = datetime.now() + timedelta(
|
||||
seconds=self.script_config.get("Game", "WaitTime") + 10
|
||||
)
|
||||
|
||||
await Config.send_json(
|
||||
WebSocketMessage(
|
||||
id=self.ws_id,
|
||||
type="Update",
|
||||
data={
|
||||
"log": f"正在等待游戏/模拟器完成启动\n请等待{self.script_config.get('Game', 'WaitTime')}s"
|
||||
},
|
||||
).model_dump()
|
||||
)
|
||||
await asyncio.sleep(self.script_config.get("Game", "WaitTime"))
|
||||
|
||||
# 运行脚本任务
|
||||
logger.info(
|
||||
f"运行脚本任务: {self.script_exe_path}, 参数: {self.script_arguments}"
|
||||
)
|
||||
await self.general_process_manager.open_process(
|
||||
self.script_exe_path,
|
||||
self.script_arguments,
|
||||
tracking_time=(
|
||||
60
|
||||
if self.script_config.get("Script", "IfTrackProcess")
|
||||
else 0
|
||||
),
|
||||
)
|
||||
|
||||
# 监测运行状态
|
||||
await self.general_log_monitor.start(
|
||||
self.script_log_path, self.log_start_time
|
||||
)
|
||||
self.wait_event.clear()
|
||||
await self.wait_event.wait()
|
||||
|
||||
await self.general_log_monitor.stop()
|
||||
|
||||
# 处理通用脚本结果
|
||||
if self.general_result == "Success!":
|
||||
|
||||
# 标记任务完成
|
||||
self.run_book = True
|
||||
|
||||
logger.info(
|
||||
f"用户: {user['user_id']} - 通用脚本进程完成代理任务"
|
||||
)
|
||||
await Config.send_json(
|
||||
WebSocketMessage(
|
||||
id=self.ws_id,
|
||||
type="Update",
|
||||
data={
|
||||
"log": "检测到通用脚本进程完成代理任务\n正在等待相关程序结束\n请等待10s"
|
||||
},
|
||||
).model_dump()
|
||||
)
|
||||
|
||||
# 中止相关程序
|
||||
logger.info(f"中止相关程序: {self.script_exe_path}")
|
||||
await self.general_process_manager.kill()
|
||||
await System.kill_process(self.script_exe_path)
|
||||
if self.script_config.get("Game", "Enabled"):
|
||||
logger.info(
|
||||
f"中止游戏/模拟器进程: {list(self.game_process_manager.tracked_pids)}"
|
||||
)
|
||||
await self.game_process_manager.kill()
|
||||
if self.script_config.get("Game", "IfForceClose"):
|
||||
await System.kill_process(self.game_path)
|
||||
|
||||
await asyncio.sleep(10)
|
||||
|
||||
# 更新脚本配置文件
|
||||
if self.script_config.get("Script", "UpdateConfigMode") in [
|
||||
"Success",
|
||||
"Always",
|
||||
]:
|
||||
|
||||
if (
|
||||
self.script_config.get("Script", "ConfigPathMode")
|
||||
== "Folder"
|
||||
):
|
||||
shutil.copytree(
|
||||
self.script_config_path,
|
||||
Path.cwd()
|
||||
/ f"data/{self.script_id}/{user['user_id']}/ConfigFile",
|
||||
dirs_exist_ok=True,
|
||||
)
|
||||
elif (
|
||||
self.script_config.get("Script", "ConfigPathMode")
|
||||
== "File"
|
||||
):
|
||||
shutil.copy(
|
||||
self.script_config_path,
|
||||
Path.cwd()
|
||||
/ f"data/{self.script_id}/{user['user_id']}/ConfigFile"
|
||||
/ self.script_config_path.name,
|
||||
)
|
||||
logger.success("通用脚本配置文件已更新")
|
||||
|
||||
else:
|
||||
logger.error(
|
||||
f"配置: {user['user_id']} - 代理任务异常: {self.general_result}"
|
||||
)
|
||||
# 打印中止信息
|
||||
# 此时, log变量内存储的就是出现异常的日志信息, 可以保存或发送用于问题排查
|
||||
await Config.send_json(
|
||||
WebSocketMessage(
|
||||
id=self.ws_id,
|
||||
type="Update",
|
||||
data={
|
||||
"log": f"{self.general_result}\n正在中止相关程序\n请等待10s"
|
||||
},
|
||||
).model_dump()
|
||||
)
|
||||
|
||||
# 中止相关程序
|
||||
logger.info(f"中止相关程序: {self.script_exe_path}")
|
||||
await self.general_process_manager.kill()
|
||||
await System.kill_process(self.script_exe_path)
|
||||
if self.script_config.get("Game", "Enabled"):
|
||||
logger.info(
|
||||
f"中止游戏/模拟器进程: {list(self.game_process_manager.tracked_pids)}"
|
||||
)
|
||||
await self.game_process_manager.kill()
|
||||
if self.script_config.get("Game", "IfForceClose"):
|
||||
await System.kill_process(self.game_path)
|
||||
|
||||
# 推送异常通知
|
||||
await Notify.push_plyer(
|
||||
"用户自动代理出现异常!",
|
||||
f"用户 {user['name']} 的自动代理出现一次异常",
|
||||
f"{user['name']} 的自动代理出现异常",
|
||||
3,
|
||||
)
|
||||
|
||||
await asyncio.sleep(10)
|
||||
|
||||
# 更新脚本配置文件
|
||||
if self.script_config.get("Script", "UpdateConfigMode") in [
|
||||
"Failure",
|
||||
"Always",
|
||||
]:
|
||||
|
||||
if (
|
||||
self.script_config.get("Script", "ConfigPathMode")
|
||||
== "Folder"
|
||||
):
|
||||
shutil.copytree(
|
||||
self.script_config_path,
|
||||
Path.cwd()
|
||||
/ f"data/{self.script_id}/{user['user_id']}/ConfigFile",
|
||||
dirs_exist_ok=True,
|
||||
)
|
||||
elif (
|
||||
self.script_config.get("Script", "ConfigPathMode")
|
||||
== "File"
|
||||
):
|
||||
shutil.copy(
|
||||
self.script_config_path,
|
||||
Path.cwd()
|
||||
/ f"data/{self.script_id}/{user['user_id']}/ConfigFile"
|
||||
/ self.script_config_path.name,
|
||||
)
|
||||
logger.success("通用脚本配置文件已更新")
|
||||
|
||||
# 执行任务后脚本
|
||||
if (
|
||||
self.cur_user_data.get("Info", "IfScriptAfterTask")
|
||||
and Path(
|
||||
self.cur_user_data.get("Info", "ScriptAfterTask")
|
||||
).exists()
|
||||
):
|
||||
await self.execute_script_task(
|
||||
Path(self.cur_user_data.get("Info", "ScriptAfterTask")),
|
||||
"脚本后任务",
|
||||
)
|
||||
|
||||
# 保存运行日志以及统计信息
|
||||
await Config.save_general_log(
|
||||
Path.cwd()
|
||||
/ f"history/{self.curdate}/{user['name']}/{self.log_start_time.strftime('%H-%M-%S')}.log",
|
||||
self.general_logs,
|
||||
self.general_result,
|
||||
)
|
||||
|
||||
await self.result_record()
|
||||
|
||||
# 设置通用脚本模式
|
||||
elif self.mode == "设置脚本":
|
||||
@@ -545,11 +567,21 @@ class GeneralManager:
|
||||
"end_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||||
"user_result": "代理成功" if self.run_book else self.general_result,
|
||||
}
|
||||
await self.push_notification(
|
||||
"统计信息",
|
||||
f"{self.current_date} | 用户 {self.user_list[self.index]['name']} 的自动代理统计报告",
|
||||
statistics,
|
||||
)
|
||||
try:
|
||||
await self.push_notification(
|
||||
"统计信息",
|
||||
f"{self.current_date} | 用户 {self.user_list[self.index]['name']} 的自动代理统计报告",
|
||||
statistics,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.exception(f"推送统计信息失败: {e}")
|
||||
await Config.send_json(
|
||||
WebSocketMessage(
|
||||
id=self.ws_id,
|
||||
type="Info",
|
||||
data={"Error": f"推送统计信息失败: {e}"},
|
||||
).model_dump()
|
||||
)
|
||||
|
||||
if self.run_book:
|
||||
# 成功完成代理的用户修改相关参数
|
||||
@@ -659,10 +691,10 @@ class GeneralManager:
|
||||
if self.mode == "自动代理":
|
||||
|
||||
# 更新用户数据
|
||||
sc = Config.ScriptConfig[self.script_id]
|
||||
if isinstance(sc, GeneralConfig):
|
||||
await sc.UserData.load(await self.user_config.toDict())
|
||||
await Config.ScriptConfig.save()
|
||||
await Config.ScriptConfig[self.script_id].UserData.load(
|
||||
await self.user_config.toDict()
|
||||
)
|
||||
await Config.ScriptConfig.save()
|
||||
|
||||
error_user = [_["name"] for _ in self.user_list if _["status"] == "异常"]
|
||||
over_user = [_["name"] for _ in self.user_list if _["status"] == "完成"]
|
||||
@@ -708,7 +740,17 @@ class GeneralManager:
|
||||
f"已完成配置数: {len(over_user)}, 未完成配置数: {len(error_user) + len(wait_user)}",
|
||||
10,
|
||||
)
|
||||
await self.push_notification("代理结果", title, result)
|
||||
try:
|
||||
await self.push_notification("代理结果", title, result)
|
||||
except Exception as e:
|
||||
logger.exception(f"推送代理结果失败: {e}")
|
||||
await Config.send_json(
|
||||
WebSocketMessage(
|
||||
id=self.ws_id,
|
||||
type="Info",
|
||||
data={"Error": f"推送代理结果失败: {e}"},
|
||||
).model_dump()
|
||||
)
|
||||
|
||||
elif self.mode == "设置脚本":
|
||||
|
||||
@@ -970,7 +1012,6 @@ class GeneralManager:
|
||||
serverchan_message = message_text.replace("\n", "\n\n")
|
||||
|
||||
# 发送全局通知
|
||||
|
||||
if Config.get("Notify", "IfSendMail"):
|
||||
await Notify.send_mail(
|
||||
"网页", title, message_html, Config.get("Notify", "ToAddress")
|
||||
@@ -984,21 +1025,10 @@ class GeneralManager:
|
||||
)
|
||||
|
||||
# 发送自定义Webhook通知
|
||||
try:
|
||||
custom_webhooks = Config.get("Notify", "CustomWebhooks")
|
||||
except AttributeError:
|
||||
custom_webhooks = []
|
||||
if custom_webhooks:
|
||||
for webhook in custom_webhooks:
|
||||
if webhook.get("enabled", True):
|
||||
try:
|
||||
await Notify.CustomWebhookPush(
|
||||
title, f"{message_text}\n\nAUTO-MAS 敬上", webhook
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"自定义Webhook推送失败 ({webhook.get('name', 'Unknown')}): {e}"
|
||||
)
|
||||
for webhook in Config.Notify_CustomWebhooks.values():
|
||||
await Notify.WebhookPush(
|
||||
title, f"{message_text}\n\nAUTO-MAS 敬上", webhook
|
||||
)
|
||||
|
||||
elif mode == "统计信息":
|
||||
|
||||
@@ -1031,21 +1061,10 @@ class GeneralManager:
|
||||
)
|
||||
|
||||
# 发送自定义Webhook通知
|
||||
try:
|
||||
custom_webhooks = Config.get("Notify", "CustomWebhooks")
|
||||
except AttributeError:
|
||||
custom_webhooks = []
|
||||
if custom_webhooks:
|
||||
for webhook in custom_webhooks:
|
||||
if webhook.get("enabled", True):
|
||||
try:
|
||||
await Notify.CustomWebhookPush(
|
||||
title, f"{message_text}\n\nAUTO-MAS 敬上", webhook
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"自定义Webhook推送失败 ({webhook.get('name', 'Unknown')}): {e}"
|
||||
)
|
||||
for webhook in Config.Notify_CustomWebhooks.values():
|
||||
await Notify.WebhookPush(
|
||||
title, f"{message_text}\n\nAUTO-MAS 敬上", webhook
|
||||
)
|
||||
|
||||
# 发送用户单独通知
|
||||
if self.cur_user_data.get("Notify", "Enabled") and self.cur_user_data.get(
|
||||
@@ -1078,22 +1097,7 @@ class GeneralManager:
|
||||
)
|
||||
|
||||
# 推送CompanyWebHookBot通知
|
||||
# 发送用户自定义Webhook通知
|
||||
user_webhooks = self.cur_user_data.get("Notify", "CustomWebhooks")
|
||||
if user_webhooks:
|
||||
for webhook in user_webhooks:
|
||||
if webhook.get("enabled", True):
|
||||
try:
|
||||
await Notify.CustomWebhookPush(
|
||||
title, f"{message_text}\n\nAUTO-MAS 敬上", webhook
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"用户自定义Webhook推送失败 ({webhook.get('name', 'Unknown')}): {e}"
|
||||
)
|
||||
else:
|
||||
logger.error(
|
||||
"用户CompanyWebHookBot密钥为空, 无法发送用户单独的CompanyWebHookBot通知"
|
||||
)
|
||||
|
||||
return None
|
||||
for webhook in self.cur_user_data.Notify_CustomWebhooks.values():
|
||||
await Notify.WebhookPush(
|
||||
title, f"{message_text}\n\nAUTO-MAS 敬上", webhook
|
||||
)
|
||||
|
||||
@@ -23,6 +23,10 @@
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
TYPE_BOOK = {"MaaConfig": "MAA", "GeneralConfig": "通用"}
|
||||
"""配置类型映射表"""
|
||||
|
||||
RESOURCE_STAGE_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]},
|
||||
|
||||
Reference in New Issue
Block a user