您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Fetch subtitles as SRT with manual trigger, bypass TrustedHTML policy, and insert a button (with spinner) into a Polymer dropdown element
当前为
// ==UserScript== // @name Lihuelworks' YouTube Subtitle Downloader (Manual Trigger) with TrustedHTML Bypass // @namespace http://tampermonkey.net/ // @version 1.5 // @description Fetch subtitles as SRT with manual trigger, bypass TrustedHTML policy, and insert a button (with spinner) into a Polymer dropdown element // @match *://www.youtube.com/watch?v* // @grant GM_xmlhttpRequest // @grant GM_addStyle // @license MIT // @run-at document-end // @supportURL https://github.com/lihuelworks/youtube_translation_button_restorer/issues // @contributionURL https://github.com/lihuelworks/youtube_translation_button_restorer#donate // ==/UserScript== // Apply container-specific CSS styles outside of the function using GM_addStyle GM_addStyle(` .ytd-popup-container.style-scope { height: 250px; max-height: none; overflow: hidden; } #lihuelworks-subtitle-container:hover { background-color: var(--yt-spec-10-percent-layer); } .spinner { border: 3px solid #ccc; border-top: 3px solid #333; border-radius: 50%; width: 15px; height: 15px; margin-left: 20px; animation: spin 0.6s linear infinite; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } ; `); // Main function to handle the process (function() { 'use strict'; if (window.trustedTypes && trustedTypes.createPolicy) { if (!trustedTypes.defaultPolicy) { const passThroughFn = (x) => x; trustedTypes.createPolicy('default', { createHTML: passThroughFn, createScriptURL: passThroughFn, createScript: passThroughFn, }); } } function getVideoID() { return new URLSearchParams(window.location.search).get("v"); } function fetchCaptions(videoID) { showSpinner(); GM_xmlhttpRequest({ method: "GET", url: `https://www.youtube.com/watch?v=${videoID}`, onload: function(response) { const match = response.responseText.match(/"captionTracks":(\[.*?\])/); if (match) { const captions = JSON.parse(match[1]); const captionUrl = captions[0].baseUrl.replace(/\\u0026/g, "&"); fetchSubtitle(captionUrl); } else { alert("No captions found."); hideSpinner(); } } }); } function fetchSubtitle(url) { GM_xmlhttpRequest({ method: "GET", url: url, onload: function(response) { const srtData = xmlToSrt(response.responseText); downloadSrtFile(srtData); hideSpinner(); } }); } function xmlToSrt(xml) { const parser = new DOMParser(); const xmlDoc = parser.parseFromString(xml, "text/xml"); let srt = ""; let counter = 1; xmlDoc.querySelectorAll("body > p").forEach((node) => { let start = parseFloat(node.getAttribute("d")); let duration = parseFloat(node.getAttribute("d")); let end = start + duration; let startTime = formatTime(start); let endTime = formatTime(end); let text = decodeHtmlEntities(node.textContent); srt += `${counter}\n${startTime} --> ${endTime}\n${text}\n\n`; counter++; }); return srt; } function decodeHtmlEntities(text) { const element = document.createElement('div'); if (text) { element.innerHTML = text; // Use innerText to extract correctly decoded characters return element.innerText || element.textContent; } return text; } function formatTime(seconds) { let date = new Date(0); date.setSeconds(seconds); return date.toISOString().substr(11, 12).replace(".", ","); } // Function to download the SRT file function downloadSrtFile(srtContent) { const blob = new Blob([srtContent], { type: "text/plain; charset=utf-8" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = "captions.srt"; document.body.appendChild(a); a.click(); document.body.removeChild(a); } function showSpinner() { const button = document.getElementById("lihuelworks-subtitle-getter"); if (button) { button.innerHTML = ''; const spinner = document.createElement("div"); spinner.className = 'spinner'; button.appendChild(spinner); button.disabled = true; } } function hideSpinner() { const button = document.getElementById("lihuelworks-subtitle-getter"); if (button) { button.innerText = "Transcription"; button.disabled = false; } } function createButton() { const container = document.querySelector(".ytd-popup-container.style-scope > .ytd-menu-popup-renderer.style-scope"); if (!container) { console.log("Menu container not found, retrying..."); setTimeout(createButton, 1000); return; } const divContainer = document.createElement("div"); divContainer.id = "lihuelworks-subtitle-container"; divContainer.style.display = "flex"; divContainer.style.alignItems = "center"; const svgIcon = document.createElementNS("http://www.w3.org/2000/svg", "svg"); svgIcon.setAttribute("width", "16"); svgIcon.setAttribute("height", "16"); svgIcon.setAttribute("fill", "currentColor"); svgIcon.setAttribute("class", "bi bi-body-text"); svgIcon.setAttribute("viewBox", "0 0 16 16"); svgIcon.style.marginLeft = "20px"; svgIcon.style.paddingTop = "-3px"; svgIcon.style.textAlign = "baseline"; const path = document.createElementNS("http://www.w3.org/2000/svg", "path"); path.setAttribute("fill-rule", "evenodd"); path.setAttribute("d", "M0 .5A.5.5 0 0 1 .5 0h4a.5.5 0 0 1 0 1h-4A.5.5 0 0 1 0 .5m0 2A.5.5 0 0 1 .5 2h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5m9 0a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5m-9 2A.5.5 0 0 1 .5 4h3a.5.5 0 0 1 0 1h-3a.5.5 0 0 1-.5-.5m5 0a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5m7 0a.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 0 1h-3a.5.5 0 0 1-.5-.5m-12 2A.5.5 0 0 1 .5 6h6a.5.5 0 0 1 0 1h-6a.5.5 0 0 1-.5-.5m8 0a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5m-8 2A.5.5 0 0 1 .5 8h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5m7 0a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5m-7 2a.5.5 0 0 1 .5-.5h8a.5.5 0 0 1 0 1h-8a.5.5 0 0 1-.5-.5m0 2a.5.5 0 0 1 .5-.5h4a.5.5 0 0 1 0 1h-4a.5.5 0 0 1-.5-.5m0 2a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 0 1h-2a.5.5 0 0 1-.5-.5"); svgIcon.appendChild(path); const button = document.createElement("button"); button.id = "lihuelworks-subtitle-getter"; button.classList.add("style-scope", "ytd-menu-service-item-renderer"); button.innerText = "Transcription"; button.style.flexBasis = "1e-09px"; button.style.flexGrow = "1"; button.style.flexShrink = "1"; button.style.height = "36px"; button.style.width = "auto"; button.style.fontFamily = "Roboto, Arial, sans-serif"; button.style.fontSize = "1.4rem"; button.style.fontWeight = "400"; button.style.lineHeight = "normal"; button.style.textSizeAdjust = "100%"; button.style.whiteSpace = "nowrap"; button.style.whiteSpaceCollapse = "collapse"; button.style.color = "rgb(241, 241, 241)"; button.style.cursor = "pointer"; button.style.border = "none"; button.style.margin = "0"; button.style.padding = "0"; button.style.width = "auto"; button.style.overflow = "visible"; button.style.background = "transparent"; button.style.color = "inherit"; button.style.lineHeight = "normal"; button.style.webkitFontSmoothing = "inherit"; button.style.mozOsxFontSmoothing = "inherit"; button.style.webkitAppearance = "none"; button.addEventListener("click", function() { const videoID = getVideoID(); fetchCaptions(videoID); }); divContainer.appendChild(svgIcon); divContainer.appendChild(button); container.appendChild(divContainer); } createButton(); })();