Copy Crushon Story

Copies all listed chat bubbles with formatting

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

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.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

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