JanitorAI JannyAI Buttons

Inserts a JannyAI button into Character and My Chats Pages for convenience.

// ==UserScript==
// @name         JanitorAI JannyAI Buttons
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Inserts a JannyAI button into  Character and My Chats Pages for convenience.
// @author       firecat (and Selene)
// @match        https://janitorai.com/*
// @grant        none
// @license      WTFPL
// ==/UserScript==

(function() {
    'use strict';

    // Debug configuration
    const DEBUG_MODE = false; // Set to true for debugging
    let processing = false;
    const DEBOUNCE_DELAY = 300;
    const INITIAL_DELAY = 800;
    let retryCount = 0;
    const MAX_RETRIES = 2;

    const debugLog = (...args) => {
        if (DEBUG_MODE) {
            console.log('[JannyAI-Redirect]', ...args);
        }
    };

    const shouldRunOnPage = () => {
        return window.location.pathname.includes('/characters/') ||
               window.location.pathname === '/my_chats';
    };

    const createJannyButton = (originalUrl) => {
        debugLog('Creating JannyAI button for URL:', originalUrl);
        const button = document.createElement('button');
        button.className = 'jannyai-button';
        button.innerHTML = '<span style="margin-right:6px">🃏</span>JannyAI';
        button.style.cssText = `
            background: #20B2AA !important;
            color: white !important;
            margin: 8px 0 !important;
            padding: 8px 16px !important;
            border-radius: 4px !important;
            font-size: 0.9rem !important;
            font-family: Jura,sans-serif !important;
            height: var(--chakra-sizes-8);
            display: inline-flex;
            appearance: none;
            -moz-box-align: center;
            align-items: center;
            -moz-box-pack: center;
            justify-content: center;
            user-select: none;
            position: relative;
            white-space: nowrap;
            vertical-align: middle;
            outline: transparent solid 2px;
            outline-offset: 2px;
            line-height: 1.2;
            border-radius: var(--chakra-radii-md);
            font-weight: var(--chakra-fontWeights-semibold);
            transition-property: var(--chakra-transition-property-common);
            transition-duration: var(--chakra-transition-duration-normal);
            height: var(--chakra-sizes-8);
            min-width: var(--chakra-sizes-8);
            font-size: var(--chakra-fontSizes-sm);
            padding-inline-start: var(--chakra-space-3);
            padding-inline-end: var(--chakra-space-3);
            background: var(--chakra-colors-red-200);
            color: var(--chakra-colors-gray-800);
        `;
        button.onclick = () => {
            debugLog('Button clicked - redirecting to JannyAI');
            window.open(originalUrl.replace('janitorai.com', 'jannyai.com'), '_blank');
        };
        return button;
    };

    const handle404Page = () => {
        if (!shouldRunOnPage() || processing) {
            debugLog('Already processing or not target page - skipping 404 check');
            return;
        }
        processing = true;
        debugLog('Processing 404 page check');

        const notFoundContainer = document.querySelector('.css-64aicp')?.parentElement;
        if (!notFoundContainer) {
            debugLog('No 404 container found');
            processing = false;
            return;
        }

        if (notFoundContainer.querySelector('.jannyai-button')) {
            debugLog('JannyAI button already exists on 404 page');
            processing = false;
            return;
        }

        const homePageLink = notFoundContainer.querySelector('a[href="/"]');
        if (!homePageLink) {
            debugLog('Home page link not found in 404 container');
            processing = false;
            return;
        }

        debugLog('Injecting JannyAI button into 404 page');
        const jannyButton = createJannyButton(window.location.href);
        notFoundContainer.insertBefore(jannyButton, homePageLink.nextSibling);

        processing = false;
    };

    const handleCharacterPages = () => {
        if (!shouldRunOnPage() || processing) {
            debugLog('Already processing or not target page - skipping character page');
            return;
        }
        processing = true;
        debugLog('Processing character page');

        const targetDiv = document.querySelector('.css-3f016y');
        if (!targetDiv) {
            debugLog('Character page target div not found');

            // Check if this is actually a 404 page first
            if (document.querySelector('.css-64aicp')) {
                debugLog('Found 404 page instead of character page');
                handle404Page();
                return;
            }

            if (retryCount < MAX_RETRIES) {
                debugLog(`Retrying character page check (${retryCount + 1}/${MAX_RETRIES})`);
                retryCount++;
                setTimeout(handleCharacterPages, 500);
            } else {
                debugLog('Max retries reached for character page check');
            }
            processing = false;
            return;
        }

        if (targetDiv.querySelector('.jannyai-button')) {
            debugLog('JannyAI button already exists on character page');
            processing = false;
            return;
        }

        const jannyButton = createJannyButton(window.location.href);
        const actionButtons = targetDiv.querySelector('.css-70qvj9, button');

        if (actionButtons) {
            debugLog('Inserting JannyAI button before action buttons');
            targetDiv.insertBefore(jannyButton, actionButtons);
        } else {
            debugLog('Prepending JannyAI button to target div');
            targetDiv.prepend(jannyButton);
        }

        retryCount = 0;
        setTimeout(() => { processing = false; }, DEBOUNCE_DELAY);
    };

    const handleChatListings = () => {
        if (!shouldRunOnPage() || processing) {
            debugLog('Already processing or not target page - skipping chat listings');
            return;
        }
        processing = true;
        debugLog('Processing chat listings page');

        const chatBoxes = document.querySelectorAll('.css-ch9mb');
        if (chatBoxes.length === 0 && retryCount < MAX_RETRIES) {
            debugLog(`No chat boxes found, retrying (${retryCount + 1}/${MAX_RETRIES})`);
            retryCount++;
            setTimeout(handleChatListings, 500);
            processing = false;
            return;
        }

        debugLog(`Found ${chatBoxes.length} chat boxes`);
        chatBoxes.forEach((chatBox, index) => {
            const charPageButton = chatBox.nextElementSibling;
            if (charPageButton?.classList.contains('_characterPageButton_1gdr6_37') &&
                !charPageButton.nextElementSibling?.classList.contains('jannyai-button')) {
                debugLog(`Injecting JannyAI button for chat box ${index + 1}`);
                const jannyButton = createJannyButton(charPageButton.href);
                charPageButton.parentNode.insertBefore(jannyButton, charPageButton.nextSibling);
            }
        });

        retryCount = 0;
        setTimeout(() => { processing = false; }, DEBOUNCE_DELAY);
    };

    const runObserver = () => {
        if (!shouldRunOnPage()) {
            debugLog('Not a target page - observer skipping');
            return;
        }

        debugLog('Observer triggered - checking page state');
        setTimeout(() => {
            if (document.querySelector('.css-64aicp')) {
                debugLog('404 page detected');
                handle404Page();
            } else if (window.location.pathname.includes('/characters/')) {
                debugLog('Character page detected');
                handleCharacterPages();
            } else if (window.location.pathname === '/my_chats') {
                debugLog('Chat listings page detected');
                handleChatListings();
            } else {
                debugLog('Unknown page type - no action taken');
            }
        }, INITIAL_DELAY);
    };

    // New history event handlers
    history.pushState = (f => function pushState() {
        debugLog('pushState detected - dispatching events');
        const ret = f.apply(this, arguments);
        window.dispatchEvent(new Event('pushstate'));
        window.dispatchEvent(new Event('locationchange'));
        return ret;
    })(history.pushState);

    history.replaceState = (f => function replaceState() {
        debugLog('replaceState detected - dispatching events');
        const ret = f.apply(this, arguments);
        window.dispatchEvent(new Event('replacestate'));
        window.dispatchEvent(new Event('locationchange'));
        return ret;
    })(history.replaceState);

    const observer = new MutationObserver((mutations) => {
        debugLog(`Mutation observed - ${mutations.length} changes`);
        if (mutations.some(m => m.addedNodes.length > 0)) {
            debugLog('Added nodes detected - triggering runObserver');
            runObserver();
        }
    });

    // Initial run
    debugLog('Starting initial observer run');
    setTimeout(runObserver, INITIAL_DELAY);

    observer.observe(document.body, {
        childList: true,
        subtree: true
    });

    window.addEventListener('locationchange', () => {
        debugLog('locationchange event received - resetting retries');
        retryCount = 0;
        runObserver();
    });

    debugLog('JannyAI redirect script initialized');
})();