cinnamon-cli 0.2.27__tar.gz → 0.2.28__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.
Files changed (35) hide show
  1. {cinnamon_cli-0.2.27/cinnamon_cli.egg-info → cinnamon_cli-0.2.28}/PKG-INFO +1 -1
  2. cinnamon_cli-0.2.28/cinnamon/__init__.py +1 -0
  3. {cinnamon_cli-0.2.27 → cinnamon_cli-0.2.28}/cinnamon/player.py +3 -1
  4. {cinnamon_cli-0.2.27 → cinnamon_cli-0.2.28}/cinnamon/scrapers/anime.py +57 -6
  5. {cinnamon_cli-0.2.27 → cinnamon_cli-0.2.28/cinnamon_cli.egg-info}/PKG-INFO +1 -1
  6. {cinnamon_cli-0.2.27 → cinnamon_cli-0.2.28}/pyproject.toml +1 -1
  7. cinnamon_cli-0.2.27/cinnamon/__init__.py +0 -1
  8. {cinnamon_cli-0.2.27 → cinnamon_cli-0.2.28}/LICENSE +0 -0
  9. {cinnamon_cli-0.2.27 → cinnamon_cli-0.2.28}/README.md +0 -0
  10. {cinnamon_cli-0.2.27 → cinnamon_cli-0.2.28}/cinnamon/anilist.py +0 -0
  11. {cinnamon_cli-0.2.27 → cinnamon_cli-0.2.28}/cinnamon/cli.py +0 -0
  12. {cinnamon_cli-0.2.27 → cinnamon_cli-0.2.28}/cinnamon/config.py +0 -0
  13. {cinnamon_cli-0.2.27 → cinnamon_cli-0.2.28}/cinnamon/downloads.py +0 -0
  14. {cinnamon_cli-0.2.27 → cinnamon_cli-0.2.28}/cinnamon/errors.py +0 -0
  15. {cinnamon_cli-0.2.27 → cinnamon_cli-0.2.28}/cinnamon/history.py +0 -0
  16. {cinnamon_cli-0.2.27 → cinnamon_cli-0.2.28}/cinnamon/scrapers/__init__.py +0 -0
  17. {cinnamon_cli-0.2.27 → cinnamon_cli-0.2.28}/cinnamon/scrapers/base.py +0 -0
  18. {cinnamon_cli-0.2.27 → cinnamon_cli-0.2.28}/cinnamon/scrapers/torrentio.py +0 -0
  19. {cinnamon_cli-0.2.27 → cinnamon_cli-0.2.28}/cinnamon/scrapers/vidsrc.py +0 -0
  20. {cinnamon_cli-0.2.27 → cinnamon_cli-0.2.28}/cinnamon/scrapers/webstream.py +0 -0
  21. {cinnamon_cli-0.2.27 → cinnamon_cli-0.2.28}/cinnamon/tmdb.py +0 -0
  22. {cinnamon_cli-0.2.27 → cinnamon_cli-0.2.28}/cinnamon/tui.py +0 -0
  23. {cinnamon_cli-0.2.27 → cinnamon_cli-0.2.28}/cinnamon_cli.egg-info/SOURCES.txt +0 -0
  24. {cinnamon_cli-0.2.27 → cinnamon_cli-0.2.28}/cinnamon_cli.egg-info/dependency_links.txt +0 -0
  25. {cinnamon_cli-0.2.27 → cinnamon_cli-0.2.28}/cinnamon_cli.egg-info/entry_points.txt +0 -0
  26. {cinnamon_cli-0.2.27 → cinnamon_cli-0.2.28}/cinnamon_cli.egg-info/requires.txt +0 -0
  27. {cinnamon_cli-0.2.27 → cinnamon_cli-0.2.28}/cinnamon_cli.egg-info/top_level.txt +0 -0
  28. {cinnamon_cli-0.2.27 → cinnamon_cli-0.2.28}/setup.cfg +0 -0
  29. {cinnamon_cli-0.2.27 → cinnamon_cli-0.2.28}/tests/test_directstream.py +0 -0
  30. {cinnamon_cli-0.2.27 → cinnamon_cli-0.2.28}/tests/test_directstream2.py +0 -0
  31. {cinnamon_cli-0.2.27 → cinnamon_cli-0.2.28}/tests/test_directstream3.py +0 -0
  32. {cinnamon_cli-0.2.27 → cinnamon_cli-0.2.28}/tests/test_directstream4.py +0 -0
  33. {cinnamon_cli-0.2.27 → cinnamon_cli-0.2.28}/tests/test_directstream5.py +0 -0
  34. {cinnamon_cli-0.2.27 → cinnamon_cli-0.2.28}/tests/test_directstream6.py +0 -0
  35. {cinnamon_cli-0.2.27 → cinnamon_cli-0.2.28}/tests/test_ui_smoke.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cinnamon-cli
3
- Version: 0.2.27
3
+ Version: 0.2.28
4
4
  Summary: CLI to search TV shows, movies & anime via TMDB/AniList, scrape m3u8 links, and play in VLC/mpv
5
5
  Author: pizza-droid
6
6
  License: CC BY-NC 4.0
@@ -0,0 +1 @@
1
+ __version__ = "0.2.28"
@@ -470,7 +470,9 @@ def download_video(url, title="", referer=None, output_dir=".", track_id=None):
470
470
  safe = "".join(c if c.isalnum() or c in " .-_()" else "_" for c in title) or "video"
471
471
  outtmpl = os.path.join(output_dir, f"{safe}.%(ext)s")
472
472
 
473
- cmd = [exe, "--no-mtime", "--no-warnings", "-o", outtmpl]
473
+ cmd = [
474
+ exe, "--no-mtime", "--no-warnings", "-o", outtmpl,
475
+ ]
474
476
  if referer:
475
477
  cmd += ["--referer", referer]
476
478
  cmd.append(url)
@@ -2,15 +2,60 @@ import base64
2
2
  import hashlib
3
3
  import json
4
4
  import re
5
+ import socket
5
6
  import time as _time
7
+ import contextlib
6
8
  from typing import Optional, Tuple
7
9
 
8
10
  import requests
11
+ from requests.adapters import HTTPAdapter
12
+ from urllib3.util.retry import Retry
9
13
 
10
14
  from ..errors import ScraperNetworkError, ScraperNoStreamError, ScraperParseError
11
15
  from ..player import DEFAULT_UA as UA
12
16
  from .base import BaseScraper, ScraperResult
13
17
 
18
+
19
+ @contextlib.contextmanager
20
+ def _force_ipv4():
21
+ """Temporarily make getaddrinfo prefer IPv4.
22
+
23
+ allanime's API is served behind Cloudflare which publishes AAAA (IPv6)
24
+ records first. On hosts without working IPv6, Python's getaddrinfo can
25
+ intermittently fail with 'Errno 11002 getaddrinfo failed', so we bias
26
+ resolution toward IPv4 for the duration of a request.
27
+ """
28
+ orig = socket.getaddrinfo
29
+
30
+ def _prefer_v4(host, port, family=socket.AF_UNSPEC, type=0, proto=0, flags=0):
31
+ try:
32
+ return orig(host, port, socket.AF_INET, type, proto, flags)
33
+ except socket.gaierror:
34
+ return orig(host, port, family, type, proto, flags)
35
+
36
+ socket.getaddrinfo = _prefer_v4
37
+ try:
38
+ yield
39
+ finally:
40
+ socket.getaddrinfo = orig
41
+
42
+
43
+ def _new_session(ua=UA, origin="https://youtu-chan.com"):
44
+ """Build a requests.Session with retries and IPv4-biased DNS."""
45
+ s = requests.Session()
46
+ s.headers.update({"User-Agent": ua, "Origin": origin})
47
+ retry = Retry(
48
+ total=3,
49
+ backoff_factor=1.0,
50
+ status_forcelist=[500, 502, 503, 504],
51
+ allowed_methods=frozenset(["GET", "POST"]),
52
+ raise_on_status=False,
53
+ )
54
+ adapter = HTTPAdapter(max_retries=retry, pool_connections=4, pool_maxsize=4)
55
+ s.mount("https://", adapter)
56
+ s.mount("http://", adapter)
57
+ return s
58
+
14
59
  TIMEOUT = 15
15
60
  API = "https://api.allanime.day/api"
16
61
 
@@ -84,8 +129,7 @@ def _aa_source_query_hash(chunk_js: str) -> Optional[str]:
84
129
  def _aa_fetch():
85
130
  """Fetch (expires_ms, epoch, key, mask, query_hash) from the live site."""
86
131
  try:
87
- s = requests.Session()
88
- s.headers.update({"User-Agent": _BROWSER_UA})
132
+ s = _new_session(ua=_BROWSER_UA, origin="https://mkissa.to")
89
133
  html = s.get(_MKISSA_URL, timeout=10).text
90
134
  aa = json.loads(re.search(r"window\.__aaCrypto\s*=\s*(\{.*?\})", html).group(1))
91
135
  part_b, epoch = aa["partB"], int(aa["epoch"])
@@ -179,7 +223,11 @@ def _gql(session, query, variables):
179
223
  if elapsed < 3.5:
180
224
  _time.sleep(3.5 - elapsed)
181
225
  body = json.dumps({"variables": json.dumps(variables), "query": query}, ensure_ascii=False, separators=(",", ":"))
182
- resp = session.post(API, data=body, timeout=TIMEOUT, headers={"Content-Type": "application/json"})
226
+ try:
227
+ with _force_ipv4():
228
+ resp = session.post(API, data=body, timeout=TIMEOUT, headers={"Content-Type": "application/json"})
229
+ except (requests.ConnectionError, requests.Timeout) as e:
230
+ raise ScraperNetworkError("allanime", f"Could not reach allanime API (check your connection / DNS): {e}")
183
231
  _LAST_REQUEST = _time.time()
184
232
  if resp.status_code != 200:
185
233
  raise ScraperNetworkError("allanime", f"GraphQL returned {resp.status_code}")
@@ -199,7 +247,11 @@ def _gql_src(session, variables, query_hash=None, aa_req=None):
199
247
  {"extensions": extensions, "variables": json.dumps(variables)},
200
248
  ensure_ascii=False, separators=(",", ":"),
201
249
  )
202
- resp = session.post(API, data=body, timeout=TIMEOUT, headers={"Content-Type": "application/json"})
250
+ try:
251
+ with _force_ipv4():
252
+ resp = session.post(API, data=body, timeout=TIMEOUT, headers={"Content-Type": "application/json"})
253
+ except (requests.ConnectionError, requests.Timeout) as e:
254
+ raise ScraperNetworkError("allanime", f"Could not reach allanime API (check your connection / DNS): {e}")
203
255
  _LAST_REQUEST = _time.time()
204
256
  if resp.status_code != 200:
205
257
  raise ScraperNetworkError("allanime", f"Source GQL returned {resp.status_code}")
@@ -274,8 +326,7 @@ class AnimeScraper(BaseScraper):
274
326
  raise ScraperParseError(self.name, "Missing show name in episode_info")
275
327
 
276
328
  deadline = _time.time() + 30
277
- session = requests.Session()
278
- session.headers.update({"User-Agent": UA, "Origin": "https://youtu-chan.com"})
329
+ session = _new_session()
279
330
 
280
331
  if _time.time() >= deadline:
281
332
  raise ScraperNoStreamError(self.name, "Timeout searching allanime")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cinnamon-cli
3
- Version: 0.2.27
3
+ Version: 0.2.28
4
4
  Summary: CLI to search TV shows, movies & anime via TMDB/AniList, scrape m3u8 links, and play in VLC/mpv
5
5
  Author: pizza-droid
6
6
  License: CC BY-NC 4.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "cinnamon-cli"
7
- version = "0.2.27"
7
+ version = "0.2.28"
8
8
  description = "CLI to search TV shows, movies & anime via TMDB/AniList, scrape m3u8 links, and play in VLC/mpv"
9
9
  license = { text = "CC BY-NC 4.0" }
10
10
  readme = "README.md"
@@ -1 +0,0 @@
1
- __version__ = "0.2.27"
File without changes
File without changes
File without changes