158 lines
3.9 KiB
Python
158 lines
3.9 KiB
Python
|
|
"""PA multi-site live status — 4 browser slots for Cockpit."""
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
from datetime import datetime, timezone
|
||
|
|
from typing import Any
|
||
|
|
|
||
|
|
PA_SITES = ("Airbnb", "Booking.com", "DuckDuckGo", "HolidayCheck")
|
||
|
|
|
||
|
|
_state: dict[str, Any] = {
|
||
|
|
"job_id": None,
|
||
|
|
"query": "",
|
||
|
|
"chat_id": None,
|
||
|
|
"user_name": "",
|
||
|
|
"status": "idle",
|
||
|
|
"updated_at": None,
|
||
|
|
"slots": {
|
||
|
|
site: {
|
||
|
|
"label": site,
|
||
|
|
"status": "idle",
|
||
|
|
"url": None,
|
||
|
|
"title": None,
|
||
|
|
"error": None,
|
||
|
|
"index": i + 1,
|
||
|
|
"total": len(PA_SITES),
|
||
|
|
"screenshot_b64": None,
|
||
|
|
"updated_at": None,
|
||
|
|
}
|
||
|
|
for i, site in enumerate(PA_SITES)
|
||
|
|
},
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
def _now() -> str:
|
||
|
|
return datetime.now(timezone.utc).isoformat()
|
||
|
|
|
||
|
|
|
||
|
|
def _slot(label: str) -> dict[str, Any]:
|
||
|
|
slots = _state["slots"]
|
||
|
|
if label not in slots:
|
||
|
|
slots[label] = {
|
||
|
|
"label": label,
|
||
|
|
"status": "idle",
|
||
|
|
"url": None,
|
||
|
|
"title": None,
|
||
|
|
"error": None,
|
||
|
|
"index": 0,
|
||
|
|
"total": len(PA_SITES),
|
||
|
|
"screenshot_b64": None,
|
||
|
|
"updated_at": _now(),
|
||
|
|
}
|
||
|
|
return slots[label]
|
||
|
|
|
||
|
|
|
||
|
|
def start_job(
|
||
|
|
job_id: str,
|
||
|
|
query: str,
|
||
|
|
*,
|
||
|
|
chat_id: int | None = None,
|
||
|
|
user_name: str = "",
|
||
|
|
sites: list[str] | None = None,
|
||
|
|
) -> None:
|
||
|
|
site_list = sites or list(PA_SITES)
|
||
|
|
_state["job_id"] = job_id
|
||
|
|
_state["query"] = query[:200]
|
||
|
|
_state["chat_id"] = chat_id
|
||
|
|
_state["user_name"] = user_name
|
||
|
|
_state["status"] = "running"
|
||
|
|
_state["updated_at"] = _now()
|
||
|
|
_state["slots"] = {
|
||
|
|
site: {
|
||
|
|
"label": site,
|
||
|
|
"status": "waiting",
|
||
|
|
"url": None,
|
||
|
|
"title": None,
|
||
|
|
"error": None,
|
||
|
|
"index": i + 1,
|
||
|
|
"total": len(site_list),
|
||
|
|
"screenshot_b64": None,
|
||
|
|
"updated_at": _now(),
|
||
|
|
}
|
||
|
|
for i, site in enumerate(site_list)
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
def set_comparing() -> None:
|
||
|
|
_state["status"] = "comparing"
|
||
|
|
_state["updated_at"] = _now()
|
||
|
|
|
||
|
|
|
||
|
|
def finish_job() -> None:
|
||
|
|
_state["status"] = "done"
|
||
|
|
_state["updated_at"] = _now()
|
||
|
|
|
||
|
|
|
||
|
|
def fail_job(error: str) -> None:
|
||
|
|
_state["status"] = "failed"
|
||
|
|
_state["updated_at"] = _now()
|
||
|
|
_state["error"] = error[:200]
|
||
|
|
|
||
|
|
|
||
|
|
def update_slot(
|
||
|
|
label: str,
|
||
|
|
*,
|
||
|
|
status: str,
|
||
|
|
url: str | None = None,
|
||
|
|
title: str | None = None,
|
||
|
|
error: str | None = None,
|
||
|
|
index: int | None = None,
|
||
|
|
total: int | None = None,
|
||
|
|
screenshot_b64: str | None = None,
|
||
|
|
) -> None:
|
||
|
|
slot = _slot(label)
|
||
|
|
slot["status"] = status
|
||
|
|
slot["updated_at"] = _now()
|
||
|
|
if url is not None:
|
||
|
|
slot["url"] = url
|
||
|
|
if title is not None:
|
||
|
|
slot["title"] = title
|
||
|
|
if error is not None:
|
||
|
|
slot["error"] = error[:300]
|
||
|
|
if index is not None:
|
||
|
|
slot["index"] = index
|
||
|
|
if total is not None:
|
||
|
|
slot["total"] = total
|
||
|
|
if screenshot_b64 is not None:
|
||
|
|
slot["screenshot_b64"] = screenshot_b64
|
||
|
|
_state["updated_at"] = _now()
|
||
|
|
|
||
|
|
|
||
|
|
def get_live() -> dict[str, Any]:
|
||
|
|
out = {
|
||
|
|
"job_id": _state.get("job_id"),
|
||
|
|
"query": _state.get("query"),
|
||
|
|
"chat_id": _state.get("chat_id"),
|
||
|
|
"user_name": _state.get("user_name"),
|
||
|
|
"status": _state.get("status", "idle"),
|
||
|
|
"updated_at": _state.get("updated_at"),
|
||
|
|
"sites": list(PA_SITES),
|
||
|
|
"slots": [],
|
||
|
|
}
|
||
|
|
for site in PA_SITES:
|
||
|
|
slot = dict(_state["slots"].get(site) or _slot(site))
|
||
|
|
slot.pop("screenshot_b64", None)
|
||
|
|
slot["has_screenshot"] = bool(
|
||
|
|
(_state["slots"].get(site) or {}).get("screenshot_b64")
|
||
|
|
)
|
||
|
|
out["slots"].append(slot)
|
||
|
|
return out
|
||
|
|
|
||
|
|
|
||
|
|
def get_slot_screenshot(label: str) -> bytes | None:
|
||
|
|
slot = _state["slots"].get(label)
|
||
|
|
if not slot or not slot.get("screenshot_b64"):
|
||
|
|
return None
|
||
|
|
import base64
|
||
|
|
|
||
|
|
return base64.b64decode(slot["screenshot_b64"])
|