feat(site): add POST /site/cookie/{site_id} endpoint for updating site Cookie&UA via request body

- Introduce SiteCookieUpdate schema for structured cookie update requests
- Add POST endpoint to update site Cookie&UA using JSON body
- Refactor cookie update logic into shared _update_site_cookie function
- Update SKILL.md to document new endpoint and endpoint count
- Add tests for both POST and legacy GET cookie update endpoints
This commit is contained in:
jxxghp
2026-05-31 09:11:09 +08:00
parent ac09ce5230
commit 16ada1a6c4
4 changed files with 139 additions and 14 deletions

View File

@@ -26,6 +26,7 @@ from app.db.user_oper import (
get_current_active_superuser_async,
)
from app.helper.sites import SitesHelper # noqa
from app.log import logger
from app.scheduler import Scheduler
from app.schemas.types import SystemConfigKey, EventType
from app.utils.string import StringUtils
@@ -161,6 +162,61 @@ async def update_sites_priority(
return schemas.Response(success=True)
def _update_site_cookie(
site_id: int,
username: str,
password: str,
code: Optional[str],
db: Session,
) -> schemas.Response:
"""
执行站点 Cookie 与 UA 更新。
:param site_id: 站点编号
:param username: 站点登录用户名
:param password: 站点登录密码
:param code: 二步验证码或密钥
:param db: 数据库会话
:return: 更新结果
"""
site_info = Site.get(db, site_id)
if not site_info:
raise HTTPException(
status_code=404,
detail=f"站点 {site_id} 不存在!",
)
logger.info(f"开始更新站点【{site_info.name}】Cookie&UA")
state, message = SiteChain().update_cookie(
site_info=site_info, username=username, password=password, two_step_code=code
)
if state:
logger.info(f"站点【{site_info.name}】Cookie&UA更新成功")
else:
logger.error(f"站点【{site_info.name}】Cookie&UA更新失败{message}")
return schemas.Response(success=state, message=message)
@router.post(
"/cookie/{site_id}", summary="更新站点Cookie&UA", response_model=schemas.Response
)
def update_cookie_by_body(
site_id: int,
site_cookie_update: schemas.SiteCookieUpdate,
db: Session = Depends(get_db),
_: User = Depends(get_current_active_superuser),
) -> Any:
"""
使用请求体中的用户密码更新站点Cookie
"""
return _update_site_cookie(
site_id=site_id,
username=site_cookie_update.username,
password=site_cookie_update.password,
code=site_cookie_update.code,
db=db,
)
@router.get(
"/cookie/{site_id}", summary="更新站点Cookie&UA", response_model=schemas.Response
)
@@ -175,18 +231,13 @@ def update_cookie(
"""
使用用户密码更新站点Cookie
"""
# 查询站点
site_info = Site.get(db, site_id)
if not site_info:
raise HTTPException(
status_code=404,
detail=f"站点 {site_id} 不存在!",
)
# 更新Cookie
state, message = SiteChain().update_cookie(
site_info=site_info, username=username, password=password, two_step_code=code
return _update_site_cookie(
site_id=site_id,
username=username,
password=password,
code=code,
db=db,
)
return schemas.Response(success=state, message=message)
@router.post(

View File

@@ -115,6 +115,15 @@ class SiteAuth(BaseModel):
params: Optional[Dict[str, Union[int, str]]] = Field(default_factory=dict)
class SiteCookieUpdate(BaseModel):
"""
站点 Cookie 与 UA 更新请求。
"""
username: str = Field(..., description="站点登录用户名")
password: str = Field(..., description="站点登录密码")
code: Optional[str] = Field(None, description="二步验证码或密钥")
class SiteCategory(BaseModel):
id: Optional[int] = None
cat: Optional[str] = None

View File

@@ -1,7 +1,7 @@
---
name: moviepilot-api
version: 1
description: Use this skill when you need to call MoviePilot REST API endpoints directly. Covers all 237 API endpoints across 27 categories including media search, downloads, subscriptions, library management, site management, system administration, plugins, workflows, and more. Use this skill whenever the user asks to interact with MoviePilot via its HTTP API, or when the moviepilot-cli skill cannot cover a specific operation.
description: Use this skill when you need to call MoviePilot REST API endpoints directly. Covers all 238 API endpoints across 27 categories including media search, downloads, subscriptions, library management, site management, system administration, plugins, workflows, and more. Use this skill whenever the user asks to interact with MoviePilot via its HTTP API, or when the moviepilot-cli skill cannot cover a specific operation.
---
# MoviePilot REST API
@@ -161,7 +161,7 @@ All endpoints are under the base URL `{MP_HOST}`. Path parameters are shown as `
| GET | `/api/v1/subscribe/shares` | List shared subscriptions. Params: `name`, `page`, `count`, `genre_id`, `min_rating`, `max_rating`, `sort_type` |
| GET | `/api/v1/subscribe/share/statistics` | Share statistics |
### Site (24 endpoints)
### Site (25 endpoints)
| Method | Path | Description |
|--------|------|-------------|
@@ -174,7 +174,8 @@ All endpoints are under the base URL `{MP_HOST}`. Path parameters are shown as `
| GET | `/api/v1/site/cookiecloud` | Sync CookieCloud |
| GET | `/api/v1/site/reset` | Reset sites |
| POST | `/api/v1/site/priorities` | Batch update site priorities. Body: array |
| GET | `/api/v1/site/cookie/{site_id}` | Update site cookie & UA. Params: `username`, `password`, `code` |
| POST | `/api/v1/site/cookie/{site_id}` | Update site cookie & UA. Body: `SiteCookieUpdate` JSON |
| GET | `/api/v1/site/cookie/{site_id}` | Legacy update site cookie & UA. Params: `username`, `password`, `code` |
| POST | `/api/v1/site/userdata/{site_id}` | Refresh site user data |
| GET | `/api/v1/site/userdata/{site_id}` | Get site user data. Params: `workdate` |
| GET | `/api/v1/site/userdata/latest` | All sites latest user data |

View File

@@ -0,0 +1,64 @@
from types import SimpleNamespace
from unittest.mock import Mock, patch
from app import schemas
from app.api.endpoints import site as site_endpoint
def test_update_cookie_by_body_uses_request_body():
"""
POST 更新站点 Cookie 时应从请求体读取登录参数。
"""
fake_site = SimpleNamespace(id=1, name="TestSite")
fake_chain = Mock()
fake_chain.update_cookie.return_value = (True, "ok")
request = schemas.SiteCookieUpdate(username="user", password="password", code="123456")
with patch.object(site_endpoint.Site, "get", return_value=fake_site), patch.object(
site_endpoint, "SiteChain", return_value=fake_chain
):
response = site_endpoint.update_cookie_by_body(
site_id=1,
site_cookie_update=request,
db=Mock(),
_=Mock(),
)
assert response.success is True
assert response.message == "ok"
fake_chain.update_cookie.assert_called_once_with(
site_info=fake_site,
username="user",
password="password",
two_step_code="123456",
)
def test_update_cookie_legacy_get_keeps_query_params():
"""
旧 GET 入口仍应兼容查询参数更新站点 Cookie。
"""
fake_site = SimpleNamespace(id=1, name="TestSite")
fake_chain = Mock()
fake_chain.update_cookie.return_value = (False, "failed")
with patch.object(site_endpoint.Site, "get", return_value=fake_site), patch.object(
site_endpoint, "SiteChain", return_value=fake_chain
):
response = site_endpoint.update_cookie(
site_id=1,
username="user",
password="password",
code=None,
db=Mock(),
_=Mock(),
)
assert response.success is False
assert response.message == "failed"
fake_chain.update_cookie.assert_called_once_with(
site_info=fake_site,
username="user",
password="password",
two_step_code=None,
)