snapapi-python 1.3.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.
snapapi.py
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SnapAPI Python SDK — capture any website as an image.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
from snapapi import SnapAPI
|
|
6
|
+
|
|
7
|
+
snap = SnapAPI("snap_your_key_here")
|
|
8
|
+
image = snap.screenshot("example.com")
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import requests
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class SnapAPIError(Exception):
|
|
15
|
+
"""Raised when the SnapAPI returns a non-2xx response."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, status, message, body=None):
|
|
18
|
+
self.status = status
|
|
19
|
+
self.message = message
|
|
20
|
+
self.body = body
|
|
21
|
+
super().__init__(f"SnapAPI Error {status}: {message}")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class SnapAPI:
|
|
25
|
+
"""Client for the SnapAPI screenshot service."""
|
|
26
|
+
|
|
27
|
+
def __init__(self, api_key, base_url="https://snapapi.tech"):
|
|
28
|
+
self.api_key = api_key
|
|
29
|
+
self.base_url = base_url.rstrip("/")
|
|
30
|
+
self._session = requests.Session()
|
|
31
|
+
self._session.headers.update({"X-Api-Key": self.api_key})
|
|
32
|
+
|
|
33
|
+
def _request(self, method, path, params=None, json=None):
|
|
34
|
+
"""Send a request and return the response, raising on errors."""
|
|
35
|
+
url = f"{self.base_url}{path}"
|
|
36
|
+
resp = self._session.request(method, url, params=params, json=json)
|
|
37
|
+
|
|
38
|
+
if not resp.ok:
|
|
39
|
+
try:
|
|
40
|
+
body = resp.json()
|
|
41
|
+
message = body.get("error", body.get("message", resp.reason))
|
|
42
|
+
except ValueError:
|
|
43
|
+
body = None
|
|
44
|
+
message = resp.reason or resp.text
|
|
45
|
+
raise SnapAPIError(resp.status_code, message, body)
|
|
46
|
+
|
|
47
|
+
return resp
|
|
48
|
+
|
|
49
|
+
@staticmethod
|
|
50
|
+
def _bool_param(value):
|
|
51
|
+
"""Convert a Python bool to a query-string friendly string."""
|
|
52
|
+
return "true" if value else "false"
|
|
53
|
+
|
|
54
|
+
def screenshot(self, url, width=1280, height=800, format="png", quality=80,
|
|
55
|
+
full_page=False, delay=0, dark_mode=False, block_ads=False,
|
|
56
|
+
cache=True, selector=None, meta=False, device=None):
|
|
57
|
+
"""Capture a screenshot of the given URL.
|
|
58
|
+
|
|
59
|
+
Returns bytes (raw image data) by default.
|
|
60
|
+
When meta=True, returns a dict with page metadata instead.
|
|
61
|
+
"""
|
|
62
|
+
params = {
|
|
63
|
+
"url": url,
|
|
64
|
+
"width": width,
|
|
65
|
+
"height": height,
|
|
66
|
+
"format": format,
|
|
67
|
+
"quality": quality,
|
|
68
|
+
"full_page": self._bool_param(full_page),
|
|
69
|
+
"delay": delay,
|
|
70
|
+
"dark_mode": self._bool_param(dark_mode),
|
|
71
|
+
"block_ads": self._bool_param(block_ads),
|
|
72
|
+
"cache": self._bool_param(cache),
|
|
73
|
+
"meta": self._bool_param(meta),
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if selector is not None:
|
|
77
|
+
params["selector"] = selector
|
|
78
|
+
if device is not None:
|
|
79
|
+
params["device"] = device
|
|
80
|
+
|
|
81
|
+
resp = self._request("GET", "/v1/screenshot", params=params)
|
|
82
|
+
|
|
83
|
+
content_type = resp.headers.get("Content-Type", "")
|
|
84
|
+
if "application/json" in content_type:
|
|
85
|
+
return resp.json()
|
|
86
|
+
|
|
87
|
+
return resp.content
|
|
88
|
+
|
|
89
|
+
def metadata(self, url):
|
|
90
|
+
"""Extract metadata from a URL without taking a screenshot.
|
|
91
|
+
|
|
92
|
+
Returns title, description, OG tags, favicon, headings, links, and more.
|
|
93
|
+
Available on all plans.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
url: The webpage URL to extract metadata from.
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
dict with metadata fields.
|
|
100
|
+
"""
|
|
101
|
+
resp = self._request("GET", "/v1/metadata", params={"url": url})
|
|
102
|
+
return resp.json()
|
|
103
|
+
|
|
104
|
+
def render(self, html, width=1200, height=630, format="png", quality=90):
|
|
105
|
+
"""Render raw HTML to an image (PNG, JPEG, or WebP).
|
|
106
|
+
|
|
107
|
+
Perfect for OG cards, email previews, and dashboard exports.
|
|
108
|
+
Requires Starter tier or above.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
html: Full HTML string to render (max 2MB).
|
|
112
|
+
width: Viewport width in pixels (default 1200).
|
|
113
|
+
height: Viewport height in pixels (default 630).
|
|
114
|
+
format: Output format — 'png', 'jpeg', or 'webp' (default 'png').
|
|
115
|
+
quality: Image quality 1-100 for jpeg/webp (default 90).
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
bytes (raw image data).
|
|
119
|
+
"""
|
|
120
|
+
resp = self._request("POST", "/v1/render", json={
|
|
121
|
+
"html": html,
|
|
122
|
+
"width": width,
|
|
123
|
+
"height": height,
|
|
124
|
+
"format": format,
|
|
125
|
+
"quality": quality,
|
|
126
|
+
})
|
|
127
|
+
return resp.content
|
|
128
|
+
|
|
129
|
+
def usage(self):
|
|
130
|
+
"""Return current usage statistics (tier, usage count, limits)."""
|
|
131
|
+
resp = self._request("GET", "/v1/usage")
|
|
132
|
+
return resp.json()
|
|
133
|
+
|
|
134
|
+
def account(self):
|
|
135
|
+
"""Return full account information."""
|
|
136
|
+
resp = self._request("GET", "/v1/account")
|
|
137
|
+
return resp.json()
|
|
138
|
+
|
|
139
|
+
def update_settings(self, name=None, allowed_domains=None):
|
|
140
|
+
"""Update account settings.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
name: Display name to set.
|
|
144
|
+
allowed_domains: List of allowed domains for the API key.
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
dict with updated settings.
|
|
148
|
+
"""
|
|
149
|
+
payload = {}
|
|
150
|
+
if name is not None:
|
|
151
|
+
payload["name"] = name
|
|
152
|
+
if allowed_domains is not None:
|
|
153
|
+
payload["allowed_domains"] = allowed_domains
|
|
154
|
+
|
|
155
|
+
resp = self._request("POST", "/v1/account/settings", json=payload)
|
|
156
|
+
return resp.json()
|
|
157
|
+
|
|
158
|
+
# ─── Monitors (Scheduled Screenshots) ───
|
|
159
|
+
|
|
160
|
+
def create_monitor(self, url, interval_minutes, name=None, params=None, webhook_url=None):
|
|
161
|
+
"""Create a new scheduled monitor.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
url: URL to monitor.
|
|
165
|
+
interval_minutes: Capture interval in minutes.
|
|
166
|
+
name: Optional display name.
|
|
167
|
+
params: Optional screenshot options dict (width, height, format, etc.).
|
|
168
|
+
webhook_url: Optional webhook URL for notifications.
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
dict with monitor details.
|
|
172
|
+
"""
|
|
173
|
+
payload = {"url": url, "interval_minutes": interval_minutes}
|
|
174
|
+
if name is not None:
|
|
175
|
+
payload["name"] = name
|
|
176
|
+
if params is not None:
|
|
177
|
+
payload["params"] = params
|
|
178
|
+
if webhook_url is not None:
|
|
179
|
+
payload["webhook_url"] = webhook_url
|
|
180
|
+
|
|
181
|
+
resp = self._request("POST", "/v1/monitors", json=payload)
|
|
182
|
+
return resp.json()
|
|
183
|
+
|
|
184
|
+
def list_monitors(self):
|
|
185
|
+
"""List all monitors for this API key.
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
list of monitor dicts.
|
|
189
|
+
"""
|
|
190
|
+
resp = self._request("GET", "/v1/monitors")
|
|
191
|
+
return resp.json()
|
|
192
|
+
|
|
193
|
+
def get_monitor(self, monitor_id):
|
|
194
|
+
"""Get a single monitor by ID.
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
dict with monitor details.
|
|
198
|
+
"""
|
|
199
|
+
resp = self._request("GET", f"/v1/monitors/{monitor_id}")
|
|
200
|
+
return resp.json()
|
|
201
|
+
|
|
202
|
+
def update_monitor(self, monitor_id, **kwargs):
|
|
203
|
+
"""Update a monitor.
|
|
204
|
+
|
|
205
|
+
Keyword Args:
|
|
206
|
+
name: New display name.
|
|
207
|
+
interval_minutes: New interval.
|
|
208
|
+
status: 'active' or 'paused'.
|
|
209
|
+
params: New screenshot options dict.
|
|
210
|
+
webhook_url: New webhook URL.
|
|
211
|
+
|
|
212
|
+
Returns:
|
|
213
|
+
dict with updated monitor.
|
|
214
|
+
"""
|
|
215
|
+
resp = self._request("PATCH", f"/v1/monitors/{monitor_id}", json=kwargs)
|
|
216
|
+
return resp.json()
|
|
217
|
+
|
|
218
|
+
def delete_monitor(self, monitor_id):
|
|
219
|
+
"""Delete a monitor and all its snapshots."""
|
|
220
|
+
self._request("DELETE", f"/v1/monitors/{monitor_id}")
|
|
221
|
+
|
|
222
|
+
def list_snapshots(self, monitor_id, limit=50, offset=0):
|
|
223
|
+
"""List snapshots for a monitor.
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
monitor_id: Monitor ID.
|
|
227
|
+
limit: Max results (default 50).
|
|
228
|
+
offset: Pagination offset.
|
|
229
|
+
|
|
230
|
+
Returns:
|
|
231
|
+
dict with snapshots list, total, limit, offset.
|
|
232
|
+
"""
|
|
233
|
+
resp = self._request("GET", f"/v1/monitors/{monitor_id}/snapshots",
|
|
234
|
+
params={"limit": limit, "offset": offset})
|
|
235
|
+
return resp.json()
|
|
236
|
+
|
|
237
|
+
def get_snapshot(self, monitor_id, snapshot_id):
|
|
238
|
+
"""Download a specific snapshot image.
|
|
239
|
+
|
|
240
|
+
Returns:
|
|
241
|
+
bytes (raw image data).
|
|
242
|
+
"""
|
|
243
|
+
resp = self._request("GET", f"/v1/monitors/{monitor_id}/snapshots/{snapshot_id}")
|
|
244
|
+
return resp.content
|
|
245
|
+
|
|
246
|
+
@staticmethod
|
|
247
|
+
def signup(email, base_url="https://snapapi.tech"):
|
|
248
|
+
"""Create a new SnapAPI account. No API key required.
|
|
249
|
+
|
|
250
|
+
Args:
|
|
251
|
+
email: Email address for the new account.
|
|
252
|
+
base_url: API base URL.
|
|
253
|
+
|
|
254
|
+
Returns:
|
|
255
|
+
dict with key, tier, and limit.
|
|
256
|
+
"""
|
|
257
|
+
url = f"{base_url.rstrip('/')}/v1/signup"
|
|
258
|
+
resp = requests.post(url, json={"email": email})
|
|
259
|
+
|
|
260
|
+
if not resp.ok:
|
|
261
|
+
try:
|
|
262
|
+
body = resp.json()
|
|
263
|
+
message = body.get("error", body.get("message", resp.reason))
|
|
264
|
+
except ValueError:
|
|
265
|
+
body = None
|
|
266
|
+
message = resp.reason or resp.text
|
|
267
|
+
raise SnapAPIError(resp.status_code, message, body)
|
|
268
|
+
|
|
269
|
+
return resp.json()
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: snapapi-python
|
|
3
|
+
Version: 1.3.0
|
|
4
|
+
Summary: Official Python SDK for SnapAPI — screenshot, metadata extraction, and HTML rendering
|
|
5
|
+
License: MIT
|
|
6
|
+
Keywords: screenshot,api,webpage-capture,snapapi
|
|
7
|
+
Requires-Python: >=3.7
|
|
8
|
+
Requires-Dist: requests>=2.20.0
|
|
9
|
+
Dynamic: keywords
|
|
10
|
+
Dynamic: license
|
|
11
|
+
Dynamic: requires-dist
|
|
12
|
+
Dynamic: requires-python
|
|
13
|
+
Dynamic: summary
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
snapapi.py,sha256=sNkVGGihSbQln8n3kYfeShworXs32B5vf8p5i9Eq0Ug,8726
|
|
2
|
+
snapapi_python-1.3.0.dist-info/METADATA,sha256=PY-9BZ9ybpFJcjDx31o8w5_IWALRCafbKg-6RrmmY3w,385
|
|
3
|
+
snapapi_python-1.3.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
4
|
+
snapapi_python-1.3.0.dist-info/top_level.txt,sha256=ixqJtWjxOFIoE_Mirko8xyRTH-3pKC_AYNRLLUuESoA,8
|
|
5
|
+
snapapi_python-1.3.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
snapapi
|