eaf-base-api 2.0__tar.gz → 2.1__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.1
2
2
  Name: eaf_base_api
3
- Version: 2.0
3
+ Version: 2.1
4
4
  Summary: A base API for EchterAlsFake's Porn APIs
5
5
  Home-page: https://github.com/EchterAlsFake/eaf_base_api
6
6
  Author: Johannes Habel
@@ -10,9 +10,9 @@ Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 (LGP
10
10
  Classifier: Programming Language :: Python
11
11
  Description-Content-Type: text/markdown
12
12
  License-File: LICENSE
13
- Requires-Dist: requests
14
13
  Requires-Dist: ffmpeg-progress-yield
15
14
  Requires-Dist: m3u8
15
+ Requires-Dist: httpx
16
16
 
17
17
  # EAF Base API
18
18
 
@@ -1,10 +1,10 @@
1
1
  import os
2
- import m3u8
3
2
  import time
3
+ import m3u8
4
+ import httpx
4
5
  import random
5
6
  import string
6
7
  import logging
7
- import requests
8
8
  import traceback
9
9
  import threading
10
10
 
@@ -67,13 +67,26 @@ class BaseCore:
67
67
  def __init__(self):
68
68
  self.last_request_time = time.time()
69
69
  self.total_requests = 0 # Tracks how many requests have been made
70
- self.session = requests.Session()
71
- self.session.headers.update(consts.HEADERS)
70
+ self.session = None
71
+ self.initialize_session()
72
+
72
73
 
73
74
  def update_user_agent(self):
74
75
  """Updates the User-Agent"""
75
76
  self.session.headers.update({"User-Agent": random.choice(consts.USER_AGENTS)})
76
77
 
78
+ def initialize_session(self):
79
+ verify = True
80
+
81
+ if consts.PROXY is not None:
82
+ verify = False
83
+
84
+ self.session = httpx.Client(proxy=consts.PROXY,
85
+ headers=consts.HEADERS,
86
+ verify=verify,
87
+ timeout=consts.TIMEOUT
88
+ )
89
+
77
90
  def enforce_delay(self):
78
91
  """Enforces the specified delay in consts.REQUEST_DELAY"""
79
92
  delay = consts.REQUEST_DELAY
@@ -85,67 +98,77 @@ class BaseCore:
85
98
  logger.debug(f"Enforcing delay of {sleep_time:.2f} seconds.")
86
99
  time.sleep(sleep_time)
87
100
 
88
- def fetch(self, url: str, get_bytes: bool = False, stream: bool = False, timeout: int = consts.TIMEOUT,
89
- get_response: bool = False, save_cache: bool = True, cookies: dict = None,
90
- allow_redirects: bool = True) -> Union[bytes, str, requests.Response]:
101
+ def fetch(
102
+ self,
103
+ url: str,
104
+ get_bytes: bool = False,
105
+ timeout: int = consts.TIMEOUT,
106
+ get_response: bool = False,
107
+ save_cache: bool = True,
108
+ cookies: dict = None,
109
+ allow_redirects: bool = True,
110
+ ) -> Union[bytes, str, httpx.Response, None]:
91
111
  """
92
- Fetches content in UTF-8 Text, Bytes or as a stream using multiple request attempts, support for proxies
93
- and custom timeout.
112
+ Fetches content in UTF-8 Text, Bytes, or as a stream using multiple request attempts,
113
+ support for proxies and custom timeout.
94
114
  """
115
+ # Check cache first
95
116
  content = cache.handle_cache(url)
96
117
  if content is not None:
97
118
  logger.info(f"Fetched content for: {url} from cache!")
98
119
  return content
99
120
 
100
- for attempt in range(1, consts.MAX_RETRIES):
101
- if self.total_requests % 3 == 0: # Change user agent after 3 requests to prevent bot detection
102
- self.update_user_agent()
103
-
121
+ for attempt in range(1, consts.MAX_RETRIES + 1):
104
122
  try:
105
- self.enforce_delay()
106
- if consts.USE_PROXIES:
107
- verify = False # Disable SSL verification
108
- url = url.replace("https://", "http://") # Replace https to http to support HTTP proxies
123
+ # Update user agent periodically
124
+ if self.total_requests % 3 == 0:
125
+ self.update_user_agent()
109
126
 
110
- else:
111
- verify = True
127
+ self.enforce_delay()
112
128
 
113
- response = self.session.get(url, stream=stream, proxies=consts.PROXIES, verify=verify,
114
- timeout=timeout, allow_redirects=allow_redirects, cookies=cookies)
129
+ # Perform the request with stream handling
130
+ with self.session.stream("GET", url, timeout=timeout, cookies=cookies,
131
+ follow_redirects=allow_redirects) as response:
132
+ self.total_requests += 1
115
133
 
116
- if response.status_code != 200:
117
- logger.error(f"Unexpected status code {response.status_code} for URL: {url}")
134
+ # Log and handle non-200 status codes
135
+ if response.status_code != 200:
136
+ logger.warning(
137
+ f"Attempt {attempt}: Unexpected status code {response.status_code} for URL: {url}")
118
138
 
119
- if response.status_code == 404:
120
- logger.error("Website returned 404. Not an issue for xivdeos, but for all other sites")
121
- return response # Needed for xvideos. Means that video is unavailable lol
139
+ if response.status_code == 404:
140
+ logger.error("Resource not found (404). This may indicate the content is unavailable.")
141
+ return None # Return None for unavailable resources
122
142
 
123
- continue
143
+ continue # Retry for other non-200 status codes
124
144
 
125
- if get_response:
126
- return response
145
+ logger.debug(f"Attempt {attempt}: Successfully fetched URL: {url}")
127
146
 
128
- self.total_requests += 1
147
+ # Return response if requested
148
+ if get_response:
149
+ return response
129
150
 
130
- except Exception:
131
- error = traceback.format_exc()
132
- logger.error(f"Could not fetch: {url} ->: {error}")
133
- continue # Next attempt
151
+ # Process and return content
152
+ if get_bytes:
153
+ content = b"".join([chunk for chunk in response.iter_bytes()])
154
+ else:
155
+ content = "".join([chunk.decode("utf-8") for chunk in response.iter_bytes()])
156
+ if save_cache:
157
+ logger.debug(f"Saving content of {url} to local cache.")
158
+ cache.save_cache(url, content)
134
159
 
135
- logger.debug(f"[{attempt}|{consts.MAX_RETRIES}] Fetch ->: {url}")
160
+ return content
136
161
 
137
- logger.debug(f"[{attempt}|{consts.MAX_RETRIES}] Fetch ->: 200 | Success")
138
- if get_bytes is False:
139
- content = response.content.decode("utf-8")
140
- if save_cache:
141
- logger.debug(f"Trying to save content of: {url} in local cache...")
142
- cache.save_cache(url, content)
162
+ except httpx.RequestError as e:
163
+ logger.error(f"Attempt {attempt}: Request error for URL {url}: {e}")
143
164
 
144
- return content
165
+ except Exception:
166
+ logger.error(f"Attempt {attempt}: Unexpected error for URL {url}: {traceback.format_exc()}")
145
167
 
146
- return response.content
168
+ logger.info(f"Retrying ({attempt}/{consts.MAX_RETRIES}) for URL: {url}")
147
169
 
148
- raise ConnectionError(f"Could not fetch content for: {url}")
170
+ logger.error(f"Failed to fetch URL {url} after {consts.MAX_RETRIES} attempts.")
171
+ return None # Return None if all attempts fail
149
172
 
150
173
  @classmethod
151
174
  def strip_title(cls, title: str) -> str:
@@ -243,7 +266,8 @@ class BaseCore:
243
266
 
244
267
  response = BaseCore().fetch(url, timeout=timeout, get_response=True)
245
268
  response.raise_for_status()
246
- return url, response.content, True # Success
269
+ content = b"".join(response.iter_bytes())
270
+ return url, content, True # Success
247
271
 
248
272
  def threaded(self, max_workers: int = 20, timeout: int = 10):
249
273
  """
@@ -259,10 +283,10 @@ class BaseCore:
259
283
  completed, successful_downloads = 0, 0
260
284
 
261
285
  with ThreadPoolExecutor(max_workers=max_workers) as executor:
262
- # Map the last part of the URL (filename) to the future
263
- future_to_hls_part = {executor.submit(self.download_segment, url, timeout): os.path.basename(url) for
264
- url in
265
- segments}
286
+ future_to_hls_part = {
287
+ executor.submit(self.download_segment, url, timeout): os.path.basename(url)
288
+ for url in segments
289
+ }
266
290
 
267
291
  for future in as_completed(future_to_hls_part):
268
292
  hls_part = future_to_hls_part[future]
@@ -271,26 +295,25 @@ class BaseCore:
271
295
  completed += 1
272
296
  if success:
273
297
  successful_downloads += 1
274
- callback(completed, length) # Update callback regardless of success to reflect progress
298
+ callback(completed, length) # Update progress callback
275
299
  except Exception as e:
276
- raise e
300
+ logger.error(f"Error processing segment {hls_part}: {e}")
277
301
 
278
- # Writing only successful downloads to the file
302
+ # Write only successful segments to the output file
279
303
  with open(path, 'wb') as file:
280
304
  for segment_url in segments:
281
- # Find the future object using the HLS part of the URL as the key
282
- matched_futures = [future for future, hls_part in future_to_hls_part.items() if
283
- hls_part == os.path.basename(segment_url)]
305
+ matched_futures = [
306
+ future for future, hls_part in future_to_hls_part.items()
307
+ if hls_part == os.path.basename(segment_url)
308
+ ]
284
309
  if matched_futures:
285
- logging.info("Got a match")
286
- future = matched_futures[0] # Assuming unique HLS parts, take the first match
310
+ future = matched_futures[0]
287
311
  try:
288
312
  _, data, success = future.result()
289
313
  if success:
290
314
  file.write(data)
291
- except Exception:
292
- error = traceback.format_exc()
293
- logger.error(f"Exception while downloading segment: {error}")
315
+ except Exception as e:
316
+ logger.error(f"Exception writing segment {segment_url}: {e}")
294
317
 
295
318
  return wrapper
296
319
 
@@ -325,7 +348,7 @@ class BaseCore:
325
348
  "-i", new_url, # Input URL
326
349
  "-bsf:a", "aac_adtstoasc",
327
350
  "-y", # Overwrite output files without asking
328
- "-c", "copy", # Copy streams without reencoding
351
+ "-c", "copy", # Copy streams without re-encoding
329
352
  path # Output file path
330
353
  ]
331
354
 
@@ -340,34 +363,47 @@ class BaseCore:
340
363
 
341
364
  return False
342
365
 
343
- def legacy_download(self, stream: bool, path: str, url: str, callback=None) -> bool:
344
- response = self.fetch(url, stream=stream, get_bytes=True, get_response=True)
345
- file_size = int(response.headers.get('content-length', 0))
366
+ def legacy_download(self, path: str, url: str, callback=None) -> bool:
367
+ """
368
+ Download a file using streaming, with support for progress updates.
369
+ """
370
+ try:
371
+ with self.session.stream("GET", url, timeout=30) as response: # Use a reasonable timeout
372
+ response.raise_for_status()
346
373
 
347
- if callback is None:
348
- progress_bar = Callback()
374
+ file_size = int(response.headers.get('content-length', 0))
349
375
 
350
- downloaded_so_far = 0
376
+ if callback is None:
377
+ progress_bar = Callback()
351
378
 
352
- if not os.path.exists(path):
353
- with open(path, 'wb') as file:
354
- for chunk in response.iter_content(chunk_size=1024):
355
- file.write(chunk)
356
- downloaded_so_far += len(chunk)
379
+ downloaded_so_far = 0
357
380
 
358
- if callback:
359
- callback(downloaded_so_far, file_size)
381
+ # Open file for writing
382
+ with open(path, 'wb') as file:
383
+ for chunk in response.iter_bytes(chunk_size=1024):
384
+ if not chunk:
385
+ break # End of stream
360
386
 
361
- else:
362
- progress_bar.text_progress_bar(downloaded=downloaded_so_far, total=file_size)
387
+ file.write(chunk)
388
+ downloaded_so_far += len(chunk)
363
389
 
364
- if not callback:
365
- del progress_bar
390
+ # Update progress
391
+ if callback:
392
+ callback(downloaded_so_far, file_size)
393
+ else:
394
+ progress_bar.text_progress_bar(downloaded=downloaded_so_far, total=file_size)
366
395
 
367
- return True
396
+ if not callback:
397
+ del progress_bar
368
398
 
369
- else:
370
- raise FileExistsError("The file already exists.")
399
+ return True
400
+
401
+ except httpx.RequestError as e:
402
+ logger.error(f"Request failed for URL {url}: {e}")
403
+ return False
404
+ except Exception as e:
405
+ logger.error(f"Unexpected error during download: {e}")
406
+ return False
371
407
 
372
408
  @classmethod
373
409
  def return_path(cls, video, args) -> str:
@@ -5,8 +5,7 @@ MAX_RETRIES = 4
5
5
  REQUEST_DELAY = 0
6
6
  TIMEOUT = 20 # Maximal request timeout
7
7
  FFMPEG_PATH = "ffmpeg"
8
- USE_PROXIES = False
9
- PROXIES = {}
8
+ PROXY = None
10
9
  USER_AGENTS = ["Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
11
10
  "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.6 Safari/605.1.1",
12
11
  "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.3",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: eaf_base_api
3
- Version: 2.0
3
+ Version: 2.1
4
4
  Summary: A base API for EchterAlsFake's Porn APIs
5
5
  Home-page: https://github.com/EchterAlsFake/eaf_base_api
6
6
  Author: Johannes Habel
@@ -10,9 +10,9 @@ Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 (LGP
10
10
  Classifier: Programming Language :: Python
11
11
  Description-Content-Type: text/markdown
12
12
  License-File: LICENSE
13
- Requires-Dist: requests
14
13
  Requires-Dist: ffmpeg-progress-yield
15
14
  Requires-Dist: m3u8
15
+ Requires-Dist: httpx
16
16
 
17
17
  # EAF Base API
18
18
 
@@ -1,3 +1,3 @@
1
- requests
2
1
  ffmpeg-progress-yield
3
2
  m3u8
3
+ httpx
@@ -2,10 +2,9 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name="eaf_base_api",
5
- version="2.0",
5
+ version="2.1",
6
6
  packages=find_packages(),
7
- install_requires=[
8
- "requests", "ffmpeg-progress-yield", "m3u8"
7
+ install_requires=["ffmpeg-progress-yield", "m3u8", "httpx"
9
8
  ],
10
9
  entry_points={
11
10
  'console_scripts': [
File without changes
File without changes
File without changes