您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Setzt alle flpPlaylist-Items auf free:true, zeigt Download-Liste an und fügt "Download all" für m4a/ogg hinzu
// ==UserScript== // @name Feynman FLP: Show Download Links + Download All (Firefox) // @namespace https://example.local/ // @version 1.5 // @description Setzt alle flpPlaylist-Items auf free:true, zeigt Download-Liste an und fügt "Download all" für m4a/ogg hinzu // @match *://feynmanlectures.caltech.edu/flptapes.html // @match *://www.feynmanlectures.caltech.edu/flptapes.html // @run-at document-start // @grant none // @license MIT // ==/UserScript== (function() { "use strict"; function inject(fn) { const s = document.createElement('script'); s.textContent = `(${fn})();`; (document.head || document.documentElement).appendChild(s); s.remove(); } inject(function pageContext() { // ---- Helpers ---- function sanitizeFilename(name) { return name .replace(/<[^>]+>/g, "") // HTML-Tags weg .replace(/[\\/:*?"<>|]+/g, "_") // verbotene FS-Zeichen .replace(/\s+/g, " ") .trim() .slice(0, 120); } function basename(path) { try { return path.split("?")[0].split("#")[0].split("/").pop(); } catch { return path; } } function clickDownload(url, fileName) { const a = document.createElement("a"); a.href = url; if (fileName) a.download = fileName; a.rel = "noopener"; a.target = "_blank"; // in FF hilft das oft beim Download-Flow document.body.appendChild(a); a.click(); a.remove(); } // ---- Patch + UI ---- function patchPlaylistOnce() { if (window.flpPlaylist && Array.isArray(window.flpPlaylist)) { try { window.flpPlaylist.forEach(it => { it.free = true; }); console.log("[FLP userscript] flpPlaylist gepatcht (free:true)."); } catch (e) { console.warn("[FLP userscript] Patch-Fehler:", e); } return true; } return false; } (function waitForPlaylist() { if (!patchPlaylistOnce()) { setTimeout(waitForPlaylist, 150); } else { setTimeout(buildDownloadPanel, 400); } })(); function buildDownloadPanel() { if (!Array.isArray(window.flpPlaylist) || window.flpPlaylist.length === 0) return; const base = new URL(window.location.href); const panel = document.createElement('div'); panel.id = "flp-download-panel"; Object.assign(panel.style, { position: "fixed", bottom: "16px", right: "16px", maxHeight: "70vh", overflow: "auto", background: "rgba(0,0,0,0.85)", color: "#fff", font: "14px/1.4 system-ui, sans-serif", padding: "12px 14px 14px", borderRadius: "10px", boxShadow: "0 6px 18px rgba(0,0,0,0.35)", zIndex: "99999", width: "420px", }); // Header const header = document.createElement('div'); header.textContent = "FLP Downloads (m4a | ogg)"; header.style.fontWeight = "600"; header.style.marginBottom = "6px"; panel.appendChild(header); const info = document.createElement('div'); info.textContent = "Rechtsklick auf Links → „Ziel speichern unter…“ oder nutze „Download all“."; info.style.opacity = "0.85"; info.style.marginBottom = "8px"; panel.appendChild(info); // Controls const controls = document.createElement('div'); controls.style.display = "flex"; controls.style.gap = "8px"; controls.style.flexWrap = "wrap"; controls.style.marginBottom = "8px"; function makeBtn(label) { const b = document.createElement('button'); b.textContent = label; Object.assign(b.style, { cursor: "pointer", padding: "6px 10px", borderRadius: "8px", border: "1px solid rgba(255,255,255,0.25)", background: "rgba(255,255,255,0.08)", color: "#fff", }); b.onmouseenter = () => b.style.background = "rgba(255,255,255,0.14)"; b.onmouseleave = () => b.style.background = "rgba(255,255,255,0.08)"; return b; } const btnAllM4A = makeBtn("Download all (m4a)"); const btnAllOGG = makeBtn("Download all (ogg)"); const btnStop = makeBtn("Stop"); btnStop.style.display = "none"; // Delay input const delayWrap = document.createElement('div'); delayWrap.style.display = "flex"; delayWrap.style.alignItems = "center"; delayWrap.style.gap = "6px"; delayWrap.style.marginLeft = "auto"; const delayLabel = document.createElement('label'); delayLabel.textContent = "Delay (ms):"; const delayInput = document.createElement('input'); delayInput.type = "number"; delayInput.min = "200"; delayInput.value = "1200"; Object.assign(delayInput.style, { width: "80px", padding: "4px 6px", borderRadius: "6px", border: "1px solid rgba(255,255,255,0.25)", background: "rgba(255,255,255,0.06)", color: "#fff", }); delayWrap.appendChild(delayLabel); delayWrap.appendChild(delayInput); controls.appendChild(btnAllM4A); controls.appendChild(btnAllOGG); controls.appendChild(btnStop); controls.appendChild(delayWrap); panel.appendChild(controls); // Progress const progress = document.createElement('div'); progress.style.marginBottom = "8px"; progress.style.opacity = "0.9"; panel.appendChild(progress); // Liste const list = document.createElement('ol'); list.style.margin = "0"; list.style.paddingLeft = "18px"; window.flpPlaylist.forEach((item, idx) => { const li = document.createElement('li'); li.style.marginBottom = "6px"; const title = (item.title || "").replace(/<[^>]+>/g, "").trim() || `Track ${idx+1}`; const m4aHref = item.m4a ? new URL(item.m4a, base).href : null; const oggHref = item.oga ? new URL(item.oga, base).href : null; const titleSpan = document.createElement('span'); titleSpan.textContent = title + " — "; li.appendChild(titleSpan); function link(href, label) { const a = document.createElement('a'); a.href = href; a.textContent = label; a.style.color = "#7cc7ff"; a.setAttribute("download", ""); a.rel = "noopener"; a.target = "_blank"; return a; } if (m4aHref) li.appendChild(link(m4aHref, "m4a")); if (m4aHref && oggHref) li.appendChild(document.createTextNode(" | ")); if (oggHref) li.appendChild(link(oggHref, "ogg")); list.appendChild(li); }); panel.appendChild(list); // Close const closeBtn = document.createElement('button'); closeBtn.textContent = "×"; closeBtn.title = "Panel schließen"; Object.assign(closeBtn.style, { position: "absolute", top: "4px", right: "8px", border: "none", background: "transparent", color: "#fff", fontSize: "18px", cursor: "pointer", }); closeBtn.onclick = () => panel.remove(); panel.appendChild(closeBtn); document.documentElement.appendChild(panel); // ---- Download-All Logik ---- let stopFlag = false; let running = false; async function downloadAll(kind /* 'm4a' | 'ogg' */) { if (running) return; running = true; stopFlag = false; btnStop.style.display = "inline-block"; progress.textContent = `Start: Alle ${kind.toUpperCase()}-Dateien werden nacheinander geladen…`; const delay = Math.max(200, parseInt(delayInput.value || "1200", 10)); const items = window.flpPlaylist.map((item, idx) => { const title = sanitizeFilename(item.title || `Track ${idx+1}`); const href = kind === "m4a" ? (item.m4a ? new URL(item.m4a, base).href : null) : (item.oga ? new URL(item.oga, base).href : null); let ext = kind === "m4a" ? ".m4a" : ".ogg"; if (href) { const baseName = basename(href); const detectedExt = (baseName.match(/\.(m4a|mp4|ogg|oga)$/i) || [])[0]; if (detectedExt) ext = detectedExt.toLowerCase(); } const fileName = `${title}${ext}`; return { href, fileName, idx }; }).filter(x => x.href); if (items.length === 0) { progress.textContent = `Keine ${kind.toUpperCase()}-Links gefunden.`; running = false; btnStop.style.display = "none"; return; } // Hinweis an Nutzer (Popup-Blocker) alert(`Ich starte ${items.length} Downloads (${kind.toUpperCase()}).\nWenn Firefox fragt, erlaube mehrere Downloads von dieser Seite.`); for (let i = 0; i < items.length; i++) { if (stopFlag) { progress.textContent = `Abgebrochen bei ${i}/${items.length}.`; break; } const { href, fileName } = items[i]; progress.textContent = `(${i+1}/${items.length}) Lade: ${fileName}`; clickDownload(href, fileName); // Delay zwischen Downloads await new Promise(r => setTimeout(r, delay)); } if (!stopFlag) progress.textContent = `Fertig (${kind.toUpperCase()}).`; running = false; stopFlag = false; btnStop.style.display = "none"; } btnAllM4A.onclick = () => downloadAll("m4a"); btnAllOGG.onclick = () => downloadAll("ogg"); btnStop.onclick = () => { stopFlag = true; }; console.log("[FLP userscript] Download-Panel eingefügt (mit Download-all)."); } }); })();