feat: 初步完成后端自定义webhook适配;重构配置项管理体系
This commit is contained in:
@@ -100,141 +100,83 @@ async def test_notify() -> OutBase:
|
|||||||
|
|
||||||
|
|
||||||
@router.post(
|
@router.post(
|
||||||
"/webhook/create",
|
"/webhook/get",
|
||||||
summary="创建自定义Webhook",
|
summary="查询 webhook 配置",
|
||||||
response_model=OutBase,
|
response_model=WebhookGetOut,
|
||||||
status_code=200,
|
status_code=200,
|
||||||
)
|
)
|
||||||
async def create_webhook(webhook_data: dict = Body(...)) -> OutBase:
|
async def get_webhook(webhook: WebhookGetIn = Body(...)) -> WebhookGetOut:
|
||||||
"""创建自定义Webhook"""
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 生成唯一ID
|
index, data = await Config.get_webhook(None, None, webhook.webhookId)
|
||||||
webhook_id = str(uuid.uuid4())
|
index = [WebhookIndexItem(**_) for _ in index]
|
||||||
|
data = {uid: Webhook(**cfg) for uid, cfg in data.items()}
|
||||||
# 创建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']}' 创建成功")
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return OutBase(
|
return WebhookGetOut(
|
||||||
code=500, status="error", message=f"{type(e).__name__}: {str(e)}"
|
code=500,
|
||||||
|
status="error",
|
||||||
|
message=f"{type(e).__name__}: {str(e)}",
|
||||||
|
index=[],
|
||||||
|
data={},
|
||||||
)
|
)
|
||||||
|
return WebhookGetOut(index=index, data=data)
|
||||||
|
|
||||||
|
|
||||||
@router.post(
|
@router.post(
|
||||||
"/webhook/update",
|
"/webhook/add",
|
||||||
summary="更新自定义Webhook",
|
summary="添加定时项",
|
||||||
response_model=OutBase,
|
response_model=WebhookCreateOut,
|
||||||
status_code=200,
|
status_code=200,
|
||||||
)
|
)
|
||||||
async def update_webhook(webhook_data: dict = Body(...)) -> OutBase:
|
async def add_webhook() -> WebhookCreateOut:
|
||||||
"""更新自定义Webhook"""
|
|
||||||
|
|
||||||
try:
|
uid, config = await Config.add_webhook(None, None)
|
||||||
webhook_id = webhook_data.get("id")
|
data = Webhook(**(await config.toDict()))
|
||||||
if not webhook_id:
|
return WebhookCreateOut(webhookId=str(uid), data=data)
|
||||||
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)}"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@router.post(
|
@router.post(
|
||||||
"/webhook/delete",
|
"/webhook/update", summary="更新定时项", response_model=OutBase, status_code=200
|
||||||
summary="删除自定义Webhook",
|
|
||||||
response_model=OutBase,
|
|
||||||
status_code=200,
|
|
||||||
)
|
)
|
||||||
async def delete_webhook(webhook_data: dict = Body(...)) -> OutBase:
|
async def update_webhook(webhook: WebhookUpdateIn = Body(...)) -> OutBase:
|
||||||
"""删除自定义Webhook"""
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
webhook_id = webhook_data.get("id")
|
await Config.update_webhook(
|
||||||
if not webhook_id:
|
None, None, webhook.webhookId, webhook.data.model_dump(exclude_unset=True)
|
||||||
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删除成功")
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return OutBase(
|
return OutBase(
|
||||||
code=500, status="error", message=f"{type(e).__name__}: {str(e)}"
|
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(
|
@router.post(
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -62,10 +62,7 @@ class _TaskManager:
|
|||||||
actual_id = None
|
actual_id = None
|
||||||
else:
|
else:
|
||||||
for script_id, script in Config.ScriptConfig.items():
|
for script_id, script in Config.ScriptConfig.items():
|
||||||
if (
|
if actual_id in script.UserData:
|
||||||
isinstance(script, (MaaConfig | GeneralConfig))
|
|
||||||
and actual_id in script.UserData
|
|
||||||
):
|
|
||||||
task_id = script_id
|
task_id = script_id
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
@@ -142,31 +139,26 @@ class _TaskManager:
|
|||||||
# 初始化任务列表
|
# 初始化任务列表
|
||||||
if task_id in Config.QueueConfig:
|
if task_id in Config.QueueConfig:
|
||||||
|
|
||||||
queue = Config.QueueConfig[task_id]
|
|
||||||
if not isinstance(queue, QueueConfig):
|
|
||||||
return
|
|
||||||
|
|
||||||
task_list = []
|
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") == "-":
|
if queue_item.get("Info", "ScriptId") == "-":
|
||||||
continue
|
continue
|
||||||
script_id = uuid.UUID(queue_item.get("Info", "ScriptId"))
|
script_uid = 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
|
|
||||||
task_list.append(
|
task_list.append(
|
||||||
{
|
{
|
||||||
"script_id": str(script_id),
|
"script_id": str(script_uid),
|
||||||
"status": "等待",
|
"status": "等待",
|
||||||
"name": script.get("Info", "Name"),
|
"name": Config.ScriptConfig[script_uid].get("Info", "Name"),
|
||||||
"user_list": [
|
"user_list": [
|
||||||
{
|
{
|
||||||
"user_id": str(user_id),
|
"user_id": str(user_id),
|
||||||
"status": "等待",
|
"status": "等待",
|
||||||
"name": config.get("Info", "Name"),
|
"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")
|
if config.get("Info", "Status")
|
||||||
and config.get("Info", "RemainedDay") != 0
|
and config.get("Info", "RemainedDay") != 0
|
||||||
],
|
],
|
||||||
@@ -175,23 +167,20 @@ class _TaskManager:
|
|||||||
|
|
||||||
elif actual_id is not None and actual_id in Config.ScriptConfig:
|
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 = [
|
task_list = [
|
||||||
{
|
{
|
||||||
"script_id": str(actual_id),
|
"script_id": str(actual_id),
|
||||||
"status": "等待",
|
"status": "等待",
|
||||||
"name": script.get("Info", "Name"),
|
"name": Config.ScriptConfig[actual_id].get("Info", "Name"),
|
||||||
"user_list": [
|
"user_list": [
|
||||||
{
|
{
|
||||||
"user_id": str(user_id),
|
"user_id": str(user_id),
|
||||||
"status": "等待",
|
"status": "等待",
|
||||||
"name": config.get("Info", "Name"),
|
"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")
|
if config.get("Info", "Status")
|
||||||
and config.get("Info", "RemainedDay") != 0
|
and config.get("Info", "RemainedDay") != 0
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -81,9 +81,7 @@ class _MainTimer:
|
|||||||
|
|
||||||
for uid, queue in Config.QueueConfig.items():
|
for uid, queue in Config.QueueConfig.items():
|
||||||
|
|
||||||
if not isinstance(queue, QueueConfig) or not queue.get(
|
if not queue.get("Info", "TimeEnabled"):
|
||||||
"Info", "TimeEnabled"
|
|
||||||
):
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# 避免重复调起任务
|
# 避免重复调起任务
|
||||||
|
|||||||
@@ -25,9 +25,10 @@ import json
|
|||||||
import uuid
|
import uuid
|
||||||
import win32com.client
|
import win32com.client
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
|
from urllib.parse import urlparse
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from pathlib import Path
|
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
|
from app.utils import dpapi_encrypt, dpapi_decrypt
|
||||||
@@ -127,17 +128,25 @@ class DateTimeValidator(ConfigValidator):
|
|||||||
|
|
||||||
class JSONValidator(ConfigValidator):
|
class JSONValidator(ConfigValidator):
|
||||||
|
|
||||||
|
def __init__(self, tpye: type[dict] | type[list] = dict) -> None:
|
||||||
|
self.type = tpye
|
||||||
|
|
||||||
def validate(self, value: Any) -> bool:
|
def validate(self, value: Any) -> bool:
|
||||||
if not isinstance(value, str):
|
if not isinstance(value, str):
|
||||||
return False
|
return False
|
||||||
try:
|
try:
|
||||||
json.loads(value)
|
data = json.loads(value)
|
||||||
return True
|
if isinstance(data, self.type):
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def correct(self, value: Any) -> str:
|
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):
|
class EncryptValidator(ConfigValidator):
|
||||||
@@ -246,6 +255,67 @@ class UserNameValidator(ConfigValidator):
|
|||||||
return value
|
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:
|
class ConfigItem:
|
||||||
"""配置项"""
|
"""配置项"""
|
||||||
|
|
||||||
@@ -537,7 +607,10 @@ class ConfigBase:
|
|||||||
await item.unlock()
|
await item.unlock()
|
||||||
|
|
||||||
|
|
||||||
class MultipleConfig:
|
T = TypeVar("T", bound="ConfigBase")
|
||||||
|
|
||||||
|
|
||||||
|
class MultipleConfig(Generic[T]):
|
||||||
"""
|
"""
|
||||||
多配置项管理类
|
多配置项管理类
|
||||||
|
|
||||||
@@ -550,7 +623,7 @@ class MultipleConfig:
|
|||||||
子配置项的类型列表, 必须是 ConfigBase 的子类
|
子配置项的类型列表, 必须是 ConfigBase 的子类
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, sub_config_type: List[type]):
|
def __init__(self, sub_config_type: List[Type[T]]):
|
||||||
|
|
||||||
if not sub_config_type:
|
if not sub_config_type:
|
||||||
raise ValueError("子配置项类型列表不能为空")
|
raise ValueError("子配置项类型列表不能为空")
|
||||||
@@ -561,13 +634,13 @@ class MultipleConfig:
|
|||||||
f"配置类型 {config_type.__name__} 必须是 ConfigBase 的子类"
|
f"配置类型 {config_type.__name__} 必须是 ConfigBase 的子类"
|
||||||
)
|
)
|
||||||
|
|
||||||
self.sub_config_type = sub_config_type
|
self.sub_config_type: List[Type[T]] = sub_config_type
|
||||||
self.file: None | Path = None
|
self.file: Path | None = None
|
||||||
self.order: List[uuid.UUID] = []
|
self.order: List[uuid.UUID] = []
|
||||||
self.data: Dict[uuid.UUID, ConfigBase] = {}
|
self.data: Dict[uuid.UUID, T] = {}
|
||||||
self.is_locked = False
|
self.is_locked = False
|
||||||
|
|
||||||
def __getitem__(self, key: uuid.UUID) -> ConfigBase:
|
def __getitem__(self, key: uuid.UUID) -> T:
|
||||||
"""允许通过 config[uuid] 访问配置项"""
|
"""允许通过 config[uuid] 访问配置项"""
|
||||||
if key not in self.data:
|
if key not in self.data:
|
||||||
raise KeyError(f"配置项 '{key}' 不存在")
|
raise KeyError(f"配置项 '{key}' 不存在")
|
||||||
@@ -665,7 +738,9 @@ class MultipleConfig:
|
|||||||
if self.file:
|
if self.file:
|
||||||
await self.save()
|
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():
|
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
|
return data
|
||||||
|
|
||||||
async def get(self, uid: uuid.UUID) -> Dict[str, Union[list, dict]]:
|
async def get(self, uid: uuid.UUID) -> Dict[str, Union[list, dict]]:
|
||||||
@@ -721,7 +796,7 @@ class MultipleConfig:
|
|||||||
encoding="utf-8",
|
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"
|
__license__ = "GPL-3.0 license"
|
||||||
|
|
||||||
from .ConfigBase import *
|
from .ConfigBase import *
|
||||||
|
from .config import *
|
||||||
from .schema 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):
|
class GlobalConfig_Function(BaseModel):
|
||||||
HistoryRetentionTime: Optional[Literal[7, 15, 30, 60, 90, 180, 365, 0]] = Field(
|
HistoryRetentionTime: Optional[Literal[7, 15, 30, 60, 90, 180, 365, 0]] = Field(
|
||||||
None, description="历史记录保留时间, 0表示永久保存"
|
None, description="历史记录保留时间, 0表示永久保存"
|
||||||
@@ -111,18 +135,6 @@ class GlobalConfig_UI(BaseModel):
|
|||||||
IfToTray: Optional[bool] = Field(default=None, description="是否最小化到托盘")
|
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):
|
class GlobalConfig_Notify(BaseModel):
|
||||||
SendTaskResultTime: Optional[Literal["不推送", "任何时刻", "仅失败时"]] = Field(
|
SendTaskResultTime: Optional[Literal["不推送", "任何时刻", "仅失败时"]] = Field(
|
||||||
default=None, description="任务结果推送时机"
|
default=None, description="任务结果推送时机"
|
||||||
@@ -143,9 +155,6 @@ class GlobalConfig_Notify(BaseModel):
|
|||||||
default=None, description="是否使用ServerChan推送"
|
default=None, description="是否使用ServerChan推送"
|
||||||
)
|
)
|
||||||
ServerChanKey: Optional[str] = Field(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):
|
class GlobalConfig_Update(BaseModel):
|
||||||
@@ -313,9 +322,6 @@ class MaaUserConfig_Notify(BaseModel):
|
|||||||
default=None, description="是否使用Server酱推送"
|
default=None, description="是否使用Server酱推送"
|
||||||
)
|
)
|
||||||
ServerChanKey: Optional[str] = Field(default=None, description="ServerChanKey")
|
ServerChanKey: Optional[str] = Field(default=None, description="ServerChanKey")
|
||||||
CustomWebhooks: Optional[List[CustomWebhook]] = Field(
|
|
||||||
default=None, description="用户自定义Webhook列表"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class GeneralUserConfig_Notify(BaseModel):
|
class GeneralUserConfig_Notify(BaseModel):
|
||||||
@@ -618,6 +624,46 @@ class UserSetIn(UserInBase):
|
|||||||
jsonFile: str = Field(..., description="JSON文件路径, 用于导入自定义基建文件")
|
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):
|
class PlanCreateIn(BaseModel):
|
||||||
type: Literal["MaaPlan"]
|
type: Literal["MaaPlan"]
|
||||||
|
|
||||||
|
|||||||
@@ -21,17 +21,20 @@
|
|||||||
|
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
import json
|
||||||
import smtplib
|
import smtplib
|
||||||
import requests
|
import requests
|
||||||
|
from datetime import datetime
|
||||||
|
from plyer import notification
|
||||||
from email.header import Header
|
from email.header import Header
|
||||||
from email.mime.multipart import MIMEMultipart
|
from email.mime.multipart import MIMEMultipart
|
||||||
from email.mime.text import MIMEText
|
from email.mime.text import MIMEText
|
||||||
from email.utils import formataddr
|
from email.utils import formataddr
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Literal
|
||||||
from plyer import notification
|
|
||||||
|
|
||||||
from app.core import Config
|
from app.core import Config
|
||||||
|
from app.models.config import Webhook
|
||||||
from app.utils import get_logger, ImageUtils
|
from app.utils import get_logger, ImageUtils
|
||||||
|
|
||||||
logger = get_logger("通知服务")
|
logger = get_logger("通知服务")
|
||||||
@@ -39,71 +42,76 @@ logger = get_logger("通知服务")
|
|||||||
|
|
||||||
class Notification:
|
class Notification:
|
||||||
|
|
||||||
def __init__(self):
|
async def push_plyer(self, title: str, message: str, ticker: str, t: int) -> None:
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
async def push_plyer(self, title, message, ticker, t) -> bool:
|
|
||||||
"""
|
"""
|
||||||
推送系统通知
|
推送系统通知
|
||||||
|
|
||||||
:param title: 通知标题
|
Parameters
|
||||||
:param message: 通知内容
|
----------
|
||||||
:param ticker: 通知横幅
|
title: str
|
||||||
:param t: 通知持续时间
|
通知标题
|
||||||
:return: bool
|
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:
|
if notification.notify is not None:
|
||||||
notification.notify(
|
notification.notify(
|
||||||
title=title,
|
title=title,
|
||||||
message=message,
|
message=message,
|
||||||
app_name="AUTO-MAS",
|
app_name="AUTO-MAS",
|
||||||
app_icon=(Path.cwd() / "res/icons/AUTO-MAS.ico").as_posix(),
|
app_icon=(Path.cwd() / "res/icons/AUTO-MAS.ico").as_posix(),
|
||||||
timeout=t,
|
timeout=t,
|
||||||
ticker=ticker,
|
ticker=ticker,
|
||||||
toast=True,
|
toast=True,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
logger.error("plyer.notification 未正确导入, 无法推送系统通知")
|
logger.error("plyer.notification 未正确导入, 无法推送系统通知")
|
||||||
|
|
||||||
return True
|
async def send_mail(
|
||||||
|
self, mode: Literal["文本", "网页"], title: str, content: str, to_address: str
|
||||||
async def send_mail(self, mode, title, content, to_address) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
推送邮件通知
|
推送邮件通知
|
||||||
|
|
||||||
:param mode: 邮件内容模式, 支持 "文本" 和 "网页"
|
Parameters
|
||||||
:param title: 邮件标题
|
----------
|
||||||
:param content: 邮件内容
|
mode: Literal["文本", "网页"]
|
||||||
:param to_address: 收件人地址
|
邮件内容模式, 支持 "文本" 和 "网页"
|
||||||
|
title: str
|
||||||
|
邮件标题
|
||||||
|
content: str
|
||||||
|
邮件内容
|
||||||
|
to_address: str
|
||||||
|
收件人地址
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if (
|
if Config.get("Notify", "SMTPServerAddress") == "":
|
||||||
Config.get("Notify", "SMTPServerAddress") == ""
|
raise ValueError("邮件通知的SMTP服务器地址不能为空")
|
||||||
or Config.get("Notify", "AuthorizationCode") == ""
|
if Config.get("Notify", "AuthorizationCode") == "":
|
||||||
or not bool(
|
raise ValueError("邮件通知的授权码不能为空")
|
||||||
re.match(
|
if not bool(
|
||||||
r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$",
|
re.match(
|
||||||
Config.get("Notify", "FromAddress"),
|
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,
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
):
|
):
|
||||||
logger.error(
|
raise ValueError("邮件通知的发送邮箱格式错误或为空")
|
||||||
"请正确设置邮件通知的SMTP服务器地址、授权码、发件人地址和收件人地址"
|
if not bool(
|
||||||
)
|
re.match(
|
||||||
raise ValueError(
|
r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$",
|
||||||
"邮件通知的SMTP服务器地址、授权码、发件人地址或收件人地址未正确配置"
|
to_address,
|
||||||
)
|
)
|
||||||
|
):
|
||||||
|
raise ValueError("邮件通知的接收邮箱格式错误或为空")
|
||||||
|
|
||||||
# 定义邮件正文
|
# 定义邮件正文
|
||||||
if mode == "文本":
|
if mode == "文本":
|
||||||
@@ -135,16 +143,21 @@ class Notification:
|
|||||||
smtpObj.quit()
|
smtpObj.quit()
|
||||||
logger.success(f"邮件发送成功: {title}")
|
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酱推送通知
|
使用Server酱推送通知
|
||||||
|
|
||||||
:param title: 通知标题
|
Parameters
|
||||||
:param content: 通知内容
|
----------
|
||||||
:param send_key: Server酱的SendKey
|
title: str
|
||||||
|
通知标题
|
||||||
|
content: str
|
||||||
|
通知内容
|
||||||
|
send_key: str
|
||||||
|
Server酱的SendKey
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not send_key:
|
if send_key == "":
|
||||||
raise ValueError("ServerChan SendKey 不能为空")
|
raise ValueError("ServerChan SendKey 不能为空")
|
||||||
|
|
||||||
# 构造 URL
|
# 构造 URL
|
||||||
@@ -171,33 +184,33 @@ class Notification:
|
|||||||
else:
|
else:
|
||||||
raise Exception(f"ServerChan 推送通知失败: {response.text}")
|
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: 通知标题
|
Parameters
|
||||||
:param content: 通知内容
|
----------
|
||||||
:param webhook_config: Webhook配置对象
|
title: str
|
||||||
|
通知标题
|
||||||
|
content: str
|
||||||
|
通知内容
|
||||||
|
webhook: Webhook
|
||||||
|
Webhook配置对象
|
||||||
"""
|
"""
|
||||||
|
if not webhook.get("Info", "Enabled"):
|
||||||
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')} 已禁用,跳过推送"
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if webhook.get("Data", "Url") == "":
|
||||||
|
raise ValueError("Webhook URL 不能为空")
|
||||||
|
|
||||||
# 解析模板
|
# 解析模板
|
||||||
template = webhook_config.get(
|
template = (
|
||||||
"template", '{"title": "{title}", "content": "{content}"}'
|
webhook.get("Data", "Template")
|
||||||
|
or '{"title": "{title}", "content": "{content}"}'
|
||||||
)
|
)
|
||||||
|
|
||||||
# 替换模板变量
|
# 替换模板变量
|
||||||
try:
|
try:
|
||||||
import json
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
# 准备模板变量
|
# 准备模板变量
|
||||||
template_vars = {
|
template_vars = {
|
||||||
@@ -264,56 +277,55 @@ class Notification:
|
|||||||
|
|
||||||
# 准备请求头
|
# 准备请求头
|
||||||
headers = {"Content-Type": "application/json"}
|
headers = {"Content-Type": "application/json"}
|
||||||
if webhook_config.get("headers"):
|
headers.update(json.loads(webhook.get("Data", "Headers")))
|
||||||
headers.update(webhook_config["headers"])
|
|
||||||
|
|
||||||
# 发送请求
|
if webhook.get("Data", "Method") == "POST":
|
||||||
method = webhook_config.get("method", "POST").upper()
|
if isinstance(data, dict):
|
||||||
|
response = requests.post(
|
||||||
try:
|
url=webhook.get("Data", "Url"),
|
||||||
if method == "POST":
|
json=data,
|
||||||
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,
|
|
||||||
headers=headers,
|
headers=headers,
|
||||||
timeout=10,
|
timeout=10,
|
||||||
proxies=Config.get_proxies(),
|
proxies=Config.get_proxies(),
|
||||||
)
|
)
|
||||||
|
elif isinstance(data, str):
|
||||||
# 检查响应
|
response = requests.post(
|
||||||
if response.status_code == 200:
|
url=webhook.get("Data", "Url"),
|
||||||
logger.success(
|
data=data,
|
||||||
f"自定义Webhook推送成功: {webhook_config.get('name', 'Unknown')} - {title}"
|
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:
|
else:
|
||||||
raise Exception(f"HTTP {response.status_code}: {response.text}")
|
params = {"message": str(data)}
|
||||||
|
response = requests.get(
|
||||||
except Exception as e:
|
url=webhook.get("Data", "Url"),
|
||||||
raise Exception(
|
params=params,
|
||||||
f"自定义Webhook推送失败 ({webhook_config.get('name', 'Unknown')}): {str(e)}"
|
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 title: 通知标题
|
||||||
:param content: 通知内容
|
:param content: 通知内容
|
||||||
@@ -340,7 +352,7 @@ class Notification:
|
|||||||
self, image_path: Path, webhook_url: str
|
self, image_path: Path, webhook_url: str
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
使用企业微信群机器人推送图片通知
|
使用企业微信群机器人推送图片通知(等待重新适配)
|
||||||
|
|
||||||
:param image_path: 图片文件路径
|
:param image_path: 图片文件路径
|
||||||
:param webhook_url: 企业微信群机器人的WebHook地址
|
:param webhook_url: 企业微信群机器人的WebHook地址
|
||||||
@@ -406,23 +418,12 @@ class Notification:
|
|||||||
)
|
)
|
||||||
|
|
||||||
# 发送自定义Webhook通知
|
# 发送自定义Webhook通知
|
||||||
try:
|
for webhook in Config.Notify_CustomWebhooks.values():
|
||||||
custom_webhooks = Config.get("Notify", "CustomWebhooks")
|
await self.WebhookPush(
|
||||||
except AttributeError:
|
"AUTO-MAS测试通知",
|
||||||
custom_webhooks = []
|
"这是 AUTO-MAS 外部通知测试信息。如果你看到了这段内容, 说明 AUTO-MAS 的通知功能已经正确配置且可以正常工作!",
|
||||||
if custom_webhooks:
|
webhook,
|
||||||
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}"
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.success("测试通知发送完成")
|
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 asyncio
|
||||||
import subprocess
|
import subprocess
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from fastapi import WebSocket
|
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from jinja2 import Environment, FileSystemLoader
|
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
|
from app.core import Config, GeneralConfig, GeneralUserConfig
|
||||||
@@ -44,7 +43,7 @@ logger = get_logger("通用调度器")
|
|||||||
|
|
||||||
|
|
||||||
class GeneralManager:
|
class GeneralManager:
|
||||||
"""通用脚本通用控制器"""
|
"""通用脚本调度器"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, mode: str, script_id: uuid.UUID, user_id: Optional[uuid.UUID], ws_id: str
|
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()
|
await Config.ScriptConfig[self.script_id].lock()
|
||||||
|
|
||||||
self.script_config = Config.ScriptConfig[self.script_id]
|
self.script_config = Config.ScriptConfig[self.script_id]
|
||||||
if isinstance(self.script_config, GeneralConfig):
|
self.user_config = MultipleConfig([GeneralUserConfig])
|
||||||
self.user_config = MultipleConfig([GeneralUserConfig])
|
await self.user_config.load(await self.script_config.UserData.toDict())
|
||||||
await self.user_config.load(await self.script_config.UserData.toDict())
|
|
||||||
|
|
||||||
self.script_root_path = Path(self.script_config.get("Info", "RootPath"))
|
self.script_root_path = Path(self.script_config.get("Info", "RootPath"))
|
||||||
self.script_path = Path(self.script_config.get("Script", "ScriptPath"))
|
self.script_path = Path(self.script_config.get("Script", "ScriptPath"))
|
||||||
@@ -143,6 +141,9 @@ class GeneralManager:
|
|||||||
def check_config(self) -> str:
|
def check_config(self) -> str:
|
||||||
"""检查配置是否可用"""
|
"""检查配置是否可用"""
|
||||||
|
|
||||||
|
if not isinstance(Config.ScriptConfig[self.script_id], GeneralConfig):
|
||||||
|
return "脚本配置类型错误, 不是通用脚本类型"
|
||||||
|
|
||||||
if self.mode == "人工排查":
|
if self.mode == "人工排查":
|
||||||
return "通用脚本不支持人工排查模式"
|
return "通用脚本不支持人工排查模式"
|
||||||
if self.mode == "设置脚本" and self.user_id is None:
|
if self.mode == "设置脚本" and self.user_id is None:
|
||||||
@@ -221,298 +222,319 @@ class GeneralManager:
|
|||||||
# 开始代理
|
# 开始代理
|
||||||
for self.index, user in enumerate(self.user_list):
|
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 = self.user_config[uuid.UUID(user["user_id"])]
|
||||||
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
|
|
||||||
|
|
||||||
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 (
|
self.run_book = False
|
||||||
Path.cwd() / f"data/{self.script_id}/{user['user_id']}/ConfigFile"
|
|
||||||
).exists():
|
|
||||||
|
|
||||||
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(
|
await Config.send_json(
|
||||||
WebSocketMessage(
|
WebSocketMessage(
|
||||||
id=self.ws_id,
|
id=self.ws_id,
|
||||||
type="Info",
|
type="Info",
|
||||||
data={"Error": f"未找到 {user['user_id']} 的配置文件"},
|
data={"Error": f"代理用户 {user['name']} 时出现异常: {e}"},
|
||||||
).model_dump()
|
).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 == "设置脚本":
|
elif self.mode == "设置脚本":
|
||||||
@@ -545,11 +567,21 @@ class GeneralManager:
|
|||||||
"end_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
"end_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
"user_result": "代理成功" if self.run_book else self.general_result,
|
"user_result": "代理成功" if self.run_book else self.general_result,
|
||||||
}
|
}
|
||||||
await self.push_notification(
|
try:
|
||||||
"统计信息",
|
await self.push_notification(
|
||||||
f"{self.current_date} | 用户 {self.user_list[self.index]['name']} 的自动代理统计报告",
|
"统计信息",
|
||||||
statistics,
|
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:
|
if self.run_book:
|
||||||
# 成功完成代理的用户修改相关参数
|
# 成功完成代理的用户修改相关参数
|
||||||
@@ -659,10 +691,10 @@ class GeneralManager:
|
|||||||
if self.mode == "自动代理":
|
if self.mode == "自动代理":
|
||||||
|
|
||||||
# 更新用户数据
|
# 更新用户数据
|
||||||
sc = Config.ScriptConfig[self.script_id]
|
await Config.ScriptConfig[self.script_id].UserData.load(
|
||||||
if isinstance(sc, GeneralConfig):
|
await self.user_config.toDict()
|
||||||
await sc.UserData.load(await self.user_config.toDict())
|
)
|
||||||
await Config.ScriptConfig.save()
|
await Config.ScriptConfig.save()
|
||||||
|
|
||||||
error_user = [_["name"] for _ in self.user_list if _["status"] == "异常"]
|
error_user = [_["name"] for _ in self.user_list if _["status"] == "异常"]
|
||||||
over_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)}",
|
f"已完成配置数: {len(over_user)}, 未完成配置数: {len(error_user) + len(wait_user)}",
|
||||||
10,
|
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 == "设置脚本":
|
elif self.mode == "设置脚本":
|
||||||
|
|
||||||
@@ -970,7 +1012,6 @@ class GeneralManager:
|
|||||||
serverchan_message = message_text.replace("\n", "\n\n")
|
serverchan_message = message_text.replace("\n", "\n\n")
|
||||||
|
|
||||||
# 发送全局通知
|
# 发送全局通知
|
||||||
|
|
||||||
if Config.get("Notify", "IfSendMail"):
|
if Config.get("Notify", "IfSendMail"):
|
||||||
await Notify.send_mail(
|
await Notify.send_mail(
|
||||||
"网页", title, message_html, Config.get("Notify", "ToAddress")
|
"网页", title, message_html, Config.get("Notify", "ToAddress")
|
||||||
@@ -984,21 +1025,10 @@ class GeneralManager:
|
|||||||
)
|
)
|
||||||
|
|
||||||
# 发送自定义Webhook通知
|
# 发送自定义Webhook通知
|
||||||
try:
|
for webhook in Config.Notify_CustomWebhooks.values():
|
||||||
custom_webhooks = Config.get("Notify", "CustomWebhooks")
|
await Notify.WebhookPush(
|
||||||
except AttributeError:
|
title, f"{message_text}\n\nAUTO-MAS 敬上", webhook
|
||||||
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}"
|
|
||||||
)
|
|
||||||
|
|
||||||
elif mode == "统计信息":
|
elif mode == "统计信息":
|
||||||
|
|
||||||
@@ -1031,21 +1061,10 @@ class GeneralManager:
|
|||||||
)
|
)
|
||||||
|
|
||||||
# 发送自定义Webhook通知
|
# 发送自定义Webhook通知
|
||||||
try:
|
for webhook in Config.Notify_CustomWebhooks.values():
|
||||||
custom_webhooks = Config.get("Notify", "CustomWebhooks")
|
await Notify.WebhookPush(
|
||||||
except AttributeError:
|
title, f"{message_text}\n\nAUTO-MAS 敬上", webhook
|
||||||
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}"
|
|
||||||
)
|
|
||||||
|
|
||||||
# 发送用户单独通知
|
# 发送用户单独通知
|
||||||
if self.cur_user_data.get("Notify", "Enabled") and self.cur_user_data.get(
|
if self.cur_user_data.get("Notify", "Enabled") and self.cur_user_data.get(
|
||||||
@@ -1078,22 +1097,7 @@ class GeneralManager:
|
|||||||
)
|
)
|
||||||
|
|
||||||
# 推送CompanyWebHookBot通知
|
# 推送CompanyWebHookBot通知
|
||||||
# 发送用户自定义Webhook通知
|
for webhook in self.cur_user_data.Notify_CustomWebhooks.values():
|
||||||
user_webhooks = self.cur_user_data.get("Notify", "CustomWebhooks")
|
await Notify.WebhookPush(
|
||||||
if user_webhooks:
|
title, f"{message_text}\n\nAUTO-MAS 敬上", webhook
|
||||||
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
|
|
||||||
|
|||||||
@@ -23,6 +23,10 @@
|
|||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
|
TYPE_BOOK = {"MaaConfig": "MAA", "GeneralConfig": "通用"}
|
||||||
|
"""配置类型映射表"""
|
||||||
|
|
||||||
RESOURCE_STAGE_INFO = [
|
RESOURCE_STAGE_INFO = [
|
||||||
{"value": "-", "text": "当前/上次", "days": [1, 2, 3, 4, 5, 6, 7]},
|
{"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": "1-7", "text": "1-7", "days": [1, 2, 3, 4, 5, 6, 7]},
|
||||||
|
|||||||
Reference in New Issue
Block a user