Transcript Collector

Collects transcript entries and replaces download button

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

Advertisement:

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

Advertisement:

// ==UserScript==
// @name         Transcript Collector
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  Collects transcript entries and replaces download button
// @author       You
// @license      MIT
// @match        https://audiototext.com/*
// @grant        GM_download
// @grant        GM_setClipboard
// ==/UserScript==

(function() {
    'use strict';

    // Function to extract transcript data
    function extractTranscriptData() {
        const transcriptItems = document.querySelectorAll('.group.relative.rounded-lg.sm\\:rounded-xl.shadow-sm.border');
        const formattedData = [];
        const plainText = [];

        transcriptItems.forEach(item => {
            // Extract name
            const nameElement = item.querySelector('.inline-flex.items-center.rounded-full');
            const name = nameElement ? nameElement.textContent.trim() : 'Unknown';

            // Extract timestamp
            const timestampElement = item.querySelector('.text-xs.font-mono');
            const timestamp = timestampElement ? timestampElement.textContent.trim() : '';

            // Extract text
            const textElement = item.querySelector('p');
            const text = textElement ? textElement.textContent.trim() : '';

            // Add formatted line
            if (name && timestamp && text) {
                formattedData.push(`[${name}] [${timestamp}] ${text}`);
                plainText.push(text);
            }
        });

        return {
            formatted: formattedData.join('\n'),
            plainText: plainText.join('\n'),
            raw: formattedData
        };
    }

    // Function to create and trigger download
    function downloadTranscript(format = 'formatted') {
        const data = extractTranscriptData();
        let content = '';
        let filename = '';

        if (format === 'formatted') {
            content = data.formatted;
            filename = 'transcript_formatted.txt';
        } else if (format === 'plain') {
            content = data.plainText;
            filename = 'transcript_plain.txt';
        } else if (format === 'json') {
            content = JSON.stringify(data.raw, null, 2);
            filename = 'transcript.json';
        }

        // Create download
        const blob = new Blob([content], { 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);
    }

    // Function to create dropdown menu
    function createDownloadMenu() {
        const menu = document.createElement('div');
        menu.style.position = 'absolute';
        menu.style.top = '100%';
        menu.style.right = '0';
        menu.style.marginTop = '0.5rem';
        menu.style.backgroundColor = 'white';
        menu.style.border = '1px solid #e2e8f0';
        menu.style.borderRadius = '0.5rem';
        menu.style.boxShadow = '0 10px 15px -3px rgba(0, 0, 0, 0.1)';
        menu.style.zIndex = '50';
        menu.style.minWidth = '150px';
        menu.style.display = 'none';

        const options = [
            { label: 'Formatted (Name + Timestamp + Text)', format: 'formatted' },
            { label: 'Plain Text Only', format: 'plain' },
            { label: 'JSON Format', format: 'json' },
            { label: 'Copy to Clipboard (Formatted)', format: 'copy_formatted' },
            { label: 'Copy to Clipboard (Plain)', format: 'copy_plain' }
        ];

        options.forEach(opt => {
            const option = document.createElement('div');
            option.textContent = opt.label;
            option.style.padding = '0.5rem 1rem';
            option.style.cursor = 'pointer';
            option.style.fontSize = '0.875rem';
            option.style.color = '#374151';
            option.style.transition = 'all 0.2s';

            option.addEventListener('mouseenter', () => {
                option.style.backgroundColor = '#f3f4f6';
            });
            option.addEventListener('mouseleave', () => {
                option.style.backgroundColor = 'white';
            });

            option.addEventListener('click', () => {
                if (opt.format === 'copy_formatted') {
                    const data = extractTranscriptData();
                    GM_setClipboard(data.formatted);
                    showNotification('Copied formatted transcript to clipboard!');
                } else if (opt.format === 'copy_plain') {
                    const data = extractTranscriptData();
                    GM_setClipboard(data.plainText);
                    showNotification('Copied plain text to clipboard!');
                } else {
                    downloadTranscript(opt.format);
                    showNotification(`Downloading ${opt.format}...`);
                }
                menu.style.display = 'none';
            });

            menu.appendChild(option);
        });

        return menu;
    }

    // Function to show notification
    function showNotification(message) {
        const notification = document.createElement('div');
        notification.textContent = message;
        notification.style.position = 'fixed';
        notification.style.bottom = '20px';
        notification.style.right = '20px';
        notification.style.backgroundColor = '#10b981';
        notification.style.color = 'white';
        notification.style.padding = '12px 20px';
        notification.style.borderRadius = '8px';
        notification.style.fontSize = '14px';
        notification.style.zIndex = '10000';
        notification.style.boxShadow = '0 4px 6px rgba(0, 0, 0, 0.1)';
        notification.style.animation = 'fadeInOut 2s ease-in-out';

        document.body.appendChild(notification);
        setTimeout(() => notification.remove(), 2000);
    }

    // Add animation styles
    const style = document.createElement('style');
    style.textContent = `
        @keyframes fadeInOut {
            0% { opacity: 0; transform: translateY(10px); }
            15% { opacity: 1; transform: translateY(0); }
            85% { opacity: 1; transform: translateY(0); }
            100% { opacity: 0; transform: translateY(10px); }
        }
    `;
    document.head.appendChild(style);

    // Function to replace download button
    function replaceDownloadButton() {
        const downloadButton = document.querySelector('[title="Download"]');
        if (!downloadButton) return false;

        const buttonContainer = downloadButton.closest('div[title="Download"]');
        if (!buttonContainer) return false;

        // Store original button HTML if needed
        const originalHTML = buttonContainer.outerHTML;

        // Create new button with dropdown
        const wrapper = document.createElement('div');
        wrapper.style.position = 'relative';
        wrapper.style.display = 'inline-flex';

        const newButton = buttonContainer.cloneNode(true);
        const svg = newButton.querySelector('svg');
        const span = newButton.querySelector('span');

        if (svg) svg.outerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-file-down w-3.5 h-3.5 text-gray-600 group-hover:text-primary transition-colors"><path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M12 18v-6"/><path d="M9 15l3-3 3 3"/></svg>`;
        if (span) span.textContent = 'Export';

        newButton.classList.add('group');
        newButton.style.cursor = 'pointer';

        const menu = createDownloadMenu();
        wrapper.appendChild(newButton);
        wrapper.appendChild(menu);

        // Toggle menu on click
        newButton.addEventListener('click', (e) => {
            e.stopPropagation();
            const isVisible = menu.style.display === 'block';
            // Close all other menus
            document.querySelectorAll('.transcript-menu').forEach(m => {
                if (m !== menu) m.style.display = 'none';
            });
            menu.style.display = isVisible ? 'none' : 'block';
        });

        // Close menu when clicking outside
        document.addEventListener('click', function closeMenu(e) {
            if (!wrapper.contains(e.target)) {
                menu.style.display = 'none';
            }
        });

        // Replace the original button
        buttonContainer.parentNode.replaceChild(wrapper, buttonContainer);
        wrapper.className = 'transcript-menu-container';

        return true;
    }

    // Function to add a floating button for quick access
    function addFloatingButton() {
        const floatingBtn = document.createElement('button');
        floatingBtn.innerHTML = `
            <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
            <span>Export Transcript</span>
        `;
        floatingBtn.style.position = 'fixed';
        floatingBtn.style.bottom = '20px';
        floatingBtn.style.right = '20px';
        floatingBtn.style.backgroundColor = '#3b82f6';
        floatingBtn.style.color = 'white';
        floatingBtn.style.border = 'none';
        floatingBtn.style.borderRadius = '8px';
        floatingBtn.style.padding = '10px 16px';
        floatingBtn.style.cursor = 'pointer';
        floatingBtn.style.zIndex = '9999';
        floatingBtn.style.display = 'flex';
        floatingBtn.style.alignItems = 'center';
        floatingBtn.style.gap = '8px';
        floatingBtn.style.fontSize = '14px';
        floatingBtn.style.fontWeight = '500';
        floatingBtn.style.boxShadow = '0 2px 8px rgba(0,0,0,0.15)';
        floatingBtn.style.transition = 'all 0.2s';

        floatingBtn.addEventListener('mouseenter', () => {
            floatingBtn.style.backgroundColor = '#2563eb';
            floatingBtn.style.transform = 'scale(1.05)';
        });
        floatingBtn.addEventListener('mouseleave', () => {
            floatingBtn.style.backgroundColor = '#3b82f6';
            floatingBtn.style.transform = 'scale(1)';
        });

        let menuVisible = false;
        let floatingMenu = null;

        floatingBtn.addEventListener('click', () => {
            if (!floatingMenu) {
                floatingMenu = createDownloadMenu();
                floatingMenu.style.position = 'fixed';
                floatingMenu.style.bottom = '70px';
                floatingMenu.style.right = '20px';
                floatingMenu.style.display = 'block';
                document.body.appendChild(floatingMenu);

                // Remove menu when clicking outside
                const removeMenu = (e) => {
                    if (!floatingBtn.contains(e.target) && !floatingMenu.contains(e.target)) {
                        floatingMenu.remove();
                        floatingMenu = null;
                        document.removeEventListener('click', removeMenu);
                    }
                };
                setTimeout(() => document.addEventListener('click', removeMenu), 100);
            } else {
                floatingMenu.remove();
                floatingMenu = null;
            }
        });

        document.body.appendChild(floatingBtn);
    }

    // Wait for elements to load and replace button
    function init() {
        const checkInterval = setInterval(() => {
            if (replaceDownloadButton()) {
                clearInterval(checkInterval);
                addFloatingButton();
                console.log('Transcript collector loaded successfully');
            }
        }, 1000);

        // Stop checking after 30 seconds
        setTimeout(() => clearInterval(checkInterval), 30000);
    }

    // Start the script
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }
})();