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.
- {cinnamon_cli-0.2.27/cinnamon_cli.egg-info → cinnamon_cli-0.2.28}/PKG-INFO +1 -1
- cinnamon_cli-0.2.28/cinnamon/__init__.py +1 -0
- {cinnamon_cli-0.2.27 → cinnamon_cli-0.2.28}/cinnamon/player.py +3 -1
- {cinnamon_cli-0.2.27 → cinnamon_cli-0.2.28}/cinnamon/scrapers/anime.py +57 -6
- {cinnamon_cli-0.2.27 → cinnamon_cli-0.2.28/cinnamon_cli.egg-info}/PKG-INFO +1 -1
- {cinnamon_cli-0.2.27 → cinnamon_cli-0.2.28}/pyproject.toml +1 -1
- cinnamon_cli-0.2.27/cinnamon/__init__.py +0 -1
- {cinnamon_cli-0.2.27 → cinnamon_cli-0.2.28}/LICENSE +0 -0
- {cinnamon_cli-0.2.27 → cinnamon_cli-0.2.28}/README.md +0 -0
- {cinnamon_cli-0.2.27 → cinnamon_cli-0.2.28}/cinnamon/anilist.py +0 -0
- {cinnamon_cli-0.2.27 → cinnamon_cli-0.2.28}/cinnamon/cli.py +0 -0
- {cinnamon_cli-0.2.27 → cinnamon_cli-0.2.28}/cinnamon/config.py +0 -0
- {cinnamon_cli-0.2.27 → cinnamon_cli-0.2.28}/cinnamon/downloads.py +0 -0
- {cinnamon_cli-0.2.27 → cinnamon_cli-0.2.28}/cinnamon/errors.py +0 -0
- {cinnamon_cli-0.2.27 → cinnamon_cli-0.2.28}/cinnamon/history.py +0 -0
- {cinnamon_cli-0.2.27 → cinnamon_cli-0.2.28}/cinnamon/scrapers/__init__.py +0 -0
- {cinnamon_cli-0.2.27 → cinnamon_cli-0.2.28}/cinnamon/scrapers/base.py +0 -0
- {cinnamon_cli-0.2.27 → cinnamon_cli-0.2.28}/cinnamon/scrapers/torrentio.py +0 -0
- {cinnamon_cli-0.2.27 → cinnamon_cli-0.2.28}/cinnamon/scrapers/vidsrc.py +0 -0
- {cinnamon_cli-0.2.27 → cinnamon_cli-0.2.28}/cinnamon/scrapers/webstream.py +0 -0
- {cinnamon_cli-0.2.27 → cinnamon_cli-0.2.28}/cinnamon/tmdb.py +0 -0
- {cinnamon_cli-0.2.27 → cinnamon_cli-0.2.28}/cinnamon/tui.py +0 -0
- {cinnamon_cli-0.2.27 → cinnamon_cli-0.2.28}/cinnamon_cli.egg-info/SOURCES.txt +0 -0
- {cinnamon_cli-0.2.27 → cinnamon_cli-0.2.28}/cinnamon_cli.egg-info/dependency_links.txt +0 -0
- {cinnamon_cli-0.2.27 → cinnamon_cli-0.2.28}/cinnamon_cli.egg-info/entry_points.txt +0 -0
- {cinnamon_cli-0.2.27 → cinnamon_cli-0.2.28}/cinnamon_cli.egg-info/requires.txt +0 -0
- {cinnamon_cli-0.2.27 → cinnamon_cli-0.2.28}/cinnamon_cli.egg-info/top_level.txt +0 -0
- {cinnamon_cli-0.2.27 → cinnamon_cli-0.2.28}/setup.cfg +0 -0
- {cinnamon_cli-0.2.27 → cinnamon_cli-0.2.28}/tests/test_directstream.py +0 -0
- {cinnamon_cli-0.2.27 → cinnamon_cli-0.2.28}/tests/test_directstream2.py +0 -0
- {cinnamon_cli-0.2.27 → cinnamon_cli-0.2.28}/tests/test_directstream3.py +0 -0
- {cinnamon_cli-0.2.27 → cinnamon_cli-0.2.28}/tests/test_directstream4.py +0 -0
- {cinnamon_cli-0.2.27 → cinnamon_cli-0.2.28}/tests/test_directstream5.py +0 -0
- {cinnamon_cli-0.2.27 → cinnamon_cli-0.2.28}/tests/test_directstream6.py +0 -0
- {cinnamon_cli-0.2.27 → cinnamon_cli-0.2.28}/tests/test_ui_smoke.py +0 -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 = [
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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")
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "cinnamon-cli"
|
|
7
|
-
version = "0.2.
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|