SurfSense/surfsense_backend/app/capabilities/tiktok/user_search/schemas.py
CREDO23 192b6dc31a feat(tiktok): add tiktok.user_search verb for account discovery
Video/general search is login-walled for anonymous sessions, but the Users
tab (/api/search/user) returns public account records without a redirect, so
this exposes the one reliably-unblocked search path. A keyword yields
TikTokProfileItems (name, followers, bio, verification), deduped per query,
capped, and degraded to an ErrorItem when a query is empty/withheld.

Reuses the browser capture (generalized over XHR markers + extractor) and the
shared profile item shape. Billed per account on a new TIKTOK_USER meter
(TIKTOK_MICROS_PER_USER), surfaced on the chat subagent alongside tiktok.scrape.
2026-07-09 18:00:40 +02:00

56 lines
1.8 KiB
Python

"""``tiktok.user_search`` I/O contracts.
Account discovery over ``TikTok``'s Users tab. Where video/general search is
login-walled for anonymous sessions, ``/api/search/user`` returns public account
records, so this verb exposes the one reliably-unblocked search path. Each result
is a :class:`TikTokProfileItem` (the same shape the profile verb emits).
"""
from __future__ import annotations
from pydantic import BaseModel, Field
from app.capabilities.tiktok.scrape.schemas import (
MAX_TIKTOK_ITEMS,
MAX_TIKTOK_SOURCES,
)
from app.proprietary.platforms.tiktok import TikTokProfileItem
class UserSearchInput(BaseModel):
queries: list[str] = Field(
min_length=1,
max_length=MAX_TIKTOK_SOURCES,
description="Keywords to search for TikTok accounts (e.g. names, brands).",
)
results_per_query: int = Field(
default=10,
ge=1,
le=MAX_TIKTOK_ITEMS,
description="Max accounts to return per query.",
)
max_items: int = Field(
default=10,
ge=1,
le=MAX_TIKTOK_ITEMS,
description="Max total accounts to return across all queries.",
)
@property
def estimated_units(self) -> int:
"""Worst-case billable accounts for the pre-flight gate: ``max_items`` is a
hard cross-query ceiling (le=100), so no call can exceed it."""
return self.max_items
class UserSearchOutput(BaseModel):
items: list[TikTokProfileItem] = Field(
default_factory=list,
description="One item per account found, in emission order.",
)
@property
def billable_units(self) -> int:
"""One returned account = one billable unit; ErrorItems (``errorCode`` set,
for empty/withheld queries) are surfaced but never charged."""
return sum(1 for item in self.items if not getattr(item, "errorCode", None))