xhamster_api 2.1__tar.gz → 2.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.
- {xhamster_api-2.1 → xhamster_api-2.2}/PKG-INFO +54 -19
- {xhamster_api-2.1 → xhamster_api-2.2}/README.md +52 -15
- {xhamster_api-2.1 → xhamster_api-2.2}/pyproject.toml +3 -3
- xhamster_api-2.2/xhamster_api/modules/type_hints.py +4 -0
- {xhamster_api-2.1 → xhamster_api-2.2}/xhamster_api/xhamster_api.py +32 -12
- xhamster_api-2.1/xhamster_api/modules/type_hints.py +0 -3
- {xhamster_api-2.1 → xhamster_api-2.2}/LICENSE +0 -0
- {xhamster_api-2.1 → xhamster_api-2.2}/xhamster_api/__init__.py +0 -0
- {xhamster_api-2.1 → xhamster_api-2.2}/xhamster_api/modules/__init__.py +0 -0
- {xhamster_api-2.1 → xhamster_api-2.2}/xhamster_api/modules/consts.py +0 -0
- {xhamster_api-2.1 → xhamster_api-2.2}/xhamster_api/modules/errors.py +0 -0
- {xhamster_api-2.1 → xhamster_api-2.2}/xhamster_api/tests/__init__.py +0 -0
- {xhamster_api-2.1 → xhamster_api-2.2}/xhamster_api/tests/test_all.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: xhamster_api
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2
|
|
4
4
|
Summary: A Python API for the Porn Site xhamster.com
|
|
5
5
|
Author: Johannes Habel
|
|
6
6
|
Author-email: Johannes Habel <EchterAlsFake@proton.me>
|
|
@@ -14,9 +14,7 @@ Requires-Dist: eaf-base-api
|
|
|
14
14
|
Requires-Dist: m3u8
|
|
15
15
|
Requires-Dist: av ; python_full_version >= '3.10' and extra == 'av'
|
|
16
16
|
Requires-Dist: lxml ; extra == 'full'
|
|
17
|
-
Requires-
|
|
18
|
-
Requires-Dist: httpx[socks] ; extra == 'full'
|
|
19
|
-
Requires-Python: >=3.10
|
|
17
|
+
Requires-Python: >=3.12
|
|
20
18
|
Project-URL: Homepage, https://github.com/EchterAlsFake/xhamster_api
|
|
21
19
|
Provides-Extra: av
|
|
22
20
|
Provides-Extra: full
|
|
@@ -34,10 +32,45 @@ Description-Content-Type: text/markdown
|
|
|
34
32
|
XHamster API is an API for xhamster.com. It allows you to fetch information from videos using regexes and requests.
|
|
35
33
|
|
|
36
34
|
# Disclaimer
|
|
37
|
-
> [!IMPORTANT]
|
|
38
|
-
>
|
|
39
|
-
>
|
|
40
|
-
>
|
|
35
|
+
> [!IMPORTANT]
|
|
36
|
+
> This is an unofficial and unaffiliated project. Please read the full disclaimer before use:
|
|
37
|
+
> **[DISCLAIMER.md](https://github.com/EchterAlsFake/API_Docs/blob/master/Disclaimer.md)**
|
|
38
|
+
>
|
|
39
|
+
> By using this project you agree to comply with the target site’s rules, copyright/licensing requirements,
|
|
40
|
+
> and applicable laws. Do not use it to bypass access controls or scrape at disruptive rates.
|
|
41
|
+
|
|
42
|
+
# Features
|
|
43
|
+
- Fetch videos + metadata
|
|
44
|
+
- Download videos
|
|
45
|
+
- Fetch Channels
|
|
46
|
+
- Fetch Pornstars
|
|
47
|
+
- Fetch Creators
|
|
48
|
+
- Fetch Shorts
|
|
49
|
+
- Search for videos
|
|
50
|
+
- Fetch playlists
|
|
51
|
+
- Asynchronous
|
|
52
|
+
- Built-in caching
|
|
53
|
+
- Easy interface
|
|
54
|
+
- Great type hinting
|
|
55
|
+
|
|
56
|
+
#### Networking Features
|
|
57
|
+
- HTTP 2.0 / HTTP 3.0
|
|
58
|
+
- Browser impersonation
|
|
59
|
+
- Custom JA3
|
|
60
|
+
- All proxy types
|
|
61
|
+
- Proxy authentication
|
|
62
|
+
- Speed Limit
|
|
63
|
+
- DNS over HTTPS
|
|
64
|
+
- And even more...
|
|
65
|
+
- All of this is configurable and can be adjusted as you like!
|
|
66
|
+
|
|
67
|
+
# Supported Platforms
|
|
68
|
+
This API has been tested and confirmed working on:
|
|
69
|
+
|
|
70
|
+
- Windows 11 (x64)
|
|
71
|
+
- macOS Sequoia (x86_64)
|
|
72
|
+
- Linux (Arch) (x86_64)
|
|
73
|
+
- Android 16 (aarch64)
|
|
41
74
|
|
|
42
75
|
# Quickstart
|
|
43
76
|
|
|
@@ -48,17 +81,19 @@ XHamster API is an API for xhamster.com. It allows you to fetch information from
|
|
|
48
81
|
```python
|
|
49
82
|
from xhamster_api import Client
|
|
50
83
|
# Initialize a Client object
|
|
51
|
-
client = Client()
|
|
52
84
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
#
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
#
|
|
60
|
-
|
|
61
|
-
video_object.
|
|
85
|
+
async def main():
|
|
86
|
+
client = Client()
|
|
87
|
+
|
|
88
|
+
# Fetch a video
|
|
89
|
+
video_object = await client.get_video("<insert_url_here>")
|
|
90
|
+
|
|
91
|
+
# Information from Video objects
|
|
92
|
+
print(video_object.title)
|
|
93
|
+
print(video_object.likes)
|
|
94
|
+
# Download the video
|
|
95
|
+
|
|
96
|
+
await video_object.download(downloader="threaded", quality="best", path="your_output_path + filename")
|
|
62
97
|
|
|
63
98
|
# SEE DOCUMENTATION FOR MORE
|
|
64
99
|
```
|
|
@@ -82,4 +117,4 @@ Pull requests are also welcome.
|
|
|
82
117
|
|
|
83
118
|
# License
|
|
84
119
|
Licensed under the LGPLv3 License
|
|
85
|
-
<br>Copyright (C) 2023–
|
|
120
|
+
<br>Copyright (C) 2023–2026 Johannes Habel
|
|
@@ -10,10 +10,45 @@
|
|
|
10
10
|
XHamster API is an API for xhamster.com. It allows you to fetch information from videos using regexes and requests.
|
|
11
11
|
|
|
12
12
|
# Disclaimer
|
|
13
|
-
> [!IMPORTANT]
|
|
14
|
-
>
|
|
15
|
-
>
|
|
16
|
-
>
|
|
13
|
+
> [!IMPORTANT]
|
|
14
|
+
> This is an unofficial and unaffiliated project. Please read the full disclaimer before use:
|
|
15
|
+
> **[DISCLAIMER.md](https://github.com/EchterAlsFake/API_Docs/blob/master/Disclaimer.md)**
|
|
16
|
+
>
|
|
17
|
+
> By using this project you agree to comply with the target site’s rules, copyright/licensing requirements,
|
|
18
|
+
> and applicable laws. Do not use it to bypass access controls or scrape at disruptive rates.
|
|
19
|
+
|
|
20
|
+
# Features
|
|
21
|
+
- Fetch videos + metadata
|
|
22
|
+
- Download videos
|
|
23
|
+
- Fetch Channels
|
|
24
|
+
- Fetch Pornstars
|
|
25
|
+
- Fetch Creators
|
|
26
|
+
- Fetch Shorts
|
|
27
|
+
- Search for videos
|
|
28
|
+
- Fetch playlists
|
|
29
|
+
- Asynchronous
|
|
30
|
+
- Built-in caching
|
|
31
|
+
- Easy interface
|
|
32
|
+
- Great type hinting
|
|
33
|
+
|
|
34
|
+
#### Networking Features
|
|
35
|
+
- HTTP 2.0 / HTTP 3.0
|
|
36
|
+
- Browser impersonation
|
|
37
|
+
- Custom JA3
|
|
38
|
+
- All proxy types
|
|
39
|
+
- Proxy authentication
|
|
40
|
+
- Speed Limit
|
|
41
|
+
- DNS over HTTPS
|
|
42
|
+
- And even more...
|
|
43
|
+
- All of this is configurable and can be adjusted as you like!
|
|
44
|
+
|
|
45
|
+
# Supported Platforms
|
|
46
|
+
This API has been tested and confirmed working on:
|
|
47
|
+
|
|
48
|
+
- Windows 11 (x64)
|
|
49
|
+
- macOS Sequoia (x86_64)
|
|
50
|
+
- Linux (Arch) (x86_64)
|
|
51
|
+
- Android 16 (aarch64)
|
|
17
52
|
|
|
18
53
|
# Quickstart
|
|
19
54
|
|
|
@@ -24,17 +59,19 @@ XHamster API is an API for xhamster.com. It allows you to fetch information from
|
|
|
24
59
|
```python
|
|
25
60
|
from xhamster_api import Client
|
|
26
61
|
# Initialize a Client object
|
|
27
|
-
client = Client()
|
|
28
62
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
#
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
#
|
|
36
|
-
|
|
37
|
-
video_object.
|
|
63
|
+
async def main():
|
|
64
|
+
client = Client()
|
|
65
|
+
|
|
66
|
+
# Fetch a video
|
|
67
|
+
video_object = await client.get_video("<insert_url_here>")
|
|
68
|
+
|
|
69
|
+
# Information from Video objects
|
|
70
|
+
print(video_object.title)
|
|
71
|
+
print(video_object.likes)
|
|
72
|
+
# Download the video
|
|
73
|
+
|
|
74
|
+
await video_object.download(downloader="threaded", quality="best", path="your_output_path + filename")
|
|
38
75
|
|
|
39
76
|
# SEE DOCUMENTATION FOR MORE
|
|
40
77
|
```
|
|
@@ -58,4 +95,4 @@ Pull requests are also welcome.
|
|
|
58
95
|
|
|
59
96
|
# License
|
|
60
97
|
Licensed under the LGPLv3 License
|
|
61
|
-
<br>Copyright (C) 2023–
|
|
98
|
+
<br>Copyright (C) 2023–2026 Johannes Habel
|
|
@@ -4,10 +4,10 @@ build-backend = "uv_build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "xhamster_api"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.2"
|
|
8
8
|
description = "A Python API for the Porn Site xhamster.com"
|
|
9
9
|
readme = { file = "README.md", content-type = "text/markdown" }
|
|
10
|
-
requires-python = ">=3.
|
|
10
|
+
requires-python = ">=3.12"
|
|
11
11
|
license = "LGPL-3.0-only"
|
|
12
12
|
license-files = ["LICENSE"]
|
|
13
13
|
authors = [
|
|
@@ -28,7 +28,7 @@ classifiers = [
|
|
|
28
28
|
|
|
29
29
|
[project.optional-dependencies]
|
|
30
30
|
av = ["av; python_version >= '3.10'"]
|
|
31
|
-
full = ["lxml"
|
|
31
|
+
full = ["lxml"] # H2 for HTTP 2.0 support, LXML for faster parsing speed, though optional :)
|
|
32
32
|
|
|
33
33
|
[project.urls]
|
|
34
34
|
Homepage = "https://github.com/EchterAlsFake/xhamster_api"
|
|
@@ -7,21 +7,21 @@ import threading
|
|
|
7
7
|
|
|
8
8
|
from functools import cached_property
|
|
9
9
|
from urllib.parse import urlencode, quote
|
|
10
|
-
from base_api.modules.config import RuntimeConfig
|
|
11
|
-
from base_api.modules.errors import NetworkingError, BotProtectionDetected, UnknownError, InvalidProxy
|
|
12
|
-
from typing import Literal, AsyncGenerator, Any, Dict, List
|
|
13
|
-
from base_api.base import BaseCore, setup_logger, Helper
|
|
14
10
|
from curl_cffi import AsyncSession, Response
|
|
11
|
+
from base_api.modules.config import RuntimeConfig
|
|
12
|
+
from typing import Literal, AsyncGenerator, Any, Dict
|
|
15
13
|
from base_api.modules.type_hints import DownloadReport
|
|
14
|
+
from base_api.base import BaseCore, setup_logger, Helper
|
|
15
|
+
from base_api.modules.errors import NetworkingError, BotProtectionDetected, UnknownError, InvalidProxy, ResourceGone
|
|
16
16
|
|
|
17
17
|
try:
|
|
18
18
|
from modules.consts import *
|
|
19
19
|
from modules.errors import *
|
|
20
|
-
from modules.type_hints import
|
|
20
|
+
from modules.type_hints import *
|
|
21
21
|
except (ModuleNotFoundError, ImportError):
|
|
22
22
|
from .modules.consts import *
|
|
23
23
|
from .modules.errors import *
|
|
24
|
-
from .modules.type_hints import
|
|
24
|
+
from .modules.type_hints import *
|
|
25
25
|
|
|
26
26
|
try:
|
|
27
27
|
import lxml
|
|
@@ -30,6 +30,14 @@ except (ModuleNotFoundError, ImportError):
|
|
|
30
30
|
parser = "html.parser"
|
|
31
31
|
|
|
32
32
|
|
|
33
|
+
async def on_error(url: str, error: Exception, attempt: int) -> bool:
|
|
34
|
+
print(f"URL: {url}, ERROR: {error}, Attempt: {attempt}")
|
|
35
|
+
|
|
36
|
+
if isinstance(error, ResourceGone):
|
|
37
|
+
return False
|
|
38
|
+
|
|
39
|
+
return True
|
|
40
|
+
|
|
33
41
|
async def get_html_content(core: BaseCore, url: str) -> str | None | dict:
|
|
34
42
|
# What should I do here?
|
|
35
43
|
try:
|
|
@@ -116,14 +124,18 @@ class Something(Helper):
|
|
|
116
124
|
def avatar_url(self) -> str:
|
|
117
125
|
return REGEX_AVATAR.search(self.html_content).group(1)
|
|
118
126
|
|
|
119
|
-
async def videos(self, pages: int = 2, videos_concurrency: int | None = None, pages_concurrency: int | None = None
|
|
127
|
+
async def videos(self, pages: int = 2, videos_concurrency: int | None = None, pages_concurrency: int | None = None,
|
|
128
|
+
on_video_error: on_error_hint = on_error,
|
|
129
|
+
on_page_error: on_error_hint = None
|
|
130
|
+
) -> AsyncGenerator[Video, None]:
|
|
120
131
|
page_urls = [build_page_url(url=self.url, is_search=False, idx=page) for page in range(1, pages + 1)]
|
|
121
132
|
videos_concurrency = videos_concurrency or self.core.configuration.videos_concurrency
|
|
122
133
|
pages_concurrency = pages_concurrency or self.core.configuration.pages_concurrency
|
|
123
134
|
assert videos_concurrency and pages_concurrency
|
|
124
135
|
|
|
125
136
|
async for video in self.iterator(use_alternative_constructor=True, video_link_extractor=extractor_shorts, target_page_urls=page_urls,
|
|
126
|
-
max_video_concurrency=videos_concurrency, max_page_concurrency=pages_concurrency
|
|
137
|
+
max_video_concurrency=videos_concurrency, max_page_concurrency=pages_concurrency,
|
|
138
|
+
on_video_error=on_video_error, on_page_error=on_page_error):
|
|
127
139
|
yield await video.init()
|
|
128
140
|
|
|
129
141
|
@cached_property
|
|
@@ -148,14 +160,18 @@ class Something(Helper):
|
|
|
148
160
|
|
|
149
161
|
return dictionary
|
|
150
162
|
|
|
151
|
-
async def get_shorts(self, pages: int = 2, videos_concurrency: int = 2, pages_concurrency: int = 1
|
|
163
|
+
async def get_shorts(self, pages: int = 2, videos_concurrency: int = 2, pages_concurrency: int = 1,
|
|
164
|
+
on_video_error: on_error_hint = on_error,
|
|
165
|
+
on_page_error: on_error_hint = None
|
|
166
|
+
) -> AsyncGenerator[Short, None]:
|
|
152
167
|
if not self.url.endswith("/"):
|
|
153
168
|
self.url += "/"
|
|
154
169
|
|
|
155
170
|
self.url += "shorts"
|
|
156
171
|
page_urls = [build_page_url(self.url, is_search=False, idx=page) for page in range(1, pages + 1)]
|
|
157
172
|
async for short in self.iterator(use_alternative_constructor=True, video_link_extractor=extractor_shorts, target_page_urls=page_urls,
|
|
158
|
-
max_video_concurrency=videos_concurrency, max_page_concurrency=pages_concurrency
|
|
173
|
+
max_video_concurrency=videos_concurrency, max_page_concurrency=pages_concurrency,
|
|
174
|
+
on_video_error=on_video_error, on_page_error=on_page_error):
|
|
159
175
|
yield await short.init()
|
|
160
176
|
|
|
161
177
|
class Channel(Something):
|
|
@@ -424,7 +440,10 @@ class Client(Helper):
|
|
|
424
440
|
date: Literal["latest", "weekly", "monthly", "yearly"] | None = None,
|
|
425
441
|
production: Literal["studios", "creators"] | None = None,
|
|
426
442
|
fps: Literal["30", "60"] | None = None,
|
|
427
|
-
pages: int = 2, videos_concurrency: int | None = None, pages_concurrency: int | None = None,
|
|
443
|
+
pages: int = 2, videos_concurrency: int | None = None, pages_concurrency: int | None = None,
|
|
444
|
+
on_video_error: on_error_hint = on_error,
|
|
445
|
+
on_page_error: on_error_hint = None
|
|
446
|
+
) -> AsyncGenerator[Video, None]:
|
|
428
447
|
path = quote(str(query), safe="") # e.g. "4k cats & dogs" -> "4k%20cats%20%26%20dogs"
|
|
429
448
|
base = f"https://xhamster.com/search/"
|
|
430
449
|
url = base + path
|
|
@@ -468,5 +487,6 @@ class Client(Helper):
|
|
|
468
487
|
assert isinstance(pages_concurrency, int)
|
|
469
488
|
|
|
470
489
|
async for video in self.iterator(use_alternative_constructor=True, video_link_extractor=extractor_shorts, target_page_urls=page_urls,
|
|
471
|
-
max_video_concurrency=videos_concurrency, max_page_concurrency=pages_concurrency
|
|
490
|
+
max_video_concurrency=videos_concurrency, max_page_concurrency=pages_concurrency,
|
|
491
|
+
on_video_error=on_video_error, on_page_error=on_page_error):
|
|
472
492
|
yield await video.init()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|