asockslib 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.
- asockslib/__init__.py +117 -0
- asockslib/_console.py +18 -0
- asockslib/_port_utils.py +132 -0
- asockslib/benchmark.py +365 -0
- asockslib/cli/__init__.py +72 -0
- asockslib/cli/_helpers.py +162 -0
- asockslib/cli/_output.py +52 -0
- asockslib/cli/api_commands.py +377 -0
- asockslib/cli/commands.py +485 -0
- asockslib/client.py +634 -0
- asockslib/exceptions.py +89 -0
- asockslib/geo.json +649269 -0
- asockslib/geo_picker/__init__.py +30 -0
- asockslib/geo_picker/_completer.py +90 -0
- asockslib/geo_picker/_messages.py +89 -0
- asockslib/geo_picker/_types.py +89 -0
- asockslib/geo_picker/picker.py +667 -0
- asockslib/models/__init__.py +72 -0
- asockslib/models/directory.py +65 -0
- asockslib/models/enums.py +65 -0
- asockslib/models/port.py +187 -0
- asockslib/models/requests.py +89 -0
- asockslib/models/responses.py +20 -0
- asockslib/proxy_pool.py +659 -0
- asockslib/quick.py +174 -0
- asockslib/smart_proxy.py +251 -0
- asockslib-0.1.0.dist-info/METADATA +228 -0
- asockslib-0.1.0.dist-info/RECORD +30 -0
- asockslib-0.1.0.dist-info/WHEEL +4 -0
- asockslib-0.1.0.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"""CLI helper utilities.
|
|
2
|
+
|
|
3
|
+
Contains non-UI functions: clipboard operations, export formatting,
|
|
4
|
+
API key resolution, async runner, and proxy template application.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
import csv
|
|
11
|
+
import os
|
|
12
|
+
import platform
|
|
13
|
+
import shutil
|
|
14
|
+
import subprocess
|
|
15
|
+
from enum import StrEnum
|
|
16
|
+
from io import StringIO
|
|
17
|
+
from typing import TYPE_CHECKING
|
|
18
|
+
|
|
19
|
+
import typer
|
|
20
|
+
from beartype import beartype
|
|
21
|
+
|
|
22
|
+
from asockslib._console import console
|
|
23
|
+
from asockslib.exceptions import ASocksError
|
|
24
|
+
|
|
25
|
+
if TYPE_CHECKING:
|
|
26
|
+
from collections.abc import Coroutine
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ExportFormat(StrEnum):
|
|
30
|
+
"""Supported export formats."""
|
|
31
|
+
|
|
32
|
+
txt = "txt"
|
|
33
|
+
json = "json"
|
|
34
|
+
csv = "csv"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# ── Clipboard ─────────────────────────────────────────────────────────────── #
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@beartype
|
|
41
|
+
def copy_to_clipboard(text: str) -> bool:
|
|
42
|
+
"""Try to copy text to the system clipboard.
|
|
43
|
+
|
|
44
|
+
Supports macOS (pbcopy), Linux (xclip/xsel) and Windows (clip).
|
|
45
|
+
"""
|
|
46
|
+
system = platform.system()
|
|
47
|
+
try:
|
|
48
|
+
if system == "Darwin" and shutil.which("pbcopy"):
|
|
49
|
+
subprocess.run(["pbcopy"], input=text.encode(), check=True) # noqa: S603, S607
|
|
50
|
+
return True
|
|
51
|
+
if system == "Linux":
|
|
52
|
+
for cmd in ("xclip", "xsel"):
|
|
53
|
+
if shutil.which(cmd):
|
|
54
|
+
args = (
|
|
55
|
+
[cmd, "-selection", "clipboard"]
|
|
56
|
+
if cmd == "xclip"
|
|
57
|
+
else [cmd, "--clipboard", "--input"]
|
|
58
|
+
)
|
|
59
|
+
subprocess.run(args, input=text.encode(), check=True) # noqa: S603
|
|
60
|
+
return True
|
|
61
|
+
if system == "Windows" and shutil.which("clip"):
|
|
62
|
+
subprocess.run(["clip"], input=text.encode(), check=True) # noqa: S603, S607
|
|
63
|
+
return True
|
|
64
|
+
except (subprocess.SubprocessError, OSError):
|
|
65
|
+
pass
|
|
66
|
+
return False
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
# ── API key ───────────────────────────────────────────────────────────────── #
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@beartype
|
|
73
|
+
def get_api_key() -> str:
|
|
74
|
+
"""Read API key from ``ASOCKS_API_KEY`` env var."""
|
|
75
|
+
key = os.environ.get("ASOCKS_API_KEY", "")
|
|
76
|
+
if not key:
|
|
77
|
+
console.print("[red]Error:[/red] ASOCKS_API_KEY environment variable not set.")
|
|
78
|
+
raise typer.Exit(code=1)
|
|
79
|
+
return key
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# ── Async runner ──────────────────────────────────────────────────────────── #
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def run_async[T](coro: Coroutine[object, object, T]) -> T:
|
|
86
|
+
"""Run a coroutine synchronously with error handling."""
|
|
87
|
+
try:
|
|
88
|
+
return asyncio.run(coro)
|
|
89
|
+
except ASocksError as exc:
|
|
90
|
+
console.print(f"[red]Error:[/red] {exc.message}")
|
|
91
|
+
raise typer.Exit(code=1) from None
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
# ── Export formatting ─────────────────────────────────────────────────────── #
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@beartype
|
|
98
|
+
def format_output(proxies: list[str], fmt: ExportFormat) -> str:
|
|
99
|
+
"""Format proxy list for export."""
|
|
100
|
+
import json
|
|
101
|
+
|
|
102
|
+
if fmt == ExportFormat.txt:
|
|
103
|
+
return "\n".join(proxies)
|
|
104
|
+
if fmt == ExportFormat.json:
|
|
105
|
+
return json.dumps(proxies, indent=2)
|
|
106
|
+
buf = StringIO()
|
|
107
|
+
writer = csv.writer(buf)
|
|
108
|
+
writer.writerow(["proxy_url"])
|
|
109
|
+
for p in proxies:
|
|
110
|
+
writer.writerow([p])
|
|
111
|
+
return buf.getvalue()
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
# ── Proxy template application ────────────────────────────────────────────── #
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@beartype
|
|
118
|
+
def apply_proxy_template(
|
|
119
|
+
proxy_url: str,
|
|
120
|
+
template: str,
|
|
121
|
+
port_id: int = 0,
|
|
122
|
+
name: str = "",
|
|
123
|
+
) -> str:
|
|
124
|
+
"""Apply a template to a standard proxy URL.
|
|
125
|
+
|
|
126
|
+
Parses ``protocol://login:password@host:port`` and substitutes
|
|
127
|
+
values into template placeholders.
|
|
128
|
+
"""
|
|
129
|
+
protocol = "socks5"
|
|
130
|
+
login = ""
|
|
131
|
+
password = ""
|
|
132
|
+
host = ""
|
|
133
|
+
port_str = "0"
|
|
134
|
+
try:
|
|
135
|
+
proto_rest = proxy_url.split("://", 1)
|
|
136
|
+
protocol = proto_rest[0]
|
|
137
|
+
rest = proto_rest[1] if len(proto_rest) > 1 else proxy_url
|
|
138
|
+
if "@" in rest:
|
|
139
|
+
auth, hostport = rest.rsplit("@", 1)
|
|
140
|
+
parts = auth.split(":", 1)
|
|
141
|
+
login = parts[0]
|
|
142
|
+
password = parts[1] if len(parts) > 1 else ""
|
|
143
|
+
else:
|
|
144
|
+
hostport = rest
|
|
145
|
+
hp = hostport.rsplit(":", 1)
|
|
146
|
+
host = hp[0]
|
|
147
|
+
port_str = hp[1] if len(hp) > 1 else "0"
|
|
148
|
+
except (IndexError, ValueError):
|
|
149
|
+
pass
|
|
150
|
+
|
|
151
|
+
refresh = f"https://api.asocks.com/v2/proxy/refresh-ip/{port_id}"
|
|
152
|
+
return (
|
|
153
|
+
template.replace("{protocol}", protocol)
|
|
154
|
+
.replace("{id}", str(port_id))
|
|
155
|
+
.replace("{login}", login)
|
|
156
|
+
.replace("{password}", password)
|
|
157
|
+
.replace("{ip}", host)
|
|
158
|
+
.replace("{port}", port_str)
|
|
159
|
+
.replace("{refresh_link}", refresh)
|
|
160
|
+
.replace("{name}", name)
|
|
161
|
+
.replace("{external_ip}", "")
|
|
162
|
+
)
|
asockslib/cli/_output.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""Rich console output functions for the CLI.
|
|
2
|
+
|
|
3
|
+
All Rich table printing and clipboard notification logic is
|
|
4
|
+
centralized here, keeping command handlers clean.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from beartype import beartype
|
|
10
|
+
from rich.table import Table
|
|
11
|
+
|
|
12
|
+
from asockslib._console import console
|
|
13
|
+
from asockslib.cli._helpers import copy_to_clipboard
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@beartype
|
|
17
|
+
def print_proxy_table(
|
|
18
|
+
urls: list[str],
|
|
19
|
+
*,
|
|
20
|
+
title: str = "Proxies",
|
|
21
|
+
url_header: str = "Proxy",
|
|
22
|
+
) -> None:
|
|
23
|
+
"""Print a proxy table and auto-copy to clipboard."""
|
|
24
|
+
table = Table(title=title)
|
|
25
|
+
table.add_column("#", style="dim")
|
|
26
|
+
table.add_column(url_header, style="green", overflow="fold")
|
|
27
|
+
for i, u in enumerate(urls, 1):
|
|
28
|
+
table.add_row(str(i), u)
|
|
29
|
+
console.print(table)
|
|
30
|
+
|
|
31
|
+
plain = "\n".join(urls)
|
|
32
|
+
copied = copy_to_clipboard(plain)
|
|
33
|
+
|
|
34
|
+
console.print()
|
|
35
|
+
if copied:
|
|
36
|
+
console.print(f"[green]📋 Copied {len(urls)} proxies to clipboard![/green]")
|
|
37
|
+
else:
|
|
38
|
+
console.print("[dim]── Copy-friendly list ──[/dim]")
|
|
39
|
+
console.print(plain)
|
|
40
|
+
console.print("[dim]────────────────────────[/dim]")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@beartype
|
|
44
|
+
def print_copy_result(urls: list[str]) -> None:
|
|
45
|
+
"""Copy proxy URLs to clipboard and show result."""
|
|
46
|
+
plain = "\n".join(urls)
|
|
47
|
+
if copy_to_clipboard(plain):
|
|
48
|
+
console.print(f"\n[green]📋 Copied {len(urls)} proxies to clipboard![/green]")
|
|
49
|
+
else:
|
|
50
|
+
console.print("\n[dim]── Copy-friendly list ──[/dim]")
|
|
51
|
+
console.print(plain)
|
|
52
|
+
console.print("[dim]────────────────────────[/dim]")
|
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
"""Raw API sub-commands (``asocks api ...``).
|
|
2
|
+
|
|
3
|
+
Low-level CLI wrappers around every ASocks API endpoint.
|
|
4
|
+
Intended for advanced users and debugging.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
import typer
|
|
13
|
+
from beartype import beartype
|
|
14
|
+
from rich.table import Table
|
|
15
|
+
|
|
16
|
+
from asockslib._console import console
|
|
17
|
+
from asockslib.cli._helpers import ExportFormat, format_output, get_api_key, run_async
|
|
18
|
+
from asockslib.client import ASocksClient
|
|
19
|
+
from asockslib.models import (
|
|
20
|
+
CityInfo,
|
|
21
|
+
CountryInfo,
|
|
22
|
+
CreatePortRequest,
|
|
23
|
+
CreateTemplateRequest,
|
|
24
|
+
StateInfo,
|
|
25
|
+
WhitelistAddRequest,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def register_api_commands(api_app: typer.Typer) -> None:
|
|
30
|
+
"""Register all raw API sub-commands on the Typer app."""
|
|
31
|
+
|
|
32
|
+
@api_app.command()
|
|
33
|
+
@beartype
|
|
34
|
+
def countries() -> None:
|
|
35
|
+
"""List countries (GET /v2/proxy/countries)."""
|
|
36
|
+
|
|
37
|
+
async def _countries() -> list[CountryInfo]:
|
|
38
|
+
async with ASocksClient(api_key=get_api_key()) as client:
|
|
39
|
+
return await client.get_countries()
|
|
40
|
+
|
|
41
|
+
items = run_async(_countries())
|
|
42
|
+
table = Table(title=f"Countries ({len(items)})")
|
|
43
|
+
table.add_column("ID", style="cyan")
|
|
44
|
+
table.add_column("Name", style="green")
|
|
45
|
+
table.add_column("Code", style="yellow")
|
|
46
|
+
for c in items:
|
|
47
|
+
table.add_row(str(c.id), c.name, c.code)
|
|
48
|
+
console.print(table)
|
|
49
|
+
|
|
50
|
+
@api_app.command()
|
|
51
|
+
@beartype
|
|
52
|
+
def states(
|
|
53
|
+
country_id: int = typer.Option(..., "--country-id", "-c", help="Country ID"),
|
|
54
|
+
) -> None:
|
|
55
|
+
"""List states/regions (GET /v2/proxy/states)."""
|
|
56
|
+
|
|
57
|
+
async def _states() -> list[StateInfo]:
|
|
58
|
+
async with ASocksClient(api_key=get_api_key()) as client:
|
|
59
|
+
return await client.get_states(country_id=country_id)
|
|
60
|
+
|
|
61
|
+
items = run_async(_states())
|
|
62
|
+
table = Table(title=f"States ({len(items)})")
|
|
63
|
+
table.add_column("ID", style="cyan")
|
|
64
|
+
table.add_column("Name", style="green")
|
|
65
|
+
for s in items:
|
|
66
|
+
table.add_row(str(s.id), s.name)
|
|
67
|
+
console.print(table)
|
|
68
|
+
|
|
69
|
+
@api_app.command()
|
|
70
|
+
@beartype
|
|
71
|
+
def cities(
|
|
72
|
+
country_id: int = typer.Option(..., "--country-id", "-c", help="Country ID"),
|
|
73
|
+
state_id: int | None = typer.Option(None, "--state-id", "-s", help="State ID"),
|
|
74
|
+
) -> None:
|
|
75
|
+
"""List cities (GET /v2/proxy/cities)."""
|
|
76
|
+
|
|
77
|
+
async def _cities() -> list[CityInfo]:
|
|
78
|
+
async with ASocksClient(api_key=get_api_key()) as client:
|
|
79
|
+
return await client.get_cities(country_id=country_id, state_id=state_id)
|
|
80
|
+
|
|
81
|
+
items = run_async(_cities())
|
|
82
|
+
table = Table(title=f"Cities ({len(items)})")
|
|
83
|
+
table.add_column("ID", style="cyan")
|
|
84
|
+
table.add_column("Name", style="green")
|
|
85
|
+
for c in items:
|
|
86
|
+
table.add_row(str(c.id), c.name)
|
|
87
|
+
console.print(table)
|
|
88
|
+
|
|
89
|
+
@api_app.command()
|
|
90
|
+
@beartype
|
|
91
|
+
def asns(
|
|
92
|
+
country_id: int | None = typer.Option(None, "--country-id", "-c", help="Country ID"),
|
|
93
|
+
state_id: int | None = typer.Option(None, "--state-id", "-s", help="State ID"),
|
|
94
|
+
city_id: int | None = typer.Option(None, "--city-id", help="City ID"),
|
|
95
|
+
page: int = typer.Option(1, "--page", "-p", help="Page number"),
|
|
96
|
+
) -> None:
|
|
97
|
+
"""List ASN providers (GET /v2/proxy/asn)."""
|
|
98
|
+
from asockslib.models import ASNListResponse
|
|
99
|
+
|
|
100
|
+
async def _asns() -> ASNListResponse:
|
|
101
|
+
async with ASocksClient(api_key=get_api_key()) as client:
|
|
102
|
+
return await client.get_asns(
|
|
103
|
+
country_id=country_id,
|
|
104
|
+
state_id=state_id,
|
|
105
|
+
city_id=city_id,
|
|
106
|
+
page=page,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
response = run_async(_asns())
|
|
110
|
+
items = response.items
|
|
111
|
+
table = Table(
|
|
112
|
+
title=(
|
|
113
|
+
f"ASNs (page {response.current_page}/{response.last_page}, total {response.total})"
|
|
114
|
+
)
|
|
115
|
+
)
|
|
116
|
+
table.add_column("ASN", style="cyan")
|
|
117
|
+
table.add_column("Name", style="green")
|
|
118
|
+
for a in items:
|
|
119
|
+
table.add_row(str(a.asn), a.name)
|
|
120
|
+
console.print(table)
|
|
121
|
+
|
|
122
|
+
@api_app.command()
|
|
123
|
+
@beartype
|
|
124
|
+
def plan() -> None:
|
|
125
|
+
"""Show plan info (GET /v2/plan/info)."""
|
|
126
|
+
|
|
127
|
+
async def _plan() -> dict[str, Any]:
|
|
128
|
+
async with ASocksClient(api_key=get_api_key()) as client:
|
|
129
|
+
return await client.get_plan_info()
|
|
130
|
+
|
|
131
|
+
data = run_async(_plan())
|
|
132
|
+
msg = data.get("message", data)
|
|
133
|
+
console.print_json(json.dumps(msg, indent=2, default=str))
|
|
134
|
+
|
|
135
|
+
@api_app.command()
|
|
136
|
+
@beartype
|
|
137
|
+
def search(
|
|
138
|
+
limit: int = typer.Argument(10, help="Number of proxies"),
|
|
139
|
+
country: str = typer.Option("", "--country", "-c", help="ISO country code"),
|
|
140
|
+
) -> None:
|
|
141
|
+
"""Search proxies without creating ports (POST /v2/proxy/search)."""
|
|
142
|
+
|
|
143
|
+
async def _search() -> list[str]:
|
|
144
|
+
async with ASocksClient(api_key=get_api_key()) as client:
|
|
145
|
+
return await client.search_proxies(country=country, limit=limit)
|
|
146
|
+
|
|
147
|
+
proxies = run_async(_search())
|
|
148
|
+
if not proxies:
|
|
149
|
+
console.print("[yellow]No proxies found.[/yellow]")
|
|
150
|
+
raise typer.Exit()
|
|
151
|
+
table = Table(title=f"Available Proxies ({len(proxies)})")
|
|
152
|
+
table.add_column("#", style="dim")
|
|
153
|
+
table.add_column("Proxy (IP:Port)", style="green")
|
|
154
|
+
for i, p in enumerate(proxies, 1):
|
|
155
|
+
table.add_row(str(i), p)
|
|
156
|
+
console.print(table)
|
|
157
|
+
|
|
158
|
+
@api_app.command()
|
|
159
|
+
@beartype
|
|
160
|
+
def generate(
|
|
161
|
+
count: int = typer.Argument(1, help="Number of ports"),
|
|
162
|
+
country: str = typer.Option("US", "--country", "-c", help="ISO country code"),
|
|
163
|
+
city: str = typer.Option("", "--city", help="City name"),
|
|
164
|
+
state: str = typer.Option("", "--state", "-s", help="State name"),
|
|
165
|
+
name: str = typer.Option("", "--name", "-n", help="Port name"),
|
|
166
|
+
ttl: int = typer.Option(1, "--ttl", help="TTL in days"),
|
|
167
|
+
traffic_limit: int = typer.Option(10, "--traffic-limit", help="Traffic limit (GB)"),
|
|
168
|
+
type_id: int = typer.Option(
|
|
169
|
+
1, "--type-id", help="Connection type: 1=keep-proxy, 2=keep-conn, 3=rotate"
|
|
170
|
+
),
|
|
171
|
+
proxy_type_id: int = typer.Option(
|
|
172
|
+
1, "--proxy-type-id", help="Proxy type: 1=residential, 3=mobile, 4=corporate"
|
|
173
|
+
),
|
|
174
|
+
server_port_type_id: int = typer.Option(
|
|
175
|
+
0, "--server-port-type-id", help="Port type: 0=shared, 1=dedicated"
|
|
176
|
+
),
|
|
177
|
+
fmt: ExportFormat = typer.Option( # noqa: B008 — required Typer CLI pattern
|
|
178
|
+
ExportFormat.txt, "--format", "-f", help="Export format"
|
|
179
|
+
),
|
|
180
|
+
output: str | None = typer.Option(None, "--output", "-o", help="Output file"),
|
|
181
|
+
) -> None:
|
|
182
|
+
"""Create ports directly (POST /v2/proxy/generate)."""
|
|
183
|
+
|
|
184
|
+
async def _create() -> list[str]:
|
|
185
|
+
req = CreatePortRequest(
|
|
186
|
+
country_code=country,
|
|
187
|
+
city=city,
|
|
188
|
+
state=state,
|
|
189
|
+
name=name,
|
|
190
|
+
count=count,
|
|
191
|
+
ttl=ttl,
|
|
192
|
+
type_id=type_id,
|
|
193
|
+
proxy_type_id=proxy_type_id,
|
|
194
|
+
server_port_type_id=server_port_type_id,
|
|
195
|
+
traffic_limit=traffic_limit,
|
|
196
|
+
)
|
|
197
|
+
async with ASocksClient(api_key=get_api_key()) as client:
|
|
198
|
+
ports = await client.create_ports(req)
|
|
199
|
+
return [p.proxy_url for p in ports]
|
|
200
|
+
|
|
201
|
+
urls = run_async(_create())
|
|
202
|
+
if not urls:
|
|
203
|
+
console.print("[yellow]No ports created.[/yellow]")
|
|
204
|
+
raise typer.Exit()
|
|
205
|
+
|
|
206
|
+
content = format_output(urls, fmt)
|
|
207
|
+
if output:
|
|
208
|
+
with open(output, "w") as f:
|
|
209
|
+
f.write(content)
|
|
210
|
+
console.print(f"[green]Saved {len(urls)} proxies to {output}[/green]")
|
|
211
|
+
else:
|
|
212
|
+
console.print(content)
|
|
213
|
+
|
|
214
|
+
@api_app.command()
|
|
215
|
+
@beartype
|
|
216
|
+
def archive(
|
|
217
|
+
port_id: int = typer.Argument(..., help="Port ID"),
|
|
218
|
+
) -> None:
|
|
219
|
+
"""Archive port (POST /v2/proxy/archive)."""
|
|
220
|
+
|
|
221
|
+
async def _archive() -> bool:
|
|
222
|
+
async with ASocksClient(api_key=get_api_key()) as client:
|
|
223
|
+
return await client.archive_port(port_id)
|
|
224
|
+
|
|
225
|
+
ok = run_async(_archive())
|
|
226
|
+
console.print(f"[green]Port {port_id} archived.[/green]" if ok else "[red]Failed.[/red]")
|
|
227
|
+
|
|
228
|
+
@api_app.command()
|
|
229
|
+
@beartype
|
|
230
|
+
def unarchive(
|
|
231
|
+
port_id: int = typer.Argument(..., help="Port ID"),
|
|
232
|
+
) -> None:
|
|
233
|
+
"""Unarchive port (POST /v2/proxy/unarchive)."""
|
|
234
|
+
|
|
235
|
+
async def _unarchive() -> bool:
|
|
236
|
+
async with ASocksClient(api_key=get_api_key()) as client:
|
|
237
|
+
return await client.unarchive_port(port_id)
|
|
238
|
+
|
|
239
|
+
ok = run_async(_unarchive())
|
|
240
|
+
console.print(f"[green]Port {port_id} unarchived.[/green]" if ok else "[red]Failed.[/red]")
|
|
241
|
+
|
|
242
|
+
@api_app.command(name="refresh-ip")
|
|
243
|
+
@beartype
|
|
244
|
+
def refresh_ip(
|
|
245
|
+
port_id: int = typer.Argument(..., help="Port ID"),
|
|
246
|
+
) -> None:
|
|
247
|
+
"""Refresh port IP (GET /v2/proxy/refresh-ip)."""
|
|
248
|
+
|
|
249
|
+
async def _refresh() -> bool:
|
|
250
|
+
async with ASocksClient(api_key=get_api_key()) as client:
|
|
251
|
+
return await client.refresh_ip(port_id)
|
|
252
|
+
|
|
253
|
+
ok = run_async(_refresh())
|
|
254
|
+
console.print(
|
|
255
|
+
f"[green]IP refreshed for port {port_id}.[/green]" if ok else "[red]Failed.[/red]"
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
@api_app.command()
|
|
259
|
+
@beartype
|
|
260
|
+
def rename(
|
|
261
|
+
port_id: int = typer.Argument(..., help="Port ID"),
|
|
262
|
+
name: str = typer.Argument(..., help="New name"),
|
|
263
|
+
) -> None:
|
|
264
|
+
"""Rename port (POST /v2/proxy/change-name)."""
|
|
265
|
+
|
|
266
|
+
async def _rename() -> bool:
|
|
267
|
+
async with ASocksClient(api_key=get_api_key()) as client:
|
|
268
|
+
return await client.change_port_name(port_id, name)
|
|
269
|
+
|
|
270
|
+
ok = run_async(_rename())
|
|
271
|
+
console.print(f"[green]Renamed to '{name}'.[/green]" if ok else "[red]Failed.[/red]")
|
|
272
|
+
|
|
273
|
+
@api_app.command()
|
|
274
|
+
@beartype
|
|
275
|
+
def traffic() -> None:
|
|
276
|
+
"""Total spent traffic (GET /v2/proxy/total-spent-traffic)."""
|
|
277
|
+
|
|
278
|
+
async def _traffic() -> dict[str, Any]:
|
|
279
|
+
async with ASocksClient(api_key=get_api_key()) as client:
|
|
280
|
+
return await client.get_total_spent_traffic()
|
|
281
|
+
|
|
282
|
+
data = run_async(_traffic())
|
|
283
|
+
total = data.get("total_spent_traffic", "N/A")
|
|
284
|
+
console.print(f"Total spent traffic: [cyan]{total}[/cyan]")
|
|
285
|
+
|
|
286
|
+
@api_app.command(name="change-credentials")
|
|
287
|
+
@beartype
|
|
288
|
+
def change_credentials() -> None:
|
|
289
|
+
"""Change credentials for all proxies (GET /v2/proxy/change-credentials)."""
|
|
290
|
+
|
|
291
|
+
async def _change() -> bool:
|
|
292
|
+
async with ASocksClient(api_key=get_api_key()) as client:
|
|
293
|
+
return await client.change_credentials()
|
|
294
|
+
|
|
295
|
+
ok = run_async(_change())
|
|
296
|
+
console.print("[green]Credentials changed.[/green]" if ok else "[red]Failed.[/red]")
|
|
297
|
+
|
|
298
|
+
@api_app.command()
|
|
299
|
+
@beartype
|
|
300
|
+
def templates(
|
|
301
|
+
page: int = typer.Option(1, "--page", "-p", help="Page number"),
|
|
302
|
+
) -> None:
|
|
303
|
+
"""List templates (GET /v2/proxy-template)."""
|
|
304
|
+
|
|
305
|
+
async def _templates() -> dict[str, Any]:
|
|
306
|
+
async with ASocksClient(api_key=get_api_key()) as client:
|
|
307
|
+
return await client.list_templates(page=page)
|
|
308
|
+
|
|
309
|
+
data = run_async(_templates())
|
|
310
|
+
console.print_json(json.dumps(data, indent=2, default=str))
|
|
311
|
+
|
|
312
|
+
@api_app.command(name="template-create")
|
|
313
|
+
@beartype
|
|
314
|
+
def template_create(
|
|
315
|
+
label: str = typer.Option(..., "--label", "-l", help="Template name"),
|
|
316
|
+
template: str = typer.Option(..., "--template", "-t", help="Template string"),
|
|
317
|
+
) -> None:
|
|
318
|
+
"""Create template (POST /v2/proxy-template/create-template)."""
|
|
319
|
+
|
|
320
|
+
async def _create() -> dict[str, Any]:
|
|
321
|
+
async with ASocksClient(api_key=get_api_key()) as client:
|
|
322
|
+
req = CreateTemplateRequest(label=label, template=template)
|
|
323
|
+
return await client.create_template(req)
|
|
324
|
+
|
|
325
|
+
data = run_async(_create())
|
|
326
|
+
if data.get("success"):
|
|
327
|
+
console.print("[green]Template created.[/green]")
|
|
328
|
+
console.print_json(json.dumps(data, indent=2, default=str))
|
|
329
|
+
|
|
330
|
+
@api_app.command(name="template-delete")
|
|
331
|
+
@beartype
|
|
332
|
+
def template_delete(
|
|
333
|
+
template_id: int = typer.Argument(..., help="Template ID"),
|
|
334
|
+
) -> None:
|
|
335
|
+
"""Delete template (DELETE /v2/proxy-template/delete-template)."""
|
|
336
|
+
|
|
337
|
+
async def _delete() -> bool:
|
|
338
|
+
async with ASocksClient(api_key=get_api_key()) as client:
|
|
339
|
+
return await client.delete_template(template_id)
|
|
340
|
+
|
|
341
|
+
ok = run_async(_delete())
|
|
342
|
+
console.print(
|
|
343
|
+
f"[green]Template {template_id} deleted.[/green]" if ok else "[red]Failed.[/red]"
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
@api_app.command(name="whitelist-add")
|
|
347
|
+
@beartype
|
|
348
|
+
def whitelist_add(
|
|
349
|
+
ip: str = typer.Argument(..., help="IP address"),
|
|
350
|
+
description: str = typer.Option("", "--desc", "-d", help="Description"),
|
|
351
|
+
) -> None:
|
|
352
|
+
"""Add IP to whitelist (POST /v2/whitelist/add)."""
|
|
353
|
+
|
|
354
|
+
async def _add() -> dict[str, Any]:
|
|
355
|
+
async with ASocksClient(api_key=get_api_key()) as client:
|
|
356
|
+
req = WhitelistAddRequest(ip=ip, description=description)
|
|
357
|
+
return await client.add_whitelist_ip(req)
|
|
358
|
+
|
|
359
|
+
data = run_async(_add())
|
|
360
|
+
if data.get("success"):
|
|
361
|
+
console.print(f"[green]IP {ip} added to whitelist.[/green]")
|
|
362
|
+
else:
|
|
363
|
+
console.print(f"[red]Failed to add {ip}.[/red]")
|
|
364
|
+
|
|
365
|
+
@api_app.command(name="whitelist-remove")
|
|
366
|
+
@beartype
|
|
367
|
+
def whitelist_remove(
|
|
368
|
+
ip: str = typer.Argument(..., help="IP address"),
|
|
369
|
+
) -> None:
|
|
370
|
+
"""Remove IP from whitelist (DELETE /v2/whitelist/delete)."""
|
|
371
|
+
|
|
372
|
+
async def _remove() -> bool:
|
|
373
|
+
async with ASocksClient(api_key=get_api_key()) as client:
|
|
374
|
+
return await client.delete_whitelist_ip(ip)
|
|
375
|
+
|
|
376
|
+
ok = run_async(_remove())
|
|
377
|
+
console.print(f"[green]IP {ip} removed.[/green]" if ok else "[red]Failed.[/red]")
|