socialscrape-cli 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- socialscrape_cli/__init__.py +3 -0
- socialscrape_cli/__main__.py +5 -0
- socialscrape_cli/catalog.json +18929 -0
- socialscrape_cli/catalog.py +166 -0
- socialscrape_cli/cli.py +689 -0
- socialscrape_cli/client.py +104 -0
- socialscrape_cli/config.py +40 -0
- socialscrape_cli/table.py +76 -0
- socialscrape_cli-0.1.0.dist-info/METADATA +76 -0
- socialscrape_cli-0.1.0.dist-info/RECORD +14 -0
- socialscrape_cli-0.1.0.dist-info/WHEEL +5 -0
- socialscrape_cli-0.1.0.dist-info/entry_points.txt +2 -0
- socialscrape_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
- socialscrape_cli-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# GENERATED COPY of sdks/mcp/socialscrape_mcp/catalog.py: edit that file, then run sdks/mcp/generate.py.
|
|
2
|
+
"""The generated catalog (catalog.json), as plain objects.
|
|
3
|
+
|
|
4
|
+
`catalog.json` is written by `sdks/mcp/generate.py` from the endpoint registry
|
|
5
|
+
and holds only endpoints that are built (never stubs). Everything the server
|
|
6
|
+
knows about SocialScrape comes from here, so the server needs no import of the
|
|
7
|
+
api's own packages and can run in a small image of its own.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
from dataclasses import dataclass, field
|
|
14
|
+
from functools import lru_cache
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
CATALOG_PATH = Path(__file__).with_name("catalog.json")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass(frozen=True)
|
|
22
|
+
class Param:
|
|
23
|
+
name: str
|
|
24
|
+
type: str
|
|
25
|
+
required: bool
|
|
26
|
+
description: str
|
|
27
|
+
location: str = "query"
|
|
28
|
+
enum: tuple[str, ...] | None = None
|
|
29
|
+
default: Any = None
|
|
30
|
+
example: Any = None
|
|
31
|
+
minimum: float | None = None
|
|
32
|
+
maximum: float | None = None
|
|
33
|
+
hosts: tuple[str, ...] | None = None
|
|
34
|
+
aliases: tuple[str, ...] = ()
|
|
35
|
+
common: bool = False
|
|
36
|
+
|
|
37
|
+
@classmethod
|
|
38
|
+
def from_dict(cls, d: dict) -> "Param":
|
|
39
|
+
return cls(
|
|
40
|
+
name=d["name"], type=d["type"], required=d["required"], description=d["description"],
|
|
41
|
+
location=d.get("location", "query"), enum=tuple(d["enum"]) if d.get("enum") else None,
|
|
42
|
+
default=d.get("default"), example=d.get("example"), minimum=d.get("minimum"),
|
|
43
|
+
maximum=d.get("maximum"), hosts=tuple(d["hosts"]) if d.get("hosts") else None,
|
|
44
|
+
aliases=tuple(d.get("aliases", ())), common=bool(d.get("common")),
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
def json_schema(self) -> dict[str, Any]:
|
|
48
|
+
t = {"url": "string"}.get(self.type, self.type)
|
|
49
|
+
schema: dict[str, Any] = {"type": t}
|
|
50
|
+
if self.type == "url":
|
|
51
|
+
schema["format"] = "uri"
|
|
52
|
+
if self.enum:
|
|
53
|
+
schema["enum"] = list(self.enum)
|
|
54
|
+
if self.minimum is not None:
|
|
55
|
+
schema["minimum"] = self.minimum
|
|
56
|
+
if self.maximum is not None:
|
|
57
|
+
schema["maximum"] = self.maximum
|
|
58
|
+
return schema
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@dataclass(frozen=True)
|
|
62
|
+
class Endpoint:
|
|
63
|
+
id: str
|
|
64
|
+
method: str
|
|
65
|
+
path: str
|
|
66
|
+
summary: str
|
|
67
|
+
description: str
|
|
68
|
+
kind: str
|
|
69
|
+
status: str
|
|
70
|
+
mode: str
|
|
71
|
+
response: str
|
|
72
|
+
paginated: bool
|
|
73
|
+
credits: str
|
|
74
|
+
one_of: tuple[tuple[str, ...], ...]
|
|
75
|
+
regional: bool
|
|
76
|
+
timeout_seconds: float
|
|
77
|
+
params: tuple[Param, ...]
|
|
78
|
+
examples: tuple[dict, ...]
|
|
79
|
+
group: str
|
|
80
|
+
resource: str
|
|
81
|
+
|
|
82
|
+
@classmethod
|
|
83
|
+
def from_dict(cls, d: dict) -> "Endpoint":
|
|
84
|
+
return cls(
|
|
85
|
+
id=d["id"], method=d["method"], path=d["path"], summary=d["summary"], description=d.get("description", ""),
|
|
86
|
+
kind=d["kind"], status=d["status"], mode=d["mode"], response=d["response"], paginated=d["paginated"],
|
|
87
|
+
credits=d["credits"], one_of=tuple(tuple(g) for g in d["one_of"]), regional=d["regional"],
|
|
88
|
+
timeout_seconds=d["timeout_seconds"], params=tuple(Param.from_dict(p) for p in d["params"]),
|
|
89
|
+
examples=tuple(d["examples"]), group=d["group"], resource=d["resource"],
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
def param(self, name: str) -> Param | None:
|
|
93
|
+
"""A parameter by its name or any alias the api accepts."""
|
|
94
|
+
for p in self.params:
|
|
95
|
+
if p.name == name or name in p.aliases:
|
|
96
|
+
return p
|
|
97
|
+
return None
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def own_params(self) -> tuple[Param, ...]:
|
|
101
|
+
"""Parameters specific to this endpoint (not trim / raw / max_age_days)."""
|
|
102
|
+
return tuple(p for p in self.params if not p.common)
|
|
103
|
+
|
|
104
|
+
def required_names(self) -> list[str]:
|
|
105
|
+
return [p.name for p in self.params if p.required]
|
|
106
|
+
|
|
107
|
+
def signature(self) -> str:
|
|
108
|
+
"""'handle|url' style: what the caller must supply."""
|
|
109
|
+
parts = [p.name for p in self.params if p.required and p.location != "body"]
|
|
110
|
+
parts += ["|".join(g) for g in self.one_of]
|
|
111
|
+
return ", ".join(parts)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@dataclass(frozen=True)
|
|
115
|
+
class Group:
|
|
116
|
+
key: str
|
|
117
|
+
title: str
|
|
118
|
+
description: str
|
|
119
|
+
platform_enum: bool
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@dataclass
|
|
123
|
+
class Catalog:
|
|
124
|
+
api: dict
|
|
125
|
+
credit_rules: dict[str, str]
|
|
126
|
+
errors: dict[str, dict]
|
|
127
|
+
models: dict[str, list[str]]
|
|
128
|
+
groups: list[Group]
|
|
129
|
+
roles: dict[str, dict]
|
|
130
|
+
endpoints: list[Endpoint]
|
|
131
|
+
by_id: dict[str, Endpoint] = field(default_factory=dict)
|
|
132
|
+
|
|
133
|
+
def __post_init__(self) -> None:
|
|
134
|
+
self.by_id = {e.id: e for e in self.endpoints}
|
|
135
|
+
|
|
136
|
+
@classmethod
|
|
137
|
+
def from_dict(cls, d: dict) -> "Catalog":
|
|
138
|
+
return cls(
|
|
139
|
+
api=d["api"], credit_rules=d["credit_rules"], errors=d["errors"], models=d["models"],
|
|
140
|
+
groups=[Group(g["key"], g["title"], g["description"], g["platform_enum"]) for g in d["groups"]],
|
|
141
|
+
roles=d["roles"], endpoints=[Endpoint.from_dict(e) for e in d["endpoints"]],
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
def group_endpoints(self, key: str) -> list[Endpoint]:
|
|
145
|
+
return [e for e in self.endpoints if e.group == key]
|
|
146
|
+
|
|
147
|
+
def group(self, key: str) -> Group | None:
|
|
148
|
+
return next((g for g in self.groups if g.key == key), None)
|
|
149
|
+
|
|
150
|
+
def find(self, group: str, resource: str) -> Endpoint | None:
|
|
151
|
+
for e in self.endpoints:
|
|
152
|
+
if e.group == group and e.resource == resource:
|
|
153
|
+
return e
|
|
154
|
+
return None
|
|
155
|
+
|
|
156
|
+
def credit_text(self, ep: Endpoint) -> str:
|
|
157
|
+
"""The price in words, from the registry's credit rule."""
|
|
158
|
+
desc = self.credit_rules.get(ep.credits, ep.credits)
|
|
159
|
+
return {"free": "free", "standard": "1 credit", "transcript": "1 credit (captions); AI transcription 1 credit "
|
|
160
|
+
"up to 2 minutes, then 5 per started minute of the whole video", "per_5_items": "1 credit per 5 items",
|
|
161
|
+
"batch": "the sum of its requests"}.get(ep.credits, desc)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
@lru_cache(maxsize=1)
|
|
165
|
+
def load_catalog(path: str | None = None) -> Catalog:
|
|
166
|
+
return Catalog.from_dict(json.loads(Path(path).read_text() if path else CATALOG_PATH.read_text()))
|