From a4baeb99f83b4359c6a1ca2af78a0accb85be245 Mon Sep 17 00:00:00 2001 From: Marc <50042689+marcsnvv@users.noreply.github.com> Date: Tue, 4 Nov 2025 22:29:59 +0100 Subject: [PATCH 1/6] Filter out None cookies when creating request_cookies --- tls_client/sessions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tls_client/sessions.py b/tls_client/sessions.py index e625669..0fb58ff 100644 --- a/tls_client/sessions.py +++ b/tls_client/sessions.py @@ -362,7 +362,7 @@ def execute_request( # in the cookie value the " gets removed, because the fhttp library in golang doesn't accept the character request_cookies = [ {'domain': c.domain, 'expires': c.expires, 'name': c.name, 'path': c.path, 'value': c.value.replace('"', "")} - for c in cookies + for c in cookies if c is not None ] # --- Proxy ---------------------------------------------------------------------------------------------------- From 8cf6d2acad72f467c51f3f80c0f8ea4f75116fa7 Mon Sep 17 00:00:00 2001 From: Marc <50042689+marcsnvv@users.noreply.github.com> Date: Tue, 4 Nov 2025 22:34:43 +0100 Subject: [PATCH 2/6] Refactor cookie handling to use getattr for attributes --- tls_client/sessions.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tls_client/sessions.py b/tls_client/sessions.py index 0fb58ff..f4000e8 100644 --- a/tls_client/sessions.py +++ b/tls_client/sessions.py @@ -361,8 +361,15 @@ def execute_request( # turn cookie jar into dict # in the cookie value the " gets removed, because the fhttp library in golang doesn't accept the character request_cookies = [ - {'domain': c.domain, 'expires': c.expires, 'name': c.name, 'path': c.path, 'value': c.value.replace('"', "")} - for c in cookies if c is not None + { + 'domain': getattr(c, "domain", ""), + 'expires': getattr(c, "expires", ""), + 'name': getattr(c, "name", ""), + 'path': getattr(c, "path", ""), + 'value': (getattr(c, "value", "") or "").replace('"', ""), + } + for c in cookies + if c is not None ] # --- Proxy ---------------------------------------------------------------------------------------------------- From adf9a045a50824ee18b418dd607168e20690ef59 Mon Sep 17 00:00:00 2001 From: Marc <50042689+marcsnvv@users.noreply.github.com> Date: Wed, 5 Nov 2025 12:16:08 +0100 Subject: [PATCH 3/6] Enhance proxy handling in sessions.py Auto-format proxy string for common proxy formats. --- tls_client/sessions.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tls_client/sessions.py b/tls_client/sessions.py index f4000e8..6b958a1 100644 --- a/tls_client/sessions.py +++ b/tls_client/sessions.py @@ -378,7 +378,16 @@ def execute_request( if type(proxy) is dict and "http" in proxy: proxy = proxy["http"] elif type(proxy) is str: - proxy = proxy + # Auto-format the proxy. Most common format when you buy proxies is ip:port:username:password. + if not proxy.startswith("http"): + method = "http://" + if "@" not in proxy: + ip, port, username, password = proxy.split(":") + proxy = f"{method}{username}:{password}@{ip}:{port}" + else: + proxy = f"{method}{proxy}" + else: + proxy = proxy else: proxy = "" From 39a34038be3aaf4bd7a49317273fefb1a820bb8a Mon Sep 17 00:00:00 2001 From: Marc <50042689+marcsnvv@users.noreply.github.com> Date: Wed, 5 Nov 2025 12:45:27 +0100 Subject: [PATCH 4/6] Enhance proxy format handling in sessions.py Refactor proxy handling to improve format validation and support various proxy formats. --- tls_client/sessions.py | 37 ++++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/tls_client/sessions.py b/tls_client/sessions.py index 6b958a1..d1ca62f 100644 --- a/tls_client/sessions.py +++ b/tls_client/sessions.py @@ -378,16 +378,35 @@ def execute_request( if type(proxy) is dict and "http" in proxy: proxy = proxy["http"] elif type(proxy) is str: - # Auto-format the proxy. Most common format when you buy proxies is ip:port:username:password. - if not proxy.startswith("http"): - method = "http://" - if "@" not in proxy: - ip, port, username, password = proxy.split(":") - proxy = f"{method}{username}:{password}@{ip}:{port}" + try: + if proxy.startswith("http"): + proxy = proxy else: - proxy = f"{method}{proxy}" - else: - proxy = proxy + if not "@" in proxy: + if len(proxy.split(":")) > 2: + ip, port, username, password = proxy.split(":") + proxy = f"http://{username}:{password}@{ip}:{port}" + else: + proxy = f"http://{proxy}" + else: + proxy = f"http://{proxy}" + except ValueError: + raise ValueError( + f"Invalid proxy format: {proxy}\n\n" + "Accepted proxy formats:\n" + " 1) http://ip:port\n" + " 2) https://ip:port\n" + " 3) http://username:password@ip:port\n" + " 4) https://username:password@ip:port\n" + " 5) ip:port (auto-converted to http://ip:port)\n" + " 6) ip:port:username:password (auto-converted to http://username:password@ip:port)\n" + " 7) username:password@ip:port (auto-converted to http://username:password@ip:port)\n" + " 8) {\"http\": \"\"} (dictionary form — value is taken as-is)\n\n" + "Notes:\n" + " - Only HTTP/HTTPS proxy schemas are automatically recognized.\n" + " - SOCKS proxies must be passed via dict form (e.g. {\"http\": \"socks5://...\"}).\n" + " - IPv6 and credentials containing ':' or '@' are not supported by this parser.\n" + ) else: proxy = "" From 027477b69f028103afd64767366c70ae077a8a6a Mon Sep 17 00:00:00 2001 From: Marc <50042689+marcsnvv@users.noreply.github.com> Date: Wed, 5 Nov 2025 12:48:47 +0100 Subject: [PATCH 5/6] Implement latest Chrome version retrieval Add function to retrieve the latest Chrome version for Windows. --- tls_client/sessions.py | 52 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/tls_client/sessions.py b/tls_client/sessions.py index d1ca62f..b4a479d 100644 --- a/tls_client/sessions.py +++ b/tls_client/sessions.py @@ -7,12 +7,53 @@ from .__version__ import __version__ from typing import Any, Dict, List, Optional, Union -from json import dumps, loads +from json import dumps, loads, load import urllib.parse +import urllib.request +import urllib.error import base64 import ctypes import uuid +_CHROME_STABLE_WIN_API = "https://versionhistory.googleapis.com/v1/chrome/platforms/win/channels/stable/versions" +_CHROME_LATEST_CACHE = None + +def _get_latest_chrome_version(timeout: float = 5.0): + """ + Returns the latest stable version of Chrome for Windows (e.g., '142.0.7444.60'). + Caches the result in memory to avoid multiple requests. + """ + global _CHROME_LATEST_CACHE + if _CHROME_LATEST_CACHE: + return _CHROME_LATEST_CACHE + try: + with urllib.request.urlopen(_CHROME_STABLE_WIN_API, timeout=timeout) as resp: + data = load(resp) + versions = data.get("versions", []) + if not versions: + return None + + # The API usually brings the latest one first. + latest = versions[0].get("version") + if latest: + _CHROME_LATEST_CACHE = latest + return latest + + # Backup: sort by numeric tuple major.minor.build.patch + def parse_version(vobj: dict): + v = vobj.get("version", "") + try: + return tuple(int(p) for p in v.split(".")) + except ValueError: + return tuple() + + versions_sorted = sorted(versions, key=parse_version, reverse=True) + latest = versions_sorted[0].get("version") if versions_sorted else None + _CHROME_LATEST_CACHE = latest + return latest + except (urllib.error.URLError, urllib.error.HTTPError, TimeoutError, ValueError): + return None + class Session: @@ -83,6 +124,15 @@ def __init__( # iPadOS --> safari_ios_15_6 # # for all possible client identifiers, check out the settings.py + if client_identifier == "chrome_latest": + latest_version = _get_latest_chrome_version() + if latest_version: + # We convert to 'chrome_' which is the format expected by the fingerprint + major = latest_version.split(".")[0] + client_identifier = f"chrome_{major}" + # Optional: Keep the full version in case you need it later + self.chrome_full_version = latest_version + self.client_identifier = client_identifier # Set JA3 --> TLSVersion, Ciphers, Extensions, EllipticCurves, EllipticCurvePointFormats From 39d737d27edb384f6dde776b3466c7714d983e4e Mon Sep 17 00:00:00 2001 From: Marc <50042689+marcsnvv@users.noreply.github.com> Date: Wed, 5 Nov 2025 12:49:34 +0100 Subject: [PATCH 6/6] Add option for latest stable Chrome version Added a new option to retrieve the latest stable version of Chrome. --- tls_client/settings.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tls_client/settings.py b/tls_client/settings.py index e449a66..d3961dc 100644 --- a/tls_client/settings.py +++ b/tls_client/settings.py @@ -16,6 +16,7 @@ "chrome_116_PSK_PQ", "chrome_117", "chrome_120", + "chrome_latest", # This retrieves the last stable version of Chrome in Windows using Google API. # Safari "safari_15_6_1", "safari_16_0", @@ -61,4 +62,4 @@ "confirmed_ios", "confirmed_android", "confirmed_android_2", -] \ No newline at end of file +]