feat: 增加桌面端登录、视频清单和封面缓存

This commit is contained in:
Nixevol
2026-09-27 03:57:00 +08:00
parent 0c1e04464a
commit cc930cd1ed
69 changed files with 9895 additions and 0 deletions
View File
+11
View File
@@ -0,0 +1,11 @@
from fastapi import APIRouter
from server.errors import call
from server.services.adb import Adb
router = APIRouter(prefix="/api/adb")
@router.get("/devices")
def devices():
return call(lambda: {"devices": Adb.locate().list_devices()})
+28
View File
@@ -0,0 +1,28 @@
from fastapi import APIRouter
from server.errors import call
from server.schemas import AdbBody, GuestBody, PasswordBody, device_payload
from server.services.login import login
router = APIRouter(prefix="/api/login")
@router.post("/guest")
def login_guest(body: GuestBody | None = None):
raw = device_payload(body.device) if body else None
return call(lambda: login.guest(raw))
@router.post("/password")
def login_password(body: PasswordBody):
return call(lambda: login.password(body.email, body.password, device_payload(body.device)))
@router.post("/adb")
def login_adb(body: AdbBody):
return call(lambda: login.adb(
body.serial,
body.adb,
body.device_source,
device_payload(body.device),
))
+27
View File
@@ -0,0 +1,27 @@
import json
from fastapi import APIRouter, HTTPException
from fastapi.responses import StreamingResponse
from server.schemas import SessionBody, device_payload
from server.services.catalog import catalog
router = APIRouter(prefix="/api")
@router.post("/catalog")
async def load_catalog(body: SessionBody):
token = body.token.strip()
if not token:
raise HTTPException(status_code=400, detail="请先登录")
raw_device = device_payload(body.device)
async def generate():
try:
async for event in catalog.stream(token, raw_device):
yield json.dumps(event, ensure_ascii=False) + "\n"
except Exception as exc:
message = str(exc) or "获取清单失败"
yield json.dumps({"type": "error", "message": message}, ensure_ascii=False) + "\n"
return StreamingResponse(generate(), media_type="application/x-ndjson")
+19
View File
@@ -0,0 +1,19 @@
from fastapi import APIRouter, Response
from server.services.covers import covers
router = APIRouter(prefix="/api/covers")
@router.get("/{digest}")
def cover(digest: str):
try:
data, media = covers.read(digest)
except RuntimeError as exc:
from fastapi import HTTPException
raise HTTPException(status_code=404, detail=str(exc)) from exc
return Response(
content=data,
media_type=media,
headers={"Cache-Control": "public, max-age=31536000, immutable"},
)
+10
View File
@@ -0,0 +1,10 @@
from fastapi import APIRouter
from server.services.device import DeviceProfile
router = APIRouter(prefix="/api/device")
@router.get("/defaults")
def defaults():
return DeviceProfile.builtin().as_dict()
+17
View File
@@ -0,0 +1,17 @@
from fastapi import APIRouter
from server.errors import call
from server.schemas import ProxyBody
from server.services.http import http
router = APIRouter(prefix="/api")
@router.post("/proxy")
def set_proxy(body: ProxyBody):
return call(lambda: _apply(body))
def _apply(body: ProxyBody) -> dict:
http.configure_proxy(body.model_dump())
return {"ok": True}
+30
View File
@@ -0,0 +1,30 @@
from fastapi import APIRouter, HTTPException
from server.errors import call
from server.schemas import SessionBody, device_payload
from server.services.login import login
from server.services.profile_poller import profile_poller
from server.services.sessions import sessions
router = APIRouter(prefix="/api")
@router.post("/me")
def me(body: SessionBody):
return call(lambda: login.current(body.token, device_payload(body.device)))
@router.get("/profile")
def profile():
data = sessions.snapshot()
if data is None:
raise HTTPException(status_code=404, detail="未登录")
return data
@router.post("/logout")
def logout(body: SessionBody):
token = body.token.strip()
profile_poller.stop_if(token)
sessions.clear(token)
return {"ok": True}