Transcript Collector

Collects transcript entries and replaces download button

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Greasemonkey lub Violentmonkey.

You will need to install an extension such as Tampermonkey to install this script.

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Violentmonkey.

Aby zainstalować ten skrypt, wymagana będzie instalacja rozszerzenia Tampermonkey lub Userscripts.

You will need to install an extension such as Tampermonkey to install this script.

Aby zainstalować ten skrypt, musisz zainstalować rozszerzenie menedżera skryptów użytkownika.

(Mam już menedżera skryptów użytkownika, pozwól mi to zainstalować!)

Advertisement:

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

Będziesz musiał zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

Będziesz musiał zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

Musisz zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

(Mam już menedżera stylów użytkownika, pozwól mi to zainstalować!)

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();
    }
})();