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.
Files changed (37) hide show
  1. {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/PKG-INFO +1 -1
  2. cinnamon_cli-0.2.40/cinnamon/__init__.py +1 -0
  3. {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/cinnamon/anilist.py +71 -0
  4. {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/cinnamon/cli.py +150 -0
  5. {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/cinnamon/tmdb.py +3 -0
  6. {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/cinnamon_cli.egg-info/PKG-INFO +1 -1
  7. {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/pyproject.toml +1 -1
  8. cinnamon_cli-0.2.39/cinnamon/__init__.py +0 -1
  9. {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/LICENSE +0 -0
  10. {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/README.md +0 -0
  11. {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/cinnamon/config.py +0 -0
  12. {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/cinnamon/downloads.py +0 -0
  13. {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/cinnamon/embedapi.py +0 -0
  14. {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/cinnamon/errors.py +0 -0
  15. {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/cinnamon/history.py +0 -0
  16. {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/cinnamon/player.py +0 -0
  17. {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/cinnamon/scrapers/__init__.py +0 -0
  18. {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/cinnamon/scrapers/_vidlink_crypto.py +0 -0
  19. {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/cinnamon/scrapers/anime.py +0 -0
  20. {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/cinnamon/scrapers/base.py +0 -0
  21. {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/cinnamon/scrapers/torrentio.py +0 -0
  22. {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/cinnamon/scrapers/vidsrc.py +0 -0
  23. {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/cinnamon/scrapers/webstream.py +0 -0
  24. {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/cinnamon/tui.py +0 -0
  25. {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/cinnamon_cli.egg-info/SOURCES.txt +0 -0
  26. {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/cinnamon_cli.egg-info/dependency_links.txt +0 -0
  27. {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/cinnamon_cli.egg-info/entry_points.txt +0 -0
  28. {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/cinnamon_cli.egg-info/requires.txt +0 -0
  29. {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/cinnamon_cli.egg-info/top_level.txt +0 -0
  30. {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/setup.cfg +0 -0
  31. {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/tests/test_directstream.py +0 -0
  32. {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/tests/test_directstream2.py +0 -0
  33. {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/tests/test_directstream3.py +0 -0
  34. {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/tests/test_directstream4.py +0 -0
  35. {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/tests/test_directstream5.py +0 -0
  36. {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/tests/test_directstream6.py +0 -0
  37. {cinnamon_cli-0.2.39 → cinnamon_cli-0.2.40}/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.39
3
+ Version: 0.2.40
4
4
  Summary: Watch Movies, TV shows and anime from your terminal.
5
5
  Author: pizza-droid
6
6
  License: CC BY-NC 4.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
  # ---------------------------------------------------------------------------
@@ -81,3 +81,6 @@ class TMDBClient:
81
81
 
82
82
  def search_movie(self, query):
83
83
  return self._request("/search/movie", {"query": query})
84
+
85
+ def get_trending(self, media_type="all", time_window="week"):
86
+ return self._request(f"/trending/{media_type}/{time_window}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cinnamon-cli
3
- Version: 0.2.39
3
+ Version: 0.2.40
4
4
  Summary: Watch Movies, TV shows and anime from your terminal.
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.39"
7
+ version = "0.2.40"
8
8
  description = "Watch Movies, TV shows and anime from your terminal."
9
9
  license = { text = "CC BY-NC 4.0" }
10
10
  readme = "README.md"
@@ -1 +0,0 @@
1
- __version__ = "0.2.39"
File without changes
File without changes
File without changes