kryten-webqueue 0.35.4__tar.gz → 0.35.6__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 (162) hide show
  1. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/CHANGELOG.md +22 -0
  2. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/PKG-INFO +1 -1
  3. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/catalog/db/_catalog.py +47 -18
  4. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/queue/ordering.py +14 -1
  5. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/pyproject.toml +1 -1
  6. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/.github/workflows/gitleaks.yml +0 -0
  7. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/.github/workflows/python-publish.yml +0 -0
  8. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/.github/workflows/release.yml +0 -0
  9. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/.gitignore +0 -0
  10. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/.gitleaks.toml +0 -0
  11. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/.pre-commit-config.yaml +0 -0
  12. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/AGENT-WORKFLOW-GUIDE.md +0 -0
  13. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/README.md +0 -0
  14. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/config.example.json +0 -0
  15. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/deploy/kryten-webqueue.service +0 -0
  16. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/deploy/nginx-queue.conf +0 -0
  17. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/docs/IMPLEMENTATION_SPEC.md +0 -0
  18. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/docs/IMPL_API_GATE.md +0 -0
  19. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/docs/IMPL_ECONOMY.md +0 -0
  20. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/docs/IMPL_KRYTEN_PY.md +0 -0
  21. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/docs/IMPL_ROBOT.md +0 -0
  22. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/docs/Kryten-Economy Service Quality Survey Report at v0.13.0.md +0 -0
  23. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/docs/Kryten-Webqueue Service Quality Survey Report at v0.27.1.md +0 -0
  24. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/docs/Log for incorrect promo insertion problem.txt +0 -0
  25. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/docs/PLAN_PRESENCE_AND_PROMOS.md +0 -0
  26. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/docs/PRE_PLAN_GAPS.md +0 -0
  27. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/docs/PRODUCT_PLAN.md +0 -0
  28. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/docs/SPEC_CATALOG_ENRICHMENT_PIPELINE.md +0 -0
  29. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/docs/SPEC_EMOTE_REHOST.md +0 -0
  30. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/docs/SPEC_JOBS_AND_BROWSE.md +0 -0
  31. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/docs/UX_POLISH_PLAN.md +0 -0
  32. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/__init__.py +0 -0
  33. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/__main__.py +0 -0
  34. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/api_gate/__init__.py +0 -0
  35. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/api_gate/client.py +0 -0
  36. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/app.py +0 -0
  37. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/auth/__init__.py +0 -0
  38. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/auth/otp.py +0 -0
  39. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/auth/rate_limit.py +0 -0
  40. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/auth/session.py +0 -0
  41. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/catalog/__init__.py +0 -0
  42. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/catalog/db/__init__.py +0 -0
  43. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/catalog/db/_connection.py +0 -0
  44. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/catalog/db/_enrichment.py +0 -0
  45. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/catalog/db/_feedback.py +0 -0
  46. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/catalog/db/_people.py +0 -0
  47. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/catalog/db/_playlists.py +0 -0
  48. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/catalog/db/_queue.py +0 -0
  49. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/catalog/db/_watchlist.py +0 -0
  50. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/catalog/enrichment/__init__.py +0 -0
  51. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/catalog/enrichment/classify.py +0 -0
  52. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/catalog/enrichment/normalise.py +0 -0
  53. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/catalog/enrichment/pipeline.py +0 -0
  54. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/catalog/enrichment/providers.py +0 -0
  55. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/catalog/enrichment/report.py +0 -0
  56. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/catalog/enrichment/steps/__init__.py +0 -0
  57. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/catalog/enrichment/steps/art.py +0 -0
  58. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/catalog/enrichment/steps/categories.py +0 -0
  59. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/catalog/enrichment/steps/meta.py +0 -0
  60. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/catalog/enrichment/steps/sync.py +0 -0
  61. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/catalog/enrichment/steps/tags.py +0 -0
  62. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/catalog/enrichment/steps/title.py +0 -0
  63. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/catalog/images.py +0 -0
  64. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/catalog/mediacms.py +0 -0
  65. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/catalog/sync.py +0 -0
  66. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/config.py +0 -0
  67. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/integrations/__init__.py +0 -0
  68. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/integrations/cmsutils/__init__.py +0 -0
  69. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/integrations/cmsutils/_common.py +0 -0
  70. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/integrations/cmsutils/enrichmeta.py +0 -0
  71. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/integrations/cmsutils/enrichtitles.py +0 -0
  72. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/integrations/cmsutils/enrichtv.py +0 -0
  73. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/integrations/cmsutils/fetchurls.py +0 -0
  74. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/integrations/cmsutils/motdposters.py +0 -0
  75. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/integrations/cmsutils/playedmovies.py +0 -0
  76. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/integrations/ytpipe/__init__.py +0 -0
  77. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/integrations/ytpipe/downloader.py +0 -0
  78. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/jobs/__init__.py +0 -0
  79. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/jobs/fetchurls_auth.py +0 -0
  80. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/jobs/job_scheduler.py +0 -0
  81. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/jobs/manager.py +0 -0
  82. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/jobs/rehost_emotes.py +0 -0
  83. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/jobs/tasks.py +0 -0
  84. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/logging_config.py +0 -0
  85. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/playlists/__init__.py +0 -0
  86. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/playlists/bulk_add.py +0 -0
  87. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/playlists/fire.py +0 -0
  88. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/playlists/importer.py +0 -0
  89. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/playlists/ordering.py +0 -0
  90. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/playlists/scheduler.py +0 -0
  91. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/promos/__init__.py +0 -0
  92. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/promos/director.py +0 -0
  93. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/queue/__init__.py +0 -0
  94. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/queue/completion.py +0 -0
  95. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/queue/poller.py +0 -0
  96. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/queue/presence.py +0 -0
  97. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/queue/race_poller.py +0 -0
  98. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/queue/shadow.py +0 -0
  99. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/routes/__init__.py +0 -0
  100. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/routes/admin_catalog.py +0 -0
  101. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/routes/admin_feedback.py +0 -0
  102. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/routes/admin_job_schedules.py +0 -0
  103. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/routes/admin_jobs.py +0 -0
  104. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/routes/admin_moderation.py +0 -0
  105. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/routes/admin_playlists.py +0 -0
  106. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/routes/admin_promos.py +0 -0
  107. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/routes/admin_queue.py +0 -0
  108. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/routes/admin_schedules.py +0 -0
  109. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/routes/auth.py +0 -0
  110. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/routes/catalog.py +0 -0
  111. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/routes/feedback.py +0 -0
  112. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/routes/pages.py +0 -0
  113. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/routes/queue.py +0 -0
  114. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/routes/user.py +0 -0
  115. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/static/css/main.css +0 -0
  116. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/static/js/admin-playlists.js +0 -0
  117. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/static/js/admin-promos.js +0 -0
  118. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/static/js/admin-queue-mgmt.js +0 -0
  119. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/static/js/admin-schedules.js +0 -0
  120. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/static/js/admin.js +0 -0
  121. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/static/js/main.js +0 -0
  122. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/templates/admin/index.html +0 -0
  123. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/templates/admin/playlists.html +0 -0
  124. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/templates/admin/promos.html +0 -0
  125. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/templates/admin/queue_mgmt.html +0 -0
  126. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/templates/admin/schedules.html +0 -0
  127. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/templates/auth/login.html +0 -0
  128. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/templates/base.html +0 -0
  129. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/templates/catalog/browse.html +0 -0
  130. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/templates/catalog/item_detail.html +0 -0
  131. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/templates/catalog/item_not_found.html +0 -0
  132. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/templates/feedback/index.html +0 -0
  133. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/templates/queue/index.html +0 -0
  134. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/templates/race.html +0 -0
  135. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/templates/user/dashboard.html +0 -0
  136. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/templates/user/my_list.html +0 -0
  137. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/ws/__init__.py +0 -0
  138. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/ws/handler.py +0 -0
  139. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/kryten_webqueue/ws/manager.py +0 -0
  140. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/tests/__init__.py +0 -0
  141. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/tests/test_classify.py +0 -0
  142. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/tests/test_config_persistence.py +0 -0
  143. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/tests/test_feedback.py +0 -0
  144. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/tests/test_fetchurls_sharepoint.py +0 -0
  145. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/tests/test_people_facets.py +0 -0
  146. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/tests/test_phase1.py +0 -0
  147. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/tests/test_phase2_jobs.py +0 -0
  148. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/tests/test_phase3_jobs.py +0 -0
  149. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/tests/test_phase4_live_fixes.py +0 -0
  150. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/tests/test_playlist_import.py +0 -0
  151. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/tests/test_presence_refund.py +0 -0
  152. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/tests/test_promo_director.py +0 -0
  153. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/tests/test_promo_pool_exclusion.py +0 -0
  154. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/tests/test_providers.py +0 -0
  155. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/tests/test_queue_announce.py +0 -0
  156. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/tests/test_race_poller.py +0 -0
  157. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/tests/test_recently_played_hide.py +0 -0
  158. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/tests/test_save_results_to_playlist.py +0 -0
  159. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/tests/test_schedule_lock.py +0 -0
  160. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/tests/test_search_facets.py +0 -0
  161. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/tests/test_title_normalization.py +0 -0
  162. {kryten_webqueue-0.35.4 → kryten_webqueue-0.35.6}/tests/test_watchlist.py +0 -0
@@ -2,6 +2,28 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.35.6] - 2026-08-14
6
+
7
+ ### Fixed
8
+
9
+ - **Better error messages for deleted media items.** When attempting to queue
10
+ an item that no longer exists in MediaCMS (has been deleted), users now see
11
+ "Item no longer available (media may have been deleted)" instead of the
12
+ generic httpx error message "Expected HTTP 200 OK, not 500 Internal Server
13
+ Error". Also added specific handling for 404 responses and attempts to
14
+ extract short error messages from CyTube responses.
15
+
16
+ ## [0.35.5] - 2026-08-14
17
+
18
+ ### Fixed
19
+
20
+ - **SQL binding parameter error in enrichment pipeline.** Fixed `update_catalog`
21
+ method to support partial updates (updating only specific fields like title
22
+ or description without requiring all fields). Previously, the method required
23
+ all catalog fields to be present, causing "You did not supply a value for
24
+ binding parameter" errors when the meta and title enrichment steps attempted
25
+ partial updates. Now builds SQL dynamically based on provided fields.
26
+
5
27
  ## [0.35.4] - 2026-08-13
6
28
 
7
29
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: kryten-webqueue
3
- Version: 0.35.4
3
+ Version: 0.35.6
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
@@ -792,25 +792,54 @@ class _CatalogMixin:
792
792
  await self._db.commit()
793
793
 
794
794
  async def update_catalog(self, friendly_token: str, row: dict):
795
- sql = """
796
- UPDATE catalog SET title=:title, description=:description,
797
- duration_sec=:duration_sec, manifest_url=:manifest_url,
798
- thumbnail_url=:thumbnail_url,
799
- added_at=COALESCE(:added_at, added_at),
800
- synced_at=:synced_at, updated_at=:synced_at
801
- WHERE friendly_token=:friendly_token
795
+ """Update catalog item with partial or full field set.
796
+
797
+ Only fields present in *row* are updated. Always adds friendly_token
798
+ and updates updated_at to current time when any field changes.
802
799
  """
803
- row = {"added_at": None, **row}
804
- await self._db.execute(sql, row)
805
- # Rebuild FTS for this row
806
- await self._db.execute(
807
- "DELETE FROM catalog_fts WHERE friendly_token = ?", [friendly_token]
808
- )
809
- await self._db.execute(
810
- "INSERT INTO catalog_fts(rowid, friendly_token, title, description) "
811
- "SELECT rowid, friendly_token, title, description FROM catalog WHERE friendly_token = ?",
812
- [friendly_token],
813
- )
800
+ if not row:
801
+ return
802
+
803
+ # Build SET clause from provided fields
804
+ allowed = {
805
+ "title", "description", "duration_sec", "manifest_url",
806
+ "thumbnail_url", "added_at", "synced_at"
807
+ }
808
+ updates = []
809
+ params = {"friendly_token": friendly_token}
810
+
811
+ for key in row:
812
+ if key in allowed:
813
+ # added_at uses COALESCE to preserve existing if None
814
+ if key == "added_at":
815
+ updates.append("added_at=COALESCE(:added_at, added_at)")
816
+ else:
817
+ updates.append(f"{key}=:{key}")
818
+ params[key] = row[key]
819
+
820
+ # Always update updated_at when modifying
821
+ updates.append("updated_at=:updated_at")
822
+ params["updated_at"] = row.get("synced_at") or params.get("synced_at")
823
+ if not params["updated_at"]:
824
+ from datetime import datetime, UTC
825
+ params["updated_at"] = datetime.now(UTC).isoformat()
826
+
827
+ if not updates:
828
+ return
829
+
830
+ sql = f"UPDATE catalog SET {', '.join(updates)} WHERE friendly_token=:friendly_token"
831
+ await self._db.execute(sql, params)
832
+
833
+ # Rebuild FTS for this row if title or description changed
834
+ if "title" in row or "description" in row:
835
+ await self._db.execute(
836
+ "DELETE FROM catalog_fts WHERE friendly_token = ?", [friendly_token]
837
+ )
838
+ await self._db.execute(
839
+ "INSERT INTO catalog_fts(rowid, friendly_token, title, description) "
840
+ "SELECT rowid, friendly_token, title, description FROM catalog WHERE friendly_token = ?",
841
+ [friendly_token],
842
+ )
814
843
  await self._db.commit()
815
844
 
816
845
  async def update_cover_art(self, friendly_token: str, path: str, source: str):
@@ -21,7 +21,20 @@ def _add_failure_reason(
21
21
  return str(detail)
22
22
  except Exception:
23
23
  pass
24
- return f"playlist add failed ({exc.response.status_code})"
24
+ # Check for common error scenarios
25
+ if exc.response.status_code == 500:
26
+ # CyTube 500 often means the media doesn't exist in MediaCMS
27
+ return "Item no longer available (media may have been deleted)"
28
+ if exc.response.status_code == 404:
29
+ return "Media not found"
30
+ # Try to extract text from response body
31
+ try:
32
+ text = exc.response.text
33
+ if text and len(text) < 200: # Only use short error messages
34
+ return f"Playlist add failed: {text}"
35
+ except Exception:
36
+ pass
37
+ return f"Playlist add failed (HTTP {exc.response.status_code})"
25
38
  if add_result is not None:
26
39
  return str(add_result.get("error", "Failed to add to playlist"))
27
40
  return "Failed to add to playlist"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "kryten-webqueue"
3
- version = "0.35.4"
3
+ version = "0.35.6"
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"