kryten-webqueue 0.45.1__tar.gz → 0.46.0__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.0}/CHANGELOG.md +24 -0
  2. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/PKG-INFO +1 -1
  3. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/config.example.json +4 -2
  4. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/config.py +7 -1
  5. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/motd/builder.py +45 -9
  6. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/motd/render.py +1 -0
  7. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/templates/motd/channel_z.html +1 -1
  8. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/pyproject.toml +1 -1
  9. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/tests/test_motd_publish.py +72 -2
  10. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/.github/workflows/gitleaks.yml +0 -0
  11. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/.github/workflows/python-publish.yml +0 -0
  12. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/.github/workflows/release.yml +0 -0
  13. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/.gitignore +0 -0
  14. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/.gitleaks.toml +0 -0
  15. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/.pre-commit-config.yaml +0 -0
  16. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/AGENT-WORKFLOW-GUIDE.md +0 -0
  17. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/AGENTS.md +0 -0
  18. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/README.md +0 -0
  19. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/deploy/kryten-webqueue.service +0 -0
  20. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/deploy/nginx-queue.conf +0 -0
  21. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/docs/IMPLEMENTATION_SPEC.md +0 -0
  22. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/docs/IMPL_API_GATE.md +0 -0
  23. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/docs/IMPL_ECONOMY.md +0 -0
  24. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/docs/IMPL_KRYTEN_PY.md +0 -0
  25. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/docs/IMPL_ROBOT.md +0 -0
  26. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/docs/Kryten-Economy Service Quality Survey Report at v0.13.0.md +0 -0
  27. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/docs/Kryten-Webqueue Service Quality Survey Report at v0.27.1.md +0 -0
  28. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/docs/Log for incorrect promo insertion problem.txt +0 -0
  29. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/docs/PLAN_PRESENCE_AND_PROMOS.md +0 -0
  30. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/docs/PRD_TMDB_LOCAL_INDEX.md +0 -0
  31. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/docs/PRE_PLAN_GAPS.md +0 -0
  32. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/docs/PRODUCT_PLAN.md +0 -0
  33. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/docs/PUBLIC_API.md +0 -0
  34. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/docs/SPEC-Visual-UX-Sprint-August-2026.md +0 -0
  35. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/docs/SPEC_ADMIN_ITEM_EDITOR.md +0 -0
  36. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/docs/SPEC_CATALOG_ENRICHMENT_PIPELINE.md +0 -0
  37. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/docs/SPEC_EMOTE_REHOST.md +0 -0
  38. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/docs/SPEC_JOBS_AND_BROWSE.md +0 -0
  39. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/docs/SPEC_TMDB_LOCAL_INDEX.md +0 -0
  40. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/docs/UX_POLISH_PLAN.md +0 -0
  41. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/__init__.py +0 -0
  42. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/__main__.py +0 -0
  43. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/api_gate/__init__.py +0 -0
  44. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/api_gate/client.py +0 -0
  45. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/app.py +0 -0
  46. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/auth/__init__.py +0 -0
  47. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/auth/device_keys.py +0 -0
  48. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/auth/otp.py +0 -0
  49. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/auth/rate_limit.py +0 -0
  50. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/auth/session.py +0 -0
  51. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/catalog/__init__.py +0 -0
  52. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/catalog/db/__init__.py +0 -0
  53. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/catalog/db/_blackouts.py +0 -0
  54. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/catalog/db/_catalog.py +0 -0
  55. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/catalog/db/_connection.py +0 -0
  56. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/catalog/db/_devices.py +0 -0
  57. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/catalog/db/_enrichment.py +0 -0
  58. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/catalog/db/_feedback.py +0 -0
  59. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/catalog/db/_fetch_queue.py +0 -0
  60. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/catalog/db/_motd.py +0 -0
  61. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/catalog/db/_people.py +0 -0
  62. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/catalog/db/_playlists.py +0 -0
  63. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/catalog/db/_queue.py +0 -0
  64. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/catalog/db/_watchlist.py +0 -0
  65. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/catalog/enrichment/__init__.py +0 -0
  66. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/catalog/enrichment/classify.py +0 -0
  67. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/catalog/enrichment/normalise.py +0 -0
  68. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/catalog/enrichment/pipeline.py +0 -0
  69. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/catalog/enrichment/providers.py +0 -0
  70. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/catalog/enrichment/report.py +0 -0
  71. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/catalog/enrichment/steps/__init__.py +0 -0
  72. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/catalog/enrichment/steps/art.py +0 -0
  73. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/catalog/enrichment/steps/categories.py +0 -0
  74. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/catalog/enrichment/steps/identify.py +0 -0
  75. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/catalog/enrichment/steps/meta.py +0 -0
  76. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/catalog/enrichment/steps/sync.py +0 -0
  77. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/catalog/enrichment/steps/tags.py +0 -0
  78. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/catalog/enrichment/steps/title.py +0 -0
  79. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/catalog/images.py +0 -0
  80. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/catalog/mediacms.py +0 -0
  81. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/catalog/sync.py +0 -0
  82. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/catalog/tmdb_index/__init__.py +0 -0
  83. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/catalog/tmdb_index/_schema.py +0 -0
  84. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/catalog/tmdb_index/_textmatch.py +0 -0
  85. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/catalog/tmdb_index/_ttscrape.py +0 -0
  86. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/catalog/tmdb_index/builder.py +0 -0
  87. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/catalog/tmdb_index/coverage.py +0 -0
  88. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/catalog/tmdb_index/index.py +0 -0
  89. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/integrations/__init__.py +0 -0
  90. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/integrations/cmsutils/__init__.py +0 -0
  91. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/integrations/cmsutils/_common.py +0 -0
  92. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/integrations/cmsutils/blackout_scan.py +0 -0
  93. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/integrations/cmsutils/enrichmeta.py +0 -0
  94. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/integrations/cmsutils/enrichtitles.py +0 -0
  95. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/integrations/cmsutils/enrichtv.py +0 -0
  96. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/integrations/cmsutils/fetchurls.py +0 -0
  97. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/integrations/cmsutils/motdposters.py +0 -0
  98. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/integrations/cmsutils/playedmovies.py +0 -0
  99. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/integrations/ytpipe/__init__.py +0 -0
  100. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/integrations/ytpipe/downloader.py +0 -0
  101. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/jobs/__init__.py +0 -0
  102. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/jobs/fetchurls_auth.py +0 -0
  103. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/jobs/job_scheduler.py +0 -0
  104. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/jobs/log_capture.py +0 -0
  105. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/jobs/manager.py +0 -0
  106. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/jobs/motd_publish.py +0 -0
  107. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/jobs/rehost_emotes.py +0 -0
  108. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/jobs/tasks.py +0 -0
  109. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/logging_config.py +0 -0
  110. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/motd/__init__.py +0 -0
  111. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/playlists/__init__.py +0 -0
  112. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/playlists/bulk_add.py +0 -0
  113. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/playlists/fire.py +0 -0
  114. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/playlists/importer.py +0 -0
  115. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/playlists/ordering.py +0 -0
  116. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/playlists/scheduler.py +0 -0
  117. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/promos/__init__.py +0 -0
  118. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/promos/director.py +0 -0
  119. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/queue/__init__.py +0 -0
  120. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/queue/completion.py +0 -0
  121. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/queue/ordering.py +0 -0
  122. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/queue/poller.py +0 -0
  123. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/queue/presence.py +0 -0
  124. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/queue/race_poller.py +0 -0
  125. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/queue/shadow.py +0 -0
  126. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/routes/__init__.py +0 -0
  127. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/routes/admin_catalog.py +0 -0
  128. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/routes/admin_feedback.py +0 -0
  129. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/routes/admin_job_schedules.py +0 -0
  130. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/routes/admin_jobs.py +0 -0
  131. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/routes/admin_moderation.py +0 -0
  132. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/routes/admin_motd.py +0 -0
  133. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/routes/admin_playlists.py +0 -0
  134. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/routes/admin_promos.py +0 -0
  135. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/routes/admin_queue.py +0 -0
  136. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/routes/admin_schedules.py +0 -0
  137. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/routes/auth.py +0 -0
  138. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/routes/catalog.py +0 -0
  139. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/routes/devices.py +0 -0
  140. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/routes/feedback.py +0 -0
  141. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/routes/pages.py +0 -0
  142. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/routes/public_api.py +0 -0
  143. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/routes/queue.py +0 -0
  144. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/routes/user.py +0 -0
  145. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/static/css/main.css +0 -0
  146. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/static/js/admin-motd.js +0 -0
  147. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/static/js/admin-playlists.js +0 -0
  148. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/static/js/admin-promos.js +0 -0
  149. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/static/js/admin-queue-mgmt.js +0 -0
  150. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/static/js/admin-schedules.js +0 -0
  151. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/static/js/admin.js +0 -0
  152. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/static/js/keybindings.js +0 -0
  153. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/static/js/main.js +0 -0
  154. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/templates/admin/index.html +0 -0
  155. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/templates/admin/motd.html +0 -0
  156. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/templates/admin/playlists.html +0 -0
  157. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/templates/admin/promos.html +0 -0
  158. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/templates/admin/queue_mgmt.html +0 -0
  159. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/templates/admin/schedules.html +0 -0
  160. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/templates/auth/login.html +0 -0
  161. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/templates/base.html +0 -0
  162. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/templates/catalog/browse.html +0 -0
  163. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/templates/catalog/edit_item_modal.html +0 -0
  164. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/templates/catalog/item_detail.html +0 -0
  165. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/templates/catalog/item_not_found.html +0 -0
  166. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/templates/feedback/index.html +0 -0
  167. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/templates/queue/index.html +0 -0
  168. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/templates/race.html +0 -0
  169. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/templates/user/dashboard.html +0 -0
  170. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/templates/user/link_device.html +0 -0
  171. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/templates/user/my_list.html +0 -0
  172. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/ws/__init__.py +0 -0
  173. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/ws/handler.py +0 -0
  174. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/kryten_webqueue/ws/manager.py +0 -0
  175. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/scripts/AGENTS.md +0 -0
  176. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/scripts/patch_mediacms_bulk_facets.py +0 -0
  177. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/scripts/patch_mediacms_tags.py +0 -0
  178. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/scripts/trigger_enrich.py +0 -0
  179. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/tests/__init__.py +0 -0
  180. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/tests/test_catalog_blackout.py +0 -0
  181. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/tests/test_catalog_delete.py +0 -0
  182. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/tests/test_classify.py +0 -0
  183. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/tests/test_config_persistence.py +0 -0
  184. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/tests/test_device_linking.py +0 -0
  185. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/tests/test_feedback.py +0 -0
  186. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/tests/test_fetch_queue_pacing.py +0 -0
  187. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/tests/test_fetchurls_sharepoint.py +0 -0
  188. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/tests/test_identify_step.py +0 -0
  189. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/tests/test_job_logs_and_queue_visibility.py +0 -0
  190. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/tests/test_people_facets.py +0 -0
  191. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/tests/test_phase1.py +0 -0
  192. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/tests/test_phase2_jobs.py +0 -0
  193. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/tests/test_phase3_jobs.py +0 -0
  194. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/tests/test_phase4_live_fixes.py +0 -0
  195. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/tests/test_playlist_import.py +0 -0
  196. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/tests/test_presence_refund.py +0 -0
  197. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/tests/test_promo_director.py +0 -0
  198. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/tests/test_promo_pool_exclusion.py +0 -0
  199. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/tests/test_providers.py +0 -0
  200. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/tests/test_queue_announce.py +0 -0
  201. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/tests/test_queue_preview_fallback.py +0 -0
  202. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/tests/test_race_poller.py +0 -0
  203. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/tests/test_recently_played_hide.py +0 -0
  204. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/tests/test_save_results_to_playlist.py +0 -0
  205. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/tests/test_schedule_lock.py +0 -0
  206. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/tests/test_search_facets.py +0 -0
  207. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/tests/test_title_normalization.py +0 -0
  208. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/tests/test_tmdb_index.py +0 -0
  209. {kryten_webqueue-0.45.1 → kryten_webqueue-0.46.0}/tests/test_watchlist.py +0 -0
@@ -1,5 +1,29 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.46.0] - 2026-09-10
4
+
5
+ ### Changed
6
+
7
+ - **MOTD template rebuilt from the live hand-built MOTD** (pulled from the
8
+ channel via api-gate) rather than the older copy in kryten-llm. Banner image,
9
+ headline, and both footer lines now match the real markup verbatim — including
10
+ the "Our Webqueue app is in open beta!" footer text that the earlier default
11
+ dropped. The generated snippet diffs clean against the live one apart from
12
+ properly closed `<a>` tags and `title`/`alt` attributes.
13
+ - **Grid shape follows the schedule.** A 5/7 weekend now renders 5/7 instead of
14
+ being forced into an even 6/6, matching how the MOTD is actually built. A
15
+ night is never truncated, so an over-full weekend shows every scheduled movie;
16
+ `motd.slots` is a target the grid is only padded *up* to when the weekend is
17
+ still thin.
18
+ - **Sunday is excluded.** New `motd.nights` (default `[1, 2]`) controls which
19
+ nights the grid covers; Sunday has no schedule yet, so its workbook titles are
20
+ parsed but ignored. Add `3` once the scheduled-playlists feature fills that
21
+ slot.
22
+ - **The next-event line stays hidden.** It is still carried in the render
23
+ context, but is only emitted when `motd.show_next_event` is true (default
24
+ false), so the published MOTD matches the hand-built one until the feature is
25
+ deliberately debuted.
26
+
3
27
  ## [0.45.1] - 2026-09-10
4
28
 
5
29
  ### 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.0
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,43 @@ 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(
131
+ titles_by_night: dict[int, list[str]], nights: tuple[int, ...], slots: int
132
+ ) -> dict[int, int]:
133
+ """Decide how many positions each night gets.
134
+
135
+ The schedule drives the shape — a 5/7 weekend renders 5/7, not an even 6/6 —
136
+ and a night is never truncated, so a scheduled movie can't be hidden. Only
137
+ when the weekend is still thin is the grid padded up to ``slots``, handing
138
+ each extra to whichever night is furthest below its even share.
139
+ """
140
+ counts = {n: len(titles_by_night.get(n, [])) for n in nights}
141
+ share = slots / len(nights)
142
+ while sum(counts.values()) < slots:
143
+ night = min(nights, key=lambda n: (counts[n] - share, nights.index(n)))
144
+ counts[night] += 1
145
+ return counts
146
+
147
+
121
148
  def _blank_grid(
122
- friday: datetime.date, saturday: datetime.date, sunday: datetime.date, slots: int
149
+ friday: datetime.date,
150
+ saturday: datetime.date,
151
+ sunday: datetime.date,
152
+ counts: dict[int, int],
153
+ nights: tuple[int, ...],
123
154
  ) -> 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)
155
+ """Lay out the per-night positions in display order."""
127
156
  dates = {1: friday, 2: saturday, 3: sunday}
128
157
  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):
158
+ for night in nights:
159
+ for position in range(1, counts[night] + 1):
132
160
  grid.append(
133
161
  MOTDSlot(
134
162
  slot_key=f"night{night}-slot{position}",
@@ -212,7 +240,6 @@ def build_slots(
212
240
  week_key, friday, saturday = upcoming_weekend_sheet(base_day)
213
241
  sunday = friday + datetime.timedelta(days=2)
214
242
  week = MOTDWeek(week_key=week_key, friday=friday, saturday=saturday, sunday=sunday)
215
- week.slots = _blank_grid(friday, saturday, sunday, slot_count)
216
243
 
217
244
  # The workbook is best-effort: a missing sheet yields an all-mystery grid
218
245
  # rather than a failed run, so the MOTD still publishes on schedule.
@@ -230,6 +257,15 @@ def build_slots(
230
257
  week.warnings.append(f"workbook unavailable: {exc}")
231
258
  logger.warning("motd: workbook unavailable for %s: %s", week_key, exc)
232
259
 
260
+ nights = _grid_nights(motd_cfg)
261
+ week.slots = _blank_grid(
262
+ friday,
263
+ saturday,
264
+ sunday,
265
+ _slot_counts(titles_by_night, nights, slot_count),
266
+ nights,
267
+ )
268
+
233
269
  if not dry_run:
234
270
  poster_dir.mkdir(parents=True, exist_ok=True)
235
271
 
@@ -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.0"
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,70 @@ 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_follows_the_schedule(tmp_path, monkeypatch, stub_lookup):
89
+ # A real 5/7 weekend renders 5/7, not an even 6/6.
90
+ _stub_workbook(
91
+ monkeypatch,
92
+ {
93
+ 1: [f"Friday {n} (1980)" for n in range(5)],
94
+ 2: [f"Saturday {n} (1980)" for n in range(7)],
95
+ },
96
+ )
97
+ week = builder.build_slots(_config(tmp_path), today=datetime.date(2026, 3, 4))
98
+ assert len([s for s in week.slots if s.night == 1]) == 5
99
+ assert len([s for s in week.slots if s.night == 2]) == 7
100
+ assert all(s.resolved for s in week.slots)
101
+
102
+
103
+ def test_grid_never_truncates_an_overfull_weekend(tmp_path, monkeypatch, stub_lookup):
104
+ _stub_workbook(
105
+ monkeypatch,
106
+ {
107
+ 1: [f"Friday {n} (1980)" for n in range(8)],
108
+ 2: [f"Saturday {n} (1980)" for n in range(7)],
109
+ },
110
+ )
111
+ week = builder.build_slots(_config(tmp_path), today=datetime.date(2026, 3, 4))
112
+ assert len(week.slots) == 15
113
+ assert "Friday 7 (1980)" in [s.title for s in week.slots]
114
+
115
+
116
+ def test_sunday_titles_are_ignored(tmp_path, monkeypatch, stub_lookup):
117
+ _stub_workbook(
118
+ monkeypatch,
119
+ {
120
+ 1: ["Friday One (1976)"],
121
+ 2: ["Saturday One (1980)"],
122
+ 3: ["Sunday One (1990)"],
123
+ },
124
+ )
125
+ week = builder.build_slots(_config(tmp_path), today=datetime.date(2026, 3, 4))
126
+ titles = [s.title for s in week.slots if s.title]
127
+ assert "Friday One (1976)" in titles
128
+ assert "Saturday One (1980)" in titles
129
+ assert "Sunday One (1990)" not in titles
130
+
131
+
132
+ def test_nights_config_can_re_enable_sunday(tmp_path, monkeypatch, stub_lookup):
133
+ _stub_workbook(monkeypatch, {3: ["Sunday One (1990)"]})
134
+ week = builder.build_slots(
135
+ _config(tmp_path, nights=[1, 2, 3]), today=datetime.date(2026, 3, 4)
136
+ )
80
137
  assert {s.night for s in week.slots} == {1, 2, 3}
138
+ assert "Sunday One (1990)" in [s.title for s in week.slots if s.title]
81
139
 
82
140
 
83
141
  def test_missing_workbook_yields_all_mystery(tmp_path, monkeypatch, stub_lookup):
@@ -241,13 +299,25 @@ def test_render_groups_by_night_and_includes_links(tmp_path, monkeypatch, stub_l
241
299
  assert "3/6 @ 6pm ET &amp; 3/7 @ 6pm ET" in html
242
300
 
243
301
 
244
- def test_render_includes_next_event_when_present(tmp_path, monkeypatch, stub_lookup):
302
+ def test_next_event_is_carried_but_hidden_by_default(
303
+ tmp_path, monkeypatch, stub_lookup
304
+ ):
245
305
  _stub_workbook(monkeypatch, {})
246
306
  config = _config(tmp_path)
247
307
  week = builder.build_slots(config, today=datetime.date(2026, 3, 4))
248
308
  html = render.render_motd(
249
309
  config, week, next_event={"label": "Saturday Night", "starts_in": "in 2h"}
250
310
  )
311
+ assert "Up next" not in html
312
+
313
+
314
+ def test_render_includes_next_event_when_enabled(tmp_path, monkeypatch, stub_lookup):
315
+ _stub_workbook(monkeypatch, {})
316
+ config = _config(tmp_path, show_next_event=True)
317
+ week = builder.build_slots(config, today=datetime.date(2026, 3, 4))
318
+ html = render.render_motd(
319
+ config, week, next_event={"label": "Saturday Night", "starts_in": "in 2h"}
320
+ )
251
321
  assert "Up next: Saturday Night — in 2h" in html
252
322
 
253
323