never-primp 1.0.1__tar.gz → 1.0.2__tar.gz
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.
Potentially problematic release.
This version of never-primp might be problematic. Click here for more details.
- {never_primp-1.0.1 → never_primp-1.0.2}/.gitignore +1 -0
- {never_primp-1.0.1 → never_primp-1.0.2}/Cargo.lock +1 -1
- {never_primp-1.0.1 → never_primp-1.0.2}/Cargo.toml +1 -1
- {never_primp-1.0.1 → never_primp-1.0.2}/PKG-INFO +1 -1
- {never_primp-1.0.1 → never_primp-1.0.2}/never_primp/__init__.py +137 -1
- {never_primp-1.0.1 → never_primp-1.0.2}/never_primp/never_primp.pyi +50 -2
- {never_primp-1.0.1 → never_primp-1.0.2}/pyproject.toml +1 -1
- {never_primp-1.0.1 → never_primp-1.0.2}/src/lib.rs +124 -16
- never_primp-1.0.2/test.py +53 -0
- never_primp-1.0.1/test.py +0 -20
- {never_primp-1.0.1 → never_primp-1.0.2}/.claude/settings.local.json +0 -0
- {never_primp-1.0.1 → never_primp-1.0.2}/.github/workflows/build.yml +0 -0
- {never_primp-1.0.1 → never_primp-1.0.2}/LICENSE +0 -0
- {never_primp-1.0.1 → never_primp-1.0.2}/ORDERED_HEADERS.md +0 -0
- {never_primp-1.0.1 → never_primp-1.0.2}/README.md +0 -0
- {never_primp-1.0.1 → never_primp-1.0.2}/README_CN.md +0 -0
- {never_primp-1.0.1 → never_primp-1.0.2}/SPLIT_COOKIES.md +0 -0
- {never_primp-1.0.1 → never_primp-1.0.2}/benchmark/README.md +0 -0
- {never_primp-1.0.1 → never_primp-1.0.2}/benchmark/benchmark.py +0 -0
- {never_primp-1.0.1 → never_primp-1.0.2}/benchmark/generate_image.py +0 -0
- {never_primp-1.0.1 → never_primp-1.0.2}/benchmark/requirements.txt +0 -0
- {never_primp-1.0.1 → never_primp-1.0.2}/benchmark/server.py +0 -0
- {never_primp-1.0.1 → never_primp-1.0.2}/example_ordered_headers.py +0 -0
- {never_primp-1.0.1 → never_primp-1.0.2}/example_split_cookies.py +0 -0
- {never_primp-1.0.1 → never_primp-1.0.2}/never_primp/py.typed +0 -0
- {never_primp-1.0.1 → never_primp-1.0.2}/src/impersonate.rs +0 -0
- {never_primp-1.0.1 → never_primp-1.0.2}/src/response.rs +0 -0
- {never_primp-1.0.1 → never_primp-1.0.2}/src/traits.rs +0 -0
- {never_primp-1.0.1 → never_primp-1.0.2}/src/utils.rs +0 -0
- {never_primp-1.0.1 → never_primp-1.0.2}/test_features.py +0 -0
- {never_primp-1.0.1 → never_primp-1.0.2}/test_performance.py +0 -0
- {never_primp-1.0.1 → never_primp-1.0.2}/tests/test_asyncclient.py +0 -0
- {never_primp-1.0.1 → never_primp-1.0.2}/tests/test_client.py +0 -0
- {never_primp-1.0.1 → never_primp-1.0.2}/tests/test_defs.py +0 -0
- {never_primp-1.0.1 → never_primp-1.0.2}/tests/test_main.py +0 -0
- {never_primp-1.0.1 → never_primp-1.0.2}/tests/test_response.py +0 -0
|
@@ -3,7 +3,8 @@ from __future__ import annotations
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import sys
|
|
5
5
|
from functools import partial
|
|
6
|
-
from typing import TYPE_CHECKING, TypedDict
|
|
6
|
+
from typing import TYPE_CHECKING, TypedDict, Iterator
|
|
7
|
+
from collections.abc import MutableMapping
|
|
7
8
|
|
|
8
9
|
if sys.version_info <= (3, 11):
|
|
9
10
|
from typing_extensions import Unpack
|
|
@@ -26,6 +27,112 @@ else:
|
|
|
26
27
|
RequestParams = ClientRequestParams = TypedDict
|
|
27
28
|
|
|
28
29
|
|
|
30
|
+
class CookieJar(MutableMapping):
|
|
31
|
+
"""
|
|
32
|
+
A dict-like container for managing HTTP cookies, compatible with requests.Session.cookies API.
|
|
33
|
+
|
|
34
|
+
Examples:
|
|
35
|
+
client = Client()
|
|
36
|
+
|
|
37
|
+
# Set cookies
|
|
38
|
+
client.cookies['session_id'] = 'abc123'
|
|
39
|
+
client.cookies['user_token'] = 'xyz789'
|
|
40
|
+
|
|
41
|
+
# Get cookies
|
|
42
|
+
value = client.cookies['session_id']
|
|
43
|
+
value = client.cookies.get('user_token', 'default')
|
|
44
|
+
|
|
45
|
+
# Update multiple cookies
|
|
46
|
+
client.cookies.update({'key1': 'value1', 'key2': 'value2'})
|
|
47
|
+
|
|
48
|
+
# Delete cookies
|
|
49
|
+
del client.cookies['session_id']
|
|
50
|
+
|
|
51
|
+
# Clear all cookies
|
|
52
|
+
client.cookies.clear()
|
|
53
|
+
|
|
54
|
+
# Check existence
|
|
55
|
+
if 'session_id' in client.cookies:
|
|
56
|
+
print("Session exists")
|
|
57
|
+
|
|
58
|
+
# Convert to dict
|
|
59
|
+
all_cookies = dict(client.cookies)
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
def __init__(self, client: RClient):
|
|
63
|
+
"""Initialize CookieJar with a reference to the client."""
|
|
64
|
+
self._client = client
|
|
65
|
+
|
|
66
|
+
def __getitem__(self, name: str) -> str:
|
|
67
|
+
"""Get a cookie value by name."""
|
|
68
|
+
value = self._client.get_cookie(name)
|
|
69
|
+
if value is None:
|
|
70
|
+
raise KeyError(name)
|
|
71
|
+
return value
|
|
72
|
+
|
|
73
|
+
def __setitem__(self, name: str, value: str) -> None:
|
|
74
|
+
"""Set a cookie by name."""
|
|
75
|
+
self._client.set_cookie(name, value)
|
|
76
|
+
|
|
77
|
+
def __delitem__(self, name: str) -> None:
|
|
78
|
+
"""Delete a cookie by name."""
|
|
79
|
+
# Check if cookie exists first
|
|
80
|
+
if self._client.get_cookie(name) is None:
|
|
81
|
+
raise KeyError(name)
|
|
82
|
+
self._client.delete_cookie(name)
|
|
83
|
+
|
|
84
|
+
def __iter__(self) -> Iterator[str]:
|
|
85
|
+
"""Iterate over cookie names."""
|
|
86
|
+
return iter(self._client.get_all_cookies().keys())
|
|
87
|
+
|
|
88
|
+
def __len__(self) -> int:
|
|
89
|
+
"""Return the number of cookies."""
|
|
90
|
+
return len(self._client.get_all_cookies())
|
|
91
|
+
|
|
92
|
+
def __contains__(self, name: object) -> bool:
|
|
93
|
+
"""Check if a cookie exists."""
|
|
94
|
+
if not isinstance(name, str):
|
|
95
|
+
return False
|
|
96
|
+
return self._client.get_cookie(name) is not None
|
|
97
|
+
|
|
98
|
+
def __repr__(self) -> str:
|
|
99
|
+
"""Return string representation of cookies."""
|
|
100
|
+
cookies = self._client.get_all_cookies()
|
|
101
|
+
return f"CookieJar({cookies})"
|
|
102
|
+
|
|
103
|
+
def get(self, name: str, default: str | None = None) -> str | None:
|
|
104
|
+
"""Get a cookie value with a default fallback."""
|
|
105
|
+
value = self._client.get_cookie(name)
|
|
106
|
+
return value if value is not None else default
|
|
107
|
+
|
|
108
|
+
def update(self, cookies: dict[str, str], domain: str | None = None, path: str | None = None) -> None:
|
|
109
|
+
"""
|
|
110
|
+
Update multiple cookies at once.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
cookies: Dictionary of cookie names to values
|
|
114
|
+
domain: Optional domain for the cookies (e.g., ".example.com")
|
|
115
|
+
path: Optional path for the cookies (e.g., "/")
|
|
116
|
+
"""
|
|
117
|
+
self._client.update_cookies(cookies, domain=domain, path=path)
|
|
118
|
+
|
|
119
|
+
def clear(self) -> None:
|
|
120
|
+
"""Remove all cookies."""
|
|
121
|
+
self._client.clear_cookies()
|
|
122
|
+
|
|
123
|
+
def set(self, name: str, value: str, domain: str | None = None, path: str | None = None) -> None:
|
|
124
|
+
"""
|
|
125
|
+
Set a single cookie with optional domain and path.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
name: Cookie name
|
|
129
|
+
value: Cookie value
|
|
130
|
+
domain: Optional domain (e.g., ".example.com")
|
|
131
|
+
path: Optional path (e.g., "/")
|
|
132
|
+
"""
|
|
133
|
+
self._client.set_cookie(name, value, domain=domain, path=path)
|
|
134
|
+
|
|
135
|
+
|
|
29
136
|
class Client(RClient):
|
|
30
137
|
"""Initializes an HTTP client that can impersonate web browsers."""
|
|
31
138
|
|
|
@@ -103,6 +210,35 @@ class Client(RClient):
|
|
|
103
210
|
http2_only: if true - use only HTTP/2, if false - use only HTTP/1. Default is False.
|
|
104
211
|
"""
|
|
105
212
|
super().__init__()
|
|
213
|
+
self._cookies_jar: CookieJar | None = None
|
|
214
|
+
|
|
215
|
+
@property
|
|
216
|
+
def cookies(self) -> CookieJar:
|
|
217
|
+
"""
|
|
218
|
+
Access the cookie jar for dict-like cookie management.
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
CookieJar: A dict-like container for managing cookies.
|
|
222
|
+
|
|
223
|
+
Examples:
|
|
224
|
+
# Set cookies
|
|
225
|
+
client.cookies['session_id'] = 'abc123'
|
|
226
|
+
|
|
227
|
+
# Get cookies
|
|
228
|
+
value = client.cookies['session_id']
|
|
229
|
+
|
|
230
|
+
# Update cookies
|
|
231
|
+
client.cookies.update({'key': 'value'})
|
|
232
|
+
|
|
233
|
+
# Delete cookies
|
|
234
|
+
del client.cookies['session_id']
|
|
235
|
+
|
|
236
|
+
# Clear all
|
|
237
|
+
client.cookies.clear()
|
|
238
|
+
"""
|
|
239
|
+
if self._cookies_jar is None:
|
|
240
|
+
self._cookies_jar = CookieJar(self)
|
|
241
|
+
return self._cookies_jar
|
|
106
242
|
|
|
107
243
|
def __enter__(self) -> Client:
|
|
108
244
|
return self
|
|
@@ -38,6 +38,7 @@ class RequestParams(TypedDict, total=False):
|
|
|
38
38
|
auth_bearer: str | None
|
|
39
39
|
params: dict[str, str] | None
|
|
40
40
|
headers: dict[str, str] | None
|
|
41
|
+
ordered_headers: dict[str, str] | None
|
|
41
42
|
cookies: dict[str, str] | None
|
|
42
43
|
timeout: float | None
|
|
43
44
|
content: bytes | None
|
|
@@ -82,8 +83,10 @@ class RClient:
|
|
|
82
83
|
auth_bearer: str | None = None,
|
|
83
84
|
params: dict[str, str] | None = None,
|
|
84
85
|
headers: dict[str, str] | None = None,
|
|
86
|
+
ordered_headers: dict[str, str] | None = None,
|
|
85
87
|
timeout: float | None = None,
|
|
86
88
|
cookie_store: bool | None = True,
|
|
89
|
+
split_cookies: bool | None = False,
|
|
87
90
|
referer: bool | None = True,
|
|
88
91
|
proxy: str | None = None,
|
|
89
92
|
impersonate: IMPERSONATE | None = None,
|
|
@@ -94,14 +97,59 @@ class RClient:
|
|
|
94
97
|
ca_cert_file: str | None = None,
|
|
95
98
|
https_only: bool | None = False,
|
|
96
99
|
http2_only: bool | None = False,
|
|
100
|
+
pool_idle_timeout: float | None = None,
|
|
101
|
+
pool_max_idle_per_host: int | None = None,
|
|
102
|
+
tcp_nodelay: bool | None = None,
|
|
103
|
+
tcp_keepalive: float | None = None,
|
|
104
|
+
retry_count: int | None = None,
|
|
105
|
+
retry_backoff: float | None = None,
|
|
97
106
|
): ...
|
|
98
107
|
@property
|
|
99
108
|
def headers(self) -> dict[str, str]: ...
|
|
100
109
|
@headers.setter
|
|
101
110
|
def headers(self, headers: dict[str, str]) -> None: ...
|
|
102
111
|
def headers_update(self, headers: dict[str, str]) -> None: ...
|
|
103
|
-
|
|
104
|
-
def
|
|
112
|
+
@property
|
|
113
|
+
def ordered_headers(self) -> dict[str, str]: ...
|
|
114
|
+
@ordered_headers.setter
|
|
115
|
+
def ordered_headers(self, ordered_headers: dict[str, str]) -> None: ...
|
|
116
|
+
def ordered_headers_update(self, ordered_headers: dict[str, str]) -> None: ...
|
|
117
|
+
# Cookie management methods (no URL required)
|
|
118
|
+
def get_all_cookies(self) -> dict[str, str]: ...
|
|
119
|
+
def set_cookie(self, name: str, value: str, domain: str | None = None, path: str | None = None) -> None: ...
|
|
120
|
+
def get_cookie(self, name: str) -> str | None: ...
|
|
121
|
+
def update_cookies(self, cookies: dict[str, str], domain: str | None = None, path: str | None = None) -> None: ...
|
|
122
|
+
def delete_cookie(self, name: str) -> None: ...
|
|
123
|
+
def clear_cookies(self) -> None: ...
|
|
124
|
+
# Client properties
|
|
125
|
+
@property
|
|
126
|
+
def auth(self) -> tuple[str, str | None] | None: ...
|
|
127
|
+
@auth.setter
|
|
128
|
+
def auth(self, auth: tuple[str, str | None] | None) -> None: ...
|
|
129
|
+
@property
|
|
130
|
+
def auth_bearer(self) -> str | None: ...
|
|
131
|
+
@auth_bearer.setter
|
|
132
|
+
def auth_bearer(self, auth_bearer: str | None) -> None: ...
|
|
133
|
+
@property
|
|
134
|
+
def params(self) -> dict[str, str] | None: ...
|
|
135
|
+
@params.setter
|
|
136
|
+
def params(self, params: dict[str, str] | None) -> None: ...
|
|
137
|
+
@property
|
|
138
|
+
def timeout(self) -> float | None: ...
|
|
139
|
+
@timeout.setter
|
|
140
|
+
def timeout(self, timeout: float | None) -> None: ...
|
|
141
|
+
@property
|
|
142
|
+
def split_cookies(self) -> bool | None: ...
|
|
143
|
+
@split_cookies.setter
|
|
144
|
+
def split_cookies(self, split_cookies: bool | None) -> None: ...
|
|
145
|
+
@property
|
|
146
|
+
def retry_count(self) -> int | None: ...
|
|
147
|
+
@retry_count.setter
|
|
148
|
+
def retry_count(self, retry_count: int | None) -> None: ...
|
|
149
|
+
@property
|
|
150
|
+
def retry_backoff(self) -> float | None: ...
|
|
151
|
+
@retry_backoff.setter
|
|
152
|
+
def retry_backoff(self, retry_backoff: float | None) -> None: ...
|
|
105
153
|
@property
|
|
106
154
|
def proxy(self) -> str | None: ...
|
|
107
155
|
@proxy.setter
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#![allow(clippy::too_many_arguments)]
|
|
2
2
|
use std::sync::{Arc, LazyLock, Mutex};
|
|
3
|
+
use std::collections::HashSet;
|
|
3
4
|
use std::time::Duration;
|
|
4
5
|
|
|
5
6
|
use anyhow::Result;
|
|
@@ -48,6 +49,8 @@ pub struct RClient {
|
|
|
48
49
|
client: Arc<Mutex<wreq::Client>>,
|
|
49
50
|
// Cookie jar for manual cookie management
|
|
50
51
|
cookie_jar: Arc<wreq::cookie::Jar>,
|
|
52
|
+
// Track deleted cookies (workaround for wreq not filtering expired cookies in get_all())
|
|
53
|
+
deleted_cookies: Arc<Mutex<std::collections::HashSet<String>>>,
|
|
51
54
|
#[pyo3(get, set)]
|
|
52
55
|
auth: Option<(String, Option<String>)>,
|
|
53
56
|
#[pyo3(get, set)]
|
|
@@ -287,6 +290,7 @@ impl RClient {
|
|
|
287
290
|
Ok(RClient {
|
|
288
291
|
client,
|
|
289
292
|
cookie_jar,
|
|
293
|
+
deleted_cookies: Arc::new(Mutex::new(HashSet::new())),
|
|
290
294
|
auth,
|
|
291
295
|
auth_bearer,
|
|
292
296
|
params,
|
|
@@ -415,32 +419,136 @@ impl RClient {
|
|
|
415
419
|
Ok(())
|
|
416
420
|
}
|
|
417
421
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
fn
|
|
422
|
+
/// Get all cookies from the jar without requiring a URL.
|
|
423
|
+
/// Returns a dictionary of cookie names to values.
|
|
424
|
+
fn get_all_cookies(&self) -> Result<IndexMapSSR> {
|
|
421
425
|
let mut cookies = IndexMap::with_capacity_and_hasher(10, RandomState::default());
|
|
426
|
+
let deleted = self.deleted_cookies.lock().unwrap();
|
|
422
427
|
|
|
423
|
-
// Get all cookies from the jar
|
|
424
|
-
// Note: wreq's get_all() returns all cookies regardless of URL
|
|
425
|
-
// To get URL-specific cookies, you would need to filter manually or use get(name, uri) per cookie
|
|
426
428
|
for cookie in self.cookie_jar.get_all() {
|
|
427
|
-
|
|
429
|
+
let name = cookie.name();
|
|
430
|
+
// Filter out deleted cookies
|
|
431
|
+
if !deleted.contains(name) {
|
|
432
|
+
cookies.insert(name.to_string(), cookie.value().to_string());
|
|
433
|
+
}
|
|
428
434
|
}
|
|
429
|
-
|
|
430
435
|
Ok(cookies)
|
|
431
436
|
}
|
|
432
437
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
438
|
+
/// Set a single cookie without requiring a URL.
|
|
439
|
+
///
|
|
440
|
+
/// # Arguments
|
|
441
|
+
/// * `name` - Cookie name
|
|
442
|
+
/// * `value` - Cookie value
|
|
443
|
+
/// * `domain` - Optional domain (e.g., ".example.com"). If None, uses a wildcard domain.
|
|
444
|
+
/// * `path` - Optional path (e.g., "/"). If None, uses "/".
|
|
445
|
+
#[pyo3(signature = (name, value, domain=None, path=None))]
|
|
446
|
+
fn set_cookie(
|
|
447
|
+
&self,
|
|
448
|
+
name: String,
|
|
449
|
+
value: String,
|
|
450
|
+
domain: Option<String>,
|
|
451
|
+
path: Option<String>,
|
|
452
|
+
) -> Result<()> {
|
|
453
|
+
let domain = domain.unwrap_or_else(|| "0.0.0.0".to_string());
|
|
454
|
+
let path = path.unwrap_or_else(|| "/".to_string());
|
|
455
|
+
|
|
456
|
+
// Construct a URL from domain and path
|
|
457
|
+
let url = format!("http://{}{}", domain, path);
|
|
458
|
+
let uri: wreq::Uri = url.parse()?;
|
|
459
|
+
|
|
460
|
+
let cookie_str = format!("{}={}", name, value);
|
|
461
|
+
self.cookie_jar.add_cookie_str(&cookie_str, &uri);
|
|
462
|
+
|
|
463
|
+
// Remove from deleted list
|
|
464
|
+
self.deleted_cookies.lock().unwrap().remove(&name);
|
|
465
|
+
Ok(())
|
|
466
|
+
}
|
|
437
467
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
468
|
+
/// Get a single cookie value by name.
|
|
469
|
+
/// Returns None if the cookie doesn't exist.
|
|
470
|
+
#[pyo3(signature = (name))]
|
|
471
|
+
fn get_cookie(&self, name: String) -> Result<Option<String>> {
|
|
472
|
+
// Check if deleted
|
|
473
|
+
if self.deleted_cookies.lock().unwrap().contains(&name) {
|
|
474
|
+
return Ok(None);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
for cookie in self.cookie_jar.get_all() {
|
|
478
|
+
if cookie.name() == name {
|
|
479
|
+
return Ok(Some(cookie.value().to_string()));
|
|
442
480
|
}
|
|
443
481
|
}
|
|
482
|
+
Ok(None)
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/// Update multiple cookies at once without requiring a URL.
|
|
486
|
+
///
|
|
487
|
+
/// # Arguments
|
|
488
|
+
/// * `cookies` - Dictionary of cookie names to values
|
|
489
|
+
/// * `domain` - Optional domain. If None, uses a wildcard domain.
|
|
490
|
+
/// * `path` - Optional path. If None, uses "/".
|
|
491
|
+
#[pyo3(signature = (cookies, domain=None, path=None))]
|
|
492
|
+
fn update_cookies(
|
|
493
|
+
&self,
|
|
494
|
+
cookies: IndexMapSSR,
|
|
495
|
+
domain: Option<String>,
|
|
496
|
+
path: Option<String>,
|
|
497
|
+
) -> Result<()> {
|
|
498
|
+
let domain = domain.unwrap_or_else(|| "0.0.0.0".to_string());
|
|
499
|
+
let path = path.unwrap_or_else(|| "/".to_string());
|
|
500
|
+
|
|
501
|
+
let url = format!("http://{}{}", domain, path);
|
|
502
|
+
let uri: wreq::Uri = url.parse()?;
|
|
503
|
+
|
|
504
|
+
let mut deleted = self.deleted_cookies.lock().unwrap();
|
|
505
|
+
for (name, value) in cookies {
|
|
506
|
+
let cookie_str = format!("{}={}", name, value);
|
|
507
|
+
self.cookie_jar.add_cookie_str(&cookie_str, &uri);
|
|
508
|
+
// Remove from deleted list
|
|
509
|
+
deleted.remove(&name);
|
|
510
|
+
}
|
|
511
|
+
Ok(())
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/// Delete a single cookie by name.
|
|
515
|
+
/// Sets the cookie to an empty value with Max-Age=0 to delete it.
|
|
516
|
+
#[pyo3(signature = (name))]
|
|
517
|
+
fn delete_cookie(&self, name: String) -> Result<()> {
|
|
518
|
+
// To delete a cookie, set it with an expiration in the past
|
|
519
|
+
let url = "http://0.0.0.0/";
|
|
520
|
+
let uri: wreq::Uri = url.parse()?;
|
|
521
|
+
|
|
522
|
+
// Set cookie with Max-Age=0 to delete it
|
|
523
|
+
let cookie_str = format!("{}=; Max-Age=0", name);
|
|
524
|
+
self.cookie_jar.add_cookie_str(&cookie_str, &uri);
|
|
525
|
+
|
|
526
|
+
// Add to deleted list
|
|
527
|
+
self.deleted_cookies.lock().unwrap().insert(name);
|
|
528
|
+
Ok(())
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/// Clear all cookies from the jar.
|
|
532
|
+
/// Sets all cookies with Max-Age=0 to mark them as expired.
|
|
533
|
+
fn clear_cookies(&self) -> Result<()> {
|
|
534
|
+
// Get all cookie names first to avoid borrow issues
|
|
535
|
+
let cookie_names: Vec<String> = self.cookie_jar
|
|
536
|
+
.get_all()
|
|
537
|
+
.map(|c| c.name().to_string())
|
|
538
|
+
.collect();
|
|
539
|
+
|
|
540
|
+
// Set each cookie with Expires in the past to mark as deleted
|
|
541
|
+
let url = "http://0.0.0.0/";
|
|
542
|
+
let uri: wreq::Uri = url.parse()?;
|
|
543
|
+
|
|
544
|
+
let mut deleted = self.deleted_cookies.lock().unwrap();
|
|
545
|
+
for name in cookie_names {
|
|
546
|
+
// Use Expires with a date in the past (Unix epoch)
|
|
547
|
+
let cookie_str = format!("{}=; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Max-Age=0", name);
|
|
548
|
+
self.cookie_jar.add_cookie_str(&cookie_str, &uri);
|
|
549
|
+
// Add to deleted list
|
|
550
|
+
deleted.insert(name);
|
|
551
|
+
}
|
|
444
552
|
Ok(())
|
|
445
553
|
}
|
|
446
554
|
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from never_primp import Client
|
|
2
|
+
|
|
3
|
+
headers = {
|
|
4
|
+
"accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
|
|
5
|
+
"accept-language": "en,zh-CN;q=0.9,zh;q=0.8,zh-HK;q=0.7",
|
|
6
|
+
"cache-control": "no-cache",
|
|
7
|
+
"pragma": "no-cache",
|
|
8
|
+
"priority": "u=0, i",
|
|
9
|
+
"referer": "https://hisky.aero/",
|
|
10
|
+
"sec-ch-ua": "\"Google Chrome\";v=\"141\", \"Not?A_Brand\";v=\"8\", \"Chromium\";v=\"141\"",
|
|
11
|
+
"sec-ch-ua-mobile": "?0",
|
|
12
|
+
"sec-ch-ua-platform": "\"Windows\"",
|
|
13
|
+
"sec-fetch-dest": "document",
|
|
14
|
+
"sec-fetch-mode": "navigate",
|
|
15
|
+
"sec-fetch-site": "same-site",
|
|
16
|
+
"sec-fetch-user": "?1",
|
|
17
|
+
"upgrade-insecure-requests": "1",
|
|
18
|
+
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36"
|
|
19
|
+
}
|
|
20
|
+
cookies = {
|
|
21
|
+
"_gcl_au": "1.1.223070972.1761814157",
|
|
22
|
+
"cf_clearance": "sbUF9oZS4pm8XVVNE.dnxxtZA.ZaK_IUE1lAmJTQfME-1761814156-1.2.1.1-0j8chJiO99fUkkj9ScKLe3_NzwwYuNoB_8Y9nZf5GTAa0Fn0l8kcDxngl1aISqK7ZDiJ.YN1cfipphzCIn46LkoBNjVryWuORy4m_tEhJQDlaptDTEoVnYoe4MmrLRcTIFia4sDvJ5CdSiTqewQsawG51.w_thq8MRZ9Z2Obf7SoV6MNC2eyRbCm96orSApfuK2tURiL_0K9EeNfES92Ss8kDoKZXBane0dwO_5vdMY",
|
|
23
|
+
"_gid": "GA1.2.339976247.1761814157",
|
|
24
|
+
"_gat_UA-155319482-1": "1",
|
|
25
|
+
"_fbp": "fb.1.1761814157993.834210106761768202",
|
|
26
|
+
"ASP.NET_SessionId": "0eme0emlicujjzldxxilfrrh",
|
|
27
|
+
"_ga": "GA1.2.1350469137.1761814157",
|
|
28
|
+
"_ga_0KDTF1TCBJ": "GS2.1.s1761814157$o1$g1$t1761814185$j32$l0$h0"
|
|
29
|
+
}
|
|
30
|
+
url = "https://booking.hisky.aero/VARS/Public/deeplink.aspx"
|
|
31
|
+
params = {
|
|
32
|
+
"TripType": "roundtrip",
|
|
33
|
+
"UserLanguage": "ro",
|
|
34
|
+
"Adult": "1",
|
|
35
|
+
"Child": "0",
|
|
36
|
+
"InfantLap": "0",
|
|
37
|
+
"UserCurrency": "USD",
|
|
38
|
+
"Origin1": "OTP",
|
|
39
|
+
"Destination2": "OTP",
|
|
40
|
+
"Destination1": "TSR",
|
|
41
|
+
"Origin2": "TSR",
|
|
42
|
+
"DepartureDate1": "31.10.2025",
|
|
43
|
+
"DepartureDate2": "31.10.2025",
|
|
44
|
+
"flightvoucher": ""
|
|
45
|
+
}
|
|
46
|
+
session = Client(proxy='http://127.0.0.1:7890')
|
|
47
|
+
|
|
48
|
+
response = session.get(url, headers=headers, cookies=cookies, params=params)
|
|
49
|
+
|
|
50
|
+
print(response.url)
|
|
51
|
+
print(response.headers)
|
|
52
|
+
print(response.cookies)
|
|
53
|
+
print(session.get_all_cookies())
|
never_primp-1.0.1/test.py
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import never_primp as primp
|
|
2
|
-
|
|
3
|
-
# Automatic cookie management (default)
|
|
4
|
-
url = 'https://ip.smartdaili-china.com/json'
|
|
5
|
-
client = primp.Client(cookie_store=True,
|
|
6
|
-
# proxy="http://127.0.0.1:7890"
|
|
7
|
-
)
|
|
8
|
-
client.proxy = 'http://127.0.0.1:7890'
|
|
9
|
-
response = client.get(url)
|
|
10
|
-
print(response.text)
|
|
11
|
-
# Cookies automatically stored and sent in next request
|
|
12
|
-
|
|
13
|
-
# Manual cookie management
|
|
14
|
-
cookies = client.get_cookies(url) # Get all cookies
|
|
15
|
-
print(cookies) # {'session': 'abc123'}
|
|
16
|
-
|
|
17
|
-
# Set cookies manually
|
|
18
|
-
client.set_cookies(url, {"auth": "token123"})
|
|
19
|
-
cookies = client.get_cookies(url) # Get all cookies
|
|
20
|
-
print(cookies) # {'session': 'abc123'}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|