kryten-webqueue 0.4.3__tar.gz → 0.4.5__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 (62) hide show
  1. kryten_webqueue-0.4.5/CHANGELOG.md +213 -0
  2. {kryten_webqueue-0.4.3 → kryten_webqueue-0.4.5}/PKG-INFO +1 -1
  3. {kryten_webqueue-0.4.3 → kryten_webqueue-0.4.5}/kryten_webqueue/queue/ordering.py +32 -12
  4. {kryten_webqueue-0.4.3 → kryten_webqueue-0.4.5}/kryten_webqueue/routes/queue.py +8 -2
  5. {kryten_webqueue-0.4.3 → kryten_webqueue-0.4.5}/pyproject.toml +1 -1
  6. {kryten_webqueue-0.4.3 → kryten_webqueue-0.4.5}/.github/workflows/python-publish.yml +0 -0
  7. {kryten_webqueue-0.4.3 → kryten_webqueue-0.4.5}/.github/workflows/release.yml +0 -0
  8. {kryten_webqueue-0.4.3 → kryten_webqueue-0.4.5}/.gitignore +0 -0
  9. {kryten_webqueue-0.4.3 → kryten_webqueue-0.4.5}/README.md +0 -0
  10. {kryten_webqueue-0.4.3 → kryten_webqueue-0.4.5}/config.example.json +0 -0
  11. {kryten_webqueue-0.4.3 → kryten_webqueue-0.4.5}/deploy/kryten-webqueue.service +0 -0
  12. {kryten_webqueue-0.4.3 → kryten_webqueue-0.4.5}/deploy/nginx-queue.conf +0 -0
  13. {kryten_webqueue-0.4.3 → kryten_webqueue-0.4.5}/docs/IMPLEMENTATION_SPEC.md +0 -0
  14. {kryten_webqueue-0.4.3 → kryten_webqueue-0.4.5}/docs/IMPL_API_GATE.md +0 -0
  15. {kryten_webqueue-0.4.3 → kryten_webqueue-0.4.5}/docs/IMPL_ECONOMY.md +0 -0
  16. {kryten_webqueue-0.4.3 → kryten_webqueue-0.4.5}/docs/IMPL_KRYTEN_PY.md +0 -0
  17. {kryten_webqueue-0.4.3 → kryten_webqueue-0.4.5}/docs/IMPL_ROBOT.md +0 -0
  18. {kryten_webqueue-0.4.3 → kryten_webqueue-0.4.5}/docs/PRE_PLAN_GAPS.md +0 -0
  19. {kryten_webqueue-0.4.3 → kryten_webqueue-0.4.5}/docs/PRODUCT_PLAN.md +0 -0
  20. {kryten_webqueue-0.4.3 → kryten_webqueue-0.4.5}/kryten_webqueue/__init__.py +0 -0
  21. {kryten_webqueue-0.4.3 → kryten_webqueue-0.4.5}/kryten_webqueue/__main__.py +0 -0
  22. {kryten_webqueue-0.4.3 → kryten_webqueue-0.4.5}/kryten_webqueue/api_gate/__init__.py +0 -0
  23. {kryten_webqueue-0.4.3 → kryten_webqueue-0.4.5}/kryten_webqueue/api_gate/client.py +0 -0
  24. {kryten_webqueue-0.4.3 → kryten_webqueue-0.4.5}/kryten_webqueue/app.py +0 -0
  25. {kryten_webqueue-0.4.3 → kryten_webqueue-0.4.5}/kryten_webqueue/auth/__init__.py +0 -0
  26. {kryten_webqueue-0.4.3 → kryten_webqueue-0.4.5}/kryten_webqueue/auth/otp.py +0 -0
  27. {kryten_webqueue-0.4.3 → kryten_webqueue-0.4.5}/kryten_webqueue/auth/rate_limit.py +0 -0
  28. {kryten_webqueue-0.4.3 → kryten_webqueue-0.4.5}/kryten_webqueue/auth/session.py +0 -0
  29. {kryten_webqueue-0.4.3 → kryten_webqueue-0.4.5}/kryten_webqueue/catalog/__init__.py +0 -0
  30. {kryten_webqueue-0.4.3 → kryten_webqueue-0.4.5}/kryten_webqueue/catalog/db.py +0 -0
  31. {kryten_webqueue-0.4.3 → kryten_webqueue-0.4.5}/kryten_webqueue/catalog/images.py +0 -0
  32. {kryten_webqueue-0.4.3 → kryten_webqueue-0.4.5}/kryten_webqueue/catalog/sync.py +0 -0
  33. {kryten_webqueue-0.4.3 → kryten_webqueue-0.4.5}/kryten_webqueue/config.py +0 -0
  34. {kryten_webqueue-0.4.3 → kryten_webqueue-0.4.5}/kryten_webqueue/playlists/__init__.py +0 -0
  35. {kryten_webqueue-0.4.3 → kryten_webqueue-0.4.5}/kryten_webqueue/playlists/fire.py +0 -0
  36. {kryten_webqueue-0.4.3 → kryten_webqueue-0.4.5}/kryten_webqueue/playlists/importer.py +0 -0
  37. {kryten_webqueue-0.4.3 → kryten_webqueue-0.4.5}/kryten_webqueue/playlists/scheduler.py +0 -0
  38. {kryten_webqueue-0.4.3 → kryten_webqueue-0.4.5}/kryten_webqueue/queue/__init__.py +0 -0
  39. {kryten_webqueue-0.4.3 → kryten_webqueue-0.4.5}/kryten_webqueue/queue/poller.py +0 -0
  40. {kryten_webqueue-0.4.3 → kryten_webqueue-0.4.5}/kryten_webqueue/queue/shadow.py +0 -0
  41. {kryten_webqueue-0.4.3 → kryten_webqueue-0.4.5}/kryten_webqueue/routes/__init__.py +0 -0
  42. {kryten_webqueue-0.4.3 → kryten_webqueue-0.4.5}/kryten_webqueue/routes/admin_playlists.py +0 -0
  43. {kryten_webqueue-0.4.3 → kryten_webqueue-0.4.5}/kryten_webqueue/routes/admin_queue.py +0 -0
  44. {kryten_webqueue-0.4.3 → kryten_webqueue-0.4.5}/kryten_webqueue/routes/admin_schedules.py +0 -0
  45. {kryten_webqueue-0.4.3 → kryten_webqueue-0.4.5}/kryten_webqueue/routes/auth.py +0 -0
  46. {kryten_webqueue-0.4.3 → kryten_webqueue-0.4.5}/kryten_webqueue/routes/catalog.py +0 -0
  47. {kryten_webqueue-0.4.3 → kryten_webqueue-0.4.5}/kryten_webqueue/routes/pages.py +0 -0
  48. {kryten_webqueue-0.4.3 → kryten_webqueue-0.4.5}/kryten_webqueue/routes/user.py +0 -0
  49. {kryten_webqueue-0.4.3 → kryten_webqueue-0.4.5}/kryten_webqueue/static/css/main.css +0 -0
  50. {kryten_webqueue-0.4.3 → kryten_webqueue-0.4.5}/kryten_webqueue/static/js/main.js +0 -0
  51. {kryten_webqueue-0.4.3 → kryten_webqueue-0.4.5}/kryten_webqueue/templates/admin/index.html +0 -0
  52. {kryten_webqueue-0.4.3 → kryten_webqueue-0.4.5}/kryten_webqueue/templates/admin/playlists.html +0 -0
  53. {kryten_webqueue-0.4.3 → kryten_webqueue-0.4.5}/kryten_webqueue/templates/admin/queue_mgmt.html +0 -0
  54. {kryten_webqueue-0.4.3 → kryten_webqueue-0.4.5}/kryten_webqueue/templates/admin/schedules.html +0 -0
  55. {kryten_webqueue-0.4.3 → kryten_webqueue-0.4.5}/kryten_webqueue/templates/auth/login.html +0 -0
  56. {kryten_webqueue-0.4.3 → kryten_webqueue-0.4.5}/kryten_webqueue/templates/base.html +0 -0
  57. {kryten_webqueue-0.4.3 → kryten_webqueue-0.4.5}/kryten_webqueue/templates/catalog/browse.html +0 -0
  58. {kryten_webqueue-0.4.3 → kryten_webqueue-0.4.5}/kryten_webqueue/templates/queue/index.html +0 -0
  59. {kryten_webqueue-0.4.3 → kryten_webqueue-0.4.5}/kryten_webqueue/templates/user/dashboard.html +0 -0
  60. {kryten_webqueue-0.4.3 → kryten_webqueue-0.4.5}/kryten_webqueue/ws/__init__.py +0 -0
  61. {kryten_webqueue-0.4.3 → kryten_webqueue-0.4.5}/kryten_webqueue/ws/handler.py +0 -0
  62. {kryten_webqueue-0.4.3 → kryten_webqueue-0.4.5}/kryten_webqueue/ws/manager.py +0 -0
@@ -0,0 +1,213 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.4.5] - 2026-06-04
9
+
10
+ ### Fixed
11
+
12
+ - **Unhandled `HTTPStatusError` from `playlist_add`** — Both `insert_pay_queue` and `insert_pay_playnext` now catch `httpx.HTTPStatusError` thrown when the api-gate returns a non-2xx response for `/playlist/add`. On failure the spend is refunded (best-effort) and a `{success: False, error: "Failed to add to playlist"}` dict is returned so the route can surface a 400 to the browser instead of crashing with an unhandled 500
13
+
14
+ [0.4.5]: https://github.com/grobertson/kryten-webqueue/releases/tag/v0.4.5
15
+
16
+ ## [0.4.4] - 2026-06-04
17
+
18
+ ### Fixed
19
+
20
+ - **`cost_z` field name** — Economy preview response uses `cost_z` not `cost`/`z_cost`; the wrong key names caused every queue/add and queue/playnext call to raise HTTP 502 "Cost preview returned no cost value"
21
+ - **Eligibility not surfaced** — Queue routes now check `preview.get("available")` before attempting the spend; when `False`, the `error_code` from the preview (`cooldown_active`, `daily_limit_reached`, `insufficient_balance`, `blackout_active`) is returned as HTTP 400 so the UI can display a meaningful message
22
+
23
+ [0.4.4]: https://github.com/grobertson/kryten-webqueue/releases/tag/v0.4.4
24
+
25
+ ## [0.4.3] - 2026-06-04
26
+
27
+ ### Fixed
28
+
29
+ - **Phantom `success` checks on economy responses** — `api-gate` economy routes use `_unwrap()` which strips the outer `{success, data}` envelope before returning. Both `/queue/add` and `/queue/playnext` were checking `preview.get("success")` on the already-stripped data dict, which was always `None` → always HTTP 400 "Cost preview failed". Removed the checks entirely; `raise_for_status()` in the httpx client already propagates non-2xx responses as `HTTPStatusError`
30
+ - **Spend error handling** — Replaced `spend_result.get("success")` check with `try/except httpx.HTTPStatusError` around the `queue_spend` call for a clean error dict on failure
31
+
32
+ [0.4.3]: https://github.com/grobertson/kryten-webqueue/releases/tag/v0.4.3
33
+
34
+ ## [0.4.2] - 2026-06-03
35
+
36
+ ### Fixed
37
+
38
+ - **`/images/` 404s in development** — Mounted `StaticFiles` at `/images` pointing to `config.image_dir` in `app.py`; previously only the nginx alias served these in production, but uvicorn had no route so any direct or dev request returned 404
39
+
40
+ [0.4.2]: https://github.com/grobertson/kryten-webqueue/releases/tag/v0.4.2
41
+
42
+ ## [0.4.1] - 2026-06-03
43
+
44
+ ### Added
45
+
46
+ - **Parallel TMDB movie + TV search** — Replaced `search/multi` (which mixed in person results) with concurrent `search/movie` + `search/tv` calls; picks highest-popularity result across both
47
+ - **Title cleaning** — `_clean_title()` strips year suffixes, episode tags (`S01E02`), and resolution noise (`1080p`, `BluRay`, `x264`, etc.) before retrying a failed lookup
48
+ - **Year extraction** — Parsed year passed to TMDB as the `year` filter when available
49
+ - **`w780` poster size** — Upgraded from `w500` for higher-quality cover art
50
+ - **Thumbnail fallback** — If no external art is found, falls back to the MediaCMS thumbnail URL already stored in the catalog
51
+ - **DB migration 2** — Clears all cached `cover_art_path` / `cover_art_source` rows on first startup to force a full repoll with the improved resolver
52
+
53
+ ### Changed
54
+
55
+ - **OMDB lookup** — Now passes extracted year to improve match accuracy
56
+
57
+ [0.4.1]: https://github.com/grobertson/kryten-webqueue/releases/tag/v0.4.1
58
+
59
+ ## [0.4.0] - 2026-06-02
60
+
61
+ ### Added
62
+
63
+ - **Pagination** — Browse and search pages use page-number controls; `browse_count` / `search_count` added to db for total page calculation
64
+ - **Admin stub routes** — `/admin/playlists`, `/admin/schedules`, `/admin/queue-mgmt` page routes with stub templates
65
+ - **Queue page improvements** — `qi-info`/`qi-right` layout, total duration, remaining time, initial HTTP load, "queued by" metadata
66
+ - **CSS additions** — `.user-header`, `.user-rank`, `.user-online`, `.balance-meta`, `.tx-time`, `.qi-info`, `.qi-meta`, `.qi-right`, `.queue-summary`, `.np-meta`, `.np-times`
67
+
68
+ ### Fixed
69
+
70
+ - **Cover art paths** — Static image references updated from `/static/images` to `/images` (nginx alias)
71
+ - **TMDB search** — Filter to prefer `poster_path` results over person `profile_path` matches
72
+ - **Queue button error handling** — JS now surfaces API error messages inline rather than failing silently
73
+
74
+ ### Changed
75
+
76
+ - **User dashboard** — Shows rank name, Z balance with comma formatting (`toLocaleString`), `describeTx` helper for transaction labels, transaction dates
77
+
78
+ [0.4.0]: https://github.com/grobertson/kryten-webqueue/releases/tag/v0.4.0
79
+
80
+ ## [0.3.2] - 2026-06-02
81
+
82
+ ### Added
83
+
84
+ - **Cover art resolver logging** — INFO/WARNING log entries to diagnose silent failures in TMDB/OMDB lookups
85
+
86
+ [0.3.2]: https://github.com/grobertson/kryten-webqueue/releases/tag/v0.3.2
87
+
88
+ ## [0.3.1] - 2026-06-02
89
+
90
+ ### Fixed
91
+
92
+ - **Cover art rate limiting** — Added 250ms delay between API calls to avoid hitting TMDB/OMDB rate limits
93
+
94
+ [0.3.1]: https://github.com/grobertson/kryten-webqueue/releases/tag/v0.3.1
95
+
96
+ ## [0.3.0] - 2026-06-02
97
+
98
+ ### Added
99
+
100
+ - **TMDB/OMDB cover art** — Fetches and caches box art from TMDB (primary) and OMDB (fallback) during catalog sync; MediaCMS thumbnail is last resort
101
+
102
+ [0.3.0]: https://github.com/grobertson/kryten-webqueue/releases/tag/v0.3.0
103
+
104
+ ## [0.2.9] - 2026-06-02
105
+
106
+ ### Added
107
+
108
+ - **MediaCMS thumbnail fallback** — Shows MediaCMS-sourced thumbnail as cover art when no external art is available
109
+
110
+ [0.2.9]: https://github.com/grobertson/kryten-webqueue/releases/tag/v0.2.9
111
+
112
+ ## [0.2.8] - 2026-06-02
113
+
114
+ ### Fixed
115
+
116
+ - **`manifest_url`** — Now uses the watch page URL instead of the raw media URL
117
+ - **Catalog search route** — Added missing template handler for `/catalog/search`
118
+
119
+ [0.2.8]: https://github.com/grobertson/kryten-webqueue/releases/tag/v0.2.8
120
+
121
+ ## [0.2.7] - 2026-06-02
122
+
123
+ ### Fixed
124
+
125
+ - **Full catalog sync** — Switched from `/media` (capped at 1000 items) to `/manage_media` endpoint for complete pagination
126
+
127
+ [0.2.7]: https://github.com/grobertson/kryten-webqueue/releases/tag/v0.2.7
128
+
129
+ ## [0.2.6] - 2026-06-02
130
+
131
+ ### Fixed
132
+
133
+ - **Catalog pagination** — Follow `next` URL from MediaCMS API response instead of constructing page numbers manually
134
+
135
+ [0.2.6]: https://github.com/grobertson/kryten-webqueue/releases/tag/v0.2.6
136
+
137
+ ## [0.2.5] - 2026-06-02
138
+
139
+ ### Fixed
140
+
141
+ - **Network error messages** — Clear per-host error messages; distinguish DNS failure, connection refused, and timeout
142
+
143
+ [0.2.5]: https://github.com/grobertson/kryten-webqueue/releases/tag/v0.2.5
144
+
145
+ ## [0.2.4] - 2026-06-02
146
+
147
+ ### Fixed
148
+
149
+ - **MediaCMS URL misconfiguration** — Strip trailing `/api/v1` from `mediacms_url` if accidentally included; log 4xx response URL and body for debugging
150
+
151
+ [0.2.4]: https://github.com/grobertson/kryten-webqueue/releases/tag/v0.2.4
152
+
153
+ ## [0.2.3] - 2026-06-01
154
+
155
+ ### Fixed
156
+
157
+ - **Catalog sync error logging** — Log full traceback on sync failure instead of message-only
158
+
159
+ [0.2.3]: https://github.com/grobertson/kryten-webqueue/releases/tag/v0.2.3
160
+
161
+ ## [0.2.2] - 2026-06-01
162
+
163
+ ### Fixed
164
+
165
+ - **Catalog sync startup timing** — Run catalog sync immediately on startup; add `INFO`-level progress logging
166
+
167
+ [0.2.2]: https://github.com/grobertson/kryten-webqueue/releases/tag/v0.2.2
168
+
169
+ ## [0.2.1] - 2026-06-01
170
+
171
+ ### Fixed
172
+
173
+ - **Starlette compatibility** — Updated `TemplateResponse` calls to Starlette ≥0.36 signature `(request, name, context)`
174
+
175
+ [0.2.1]: https://github.com/grobertson/kryten-webqueue/releases/tag/v0.2.1
176
+
177
+ ## [0.2.0] - 2026-05-31
178
+
179
+ ### Added
180
+
181
+ - **`main()` entrypoint** — Allows `kryten-webqueue` console script invocation
182
+ - **Deploy files in wheel** — nginx config and systemd unit included as `shared-data` for `pipx`-based installs
183
+
184
+ ### Fixed
185
+
186
+ - **nginx TLS hardening** — `http2` directive syntax fixed; proxy keep-alive headers added
187
+
188
+ [0.2.0]: https://github.com/grobertson/kryten-webqueue/releases/tag/v0.2.0
189
+
190
+ ## [0.1.6] - 2026-05-31
191
+
192
+ ### Changed
193
+
194
+ - **CI** — Restored original two-workflow pattern (release.yml + python-publish.yml)
195
+
196
+ [0.1.6]: https://github.com/grobertson/kryten-webqueue/releases/tag/v0.1.6
197
+
198
+ ## [0.1.3] - 2026-05-31
199
+
200
+ ### Fixed
201
+
202
+ - **Duration parsing** — Parse `HH:MM:SS` strings to seconds
203
+ - **Duration/currentTime coercion** — Coerce API string values to `float`
204
+
205
+ [0.1.3]: https://github.com/grobertson/kryten-webqueue/releases/tag/v0.1.3
206
+
207
+ ## [0.1.0] - 2026-05-31
208
+
209
+ ### Added
210
+
211
+ - Initial release: FastAPI app with catalog sync from MediaCMS, SQLite catalog DB, queue/playnext routes, session auth, Jinja2 templates for browse/search/queue/user pages, nginx + systemd deploy configs
212
+
213
+ [0.1.0]: https://github.com/grobertson/kryten-webqueue/releases/tag/v0.1.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kryten-webqueue
3
- Version: 0.4.3
3
+ Version: 0.4.5
4
4
  Summary: Netflix/Tubi-style catalog browser and pay-to-play queue management for CyTube
5
5
  Author: grobertson
6
6
  License-Expression: MIT
@@ -44,14 +44,24 @@ async def insert_pay_queue(
44
44
  position = "end" if not last_pay_uid else str(last_pay_uid)
45
45
 
46
46
  # Add to CyTube playlist
47
- add_result = await api_gate.playlist_add(
48
- media_type=media_type,
49
- media_id=media_id,
50
- position=position,
51
- )
47
+ try:
48
+ add_result = await api_gate.playlist_add(
49
+ media_type=media_type,
50
+ media_id=media_id,
51
+ position=position,
52
+ )
53
+ except httpx.HTTPStatusError:
54
+ try:
55
+ await api_gate.queue_refund(username=username, request_id=request_id, reason="playlist_add_failed")
56
+ except Exception:
57
+ pass
58
+ return {"success": False, "error": "Failed to add to playlist"}
52
59
  if not add_result.get("success"):
53
60
  # Refund on failure
54
- await api_gate.queue_refund(username=username, request_id=request_id, reason="add_failed")
61
+ try:
62
+ await api_gate.queue_refund(username=username, request_id=request_id, reason="add_failed")
63
+ except Exception:
64
+ pass
55
65
  return {"success": False, "error": "Failed to add to playlist"}
56
66
 
57
67
  uid = add_result["uid"]
@@ -126,13 +136,23 @@ async def insert_pay_playnext(
126
136
  return {"success": False, "error": f"Spend failed: {exc.response.status_code}"}
127
137
 
128
138
  # Add to CyTube playlist at prepend position
129
- add_result = await api_gate.playlist_add(
130
- media_type=media_type,
131
- media_id=media_id,
132
- position="end",
133
- )
139
+ try:
140
+ add_result = await api_gate.playlist_add(
141
+ media_type=media_type,
142
+ media_id=media_id,
143
+ position="end",
144
+ )
145
+ except httpx.HTTPStatusError:
146
+ try:
147
+ await api_gate.queue_refund(username=username, request_id=request_id, reason="playlist_add_failed")
148
+ except Exception:
149
+ pass
150
+ return {"success": False, "error": "Failed to add to playlist"}
134
151
  if not add_result.get("success"):
135
- await api_gate.queue_refund(username=username, request_id=request_id, reason="add_failed")
152
+ try:
153
+ await api_gate.queue_refund(username=username, request_id=request_id, reason="add_failed")
154
+ except Exception:
155
+ pass
136
156
  return {"success": False, "error": "Failed to add to playlist"}
137
157
 
138
158
  uid = add_result["uid"]
@@ -43,7 +43,10 @@ async def add_to_queue(request: Request, user: dict = Depends(get_current_user))
43
43
  duration_sec=item["duration_sec"],
44
44
  tier=tier,
45
45
  )
46
- z_cost = preview.get("cost") or preview.get("z_cost")
46
+ if not preview.get("available", True):
47
+ error_code = preview.get("error_code") or "unavailable"
48
+ raise HTTPException(400, error_code)
49
+ z_cost = preview.get("cost_z")
47
50
  if z_cost is None:
48
51
  raise HTTPException(502, "Cost preview returned no cost value")
49
52
 
@@ -95,7 +98,10 @@ async def play_next(request: Request, user: dict = Depends(get_current_user)):
95
98
  duration_sec=item["duration_sec"],
96
99
  tier=tier,
97
100
  )
98
- z_cost = preview.get("cost") or preview.get("z_cost")
101
+ if not preview.get("available", True):
102
+ error_code = preview.get("error_code") or "unavailable"
103
+ raise HTTPException(400, error_code)
104
+ z_cost = preview.get("cost_z")
99
105
  if z_cost is None:
100
106
  raise HTTPException(502, "Cost preview returned no cost value")
101
107
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "kryten-webqueue"
3
- version = "0.4.3"
3
+ version = "0.4.5"
4
4
  description = "Netflix/Tubi-style catalog browser and pay-to-play queue management for CyTube"
5
5
  readme = "README.md"
6
6
  license = "MIT"