Janitor AI - Automatic Message Formatting Corrector (Settings Menu)

Draggable button with Settings! Select Italics/Bold/Plain text. Edge compatible. Remembers position. Formats narration & dialogues.

< 脚本 Janitor AI - Automatic Message Formatting Corrector (Settings Menu) 的反馈

评价:差评 - 脚本失效或无法使用

§
发布于:2025-11-13

Unusable on MS Edge.

I've located the problem. The problem is the textarea selector is relying on exact style matching with spaces and !important, which didn't work in Edge due to browser differences in attribute rendering.

Pls fix

§
发布于:2025-11-13

My homemade fix:

// ==UserScript==
// @name         Janitor AI - Automatic Message Formatting Corrector (Drag & Drop button)
// @namespace    http://tampermonkey.net/
// @version      7.1
// @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
// @downloadURL  https://update.greasyfork.org/scripts/551458/Janitor%20AI%20-%20Automatic%20Message%20Formatting%20Corrector%20%28Drag%20%20Drop%20button%29.user.js
// @updateURL    https://update.greasyfork.org/scripts/551458/Janitor%20AI%20-%20Automatic%20Message%20Formatting%20Corrector%20%28Drag%20%20Drop%20button%29.meta.js
// ==/UserScript==
(function() {
    'use strict';
    // --- SCRIPT SETTINGS ---
    const DEBUG_MODE = true;
    const BUTTON_POSITION_KEY = 'formatterButtonPosition';
    // --- UNIVERSAL SELECTORS ---
    const EDIT_BUTTON_SELECTOR = 'button[title="Edit Message"], button[aria-label="Edit"]';
    const TEXT_AREA_SELECTOR = 'textarea[style*="font-size:16px"], textarea[style*="font-size: 16px"], textarea[class*="_autoResizeTextarea"]';
    const CONFIRM_BUTTON_SELECTOR = 'button[aria-label="Confirm"], button[aria-label="Save"], button[aria-label*="Confirm"], button[aria-label*="Save"]';
    // --- DEBUGGING TOOLS ---
    function debugLog(...args) { if (DEBUG_MODE) console.log('[DEBUG]', ...args); }
    function waitForElement(selector, timeoutMs = 10000) {
        return new Promise(resolve => {
            let el = document.querySelector(selector);
            if (el) {
                debugLog(`Element found immediately: ${selector}`);
                return resolve(el);
            }
            debugLog(`Waiting for element: ${selector}`);
            const startTime = Date.now();
            const observer = new MutationObserver(() => {
                const elapsed = Date.now() - startTime;
                if (elapsed > timeoutMs) {
                    observer.disconnect();
                    debugLog(`Timeout (${timeoutMs}ms) waiting for: ${selector}`);
                    return resolve(null);
                }
                el = document.querySelector(selector);
                if (el) {
                    observer.disconnect();
                    debugLog(`Element found after ${elapsed}ms: ${selector}`);
                    return resolve(el);
                }
            });
            observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });
        });
    }
    // --- 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. Text 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);
            debugLog(`Found ${allEditButtons.length} edit buttons`);
            if (allEditButtons.length === 0) {
                debugLog('STOP: No edit buttons found.');
                return;
            }
            const lastEditButton = allEditButtons[allEditButtons.length - 1];
            debugLog('Clicking last edit button');
            lastEditButton.click();
            debugLog('Edit button clicked, waiting 800ms for modal');
            await new Promise(resolve => setTimeout(resolve, 800));
            const textField = await waitForElement(TEXT_AREA_SELECTOR);
            if (!textField) {
                debugLog('STOP: Text field not found');
                return;
            }
            debugLog('Text field found');
            const originalText = textField.value;
            debugLog(`Original text length: ${originalText.length}`);
            const newText = textProcessor(originalText);
            debugLog(`New text length: ${newText.length}`);
            textField.value = newText;
            textField.dispatchEvent(new Event('input', { bubbles: true }));
            textField.dispatchEvent(new Event('change', { bubbles: true }));
            debugLog('Text updated, events dispatched');
            const confirmButton = await waitForElement(CONFIRM_BUTTON_SELECTOR, 5000);
            if (!confirmButton) {
                debugLog('STOP: Confirm button not found');
                return;
            }
            debugLog('Confirm button found, clicking');
            confirmButton.click();
            debugLog('--- PROCESS COMPLETED SUCCESSFULLY ---');
        } catch (error) {
            debugLog('ERROR in process:', error.message);
            console.error('CRITICAL ERROR during edit process:', error);
        }
    }
    // --- DRAGGABLE BUTTON ---
    function makeButtonDraggable(button) {
        let isDragging = false;
        let wasDragged = false;
        let offsetX, offsetY;
        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');
            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;
            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');
            document.removeEventListener('mousemove', dragMove);
            document.removeEventListener('touchmove', dragMove);
            document.removeEventListener('mouseup', dragEnd);
            document.removeEventListener('touchend', dragEnd);
            if (wasDragged) {
                const pos = { top: button.style.top, left: button.style.left };
                localStorage.setItem(BUTTON_POSITION_KEY, JSON.stringify(pos));
            } else {
                processLastMessage(formatNarrationAndDialogue);
            }
        }
        button.addEventListener('mousedown', dragStart);
        button.addEventListener('touchstart', dragStart, { passive: false });
    }
    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"]', 10000);
            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) {}
    }
    // --- 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;
        }
        #janitor-editor-buttons button:active {
            transform: scale(0.95);
        }
        #formatterTrigger {
            background-color: #c9226e;
        }
        #janitor-editor-buttons button.is-dragging {
            transform: scale(1.1);
            opacity: 0.8;
            box-shadow: 0 8px 16px rgba(0,0,0,0.4);
            transition: none;
        }
        @media (min-width: 769px) {
            #formatterTrigger {
                width: 50px; height: 50px; font-size: 24px;
                right: 27%; bottom: 12%;
            }
        }
        @media (max-width: 768px) {
            #formatterTrigger {
                width: 40px; height: 40px; font-size: 16px;
                right: 28%; bottom: 20%;
            }
        }
    `);
    // --- LAUNCH ---
    createTriggerButton();
    initKeyboardBugFix();
    console.log('Script "Janitor AI - Automatic Message Formatting Corrector" (v7.1) launched.');
})();
accforfaciet作者
§
发布于:2025-12-05

Subject: Fixed in v8.0 - Thank you!

This is fantastic debugging, thank you so much! You were spot on—relying on exact style string matching was brittle across different browsers like Edge.

I have integrated your fix into Version 8.0. I've updated the selectors to use the more robust class names and looser style matching you suggested, and implemented the waitForElement logic to ensure it catches the text box even if the browser lags.

It should now work perfectly on Edge out of the box. Thanks for helping make the script better!

发布留言

登录以发布留言。