kryten-webqueue 0.45.0__tar.gz → 0.45.1__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 (209) hide show
  1. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/CHANGELOG.md +13 -0
  2. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/PKG-INFO +1 -1
  3. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/config.example.json +2 -1
  4. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/config.py +6 -1
  5. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/jobs/motd_publish.py +2 -1
  6. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/motd/builder.py +27 -5
  7. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/routes/admin_motd.py +4 -1
  8. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/pyproject.toml +1 -1
  9. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/tests/test_motd_publish.py +38 -0
  10. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/.github/workflows/gitleaks.yml +0 -0
  11. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/.github/workflows/python-publish.yml +0 -0
  12. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/.github/workflows/release.yml +0 -0
  13. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/.gitignore +0 -0
  14. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/.gitleaks.toml +0 -0
  15. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/.pre-commit-config.yaml +0 -0
  16. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/AGENT-WORKFLOW-GUIDE.md +0 -0
  17. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/AGENTS.md +0 -0
  18. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/README.md +0 -0
  19. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/deploy/kryten-webqueue.service +0 -0
  20. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/deploy/nginx-queue.conf +0 -0
  21. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/docs/IMPLEMENTATION_SPEC.md +0 -0
  22. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/docs/IMPL_API_GATE.md +0 -0
  23. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/docs/IMPL_ECONOMY.md +0 -0
  24. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/docs/IMPL_KRYTEN_PY.md +0 -0
  25. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/docs/IMPL_ROBOT.md +0 -0
  26. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/docs/Kryten-Economy Service Quality Survey Report at v0.13.0.md +0 -0
  27. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/docs/Kryten-Webqueue Service Quality Survey Report at v0.27.1.md +0 -0
  28. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/docs/Log for incorrect promo insertion problem.txt +0 -0
  29. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/docs/PLAN_PRESENCE_AND_PROMOS.md +0 -0
  30. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/docs/PRD_TMDB_LOCAL_INDEX.md +0 -0
  31. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/docs/PRE_PLAN_GAPS.md +0 -0
  32. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/docs/PRODUCT_PLAN.md +0 -0
  33. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/docs/PUBLIC_API.md +0 -0
  34. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/docs/SPEC-Visual-UX-Sprint-August-2026.md +0 -0
  35. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/docs/SPEC_ADMIN_ITEM_EDITOR.md +0 -0
  36. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/docs/SPEC_CATALOG_ENRICHMENT_PIPELINE.md +0 -0
  37. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/docs/SPEC_EMOTE_REHOST.md +0 -0
  38. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/docs/SPEC_JOBS_AND_BROWSE.md +0 -0
  39. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/docs/SPEC_TMDB_LOCAL_INDEX.md +0 -0
  40. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/docs/UX_POLISH_PLAN.md +0 -0
  41. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/__init__.py +0 -0
  42. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/__main__.py +0 -0
  43. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/api_gate/__init__.py +0 -0
  44. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/api_gate/client.py +0 -0
  45. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/app.py +0 -0
  46. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/auth/__init__.py +0 -0
  47. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/auth/device_keys.py +0 -0
  48. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/auth/otp.py +0 -0
  49. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/auth/rate_limit.py +0 -0
  50. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/auth/session.py +0 -0
  51. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/catalog/__init__.py +0 -0
  52. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/catalog/db/__init__.py +0 -0
  53. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/catalog/db/_blackouts.py +0 -0
  54. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/catalog/db/_catalog.py +0 -0
  55. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/catalog/db/_connection.py +0 -0
  56. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/catalog/db/_devices.py +0 -0
  57. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/catalog/db/_enrichment.py +0 -0
  58. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/catalog/db/_feedback.py +0 -0
  59. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/catalog/db/_fetch_queue.py +0 -0
  60. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/catalog/db/_motd.py +0 -0
  61. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/catalog/db/_people.py +0 -0
  62. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/catalog/db/_playlists.py +0 -0
  63. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/catalog/db/_queue.py +0 -0
  64. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/catalog/db/_watchlist.py +0 -0
  65. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/catalog/enrichment/__init__.py +0 -0
  66. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/catalog/enrichment/classify.py +0 -0
  67. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/catalog/enrichment/normalise.py +0 -0
  68. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/catalog/enrichment/pipeline.py +0 -0
  69. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/catalog/enrichment/providers.py +0 -0
  70. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/catalog/enrichment/report.py +0 -0
  71. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/catalog/enrichment/steps/__init__.py +0 -0
  72. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/catalog/enrichment/steps/art.py +0 -0
  73. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/catalog/enrichment/steps/categories.py +0 -0
  74. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/catalog/enrichment/steps/identify.py +0 -0
  75. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/catalog/enrichment/steps/meta.py +0 -0
  76. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/catalog/enrichment/steps/sync.py +0 -0
  77. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/catalog/enrichment/steps/tags.py +0 -0
  78. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/catalog/enrichment/steps/title.py +0 -0
  79. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/catalog/images.py +0 -0
  80. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/catalog/mediacms.py +0 -0
  81. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/catalog/sync.py +0 -0
  82. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/catalog/tmdb_index/__init__.py +0 -0
  83. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/catalog/tmdb_index/_schema.py +0 -0
  84. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/catalog/tmdb_index/_textmatch.py +0 -0
  85. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/catalog/tmdb_index/_ttscrape.py +0 -0
  86. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/catalog/tmdb_index/builder.py +0 -0
  87. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/catalog/tmdb_index/coverage.py +0 -0
  88. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/catalog/tmdb_index/index.py +0 -0
  89. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/integrations/__init__.py +0 -0
  90. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/integrations/cmsutils/__init__.py +0 -0
  91. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/integrations/cmsutils/_common.py +0 -0
  92. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/integrations/cmsutils/blackout_scan.py +0 -0
  93. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/integrations/cmsutils/enrichmeta.py +0 -0
  94. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/integrations/cmsutils/enrichtitles.py +0 -0
  95. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/integrations/cmsutils/enrichtv.py +0 -0
  96. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/integrations/cmsutils/fetchurls.py +0 -0
  97. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/integrations/cmsutils/motdposters.py +0 -0
  98. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/integrations/cmsutils/playedmovies.py +0 -0
  99. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/integrations/ytpipe/__init__.py +0 -0
  100. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/integrations/ytpipe/downloader.py +0 -0
  101. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/jobs/__init__.py +0 -0
  102. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/jobs/fetchurls_auth.py +0 -0
  103. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/jobs/job_scheduler.py +0 -0
  104. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/jobs/log_capture.py +0 -0
  105. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/jobs/manager.py +0 -0
  106. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/jobs/rehost_emotes.py +0 -0
  107. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/jobs/tasks.py +0 -0
  108. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/logging_config.py +0 -0
  109. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/motd/__init__.py +0 -0
  110. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/motd/render.py +0 -0
  111. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/playlists/__init__.py +0 -0
  112. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/playlists/bulk_add.py +0 -0
  113. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/playlists/fire.py +0 -0
  114. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/playlists/importer.py +0 -0
  115. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/playlists/ordering.py +0 -0
  116. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/playlists/scheduler.py +0 -0
  117. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/promos/__init__.py +0 -0
  118. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/promos/director.py +0 -0
  119. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/queue/__init__.py +0 -0
  120. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/queue/completion.py +0 -0
  121. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/queue/ordering.py +0 -0
  122. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/queue/poller.py +0 -0
  123. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/queue/presence.py +0 -0
  124. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/queue/race_poller.py +0 -0
  125. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/queue/shadow.py +0 -0
  126. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/routes/__init__.py +0 -0
  127. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/routes/admin_catalog.py +0 -0
  128. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/routes/admin_feedback.py +0 -0
  129. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/routes/admin_job_schedules.py +0 -0
  130. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/routes/admin_jobs.py +0 -0
  131. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/routes/admin_moderation.py +0 -0
  132. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/routes/admin_playlists.py +0 -0
  133. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/routes/admin_promos.py +0 -0
  134. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/routes/admin_queue.py +0 -0
  135. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/routes/admin_schedules.py +0 -0
  136. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/routes/auth.py +0 -0
  137. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/routes/catalog.py +0 -0
  138. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/routes/devices.py +0 -0
  139. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/routes/feedback.py +0 -0
  140. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/routes/pages.py +0 -0
  141. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/routes/public_api.py +0 -0
  142. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/routes/queue.py +0 -0
  143. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/routes/user.py +0 -0
  144. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/static/css/main.css +0 -0
  145. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/static/js/admin-motd.js +0 -0
  146. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/static/js/admin-playlists.js +0 -0
  147. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/static/js/admin-promos.js +0 -0
  148. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/static/js/admin-queue-mgmt.js +0 -0
  149. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/static/js/admin-schedules.js +0 -0
  150. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/static/js/admin.js +0 -0
  151. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/static/js/keybindings.js +0 -0
  152. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/static/js/main.js +0 -0
  153. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/templates/admin/index.html +0 -0
  154. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/templates/admin/motd.html +0 -0
  155. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/templates/admin/playlists.html +0 -0
  156. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/templates/admin/promos.html +0 -0
  157. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/templates/admin/queue_mgmt.html +0 -0
  158. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/templates/admin/schedules.html +0 -0
  159. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/templates/auth/login.html +0 -0
  160. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/templates/base.html +0 -0
  161. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/templates/catalog/browse.html +0 -0
  162. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/templates/catalog/edit_item_modal.html +0 -0
  163. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/templates/catalog/item_detail.html +0 -0
  164. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/templates/catalog/item_not_found.html +0 -0
  165. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/templates/feedback/index.html +0 -0
  166. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/templates/motd/channel_z.html +0 -0
  167. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/templates/queue/index.html +0 -0
  168. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/templates/race.html +0 -0
  169. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/templates/user/dashboard.html +0 -0
  170. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/templates/user/link_device.html +0 -0
  171. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/templates/user/my_list.html +0 -0
  172. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/ws/__init__.py +0 -0
  173. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/ws/handler.py +0 -0
  174. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/kryten_webqueue/ws/manager.py +0 -0
  175. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/scripts/AGENTS.md +0 -0
  176. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/scripts/patch_mediacms_bulk_facets.py +0 -0
  177. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/scripts/patch_mediacms_tags.py +0 -0
  178. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/scripts/trigger_enrich.py +0 -0
  179. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/tests/__init__.py +0 -0
  180. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/tests/test_catalog_blackout.py +0 -0
  181. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/tests/test_catalog_delete.py +0 -0
  182. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/tests/test_classify.py +0 -0
  183. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/tests/test_config_persistence.py +0 -0
  184. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/tests/test_device_linking.py +0 -0
  185. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/tests/test_feedback.py +0 -0
  186. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/tests/test_fetch_queue_pacing.py +0 -0
  187. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/tests/test_fetchurls_sharepoint.py +0 -0
  188. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/tests/test_identify_step.py +0 -0
  189. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/tests/test_job_logs_and_queue_visibility.py +0 -0
  190. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/tests/test_people_facets.py +0 -0
  191. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/tests/test_phase1.py +0 -0
  192. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/tests/test_phase2_jobs.py +0 -0
  193. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/tests/test_phase3_jobs.py +0 -0
  194. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/tests/test_phase4_live_fixes.py +0 -0
  195. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/tests/test_playlist_import.py +0 -0
  196. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/tests/test_presence_refund.py +0 -0
  197. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/tests/test_promo_director.py +0 -0
  198. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/tests/test_promo_pool_exclusion.py +0 -0
  199. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/tests/test_providers.py +0 -0
  200. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/tests/test_queue_announce.py +0 -0
  201. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/tests/test_queue_preview_fallback.py +0 -0
  202. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/tests/test_race_poller.py +0 -0
  203. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/tests/test_recently_played_hide.py +0 -0
  204. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/tests/test_save_results_to_playlist.py +0 -0
  205. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/tests/test_schedule_lock.py +0 -0
  206. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/tests/test_search_facets.py +0 -0
  207. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/tests/test_title_normalization.py +0 -0
  208. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/tests/test_tmdb_index.py +0 -0
  209. {kryten_webqueue-0.45.0 → kryten_webqueue-0.45.1}/tests/test_watchlist.py +0 -0
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.45.1] - 2026-09-10
4
+
5
+ ### Fixed
6
+
7
+ - **MOTD mystery boxes now use the branded placeholder art** the catalog browse
8
+ view already draws from (`placeholder_dir`, served under `/images`), picking a
9
+ different tape per slot the way browse varies it per tile. The 0.45.0 default
10
+ pointed at a `motd_boxes/mystery-box.jpg` that doesn't exist, so every
11
+ unresolved slot would have rendered a broken image. New
12
+ `motd.mystery_box_base_url` absolutizes those URLs for CyTube, which renders
13
+ the MOTD off-site; `motd.mystery_box_url` is now an empty-by-default fallback
14
+ used only when no branded placeholders are installed.
15
+
3
16
  ## [0.45.0] - 2026-09-10
4
17
 
5
18
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: kryten-webqueue
3
- Version: 0.45.0
3
+ Version: 0.45.1
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
@@ -95,7 +95,8 @@
95
95
  "banner_url": "https://i.postimg.cc/jdr2mR8Y/1562479186-8-channel-Ztitlenew.png",
96
96
  "headline": "CHANNEL Z WEEKEND MOVIE PLAYLIST ({dates})",
97
97
  "showtime": "6pm ET",
98
- "mystery_box_url": "https://www.dropsugar.co/static/motd_boxes/mystery-box.jpg",
98
+ "mystery_box_base_url": "https://queue.dropsugar.co",
99
+ "mystery_box_url": "",
99
100
  "mystery_box_href": "https://queue.dropsugar.co/",
100
101
  "upload_max_bytes": 5242880,
101
102
  "links": [
@@ -47,7 +47,12 @@ class MOTDConfig(BaseModel):
47
47
  # {dates} is substituted with the weekend showtimes (e.g. "8/14 @ 6pm ET & ...").
48
48
  headline: str = "CHANNEL Z WEEKEND MOVIE PLAYLIST ({dates})"
49
49
  showtime: str = "6pm ET"
50
- mystery_box_url: str = "https://www.dropsugar.co/static/motd_boxes/mystery-box.jpg"
50
+ # Mystery boxes are drawn from the same branded placeholder pool the browse
51
+ # view uses (served from the webqueue /images mount); this absolutizes them
52
+ # for CyTube, which renders the MOTD off-site.
53
+ mystery_box_base_url: str = "https://queue.dropsugar.co"
54
+ # Single fallback used only when no branded placeholders are installed.
55
+ mystery_box_url: str = ""
51
56
  mystery_box_href: str = "https://queue.dropsugar.co/"
52
57
  links: list[MOTDLink] = Field(
53
58
  default_factory=lambda: [
@@ -17,7 +17,7 @@ import datetime
17
17
  import logging
18
18
  from pathlib import Path
19
19
 
20
- from ..motd.builder import build_slots
20
+ from ..motd.builder import build_slots, mystery_pool
21
21
  from ..motd.render import render_motd
22
22
  from .manager import JobError
23
23
 
@@ -140,6 +140,7 @@ async def motd_publish_job(params: dict, ctx) -> dict:
140
140
  refresh_art=refresh_art,
141
141
  dry_run=dry_run,
142
142
  week_offset=week_offset,
143
+ mystery_urls=mystery_pool(ctx.config, getattr(ctx, "cover_art", None)),
143
144
  emit=_emit,
144
145
  )
145
146
  except RuntimeError as exc:
@@ -13,6 +13,7 @@ from __future__ import annotations
13
13
 
14
14
  import datetime
15
15
  import logging
16
+ import random
16
17
  import re
17
18
  from dataclasses import dataclass, field
18
19
  from pathlib import Path
@@ -149,9 +150,28 @@ def _titles_by_night(movies_by_section: dict[str, list[str]]) -> dict[int, list[
149
150
  return by_night
150
151
 
151
152
 
152
- def _apply_mystery(slot: MOTDSlot, motd_cfg) -> None:
153
+ def mystery_pool(config, cover_art) -> list[str]:
154
+ """Absolute URLs for the branded placeholder art the browse view uses.
155
+
156
+ ``list_placeholder_urls`` returns site-relative ``/images/...`` paths; CyTube
157
+ renders the MOTD off-site, so they have to be absolutized.
158
+ """
159
+ if cover_art is None:
160
+ return []
161
+ base = str(getattr(config.motd, "mystery_box_base_url", "") or "").rstrip("/")
162
+ if not base:
163
+ return []
164
+ return [base + url for url in cover_art.list_placeholder_urls()]
165
+
166
+
167
+ def _apply_mystery(slot: MOTDSlot, motd_cfg, pool: list[str]) -> None:
153
168
  slot.source = "mystery"
154
- slot.poster_url = getattr(motd_cfg, "mystery_box_url", "") or ""
169
+ # Vary the art across the grid the way browse varies it across tiles.
170
+ slot.poster_url = (
171
+ random.choice(pool)
172
+ if pool
173
+ else (getattr(motd_cfg, "mystery_box_url", "") or "")
174
+ )
155
175
  slot.href = getattr(motd_cfg, "mystery_box_href", "") or ""
156
176
 
157
177
 
@@ -164,6 +184,7 @@ def build_slots(
164
184
  dry_run: bool = False,
165
185
  today: datetime.date | None = None,
166
186
  week_offset: int = 0,
187
+ mystery_urls: list[str] | None = None,
167
188
  emit=None,
168
189
  ) -> MOTDWeek:
169
190
  """Resolve the upcoming weekend into a complete grid of poster slots.
@@ -178,6 +199,7 @@ def build_slots(
178
199
  emit(detail)
179
200
 
180
201
  overrides = overrides or {}
202
+ pool = mystery_urls or []
181
203
  motd_cfg = getattr(config, "motd", None)
182
204
  slot_count = int(getattr(motd_cfg, "slots", 12) or 12)
183
205
  poster_dir = Path(
@@ -234,13 +256,13 @@ def build_slots(
234
256
  continue
235
257
 
236
258
  if not slot.title:
237
- _apply_mystery(slot, motd_cfg)
259
+ _apply_mystery(slot, motd_cfg, pool)
238
260
  slot.note = "no title on the schedule yet"
239
261
  continue
240
262
 
241
263
  info = _resolve_omdb(slot.title, api_key=omdb_key) if omdb_key else None
242
264
  if not info:
243
- _apply_mystery(slot, motd_cfg)
265
+ _apply_mystery(slot, motd_cfg, pool)
244
266
  slot.note = "title could not be verified"
245
267
  logger.info("motd: no OMDB match for %r (%s)", slot.title, slot.slot_key)
246
268
  if override.get("href"):
@@ -266,7 +288,7 @@ def build_slots(
266
288
  slot.poster_url = f"{poster_base_url}/{filename}"
267
289
  slot.source = "omdb"
268
290
  else:
269
- _apply_mystery(slot, motd_cfg)
291
+ _apply_mystery(slot, motd_cfg, pool)
270
292
  slot.note = "poster download failed"
271
293
 
272
294
  slot.href = override.get("href") or _imdb_href(slot.imdb_id)
@@ -18,7 +18,7 @@ from fastapi import APIRouter, Depends, File, Form, HTTPException, Request, Uplo
18
18
  from pydantic import BaseModel
19
19
 
20
20
  from ..auth.session import require_admin
21
- from ..motd.builder import SLOT_KEY_RE, build_slots, week_context
21
+ from ..motd.builder import SLOT_KEY_RE, build_slots, mystery_pool, week_context
22
22
  from ..motd.render import render_motd
23
23
 
24
24
  router = APIRouter(prefix="/admin/motd", tags=["admin"])
@@ -72,6 +72,9 @@ async def _build_current(request: Request, week: str = "current"):
72
72
  overrides=overrides,
73
73
  dry_run=True,
74
74
  week_offset=_week_offset(week),
75
+ mystery_urls=mystery_pool(
76
+ config, getattr(request.app.state, "cover_art", None)
77
+ ),
75
78
  )
76
79
 
77
80
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "kryten-webqueue"
3
- version = "0.45.0"
3
+ version = "0.45.1"
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"
@@ -30,6 +30,7 @@ def _config(tmp_path, **motd_kw):
30
30
  poster_dir=str(tmp_path / "boxes"),
31
31
  poster_base_url="https://cdn.example/boxes",
32
32
  output_dir=str(tmp_path / "out"),
33
+ mystery_box_base_url="https://queue.example",
33
34
  mystery_box_url="https://cdn.example/boxes/mystery.jpg",
34
35
  mystery_box_href="https://queue.example/",
35
36
  **motd_kw,
@@ -126,6 +127,43 @@ def test_unverifiable_title_falls_back_to_mystery(tmp_path, monkeypatch, stub_lo
126
127
  assert "could not be verified" in slot.note
127
128
 
128
129
 
130
+ # --- mystery art comes from the browse view's branded placeholder pool ---
131
+
132
+
133
+ def test_mystery_pool_absolutizes_browse_placeholders(tmp_path):
134
+ config = _config(tmp_path)
135
+ cover_art = SimpleNamespace(
136
+ list_placeholder_urls=lambda: ["/images/placeholders/a.webp"]
137
+ )
138
+ assert builder.mystery_pool(config, cover_art) == [
139
+ "https://queue.example/images/placeholders/a.webp"
140
+ ]
141
+ assert builder.mystery_pool(config, None) == []
142
+
143
+
144
+ def test_mystery_slots_use_the_placeholder_pool(tmp_path, monkeypatch, stub_lookup):
145
+ _stub_workbook(monkeypatch, {})
146
+ week = builder.build_slots(
147
+ _config(tmp_path),
148
+ today=datetime.date(2026, 3, 4),
149
+ mystery_urls=["https://queue.example/images/placeholders/a.webp"],
150
+ )
151
+ assert all(
152
+ s.poster_url == "https://queue.example/images/placeholders/a.webp"
153
+ for s in week.slots
154
+ )
155
+
156
+
157
+ def test_mystery_falls_back_when_no_placeholders_installed(
158
+ tmp_path, monkeypatch, stub_lookup
159
+ ):
160
+ _stub_workbook(monkeypatch, {})
161
+ week = builder.build_slots(
162
+ _config(tmp_path), today=datetime.date(2026, 3, 4), mystery_urls=[]
163
+ )
164
+ assert all(s.poster_url.endswith("mystery.jpg") for s in week.slots)
165
+
166
+
129
167
  def test_dry_run_writes_nothing(tmp_path, monkeypatch, stub_lookup):
130
168
  _stub_workbook(monkeypatch, {1: ["The Big Bus (1976)"]})
131
169
  builder.build_slots(