mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-07-18 23:11:12 +02:00
Comments load over a signed /api/comment/list XHR that TikTok serves to anonymous sessions once the comments panel is opened (unlike profile-video and general-search feeds), so this is a reliable verb. Given video URLs it returns CommentItems (text, author, likes, reply counts; replies carry repliesToId), deduped per video, capped, and degraded to an ErrorItem for empty/withheld videos or a bad_url ErrorItem for non-video inputs. Generalizes the browser capture over a pluggable interaction step so the comments flow (open panel, scroll the panel to paginate) reuses the same warm+capture scaffolding as listing/user-search. Billed per comment on a new TIKTOK_COMMENT meter (TIKTOK_MICROS_PER_COMMENT, matching the per-comment market), surfaced on the chat subagent alongside tiktok.scrape/user_search.
56 lines
1.8 KiB
Python
56 lines
1.8 KiB
Python
"""``tiktok.comments`` I/O contracts.
|
|
|
|
URL-only: given TikTok video URLs, return each video's public comment thread.
|
|
Unlike profile-video/general-search feeds, ``/api/comment/list`` is served to
|
|
anonymous sessions once the comments panel opens, so this verb is reliable. Each
|
|
result is a :class:`CommentItem` (top-level comments; replies carry ``repliesToId``).
|
|
"""
|
|
|
|
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 CommentItem
|
|
|
|
|
|
class CommentsInput(BaseModel):
|
|
video_urls: list[str] = Field(
|
|
min_length=1,
|
|
max_length=MAX_TIKTOK_SOURCES,
|
|
description="TikTok video URLs (/@<user>/video/<id>) to pull comments from.",
|
|
)
|
|
comments_per_video: int = Field(
|
|
default=20,
|
|
ge=1,
|
|
le=MAX_TIKTOK_ITEMS,
|
|
description="Max comments to return per video.",
|
|
)
|
|
max_items: int = Field(
|
|
default=20,
|
|
ge=1,
|
|
le=MAX_TIKTOK_ITEMS,
|
|
description="Max total comments to return across all videos.",
|
|
)
|
|
|
|
@property
|
|
def estimated_units(self) -> int:
|
|
"""Worst-case billable comments for the pre-flight gate: ``max_items`` is a
|
|
hard cross-video ceiling (le=100), so no call can exceed it."""
|
|
return self.max_items
|
|
|
|
|
|
class CommentsOutput(BaseModel):
|
|
items: list[CommentItem] = Field(
|
|
default_factory=list,
|
|
description="One item per comment returned, in emission order.",
|
|
)
|
|
|
|
@property
|
|
def billable_units(self) -> int:
|
|
"""One returned comment = one billable unit; ErrorItems (``errorCode`` set,
|
|
for bad URLs or empty/withheld videos) are surfaced but never charged."""
|
|
return sum(1 for item in self.items if not getattr(item, "errorCode", None))
|