Skip to content

botonomus.network

Proxy tunnelling, the credential forwarder, exit geolocation and proxy checks.

botonomus.network

Proxy tunnelling, the credential forwarder, exit geolocation and proxy checks.

COUNTRY_LOCALE module-attribute

COUNTRY_LOCALE: Final = {'US': 'en-US', 'GB': 'en-GB', 'CA': 'en-CA', 'AU': 'en-AU', 'IE': 'en-IE', 'DE': 'de-DE', 'AT': 'de-AT', 'CH': 'de-CH', 'FR': 'fr-FR', 'BE': 'fr-BE', 'NL': 'nl-NL', 'ES': 'es-ES', 'MX': 'es-MX', 'IT': 'it-IT', 'PT': 'pt-PT', 'BR': 'pt-BR', 'PL': 'pl-PL', 'SE': 'sv-SE', 'NO': 'nb-NO', 'DK': 'da-DK', 'FI': 'fi-FI', 'CZ': 'cs-CZ', 'RO': 'ro-RO', 'UA': 'uk-UA', 'RU': 'ru-RU', 'TR': 'tr-TR', 'GE': 'ka-GE', 'JP': 'ja-JP', 'KR': 'ko-KR', 'CN': 'zh-CN', 'TW': 'zh-TW', 'IN': 'en-IN', 'IL': 'he-IL', 'GR': 'el-GR', 'HU': 'hu-HU'}

Most common primary browser locale per ISO 3166-1 alpha-2 country.

DEFAULT_PROVIDERS module-attribute

DEFAULT_PROVIDERS: Final[tuple[GeoProvider, ...]] = (IpApiProvider(), IpInfoProvider())

ip-api.com first for its risk flags, then ipinfo.io over TLS.

ProxyForwarder

ProxyForwarder(upstream: ProxySpec)

A per-browser loopback SOCKS5 server tunnelling through one upstream proxy.

Every accepted connection is registered synchronously, as soon as its transport exists, so close can abort all of them, including ones accepted in the instant before the listener closed.

Parameters:

Name Type Description Default
upstream ProxySpec

The authenticated upstream proxy.

required

Attributes:

Name Type Description
port

The bound loopback port, valid after start.

server property

server: str

The --proxy-server value pointing Chrome at this forwarder.

active_connections property

active_connections: int

Client connections accepted and not yet finished.

start async

start() -> None

Bind to an ephemeral loopback port and start accepting connections.

Raises:

Type Description
OSError

If the loopback socket cannot be bound.

close async

close() -> None

Stop accepting, abort every client connection and wait for them. Idempotent.

Client transports are aborted rather than closed gracefully: a graceful close waits for buffered writes, which never drain once the peer has stopped reading.

ExitCache

ExitCache(ttl: float = 3600.0, *, lookup: Callable[[ProxySpec], Awaitable[ExitInfo]] | None = None, clock: Callable[[], float] = monotonic)

Per-proxy exit lookups, cached in memory with a time to live.

Concurrent get calls for the same proxy share one lookup. Failures are not cached. Proxies with different credentials are distinct keys, since providers often pin a sticky exit to the username.

Parameters:

Name Type Description Default
ttl float

Seconds a successful result stays valid.

3600.0
lookup Callable[[ProxySpec], Awaitable[ExitInfo]] | None

The lookup coroutine; defaults to exit_info with its default timeout and providers.

None
clock Callable[[], float]

Monotonic time source, replaceable in tests.

monotonic

get async

get(spec: ProxySpec) -> ExitInfo

Return the cached exit for spec, looking it up if absent or expired.

Cancelling one caller does not cancel a lookup other callers share.

Raises:

Type Description
ExitLookupError

Or whatever the configured lookup raises, for every caller waiting on the failed lookup.

invalidate

invalidate(spec: ProxySpec | None = None) -> None

Forget the result for spec, or every result when spec is None.

Lookups already running are not affected.

ExitInfo dataclass

ExitInfo(ip: str, country_code: str, region: str, city: str, timezone: str, isp: str, hosting: bool, flagged_proxy: bool, latency: float, source: str = '')

A proxy exit as seen by the lookup service.

Providers differ in what they report; fields a provider does not supply are empty strings, and flags it does not supply are False.

Attributes:

Name Type Description
ip str

Public exit address.

country_code str

ISO 3166-1 alpha-2 code, or empty if unknown.

region str

Region or state name.

city str

City name.

timezone str

IANA zone such as Europe/Berlin.

isp str

Network operator (ipinfo.io reports the AS organisation).

hosting bool

The address belongs to a data-centre range (ip-api.com only).

flagged_proxy bool

The address is a known proxy, VPN or Tor exit (ip-api.com only).

latency float

Seconds for the lookup round trip through the proxy.

source str

Name of the provider that answered, such as ip-api.

locale

locale() -> str

A plausible primary browser locale for the exit country.

ExitLookupError

ExitLookupError(causes: Sequence[tuple[str, Exception]])

Bases: UpstreamError

Every exit lookup provider failed.

Attributes:

Name Type Description
causes

(provider name, exception) for each failed attempt, in order.

GeoProvider

Bases: Protocol

An exit lookup service reachable with one HTTP request through the tunnel.

name property

name: str

Short provider name, recorded in ExitInfo.source and errors.

host property

host: str

Hostname of the service, sent unresolved to the proxy.

port property

port: int

TCP port of the service.

tls_context

tls_context() -> SSLContext | None

The TLS context for the service, or None for plain HTTP.

request

request() -> bytes

The complete HTTP request, which must ask the server to close afterwards.

parse

parse(response: bytes, *, latency: float) -> ExitInfo

Turn the raw HTTP response into an ExitInfo.

Raises:

Type Description
UpstreamError

If the response is not a successful lookup.

IpApiProvider dataclass

IpApiProvider(host: str = 'ip-api.com', port: int = 80)

ip-api.com over plain HTTP: location, ISP and data-centre / proxy flags.

The free endpoint has no TLS and allows about 45 requests per minute per exit address; beyond that it answers 429.

Attributes:

Name Type Description
host str

Service hostname.

port int

Service port.

tls_context

tls_context() -> SSLContext | None

Plain HTTP, so no TLS context.

request

request() -> bytes

A GET /json/ request for the fields ExitInfo needs.

parse

parse(response: bytes, *, latency: float) -> ExitInfo

Parse an ip-api.com JSON response.

Raises:

Type Description
UpstreamError

On a non-200 status (including rate limiting), malformed JSON, or a status other than success.

IpInfoProvider dataclass

IpInfoProvider(host: str = 'ipinfo.io', port: int = 443, context: SSLContext | None = None)

ipinfo.io over TLS: location, timezone and AS organisation, no risk flags.

Attributes:

Name Type Description
host str

Service hostname, also used for SNI and certificate verification.

port int

Service port.

context SSLContext | None

TLS context; None uses the system trust store.

tls_context

tls_context() -> SSLContext

The configured context, or a verifying default one.

request

request() -> bytes

A GET /json request.

parse

parse(response: bytes, *, latency: float) -> ExitInfo

Parse an ipinfo.io JSON response.

Raises:

Type Description
UpstreamError

On a non-200 status (including rate limiting), malformed JSON, an error object, or a missing address.

ProxyCheck dataclass

ProxyCheck(address: str, ok: bool, error: str | None, exit: ExitInfo | None)

The outcome of checking one proxy. Never contains credentials.

Attributes:

Name Type Description
address str

host:port of the proxy, or entry N (1-based) when the URL itself is invalid.

ok bool

Whether an exit lookup through the proxy succeeded.

error str | None

Failure category, None on success: invalid (malformed URL), unreachable (cannot connect to the proxy), timeout, tls (TLS to the proxy or lookup service failed) or upstream (the proxy refused the tunnel or the lookup service failed).

exit ExitInfo | None

The exit as seen by the lookup service, None on failure.

UpstreamError

Bases: Exception

The upstream proxy refused or failed the tunnel.

exit_info async

exit_info(spec: ProxySpec, timeout: float = 15.0, providers: Sequence[GeoProvider] = DEFAULT_PROVIDERS) -> ExitInfo

Look up the proxy's exit address and location through the proxy itself.

Providers are tried in order until one succeeds.

Parameters:

Name Type Description Default
spec ProxySpec

The proxy to test.

required
timeout float

Seconds allowed for each provider attempt, so the whole call is bounded by timeout * len(providers).

15.0
providers Sequence[GeoProvider]

Lookup services in order of preference.

DEFAULT_PROVIDERS

Returns:

Type Description
ExitInfo

The exit as seen by the first provider that answered.

Raises:

Type Description
ExitLookupError

If every provider failed; causes holds each failure (UpstreamError, OSError, TimeoutError and similar).

check_proxies async

check_proxies(urls: Iterable[str], *, parallel: int = 16, timeout: float = 20.0, providers: Sequence[GeoProvider] = DEFAULT_PROVIDERS) -> list[ProxyCheck]

Check proxies concurrently by looking up each one's exit through it.

A failing proxy never fails the batch; it is reported in its result.

Parameters:

Name Type Description Default
urls Iterable[str]

Proxy URLs, for example from load_proxies.

required
parallel int

Maximum number of checks in flight.

16
timeout float

Seconds allowed per lookup provider attempt.

20.0
providers Sequence[GeoProvider]

Exit lookup services, tried in order for each proxy.

DEFAULT_PROVIDERS

Returns:

Type Description
list[ProxyCheck]

One result per URL, in input order.

Raises:

Type Description
ValueError

If parallel is less than 1.

load_proxies

load_proxies(path: str | Path) -> list[str]

Read one proxy URL per line.

Blank lines and lines starting with # are ignored. Every entry is validated up front, without echoing credentials in errors.

Parameters:

Name Type Description Default
path str | Path

A UTF-8 text file.

required

Returns:

Type Description
list[str]

The proxy URLs in file order.

Raises:

Type Description
ConfigurationError

If any entry is not a valid proxy URL.

OSError

If the file cannot be read.

open_tunnel async

open_tunnel(spec: ProxySpec, host: str, port: int) -> tuple[StreamReader, StreamWriter]

Open a TCP stream to host:port through the upstream proxy.

Parameters:

Name Type Description Default
spec ProxySpec

The upstream proxy. https proxies are reached over TLS.

required
host str

Destination hostname or IP literal.

required
port int

Destination port.

required

Returns:

Type Description
tuple[StreamReader, StreamWriter]

A connected (reader, writer) pair carrying the tunnelled stream.

Raises:

Type Description
UpstreamError

If the proxy refuses, fails authentication or misbehaves.

OSError

If the proxy cannot be reached.