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.
@@ -0,0 +1,30 @@
1
+ """Interactive geo-data selection via fuzzy search.
2
+
3
+ Re-exports all public types and the :class:`GeoPicker` class
4
+ so that ``from asockslib.geo_picker import GeoPicker`` continues
5
+ to work after the module was split into a package.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from asockslib.geo_picker._types import (
11
+ PickedASN,
12
+ PickedCity,
13
+ PickedConnectionType,
14
+ PickedCountry,
15
+ PickedProxyType,
16
+ PickedServerPortType,
17
+ PickedState,
18
+ )
19
+ from asockslib.geo_picker.picker import GeoPicker
20
+
21
+ __all__ = [
22
+ "GeoPicker",
23
+ "PickedASN",
24
+ "PickedCity",
25
+ "PickedConnectionType",
26
+ "PickedCountry",
27
+ "PickedProxyType",
28
+ "PickedServerPortType",
29
+ "PickedState",
30
+ ]
@@ -0,0 +1,90 @@
1
+ """Fuzzy-match completer and validators for prompt_toolkit.
2
+
3
+ Provides the autocompletion and input validation used by
4
+ :class:`~asockslib.geo_picker.GeoPicker` interactive prompts.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import re
10
+ from typing import TYPE_CHECKING
11
+
12
+ from prompt_toolkit.completion import Completer, Completion
13
+ from prompt_toolkit.styles import Style as PTStyle
14
+ from prompt_toolkit.validation import ValidationError, Validator
15
+
16
+ if TYPE_CHECKING:
17
+ from collections.abc import Iterable
18
+
19
+ from prompt_toolkit.completion import CompleteEvent
20
+ from prompt_toolkit.document import Document
21
+
22
+ _RE_GEO_NAME = re.compile(r"^[\w\s\-.'()/]+$", re.UNICODE)
23
+
24
+
25
+ class FuzzyMatchCompleter(Completer):
26
+ """Completer that shows all choices on empty input.
27
+
28
+ On non-empty input performs case-insensitive substring matching.
29
+ """
30
+
31
+ def __init__(self, choices: list[str], ignore_case: bool = True) -> None:
32
+ self.choices = choices
33
+ self.ignore_case = ignore_case
34
+
35
+ def get_completions(
36
+ self, document: Document, complete_event: CompleteEvent
37
+ ) -> Iterable[Completion]:
38
+ """Yield completions matching current input."""
39
+ text = document.text_before_cursor
40
+ if self.ignore_case:
41
+ text = text.lower()
42
+
43
+ for choice in self.choices:
44
+ compare = choice.lower() if self.ignore_case else choice
45
+ if text in compare:
46
+ yield Completion(
47
+ choice,
48
+ start_position=-len(document.text_before_cursor),
49
+ )
50
+
51
+
52
+ def make_choice_validator(
53
+ choices: list[str],
54
+ error_msg: str,
55
+ error_chars: str,
56
+ ) -> Validator:
57
+ """Create a choice validator for prompt_toolkit.
58
+
59
+ Only values present in *choices* (case-insensitive) are accepted.
60
+ """
61
+ lower_lookup: dict[str, str] = {c.strip().lower(): c for c in choices}
62
+
63
+ class _ChoiceValidator(Validator):
64
+ def validate(self, document: Document) -> None:
65
+ t = document.text.strip()
66
+ if t in choices:
67
+ return
68
+ if t.lower() in lower_lookup:
69
+ return
70
+ if t and not _RE_GEO_NAME.match(t):
71
+ raise ValidationError(message=error_chars)
72
+ raise ValidationError(message=error_msg)
73
+
74
+ return _ChoiceValidator()
75
+
76
+
77
+ def pt_style() -> PTStyle:
78
+ """Color scheme for prompt_toolkit prompts."""
79
+ return PTStyle.from_dict(
80
+ {
81
+ "qmark": "fg:ansicyan bold",
82
+ "question": "fg:ansiwhite bold",
83
+ "answer": "fg:ansigreen bold",
84
+ "completion-menu": "bg:ansiblack fg:ansiwhite",
85
+ "completion-menu.completion": "",
86
+ "completion-menu.completion.current": "bg:ansicyan fg:ansiblack",
87
+ "scrollbar.background": "bg:ansibrightblack",
88
+ "scrollbar.button": "bg:ansicyan",
89
+ }
90
+ )
@@ -0,0 +1,89 @@
1
+ """UI messages and proxy output templates for the geo-picker."""
2
+
3
+ from __future__ import annotations
4
+
5
+ # ── Messages ──────────────────────────────────────────────────────────────── #
6
+
7
+ MESSAGES: dict[str, str] = {
8
+ "pick_country": "🌍 Select country",
9
+ "pick_state": "📍 Select state/region",
10
+ "pick_city": "🏙 Select city",
11
+ "pick_asn": "🔗 Select ASN provider",
12
+ "skip": "⏭ (skip)",
13
+ "no_states": "No states available for this country.",
14
+ "no_cities": "No cities available.",
15
+ "no_asns": "No ASN providers available for this country.",
16
+ "cities_suffix": "cities",
17
+ "params": "Parameters",
18
+ "invalid_choice": "⚠ Value not in the list. Use arrows or type to filter.",
19
+ "invalid_country_code": "⚠ Invalid country code (must be 2 letters, e.g. US).",
20
+ "invalid_chars": "⚠ Input contains invalid characters.",
21
+ "pick_connection_type": "🔌 Connection type",
22
+ "pick_proxy_type": "🛡 Proxy type",
23
+ "pick_server_port_type": "🖥 Server port type",
24
+ "pick_traffic_limit": "📊 Traffic limit (GB)",
25
+ "pick_action": "🚀 What do you want to do?",
26
+ "action_create": "Create proxies — get working proxies quickly",
27
+ "action_best": "Find best proxies — create, benchmark, keep the fastest",
28
+ "pick_count": "🔢 How many proxies?",
29
+ "pick_keep": "🏆 How many best to keep?",
30
+ "pick_name": "📝 Port name (optional, Enter to skip)",
31
+ "pick_format": "📄 Export format",
32
+ "pick_output": "💾 Output file (optional, Enter to skip)",
33
+ "pick_timeout": "⏱ Benchmark timeout (seconds)",
34
+ "pick_concurrency": "⚡ Benchmark concurrency",
35
+ "pick_proxy_template": "📋 Output template (proxy format)",
36
+ }
37
+
38
+
39
+ def get_message(key: str) -> str:
40
+ """Get a message string by key."""
41
+ return MESSAGES.get(key, key)
42
+
43
+
44
+ # ── Proxy output templates ────────────────────────────────────────────────── #
45
+
46
+ PROXY_TEMPLATES: list[tuple[str, str]] = [
47
+ ("{protocol}://{login}:{password}@{ip}:{port}", "Standard URL — protocol://login:pass@ip:port"),
48
+ ("http://{login}:{password}@{ip}:{port}", "HTTP URL — http://login:pass@ip:port"),
49
+ ("socks5://{login}:{password}@{ip}:{port}", "SOCKS5 URL — socks5://login:pass@ip:port"),
50
+ ("{ip}:{port}:{login}:{password}", "ip:port:login:password"),
51
+ ("{protocol}://{ip}:{port}:{login}:{password}", "protocol://ip:port:login:password"),
52
+ (
53
+ "{protocol}://{login}:{password}@{ip}:{port}[{refresh_link}]",
54
+ "URL with refresh link — protocol://login:pass@ip:port[refresh]",
55
+ ),
56
+ (
57
+ "{protocol}://{login}:{password}@{ip}:{port}:{name}[{refresh_link}]",
58
+ "Full URL — login:pass@ip:port:name[refresh]",
59
+ ),
60
+ (
61
+ "http://{login}:{password}@{ip}:{port}:{name}[{refresh_link}]",
62
+ "HTTP full — http://login:pass@ip:port:name[refresh]",
63
+ ),
64
+ (
65
+ "socks5://{login}:{password}@{ip}:{port}:{name}[{refresh_link}]",
66
+ "SOCKS5 full — socks5://login:pass@ip:port:name[refresh]",
67
+ ),
68
+ ("{ip}:{port}:{login}:{password}:{refresh_link}", "ip:port:login:password:refresh_link"),
69
+ ("{ip}:{port}:{login}:{password}:{name}", "ip:port:login:password:name"),
70
+ ("{name}:{ip}:{port}:{login}:{password}", "name:ip:port:login:password"),
71
+ ("{ip}:{port}:{login}:{password}|{refresh_link}", "ip:port:login:password|refresh_link"),
72
+ ("HTTP|{ip}|{port}|{login}|{password}", "HTTP|ip|port|login|password (pipe separated)"),
73
+ (
74
+ "{protocol}://{ip}:{port}:{login}:{password}:{refresh_link}",
75
+ "protocol://ip:port:login:password:refresh",
76
+ ),
77
+ (
78
+ "{protocol}://{ip}:{port}:{login}:{password}[{refresh_link}]{{name}}",
79
+ "protocol://ip:port:login:password[refresh]{name}",
80
+ ),
81
+ (
82
+ "{protocol}:{ip}:{port}:{login}:{password}",
83
+ "protocol:ip:port:login:password (no slashes)",
84
+ ),
85
+ (
86
+ "curl -x http://{login}:{password}@{ip}:{port} https://i.pn",
87
+ "curl command — ready-to-use curl",
88
+ ),
89
+ ]
@@ -0,0 +1,89 @@
1
+ """Data types for the geo-picker module.
2
+
3
+ Frozen dataclasses representing user selections, plus static
4
+ choice data for connection types, proxy types, and server port types.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from dataclasses import dataclass
10
+
11
+ # ── Picked result types ───────────────────────────────────────────────────── #
12
+
13
+
14
+ @dataclass(frozen=True, slots=True)
15
+ class PickedCountry:
16
+ """Selected country."""
17
+
18
+ code: str
19
+ name: str
20
+ id: int
21
+
22
+
23
+ @dataclass(frozen=True, slots=True)
24
+ class PickedState:
25
+ """Selected state/region."""
26
+
27
+ name: str
28
+ id: int
29
+
30
+
31
+ @dataclass(frozen=True, slots=True)
32
+ class PickedCity:
33
+ """Selected city."""
34
+
35
+ name: str
36
+ id: int
37
+
38
+
39
+ @dataclass(frozen=True, slots=True)
40
+ class PickedASN:
41
+ """Selected ASN."""
42
+
43
+ number: int
44
+ name: str
45
+
46
+
47
+ @dataclass(frozen=True, slots=True)
48
+ class PickedConnectionType:
49
+ """Selected connection type."""
50
+
51
+ id: int
52
+ label: str
53
+
54
+
55
+ @dataclass(frozen=True, slots=True)
56
+ class PickedProxyType:
57
+ """Selected proxy type."""
58
+
59
+ id: int
60
+ label: str
61
+
62
+
63
+ @dataclass(frozen=True, slots=True)
64
+ class PickedServerPortType:
65
+ """Selected server port type."""
66
+
67
+ id: int
68
+ label: str
69
+
70
+
71
+ # ── Static choice data ───────────────────────────────────────────────────── #
72
+
73
+ CONNECTION_TYPES: list[tuple[int, str]] = [
74
+ (1, "Keep Proxy — fixed proxy, highest trust"),
75
+ (2, "Keep Connection — fixed connection, high trust"),
76
+ (3, "Rotate Connection — rotation on each request"),
77
+ ]
78
+
79
+ PROXY_TYPES: list[tuple[int, str]] = [
80
+ (1, "Residential — home IP addresses"),
81
+ (2, "All — all proxy types"),
82
+ (3, "Mobile — mobile operator IPs"),
83
+ (4, "Corporate — corporate network IPs"),
84
+ ]
85
+
86
+ SERVER_PORT_TYPES: list[tuple[int, str]] = [
87
+ (0, "Shared — shared port (free)"),
88
+ (1, "Dedicated — dedicated port (paid, traffic limit)"),
89
+ ]