cinnamon-cli 0.2.39__tar.gz → 0.3.0__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.39 → cinnamon_cli-0.3.0}/PKG-INFO +1 -1
- cinnamon_cli-0.3.0/cinnamon/__init__.py +1 -0
- cinnamon_cli-0.3.0/cinnamon/anilist.py +196 -0
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.3.0}/cinnamon/cli.py +775 -122
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.3.0}/cinnamon/scrapers/anime.py +75 -33
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.3.0}/cinnamon/tmdb.py +3 -0
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.3.0}/cinnamon_cli.egg-info/PKG-INFO +1 -1
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.3.0}/pyproject.toml +1 -1
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.3.0}/tests/test_ui_smoke.py +1 -1
- cinnamon_cli-0.2.39/cinnamon/__init__.py +0 -1
- cinnamon_cli-0.2.39/cinnamon/anilist.py +0 -92
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.3.0}/LICENSE +0 -0
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.3.0}/README.md +0 -0
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.3.0}/cinnamon/config.py +0 -0
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.3.0}/cinnamon/downloads.py +0 -0
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.3.0}/cinnamon/embedapi.py +0 -0
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.3.0}/cinnamon/errors.py +0 -0
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.3.0}/cinnamon/history.py +0 -0
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.3.0}/cinnamon/player.py +0 -0
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.3.0}/cinnamon/scrapers/__init__.py +0 -0
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.3.0}/cinnamon/scrapers/_vidlink_crypto.py +0 -0
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.3.0}/cinnamon/scrapers/base.py +0 -0
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.3.0}/cinnamon/scrapers/torrentio.py +0 -0
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.3.0}/cinnamon/scrapers/vidsrc.py +0 -0
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.3.0}/cinnamon/scrapers/webstream.py +0 -0
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.3.0}/cinnamon/tui.py +0 -0
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.3.0}/cinnamon_cli.egg-info/SOURCES.txt +0 -0
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.3.0}/cinnamon_cli.egg-info/dependency_links.txt +0 -0
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.3.0}/cinnamon_cli.egg-info/entry_points.txt +0 -0
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.3.0}/cinnamon_cli.egg-info/requires.txt +0 -0
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.3.0}/cinnamon_cli.egg-info/top_level.txt +0 -0
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.3.0}/setup.cfg +0 -0
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.3.0}/tests/test_directstream.py +0 -0
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.3.0}/tests/test_directstream2.py +0 -0
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.3.0}/tests/test_directstream3.py +0 -0
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.3.0}/tests/test_directstream4.py +0 -0
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.3.0}/tests/test_directstream5.py +0 -0
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.3.0}/tests/test_directstream6.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.3.0"
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import time as _time
|
|
2
|
+
|
|
3
|
+
import requests
|
|
4
|
+
|
|
5
|
+
from .errors import CinnamonError
|
|
6
|
+
|
|
7
|
+
API = "https://graphql.anilist.co"
|
|
8
|
+
UA = "cinnamon/0.1.0"
|
|
9
|
+
|
|
10
|
+
_SEARCH_QUERY = """
|
|
11
|
+
query ($search: String, $page: Int) {
|
|
12
|
+
Page(page: $page, perPage: 20) {
|
|
13
|
+
media(search: $search, type: ANIME) {
|
|
14
|
+
id
|
|
15
|
+
idMal
|
|
16
|
+
title { romaji english native }
|
|
17
|
+
episodes
|
|
18
|
+
status
|
|
19
|
+
format
|
|
20
|
+
startDate { year }
|
|
21
|
+
genres
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
_RELATION_QUERY = """
|
|
29
|
+
query ($search: String) {
|
|
30
|
+
Page(page: 1, perPage: 5) {
|
|
31
|
+
media(search: $search, type: ANIME) {
|
|
32
|
+
id
|
|
33
|
+
title { romaji english }
|
|
34
|
+
relations {
|
|
35
|
+
edges {
|
|
36
|
+
relationType
|
|
37
|
+
node {
|
|
38
|
+
id
|
|
39
|
+
title { romaji english }
|
|
40
|
+
episodes
|
|
41
|
+
status
|
|
42
|
+
format
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def search_anime(query):
|
|
53
|
+
try:
|
|
54
|
+
resp = requests.post(
|
|
55
|
+
API,
|
|
56
|
+
json={"query": _SEARCH_QUERY, "variables": {"search": query, "page": 1}},
|
|
57
|
+
timeout=15,
|
|
58
|
+
headers={"User-Agent": UA, "Content-Type": "application/json", "Accept": "application/json"},
|
|
59
|
+
)
|
|
60
|
+
resp.raise_for_status()
|
|
61
|
+
data = resp.json()
|
|
62
|
+
return data.get("data", {}).get("Page", {}).get("media", [])
|
|
63
|
+
except requests.ConnectionError:
|
|
64
|
+
raise CinnamonError("Could not reach AniList. Check your internet connection.")
|
|
65
|
+
except requests.Timeout:
|
|
66
|
+
raise CinnamonError("AniList request timed out.")
|
|
67
|
+
except requests.HTTPError as e:
|
|
68
|
+
raise CinnamonError(f"AniList returned an error (HTTP {e.response.status_code}).")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def find_sequel(anime_name):
|
|
72
|
+
"""Search AniList for an anime and return its first SEQUEL relation, or None."""
|
|
73
|
+
try:
|
|
74
|
+
resp = requests.post(
|
|
75
|
+
API,
|
|
76
|
+
json={"query": _RELATION_QUERY, "variables": {"search": anime_name}},
|
|
77
|
+
timeout=15,
|
|
78
|
+
headers={"User-Agent": UA, "Content-Type": "application/json", "Accept": "application/json"},
|
|
79
|
+
)
|
|
80
|
+
resp.raise_for_status()
|
|
81
|
+
data = resp.json()
|
|
82
|
+
media_list = data.get("data", {}).get("Page", {}).get("media", [])
|
|
83
|
+
for m in media_list:
|
|
84
|
+
edges = m.get("relations", {}).get("edges", [])
|
|
85
|
+
for e in edges:
|
|
86
|
+
if e.get("relationType") == "SEQUEL":
|
|
87
|
+
node = e.get("node", {})
|
|
88
|
+
title = node.get("title", {})
|
|
89
|
+
name = title.get("romaji") or title.get("english") or ""
|
|
90
|
+
if name:
|
|
91
|
+
return {"id": node["id"], "name": name}
|
|
92
|
+
return None
|
|
93
|
+
except (requests.RequestException, CinnamonError):
|
|
94
|
+
return None
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
_SCHEDULE_QUERY = """
|
|
98
|
+
query ($page: Int, $perPage: Int, $airingAtGreater: Int, $airingAtLesser: Int) {
|
|
99
|
+
Page(page: $page, perPage: $perPage) {
|
|
100
|
+
airingSchedules(
|
|
101
|
+
airingAt_greater: $airingAtGreater
|
|
102
|
+
airingAt_lesser: $airingAtLesser
|
|
103
|
+
sort: TIME
|
|
104
|
+
) {
|
|
105
|
+
id
|
|
106
|
+
airingAt
|
|
107
|
+
episode
|
|
108
|
+
media {
|
|
109
|
+
id
|
|
110
|
+
title { romaji english }
|
|
111
|
+
format
|
|
112
|
+
status
|
|
113
|
+
episodes
|
|
114
|
+
popularity
|
|
115
|
+
averageScore
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
_MEDIA_AIRING_QUERY = """
|
|
124
|
+
query ($mediaId: Int) {
|
|
125
|
+
Page(page: 1, perPage: 100) {
|
|
126
|
+
airingSchedules(mediaId: $mediaId, sort: EPISODE) {
|
|
127
|
+
airingAt
|
|
128
|
+
episode
|
|
129
|
+
timeUntilAiring
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
"""
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def get_media_airing(media_id):
|
|
137
|
+
"""Return list of {airingAt, episode, timeUntilAiring} for a given media ID."""
|
|
138
|
+
try:
|
|
139
|
+
resp = requests.post(
|
|
140
|
+
API,
|
|
141
|
+
json={"query": _MEDIA_AIRING_QUERY, "variables": {"mediaId": media_id}},
|
|
142
|
+
timeout=15,
|
|
143
|
+
headers={"User-Agent": UA, "Content-Type": "application/json", "Accept": "application/json"},
|
|
144
|
+
)
|
|
145
|
+
resp.raise_for_status()
|
|
146
|
+
data = resp.json()
|
|
147
|
+
return data.get("data", {}).get("Page", {}).get("airingSchedules", [])
|
|
148
|
+
except requests.ConnectionError:
|
|
149
|
+
return []
|
|
150
|
+
except requests.Timeout:
|
|
151
|
+
return []
|
|
152
|
+
except requests.HTTPError:
|
|
153
|
+
return []
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def get_schedule(days=7):
|
|
157
|
+
now = int(_time.time())
|
|
158
|
+
end = now + (days * 86400)
|
|
159
|
+
try:
|
|
160
|
+
resp = requests.post(
|
|
161
|
+
API,
|
|
162
|
+
json={
|
|
163
|
+
"query": _SCHEDULE_QUERY,
|
|
164
|
+
"variables": {
|
|
165
|
+
"page": 1,
|
|
166
|
+
"perPage": 50,
|
|
167
|
+
"airingAtGreater": now,
|
|
168
|
+
"airingAtLesser": end,
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
timeout=15,
|
|
172
|
+
headers={"User-Agent": UA, "Content-Type": "application/json", "Accept": "application/json"},
|
|
173
|
+
)
|
|
174
|
+
resp.raise_for_status()
|
|
175
|
+
data = resp.json()
|
|
176
|
+
schedules = data.get("data", {}).get("Page", {}).get("airingSchedules", [])
|
|
177
|
+
results = []
|
|
178
|
+
for s in schedules:
|
|
179
|
+
media = s.get("media", {})
|
|
180
|
+
title = media.get("title", {}) or {}
|
|
181
|
+
results.append({
|
|
182
|
+
"airing_at": s.get("airingAt", 0),
|
|
183
|
+
"episode": s.get("episode"),
|
|
184
|
+
"title_romaji": title.get("romaji", "") or "",
|
|
185
|
+
"title_english": title.get("english", "") or "",
|
|
186
|
+
"format": media.get("format", ""),
|
|
187
|
+
"popularity": media.get("popularity", 0) or 0,
|
|
188
|
+
"score": media.get("averageScore", 0) or 0,
|
|
189
|
+
})
|
|
190
|
+
return results
|
|
191
|
+
except requests.ConnectionError:
|
|
192
|
+
raise CinnamonError("Could not reach AniList. Check your internet connection.")
|
|
193
|
+
except requests.Timeout:
|
|
194
|
+
raise CinnamonError("AniList request timed out.")
|
|
195
|
+
except requests.HTTPError as e:
|
|
196
|
+
raise CinnamonError(f"AniList returned an error (HTTP {e.response.status_code}).")
|