|
@@ -0,0 +1,107 @@
|
|
|
|
|
+"""PipelineContext — 一次登录会话贯穿九任务; TaskResult 与打勾落盘。"""
|
|
|
|
|
+from __future__ import annotations
|
|
|
|
|
+
|
|
|
|
|
+import logging
|
|
|
|
|
+import time
|
|
|
|
|
+from dataclasses import dataclass, field
|
|
|
|
|
+from typing import TYPE_CHECKING, Final
|
|
|
|
|
+
|
|
|
|
|
+import requests
|
|
|
|
|
+
|
|
|
|
|
+from pbot.bot.runner import pump as _pump
|
|
|
|
|
+
|
|
|
|
|
+if TYPE_CHECKING:
|
|
|
|
|
+ from pathlib import Path
|
|
|
|
|
+
|
|
|
|
|
+ from pbot.transport.gateway import GatewayConnection
|
|
|
|
|
+ from pbot.types import ZoneId
|
|
|
|
|
+ from pbot.world.mirror import WorldMirror
|
|
|
|
|
+ from pbot.world.update import WorldUpdater
|
|
|
|
|
+
|
|
|
|
|
+_FMT: Final[str] = "%Y-%m-%d %H:%M:%S"
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+@dataclass
|
|
|
|
|
+class TaskResult:
|
|
|
|
|
+ """单任务结果。"""
|
|
|
|
|
+
|
|
|
|
|
+ ok: bool
|
|
|
|
|
+ detail: str = ""
|
|
|
|
|
+
|
|
|
|
|
+ @staticmethod
|
|
|
|
|
+ def done(detail: str = "") -> TaskResult:
|
|
|
|
|
+ """成功结果。"""
|
|
|
|
|
+ return TaskResult(ok=True, detail=detail)
|
|
|
|
|
+
|
|
|
|
|
+ @staticmethod
|
|
|
|
|
+ def fail(detail: str) -> TaskResult:
|
|
|
|
|
+ """失败结果。"""
|
|
|
|
|
+ return TaskResult(ok=False, detail=detail)
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+class ChecklistWriter:
|
|
|
|
|
+ """打勾文件实时写: 每任务一行, 完成即勾, 失败即叉, 支持更新。"""
|
|
|
|
|
+
|
|
|
|
|
+ def __init__(self, path: Path, zone_label: str, role: str) -> None:
|
|
|
|
|
+ """首写 header, 建立任务清单骨架。"""
|
|
|
|
|
+ self._path: Path = path
|
|
|
|
|
+ self._role: str = role
|
|
|
|
|
+ self._marks: dict[str, tuple[bool, str]] = {}
|
|
|
|
|
+ self._prefix: list[str] = [
|
|
|
|
|
+ f"# 传奇流水线进度 — {zone_label} — {role}\n",
|
|
|
|
|
+ "\n",
|
|
|
|
|
+ f"- 启动: {time.strftime(_FMT)}\n",
|
|
|
|
|
+ "\n",
|
|
|
|
|
+ ]
|
|
|
|
|
+ path.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
+ self._rewrite()
|
|
|
|
|
+
|
|
|
|
|
+ def _rewrite(self) -> None:
|
|
|
|
|
+ lines = [*self._prefix, "## 任务清单\n\n"]
|
|
|
|
|
+ for t, (o, d) in self._marks.items():
|
|
|
|
|
+ mark = "☑" if o else "☒"
|
|
|
|
|
+ lines.append(f"- {time.strftime(_FMT)} {mark} **{t}**"
|
|
|
|
|
+ + (f" — {d}" if d else "") + "\n")
|
|
|
|
|
+ tmp = self._path.with_suffix(".tmp")
|
|
|
|
|
+ _ = tmp.write_text("".join(lines), encoding="utf-8")
|
|
|
|
|
+ _ = tmp.replace(self._path)
|
|
|
|
|
+
|
|
|
|
|
+ def note(self, text: str) -> None:
|
|
|
|
|
+ """追加一条备注行(重写时保留)。"""
|
|
|
|
|
+ line = f"> {time.strftime(_FMT)} {text}\n"
|
|
|
|
|
+ self._prefix.append(line)
|
|
|
|
|
+ self._rewrite()
|
|
|
|
|
+
|
|
|
|
|
+ def mark(self, title: str, ok: bool, detail: str = "") -> None:
|
|
|
|
|
+ """勾/叉一个任务并实时重写文件, 同名任务更新而非追加。"""
|
|
|
|
|
+ self._marks[title] = (ok, detail)
|
|
|
|
|
+ self._rewrite()
|
|
|
|
|
+ mark = "✓" if ok else "✗"
|
|
|
|
|
+ print( # noqa: T201 — 流水线控制台实时进度为硬需求
|
|
|
|
|
+ f"[{mark}] {title}" + (f" — {detail}" if detail else ""), flush=True)
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+@dataclass
|
|
|
|
|
+class PipelineContext:
|
|
|
|
|
+ """共用会话上下文: 登录一次, 九任务复用 conn/world/sid。"""
|
|
|
|
|
+
|
|
|
|
|
+ user: str
|
|
|
|
|
+ password: str
|
|
|
|
|
+ zone: ZoneId
|
|
|
|
|
+ zone_label: str
|
|
|
|
|
+ role_name: str
|
|
|
|
|
+ woma_minutes: float
|
|
|
|
|
+ conn: GatewayConnection
|
|
|
|
|
+ world: WorldMirror
|
|
|
|
|
+ updater: WorldUpdater
|
|
|
|
|
+ sid: int
|
|
|
|
|
+ stop: list[bool]
|
|
|
|
|
+ checklist: ChecklistWriter
|
|
|
|
|
+ http: requests.Session = field(default_factory=requests.Session)
|
|
|
|
|
+ jwt: str = ""
|
|
|
|
|
+ uid: int = 0
|
|
|
|
|
+ log: logging.Logger = field(default_factory=lambda: logging.getLogger("pipeline"))
|
|
|
|
|
+
|
|
|
|
|
+ def pump(self, window: float = 0.5) -> None:
|
|
|
|
|
+ """收帧窗口(转发 runner.pump)。"""
|
|
|
|
|
+ _ = _pump(self.conn, self.world, self.updater, window)
|