eaf-base-api 2.4.9__tar.gz → 2.5.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.4
2
2
  Name: eaf_base_api
3
- Version: 2.4.9
3
+ Version: 2.5.1
4
4
  Summary: A base API for EchterAlsFake's Porn APIs
5
5
  Author-email: Johannes Habel <EchterAlsFake@proton.me>
6
6
  License: LGPLv3
@@ -0,0 +1,7 @@
1
+ __all__ = ["BaseCore", "Callback", "config", "errors", "setup_logger", "Cache"]
2
+
3
+
4
+ from base_api.modules import errors
5
+ from base_api.modules.config import config
6
+ from base_api.modules.progress_bars import Callback
7
+ from base_api.base import BaseCore, setup_logger, Cache
@@ -15,10 +15,11 @@ import threading
15
15
  from itertools import islice
16
16
  from collections import deque
17
17
  from functools import lru_cache
18
- from urllib.parse import urljoin
19
18
  from email.utils import parsedate_to_datetime
19
+ from datetime import datetime, timezone, timedelta
20
+ from urllib.parse import urljoin, urlparse, parse_qs
21
+ from concurrent.futures import ThreadPoolExecutor, wait, FIRST_COMPLETED
20
22
  from typing import Any, Dict, List, Optional, Union, Callable, Tuple, Iterable
21
- from concurrent.futures import ThreadPoolExecutor, as_completed, wait, FIRST_COMPLETED
22
23
 
23
24
  try:
24
25
  from modules.errors import *
@@ -310,6 +311,15 @@ class Cache:
310
311
 
311
312
  self.cache_dictionary[url] = content
312
313
 
314
+ def save_segments_to_cache(self, m3u8_url: str, segments: list):
315
+ with self.lock:
316
+ self.cache_dictionary[m3u8_url] = segments
317
+
318
+ def get_segments_from_cache(self, m3u8_url: str):
319
+ with self.lock:
320
+ segments = self.cache_dictionary.get(m3u8_url, None)
321
+ return segments
322
+
313
323
 
314
324
  class Helper:
315
325
  def __init__(
@@ -723,18 +733,12 @@ class BaseCore:
723
733
  raise KillSwitch("CRITICAL PROXY ERROR, CHECK LOGS!")
724
734
 
725
735
  def initialize_session(self):
726
- limits = httpx.Limits(
727
- max_connections=getattr(self.config, "max_connections", 128),
728
- max_keepalive_connections=getattr(self.config, "max_keepalive", 4),
729
- keepalive_expiry=10,
730
- )
731
736
  ctx = ssl.create_default_context(cafile=certifi.where())
732
737
  if not self.config.verify_ssl:
733
738
  ctx.check_hostname = False
734
739
  ctx.verify_mode = ssl.CERT_NONE
735
740
 
736
741
  self.session = httpx.Client(
737
- limits=limits,
738
742
  proxy=self.config.proxy,
739
743
  timeout=self.config.timeout, # e.g. 5s on good connections
740
744
  http2=self.config.use_http2,
@@ -800,6 +804,7 @@ class BaseCore:
800
804
  method: str = "GET",
801
805
  headers: Optional[Dict[str, str]] = None,
802
806
  json: Optional[Dict] = None,
807
+ params: Optional[Dict] = None,
803
808
  ) -> Union[bytes, str, httpx.Response]:
804
809
  """
805
810
  Fetch content with retries, optional caching, proxy support, and bandwidth limiting.
@@ -854,7 +859,8 @@ class BaseCore:
854
859
  timeout=req_timeout,
855
860
  follow_redirects=allow_redirects,
856
861
  data=data,
857
- json=json
862
+ json=json,
863
+ params=params,
858
864
  )
859
865
 
860
866
  last_response = response
@@ -905,6 +911,9 @@ class BaseCore:
905
911
 
906
912
  return content
907
913
 
914
+ elif status == 204:
915
+ return response
916
+
908
917
  # 403 handling: try a UA switch once, then give up
909
918
  if status == 403:
910
919
  if attempt == 0 or attempt == 1:
@@ -929,7 +938,14 @@ class BaseCore:
929
938
 
930
939
  # 410: permanent gone
931
940
  if status == 410:
932
- raise ResourceGone(f"Resource: {url} is Gone (410).")
941
+ self.logger.warning(f"""
942
+ The resource: {url} is gone. This can happen because of an expired token. I will try to fix this automatically, by creating
943
+ a new session and getting a fresh m3u8 URL. This fix is very experimental and I don't know if it works. """)
944
+ self.initialize_session()
945
+
946
+
947
+
948
+
933
949
 
934
950
  # 429: rate limited — respect Retry-After if present, else backoff and retry up to cap
935
951
  if status == 429:
@@ -1119,7 +1135,37 @@ class BaseCore:
1119
1135
  # Return rank numbers instead of heights if we truly can't infer—kept simple:
1120
1136
  return [i for i, _ in enumerate(by_bw, start=1)]
1121
1137
 
1138
+ def _to_epoch(self, v: str) -> int:
1139
+ n = int(v)
1140
+ return n // 1000 if n > 10 ** 12 else n # handle ms too
1141
+
1142
+ def m3u8_expiry_validto(self, url: str, safety_seconds: int = 60):
1143
+ q = {k: v[0] for k, v in parse_qs(urlparse(url).query).items() if v}
1144
+ vf = q.get("validfrom")
1145
+ vt = q.get("validto")
1146
+ if not vt:
1147
+ return None # not this scheme
1148
+ start_utc = datetime.fromtimestamp(self._to_epoch(vf), tz=timezone.utc) if vf else None
1149
+ expires_utc = datetime.fromtimestamp(self._to_epoch(vt), tz=timezone.utc)
1150
+ refresh_utc = expires_utc - timedelta(seconds=safety_seconds)
1151
+ secs_left = int((expires_utc - datetime.now(timezone.utc)).total_seconds())
1152
+ return {
1153
+ "start_utc": start_utc,
1154
+ "expires_utc": expires_utc,
1155
+ "refresh_utc": refresh_utc,
1156
+ "seconds_left": max(secs_left, 0),
1157
+ }
1158
+
1159
+ # Example
1160
+
1161
+
1122
1162
  def get_segments(self, m3u8_url_master: str, quality: Union[str, int]) -> list:
1163
+ _cache_url = f"{m3u8_url_master}{quality}"
1164
+ _segments: list | None = self.cache.get_segments_from_cache(_cache_url)
1165
+ if _segments:
1166
+ self.logger.info(f"Received: {len(_segments)} from cache!")
1167
+ return _segments
1168
+
1123
1169
  # Resolve the quality-specific playlist URL (may still be a master in some edge cases)
1124
1170
  playlist_url = self.get_m3u8_by_quality(m3u8_url=m3u8_url_master, quality=quality)
1125
1171
  self.logger.debug(f"Trying to fetch segment from m3u8 -> {playlist_url}")
@@ -1165,6 +1211,8 @@ class BaseCore:
1165
1211
  segments.append(urljoin(base_url, seg.uri))
1166
1212
 
1167
1213
  self.logger.debug(f"Fetched {len(segments)} segments from m3u8 URL (including init if present)")
1214
+ self.logger.info(f"Saving segments to cache....")
1215
+ self.cache.save_segments_to_cache(_cache_url, segments)
1168
1216
  return segments
1169
1217
 
1170
1218
  def download_segment(self, url: str, timeout: int) -> tuple[str, bytes, bool]:
@@ -1585,4 +1633,4 @@ You need to install `ffmpeg-progress-yield` to use the FFmpeg download mode.""")
1585
1633
  return True
1586
1634
 
1587
1635
  elif value.lower() in ("false", "0", "no"):
1588
- return False
1636
+ return False
@@ -7,7 +7,6 @@ class RuntimeConfig:
7
7
  self.max_bandwidth_mb = None # Set speed limit in megabytes per second e.g, 2.0, 3.5 etc...
8
8
  self.ffmpeg_path = "ffmpeg"
9
9
  self.proxy = None
10
- self.raise_bot_protection = False
11
10
  self.verify_ssl = True
12
11
  self.cookies = None
13
12
  self.locale = "en-US,en;q=0.9" # If you override this, it could change regexes and thus make stuff not work...
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: eaf_base_api
3
- Version: 2.4.9
3
+ Version: 2.5.1
4
4
  Summary: A base API for EchterAlsFake's Porn APIs
5
5
  Author-email: Johannes Habel <EchterAlsFake@proton.me>
6
6
  License: LGPLv3
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "eaf_base_api"
7
- version = "2.4.9"
7
+ version = "2.5.1"
8
8
  description = "A base API for EchterAlsFake's Porn APIs"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.6"
@@ -1,5 +0,0 @@
1
- __all__ = ["BaseCore", "Callback", "config"]
2
-
3
- from base_api.base import BaseCore
4
- from base_api.modules.progress_bars import Callback
5
- from base_api.modules.config import config
File without changes
File without changes
File without changes
File without changes