Copy Crushon Story

Copies all listed chat bubbles with formatting

// ==UserScript==
// @name         Copy Crushon Story
// @namespace    https://crushon.ai/
// @version      0.2
// @description  Copies all listed chat bubbles with formatting
// @author       dbzfanatic
// @match        *://crushon.ai/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=crushon.ai
// @grant        GM_setClipboard
// @license      GPLv3
// ==/UserScript==

(function() {
    'use strict';

    // Create the custom context menu
    const menu = document.createElement('ul');
    menu.id = 'custom-menu';
    menu.style.display = 'none';
    menu.style.position = 'absolute';
    menu.style.backgroundColor = 'black';
    menu.style.border = '1px solid #ccc';
    menu.style.zIndex = '1000';
    menu.style.boxShadow = '0 2px 10px rgba(0,0,0,0.2)';
    document.body.appendChild(menu);

    const copyStoryMenuItem = document.createElement('li');
    copyStoryMenuItem.id = 'copy-story';
    copyStoryMenuItem.textContent = 'Copy Story';
    copyStoryMenuItem.style.padding = '5px';
    copyStoryMenuItem.style.cursor = 'pointer';
    copyStoryMenuItem.style.color = 'white';
    copyStoryMenuItem.style.listStyleType = 'none';
    copyStoryMenuItem.onmouseover = function() {
        copyStoryMenuItem.style.backgroundColor = '#222';
    };
    copyStoryMenuItem.onmouseout = function() {
        copyStoryMenuItem.style.backgroundColor = 'black';
    };
    menu.appendChild(copyStoryMenuItem);

    const copyAsPlainMenuItem = document.createElement('li');
    copyAsPlainMenuItem.id = 'copy-as-plain';
    copyAsPlainMenuItem.textContent = 'Copy as Plain';
    copyAsPlainMenuItem.style.padding = '5px';
    copyAsPlainMenuItem.style.cursor = 'pointer';
    copyAsPlainMenuItem.style.color = 'white';
    copyAsPlainMenuItem.style.listStyleType = 'none';
    copyAsPlainMenuItem.onmouseover = function() {
        copyAsPlainMenuItem.style.backgroundColor = '#222';
    };
    copyAsPlainMenuItem.onmouseout = function() {
        copyAsPlainMenuItem.style.backgroundColor = 'black';
    };
    menu.appendChild(copyAsPlainMenuItem);

    const copyAsHTMLMenuItem = document.createElement('li');
    copyAsHTMLMenuItem.id = 'copy-as-html';
    copyAsHTMLMenuItem.textContent = 'Copy as HTML';
    copyAsHTMLMenuItem.style.padding = '5px';
    copyAsHTMLMenuItem.style.cursor = 'pointer';
    copyAsHTMLMenuItem.style.color = 'white';
    copyAsHTMLMenuItem.style.listStyleType = 'none';
    copyAsHTMLMenuItem.onmouseover = function() {
        copyAsHTMLMenuItem.style.backgroundColor = '#222';
    };
    copyAsHTMLMenuItem.onmouseout = function() {
        copyAsHTMLMenuItem.style.backgroundColor = 'black';
    };
    menu.appendChild(copyAsHTMLMenuItem);

    const copyForTelegramMenuItem = document.createElement('li');
    copyForTelegramMenuItem.id = 'copy-for-telegram';
    copyForTelegramMenuItem.textContent = 'Copy for Telegram';
    copyForTelegramMenuItem.style.padding = '5px';
    copyForTelegramMenuItem.style.cursor = 'pointer';
    copyForTelegramMenuItem.style.color = 'white';
    copyForTelegramMenuItem.style.listStyleType = 'none';
    copyForTelegramMenuItem.onmouseover = function() {
        copyForTelegramMenuItem.style.backgroundColor = '#222';
    };
    copyForTelegramMenuItem.onmouseout = function() {
        copyForTelegramMenuItem.style.backgroundColor = 'black';
    };
    menu.appendChild(copyForTelegramMenuItem);

    // Function to check if the event target is inside an excluded div
    function isInsideExcludedDiv(target) {
        return target.closest('.flex.flex-col.gap-3.self-stretch, .flex.w-full.items-start.justify-between.gap-4') !== null;
    }

    // Show the custom context menu on right-click, unless inside the excluded div
    document.addEventListener('contextmenu', function(event) {
        if (isInsideExcludedDiv(event.target)) {
            menu.style.display = 'none';
            return; // Do nothing if inside the excluded div
        }

        event.preventDefault();
        menu.style.top = `${event.pageY}px`;
        menu.style.left = `${event.pageX}px`;
        menu.style.display = 'block';
    });

    // Hide the custom menu on click elsewhere
    document.addEventListener('click', function() {
        menu.style.display = 'none';
    });

    // Function to extract text between <span> and </span>
    function extractSpanText(html) {
        const spanMatches = html.match(/<span[^>]*>(.*?)<\/span>/g);
        if (spanMatches) {
            return spanMatches.map(span => span.replace(/<\/?span[^>]*>/g, ''));
        }
        return [];
    }

    // Function to convert HTML to rich text with text formatting only
    function htmlToRichText(html) {
        // Replace <strong> with bold formatting
        html = html.replace(/<strong>/g, '<b>');
        html = html.replace(/<\/strong>/g, '</b>');
        // Replace <em> with italic formatting
        html = html.replace(/<em>/g, '<i>');
        html = html.replace(/<\/em>/g, '</i>');
        // Add more replacements as needed for other HTML tags

        // Create a temporary div element
        const tempDiv = document.createElement('div');
        // Set its innerHTML
        tempDiv.innerHTML = html;

        // Strip any unwanted styles
        tempDiv.querySelectorAll('*').forEach(node => {
            node.removeAttribute('style'); // Remove all inline styles
            node.removeAttribute('bgcolor'); // Remove background color attribute
            // Add more style removals as necessary
        });

        // Return the innerText of the temporary div
        return tempDiv.innerHTML;
    }

    // Function to handle copying story as rich text
    function copyStory() {
        // Get all divs with the specified class
        const charDivs = document.querySelectorAll('.flex.flex-1.flex-col.items-start.gap-2');
        // Create a string array containing the innerHTML of each div
        const charDivContents = Array.from(charDivs).map(div => div.innerHTML);

        // Extract span contents from charDivContents
        const extractedCharContents = charDivContents.flatMap(html => extractSpanText(html));

        // Get all divs with the specified class
        const markdownDivs = document.querySelectorAll('.not-prose.w-full.MarkdownText_CustomMarkdownText__P3bB6');
        // Create a string array containing the innerHTML of each div
        const markdownContents = Array.from(markdownDivs).map(div => div.innerHTML);

        // Create a string to store the final story
        let storyString = '';

        // Iterate through the indices of markdownContents
        for (let i = 0; i < markdownContents.length; i++) {
            // Convert HTML to rich text
            let richTextContent = htmlToRichText(markdownContents[i]);

            if (i % 2 === 0 && extractedCharContents.length > 0) {
                storyString += "\n" + extractedCharContents[0] + ": \n" + richTextContent + "\n";
            } else {
                storyString += "\n{{user}}: \n" + richTextContent + "\n";
            }
        }

        // Create a temporary element to hold the HTML
        const tempCopyDiv = document.createElement('div');

        tempCopyDiv.innerHTML = storyString;
        document.body.appendChild(tempCopyDiv);

        // Create a range and selection to select the contents of the tempCopyDiv
        const range = document.createRange();
        range.selectNodeContents(tempCopyDiv);
        const selection = window.getSelection();
        selection.removeAllRanges();
        selection.addRange(range);

        // Copy the selection to the clipboard
        try {
            document.execCommand('copy');
        } catch (err) {
            console.error('Unable to copy to clipboard', err);
        }

        // Clean up the selection and remove the temporary element
        selection.removeAllRanges();
        document.body.removeChild(tempCopyDiv);

        // Hide the custom menu after click
        menu.style.display = 'none';
    }



    // Function to handle copying story as plain text for Telegram
    function copyAsPlain() {
        // Get all divs with the specified class
        const charDivs = document.querySelectorAll('.flex.flex-1.flex-col.items-start.gap-2');
        // Create a string array containing the innerHTML of each div
        const charDivContents = Array.from(charDivs).map(div => div.innerHTML);

        // Extract span contents from charDivContents
        const extractedCharContents = charDivContents.flatMap(html => extractSpanText(html));

        // Get all divs with the specified class
        const markdownDivs = document.querySelectorAll('.not-prose.w-full.MarkdownText_CustomMarkdownText__P3bB6');
        // Create a string array containing the innerHTML of each div
        const markdownContents = Array.from(markdownDivs).map(div => div.innerHTML);

        // Create a string to store the final story
        let storyString = '';

        // Iterate through the indices of markdownContents
        for (let i = 0; i < markdownContents.length; i++) {
            // Convert HTML to plain text
            let plainTextContent = htmlToPlainText(markdownContents[i]);

            if (i % 2 === 0 && extractedCharContents.length > 0) {
                storyString += "\n" + extractedCharContents[0] + ": \n" + plainTextContent + "\n";
            } else {
                storyString += "\n{{user}}: \n" + plainTextContent + "\n";
            }
        }

        // Copy storyString to clipboard as plain text for Telegram
        GM_setClipboard(storyString, 'text/plain');

        // Hide the custom menu after click
        menu.style.display = 'none';
    }

    // Function to handle copying for Telegram
    function copyForTelegram() {
        // Get all divs with the specified class
        const charDivs = document.querySelectorAll('.flex.flex-1.flex-col.items-start.gap-2');
        // Create a string array containing the innerHTML of each div
        const charDivContents = Array.from(charDivs).map(div => div.innerHTML);

        // Extract span contents from charDivContents
        const extractedCharContents = charDivContents.flatMap(html => extractSpanText(html));

        // Get all divs with the specified class
        const markdownDivs = document.querySelectorAll('.not-prose.w-full.MarkdownText_CustomMarkdownText__P3bB6');
        // Create a string array containing the innerHTML of each div
        const markdownContents = Array.from(markdownDivs).map(div => div.innerHTML);

        // Create a string to store the final story
        let storyString = '';

        // Iterate through the indices of markdownContents
        for (let i = 0; i < markdownContents.length; i++) {
            // Convert HTML to rich text
            let richTextContent = htmlToTelegram(markdownContents[i]);

            if (i % 2 === 0 && extractedCharContents.length > 0) {
                storyString += "\n" + extractedCharContents[0] + ": " + richTextContent + "\n";
            } else {
                storyString += "\n{{user}}: " + richTextContent + "\n";
            }
        }

        // Copy storyString to clipboard as plain text for Telegram
        GM_setClipboard(storyString, 'text/plain');
    }

    // Function to handle copying for Telegram
    function copyWithHtml() {
        // Get all divs with the specified class
        const charDivs = document.querySelectorAll('.flex.flex-1.flex-col.items-start.gap-2');
        // Create a string array containing the innerHTML of each div
        const charDivContents = Array.from(charDivs).map(div => div.innerHTML);

        // Extract span contents from charDivContents
        const extractedCharContents = charDivContents.flatMap(html => extractSpanText(html));

        // Get all divs with the specified class
        const markdownDivs = document.querySelectorAll('.not-prose.w-full.MarkdownText_CustomMarkdownText__P3bB6');
        // Create a string array containing the innerHTML of each div
        const markdownContents = Array.from(markdownDivs).map(div => div.innerHTML);

        // Create a string to store the final story
        let storyString = '';

        // Iterate through the indices of markdownContents
        for (let i = 0; i < markdownContents.length; i++) {
            // Convert HTML to rich text
            let richTextContent = htmlToRichText(markdownContents[i]);

            if (i % 2 === 0 && extractedCharContents.length > 0) {
                storyString += "\n" + extractedCharContents[0] + ": \n" + richTextContent + "\n";
            } else {
                storyString += "\n{{user}}: \n" + richTextContent + "\n";
            }
        }

        // Copy storyString to clipboard as plain text for Telegram
        GM_setClipboard(storyString, 'text/plain');
    }

    // Function to convert HTML to plain text
    function htmlToPlainText(html) {
        // Create a temporary div element
        const tempDiv = document.createElement('div');
        // Set its innerHTML
        tempDiv.innerHTML = html;

        // Return the innerText of the temporary div
        return tempDiv.innerText;
    }

    // Function to convert HTML to rich text with text formatting only
    function htmlToTelegram(html) {
        // Replace <strong> with bold formatting
        html = html.replace(/(<strong.*>)\b/g, '**');
        html = html.replace(/<\/strong>/g, '**');
        // Replace <em> with italic formatting
        html = html.replace(/(<em.*>)\b/g, '__');
        html = html.replace(/<\/em>/g, '__');
        // Replace <p> with new line
        html = html.replace(/<p>/g, '\n');
        html = html.replace(/<\/p>/g, '');

        // Create a temporary div element
        const tempDiv = document.createElement('div');
        // Set its innerHTML
        tempDiv.innerHTML = html;

        // Strip any unwanted styles
        tempDiv.querySelectorAll('*').forEach(node => {
            node.removeAttribute('style'); // Remove all inline styles
            node.removeAttribute('bgcolor'); // Remove background color attribute
            // Add more style removals as necessary
        });

        // Return the innerText of the temporary div
        return tempDiv.innerText;
    }

    // Add the click event listener to the "Copy Story" item
    document.getElementById('copy-story').addEventListener('click', function() {
        copyStory();
    });

    // Add the click event listener to the "Copy as HTML" item
    document.getElementById('copy-as-html').addEventListener('click', function() {
        copyWithHtml();
    });

    // Add the click event listener to the "Copy as Plain" item
    document.getElementById('copy-as-plain').addEventListener('click', function() {
        copyAsPlain();
    });

    // Add the click event listener to the "Copy for Telegram" item
    document.getElementById('copy-for-telegram').addEventListener('click', function() {
        copyForTelegram();
    });
})();