Metadata-Version: 2.4
Name: noble_tls
Version: 0.1.9
Summary: Advanced TLS/SSL wrapper for Python
Author: Rawand Ahmed Shaswar
License: MIT
Classifier: Environment :: Web Environment
Classifier: Intended Audience :: Developers
Classifier: Natural Language :: English
Classifier: Operating System :: Unix
Classifier: Operating System :: MacOS :: MacOS X
Classifier: Operating System :: Microsoft :: Windows
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Software Development :: Libraries
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: httpx
Requires-Dist: distro
Requires-Dist: requests
Dynamic: author
Dynamic: classifier
Dynamic: description
Dynamic: description-content-type
Dynamic: license
Dynamic: license-file
Dynamic: requires-dist
Dynamic: summary

# Noble TLS

[![Python 3.10](https://img.shields.io/badge/python-3.10-blue.svg)](https://www.python.org/downloads/release/python-310/)

Noble TLS is an advanced HTTP library based on requests and tls-client.
Fully async, with auto-updating JA3 fingerprints.

# Installation
```
pip install noble-tls
```

### Features
- [x] Auto-update TLS client libs from bogdanfinn/tls-client
- [x] Async support
- [x] Proxy support (HTTP, HTTPS, SOCKS4, SOCKS5)
- [x] Custom JA3 string
- [x] Custom H2 and H3 settings
- [x] Custom supported signature algorithms
- [x] Custom supported versions
- [x] Custom key share curves (including post-quantum)
- [x] Custom cert compression algorithm
- [x] Custom pseudo header order
- [x] Custom connection flow
- [x] Custom header order
- [x] 76 preset browser profiles (Chrome, Firefox, Opera, Safari, iOS, iPadOS, Android)
- [x] Random TLS extension order
- [x] HTTP/3 (QUIC) with protocol racing
- [x] Certificate pinning (HPKP)
- [x] ECH (Encrypted Client Hello) and ALPS
- [x] Session lifecycle management (close, get/add cookies from Go layer)
- [x] Response streaming to file
- [x] Protocol detection (`response.used_protocol`)
- [x] Millisecond-precision timeouts
- [x] IPv4/IPv6 control
- [x] SNI and Host header overrides
- [x] Rotating proxy support
- [x] `requests`-style `history` and `allow_redirects`

---

## What's new

### Session lifecycle

You can now explicitly destroy sessions and manage cookies at the Go layer:

```python
session = noble_tls.Session(client=Client.CHROME_133)

# Do your work...
res = await session.get("https://example.com")

# Manage cookies in the Go session directly
cookies = await session.get_cookies("https://example.com")
await session.add_cookies("https://example.com", [
    {"name": "sid", "value": "abc", "domain": "example.com", "path": "/"},
])

# Free Go memory and connections when done
await session.close()

# Or nuke everything at once
await Session.close_all()
```

### Protocol detection

Every response now tells you which protocol was actually negotiated. Using a `Protocol` enum:

```python
from noble_tls import Protocol

res = await session.get("https://example.com")
print(res.used_protocol)  # Protocol.HTTP_2

if res.used_protocol == Protocol.HTTP_3:
    print("Running over QUIC")
```

### HTTP/3 and protocol racing

HTTP/3 (QUIC) support, with the ability to race HTTP/2 against HTTP/3 the way Chrome does (300ms head start for H2, H3 can still win):

```python
session = noble_tls.Session(
    ja3_string="...",
    protocol_racing=True,
    h3_settings={"QPACK_MAX_TABLE_CAPACITY": 4096},
    h3_settings_order=["QPACK_MAX_TABLE_CAPACITY"],
    h3_pseudo_header_order=[":method", ":authority", ":scheme", ":path"],
    h3_send_grease_frames=True,
)
```

You can also just disable H3 if it causes problems: `disable_http3=True`.

### Network and security options

Per-session network and security settings:

```python
session = noble_tls.Session(
    client=Client.CHROME_133_PSK,
    disable_ipv6=True,                # IPv4 only
    local_address="0.0.0.0:0",        # Bind to specific interface (host:port)
    server_name_overwrite="sni.com",   # Override TLS SNI
    is_rotating_proxy=True,            # Force new connection per request
    without_cookie_jar=True,           # Disable cookie jar entirely
    certificate_pinning={              # HPKP - reject if pin doesn't match
        "example.com": ["sha256_pin_base64"],
    },
)

# Per-request: millisecond timeout, host override, stream to file
res = await session.get(
    "https://example.com",
    timeout_milliseconds=2500,
    request_host_override="other.host.com",
    stream_output_path="/tmp/response.json",
    stream_output_block_size=4096,
)
```

### ECH, ALPS, and post-quantum curves

Encrypted Client Hello, Application Layer Protocol Settings, and post-quantum key share curves for custom TLS configurations:

```python
session = noble_tls.Session(
    ja3_string="...",
    alps_protocols=["h2"],
    ech_candidate_payloads=[256],
    ech_candidate_cipher_suites=[
        {"kdfId": "HKDF_SHA256", "aeadId": "AEAD_AES_128_GCM"},
    ],
    key_share_curves=["GREASE", "X25519MLKEM768", "X25519"],
    supported_signature_algorithms=[
        "ECDSAWithP256AndSHA256",
        "PSSWithSHA256",
        "Ed25519",
    ],
    record_size_limit=16384,
    allow_http=True,
    stream_id=1,
)
```

### Updated browser profiles

76 profiles now, synced with the latest Go source. New additions:

| Browser | New profiles |
|---------|-------------|
| Chrome | 130 PSK, 144, 144 PSK, 146 PSK |
| Firefox | 123, 133, 146 PSK, 147 PSK |
| Safari | iOS 18.5, iOS 26.0 |

Removed `CHROME_141` and `CHROME_142`

### Default headers (multi-value)

Separate from the regular `headers` dict, `default_headers` uses a multi-value format and acts as a fallback when no headers are specified on a request:

```python
session = noble_tls.Session(
    client=Client.CHROME_133,
    default_headers={"Accept": ["text/html", "application/json"]},
)
```

---

## :shield: Need antibot bypass?
<a href="https://hypersolutions.co/?utm_source=github&utm_medium=readme&utm_campaign=noble-tls" target="_blank"><img src="https://github.com/rawandahmad698/noble-tls/blob/master/.github/assets/hypersolutions.jpg?raw=true" height="47" width="149"></a>

TLS fingerprinting alone won't get past modern bot protection. **[Hyper Solutions](https://hypersolutions.co?utm_source=github&utm_medium=readme&utm_campaign=noble-tls)** provides API endpoints that generate valid antibot tokens for:

**Akamai** | **DataDome** | **Kasada** | **Incapsula**

No browser automation. Simple API calls that return the cookies and headers these systems expect.

**[Get your API key](https://hypersolutions.co?utm_source=github&utm_medium=readme&utm_campaign=noble-tls)** | **[Docs](https://docs.justhyped.dev)** | **[Discord](https://discord.gg/akamai)**

# Examples

The syntax follows [requests](https://github.com/psf/requests) closely. Most things work the same way.

Example 1 -- Preset browser profile:

<details>
<summary>Available client identifiers (76 profiles)</summary>

| Chrome | Safari | Firefox | Opera |
|--------|--------|---------|-------|
| `CHROME_103` | `SAFARI_15_6_1` | `FIREFOX_102` | `OPERA_89` |
| `CHROME_104` | `SAFARI_16_0` | `FIREFOX_104` | `OPERA_90` |
| `CHROME_105` | `SAFARI_IPAD_15_6` | `FIREFOX_105` | `OPERA_91` |
| `CHROME_106` | `SAFARI_IOS_15_5` | `FIREFOX_106` | |
| `CHROME_107` | `SAFARI_IOS_15_6` | `FIREFOX_108` | |
| `CHROME_108` | `SAFARI_IOS_16_0` | `FIREFOX_110` | |
| `CHROME_109` | `SAFARI_IOS_17_0` | `FIREFOX_117` | |
| `CHROME_110` | `SAFARI_IOS_18_0` | `FIREFOX_120` | |
| `CHROME_111` | `SAFARI_IOS_18_5` | `FIREFOX_123` | |
| `CHROME_112` | `SAFARI_IOS_26_0` | `FIREFOX_132` | |
| `CHROME_116_PSK` | | `FIREFOX_133` | |
| `CHROME_116_PSK_PQ` | | `FIREFOX_135` | |
| `CHROME_117` | | `FIREFOX_146_PSK` | |
| `CHROME_120` | | `FIREFOX_147` | |
| `CHROME_124` | | `FIREFOX_147_PSK` | |
| `CHROME_130_PSK` | | | |
| `CHROME_131` | | | |
| `CHROME_131_PSK` | | | |
| `CHROME_133` | | | |
| `CHROME_133_PSK` | | | |
| `CHROME_144` | | | |
| `CHROME_144_PSK` | | | |
| `CHROME_146` | | | |
| `CHROME_146_PSK` | | | |

| Mobile / App |
|-------------|
| `ZALANDO_ANDROID_MOBILE`, `ZALANDO_IOS_MOBILE` |
| `NIKE_IOS_MOBILE`, `NIKE_ANDROID_MOBILE` |
| `CLOUDSCRAPER` |
| `MMS_IOS`, `MMS_IOS_1`, `MMS_IOS_2`, `MMS_IOS_3` |
| `MESH_IOS`, `MESH_IOS_1`, `MESH_IOS_2` |
| `MESH_ANDROID`, `MESH_ANDROID_1`, `MESH_ANDROID_2` |
| `CONFIRMED_IOS`, `CONFIRMED_ANDROID` |
| `OKHTTP4_ANDROID_7` through `OKHTTP4_ANDROID_13` |

</details>

```python
import asyncio
import noble_tls
from noble_tls import Client

async def main():
    await noble_tls.update_if_necessary()
    session = noble_tls.Session(
        client=Client.CHROME_133,
        random_tls_extension_order=True
    )
    res = await session.get(
        "https://www.example.com/",
        headers={"key1": "value1"},
        proxy="http://user:password@host:port"
    )
    print(res.status_code)
    print(res.used_protocol)
    print(res.text)

    await session.close()

asyncio.run(main())
```

Example 2 -- Custom JA3 fingerprint:

```python
import asyncio
import noble_tls

async def main():
    await noble_tls.update_if_necessary()

    session = noble_tls.Session(
        ja3_string="771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,0-23-65281-10-11-35-16-5-13-18-51-45-43-27-17513,29-23-24,0",
        h2_settings={
            "HEADER_TABLE_SIZE": 65536,
            "MAX_CONCURRENT_STREAMS": 1000,
            "INITIAL_WINDOW_SIZE": 6291456,
            "MAX_HEADER_LIST_SIZE": 262144
        },
        h2_settings_order=[
            "HEADER_TABLE_SIZE",
            "MAX_CONCURRENT_STREAMS",
            "INITIAL_WINDOW_SIZE",
            "MAX_HEADER_LIST_SIZE"
        ],
        supported_signature_algorithms=[
            "ECDSAWithP256AndSHA256",
            "PSSWithSHA256",
            "PKCS1WithSHA256",
            "ECDSAWithP384AndSHA384",
            "PSSWithSHA384",
            "PKCS1WithSHA384",
            "PSSWithSHA512",
            "PKCS1WithSHA512",
        ],
        supported_versions=["GREASE", "1.3", "1.2"],
        key_share_curves=["GREASE", "X25519"],
        cert_compression_algo="brotli",
        pseudo_header_order=[":method", ":authority", ":scheme", ":path"],
        connection_flow=15663105,
        header_order=["accept", "user-agent", "accept-encoding", "accept-language"]
    )

    res = await session.post(
        "https://www.example.com/",
        headers={"key1": "value1"},
        proxy="http://user:password@host:port"
    )
    print(res.text)

    await session.close()

asyncio.run(main())
```

More examples in the [`examples/`](examples/) folder.

# Pyinstaller / Pyarmor
**If you want to pack the library with Pyinstaller or Pyarmor, add this to your command:**

Linux - Ubuntu / x86:
```
--add-binary '{path_to_library}/tls_client/dependencies/tls-client-x86.so:tls_client/dependencies'
```

Linux Alpine / AMD64:
```
--add-binary '{path_to_library}/tls_client/dependencies/tls-client-amd64.so:tls_client/dependencies'
```

MacOS M1 and older:
```
--add-binary '{path_to_library}/tls_client/dependencies/tls-client-x86.dylib:tls_client/dependencies'
```

MacOS M2:
```
--add-binary '{path_to_library}/tls_client/dependencies/tls-client-arm64.dylib:tls_client/dependencies'
```

Windows:
```
--add-binary '{path_to_library}/tls_client/dependencies/tls-client-64.dll;tls_client/dependencies'
```

### One final note
Package is named after [Admiral Atticus Noble in Rebel Moon: Part One - A Child of Fire](https://www.youtube.com/watch?v=cO-GPaASWV0)

### Acknowledgements
Big shout out to [Bogdanfinn](https://github.com/bogdanfinn) for open sourcing his [tls-client](https://github.com/bogdanfinn/tls-client) in Go,
and [FlorianREGAZ](https://github.com/FlorianREGAZ) for the original Python wrapper.
