您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Download subtitles (transcript with timestamp) from LingQ reader pages
当前为
// ==UserScript== // @name LingQ Subtitle Downloader // @namespace http://tampermonkey.net/ // @version 2.0 // @description Download subtitles (transcript with timestamp) from LingQ reader pages // @author Yuxin with ChatGPT // @match https://www.lingq.com/* // @match https://*lingq.com/* // @run-at document-start // @grant none // @license MIT // ==/UserScript== (function() { 'use strict'; console.log("LingQ Subtitle Downloader Active Request script started."); // Extract API URL from the current page URL. // For example, from "https://www.lingq.com/en/learn/es/web/listen/1003504" we derive: // - content language: es // - lesson ID: 1003504 // and build: "https://www.lingq.com/api/v3/es/lessons/1003504/simple/?" function getApiUrl() { const m = window.location.href.match(/^https:\/\/www\.lingq\.com\/([^\/]+)\/learn\/([^\/]+)\/web\/(listen|reader)\/(\d+)/); if (m) { const contentLang = m[2]; // second segment after "learn" const lessonId = m[4]; const apiUrl = `https://www.lingq.com/api/v3/${contentLang}/lessons/${lessonId}/simple/?`; console.log("Constructed API URL:", apiUrl); return apiUrl; } console.warn("Could not parse lesson API URL from:", window.location.href); return null; } // Convert seconds to SRT timestamp format (HH:MM:SS,mmm) function formatTime(seconds) { let hours = Math.floor(seconds / 3600); let minutes = Math.floor((seconds % 3600) / 60); let secs = Math.floor(seconds % 60); let ms = Math.floor((seconds % 1) * 1000); return (hours < 10 ? "0" + hours : hours) + ":" + (minutes < 10 ? "0" + minutes : minutes) + ":" + (secs < 10 ? "0" + secs : secs) + "," + (ms < 100 ? (ms < 10 ? "00" + ms : "0" + ms) : ms); } // Build SRT content from the fetched lesson data function buildSRT(data) { let srtContent = ""; let index = 1; data.tokenizedText.forEach(segment => { if (segment.length > 0) { let item = segment[0]; let start = item.timestamp[0]; let end = item.timestamp[1]; srtContent += index + "\n"; srtContent += formatTime(start) + " --> " + formatTime(end) + "\n"; srtContent += item.text + "\n\n"; index++; } }); return srtContent; } // Trigger download of the SRT file function downloadSRT(srtContent, fileName) { const blob = new Blob([srtContent], { type: 'text/plain' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = fileName; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); } // Create and insert the Download Button with improved styling. function addDownloadButton() { if (document.getElementById('lingqDownloadButton')) return; // Prevent duplicates const button = document.createElement('button'); button.id = 'lingqDownloadButton'; button.innerText = "Download Subtitle"; // Style: bottom right with background, outline, and custom text color. button.style.position = 'fixed'; button.style.bottom = '10px'; button.style.right = '10px'; button.style.zIndex = 9999; button.style.padding = '10px 15px'; button.style.fontSize = '14px'; button.style.background = '#007bff'; button.style.color = '#ffffff'; button.style.border = '2px solid #0056b3'; button.style.borderRadius = '4px'; button.style.cursor = 'pointer'; button.style.boxShadow = '2px 2px 6px rgba(0,0,0,0.2)'; button.addEventListener('click', function() { const apiUrl = getApiUrl(); if (!apiUrl) { alert("Unable to determine lesson API URL."); return; } console.log("Sending request to API URL:", apiUrl); fetch(apiUrl, { headers: { 'accept': 'application/json', 'x-lingq-app': 'Web/6.0.10' } }) .then(response => response.json()) .then(data => { console.log("Received lesson data:", data); const srtContent = buildSRT(data); console.log("Generated SRT content:\n", srtContent); const fileName = data.title.replace(/[\\\/:*?"<>|]/g, '_') + ".srt"; downloadSRT(srtContent, fileName); }) .catch(error => { console.error("Error fetching lesson data:", error); alert("Error fetching lesson data."); }); }); document.body.appendChild(button); } // Remove the download button (used when leaving allowed pages) function removeDownloadButton() { const btn = document.getElementById('lingqDownloadButton'); if (btn) { btn.remove(); } } // Update button visibility based on allowed pages (listen/reader pages) function updateButtonVisibility() { const allowedPattern = /^https:\/\/www\.lingq\.com\/[^\/]+\/learn\/[^\/]+\/web\/(listen|reader)\//; if (allowedPattern.test(window.location.href)) { addDownloadButton(); } else { removeDownloadButton(); } } // Monitor URL changes using history API overrides and popstate events. (function() { const _wr = function(type) { let orig = history[type]; return function() { let rv = orig.apply(this, arguments); window.dispatchEvent(new Event('locationchange')); return rv; }; }; history.pushState = _wr('pushState'); history.replaceState = _wr('replaceState'); window.addEventListener('popstate', () => { window.dispatchEvent(new Event('locationchange')); }); })(); window.addEventListener('locationchange', () => { console.log("Location changed to:", window.location.href); updateButtonVisibility(); }); window.addEventListener('DOMContentLoaded', () => { updateButtonVisibility(); }); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址