Kopiert das offene YouTube-Transcript zuverlässig in die Zwischenablage
// ==UserScript==
// @name YouTube Transcript
// @namespace http://tampermonkey.net/
// @version 1.4
// @description Kopiert das offene YouTube-Transcript zuverlässig in die Zwischenablage
// @match https://www.youtube.com/watch*
// @grant GM_setClipboard
// @run-at document-idle
// @license MIT
// ==/UserScript==
(function () {
'use strict';
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
function log(...args) {
console.log('[YT Transcript]', ...args);
}
function showToast(message, type = 'success') {
const existing = document.getElementById('yt-transcript-toast');
if (existing) existing.remove();
const toast = document.createElement('div');
toast.id = 'yt-transcript-toast';
toast.textContent = message;
toast.style.position = 'fixed';
toast.style.top = '20px';
toast.style.right = '20px';
toast.style.zIndex = '999999';
toast.style.padding = '10px 16px';
toast.style.borderRadius = '12px';
toast.style.background = type === 'error' ? 'rgba(180, 40, 40, 0.95)' : 'rgba(32, 32, 32, 0.95)';
toast.style.color = '#fff';
toast.style.fontSize = '14px';
toast.style.fontWeight = '500';
toast.style.fontFamily = '"Roboto","Arial",sans-serif';
toast.style.boxShadow = '0 6px 18px rgba(0,0,0,0.25)';
toast.style.opacity = '0';
toast.style.transform = 'translateY(-8px)';
toast.style.transition = 'opacity 0.2s ease, transform 0.2s ease';
toast.style.pointerEvents = 'none';
document.body.appendChild(toast);
requestAnimationFrame(() => {
toast.style.opacity = '1';
toast.style.transform = 'translateY(0)';
});
setTimeout(() => {
toast.style.opacity = '0';
toast.style.transform = 'translateY(-8px)';
setTimeout(() => toast.remove(), 220);
}, 2200);
}
function getTranscriptPanels() {
return [
...document.querySelectorAll('ytd-engagement-panel-section-list-renderer')
].filter(panel => {
const targetId =
panel.getAttribute('target-id') ||
panel.dataset.targetId ||
panel.querySelector('[data-target-id]')?.getAttribute('data-target-id') ||
'';
return /transcript|PAmodern_transcript_view/i.test(targetId);
});
}
function getVisibleTranscriptPanel() {
const panels = getTranscriptPanels();
for (const panel of panels) {
const style = window.getComputedStyle(panel);
const hidden =
panel.hasAttribute('hidden') ||
style.display === 'none' ||
style.visibility === 'hidden';
if (!hidden) return panel;
}
return panels[0] || null;
}
function getTranscriptRoot() {
const panel = getVisibleTranscriptPanel();
if (!panel) return null;
return (
panel.querySelector('.ytSectionListRendererContents') ||
panel.querySelector('#contents') ||
panel.querySelector('yt-section-list-renderer') ||
panel.querySelector('#content') ||
panel
);
}
function getTranscriptSegmentsFromRoot(root) {
if (!root) return [];
const modernSegments = [...root.querySelectorAll('transcript-segment-view-model')];
if (modernSegments.length) {
return modernSegments.map(seg => {
const textNode =
seg.querySelector('.yt-core-attributed-string[role="text"]') ||
seg.querySelector('span[role="text"]') ||
seg.querySelector('.yt-core-attributed-string') ||
seg.querySelector('span');
const text = textNode ? textNode.textContent.trim() : '';
return { text };
}).filter(item => item.text);
}
const legacySegments = [...root.querySelectorAll('ytd-transcript-segment-renderer, .cue-group')];
if (legacySegments.length) {
return legacySegments.map(seg => {
const text =
seg.querySelector('.segment-text')?.textContent?.trim() ||
seg.querySelector('.cue')?.textContent?.trim() ||
'';
return { text };
}).filter(item => item.text);
}
return [];
}
function getTranscriptSegments() {
const root = getTranscriptRoot();
return getTranscriptSegmentsFromRoot(root);
}
async function clickShowTranscriptButton() {
const buttons = [...document.querySelectorAll('button, yt-button-shape button, tp-yt-paper-button')];
const btn = buttons.find(el => {
const text = (el.innerText || el.textContent || '').trim().toLowerCase();
const aria = (el.getAttribute('aria-label') || '').trim().toLowerCase();
return (
text.includes('show transcript') ||
text.includes('transkript anzeigen') ||
aria.includes('show transcript') ||
aria.includes('transkript anzeigen')
);
});
if (btn) {
btn.click();
log('Show Transcript geklickt');
await sleep(2000);
return true;
}
return false;
}
async function ensureTranscriptOpen() {
let segments = getTranscriptSegments();
if (segments.length) return true;
await clickShowTranscriptButton();
await sleep(1500);
segments = getTranscriptSegments();
return segments.length > 0;
}
async function scrollTranscriptToLoadAll() {
const root = getTranscriptRoot();
if (!root) return;
const scrollBox =
root.closest('.ytSectionListRendererContents') ||
root.querySelector('.ytSectionListRendererContents') ||
root;
let lastCount = -1;
let stableRounds = 0;
for (let i = 0; i < 80; i++) {
scrollBox.scrollTop = scrollBox.scrollHeight;
await sleep(300);
const count = getTranscriptSegments().length;
log('Segmente nach Scroll:', count);
if (count === lastCount) {
stableRounds++;
} else {
stableRounds = 0;
}
lastCount = count;
if (stableRounds >= 4) break;
}
scrollBox.scrollTop = 0;
await sleep(200);
}
function buildTranscriptText(segments) {
return segments
.map(({ text }) => text)
.filter(Boolean)
.join('\n');
}
async function copyText(text) {
try {
if (typeof GM_setClipboard === 'function') {
GM_setClipboard(text, 'text');
return true;
}
await navigator.clipboard.writeText(text);
return true;
} catch (err) {
console.error(err);
const ta = document.createElement('textarea');
ta.value = text;
ta.style.position = 'fixed';
ta.style.left = '-9999px';
document.body.appendChild(ta);
ta.focus();
ta.select();
const ok = document.execCommand('copy');
ta.remove();
return ok;
}
}
async function extractTranscript() {
const ok = await ensureTranscriptOpen();
if (!ok) {
showToast('Transcript konnte nicht gefunden oder geöffnet werden.', 'error');
return;
}
const btn = document.getElementById('yt-transcript-copy-btn-fixed');
if (btn) {
btn.disabled = true;
btn.textContent = 'Kopiere...';
btn.style.opacity = '0.7';
}
await scrollTranscriptToLoadAll();
const segments = getTranscriptSegments();
if (!segments.length) {
console.log('Root:', getTranscriptRoot());
console.log('Panels:', getTranscriptPanels());
if (btn) {
btn.disabled = false;
btn.textContent = 'Transcript kopieren';
btn.style.opacity = '1';
}
showToast('Keine Transcript-Segmente gefunden.', 'error');
return;
}
const text = buildTranscriptText(segments);
const copied = await copyText(text);
console.log(text);
if (btn) {
btn.disabled = false;
btn.textContent = 'Transcript kopieren';
btn.style.opacity = '1';
}
if (copied) {
showToast(`Transcript kopiert: ${segments.length} Segmente.`);
} else {
showToast(`Transcript gefunden (${segments.length} Segmente), aber Kopieren fehlgeschlagen.`, 'error');
}
}
function styleButton(btn) {
btn.style.display = 'inline-flex';
btn.style.alignItems = 'center';
btn.style.justifyContent = 'center';
btn.style.height = '36px';
btn.style.padding = '0 16px';
btn.style.marginLeft = '10px';
btn.style.border = 'none';
btn.style.borderRadius = '18px';
btn.style.background = '#f2f2f2';
btn.style.color = '#0f0f0f';
btn.style.fontSize = '14px';
btn.style.fontWeight = '500';
btn.style.lineHeight = '36px';
btn.style.cursor = 'pointer';
btn.style.whiteSpace = 'nowrap';
btn.style.boxShadow = 'none';
btn.style.fontFamily = '"Roboto","Arial",sans-serif';
btn.style.transition = 'background 0.2s ease, opacity 0.2s ease';
btn.style.flex = '0 0 auto';
btn.style.verticalAlign = 'middle';
}
function addHoverEvents(btn) {
btn.addEventListener('mouseenter', () => {
if (!btn.disabled) btn.style.background = '#e5e5e5';
});
btn.addEventListener('mouseleave', () => {
btn.style.background = '#f2f2f2';
});
}
function findCreateButton() {
const candidates = [...document.querySelectorAll('button, yt-button-shape button, tp-yt-paper-button')];
return candidates.find(el => {
const text = (el.innerText || el.textContent || '').trim().toLowerCase();
const aria = (el.getAttribute('aria-label') || '').trim().toLowerCase();
return text === 'create' || aria === 'create' || text.includes('create');
}) || null;
}
function placeButtonNextToCreate(btn, createBtn) {
const reference =
createBtn.closest('yt-button-view-model') ||
createBtn.closest('yt-button-shape') ||
createBtn.parentElement;
if (!reference || !reference.parentElement) return false;
const parent = reference.parentElement;
if (window.getComputedStyle(parent).display.includes('flex')) {
btn.style.position = 'relative';
btn.style.top = '0';
btn.style.right = '0';
btn.style.marginLeft = '10px';
btn.style.marginTop = '0';
btn.style.zIndex = '1';
if (reference.nextSibling !== btn) {
reference.insertAdjacentElement('afterend', btn);
}
return true;
}
return false;
}
function createButton() {
let btn = document.getElementById('yt-transcript-copy-btn-fixed');
if (!btn) {
btn = document.createElement('button');
btn.id = 'yt-transcript-copy-btn-fixed';
btn.type = 'button';
btn.textContent = 'Transcript kopieren';
styleButton(btn);
addHoverEvents(btn);
btn.addEventListener('click', extractTranscript);
}
const createBtn = findCreateButton();
if (createBtn && placeButtonNextToCreate(btn, createBtn)) {
return;
}
if (!document.body.contains(btn)) {
btn.style.position = 'fixed';
btn.style.top = '20px';
btn.style.right = '20px';
btn.style.marginLeft = '0';
btn.style.zIndex = '999999';
document.body.appendChild(btn);
}
}
function init() {
createButton();
}
const observer = new MutationObserver(() => {
createButton();
});
window.addEventListener('load', () => {
init();
observer.observe(document.body, { childList: true, subtree: true });
});
})();