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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xhamster_api
3
- Version: 2.1
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-Dist: httpx[http2] ; extra == 'full'
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
- > XHamster API is in violation to the ToS of xhamster.com!
39
- > If you are the website owner of xhamster.com, contact me at my E-Mail, and I'll take this repository immediately offline.
40
- > EchterAlsFake@proton.me
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
- # Fetch a video
54
- video_object = client.get_video("<insert_url_here>")
55
-
56
- # Information from Video objects
57
- print(video_object.title)
58
- print(video_object.likes)
59
- # Download the video
60
-
61
- video_object.download(downloader="threaded", quality="best", path="your_output_path + filename")
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–2025 Johannes Habel
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
- > XHamster API is in violation to the ToS of xhamster.com!
15
- > If you are the website owner of xhamster.com, contact me at my E-Mail, and I'll take this repository immediately offline.
16
- > EchterAlsFake@proton.me
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
- # Fetch a video
30
- video_object = client.get_video("<insert_url_here>")
31
-
32
- # Information from Video objects
33
- print(video_object.title)
34
- print(video_object.likes)
35
- # Download the video
36
-
37
- video_object.download(downloader="threaded", quality="best", path="your_output_path + filename")
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–2025 Johannes Habel
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.1"
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"
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", "httpx[http2]", "httpx[socks]"] # H2 for HTTP 2.0 support, LXML for faster parsing speed, though optional :)
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"
@@ -0,0 +1,4 @@
1
+ from typing import Callable, Awaitable
2
+
3
+ type callback_hint = Callable[[int, int], None] | None
4
+ type on_error_hint = Callable[[str, Exception, int], Awaitable[bool]] | None
@@ -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 callback_hint
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 callback_hint
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) -> AsyncGenerator[Video, 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) -> AsyncGenerator[Short, None]:
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,) -> AsyncGenerator[Video, 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()
@@ -1,3 +0,0 @@
1
- from typing import Callable
2
-
3
- type callback_hint = Callable[[int, int], None] | None
File without changes