您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds a floating button to copy the Udemy transcript when the panel is open.
// ==UserScript== // @name Udemy Transcript Copier (Floating Button) // @namespace http://tampermonkey.net/ // @version 3.3 // @description Adds a floating button to copy the Udemy transcript when the panel is open. // @author Gemini // @match https://www.udemy.com/course/* // @grant GM_addStyle // @grant GM_setClipboard // @license MIT // ==/UserScript== (function() { 'use strict'; // --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- // STYLING FOR THE FLOATING BUTTON AND MODAL // The button is positioned fixed to the viewport. A high z-index ensures // it floats above other content on the page. // --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- GM_addStyle(` .floating-copy-button { position: fixed; bottom: 25px; right: 25px; z-index: 10000; /* High z-index to stay on top */ background-color: #a435f0; /* Udemy's primary purple */ color: #fff; border: none; padding: 12px 18px; font-weight: bold; font-family: "Udemy Sans", "SF Pro Text", -apple-system, BlinkMacSystemFont, Roboto, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; border-radius: 50px; /* Pill shape */ cursor: pointer; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25); transition: all 0.2s ease-in-out; } .floating-copy-button:hover { background-color: #8710d8; /* Darker purple on hover */ transform: translateY(-2px); /* Slight lift effect */ box-shadow: 0 6px 16px rgba(0, 0, 0, 0.3); } .floating-copy-button.copied { background-color: #2e8a5b; /* Green for success feedback */ transform: translateY(0); } `); // --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- // BUTTON CREATION LOGIC // This function creates the button and defines its copy behavior. // --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- function createFloatingButton() { // Avoid creating a duplicate button. if (document.getElementById('floating-transcript-copier')) { return; } const copyButton = document.createElement('button'); copyButton.id = 'floating-transcript-copier'; copyButton.className = 'floating-copy-button'; copyButton.innerText = 'Copy Transcript'; copyButton.addEventListener('click', () => { // The main transcript panel, which is a stable selector. const transcriptPanel = document.querySelector('div[data-purpose="transcript-panel"]'); if (!transcriptPanel) { console.error("Transcript panel not found. This should not happen if the button is visible."); alert("Error: Could not find the transcript panel. The script may need an update."); return; } // UPDATED SELECTOR: Based on the provided HTML, the transcript cues are now // `p` tags with a `data-purpose="transcript-cue"`. This is a more stable selector // than the dynamically generated class names. const cueElements = transcriptPanel.querySelectorAll('p[data-purpose="transcript-cue"]'); if (cueElements.length === 0) { console.error("No transcript cues found within the panel. The selector for the text lines might need to be changed."); alert("Error: Could not find the transcript text lines. The script may need an update."); return; } // Extract text from each cue and join with double newlines for readability. const fullTranscript = Array.from(cueElements) .map(cue => cue.innerText.trim()) .join('\n\n'); // Copy the formatted text to the clipboard. try { navigator.clipboard.writeText(fullTranscript).then(() => { // Provide visual feedback to the user. copyButton.innerText = 'Copied!'; copyButton.classList.add('copied'); setTimeout(() => { copyButton.innerText = 'Copy Transcript'; copyButton.classList.remove('copied'); }, 2000); // Reset after 2 seconds }).catch(err => { console.error('Failed to copy using navigator.clipboard:', err); GM_setClipboard(fullTranscript, 'text'); // Fallback }); } catch (err) { console.error('Error with clipboard API, using fallback:', err); GM_setClipboard(fullTranscript, 'text'); // Fallback } }); // Add the button to the main document body so it can float freely. document.body.appendChild(copyButton); } // --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- // OBSERVER TO MANAGE BUTTON VISIBILITY // We use a MutationObserver to watch for the transcript panel appearing // or disappearing from the DOM. This is the most efficient way to manage // the button's lifecycle. // --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- const observer = new MutationObserver(() => { // The selector for the overall panel seems to be more stable. const transcriptPanel = document.querySelector('div[data-purpose="transcript-panel"]'); const floatingButton = document.getElementById('floating-transcript-copier'); // If the transcript panel exists AND our button doesn't, create the button. if (transcriptPanel && !floatingButton) { createFloatingButton(); } // If the transcript panel is gone AND our button still exists, remove it. else if (!transcriptPanel && floatingButton) { floatingButton.remove(); } }); // Start observing the entire document for changes. This allows us to react // when the user opens or closes the transcript tab. observer.observe(document.body, { childList: true, subtree: true }); })();