kryten-webqueue 0.36.1__tar.gz → 0.36.3__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.
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/CHANGELOG.md +29 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/PKG-INFO +1 -1
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/config.example.json +1 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/catalog/enrichment/steps/tags.py +50 -12
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/config.py +3 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/pyproject.toml +1 -1
- kryten_webqueue-0.36.3/scripts/patch_mediacms_tags.py +90 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/.github/workflows/gitleaks.yml +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/.github/workflows/python-publish.yml +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/.github/workflows/release.yml +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/.gitignore +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/.gitleaks.toml +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/.pre-commit-config.yaml +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/AGENT-WORKFLOW-GUIDE.md +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/README.md +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/deploy/kryten-webqueue.service +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/deploy/nginx-queue.conf +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/docs/IMPLEMENTATION_SPEC.md +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/docs/IMPL_API_GATE.md +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/docs/IMPL_ECONOMY.md +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/docs/IMPL_KRYTEN_PY.md +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/docs/IMPL_ROBOT.md +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/docs/Kryten-Economy Service Quality Survey Report at v0.13.0.md +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/docs/Kryten-Webqueue Service Quality Survey Report at v0.27.1.md +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/docs/Log for incorrect promo insertion problem.txt +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/docs/PLAN_PRESENCE_AND_PROMOS.md +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/docs/PRE_PLAN_GAPS.md +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/docs/PRODUCT_PLAN.md +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/docs/SPEC_CATALOG_ENRICHMENT_PIPELINE.md +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/docs/SPEC_EMOTE_REHOST.md +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/docs/SPEC_JOBS_AND_BROWSE.md +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/docs/UX_POLISH_PLAN.md +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/__init__.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/__main__.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/api_gate/__init__.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/api_gate/client.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/app.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/auth/__init__.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/auth/otp.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/auth/rate_limit.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/auth/session.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/catalog/__init__.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/catalog/db/__init__.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/catalog/db/_catalog.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/catalog/db/_connection.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/catalog/db/_enrichment.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/catalog/db/_feedback.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/catalog/db/_people.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/catalog/db/_playlists.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/catalog/db/_queue.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/catalog/db/_watchlist.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/catalog/enrichment/__init__.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/catalog/enrichment/classify.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/catalog/enrichment/normalise.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/catalog/enrichment/pipeline.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/catalog/enrichment/providers.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/catalog/enrichment/report.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/catalog/enrichment/steps/__init__.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/catalog/enrichment/steps/art.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/catalog/enrichment/steps/categories.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/catalog/enrichment/steps/meta.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/catalog/enrichment/steps/sync.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/catalog/enrichment/steps/title.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/catalog/images.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/catalog/mediacms.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/catalog/sync.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/integrations/__init__.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/integrations/cmsutils/__init__.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/integrations/cmsutils/_common.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/integrations/cmsutils/enrichmeta.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/integrations/cmsutils/enrichtitles.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/integrations/cmsutils/enrichtv.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/integrations/cmsutils/fetchurls.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/integrations/cmsutils/motdposters.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/integrations/cmsutils/playedmovies.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/integrations/ytpipe/__init__.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/integrations/ytpipe/downloader.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/jobs/__init__.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/jobs/fetchurls_auth.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/jobs/job_scheduler.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/jobs/manager.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/jobs/rehost_emotes.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/jobs/tasks.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/logging_config.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/playlists/__init__.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/playlists/bulk_add.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/playlists/fire.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/playlists/importer.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/playlists/ordering.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/playlists/scheduler.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/promos/__init__.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/promos/director.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/queue/__init__.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/queue/completion.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/queue/ordering.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/queue/poller.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/queue/presence.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/queue/race_poller.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/queue/shadow.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/routes/__init__.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/routes/admin_catalog.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/routes/admin_feedback.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/routes/admin_job_schedules.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/routes/admin_jobs.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/routes/admin_moderation.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/routes/admin_playlists.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/routes/admin_promos.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/routes/admin_queue.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/routes/admin_schedules.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/routes/auth.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/routes/catalog.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/routes/feedback.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/routes/pages.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/routes/queue.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/routes/user.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/static/css/main.css +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/static/js/admin-playlists.js +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/static/js/admin-promos.js +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/static/js/admin-queue-mgmt.js +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/static/js/admin-schedules.js +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/static/js/admin.js +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/static/js/main.js +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/templates/admin/index.html +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/templates/admin/playlists.html +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/templates/admin/promos.html +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/templates/admin/queue_mgmt.html +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/templates/admin/schedules.html +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/templates/auth/login.html +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/templates/base.html +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/templates/catalog/browse.html +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/templates/catalog/item_detail.html +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/templates/catalog/item_not_found.html +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/templates/feedback/index.html +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/templates/queue/index.html +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/templates/race.html +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/templates/user/dashboard.html +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/templates/user/my_list.html +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/ws/__init__.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/ws/handler.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/ws/manager.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/tests/__init__.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/tests/test_classify.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/tests/test_config_persistence.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/tests/test_feedback.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/tests/test_fetchurls_sharepoint.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/tests/test_people_facets.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/tests/test_phase1.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/tests/test_phase2_jobs.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/tests/test_phase3_jobs.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/tests/test_phase4_live_fixes.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/tests/test_playlist_import.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/tests/test_presence_refund.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/tests/test_promo_director.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/tests/test_promo_pool_exclusion.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/tests/test_providers.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/tests/test_queue_announce.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/tests/test_race_poller.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/tests/test_recently_played_hide.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/tests/test_save_results_to_playlist.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/tests/test_schedule_lock.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/tests/test_search_facets.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/tests/test_title_normalization.py +0 -0
- {kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/tests/test_watchlist.py +0 -0
|
@@ -2,6 +2,35 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.36.3] - 2026-08-14
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **`mediacms_manage_all_media` config flag.** When set (paired with a CMS whose
|
|
10
|
+
`bulk_actions` is patched to let managers/superusers tag any media), the tags
|
|
11
|
+
step pushes derived genre/MPAA/hosted tags to CMS for every item, not just
|
|
12
|
+
media the API user owns. Defaults to `false` (owner-only, safe).
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- **MPAA rating tags are now alphanumeric** (`mpaar`, `mpaapg13`, …) to match
|
|
17
|
+
MediaCMS's `Tag.save()` normalization (which strips non-alphanumerics), so tags
|
|
18
|
+
round-trip cleanly between the local catalog and CMS without creating hyphenated
|
|
19
|
+
duplicates on reverse-sync.
|
|
20
|
+
|
|
21
|
+
## [0.36.2] - 2026-08-14
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- **Stop flooding MediaCMS with futile tag-push 400s.** The tags step attempted
|
|
26
|
+
`POST /media/user/bulk_actions` (`add_tags`) for every enriched item, but that
|
|
27
|
+
endpoint filters `Media.objects.filter(user=request.user, ...)` with no manager
|
|
28
|
+
or superuser bypass, so pushes against media owned by other users always return
|
|
29
|
+
400 "No matching media found". The step now looks up the API token's username
|
|
30
|
+
once via `/whoami` and only attempts the CMS push for media that user actually
|
|
31
|
+
owns. Local `catalog_tags` remains the source of truth for derived
|
|
32
|
+
genre/MPAA/hosted tags, so tags still display regardless.
|
|
33
|
+
|
|
5
34
|
## [0.36.1] - 2026-08-14
|
|
6
35
|
|
|
7
36
|
### Fixed
|
{kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/catalog/enrichment/steps/tags.py
RENAMED
|
@@ -18,15 +18,15 @@ _TIMEOUT = 20.0
|
|
|
18
18
|
_MPAA = frozenset({"G", "PG", "PG-13", "R", "NC-17", "NR", "TV-MA", "TV-14", "TV-PG"})
|
|
19
19
|
|
|
20
20
|
_MPAA_SLUG = {
|
|
21
|
-
"G": "
|
|
22
|
-
"PG": "
|
|
23
|
-
"PG-13": "
|
|
24
|
-
"R": "
|
|
25
|
-
"NC-17": "
|
|
26
|
-
"NR": "
|
|
27
|
-
"TV-MA": "
|
|
28
|
-
"TV-14": "
|
|
29
|
-
"TV-PG": "
|
|
21
|
+
"G": "mpaag",
|
|
22
|
+
"PG": "mpaapg",
|
|
23
|
+
"PG-13": "mpaapg13",
|
|
24
|
+
"R": "mpaar",
|
|
25
|
+
"NC-17": "mpaanc17",
|
|
26
|
+
"NR": "mpaanr",
|
|
27
|
+
"TV-MA": "mpaatvma",
|
|
28
|
+
"TV-14": "mpaatv14",
|
|
29
|
+
"TV-PG": "mpaatvpg",
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
|
|
@@ -41,6 +41,25 @@ class TagsStep:
|
|
|
41
41
|
config.mediacms_url.rstrip("/").removesuffix("/api/v1").removesuffix("/api")
|
|
42
42
|
)
|
|
43
43
|
self._headers = {"Authorization": f"Token {config.mediacms_token}"}
|
|
44
|
+
# When the token can manage all media (e.g. a manager/superuser on a
|
|
45
|
+
# patched CMS), push tags for every item instead of only owned media.
|
|
46
|
+
self._manage_all = bool(getattr(config, "mediacms_manage_all_media", False))
|
|
47
|
+
|
|
48
|
+
async def _get_api_username(self, client: httpx.AsyncClient) -> str | None:
|
|
49
|
+
"""Return the API token's username, or None.
|
|
50
|
+
|
|
51
|
+
Stock MediaCMS ``/media/user/bulk_actions`` filters ``Media.objects.filter(
|
|
52
|
+
user=request.user, ...)``, so tag pushes only succeed on media this user
|
|
53
|
+
owns. When ``mediacms_manage_all_media`` is not set we use this to skip
|
|
54
|
+
futile pushes against media owned by others instead of flooding CMS.
|
|
55
|
+
"""
|
|
56
|
+
try:
|
|
57
|
+
resp = await client.get(f"{self._base}/api/v1/whoami")
|
|
58
|
+
if resp.status_code != 200:
|
|
59
|
+
return None
|
|
60
|
+
return resp.json().get("username")
|
|
61
|
+
except (httpx.HTTPError, json.JSONDecodeError, ValueError):
|
|
62
|
+
return None
|
|
44
63
|
|
|
45
64
|
async def run(
|
|
46
65
|
self,
|
|
@@ -54,6 +73,11 @@ class TagsStep:
|
|
|
54
73
|
now = datetime.now(UTC).isoformat()
|
|
55
74
|
|
|
56
75
|
async with httpx.AsyncClient(headers=self._headers, timeout=_TIMEOUT) as client:
|
|
76
|
+
api_user = (
|
|
77
|
+
None
|
|
78
|
+
if dry_run or self._manage_all
|
|
79
|
+
else await self._get_api_username(client)
|
|
80
|
+
)
|
|
57
81
|
for cls in classifications:
|
|
58
82
|
result.processed += 1
|
|
59
83
|
try:
|
|
@@ -88,7 +112,10 @@ class TagsStep:
|
|
|
88
112
|
for tag in desired:
|
|
89
113
|
await self._db.add_catalog_tag(cls.friendly_token, tag)
|
|
90
114
|
changed = await self._push_tags(
|
|
91
|
-
client,
|
|
115
|
+
client,
|
|
116
|
+
cls.friendly_token,
|
|
117
|
+
desired,
|
|
118
|
+
api_user=api_user,
|
|
92
119
|
)
|
|
93
120
|
# Reverse sync: pull any CMS-only tags back to local DB
|
|
94
121
|
await self._reverse_sync(client, cls.friendly_token)
|
|
@@ -110,13 +137,21 @@ class TagsStep:
|
|
|
110
137
|
return result
|
|
111
138
|
|
|
112
139
|
async def _push_tags(
|
|
113
|
-
self,
|
|
140
|
+
self,
|
|
141
|
+
client: httpx.AsyncClient,
|
|
142
|
+
token: str,
|
|
143
|
+
new_tags: list[str],
|
|
144
|
+
*,
|
|
145
|
+
api_user: str | None = None,
|
|
114
146
|
) -> bool:
|
|
115
147
|
"""Add tags to a CMS media item via the bulk_actions ``add_tags`` action.
|
|
116
148
|
|
|
117
149
|
The ``PUT /media/{token}`` endpoint only accepts title/description/media_file
|
|
118
150
|
and silently ignores a ``tags`` field, so tag changes must go through
|
|
119
|
-
``POST /media/user/bulk_actions`` with ``action: add_tags``.
|
|
151
|
+
``POST /media/user/bulk_actions`` with ``action: add_tags``. On stock
|
|
152
|
+
MediaCMS that endpoint is owner-scoped, so unless ``manage_all`` is set we
|
|
153
|
+
skip the push for media the API user does not own rather than flooding
|
|
154
|
+
CMS with 400s.
|
|
120
155
|
"""
|
|
121
156
|
detail_url = f"{self._base}/api/v1/media/{token}"
|
|
122
157
|
try:
|
|
@@ -124,6 +159,9 @@ class TagsStep:
|
|
|
124
159
|
if resp.status_code != 200:
|
|
125
160
|
return False
|
|
126
161
|
data = resp.json()
|
|
162
|
+
owner = data.get("user")
|
|
163
|
+
if not self._manage_all and (api_user is None or owner != api_user):
|
|
164
|
+
return False
|
|
127
165
|
current = {
|
|
128
166
|
(t.get("title") if isinstance(t, dict) else t)
|
|
129
167
|
for t in (data.get("tags_info") or [])
|
|
@@ -151,6 +151,9 @@ class Config(BaseModel):
|
|
|
151
151
|
# MediaCMS
|
|
152
152
|
mediacms_url: str = "https://www.dropsugar.com"
|
|
153
153
|
mediacms_token: str
|
|
154
|
+
# Set true when the token can tag any media (manager/superuser on a CMS whose
|
|
155
|
+
# bulk_actions is patched to allow it); pushes derived tags for all items.
|
|
156
|
+
mediacms_manage_all_media: bool = False
|
|
154
157
|
|
|
155
158
|
# Cover art APIs
|
|
156
159
|
tmdb_api_key: str = ""
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Patch MediaCMS MediaBulkUserActions so editors/superusers can tag any media,
|
|
3
|
+
and add_tags creates missing tags (normalized like Tag.save)."""
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
PATH = "/home/mediacms.io/mediacms/files/views/media.py"
|
|
8
|
+
|
|
9
|
+
QS_OLD = (
|
|
10
|
+
" media = Media.objects.filter(user=request.user, friendly_token__in=media_ids)\n"
|
|
11
|
+
)
|
|
12
|
+
QS_NEW = (
|
|
13
|
+
" if is_mediacms_editor(request.user):\n"
|
|
14
|
+
" media = Media.objects.filter(friendly_token__in=media_ids)\n"
|
|
15
|
+
" else:\n"
|
|
16
|
+
" media = Media.objects.filter(user=request.user, friendly_token__in=media_ids)\n"
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
TAGS_OLD = (
|
|
20
|
+
' elif action == "add_tags":\n'
|
|
21
|
+
" tag_titles = request.data.get('tag_titles', [])\n"
|
|
22
|
+
" if not tag_titles:\n"
|
|
23
|
+
' return Response({"detail": "tag_titles is required for add_tags action"}, status=status.HTTP_400_BAD_REQUEST)\n'
|
|
24
|
+
"\n"
|
|
25
|
+
" tags = Tag.objects.filter(title__in=tag_titles)\n"
|
|
26
|
+
" if not tags:\n"
|
|
27
|
+
' return Response({"detail": "No matching tags found"}, status=status.HTTP_400_BAD_REQUEST)\n'
|
|
28
|
+
"\n"
|
|
29
|
+
" added_count = 0\n"
|
|
30
|
+
" for tag in tags:\n"
|
|
31
|
+
" for m in media:\n"
|
|
32
|
+
" if not m.tags.filter(title=tag.title).exists():\n"
|
|
33
|
+
" m.tags.add(tag)\n"
|
|
34
|
+
" added_count += 1\n"
|
|
35
|
+
"\n"
|
|
36
|
+
' return Response({"detail": f"Added {added_count} media items to {tags.count()} tags"})\n'
|
|
37
|
+
)
|
|
38
|
+
TAGS_NEW = (
|
|
39
|
+
' elif action == "add_tags":\n'
|
|
40
|
+
" tag_titles = request.data.get('tag_titles', [])\n"
|
|
41
|
+
" if not tag_titles:\n"
|
|
42
|
+
' return Response({"detail": "tag_titles is required for add_tags action"}, status=status.HTTP_400_BAD_REQUEST)\n'
|
|
43
|
+
"\n"
|
|
44
|
+
" tags = []\n"
|
|
45
|
+
" for raw in tag_titles:\n"
|
|
46
|
+
" title = helpers.get_alphanumeric_only(raw)[:100]\n"
|
|
47
|
+
" if not title:\n"
|
|
48
|
+
" continue\n"
|
|
49
|
+
" tag, _ = Tag.objects.get_or_create(title=title, defaults={\"user\": request.user})\n"
|
|
50
|
+
" tags.append(tag)\n"
|
|
51
|
+
" if not tags:\n"
|
|
52
|
+
' return Response({"detail": "No matching tags found"}, status=status.HTTP_400_BAD_REQUEST)\n'
|
|
53
|
+
"\n"
|
|
54
|
+
" added_count = 0\n"
|
|
55
|
+
" for tag in tags:\n"
|
|
56
|
+
" for m in media:\n"
|
|
57
|
+
" if not m.tags.filter(title=tag.title).exists():\n"
|
|
58
|
+
" m.tags.add(tag)\n"
|
|
59
|
+
" added_count += 1\n"
|
|
60
|
+
"\n"
|
|
61
|
+
' return Response({"detail": f"Added {added_count} media items to {len(tags)} tags"})\n'
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def main() -> int:
|
|
66
|
+
with open(PATH, encoding="utf-8") as f:
|
|
67
|
+
src = f.read()
|
|
68
|
+
|
|
69
|
+
if QS_NEW in src and TAGS_NEW in src:
|
|
70
|
+
print("Already patched; nothing to do.")
|
|
71
|
+
return 0
|
|
72
|
+
|
|
73
|
+
if src.count(QS_OLD) != 1:
|
|
74
|
+
print(f"ERROR: queryset anchor found {src.count(QS_OLD)} times (expected 1)")
|
|
75
|
+
return 2
|
|
76
|
+
if src.count(TAGS_OLD) != 1:
|
|
77
|
+
print(f"ERROR: add_tags anchor found {src.count(TAGS_OLD)} times (expected 1)")
|
|
78
|
+
return 3
|
|
79
|
+
|
|
80
|
+
src = src.replace(QS_OLD, QS_NEW, 1)
|
|
81
|
+
src = src.replace(TAGS_OLD, TAGS_NEW, 1)
|
|
82
|
+
|
|
83
|
+
with open(PATH, "w", encoding="utf-8") as f:
|
|
84
|
+
f.write(src)
|
|
85
|
+
print("Patched OK.")
|
|
86
|
+
return 0
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
if __name__ == "__main__":
|
|
90
|
+
sys.exit(main())
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/docs/Log for incorrect promo insertion problem.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/catalog/enrichment/__init__.py
RENAMED
|
File without changes
|
{kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/catalog/enrichment/classify.py
RENAMED
|
File without changes
|
{kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/catalog/enrichment/normalise.py
RENAMED
|
File without changes
|
{kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/catalog/enrichment/pipeline.py
RENAMED
|
File without changes
|
{kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/catalog/enrichment/providers.py
RENAMED
|
File without changes
|
{kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/catalog/enrichment/report.py
RENAMED
|
File without changes
|
|
File without changes
|
{kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/catalog/enrichment/steps/art.py
RENAMED
|
File without changes
|
|
File without changes
|
{kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/catalog/enrichment/steps/meta.py
RENAMED
|
File without changes
|
{kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/catalog/enrichment/steps/sync.py
RENAMED
|
File without changes
|
{kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/catalog/enrichment/steps/title.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/integrations/cmsutils/__init__.py
RENAMED
|
File without changes
|
{kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/integrations/cmsutils/_common.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/integrations/cmsutils/enrichtv.py
RENAMED
|
File without changes
|
{kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/integrations/cmsutils/fetchurls.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/integrations/ytpipe/__init__.py
RENAMED
|
File without changes
|
{kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/integrations/ytpipe/downloader.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/routes/admin_job_schedules.py
RENAMED
|
File without changes
|
|
File without changes
|
{kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/routes/admin_moderation.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/static/js/admin-playlists.js
RENAMED
|
File without changes
|
|
File without changes
|
{kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/static/js/admin-queue-mgmt.js
RENAMED
|
File without changes
|
{kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/static/js/admin-schedules.js
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/templates/admin/index.html
RENAMED
|
File without changes
|
{kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/templates/admin/playlists.html
RENAMED
|
File without changes
|
{kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/templates/admin/promos.html
RENAMED
|
File without changes
|
{kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/templates/admin/queue_mgmt.html
RENAMED
|
File without changes
|
{kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/templates/admin/schedules.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/templates/catalog/browse.html
RENAMED
|
File without changes
|
{kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/templates/catalog/item_detail.html
RENAMED
|
File without changes
|
|
File without changes
|
{kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/templates/feedback/index.html
RENAMED
|
File without changes
|
{kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/templates/queue/index.html
RENAMED
|
File without changes
|
|
File without changes
|
{kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/templates/user/dashboard.html
RENAMED
|
File without changes
|
{kryten_webqueue-0.36.1 → kryten_webqueue-0.36.3}/kryten_webqueue/templates/user/my_list.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|