mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-07-16 23:01:06 +02:00
feat(instagram): add comments capability verb
This commit is contained in:
parent
ff55537bce
commit
38a26b1ccf
4 changed files with 139 additions and 0 deletions
|
|
@ -0,0 +1,3 @@
|
|||
"""``instagram.comments`` verb: post/reel URLs → comments (and replies)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
"""``instagram.comments`` capability registration (billed per comment; see config
|
||||
``INSTAGRAM_SCRAPE_MICROS_PER_COMMENT``)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from app.capabilities.core import BillingUnit, Capability, register_capability
|
||||
from app.capabilities.instagram.comments.executor import build_comments_executor
|
||||
from app.capabilities.instagram.comments.schemas import CommentsInput, CommentsOutput
|
||||
|
||||
INSTAGRAM_COMMENTS = Capability(
|
||||
name="instagram.comments",
|
||||
description=(
|
||||
"Fetch comments (and optionally replies) for Instagram post or reel URLs."
|
||||
),
|
||||
input_schema=CommentsInput,
|
||||
output_schema=CommentsOutput,
|
||||
executor=build_comments_executor(),
|
||||
billing_unit=BillingUnit.INSTAGRAM_COMMENT,
|
||||
docs_url="/docs/connectors/native/instagram",
|
||||
)
|
||||
|
||||
register_capability(INSTAGRAM_COMMENTS)
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
"""``instagram.comments`` executor: verb input → scraper → comment items."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Awaitable, Callable
|
||||
|
||||
from app.capabilities.core import Executor
|
||||
from app.capabilities.core.progress import emit_progress
|
||||
from app.capabilities.instagram.comments.schemas import CommentsInput, CommentsOutput
|
||||
from app.exceptions import ForbiddenError
|
||||
from app.proprietary.platforms.instagram import (
|
||||
InstagramAccessBlockedError,
|
||||
InstagramScrapeInput,
|
||||
scrape_instagram,
|
||||
)
|
||||
|
||||
ScrapeFn = Callable[..., Awaitable[list[dict]]]
|
||||
|
||||
|
||||
def build_comments_executor(scrape_fn: ScrapeFn | None = None) -> Executor:
|
||||
"""Bind the executor to a scraper fn (defaults to the proprietary actor)."""
|
||||
scrape_fn = scrape_fn or scrape_instagram
|
||||
|
||||
async def execute(payload: CommentsInput) -> CommentsOutput:
|
||||
actor_input = InstagramScrapeInput(
|
||||
resultsType="comments",
|
||||
directUrls=payload.urls,
|
||||
resultsLimit=payload.max_comments_per_post,
|
||||
isNewestComments=payload.newest_first,
|
||||
includeNestedComments=payload.include_replies,
|
||||
)
|
||||
emit_progress(
|
||||
"starting",
|
||||
"Fetching Instagram comments",
|
||||
total=payload.max_items,
|
||||
unit="comment",
|
||||
)
|
||||
try:
|
||||
items = await scrape_fn(actor_input, limit=payload.max_items)
|
||||
except InstagramAccessBlockedError as exc:
|
||||
raise ForbiddenError(
|
||||
f"Instagram refused anonymous access: {exc}",
|
||||
code="INSTAGRAM_ACCESS_BLOCKED",
|
||||
) from exc
|
||||
emit_progress(
|
||||
"done",
|
||||
f"Scraped {len(items)} comment(s)",
|
||||
current=len(items),
|
||||
unit="comment",
|
||||
)
|
||||
return CommentsOutput(items=items)
|
||||
|
||||
return execute
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
"""``instagram.comments`` I/O contracts.
|
||||
|
||||
A lean surface over ``InstagramScrapeInput`` (``resultsType="comments"``). The
|
||||
scraper's ``InstagramComment`` is reused verbatim as the output element.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from app.capabilities.instagram.scrape.schemas import (
|
||||
MAX_INSTAGRAM_ITEMS,
|
||||
MAX_INSTAGRAM_SOURCES,
|
||||
)
|
||||
from app.proprietary.platforms.instagram import InstagramComment
|
||||
|
||||
MAX_COMMENTS_PER_POST = 50
|
||||
"""Anonymous web media pages surface at most ~50 comments per post."""
|
||||
|
||||
|
||||
class CommentsInput(BaseModel):
|
||||
urls: list[str] = Field(
|
||||
min_length=1,
|
||||
max_length=MAX_INSTAGRAM_SOURCES,
|
||||
description="Post or reel URLs to fetch comments for (shortCode or numeric-ID forms).",
|
||||
)
|
||||
newest_first: bool = Field(
|
||||
default=False,
|
||||
description="Return newest comments first.",
|
||||
)
|
||||
include_replies: bool = Field(
|
||||
default=False,
|
||||
description="Include nested replies; each reply is a separate billable item.",
|
||||
)
|
||||
max_comments_per_post: int = Field(
|
||||
default=10,
|
||||
ge=1,
|
||||
le=MAX_COMMENTS_PER_POST,
|
||||
description="Max comments per post (Instagram caps at 50).",
|
||||
)
|
||||
max_items: int = Field(
|
||||
default=10,
|
||||
ge=1,
|
||||
le=MAX_INSTAGRAM_ITEMS,
|
||||
description="Max total comments to return across all posts.",
|
||||
)
|
||||
|
||||
@property
|
||||
def estimated_units(self) -> int:
|
||||
return self.max_items
|
||||
|
||||
|
||||
class CommentsOutput(BaseModel):
|
||||
items: list[InstagramComment] = Field(
|
||||
default_factory=list,
|
||||
description="One item per comment (or reply), in emission order.",
|
||||
)
|
||||
|
||||
@property
|
||||
def billable_units(self) -> int:
|
||||
return len(self.items)
|
||||
Loading…
Add table
Add a link
Reference in a new issue