fix(proton): update query parsing and server selection logic for improved accuracy

This commit is contained in:
imSp4rky
2026-10-18 19:18:58 -06:00
parent 1a8e633fdc
commit 4728ac593c
+22 -15
View File
@@ -25,13 +25,14 @@ class ProtonVPN(Proxy):
Authenticates through TV login (a self-sustaining login session cached at <cache>/vpn/protonvpn.json, the
only refreshable kind) or an exported account.proton.me cookie session (AUTH-<UID>, access only,
re-export on expiry). Resolves a server by country/city, mints short-lived proxy credentials, and
re-export on expiry). Resolves a server by country/city/node, mints short-lived proxy credentials, and
returns an authenticated HTTPS proxy URL (https://user:pass@server:4443).
Query format (after the proxy provider prefix, for example "protonvpn:us"):
us random server in the country (paid preferred over free)
us12 Proton server #12 (the number shown in the connection log)
us:ny city, NordVPN-style
us random server in the country (paid preferred over free)
us12 / us-12 / us#12 Proton server #12 (the number shown in the connection log)
us:ny city, NordVPN-style
node-pl-24 specific physical server node by domain
Free Proton accounts work, limited to the free-tier exit countries.
"""
@@ -122,19 +123,25 @@ class ProtonVPN(Proxy):
query = self.server_map.get(query, query)
country, city, server_num = self.parse_query(query)
max_tier = self.get_max_tier()
eligible = [
s
for s in self.get_logicals()
if s.get("Status") == 1
and int(s.get("Tier") or 0) <= max_tier
and not int(s.get("Features") or 0) & self.EXCLUDE_FEATURES
]
pool = []
if country:
max_tier = self.get_max_tier() # only servers the account can actually connect to
pool = [
s
for s in self.get_logicals()
if str(s.get("ExitCountry") or "").lower() == country
and s.get("Status") == 1
and int(s.get("Tier") or 0) <= max_tier
and not int(s.get("Features") or 0) & self.EXCLUDE_FEATURES
]
pool = [s for s in eligible if str(s.get("ExitCountry") or "").lower() == country]
if city:
pool = self.match_city(pool, city)
else:
domain_query = query.removesuffix(".protonvpn.net")
pool = [
s for s in eligible if str(s.get("Domain") or "").lower().removesuffix(".protonvpn.net") == domain_query
]
if not pool:
log.warning("Proton: no server matched query '%s'", query)
@@ -142,13 +149,13 @@ class ProtonVPN(Proxy):
return self.select(pool, server_num)
def parse_query(self, query: str) -> tuple[Optional[str], Optional[str], Optional[int]]:
"""Parse "us", "us12"/"us-12" (server #12), and "us:ny" (city)."""
"""Parse "us", "us12"/"us-12"/"us#12" (server #12), and "us:ny" (city)."""
city = None
if ":" in query:
query, city = query.split(":", 1)
city = city.strip() or None
match = re.match(r"^([a-z]{2})-?(\d+)?$", query.strip())
match = re.match(r"^([a-z]{2})[-#]?(\d+)?$", query.strip())
if not match:
return None, None, None
country = self.COUNTRY_ALIASES.get(match.group(1), match.group(1))