您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Draggable button with visual feedback! Remembers its position, adapts to screen size, and can't be dragged off-screen. Formats narration/dialogues.
// ==UserScript== // @name Janitor AI - Automatic Message Formatting Corrector (Drag & Drop button) // @namespace http://tampermonkey.net/ // @version 7.0 // @description Draggable button with visual feedback! Remembers its position, adapts to screen size, and can't be dragged off-screen. Formats narration/dialogues. // @author accforfaciet // @match *://janitorai.com/chats/* // @grant GM_addStyle // @run-at document-idle // @license MIT // ==/UserScript== (function() { 'use strict'; // --- SCRIPT SETTINGS --- const DEBUG_MODE = false; // Set to true for console logs const BUTTON_POSITION_KEY = 'formatterButtonPosition'; // Key for saving position // --- UNIVERSAL SELECTORS --- const EDIT_BUTTON_SELECTOR = 'button[title="Edit Message"], button[aria-label="Edit"]'; const TEXT_AREA_SELECTOR = 'textarea[style*="font-size: 16px"][style*="!important"]'; const CONFIRM_BUTTON_SELECTOR = 'button[aria-label="Confirm"], button[aria-label="Save"]'; // --- DEBUGGING TOOLS --- function debugLog(...args) { if (DEBUG_MODE) console.log('[DEBUG]', ...args); } function waitForElement(selector) { return new Promise(resolve => { const el = document.querySelector(selector); if (el) return resolve(el); const observer = new MutationObserver(() => { const el = document.querySelector(selector); if (el) { observer.disconnect(); resolve(el); } }); observer.observe(document.body, { childList: true, subtree: true }); }); } // --- CORE TEXT PROCESSING FUNCTIONS --- function removeThinkTags(text) { text = text.replace(/\n?\s*<(thought|thoughts)>[\s\S]*?<\/(thought|thoughts)>\s*\n?/g, ''); text = text.replace(/<(system|response)>|<\/response>/g, ''); text = text.replace(/\n?\s*<think>[\s\S]*?<\/think>\s*\n?/g, ''); text = text.replace('</think>', ''); return removeSystemPrompt(text); } function formatNarrationAndDialogue(text) { text = removeThinkTags(text); const normalizedText = text.replace(/[«“”„‟⹂❞❝]/g, '"'); const lines = normalizedText.split('\n'); return lines.map(line => { const trimmedLine = line.trim(); if (trimmedLine === '') return ''; const cleanLine = trimmedLine.replace(/\*/g, ''); if (cleanLine.includes('"') || cleanLine.includes('`')) { return cleanLine.split(/("[\s\S]*?"|`[\s\S]*?`)/) .map(frag => { if ((frag.startsWith('"') && frag.endsWith('"')) || (frag.startsWith('`') && frag.endsWith('`'))) return frag; return frag.trim() !== '' ? `*${frag.trim()}*` : ''; }).filter(Boolean).join(' '); } return `*${cleanLine}*`; }).join('\n'); } function removeSystemPrompt(text) { if (!text.trim().toLowerCase().includes('theuser')) return text; const splitPointIndex = text.search(/[^\s\*]\*[^\s\*]/); if (splitPointIndex !== -1) { debugLog(`System prompt found. The text will be trimmed.`); return text.substring(splitPointIndex + 1); } return text; } // --- MAIN ACTION SEQUENCE --- async function processLastMessage(textProcessor) { debugLog('--- STARTING EDIT PROCESS ---'); try { const allEditButtons = document.querySelectorAll(EDIT_BUTTON_SELECTOR); if (allEditButtons.length === 0) { debugLog('STOP: No edit buttons found.'); return; } const lastEditButton = allEditButtons[allEditButtons.length - 1]; lastEditButton.click(); await new Promise(resolve => setTimeout(resolve, 500)); // Wait for modal const textField = await waitForElement(TEXT_AREA_SELECTOR); const originalText = textField.value; const newText = textProcessor(originalText); textField.value = newText; textField.dispatchEvent(new Event('input', { bubbles: true })); const confirmButton = await waitForElement(CONFIRM_BUTTON_SELECTOR); if (confirmButton) confirmButton.click(); debugLog('--- PROCESS COMPLETED SUCCESSFULLY ---'); } catch (error) { console.error('CRITICAL ERROR during edit process:', error); } } /** * Makes the button draggable, handles clicks, and applies visual effects. * @param {HTMLElement} button The button element to make draggable. */ function makeButtonDraggable(button) { let isDragging = false; let wasDragged = false; let offsetX, offsetY; // Load saved position const savedPosition = localStorage.getItem(BUTTON_POSITION_KEY); if (savedPosition) { const { top, left } = JSON.parse(savedPosition); button.style.top = top; button.style.left = left; button.style.right = 'auto'; button.style.bottom = 'auto'; } function dragStart(e) { e.preventDefault(); isDragging = true; wasDragged = false; button.classList.add('is-dragging'); // NEW: Add visual effect class const clientX = e.type === 'touchstart' ? e.touches[0].clientX : e.clientX; const clientY = e.type === 'touchstart' ? e.touches[0].clientY : e.clientY; offsetX = clientX - button.getBoundingClientRect().left; offsetY = clientY - button.getBoundingClientRect().top; document.addEventListener('mousemove', dragMove); document.addEventListener('touchmove', dragMove, { passive: false }); document.addEventListener('mouseup', dragEnd); document.addEventListener('touchend', dragEnd); } function dragMove(e) { if (!isDragging) return; e.preventDefault(); wasDragged = true; const clientX = e.type === 'touchmove' ? e.touches[0].clientX : e.clientX; const clientY = e.type === 'touchmove' ? e.touches[0].clientY : e.clientY; let newLeft = clientX - offsetX; let newTop = clientY - offsetY; // Constrain to viewport to prevent dragging off-screen const buttonRect = button.getBoundingClientRect(); newLeft = Math.max(0, Math.min(newLeft, window.innerWidth - buttonRect.width)); newTop = Math.max(0, Math.min(newTop, window.innerHeight - buttonRect.height)); button.style.right = 'auto'; button.style.bottom = 'auto'; button.style.left = `${newLeft}px`; button.style.top = `${newTop}px`; } function dragEnd() { if (!isDragging) return; isDragging = false; button.classList.remove('is-dragging'); // NEW: Remove visual effect class document.removeEventListener('mousemove', dragMove); document.removeEventListener('touchmove', dragMove); document.removeEventListener('mouseup', dragEnd); document.removeEventListener('touchend', dragEnd); if (wasDragged) { // Save the final position const pos = { top: button.style.top, left: button.style.left }; localStorage.setItem(BUTTON_POSITION_KEY, JSON.stringify(pos)); } else { // If not dragged, it's a click. processLastMessage(formatNarrationAndDialogue); } } button.addEventListener('mousedown', dragStart); button.addEventListener('touchstart', dragStart, { passive: false }); } /** * Creates the main button. */ function createTriggerButton() { const buttonContainer = document.createElement('div'); buttonContainer.id = 'janitor-editor-buttons'; document.body.appendChild(buttonContainer); const formatButton = document.createElement('button'); formatButton.innerHTML = '✏️'; formatButton.id = 'formatterTrigger'; formatButton.title = 'Format asterisks (Click) or Move Button (Drag)'; buttonContainer.appendChild(formatButton); makeButtonDraggable(formatButton); } // --- MOBILE KEYBOARD FIX --- async function initKeyboardBugFix() { try { const mainInput = await waitForElement('textarea[placeholder^="Type a message"]'); const buttonContainer = document.getElementById('janitor-editor-buttons'); if (!mainInput || !buttonContainer) return; mainInput.addEventListener('focus', () => { buttonContainer.style.display = 'none'; }); mainInput.addEventListener('blur', () => { setTimeout(() => { buttonContainer.style.display = 'block'; }, 200); }); } catch (e) { /* Expected to fail on PC, no error needed */ } } // --- ADAPTIVE STYLES --- GM_addStyle(` #janitor-editor-buttons button { position: fixed; z-index: 9999; color: white; border: none; border-radius: 50%; box-shadow: 0 4px 8px rgba(0,0,0,0.3); cursor: pointer; transition: transform 0.2s, opacity 0.2s, box-shadow 0.2s; user-select: none; /* Prevents text selection while dragging */ } #janitor-editor-buttons button:active { /* Kept for quick click feedback, drag effect overrides it */ transform: scale(0.95); } #formatterTrigger { background-color: #c9226e; } /* NEW: Visual effect for when the button is being dragged */ #janitor-editor-buttons button.is-dragging { transform: scale(1.1); /* Make it slightly bigger */ opacity: 0.8; /* Make it semi-transparent */ box-shadow: 0 8px 16px rgba(0,0,0,0.4); /* Enhance the shadow */ transition: none; /* Disable transition for smooth dragging */ } /* PC STYLES (default position) */ @media (min-width: 769px) { #formatterTrigger { width: 50px; height: 50px; font-size: 24px; right: 27%; bottom: 12%; } } /* MOBILE STYLES (default position) */ @media (max-width: 768px) { #formatterTrigger { width: 40px; height: 40px; font-size: 16px; right: 28%; bottom: 20%; } } `); // --- LAUNCH SCRIPT --- createTriggerButton(); initKeyboardBugFix(); console.log('Script "Janitor AI - Automatic Message Formatting Corrector (Drag & Drop button)" (v7.0) launched successfully.'); })();