Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 1 addition & 20 deletions app/api/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
from app.core.ws import MainConnection, Publisher, protocol
from app.models.schema import *
from app.services import System
from app.utils import get_logger, is_supervised
from app.utils import get_logger, is_backend_dev_mode, is_supervised

router = APIRouter(prefix="/api/core", tags=["核心信息"])
logger = get_logger("DEV")
Expand Down Expand Up @@ -100,25 +100,6 @@ async def get_health(request: Request) -> BackendHealthOut:
)


def is_backend_dev_mode() -> bool:
"""判断后端是否处于开发模式(后端由开发者独立管理,前端不得强杀)。

dev 分支的 AUTO_MAS_DEV 标记“由前端拉起”(跳过自行提权),生产环境同样为 1,
不能作为开发模式依据;以 main.py 启动时归一化的 AUTO_MAS_ENV 为准。

受 AUTO-MAS-Runtime 监督时优先级高于 AUTO_MAS_DEV 与 AUTO_MAS_ENV,恒为
False:监督器依赖 /api/core/close 真正退出进程,若判定为开发模式,
_shutdown_backend() 只做轻量清理、不设 should_exit,关闭请求就会永远
不生效,5 秒后被监督器硬杀。
"""

if is_supervised():
return False

raw = str(os.getenv("AUTO_MAS_ENV", "")).strip().lower()
return raw in {"dev", "development"}


@router.get(
"/ws_meta",
summary="获取主 WebSocket 元信息",
Expand Down
13 changes: 11 additions & 2 deletions app/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2653,7 +2653,7 @@ def parse_maaend_pull_count_statistics(
return statistics if len(statistics) == len(field_patterns) else None

async def save_maaend_log(
self, log_path: Path, logs: list[str], maaend_result: str
self, log_path: Path, logs: list[str], maaend_result: str, phase_label: str = ""
) -> None:
"""
Save MaaEnd logs and generate basic statistics data.
Expand All @@ -2662,6 +2662,7 @@ async def save_maaend_log(
log_path (Path): Target log file path.
logs (list[str]): Log lines.
maaend_result (str): Result label for this run.
phase_label (str): 运行阶段标签(送货/日常/自动采集),作为历史结果前缀。
"""

logger.info(
Expand All @@ -2675,6 +2676,9 @@ async def save_maaend_log(
if maaend_result == "MaaEnd 部分任务执行失败" and failed_tasks:
maaend_result = f"{maaend_result}: {'、'.join(failed_tasks)}"

if phase_label:
maaend_result = f"[{phase_label}] {maaend_result}"

data: Dict[str, Any] = {"maaend_result": maaend_result}
if has_matrix_flow and matrix_statistics is not None:
data["matrix_statistics"] = matrix_statistics
Expand Down Expand Up @@ -2786,7 +2790,11 @@ async def merge_statistic_info(self, statistic_path_list: List[Path]) -> dict:
}

def is_success_result(result_key: str, result_value: Any) -> bool:
if result_value == "Success!":
# 结果文本可能带运行阶段前缀(如 "[送货] Success!"),比对前先剥离
if not isinstance(result_value, str):
return False
value = re.sub(r"^\[[^\]]+\]\s*", "", result_value)
if value == "Success!":
return True
if result_key == "hsr_result" and result_value in hsr_success_results:
return True
Expand Down Expand Up @@ -2867,6 +2875,7 @@ def is_success_result(result_key: str, result_value: Any) -> bool:
"date": actual_date.strftime("%Y-%m-%d %H:%M:%S"),
"status": "DONE" if success else "ERROR",
"jsonFile": str(json_file),
"result": single_data[key],
}

data["index"] = [data["index"][_] for _ in sorted(data["index"])]
Expand Down
88 changes: 84 additions & 4 deletions app/models/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@
from app.utils.constants import (
CYCLE_EMPTY_TIME,
MAA_STAGE_KEY,
MAAEND_AUTO_COLLECT_MODES,
MAAEND_AUTO_COLLECT_ROUTE_OPTIONS,
MAAEND_AUTO_COLLECT_TASK,
MAAEND_DELIVERY_COMMISSION_SOURCES,
MAAEND_DELIVERY_TASK,
MAAEND_PROTOCOL_SPACE_TASK_OPTIONS,
MAAEND_SANITY_TASK_DEFAULTS,
MAAEND_SANITY_TASK_DETAIL_LABELS,
Expand Down Expand Up @@ -126,6 +131,59 @@ def init_maaend_task_config(config) -> None:
StringValidator(),
)

## 抢委托送货最低接取价格(万)
config.Task_SeizeDeliveryJobsReward = ConfigItem(
"Task", "SeizeDeliveryJobsReward", 15.9, RangeValidator(0, 9999)
)
## 抢委托送货委托接收点
config.Task_SeizeDeliveryJobsCommissionSource = ConfigItem(
"Task",
"SeizeDeliveryJobsCommissionSource",
"Unlimited",
OptionsValidator(list(MAAEND_DELIVERY_COMMISSION_SOURCES)),
)
## 独立送货任务
setattr(
config,
f"Task_If{MAAEND_DELIVERY_TASK}",
ConfigItem("Task", f"If{MAAEND_DELIVERY_TASK}", True, BoolValidator()),
)

## 独立自动采集任务
config.Task_IfAutoCollect = ConfigItem(
"Task", f"If{MAAEND_AUTO_COLLECT_TASK}", True, BoolValidator()
)
## 自动采集路线安排:分散为三日轮换,集中为每三日执行一次
config.Task_AutoCollectMode = ConfigItem(
"Task",
"AutoCollectMode",
"Distributed",
OptionsValidator(list(MAAEND_AUTO_COLLECT_MODES)),
)
## 自动采集区域资源路线
config.Task_AutoCollectRoutes = ConfigItem(
"Task",
"AutoCollectRoutes",
list(MAAEND_AUTO_COLLECT_ROUTE_OPTIONS["AutoCollectRoutes"]),
MultipleOptionsValidator(
list(MAAEND_AUTO_COLLECT_ROUTE_OPTIONS["AutoCollectRoutes"])
),
)
## 自动采集通用资源路线
config.Task_AutoCollectCommonRoutes = ConfigItem(
"Task",
"AutoCollectCommonRoutes",
list(MAAEND_AUTO_COLLECT_ROUTE_OPTIONS["AutoCollectCommonRoutes"]),
MultipleOptionsValidator(
list(MAAEND_AUTO_COLLECT_ROUTE_OPTIONS["AutoCollectCommonRoutes"])
),
)

## 每日正常完成一次后,当天剩余时间跳过的任务名列表
config.Task_DailyOnceTasks = ConfigItem(
"Task", "DailyOnceTasks", "[ ]", JSONValidator(list)
)

for task_name in MAAEND_TASKS:
setattr(
config,
Expand Down Expand Up @@ -973,6 +1031,20 @@ def __init__(self) -> None:
super().__init__()


class MaaEndConfigModeValidator(OptionsValidator):
"""兼容旧版来源名称,统一为脚本/用户/直控。"""

LEGACY_MODE_MAP = {"简洁": "脚本", "详细": "用户", "自定义": "用户"}

def __init__(self) -> None:
super().__init__(["脚本", "用户", "直控"])

def correct(self, value: Any) -> Any:
if value in self.LEGACY_MODE_MAP:
return self.LEGACY_MODE_MAP[value]
return super().correct(value)


class MaaEndUserConfig(ConfigBase):
"""MaaEnd用户配置"""

Expand All @@ -991,9 +1063,7 @@ def __init__(self) -> None:
## 密码
self.Info_Password = ConfigItem("Info", "Password", "", EncryptValidator())
## 配置文件来源
self.Info_Mode = ConfigItem(
"Info", "Mode", "脚本", ScriptUserModeValidator()
)
self.Info_Mode = ConfigItem("Info", "Mode", "脚本", MaaEndConfigModeValidator())
## 是否启用快速配置
self.Info_IfQuickConfig = ConfigItem(
"Info", "IfQuickConfig", True, BoolValidator()
Expand Down Expand Up @@ -1055,6 +1125,10 @@ def __init__(self) -> None:
"未知",
OptionsValidator(["未知", "成功", "失败"]),
)
## MaaEnd 每日任务完成记录,结构为 daily -> task name -> 日期
self.Data_PeriodTaskRecords = ConfigItem(
"Data", "PeriodTaskRecords", "{ }", JSONValidator(dict)
)
## Notify ----------------------------------------------------------
## 是否启用通知
self.Notify_Enabled = ConfigItem("Notify", "Enabled", False, BoolValidator())
Expand Down Expand Up @@ -1094,7 +1168,6 @@ async def load(self, data: dict):
):
info_data["Mode"] = "脚本"
info_data.pop("IfQuickConfig", None)

task_data = data.get("Task")
if isinstance(task_data, dict):
_normalize_maaend_sanity_task_type(task_data)
Expand Down Expand Up @@ -1228,6 +1301,13 @@ def __init__(self) -> None:
"MAS",
OptionsValidator(["MAS", "MAAEND"]),
)
## 任务切换方式
self.Run_TaskTransitionMethod = ConfigItem(
"Run",
"TaskTransitionMethod",
"NoAction",
OptionsValidator(["NoAction", "ExitGame"]),
)

## Game ------------------------------------------------------------
## 控制器类型
Expand Down
64 changes: 61 additions & 3 deletions app/models/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -1209,8 +1209,9 @@ class MaaEndUserConfig_Info(BaseModel):
Status: Optional[bool] = Field(default=None, description="用户状态")
Id: Optional[str] = Field(default=None, description="用户ID")
Password: Optional[str] = Field(default=None, description="密码")
Mode: Optional[Literal["脚本", "用户"]] = Field(
default=None, description="配置来源(脚本/用户)"
Mode: Optional[Literal["脚本", "用户", "直控"]] = Field(
default=None,
description="配置来源(脚本共享、用户独立、脚本直控)",
)
IfQuickConfig: Optional[bool] = Field(default=None, description="是否启用快速配置")
SanityMode: Optional[str] = Field(default=None, description="理智任务配置模式")
Expand All @@ -1228,6 +1229,35 @@ class MaaEndUserConfig_Info(BaseModel):
Tag: Optional[str] = Field(default=None, description="用户标签信息")


MaaEndAutoCollectRoute = Literal[
"Route1",
"Route2",
"Route3",
"Route4",
"Route5",
"Route6",
"Route7",
"Route8",
"Route9",
"Route10",
"Route11",
"Route12",
"Route13",
"Route14",
"Route15",
]
MaaEndAutoCollectCommonRoute = Literal[
"CommonRoute1",
"CommonRoute2",
"CommonRoute3",
"CommonRoute4",
"CommonRoute5",
"CommonRoute6",
"CommonRoute7",
"CommonRoute8",
]


class MaaEndUserConfig_Task(BaseModel):
SanityTaskType: Optional[
Literal["OperatorProgression", "WeaponProgression", "CrisisDrills", "Essence"]
Expand Down Expand Up @@ -1264,13 +1294,32 @@ class MaaEndUserConfig_Task(BaseModel):
IfAutoStockStaple: Optional[bool] = Field(default=None, description="购买稳定物资")
IfVisitFriends: Optional[bool] = Field(default=None, description="拜访好友")
IfCreditShoppingN2: Optional[bool] = Field(default=None, description="信用点购物")
IfSeizeEntrustTask: Optional[bool] = Field(default=None, description="抢委托")
SeizeDeliveryJobsReward: Optional[float] = Field(
default=None, ge=0, description="抢委托送货最低接取价格(万)"
)
SeizeDeliveryJobsCommissionSource: Optional[
Literal["Unlimited", "WulingCity", "TestArea"]
] = Field(default=None, description="抢委托送货委托接收点")
IfSeizeDeliveryJobs: Optional[bool] = Field(default=None, description="抢委托送货")
IfAutoEcoFarm: Optional[bool] = Field(default=None, description="生态农场")
IfAutoSell: Optional[bool] = Field(default=None, description="售卖弹性物资")
IfEnvironmentMonitoring: Optional[bool] = Field(
default=None, description="环境监测"
)
IfAutoCollect: Optional[bool] = Field(default=None, description="自动采集")
AutoCollectMode: Optional[Literal["Distributed", "Concentrated"]] = Field(
default=None, description="自动采集路线安排:分散或集中"
)
AutoCollectRoutes: Optional[list[MaaEndAutoCollectRoute]] = Field(
default=None, description="自动采集区域资源路线"
)
AutoCollectCommonRoutes: Optional[list[MaaEndAutoCollectCommonRoute]] = Field(
default=None, description="自动采集通用资源路线"
)
DailyOnceTasks: Optional[str] = Field(
default=None,
description="每日正常完成一次后当天跳过的 MaaEnd 任务名列表(JSON 字符串)",
)
IfTrialOfSwordmancy: Optional[bool] = Field(default=None, description="选剑演武")
IfDailyRewards: Optional[bool] = Field(default=None, description="日常奖励领取")
IfResourceRecycleStation: Optional[bool] = Field(
Expand All @@ -1296,6 +1345,9 @@ class MaaEndUserConfig_Data(BaseModel):
LastProxyStatus: Optional[Literal["未知", "成功", "失败"]] = Field(
default=None, description="上次代理状态"
)
PeriodTaskRecords: Optional[str] = Field(
default=None, description="MaaEnd 每日任务完成记录"
)


class MaaEndUserConfig(BaseModel):
Expand All @@ -1321,6 +1373,9 @@ class MaaEndConfig_Run(BaseModel):
AccountSwitchMethod: Optional[Literal["MAS", "MAAEND"]] = Field(
default=None, description="账号切换方式"
)
TaskTransitionMethod: Optional[Literal["NoAction", "ExitGame"]] = Field(
default=None, description="任务切换方式"
)


class MaaEndConfig_Game(BaseModel):
Expand Down Expand Up @@ -2708,6 +2763,9 @@ class HistoryIndexItem(BaseModel):
date: str = Field(..., description="日期")
status: Literal["DONE", "ERROR"] = Field(..., description="状态")
jsonFile: str = Field(..., description="对应JSON文件")
result: Optional[str] = Field(
default=None, description="运行结果文本,可能带运行阶段前缀"
)


class PullCountStatistics(BaseModel):
Expand Down
8 changes: 8 additions & 0 deletions app/models/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@
class LogRecord:
content: list[str] = field(default_factory=list)
status: str = "未开始监看日志"
# 所属运行阶段(如 MaaEnd 的送货/日常/自动采集),用于历史记录结果前缀
phase: str = ""


@dataclass
Expand Down Expand Up @@ -258,6 +260,9 @@ def result(self) -> str:
class TaskExecuteBase(ABC):
wait_for_finalizer_on_cancel = False

# 外部手动停止(TaskManager 中止)时置位,供收尾逻辑区分自然结束与手动中止
stopped_manually: bool = False

task: asyncio.Task | None = None
_task_group: asyncio.TaskGroup | None = None
accomplish: asyncio.Event = field(default_factory=asyncio.Event)
Expand Down Expand Up @@ -307,6 +312,9 @@ async def _execute_task(self, parent_tg: asyncio.TaskGroup):
self._task_group = parent_tg
try:
await self.main_task()
except asyncio.CancelledError:
self.stopped_manually = True
raise
except Exception as e:
await self.on_crash(e)
finally:
Expand Down
Loading