surfsky-cli 0.0.2__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.
- surfsky_cli/__init__.py +3 -0
- surfsky_cli/__main__.py +3 -0
- surfsky_cli/commands/__init__.py +0 -0
- surfsky_cli/commands/account.py +340 -0
- surfsky_cli/commands/actions.py +662 -0
- surfsky_cli/commands/page.py +304 -0
- surfsky_cli/commands/scrape.py +217 -0
- surfsky_cli/commands/session.py +184 -0
- surfsky_cli/commands/status.py +102 -0
- surfsky_cli/config.py +115 -0
- surfsky_cli/html.py +41 -0
- surfsky_cli/main.py +191 -0
- surfsky_cli/out.py +343 -0
- surfsky_cli/session.py +298 -0
- surfsky_cli/skill.md +167 -0
- surfsky_cli/snapshot.py +154 -0
- surfsky_cli-0.0.2.dist-info/METADATA +156 -0
- surfsky_cli-0.0.2.dist-info/RECORD +21 -0
- surfsky_cli-0.0.2.dist-info/WHEEL +4 -0
- surfsky_cli-0.0.2.dist-info/entry_points.txt +2 -0
- surfsky_cli-0.0.2.dist-info/licenses/LICENSE +21 -0
surfsky_cli/__init__.py
ADDED
surfsky_cli/__main__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from collections.abc import Awaitable, Callable
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
import click
|
|
7
|
+
from surfsky import AsyncSurfsky
|
|
8
|
+
|
|
9
|
+
from .. import session
|
|
10
|
+
from ..config import Settings
|
|
11
|
+
from ..out import (
|
|
12
|
+
emit,
|
|
13
|
+
fingerprint_options,
|
|
14
|
+
json_arg,
|
|
15
|
+
output_options,
|
|
16
|
+
pass_settings,
|
|
17
|
+
proxy_options,
|
|
18
|
+
run,
|
|
19
|
+
scalar,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
Factory = Callable[[AsyncSurfsky], Awaitable[Any]]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def call(
|
|
26
|
+
settings: Settings,
|
|
27
|
+
factory: Factory,
|
|
28
|
+
*,
|
|
29
|
+
text: Callable[[Any], str] | None = None,
|
|
30
|
+
**out: Any,
|
|
31
|
+
) -> None:
|
|
32
|
+
async def go() -> Any:
|
|
33
|
+
async with settings.client() as client:
|
|
34
|
+
return await factory(client)
|
|
35
|
+
|
|
36
|
+
result = run(go())
|
|
37
|
+
emit(result, text=text(result) if text else None, **out)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@click.group()
|
|
41
|
+
def profile() -> None:
|
|
42
|
+
"""Save fingerprints, proxies and cookies for reuse across sessions."""
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@profile.command("list")
|
|
46
|
+
@click.option(
|
|
47
|
+
"--all", "all_", is_flag=True, help="Every profile, not just the first 100."
|
|
48
|
+
)
|
|
49
|
+
@click.option("--page", type=int, help="Page number, from 0.")
|
|
50
|
+
@click.option(
|
|
51
|
+
"--ordering",
|
|
52
|
+
type=click.Choice(["created", "-created", "active", "-active", "title", "-title"]),
|
|
53
|
+
)
|
|
54
|
+
@output_options
|
|
55
|
+
@pass_settings
|
|
56
|
+
def profile_list(
|
|
57
|
+
settings: Settings, all_: bool, page: int | None, ordering: str | None, **out: Any
|
|
58
|
+
) -> None:
|
|
59
|
+
"""List up to 100 profiles; use --all or --page for more."""
|
|
60
|
+
|
|
61
|
+
async def factory(client: AsyncSurfsky) -> Any:
|
|
62
|
+
if all_:
|
|
63
|
+
return [
|
|
64
|
+
item
|
|
65
|
+
async for item in client.profiles.iter_all(
|
|
66
|
+
ordering=ordering or "created" # ty: ignore[invalid-argument-type]
|
|
67
|
+
)
|
|
68
|
+
]
|
|
69
|
+
return await client.profiles.list_page(
|
|
70
|
+
page=page,
|
|
71
|
+
page_len=100,
|
|
72
|
+
ordering=ordering, # ty: ignore[invalid-argument-type]
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
call(settings, factory, **out)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@profile.command("get")
|
|
79
|
+
@click.argument("uuid")
|
|
80
|
+
@output_options
|
|
81
|
+
@pass_settings
|
|
82
|
+
def profile_get(settings: Settings, uuid: str, **out: Any) -> None:
|
|
83
|
+
"""Show a profile's fingerprint, proxy and saved settings."""
|
|
84
|
+
call(settings, lambda client: client.profiles.get(uuid), **out)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@profile.command("create")
|
|
88
|
+
@click.argument("title")
|
|
89
|
+
@click.option("--description")
|
|
90
|
+
@click.option(
|
|
91
|
+
"--cookies", "cookies_json", help="Initial cookies: a JSON array inline, or @file."
|
|
92
|
+
)
|
|
93
|
+
@fingerprint_options
|
|
94
|
+
@proxy_options
|
|
95
|
+
@output_options
|
|
96
|
+
@pass_settings
|
|
97
|
+
def profile_create(
|
|
98
|
+
settings: Settings,
|
|
99
|
+
title: str,
|
|
100
|
+
description: str | None,
|
|
101
|
+
cookies_json: str | None,
|
|
102
|
+
os_: str | None,
|
|
103
|
+
os_version: str | None,
|
|
104
|
+
arch: str | None,
|
|
105
|
+
proxy: str | None,
|
|
106
|
+
country: str | None,
|
|
107
|
+
region: str | None,
|
|
108
|
+
city: str | None,
|
|
109
|
+
proxy_type: str | None,
|
|
110
|
+
**out: Any,
|
|
111
|
+
) -> None:
|
|
112
|
+
"""Create a profile named TITLE. Needs --os."""
|
|
113
|
+
fingerprint = session.fingerprint_from(os_, os_version, arch)
|
|
114
|
+
if not os_ or fingerprint is None:
|
|
115
|
+
raise click.UsageError("--os is required: win, mac or android")
|
|
116
|
+
proxy_value = session.proxy_from(proxy, country, region, city, proxy_type)
|
|
117
|
+
cookies = json_arg(cookies_json, "--cookies") if cookies_json else None
|
|
118
|
+
|
|
119
|
+
async def factory(client: AsyncSurfsky) -> Any:
|
|
120
|
+
return await client.profiles.create(
|
|
121
|
+
title=title,
|
|
122
|
+
fingerprint=fingerprint,
|
|
123
|
+
description=description,
|
|
124
|
+
proxy=proxy_value,
|
|
125
|
+
cookies=cookies,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
call(settings, factory, **out)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@profile.command("update")
|
|
132
|
+
@click.argument("uuid")
|
|
133
|
+
@click.option("--title")
|
|
134
|
+
@click.option("--description")
|
|
135
|
+
@proxy_options
|
|
136
|
+
@output_options
|
|
137
|
+
@pass_settings
|
|
138
|
+
def profile_update(
|
|
139
|
+
settings: Settings,
|
|
140
|
+
uuid: str,
|
|
141
|
+
title: str | None,
|
|
142
|
+
description: str | None,
|
|
143
|
+
proxy: str | None,
|
|
144
|
+
country: str | None,
|
|
145
|
+
region: str | None,
|
|
146
|
+
city: str | None,
|
|
147
|
+
proxy_type: str | None,
|
|
148
|
+
**out: Any,
|
|
149
|
+
) -> None:
|
|
150
|
+
"""Change a profile's title, description or proxy."""
|
|
151
|
+
fields: dict[str, Any] = {}
|
|
152
|
+
if title:
|
|
153
|
+
fields["title"] = title
|
|
154
|
+
if description is not None:
|
|
155
|
+
fields["description"] = description
|
|
156
|
+
if proxy or country:
|
|
157
|
+
fields["proxy"] = session.proxy_from(proxy, country, region, city, proxy_type)
|
|
158
|
+
if not fields:
|
|
159
|
+
raise click.UsageError(
|
|
160
|
+
"nothing to update: give --title, --description or a proxy"
|
|
161
|
+
)
|
|
162
|
+
call(settings, lambda client: client.profiles.update(uuid, **fields), **out)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
@profile.command("delete")
|
|
166
|
+
@click.argument("uuids", nargs=-1, required=True)
|
|
167
|
+
@output_options
|
|
168
|
+
@pass_settings
|
|
169
|
+
def profile_delete(settings: Settings, uuids: tuple[str, ...], **out: Any) -> None:
|
|
170
|
+
"""Delete one or more profiles (running ones are reported, not deleted)."""
|
|
171
|
+
|
|
172
|
+
async def factory(client: AsyncSurfsky) -> Any:
|
|
173
|
+
if len(uuids) == 1:
|
|
174
|
+
return await client.profiles.delete(uuids[0])
|
|
175
|
+
return await client.profiles.delete_many(list(uuids))
|
|
176
|
+
|
|
177
|
+
call(settings, factory, **out)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
@profile.group("cookies")
|
|
181
|
+
def profile_cookies() -> None:
|
|
182
|
+
"""Export or import a profile's cookies."""
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
@profile_cookies.command("export")
|
|
186
|
+
@click.argument("uuid")
|
|
187
|
+
@click.option(
|
|
188
|
+
"--format",
|
|
189
|
+
"fmt",
|
|
190
|
+
type=click.Choice(["json", "netscape"]),
|
|
191
|
+
default="json",
|
|
192
|
+
show_default=True,
|
|
193
|
+
)
|
|
194
|
+
@output_options
|
|
195
|
+
@pass_settings
|
|
196
|
+
def cookies_export(settings: Settings, uuid: str, fmt: str, **out: Any) -> None:
|
|
197
|
+
"""Print a profile's cookies."""
|
|
198
|
+
call(
|
|
199
|
+
settings,
|
|
200
|
+
lambda client: client.profiles.export_cookies( # ty: ignore[no-matching-overload]
|
|
201
|
+
uuid, export_format=fmt
|
|
202
|
+
),
|
|
203
|
+
text=scalar,
|
|
204
|
+
**out,
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
@profile_cookies.command("import")
|
|
209
|
+
@click.argument("uuid")
|
|
210
|
+
@click.argument("cookies_arg")
|
|
211
|
+
@output_options
|
|
212
|
+
@pass_settings
|
|
213
|
+
def cookies_import(settings: Settings, uuid: str, cookies_arg: str, **out: Any) -> None:
|
|
214
|
+
"""Import cookies: a JSON array inline, or @file holding JSON or Netscape text."""
|
|
215
|
+
raw = cookies_arg
|
|
216
|
+
if raw.startswith("@"):
|
|
217
|
+
raw = Path(raw[1:]).read_text(encoding="utf-8")
|
|
218
|
+
try:
|
|
219
|
+
cookies: Any = json.loads(raw)
|
|
220
|
+
except ValueError:
|
|
221
|
+
cookies = raw # the Netscape format is one text blob
|
|
222
|
+
|
|
223
|
+
async def factory(client: AsyncSurfsky) -> Any:
|
|
224
|
+
await client.profiles.import_cookies(uuid, cookies)
|
|
225
|
+
return {"imported": uuid}
|
|
226
|
+
|
|
227
|
+
call(settings, factory, **out)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
@click.group()
|
|
231
|
+
def proxy() -> None:
|
|
232
|
+
"""Surfsky's proxy pools: locations and quota."""
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
@proxy.command("countries")
|
|
236
|
+
@click.option("--shared", is_flag=True, help="The shared pool instead of premium.")
|
|
237
|
+
@output_options
|
|
238
|
+
@pass_settings
|
|
239
|
+
def proxy_countries(settings: Settings, shared: bool, **out: Any) -> None:
|
|
240
|
+
"""Countries with proxies."""
|
|
241
|
+
call(
|
|
242
|
+
settings,
|
|
243
|
+
lambda client: (
|
|
244
|
+
client.proxies.shared_countries() if shared else client.proxies.countries()
|
|
245
|
+
),
|
|
246
|
+
**out,
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
@proxy.command("regions")
|
|
251
|
+
@click.argument("country")
|
|
252
|
+
@output_options
|
|
253
|
+
@pass_settings
|
|
254
|
+
def proxy_regions(settings: Settings, country: str, **out: Any) -> None:
|
|
255
|
+
"""Regions of COUNTRY (premium)."""
|
|
256
|
+
call(settings, lambda client: client.proxies.regions(country), **out)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
@proxy.command("cities")
|
|
260
|
+
@click.argument("country")
|
|
261
|
+
@click.argument("region")
|
|
262
|
+
@output_options
|
|
263
|
+
@pass_settings
|
|
264
|
+
def proxy_cities(settings: Settings, country: str, region: str, **out: Any) -> None:
|
|
265
|
+
"""Cities of REGION in COUNTRY (premium)."""
|
|
266
|
+
call(settings, lambda client: client.proxies.cities(country, region), **out)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
@proxy.command("quota")
|
|
270
|
+
@click.option("--shared", is_flag=True)
|
|
271
|
+
@output_options
|
|
272
|
+
@pass_settings
|
|
273
|
+
def proxy_quota(settings: Settings, shared: bool, **out: Any) -> None:
|
|
274
|
+
"""Show remaining premium proxy traffic; --shared selects the shared pool."""
|
|
275
|
+
call(
|
|
276
|
+
settings,
|
|
277
|
+
lambda client: (
|
|
278
|
+
client.proxies.shared_quota() if shared else client.proxies.quota()
|
|
279
|
+
),
|
|
280
|
+
**out,
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
@click.group()
|
|
285
|
+
def extension() -> None:
|
|
286
|
+
"""Manage extensions; load one with session start --extension UUID."""
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
@extension.command("list")
|
|
290
|
+
@output_options
|
|
291
|
+
@pass_settings
|
|
292
|
+
def extension_list(settings: Settings, **out: Any) -> None:
|
|
293
|
+
"""Every uploaded extension."""
|
|
294
|
+
call(settings, lambda client: client.extensions.list_all(), **out)
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
@extension.command("get")
|
|
298
|
+
@click.argument("uuid")
|
|
299
|
+
@output_options
|
|
300
|
+
@pass_settings
|
|
301
|
+
def extension_get(settings: Settings, uuid: str, **out: Any) -> None:
|
|
302
|
+
"""Show extension details by UUID."""
|
|
303
|
+
call(settings, lambda client: client.extensions.get(uuid), **out)
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
@extension.command("upload")
|
|
307
|
+
@click.argument("zip_path", type=click.Path(exists=True, dir_okay=False))
|
|
308
|
+
@click.option("--name", required=True)
|
|
309
|
+
@output_options
|
|
310
|
+
@pass_settings
|
|
311
|
+
def extension_upload(settings: Settings, zip_path: str, name: str, **out: Any) -> None:
|
|
312
|
+
"""Upload an extension ZIP (max 100 MB)."""
|
|
313
|
+
call(settings, lambda client: client.extensions.upload(zip_path, name), **out)
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
@extension.command("rename")
|
|
317
|
+
@click.argument("uuid")
|
|
318
|
+
@click.argument("name")
|
|
319
|
+
@output_options
|
|
320
|
+
@pass_settings
|
|
321
|
+
def extension_rename(settings: Settings, uuid: str, name: str, **out: Any) -> None:
|
|
322
|
+
"""Rename an extension."""
|
|
323
|
+
call(settings, lambda client: client.extensions.update(uuid, name=name), **out)
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
@extension.command("delete")
|
|
327
|
+
@click.argument("uuid")
|
|
328
|
+
@output_options
|
|
329
|
+
@pass_settings
|
|
330
|
+
def extension_delete(settings: Settings, uuid: str, **out: Any) -> None:
|
|
331
|
+
"""Delete an extension."""
|
|
332
|
+
|
|
333
|
+
async def factory(client: AsyncSurfsky) -> Any:
|
|
334
|
+
await client.extensions.delete(uuid)
|
|
335
|
+
return {"deleted": uuid}
|
|
336
|
+
|
|
337
|
+
call(settings, factory, **out)
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
COMMANDS: list[click.Command] = [profile, proxy, extension]
|