kryten-webqueue 0.45.1__tar.gz → 0.46.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.1 → kryten_webqueue-0.46.1}/CHANGELOG.md +38 -0
  2. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/PKG-INFO +1 -1
  3. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/config.example.json +4 -2
  4. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/config.py +7 -1
  5. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/motd/builder.py +49 -9
  6. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/motd/render.py +1 -0
  7. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/templates/motd/channel_z.html +1 -1
  8. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/pyproject.toml +1 -1
  9. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/tests/test_motd_publish.py +75 -2
  10. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/.github/workflows/gitleaks.yml +0 -0
  11. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/.github/workflows/python-publish.yml +0 -0
  12. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/.github/workflows/release.yml +0 -0
  13. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/.gitignore +0 -0
  14. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/.gitleaks.toml +0 -0
  15. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/.pre-commit-config.yaml +0 -0
  16. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/AGENT-WORKFLOW-GUIDE.md +0 -0
  17. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/AGENTS.md +0 -0
  18. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/README.md +0 -0
  19. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/deploy/kryten-webqueue.service +0 -0
  20. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/deploy/nginx-queue.conf +0 -0
  21. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/docs/IMPLEMENTATION_SPEC.md +0 -0
  22. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/docs/IMPL_API_GATE.md +0 -0
  23. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/docs/IMPL_ECONOMY.md +0 -0
  24. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/docs/IMPL_KRYTEN_PY.md +0 -0
  25. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/docs/IMPL_ROBOT.md +0 -0
  26. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/docs/Kryten-Economy Service Quality Survey Report at v0.13.0.md +0 -0
  27. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/docs/Kryten-Webqueue Service Quality Survey Report at v0.27.1.md +0 -0
  28. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/docs/Log for incorrect promo insertion problem.txt +0 -0
  29. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/docs/PLAN_PRESENCE_AND_PROMOS.md +0 -0
  30. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/docs/PRD_TMDB_LOCAL_INDEX.md +0 -0
  31. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/docs/PRE_PLAN_GAPS.md +0 -0
  32. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/docs/PRODUCT_PLAN.md +0 -0
  33. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/docs/PUBLIC_API.md +0 -0
  34. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/docs/SPEC-Visual-UX-Sprint-August-2026.md +0 -0
  35. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/docs/SPEC_ADMIN_ITEM_EDITOR.md +0 -0
  36. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/docs/SPEC_CATALOG_ENRICHMENT_PIPELINE.md +0 -0
  37. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/docs/SPEC_EMOTE_REHOST.md +0 -0
  38. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/docs/SPEC_JOBS_AND_BROWSE.md +0 -0
  39. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/docs/SPEC_TMDB_LOCAL_INDEX.md +0 -0
  40. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/docs/UX_POLISH_PLAN.md +0 -0
  41. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/__init__.py +0 -0
  42. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/__main__.py +0 -0
  43. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/api_gate/__init__.py +0 -0
  44. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/api_gate/client.py +0 -0
  45. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/app.py +0 -0
  46. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/auth/__init__.py +0 -0
  47. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/auth/device_keys.py +0 -0
  48. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/auth/otp.py +0 -0
  49. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/auth/rate_limit.py +0 -0
  50. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/auth/session.py +0 -0
  51. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/catalog/__init__.py +0 -0
  52. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/catalog/db/__init__.py +0 -0
  53. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/catalog/db/_blackouts.py +0 -0
  54. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/catalog/db/_catalog.py +0 -0
  55. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/catalog/db/_connection.py +0 -0
  56. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/catalog/db/_devices.py +0 -0
  57. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/catalog/db/_enrichment.py +0 -0
  58. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/catalog/db/_feedback.py +0 -0
  59. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/catalog/db/_fetch_queue.py +0 -0
  60. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/catalog/db/_motd.py +0 -0
  61. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/catalog/db/_people.py +0 -0
  62. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/catalog/db/_playlists.py +0 -0
  63. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/catalog/db/_queue.py +0 -0
  64. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/catalog/db/_watchlist.py +0 -0
  65. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/catalog/enrichment/__init__.py +0 -0
  66. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/catalog/enrichment/classify.py +0 -0
  67. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/catalog/enrichment/normalise.py +0 -0
  68. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/catalog/enrichment/pipeline.py +0 -0
  69. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/catalog/enrichment/providers.py +0 -0
  70. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/catalog/enrichment/report.py +0 -0
  71. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/catalog/enrichment/steps/__init__.py +0 -0
  72. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/catalog/enrichment/steps/art.py +0 -0
  73. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/catalog/enrichment/steps/categories.py +0 -0
  74. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/catalog/enrichment/steps/identify.py +0 -0
  75. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/catalog/enrichment/steps/meta.py +0 -0
  76. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/catalog/enrichment/steps/sync.py +0 -0
  77. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/catalog/enrichment/steps/tags.py +0 -0
  78. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/catalog/enrichment/steps/title.py +0 -0
  79. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/catalog/images.py +0 -0
  80. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/catalog/mediacms.py +0 -0
  81. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/catalog/sync.py +0 -0
  82. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/catalog/tmdb_index/__init__.py +0 -0
  83. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/catalog/tmdb_index/_schema.py +0 -0
  84. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/catalog/tmdb_index/_textmatch.py +0 -0
  85. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/catalog/tmdb_index/_ttscrape.py +0 -0
  86. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/catalog/tmdb_index/builder.py +0 -0
  87. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/catalog/tmdb_index/coverage.py +0 -0
  88. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/catalog/tmdb_index/index.py +0 -0
  89. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/integrations/__init__.py +0 -0
  90. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/integrations/cmsutils/__init__.py +0 -0
  91. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/integrations/cmsutils/_common.py +0 -0
  92. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/integrations/cmsutils/blackout_scan.py +0 -0
  93. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/integrations/cmsutils/enrichmeta.py +0 -0
  94. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/integrations/cmsutils/enrichtitles.py +0 -0
  95. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/integrations/cmsutils/enrichtv.py +0 -0
  96. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/integrations/cmsutils/fetchurls.py +0 -0
  97. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/integrations/cmsutils/motdposters.py +0 -0
  98. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/integrations/cmsutils/playedmovies.py +0 -0
  99. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/integrations/ytpipe/__init__.py +0 -0
  100. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/integrations/ytpipe/downloader.py +0 -0
  101. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/jobs/__init__.py +0 -0
  102. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/jobs/fetchurls_auth.py +0 -0
  103. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/jobs/job_scheduler.py +0 -0
  104. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/jobs/log_capture.py +0 -0
  105. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/jobs/manager.py +0 -0
  106. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/jobs/motd_publish.py +0 -0
  107. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/jobs/rehost_emotes.py +0 -0
  108. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/jobs/tasks.py +0 -0
  109. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/logging_config.py +0 -0
  110. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/motd/__init__.py +0 -0
  111. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/playlists/__init__.py +0 -0
  112. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/playlists/bulk_add.py +0 -0
  113. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/playlists/fire.py +0 -0
  114. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/playlists/importer.py +0 -0
  115. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/playlists/ordering.py +0 -0
  116. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/playlists/scheduler.py +0 -0
  117. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/promos/__init__.py +0 -0
  118. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/promos/director.py +0 -0
  119. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/queue/__init__.py +0 -0
  120. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/queue/completion.py +0 -0
  121. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/queue/ordering.py +0 -0
  122. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/queue/poller.py +0 -0
  123. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/queue/presence.py +0 -0
  124. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/queue/race_poller.py +0 -0
  125. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/queue/shadow.py +0 -0
  126. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/routes/__init__.py +0 -0
  127. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/routes/admin_catalog.py +0 -0
  128. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/routes/admin_feedback.py +0 -0
  129. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/routes/admin_job_schedules.py +0 -0
  130. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/routes/admin_jobs.py +0 -0
  131. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/routes/admin_moderation.py +0 -0
  132. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/routes/admin_motd.py +0 -0
  133. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/routes/admin_playlists.py +0 -0
  134. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/routes/admin_promos.py +0 -0
  135. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/routes/admin_queue.py +0 -0
  136. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/routes/admin_schedules.py +0 -0
  137. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/routes/auth.py +0 -0
  138. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/routes/catalog.py +0 -0
  139. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/routes/devices.py +0 -0
  140. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/routes/feedback.py +0 -0
  141. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/routes/pages.py +0 -0
  142. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/routes/public_api.py +0 -0
  143. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/routes/queue.py +0 -0
  144. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/routes/user.py +0 -0
  145. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/static/css/main.css +0 -0
  146. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/static/js/admin-motd.js +0 -0
  147. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/static/js/admin-playlists.js +0 -0
  148. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/static/js/admin-promos.js +0 -0
  149. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/static/js/admin-queue-mgmt.js +0 -0
  150. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/static/js/admin-schedules.js +0 -0
  151. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/static/js/admin.js +0 -0
  152. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/static/js/keybindings.js +0 -0
  153. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/static/js/main.js +0 -0
  154. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/templates/admin/index.html +0 -0
  155. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/templates/admin/motd.html +0 -0
  156. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/templates/admin/playlists.html +0 -0
  157. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/templates/admin/promos.html +0 -0
  158. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/templates/admin/queue_mgmt.html +0 -0
  159. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/templates/admin/schedules.html +0 -0
  160. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/templates/auth/login.html +0 -0
  161. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/templates/base.html +0 -0
  162. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/templates/catalog/browse.html +0 -0
  163. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/templates/catalog/edit_item_modal.html +0 -0
  164. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/templates/catalog/item_detail.html +0 -0
  165. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/templates/catalog/item_not_found.html +0 -0
  166. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/templates/feedback/index.html +0 -0
  167. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/templates/queue/index.html +0 -0
  168. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/templates/race.html +0 -0
  169. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/templates/user/dashboard.html +0 -0
  170. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/templates/user/link_device.html +0 -0
  171. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/templates/user/my_list.html +0 -0
  172. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/ws/__init__.py +0 -0
  173. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/ws/handler.py +0 -0
  174. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/kryten_webqueue/ws/manager.py +0 -0
  175. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/scripts/AGENTS.md +0 -0
  176. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/scripts/patch_mediacms_bulk_facets.py +0 -0
  177. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/scripts/patch_mediacms_tags.py +0 -0
  178. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/scripts/trigger_enrich.py +0 -0
  179. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/tests/__init__.py +0 -0
  180. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/tests/test_catalog_blackout.py +0 -0
  181. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/tests/test_catalog_delete.py +0 -0
  182. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/tests/test_classify.py +0 -0
  183. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/tests/test_config_persistence.py +0 -0
  184. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/tests/test_device_linking.py +0 -0
  185. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/tests/test_feedback.py +0 -0
  186. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/tests/test_fetch_queue_pacing.py +0 -0
  187. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/tests/test_fetchurls_sharepoint.py +0 -0
  188. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/tests/test_identify_step.py +0 -0
  189. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/tests/test_job_logs_and_queue_visibility.py +0 -0
  190. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/tests/test_people_facets.py +0 -0
  191. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/tests/test_phase1.py +0 -0
  192. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/tests/test_phase2_jobs.py +0 -0
  193. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/tests/test_phase3_jobs.py +0 -0
  194. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/tests/test_phase4_live_fixes.py +0 -0
  195. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/tests/test_playlist_import.py +0 -0
  196. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/tests/test_presence_refund.py +0 -0
  197. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/tests/test_promo_director.py +0 -0
  198. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/tests/test_promo_pool_exclusion.py +0 -0
  199. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/tests/test_providers.py +0 -0
  200. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/tests/test_queue_announce.py +0 -0
  201. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/tests/test_queue_preview_fallback.py +0 -0
  202. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/tests/test_race_poller.py +0 -0
  203. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/tests/test_recently_played_hide.py +0 -0
  204. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/tests/test_save_results_to_playlist.py +0 -0
  205. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/tests/test_schedule_lock.py +0 -0
  206. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/tests/test_search_facets.py +0 -0
  207. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/tests/test_title_normalization.py +0 -0
  208. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/tests/test_tmdb_index.py +0 -0
  209. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.1}/tests/test_watchlist.py +0 -0
@@ -1,5 +1,43 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.46.1] - 2026-09-10
4
+
5
+ ### Changed
6
+
7
+ - **The grid is an even split again** (6/6 for a 12-slot Friday+Saturday
8
+ weekend). The 5/7 shape 0.46.0 inferred from the live MOTD was an artifact of
9
+ a markup bug: with the `<a>` tags left unclosed, the blank line after the
10
+ fifth poster looks like a night break but isn't, and most screen widths hide
11
+ the resulting reflow. The generated snippet closes its tags, so it renders the
12
+ intended 6/6.
13
+ - A night with more scheduled titles than grid positions now records a warning
14
+ (job result, admin panel, and log) instead of silently dropping the extras.
15
+
16
+ ## [0.46.0] - 2026-09-10
17
+
18
+ ### Changed
19
+
20
+ - **MOTD template rebuilt from the live hand-built MOTD** (pulled from the
21
+ channel via api-gate) rather than the older copy in kryten-llm. Banner image,
22
+ headline, and both footer lines now match the real markup verbatim — including
23
+ the "Our Webqueue app is in open beta!" footer text that the earlier default
24
+ dropped. The generated snippet diffs clean against the live one apart from
25
+ properly closed `<a>` tags and `title`/`alt` attributes.
26
+ - **Grid shape follows the schedule.** A 5/7 weekend now renders 5/7 instead of
27
+ being forced into an even 6/6, matching how the MOTD is actually built. A
28
+ night is never truncated, so an over-full weekend shows every scheduled movie;
29
+ `motd.slots` is a target the grid is only padded *up* to when the weekend is
30
+ still thin.
31
+ (Reverted in 0.46.1 — the 5/7 reading was a markup artifact.)
32
+ - **Sunday is excluded.** New `motd.nights` (default `[1, 2]`) controls which
33
+ nights the grid covers; Sunday has no schedule yet, so its workbook titles are
34
+ parsed but ignored. Add `3` once the scheduled-playlists feature fills that
35
+ slot.
36
+ - **The next-event line stays hidden.** It is still carried in the render
37
+ context, but is only emitted when `motd.show_next_event` is true (default
38
+ false), so the published MOTD matches the hand-built one until the feature is
39
+ deliberately debuted.
40
+
3
41
  ## [0.45.1] - 2026-09-10
4
42
 
5
43
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: kryten-webqueue
3
- Version: 0.45.1
3
+ Version: 0.46.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
@@ -92,6 +92,7 @@
92
92
  "output_dir": "~/kryten",
93
93
  "template": "channel_z.html",
94
94
  "slots": 12,
95
+ "nights": [1, 2],
95
96
  "banner_url": "https://i.postimg.cc/jdr2mR8Y/1562479186-8-channel-Ztitlenew.png",
96
97
  "headline": "CHANNEL Z WEEKEND MOVIE PLAYLIST ({dates})",
97
98
  "showtime": "6pm ET",
@@ -102,10 +103,11 @@
102
103
  "links": [
103
104
  { "label": "Join us on Reddit!", "url": "https://www.reddit.com/r/Channel_Z/" },
104
105
  {
105
- "label": "See the queue and decide what's next!",
106
+ "label": "See the queue and decide what's next! Our Webqueue app is in open beta!",
106
107
  "url": "https://queue.dropsugar.co/"
107
108
  }
108
- ]
109
+ ],
110
+ "show_next_event": false
109
111
  },
110
112
 
111
113
  "prometheus_port": 28292
@@ -43,6 +43,9 @@ class MOTDConfig(BaseModel):
43
43
  template: str = "channel_z.html"
44
44
  # Grid size; unresolved positions are filled with mystery boxes.
45
45
  slots: int = 12
46
+ # Nights the grid covers, in display order (1=Fri, 2=Sat, 3=Sun). Sunday has
47
+ # no schedule yet, so its workbook titles are ignored until it's added here.
48
+ nights: list[int] = Field(default_factory=lambda: [1, 2])
46
49
  banner_url: str = "https://i.postimg.cc/jdr2mR8Y/1562479186-8-channel-Ztitlenew.png"
47
50
  # {dates} is substituted with the weekend showtimes (e.g. "8/14 @ 6pm ET & ...").
48
51
  headline: str = "CHANNEL Z WEEKEND MOVIE PLAYLIST ({dates})"
@@ -61,11 +64,14 @@ class MOTDConfig(BaseModel):
61
64
  url="https://www.reddit.com/r/Channel_Z/",
62
65
  ),
63
66
  MOTDLink(
64
- label="See the queue and decide what's next!",
67
+ label="See the queue and decide what's next! Our Webqueue app is in open beta!",
65
68
  url="https://queue.dropsugar.co/",
66
69
  ),
67
70
  ]
68
71
  )
72
+ # The next-event line is carried in the render context but stays hidden until
73
+ # it's deliberately debuted; the hand-built MOTD has no such line.
74
+ show_next_event: bool = False
69
75
  # Max size accepted by the admin alternate-art upload endpoint.
70
76
  upload_max_bytes: int = 5 * 1024 * 1024
71
77
 
@@ -35,7 +35,9 @@ logger = logging.getLogger(__name__)
35
35
 
36
36
  SLOT_KEY_RE = re.compile(r"^night[1-3]-slot([1-9]\d{0,2})$")
37
37
 
38
- # Nights the grid always reserves space for, in display order.
38
+ # Every night the workbook can describe. The grid itself covers only the nights
39
+ # in ``motd.nights`` — Sunday has no schedule yet, so its titles are parsed but
40
+ # never placed.
39
41
  _NIGHTS = (1, 2, 3)
40
42
 
41
43
 
@@ -118,17 +120,34 @@ def _imdb_href(imdb_id: str | None) -> str:
118
120
  return f"https://www.imdb.com/title/{imdb_id}/" if imdb_id else ""
119
121
 
120
122
 
123
+ def _grid_nights(motd_cfg) -> tuple[int, ...]:
124
+ """Nights the grid reserves space for, in display order."""
125
+ configured = getattr(motd_cfg, "nights", None) or _NIGHTS
126
+ nights = tuple(int(n) for n in configured if int(n) in _NIGHTS)
127
+ return nights or _NIGHTS
128
+
129
+
130
+ def _slot_counts(nights: tuple[int, ...], slots: int) -> dict[int, int]:
131
+ """Split ``slots`` evenly across ``nights``; extras go to the earlier nights."""
132
+ per_night, remainder = divmod(slots, len(nights))
133
+ return {
134
+ night: per_night + (1 if index <= remainder else 0)
135
+ for index, night in enumerate(nights, 1)
136
+ }
137
+
138
+
121
139
  def _blank_grid(
122
- friday: datetime.date, saturday: datetime.date, sunday: datetime.date, slots: int
140
+ friday: datetime.date,
141
+ saturday: datetime.date,
142
+ sunday: datetime.date,
143
+ counts: dict[int, int],
144
+ nights: tuple[int, ...],
123
145
  ) -> list[MOTDSlot]:
124
- """Lay out ``slots`` positions spread evenly across the three nights."""
125
- per_night = max(1, slots // len(_NIGHTS))
126
- remainder = slots - per_night * len(_NIGHTS)
146
+ """Lay out the per-night positions in display order."""
127
147
  dates = {1: friday, 2: saturday, 3: sunday}
128
148
  grid: list[MOTDSlot] = []
129
- for night in _NIGHTS:
130
- count = per_night + (1 if night <= remainder else 0)
131
- for position in range(1, count + 1):
149
+ for night in nights:
150
+ for position in range(1, counts[night] + 1):
132
151
  grid.append(
133
152
  MOTDSlot(
134
153
  slot_key=f"night{night}-slot{position}",
@@ -212,7 +231,6 @@ def build_slots(
212
231
  week_key, friday, saturday = upcoming_weekend_sheet(base_day)
213
232
  sunday = friday + datetime.timedelta(days=2)
214
233
  week = MOTDWeek(week_key=week_key, friday=friday, saturday=saturday, sunday=sunday)
215
- week.slots = _blank_grid(friday, saturday, sunday, slot_count)
216
234
 
217
235
  # The workbook is best-effort: a missing sheet yields an all-mystery grid
218
236
  # rather than a failed run, so the MOTD still publishes on schedule.
@@ -230,6 +248,28 @@ def build_slots(
230
248
  week.warnings.append(f"workbook unavailable: {exc}")
231
249
  logger.warning("motd: workbook unavailable for %s: %s", week_key, exc)
232
250
 
251
+ nights = _grid_nights(motd_cfg)
252
+ counts = _slot_counts(nights, slot_count)
253
+ week.slots = _blank_grid(friday, saturday, sunday, counts, nights)
254
+
255
+ # The grid is a fixed size, so a night with more titles than positions would
256
+ # silently drop the extras. Surface it instead.
257
+ for night in nights:
258
+ extra = len(titles_by_night.get(night, [])) - counts[night]
259
+ if extra > 0:
260
+ label = _NIGHT_LABELS.get(night, f"Night {night}")
261
+ week.warnings.append(
262
+ f"{label}: {extra} scheduled title(s) beyond the "
263
+ f"{counts[night]}-slot grid are not shown"
264
+ )
265
+ logger.warning(
266
+ "motd: %s has %d title(s) beyond its %d slots on %s",
267
+ label,
268
+ extra,
269
+ counts[night],
270
+ week_key,
271
+ )
272
+
233
273
  if not dry_run:
234
274
  poster_dir.mkdir(parents=True, exist_ok=True)
235
275
 
@@ -73,6 +73,7 @@ def render_motd(config, week: MOTDWeek, *, next_event: dict | None = None) -> st
73
73
  for link in getattr(motd_cfg, "links", [])
74
74
  ],
75
75
  next_event=next_event,
76
+ show_next_event=bool(getattr(motd_cfg, "show_next_event", False)),
76
77
  week_key=week.week_key,
77
78
  generated_at=datetime.datetime.now(datetime.timezone.utc).isoformat(
78
79
  timespec="seconds"
@@ -22,7 +22,7 @@
22
22
  {% endfor %}
23
23
  {% endfor %}
24
24
  </div>
25
- {% if next_event %}
25
+ {% if show_next_event and next_event %}
26
26
  <div class="weekend-title">🟥🟧🟨🟩Up next: {{ next_event.label }}{% if next_event.starts_in %} — {{ next_event.starts_in }}{% endif %}🟩🟨🟧🟥</div>
27
27
  {% endif %}
28
28
  {% for link in links %}
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "kryten-webqueue"
3
- version = "0.45.1"
3
+ version = "0.46.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"
@@ -72,12 +72,73 @@ def _stub_workbook(monkeypatch, titles_by_night):
72
72
  # --- grid layout ---
73
73
 
74
74
 
75
- def test_grid_is_always_full_size(tmp_path, monkeypatch, stub_lookup):
75
+ def test_grid_pads_a_thin_weekend_to_the_target_size(
76
+ tmp_path, monkeypatch, stub_lookup
77
+ ):
76
78
  _stub_workbook(monkeypatch, {})
77
79
  week = builder.build_slots(_config(tmp_path), today=datetime.date(2026, 3, 4))
78
80
  assert len(week.slots) == 12
79
81
  assert [s.slot_key for s in week.slots[:2]] == ["night1-slot1", "night1-slot2"]
82
+ # Sunday has no schedule yet, so the grid is Friday + Saturday.
83
+ assert {s.night for s in week.slots} == {1, 2}
84
+ assert len([s for s in week.slots if s.night == 1]) == 6
85
+ assert len([s for s in week.slots if s.night == 2]) == 6
86
+
87
+
88
+ def test_grid_shape_is_an_even_split(tmp_path, monkeypatch, stub_lookup):
89
+ _stub_workbook(
90
+ monkeypatch,
91
+ {
92
+ 1: [f"Friday {n} (1980)" for n in range(6)],
93
+ 2: [f"Saturday {n} (1980)" for n in range(6)],
94
+ },
95
+ )
96
+ week = builder.build_slots(_config(tmp_path), today=datetime.date(2026, 3, 4))
97
+ assert len([s for s in week.slots if s.night == 1]) == 6
98
+ assert len([s for s in week.slots if s.night == 2]) == 6
99
+ assert all(s.resolved for s in week.slots)
100
+ assert not week.warnings
101
+
102
+
103
+ def test_overfull_night_warns_instead_of_silently_dropping(
104
+ tmp_path, monkeypatch, stub_lookup
105
+ ):
106
+ _stub_workbook(
107
+ monkeypatch,
108
+ {
109
+ 1: [f"Friday {n} (1980)" for n in range(8)],
110
+ 2: [f"Saturday {n} (1980)" for n in range(6)],
111
+ },
112
+ )
113
+ week = builder.build_slots(_config(tmp_path), today=datetime.date(2026, 3, 4))
114
+ assert len(week.slots) == 12
115
+ assert any("2 scheduled title(s)" in w for w in week.warnings)
116
+ assert "Friday 6 (1980)" not in [s.title for s in week.slots]
117
+
118
+
119
+ def test_sunday_titles_are_ignored(tmp_path, monkeypatch, stub_lookup):
120
+ _stub_workbook(
121
+ monkeypatch,
122
+ {
123
+ 1: ["Friday One (1976)"],
124
+ 2: ["Saturday One (1980)"],
125
+ 3: ["Sunday One (1990)"],
126
+ },
127
+ )
128
+ week = builder.build_slots(_config(tmp_path), today=datetime.date(2026, 3, 4))
129
+ titles = [s.title for s in week.slots if s.title]
130
+ assert "Friday One (1976)" in titles
131
+ assert "Saturday One (1980)" in titles
132
+ assert "Sunday One (1990)" not in titles
133
+
134
+
135
+ def test_nights_config_can_re_enable_sunday(tmp_path, monkeypatch, stub_lookup):
136
+ _stub_workbook(monkeypatch, {3: ["Sunday One (1990)"]})
137
+ week = builder.build_slots(
138
+ _config(tmp_path, nights=[1, 2, 3]), today=datetime.date(2026, 3, 4)
139
+ )
80
140
  assert {s.night for s in week.slots} == {1, 2, 3}
141
+ assert "Sunday One (1990)" in [s.title for s in week.slots if s.title]
81
142
 
82
143
 
83
144
  def test_missing_workbook_yields_all_mystery(tmp_path, monkeypatch, stub_lookup):
@@ -241,13 +302,25 @@ def test_render_groups_by_night_and_includes_links(tmp_path, monkeypatch, stub_l
241
302
  assert "3/6 @ 6pm ET &amp; 3/7 @ 6pm ET" in html
242
303
 
243
304
 
244
- def test_render_includes_next_event_when_present(tmp_path, monkeypatch, stub_lookup):
305
+ def test_next_event_is_carried_but_hidden_by_default(
306
+ tmp_path, monkeypatch, stub_lookup
307
+ ):
245
308
  _stub_workbook(monkeypatch, {})
246
309
  config = _config(tmp_path)
247
310
  week = builder.build_slots(config, today=datetime.date(2026, 3, 4))
248
311
  html = render.render_motd(
249
312
  config, week, next_event={"label": "Saturday Night", "starts_in": "in 2h"}
250
313
  )
314
+ assert "Up next" not in html
315
+
316
+
317
+ def test_render_includes_next_event_when_enabled(tmp_path, monkeypatch, stub_lookup):
318
+ _stub_workbook(monkeypatch, {})
319
+ config = _config(tmp_path, show_next_event=True)
320
+ week = builder.build_slots(config, today=datetime.date(2026, 3, 4))
321
+ html = render.render_motd(
322
+ config, week, next_event={"label": "Saturday Night", "starts_in": "in 2h"}
323
+ )
251
324
  assert "Up next: Saturday Night — in 2h" in html
252
325
 
253
326