Google AI Studio Paste To File

Intercepts large pastes and uploads as text files

// ==UserScript==
// @name        Google AI Studio Paste To File
// @namespace   Violentmonkey Scripts
// @match       https://aistudio.google.com/u/0/prompts/*
// @grant       none
// @author      Skibidi Rizz
// @version     1.0
// @description Intercepts large pastes and uploads as text files
// ==/UserScript==

(function() {
    'use strict';
    const maxNonPasteLength = 1000;
    // Function to clean text for use as filename
    function cleanFilename(text) {
        return text.substring(0, 20)
            .replace(/[\\/:*?"<>|]/g, '_')
            .trim();
    }

    // Function to close overlay menu by clicking the backdrop
    function closeOverlay() {
        setTimeout(() => {
            try {
                const backdropElement = document.querySelector("body > div.cdk-overlay-container > div.cdk-overlay-backdrop.cdk-overlay-transparent-backdrop.cdk-overlay-backdrop-showing");
                if (backdropElement) {
                    backdropElement.click();
                    console.log("Successfully closed upload overlay by clicking backdrop");
                } else {
                    console.log("Backdrop element not found");
                }
            } catch (e) {
                console.error("Error closing overlay:", e);
            }
        }, 10); // Give it time for the upload to complete
    }

    // Function to find and click the upload button
    function clickUploadButton() {
        // Try the provided selector first
        let uploadButton = document.querySelector("button[_ngcontent-ng-c2767593862]");

        // If that doesn't work, try alternative methods
        if (!uploadButton) {
            // Find buttons that might be related to uploads
            const buttons = Array.from(document.querySelectorAll('button'));
            uploadButton = buttons.find(el => {
                // Look for buttons with upload icons or text
                const innerText = el.innerText ? el.innerText.toLowerCase() : '';
                const hasUploadIcon = el.querySelector('svg') !== null;

                return hasUploadIcon ||
                       innerText.includes('upload') ||
                       innerText.includes('attach');
            });
        }

        if (uploadButton) {
            uploadButton.click();
            return true;
        }

        console.error('Upload button not found');
        return false;
    }

    // Function to find the file input with multiple methods
    function findFileInput() {
        // First click the upload button to reveal the file input
        if (!clickUploadButton()) {
            return Promise.resolve(null);
        }

        // Wait a short time for the input to be added to the DOM
        return new Promise(resolve => {
            setTimeout(() => {
                // Try the provided selector first
                let fileInput = document.querySelector("input[_ngcontent-ng-c2767593862]");

                // If that doesn't work, try alternative methods
                if (!fileInput) {
                    // Find any hidden file input
                    const fileInputs = Array.from(document.querySelectorAll('input[type="file"]'));
                    fileInput = fileInputs.find(el => el.multiple);
                }

                resolve(fileInput);
            }, 10); // Give it 200ms for the DOM to update
        });
    }

    // Function to create text file and trigger upload
    async function createAndUploadFile(text, filename) {
        // Create a Blob with the text content
        const blob = new Blob([text], { type: 'text/plain' });

        // Create a File object from the Blob
        const file = new File([blob], `${filename}.txt`, { type: 'text/plain' });

        // Find file input (now returns a Promise)
        const fileInput = await findFileInput();

        if (fileInput) {
            try {
                // Try using DataTransfer API
                const dataTransfer = new DataTransfer();
                dataTransfer.items.add(file);
                fileInput.files = dataTransfer.files;

                // Dispatch change event to trigger upload
                const event = new Event('change', { bubbles: true });
                fileInput.dispatchEvent(event);

                console.log(`Uploaded "${filename}.txt" (${text.length} characters)`);

                // Close overlay menu after upload by clicking backdrop
                closeOverlay();
            } catch (error) {
                console.error('DataTransfer API failed:', error);

                // Fallback: Download the file for manual selection
                const a = document.createElement('a');
                a.href = URL.createObjectURL(blob);
                a.download = `${filename}.txt`;
                a.style.display = 'none';
                document.body.appendChild(a);
                a.click();
                document.body.removeChild(a);

                // Alert the user
                setTimeout(() => {
                    alert('Please select the downloaded file manually');
                }, 500);
            }
        } else {
            console.error('File input element not found');
            alert('Could not find the file upload element. Please copy the text to a file and upload manually.');
        }
    }

    // Function to find the textarea with multiple methods
    function findTextarea() {
        // Try the provided selector first
        let textarea = document.querySelector("textarea[_ngcontent-ng-c329905920]");

        // If that doesn't work, try alternative methods
        if (!textarea) {
            // Find the main textarea in the chat interface
            const textareas = Array.from(document.querySelectorAll('textarea'));
            textarea = textareas.find(el =>
                el.placeholder &&
                (el.placeholder.toLowerCase().includes('message') ||
                 el.placeholder.toLowerCase().includes('ask'))
            );
        }

        return textarea;
    }

    // Function to set up the paste interceptor
    function setupPasteInterceptor() {
        const textarea = findTextarea();

        if (textarea) {
            // Add paste event listener
            textarea.addEventListener('paste', function(e) {
                // Get pasted text from clipboard
                const clipboardData = e.clipboardData || window.clipboardData;
                const pastedText = clipboardData.getData('text');

                // Check if pasted text is longer than maxNonPasteLength characters
                if (pastedText && pastedText.length > maxNonPasteLength) {
                    // Prevent default paste
                    e.preventDefault();

                    // Create filename from first 20 chars
                    const filename = cleanFilename(pastedText);

                    // Create and upload file
                    createAndUploadFile(pastedText, filename);
                }
            });

            console.log('Paste interceptor set up successfully');
        } else {
            // If textarea not found yet, try again in a moment
            setTimeout(setupPasteInterceptor, 1000);
        }
    }

    // Observe DOM changes to detect when the textarea is added
    function observeDOMChanges() {
        const observer = new MutationObserver(mutations => {
            if (findTextarea()) {
                // Textarea found, set up interceptor and disconnect observer
                setupPasteInterceptor();
                observer.disconnect();
            }
        });

        // Start observing
        observer.observe(document.body, {
            childList: true,
            subtree: true
        });
    }

    // Try to set up immediately or observe DOM for changes
    if (document.readyState === 'loading') {
        window.addEventListener('DOMContentLoaded', () => {
            findTextarea() ? setupPasteInterceptor() : observeDOMChanges();
        });
    } else {
        findTextarea() ? setupPasteInterceptor() : observeDOMChanges();
    }
})();