您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Sleazy Fork is available in English.
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(); }); })();