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');