cinnamon-cli 0.2.39__tar.gz → 0.2.40__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.2.40}/PKG-INFO +1 -1
- cinnamon_cli-0.2.40/cinnamon/__init__.py +1 -0
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/cinnamon/anilist.py +71 -0
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/cinnamon/cli.py +150 -0
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/cinnamon/tmdb.py +3 -0
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/cinnamon_cli.egg-info/PKG-INFO +1 -1
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/pyproject.toml +1 -1
- cinnamon_cli-0.2.39/cinnamon/__init__.py +0 -1
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/LICENSE +0 -0
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/README.md +0 -0
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/cinnamon/config.py +0 -0
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/cinnamon/downloads.py +0 -0
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/cinnamon/embedapi.py +0 -0
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/cinnamon/errors.py +0 -0
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/cinnamon/history.py +0 -0
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/cinnamon/player.py +0 -0
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/cinnamon/scrapers/__init__.py +0 -0
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/cinnamon/scrapers/_vidlink_crypto.py +0 -0
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/cinnamon/scrapers/anime.py +0 -0
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/cinnamon/scrapers/base.py +0 -0
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/cinnamon/scrapers/torrentio.py +0 -0
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/cinnamon/scrapers/vidsrc.py +0 -0
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/cinnamon/scrapers/webstream.py +0 -0
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/cinnamon/tui.py +0 -0
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/cinnamon_cli.egg-info/SOURCES.txt +0 -0
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/cinnamon_cli.egg-info/dependency_links.txt +0 -0
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/cinnamon_cli.egg-info/entry_points.txt +0 -0
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/cinnamon_cli.egg-info/requires.txt +0 -0
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/cinnamon_cli.egg-info/top_level.txt +0 -0
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/setup.cfg +0 -0
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/tests/test_directstream.py +0 -0
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/tests/test_directstream2.py +0 -0
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/tests/test_directstream3.py +0 -0
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/tests/test_directstream4.py +0 -0
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/tests/test_directstream5.py +0 -0
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/tests/test_directstream6.py +0 -0
- {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/tests/test_ui_smoke.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.2.40"
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import time as _time
|
|
2
|
+
|
|
1
3
|
import requests
|
|
2
4
|
|
|
3
5
|
from .errors import CinnamonError
|
|
@@ -90,3 +92,72 @@ def find_sequel(anime_name):
|
|
|
90
92
|
return None
|
|
91
93
|
except (requests.RequestException, CinnamonError):
|
|
92
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
|
+
def get_schedule(days=7):
|
|
124
|
+
now = int(_time.time())
|
|
125
|
+
end = now + (days * 86400)
|
|
126
|
+
try:
|
|
127
|
+
resp = requests.post(
|
|
128
|
+
API,
|
|
129
|
+
json={
|
|
130
|
+
"query": _SCHEDULE_QUERY,
|
|
131
|
+
"variables": {
|
|
132
|
+
"page": 1,
|
|
133
|
+
"perPage": 50,
|
|
134
|
+
"airingAtGreater": now,
|
|
135
|
+
"airingAtLesser": end,
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
timeout=15,
|
|
139
|
+
headers={"User-Agent": UA, "Content-Type": "application/json", "Accept": "application/json"},
|
|
140
|
+
)
|
|
141
|
+
resp.raise_for_status()
|
|
142
|
+
data = resp.json()
|
|
143
|
+
schedules = data.get("data", {}).get("Page", {}).get("airingSchedules", [])
|
|
144
|
+
results = []
|
|
145
|
+
for s in schedules:
|
|
146
|
+
media = s.get("media", {})
|
|
147
|
+
title = media.get("title", {}) or {}
|
|
148
|
+
results.append({
|
|
149
|
+
"airing_at": s.get("airingAt", 0),
|
|
150
|
+
"episode": s.get("episode"),
|
|
151
|
+
"title_romaji": title.get("romaji", "") or "",
|
|
152
|
+
"title_english": title.get("english", "") or "",
|
|
153
|
+
"format": media.get("format", ""),
|
|
154
|
+
"popularity": media.get("popularity", 0) or 0,
|
|
155
|
+
"score": media.get("averageScore", 0) or 0,
|
|
156
|
+
})
|
|
157
|
+
return results
|
|
158
|
+
except requests.ConnectionError:
|
|
159
|
+
raise CinnamonError("Could not reach AniList. Check your internet connection.")
|
|
160
|
+
except requests.Timeout:
|
|
161
|
+
raise CinnamonError("AniList request timed out.")
|
|
162
|
+
except requests.HTTPError as e:
|
|
163
|
+
raise CinnamonError(f"AniList returned an error (HTTP {e.response.status_code}).")
|
|
@@ -1937,6 +1937,156 @@ def update():
|
|
|
1937
1937
|
_print_error("Update failed.", str(e))
|
|
1938
1938
|
|
|
1939
1939
|
|
|
1940
|
+
# ---------------------------------------------------------------------------
|
|
1941
|
+
# schedule
|
|
1942
|
+
# ---------------------------------------------------------------------------
|
|
1943
|
+
|
|
1944
|
+
|
|
1945
|
+
@cli.group(invoke_without_command=True)
|
|
1946
|
+
@click.pass_context
|
|
1947
|
+
def schedule(ctx):
|
|
1948
|
+
"""Show this week's trending TV shows and movies (requires TMDB key)."""
|
|
1949
|
+
if ctx.invoked_subcommand is None:
|
|
1950
|
+
_schedule_impl()
|
|
1951
|
+
|
|
1952
|
+
|
|
1953
|
+
@schedule.command()
|
|
1954
|
+
@click.option("--days", type=int, default=7, help="Days ahead (default 7)")
|
|
1955
|
+
@_handle_errors
|
|
1956
|
+
def anime(days):
|
|
1957
|
+
"""Show anime airing schedule from AniList (no API key needed)."""
|
|
1958
|
+
from .anilist import get_schedule
|
|
1959
|
+
import datetime
|
|
1960
|
+
|
|
1961
|
+
entries = get_schedule(days=days)
|
|
1962
|
+
if not entries:
|
|
1963
|
+
_print_info(f"No anime airing in the next {days} day(s).")
|
|
1964
|
+
return
|
|
1965
|
+
|
|
1966
|
+
now = datetime.datetime.now()
|
|
1967
|
+
|
|
1968
|
+
def _day_key(entry):
|
|
1969
|
+
dt = datetime.datetime.fromtimestamp(entry["airing_at"])
|
|
1970
|
+
return dt.date()
|
|
1971
|
+
|
|
1972
|
+
def _day_label(d):
|
|
1973
|
+
diff = (d - now.date()).days
|
|
1974
|
+
if diff == 0:
|
|
1975
|
+
return "Today"
|
|
1976
|
+
if diff == 1:
|
|
1977
|
+
return "Tomorrow"
|
|
1978
|
+
return d.strftime("%a %b %d")
|
|
1979
|
+
|
|
1980
|
+
by_day = {}
|
|
1981
|
+
for e in entries:
|
|
1982
|
+
d = _day_key(e)
|
|
1983
|
+
by_day.setdefault(d, []).append(e)
|
|
1984
|
+
|
|
1985
|
+
for day in by_day:
|
|
1986
|
+
by_day[day].sort(key=lambda x: (-(x.get("score") or 0), -(x.get("popularity") or 0)))
|
|
1987
|
+
|
|
1988
|
+
sorted_days = list(reversed(sorted(by_day.keys())))
|
|
1989
|
+
|
|
1990
|
+
theme = get_theme()
|
|
1991
|
+
heading = f"[bold {theme['accent']}]Anime Schedule — next {days} day{'s' if days != 1 else ''}[/bold {theme['accent']}]"
|
|
1992
|
+
console.print(Panel(heading, border_style=theme["border"]))
|
|
1993
|
+
|
|
1994
|
+
for i, day in enumerate(sorted_days):
|
|
1995
|
+
entries = by_day[day]
|
|
1996
|
+
|
|
1997
|
+
def _est_delay(pop):
|
|
1998
|
+
if pop >= 50000:
|
|
1999
|
+
return 45
|
|
2000
|
+
if pop >= 10000:
|
|
2001
|
+
return 90
|
|
2002
|
+
if pop >= 2000:
|
|
2003
|
+
return 180
|
|
2004
|
+
return 360
|
|
2005
|
+
|
|
2006
|
+
day_table = Table(border_style="dim", show_header=False, box=None, padding=(0, 1, 0, 0))
|
|
2007
|
+
day_table.add_column("Time", no_wrap=True)
|
|
2008
|
+
day_table.add_column("Title", no_wrap=False)
|
|
2009
|
+
day_table.add_column("Ep", style="yellow", width=6, no_wrap=True)
|
|
2010
|
+
|
|
2011
|
+
for e in entries:
|
|
2012
|
+
dt = datetime.datetime.fromtimestamp(e["airing_at"])
|
|
2013
|
+
time_label = dt.strftime("%I:%M %p")
|
|
2014
|
+
title = (e.get("title_english") or e.get("title_romaji") or "?")
|
|
2015
|
+
if len(title) > 55:
|
|
2016
|
+
title = title[:52] + "..."
|
|
2017
|
+
ep = e.get("episode")
|
|
2018
|
+
fmt = e.get("format", "")
|
|
2019
|
+
ep_label = f"E{ep}" if ep and fmt != "MOVIE" else "—"
|
|
2020
|
+
ready_dt = dt + datetime.timedelta(minutes=_est_delay(e.get("popularity", 0)))
|
|
2021
|
+
time_label = f"[cyan]{time_label}[/] ~[dim]{ready_dt.strftime('%I:%M %p')}[/]"
|
|
2022
|
+
day_table.add_row(time_label, title, ep_label)
|
|
2023
|
+
|
|
2024
|
+
day_panel = Panel(
|
|
2025
|
+
day_table,
|
|
2026
|
+
title=f"[bold {theme['accent']}]{_day_label(day)}[/bold {theme['accent']}]",
|
|
2027
|
+
border_style=theme["border"],
|
|
2028
|
+
padding=(0, 1),
|
|
2029
|
+
)
|
|
2030
|
+
console.print(day_panel)
|
|
2031
|
+
if i < len(sorted_days) - 1:
|
|
2032
|
+
console.print()
|
|
2033
|
+
|
|
2034
|
+
|
|
2035
|
+
def _schedule_impl():
|
|
2036
|
+
try:
|
|
2037
|
+
tmdb = _get_tmdb()
|
|
2038
|
+
except MissingAPIKey:
|
|
2039
|
+
_print_info("A TMDB API key is required for the 'schedule' command.")
|
|
2040
|
+
_print_info(" Run: cinnamon config set-api-key <key>")
|
|
2041
|
+
_print_info(" Or use: cinnamon schedule anime (no key needed)")
|
|
2042
|
+
return
|
|
2043
|
+
|
|
2044
|
+
if isinstance(tmdb, EmbedClient):
|
|
2045
|
+
_print_info("A TMDB API key is required for the 'schedule' command.")
|
|
2046
|
+
_print_info(" Run: cinnamon config set-api-key <key>")
|
|
2047
|
+
_print_info(" Or use: cinnamon schedule anime (no key needed)")
|
|
2048
|
+
return
|
|
2049
|
+
|
|
2050
|
+
theme = get_theme()
|
|
2051
|
+
console.print(Panel(
|
|
2052
|
+
f"[bold {theme['accent']}]Trending This Week[/bold {theme['accent']}]",
|
|
2053
|
+
border_style=theme["border"],
|
|
2054
|
+
))
|
|
2055
|
+
|
|
2056
|
+
table = Table(border_style="dim")
|
|
2057
|
+
table.add_column("Title")
|
|
2058
|
+
table.add_column("Date", style="cyan")
|
|
2059
|
+
table.add_column("Type", style="green")
|
|
2060
|
+
table.add_column("Rating", style="yellow")
|
|
2061
|
+
|
|
2062
|
+
tv_data = tmdb.get_trending("tv", "week")
|
|
2063
|
+
movie_data = tmdb.get_trending("movie", "week")
|
|
2064
|
+
|
|
2065
|
+
rows = []
|
|
2066
|
+
for item in tv_data.get("results", []):
|
|
2067
|
+
rows.append({
|
|
2068
|
+
"title": item.get("name", "?"),
|
|
2069
|
+
"date": item.get("first_air_date", ""),
|
|
2070
|
+
"type": "TV",
|
|
2071
|
+
"rating": item.get("vote_average", 0),
|
|
2072
|
+
})
|
|
2073
|
+
for item in movie_data.get("results", []):
|
|
2074
|
+
rows.append({
|
|
2075
|
+
"title": item.get("title", "?"),
|
|
2076
|
+
"date": item.get("release_date", ""),
|
|
2077
|
+
"type": "Movie",
|
|
2078
|
+
"rating": item.get("vote_average", 0),
|
|
2079
|
+
})
|
|
2080
|
+
|
|
2081
|
+
rows.sort(key=lambda r: r["rating"], reverse=True)
|
|
2082
|
+
|
|
2083
|
+
for r in rows:
|
|
2084
|
+
rating_str = f"{r['rating']:.1f}" if r["rating"] else "—"
|
|
2085
|
+
table.add_row(r["title"], r["date"][:10], r["type"], rating_str)
|
|
2086
|
+
|
|
2087
|
+
console.print(table)
|
|
2088
|
+
|
|
2089
|
+
|
|
1940
2090
|
# ---------------------------------------------------------------------------
|
|
1941
2091
|
# history
|
|
1942
2092
|
# ---------------------------------------------------------------------------
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.2.39"
|
|
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
|
|
File without changes
|