diff --git a/core/automation/handlers/video_process_youtube_wishlist.py b/core/automation/handlers/video_process_youtube_wishlist.py index e76d5718..219bfdcc 100644 --- a/core/automation/handlers/video_process_youtube_wishlist.py +++ b/core/automation/handlers/video_process_youtube_wishlist.py @@ -50,6 +50,7 @@ def enqueue_ctx(video: Dict[str, Any], channel_settings: Dict[str, Any]) -> Dict cs = channel_settings if isinstance(channel_settings, dict) else {} ctx = { "channel": cs.get("custom_name") or video.get("channel_title"), + "channel_id": video.get("channel_id"), # so the drawer can open the in-app channel page "video_title": video.get("video_title"), "published_at": video.get("published_at"), } diff --git a/database/video_database.py b/database/video_database.py index 27e5c15d..4e96613c 100644 --- a/database/video_database.py +++ b/database/video_database.py @@ -1480,7 +1480,7 @@ class VideoDatabase: conn = self._get_connection() try: r = conn.execute( - "SELECT title, thumbnail_url, duration, view_count " + "SELECT channel_id, title, thumbnail_url, duration, view_count " "FROM youtube_channel_videos WHERE youtube_id=? LIMIT 1", (youtube_id,)).fetchone() return dict(r) if r else None finally: diff --git a/tests/test_video_downloads_page.py b/tests/test_video_downloads_page.py index 4d6bf3a6..94f47cae 100644 --- a/tests/test_video_downloads_page.py +++ b/tests/test_video_downloads_page.py @@ -50,15 +50,15 @@ def test_drawer_renders_the_rich_tmdb_fields(): assert "trailer_url" in _JS and "providers" in _JS -def test_youtube_open_button_links_to_youtube_not_a_show_page(): +def test_youtube_open_button_opens_the_channel_page_not_a_show(): # regression: the open-show button ran parseInt(youtube_id) → opened a random library - # show (e.g. id 3) or a broken link. youtube cards must open a YouTube link instead. + # show (id 3 → '3rd Rock'). youtube cards must open the in-app CHANNEL page instead. open_block = _JS.split("var openBtn")[1].split("var stateBtn")[0] assert "dlType(d.kind) === 'youtube'" in open_block - assert "youtube.com/watch?v=" in open_block - # the data-vdpg-open (open-show) path must NOT be taken for youtube - assert "data-vdpg-open" in open_block # still there for movie/show - assert open_block.index("'youtube'") < open_block.index("data-vdpg-open") # youtube branch first + assert "data-vdpg-open-channel" in open_block # opens the in-app channel page + assert "youtube.com/watch?v=" in open_block # fallback when the channel id is unknown + # clicking it dispatches an open-detail for the channel (reuses the show-detail page) + assert "kind: 'channel', source: 'youtube'" in _JS def test_drawer_has_episode_youtube_and_availability_blocks(): diff --git a/tests/test_video_process_youtube_wishlist.py b/tests/test_video_process_youtube_wishlist.py index 42eeaabf..e1c7025b 100644 --- a/tests/test_video_process_youtube_wishlist.py +++ b/tests/test_video_process_youtube_wishlist.py @@ -34,6 +34,13 @@ def test_videos_to_enqueue_skips_already_queued_no_cap(): assert [v["video_id"] for v in out] == ["a", "c", "d"] # b in flight; rest ALL kept +def test_enqueue_ctx_carries_channel_id_for_the_drawer(): + from core.automation.handlers.video_process_youtube_wishlist import enqueue_ctx + ctx = enqueue_ctx({"channel_id": "UC1", "channel_title": "Chan", + "video_title": "V", "published_at": "2024-01-01"}, {}) + assert ctx["channel_id"] == "UC1" and ctx["channel"] == "Chan" + + def test_videos_to_enqueue_drops_idless(): assert videos_to_enqueue([{"video_title": "no id"}], []) == [] diff --git a/webui/static/video/video-downloads-page.js b/webui/static/video/video-downloads-page.js index 995e39af..3b8b7ff2 100644 --- a/webui/static/video/video-downloads-page.js +++ b/webui/static/video/video-downloads-page.js @@ -154,12 +154,16 @@ var lab = q('label'); if (lab.textContent !== labelTxt) lab.textContent = labelTxt; var act = q('actions'); - // youtube videos aren't TMDB titles — the open button links to YouTube, not a show page - // (parseInt on a video id was opening a random library show / a broken link). + // youtube videos aren't TMDB titles. Open the in-app CHANNEL page (channels-as-shows) + // when we know the channel id; fall back to the YouTube video link otherwise. (The old + // open-show button ran parseInt on the video id → opened a random library show.) + var ytCh = dlType(d.kind) === 'youtube' ? parseCtx(d).channel_id : null; var openBtn = !d.media_id ? '' : (dlType(d.kind) === 'youtube' - ? '' + OPEN_SVG + '' + ? (ytCh + ? '' + : '' + OPEN_SVG + '') : ''); @@ -296,6 +300,8 @@ var btns = []; if (d.media_id && !isYt) btns.push(''); + var ytCh = meta.channel_id || ctx.channel_id; + if (isYt && ytCh) btns.push(''); if (isYt && d.media_id) btns.push('Open on YouTube'); if (isActive(d.status)) btns.push(''); else if (isFail(d.status)) btns.push(''); @@ -437,6 +443,13 @@ })); return; } + var och = e.target.closest('[data-vdpg-open-channel]'); + if (och) { + document.dispatchEvent(new CustomEvent('soulsync:video-open-detail', { + detail: { kind: 'channel', source: 'youtube', id: och.getAttribute('data-vdpg-open-channel') } + })); + return; + } var cp = e.target.closest('[data-vdpg-copy]'); if (cp) { var path = cp.getAttribute('data-vdpg-copy');