Janitor AI - Automatic Message Formatting Corrector (Drag & Drop button)

Draggable button with visual feedback! Remembers its position, adapts to screen size, and can't be dragged off-screen. Formats narration/dialogues.

< Feedback on Janitor AI - Automatic Message Formatting Corrector (Drag & Drop button)

Review: Bad - script does not work

§
Posted: 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

§
Posted: 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.');
})();

Post reply

Sign in to post a reply.