132 lines
4.0 KiB
Python
132 lines
4.0 KiB
Python
"""Sklik (Seznam) - JSON-RPC proxy.
|
|
|
|
The proxy logs in with X-Sklik-Token per request (client.loginByToken) and then
|
|
performs the requested call, injecting the session for you. See
|
|
``app.clients.sklik_client`` and https://api.sklik.cz/drak/ for methods.
|
|
|
|
Credentials: X-Sklik-Token (required).
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from fastapi import APIRouter, Body, Depends, Header, Path
|
|
|
|
from ..clients.sklik_client import SklikClient
|
|
from ..credentials import get_sklik_token
|
|
from ..errors import UpstreamError
|
|
|
|
router = APIRouter(prefix="/sklik", tags=["sklik"])
|
|
|
|
# Entities that expose createReport/readReport for the report helper.
|
|
_REPORT_ENTITIES = {
|
|
"campaigns",
|
|
"groups",
|
|
"ads",
|
|
"keywords",
|
|
"queries",
|
|
"sitelinks",
|
|
"productSets",
|
|
"banners",
|
|
}
|
|
|
|
_REPORT_EXAMPLE = [
|
|
{"dateFrom": "2026-06-01", "dateTo": "2026-06-18", "statGranularity": "daily"},
|
|
{"statGranularity": "daily"},
|
|
]
|
|
|
|
|
|
def _optional_user_id(
|
|
x_sklik_user_id: str | None = Header(
|
|
default=None,
|
|
alias="X-Sklik-User-Id",
|
|
description="Optional managed account id (userId) to act on behalf of, "
|
|
"for agency/MCC access.",
|
|
),
|
|
) -> int | None:
|
|
raw = (x_sklik_user_id or "").strip()
|
|
if not raw:
|
|
return None
|
|
try:
|
|
return int(raw)
|
|
except ValueError as exc:
|
|
raise UpstreamError(
|
|
"X-Sklik-User-Id must be an integer.", status=400
|
|
) from exc
|
|
|
|
|
|
@router.post("/login", summary="Verify the Sklik token (client.loginByToken)")
|
|
async def login(
|
|
token: str = Depends(get_sklik_token),
|
|
user_id: int | None = Depends(_optional_user_id),
|
|
) -> dict:
|
|
"""Check the token works. The session itself is internal and not returned."""
|
|
async with SklikClient(token, user_id=user_id) as client:
|
|
payload = await client.login()
|
|
return {
|
|
"valid": True,
|
|
"status": payload.get("status"),
|
|
"statusMessage": payload.get("statusMessage"),
|
|
}
|
|
|
|
|
|
@router.get("/limits", summary="API limits and quota (api.limits)")
|
|
async def limits(
|
|
token: str = Depends(get_sklik_token),
|
|
user_id: int | None = Depends(_optional_user_id),
|
|
) -> Any:
|
|
async with SklikClient(token, user_id=user_id) as client:
|
|
return await client.call("api.limits")
|
|
|
|
|
|
@router.post(
|
|
"/report/{entity}",
|
|
summary="Create and read a Sklik stats report (createReport + readReport)",
|
|
)
|
|
async def report(
|
|
entity: str = Path(
|
|
...,
|
|
description="Entity to report on: "
|
|
+ ", ".join(sorted(_REPORT_ENTITIES)),
|
|
),
|
|
body: list[Any] = Body(
|
|
...,
|
|
examples=[_REPORT_EXAMPLE],
|
|
description="Arguments for {entity}.createReport (restriction filter and "
|
|
"optional display options). The session is injected automatically.",
|
|
),
|
|
token: str = Depends(get_sklik_token),
|
|
user_id: int | None = Depends(_optional_user_id),
|
|
) -> Any:
|
|
if entity not in _REPORT_ENTITIES:
|
|
raise UpstreamError(
|
|
f"Unsupported report entity '{entity}'. Allowed: "
|
|
+ ", ".join(sorted(_REPORT_ENTITIES)),
|
|
status=400,
|
|
)
|
|
async with SklikClient(token, user_id=user_id) as client:
|
|
return await client.fetch_report(entity, body)
|
|
|
|
|
|
@router.post(
|
|
"/rpc/{method}",
|
|
summary="Generic authenticated Sklik call (any method)",
|
|
)
|
|
async def rpc(
|
|
method: str = Path(
|
|
...,
|
|
description="Sklik method name, e.g. campaigns.list, groups.list, "
|
|
"ads.list, api.limits. (client.loginByToken is managed by the proxy.)",
|
|
),
|
|
args: list[Any] = Body(
|
|
default=[],
|
|
description="Positional arguments AFTER the session struct (which the "
|
|
"proxy injects as the first argument). Example for campaigns.list: "
|
|
'[{"statuses": ["active"]}, {"displayColumns": ["id","name"]}]',
|
|
),
|
|
token: str = Depends(get_sklik_token),
|
|
user_id: int | None = Depends(_optional_user_id),
|
|
) -> Any:
|
|
async with SklikClient(token, user_id=user_id) as client:
|
|
return await client.call(method, args)
|