255 lines
9.8 KiB
Python
255 lines
9.8 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
from pathlib import Path
|
|
import re
|
|
|
|
from biliup_next.core.models import ActionRecord, SessionBinding, TaskContext, utc_now_iso
|
|
|
|
|
|
class SessionDeliveryService:
|
|
def __init__(self, state: dict[str, object]):
|
|
self.state = state
|
|
self.repo = state["repo"]
|
|
self.settings = state["settings"]
|
|
|
|
def bind_task_full_video(self, task_id: str, full_video_bvid: str) -> dict[str, object]:
|
|
task = self.repo.get_task(task_id)
|
|
if task is None:
|
|
return {"error": {"code": "TASK_NOT_FOUND", "message": f"task not found: {task_id}"}}
|
|
|
|
bvid = self._normalize_bvid(full_video_bvid)
|
|
if bvid is None:
|
|
return {"error": {"code": "INVALID_BVID", "message": f"invalid bvid: {full_video_bvid}"}}
|
|
|
|
now = utc_now_iso()
|
|
context = self.repo.get_task_context(task_id)
|
|
if context is None:
|
|
context = TaskContext(
|
|
id=None,
|
|
task_id=task.id,
|
|
session_key=f"task:{task.id}",
|
|
streamer=None,
|
|
room_id=None,
|
|
source_title=task.title,
|
|
segment_started_at=None,
|
|
segment_duration_seconds=None,
|
|
full_video_bvid=bvid,
|
|
created_at=task.created_at,
|
|
updated_at=now,
|
|
)
|
|
full_video_bvid_path = self._persist_task_full_video_bvid(task, context, bvid, now=now)
|
|
return {
|
|
"task_id": task.id,
|
|
"session_key": context.session_key,
|
|
"full_video_bvid": bvid,
|
|
"path": str(full_video_bvid_path),
|
|
}
|
|
|
|
def rebind_session_full_video(self, session_key: str, full_video_bvid: str) -> dict[str, object]:
|
|
bvid = self._normalize_bvid(full_video_bvid)
|
|
if bvid is None:
|
|
return {"error": {"code": "INVALID_BVID", "message": f"invalid bvid: {full_video_bvid}"}}
|
|
|
|
contexts = self.repo.list_task_contexts_by_session_key(session_key)
|
|
if not contexts:
|
|
return {"error": {"code": "SESSION_NOT_FOUND", "message": f"session not found: {session_key}"}}
|
|
|
|
now = utc_now_iso()
|
|
self.repo.update_session_full_video_bvid(session_key, bvid, now)
|
|
|
|
updated_tasks: list[dict[str, object]] = []
|
|
for context in contexts:
|
|
task = self.repo.get_task(context.task_id)
|
|
if task is None:
|
|
continue
|
|
full_video_bvid_path = self._persist_task_full_video_bvid(task, context, bvid, now=now)
|
|
updated_tasks.append({"task_id": task.id, "path": str(full_video_bvid_path)})
|
|
|
|
return {
|
|
"session_key": session_key,
|
|
"full_video_bvid": bvid,
|
|
"updated_count": len(updated_tasks),
|
|
"tasks": updated_tasks,
|
|
}
|
|
|
|
def merge_session(self, session_key: str, task_ids: list[str]) -> dict[str, object]:
|
|
normalized_task_ids: list[str] = []
|
|
for raw in task_ids:
|
|
task_id = str(raw).strip()
|
|
if task_id and task_id not in normalized_task_ids:
|
|
normalized_task_ids.append(task_id)
|
|
if not normalized_task_ids:
|
|
return {"error": {"code": "TASK_IDS_EMPTY", "message": "task_ids is empty"}}
|
|
|
|
now = utc_now_iso()
|
|
inherited_bvid = None
|
|
existing_contexts = self.repo.list_task_contexts_by_session_key(session_key)
|
|
for context in existing_contexts:
|
|
if context.full_video_bvid:
|
|
inherited_bvid = context.full_video_bvid
|
|
break
|
|
|
|
merged_tasks: list[dict[str, object]] = []
|
|
missing_tasks: list[str] = []
|
|
|
|
for task_id in normalized_task_ids:
|
|
task = self.repo.get_task(task_id)
|
|
if task is None:
|
|
missing_tasks.append(task_id)
|
|
continue
|
|
|
|
context = self.repo.get_task_context(task_id)
|
|
if context is None:
|
|
context = TaskContext(
|
|
id=None,
|
|
task_id=task.id,
|
|
session_key=session_key,
|
|
streamer=None,
|
|
room_id=None,
|
|
source_title=task.title,
|
|
segment_started_at=None,
|
|
segment_duration_seconds=None,
|
|
full_video_bvid=inherited_bvid,
|
|
created_at=task.created_at,
|
|
updated_at=now,
|
|
)
|
|
else:
|
|
context.session_key = session_key
|
|
context.updated_at = now
|
|
if inherited_bvid and not context.full_video_bvid:
|
|
context.full_video_bvid = inherited_bvid
|
|
self.repo.upsert_task_context(context)
|
|
|
|
if context.full_video_bvid:
|
|
full_video_bvid_path = self._persist_task_full_video_bvid(task, context, context.full_video_bvid, now=now)
|
|
else:
|
|
full_video_bvid_path = None
|
|
|
|
payload = {
|
|
"task_id": task.id,
|
|
"session_key": session_key,
|
|
"full_video_bvid": context.full_video_bvid,
|
|
}
|
|
if full_video_bvid_path is not None:
|
|
payload["path"] = str(full_video_bvid_path)
|
|
merged_tasks.append(payload)
|
|
|
|
return {
|
|
"session_key": session_key,
|
|
"merged_count": len(merged_tasks),
|
|
"tasks": merged_tasks,
|
|
"missing_task_ids": missing_tasks,
|
|
}
|
|
|
|
def receive_full_video_webhook(self, payload: dict[str, object]) -> dict[str, object]:
|
|
raw_bvid = str(payload.get("full_video_bvid") or payload.get("bvid") or "").strip()
|
|
bvid = self._normalize_bvid(raw_bvid)
|
|
if bvid is None:
|
|
return {"error": {"code": "INVALID_BVID", "message": f"invalid bvid: {raw_bvid}"}}
|
|
|
|
session_key = str(payload.get("session_key") or "").strip() or None
|
|
source_title = str(payload.get("source_title") or "").strip() or None
|
|
streamer = str(payload.get("streamer") or "").strip() or None
|
|
room_id = str(payload.get("room_id") or "").strip() or None
|
|
if session_key is None and source_title is None:
|
|
return {"error": {"code": "SESSION_KEY_OR_SOURCE_TITLE_REQUIRED", "message": "session_key or source_title required"}}
|
|
|
|
now = utc_now_iso()
|
|
self.repo.upsert_session_binding(
|
|
SessionBinding(
|
|
id=None,
|
|
session_key=session_key,
|
|
source_title=source_title,
|
|
streamer=streamer,
|
|
room_id=room_id,
|
|
full_video_bvid=bvid,
|
|
created_at=now,
|
|
updated_at=now,
|
|
)
|
|
)
|
|
|
|
contexts = self.repo.list_task_contexts_by_session_key(session_key) if session_key else []
|
|
if not contexts and source_title:
|
|
contexts = self.repo.list_task_contexts_by_source_title(source_title)
|
|
|
|
updated_tasks: list[dict[str, object]] = []
|
|
for context in contexts:
|
|
task = self.repo.get_task(context.task_id)
|
|
if task is None:
|
|
continue
|
|
if session_key and (context.session_key.startswith("task:") or context.session_key != session_key):
|
|
context.session_key = session_key
|
|
full_video_bvid_path = self._persist_task_full_video_bvid(task, context, bvid, now=now)
|
|
updated_tasks.append({"task_id": task.id, "path": str(full_video_bvid_path)})
|
|
|
|
self.repo.add_action_record(
|
|
ActionRecord(
|
|
id=None,
|
|
task_id=None,
|
|
action_name="webhook_full_video_uploaded",
|
|
status="ok",
|
|
summary=f"full video webhook received: {bvid}",
|
|
details_json=json.dumps(
|
|
{
|
|
"session_key": session_key,
|
|
"source_title": source_title,
|
|
"streamer": streamer,
|
|
"room_id": room_id,
|
|
"updated_count": len(updated_tasks),
|
|
},
|
|
ensure_ascii=False,
|
|
),
|
|
created_at=now,
|
|
)
|
|
)
|
|
return {
|
|
"ok": True,
|
|
"session_key": session_key,
|
|
"source_title": source_title,
|
|
"full_video_bvid": bvid,
|
|
"updated_count": len(updated_tasks),
|
|
"tasks": updated_tasks,
|
|
}
|
|
|
|
def _normalize_bvid(self, full_video_bvid: str) -> str | None:
|
|
bvid = full_video_bvid.strip()
|
|
if not re.fullmatch(r"BV[0-9A-Za-z]+", bvid):
|
|
return None
|
|
return bvid
|
|
|
|
def _full_video_bvid_path(self, task_title: str) -> Path:
|
|
session_dir = Path(str(self.settings["paths"]["session_dir"])) / task_title
|
|
session_dir.mkdir(parents=True, exist_ok=True)
|
|
return session_dir / "full_video_bvid.txt"
|
|
|
|
def _upsert_session_binding_for_context(self, context: TaskContext, full_video_bvid: str, now: str) -> None:
|
|
self.repo.upsert_session_binding(
|
|
SessionBinding(
|
|
id=None,
|
|
session_key=context.session_key,
|
|
source_title=context.source_title,
|
|
streamer=context.streamer,
|
|
room_id=context.room_id,
|
|
full_video_bvid=full_video_bvid,
|
|
created_at=now,
|
|
updated_at=now,
|
|
)
|
|
)
|
|
|
|
def _persist_task_full_video_bvid(
|
|
self,
|
|
task,
|
|
context: TaskContext,
|
|
full_video_bvid: str,
|
|
*,
|
|
now: str,
|
|
) -> Path: # type: ignore[no-untyped-def]
|
|
context.full_video_bvid = full_video_bvid
|
|
context.updated_at = now
|
|
self.repo.upsert_task_context(context)
|
|
self._upsert_session_binding_for_context(context, full_video_bvid, now)
|
|
path = self._full_video_bvid_path(task.title)
|
|
path.write_text(full_video_bvid, encoding="utf-8")
|
|
return path
|