YouTube playlist support: /api/youtube/playlist-preview za pred-confirm, /api/youtube zaznava 'list=' URL in kreira batch (1 job/video). Qnet auto-match na YT naslovu, Confirm dialog v UI z prvih 5 naslovov.

This commit is contained in:
OpenClaw Agent
2026-05-02 12:34:27 +00:00
parent 7a7d7ea20d
commit 77075795ce
3 changed files with 252 additions and 7 deletions
+50 -5
View File
@@ -340,8 +340,9 @@
</div>
<div id="tab-youtube" class="hidden">
<label>YouTube URL</label>
<input type="url" id="yt-url" placeholder="https://www.youtube.com/watch?v=...">
<label>YouTube URL <span style="font-size:11px; color:var(--muted); font-weight:normal;">— en video ali cela playlist</span></label>
<input type="url" id="yt-url" placeholder="https://www.youtube.com/watch?v=... ali https://www.youtube.com/playlist?list=...">
<div style="font-size:11px; color:var(--muted); margin-top:4px;">💡 Playlist: vsak komad postane svoj reel — Qnet auto-match po naslovu</div>
</div>
<!-- TV postaja: določa Nextcloud target mapo -->
@@ -820,7 +821,42 @@
if (isYT) {
const url = $("#yt-url").value.trim();
if (!url) { alert("Vpiši YouTube URL"); $("#submit-btn").disabled = false; return; }
showLive("Pošiljam YouTube job...", url, null);
// Detect playlist URL
const isPlaylist = /\/playlist\?/.test(url) || (url.includes("list=") && !url.match(/[?&]v=/));
if (isPlaylist) {
showLive("Berem playlist...", url, null);
try {
const previewR = await fetch("/api/youtube/playlist-preview", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ url }),
});
if (!previewR.ok) {
const err = await previewR.text();
liveFail("Ne morem prebrati playlist: " + err);
$("#submit-btn").disabled = false;
return;
}
const preview = await previewR.json();
const count = preview.items ? preview.items.length : 0;
const titlePreview = preview.items.slice(0, 5).map(it => ` • ${it.title}`).join("\n");
const moreText = count > 5 ? `\n ... in še ${count - 5} ostalih` : "";
const confirm_msg = `Najdenih ${count} komadov v playlistu "${preview.playlist_title}":\n\n${titlePreview}${moreText}\n\nNaloži vse in obdelaj?`;
if (!confirm(confirm_msg)) {
liveReset();
$("#submit-btn").disabled = false;
return;
}
} catch (e) {
liveFail("Playlist preview napaka: " + e.message);
$("#submit-btn").disabled = false;
return;
}
}
showLive(isPlaylist ? "Pošiljam playlist v queue..." : "Pošiljam YouTube job...", url, null);
const r = await fetch("/api/youtube", {
method: "POST",
headers: { "Content-Type": "application/json" },
@@ -831,8 +867,17 @@
liveFail("YouTube submit napaka: " + err);
return;
}
const job = await r.json();
watchJob(job.id);
const data = await r.json();
if (data.is_playlist) {
// Batch playlist response
showLive(`✅ ${data.count} komadov v queueu`,
`Playlist "${data.playlist_title}" — obdelujejo se zaporedno`, 100);
// Watch all jobs
(data.jobs || []).forEach(j => watchJob(j.id));
} else {
// Single video
watchJob(data.id);
}
refreshJobs();
} else {
if (pendingFiles.length === 0) {