Chaturbate PM Assistant

Automated PM system for Chaturbate broadcasters - Manual & Auto modes with token user filtering and exclusion list. NB! The scripts loads the user menu to detect gender and if is streaming, so there is a tiny flash when it's loading it.

K instalaci tototo skriptu si budete muset nainstalovat rozšíření jako Tampermonkey, Greasemonkey nebo Violentmonkey.

You will need to install an extension such as Tampermonkey to install this script.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Violentmonkey.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Userscripts.

You will need to install an extension such as Tampermonkey to install this script.

K instalaci tohoto skriptu si budete muset nainstalovat manažer uživatelských skriptů.

(Už mám manažer uživatelských skriptů, nechte mě ho nainstalovat!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Už mám manažer uživatelských stylů, nechte mě ho nainstalovat!)

// ==UserScript==
// @name            Chaturbate PM Assistant
// @name:es         Chaturbate PM Assistant
// @namespace       http://tampermonkey.net/
// @version         1.0
// @description     Automated PM system for Chaturbate broadcasters - Manual & Auto modes with token user filtering and exclusion list. NB! The scripts loads the user menu to detect gender and if is streaming, so there is a tiny flash when it's loading it.
// @description:es  Sistema PM automatizado para emisoras de Chaturbate - Modos manual y automático con filtrado de usuarios de token y lista de exclusión. ¡NB! Los scripts cargan el menú de usuario para detectar el género y si está transmitiendo, por lo que hay un pequeño flash cuando lo está cargando.
// @author          brsrkr
// @match           https://chaturbate.com/b/*
// @match           https://www.chaturbate.com/b/*
// @license         MIT 
// @grant           GM_setValue
// @grant           GM_getValue
// @grant           GM_registerMenuCommand
// @run-at          document-idle
// ==/UserScript==

(function() {
    'use strict';

    // ==================== CONFIGURATION ====================
    const CONFIG = {
        // Your custom PM messages
        welcomeMessage: "Hey! 👋 Thanks for visiting! How are you today?",

        // Automation settings
        autoMode: GM_getValue('autoMode', false),

        // Gender filters
        genderFilters: {
            male: GM_getValue('genderFilter_male', true),
            female: GM_getValue('genderFilter_female', true),
            couple: GM_getValue('genderFilter_couple', true),
            trans: GM_getValue('genderFilter_trans', true)
        },

        // Per-gender token requirements
        tokenFilters: {
            male: GM_getValue('tokenFilter_male', false),
            female: GM_getValue('tokenFilter_female', false),
            couple: GM_getValue('tokenFilter_couple', false),
            trans: GM_getValue('tokenFilter_trans', false),
            broadcaster: GM_getValue('tokenFilter_broadcaster', false)
        },

        // Sound notifications
        soundEnabled: GM_getValue('soundEnabled', true),

        // Broadcaster/Model filter
        sendToBroadcasters: GM_getValue('sendToBroadcasters', true),

        // Rate limiting (safety feature)
        minDelayBetweenPMs: 1000, // 1 second minimum between auto-PMs
        maxPMsPerHour: 300,

        // Cooldown for same user
        userCooldown: 7200000, // 2 hour - won't PM same user twice within this time

        // PM button display time (in milliseconds)
        buttonDisplayTime: 30000, // 30 seconds

        // DEPRECATED: Gender detection delay (no longer used with popup-based detection)
        // Kept for backwards compatibility but has no effect
        // Detection now happens via popup which is faster and more reliable
        genderDetectionDelay: 2500, // Not used

        // Language setting
        language: GM_getValue('language', 'en'), // 'en' or 'es' (or any key in LANG)
        blacklist: GM_getValue('blacklist', []),

        // Exclusion list (users you don't want popup buttons for - can be managed)
        exclusionList: GM_getValue('exclusionList', [])
    };

    // ==================== STATE TRACKING ====================
    let pmHistory = GM_getValue('pmHistory', {});
    let pmCount = 0;
    let hourlyResetTime = Date.now() + 3600000;
    let lastPMTime = 0;
    // ==================== LOCALIZATION ====================
    // HOW TO ADD A NEW LANGUAGE:
    // 1. Copy the entire 'en: { ... }' block below
    // 2. Paste it after the Spanish block and change 'en' to your language code (e.g. 'fr', 'de', 'pt')
    // 3. Translate all the string values (right-hand side of each line)
    // 4. Keep the function values like: tokenTip: (g) => `...${g}...`  - just translate the text around ${g}
    // 5. The language toggle button will automatically cycle through all languages you add
    const LANG = {
        en: {
            title:              'PM Assistant',
            autoPMOn:           '🟢 Auto PM',
            autoPMOff:          '⚪ Auto PM',
            sound:              '🔊 Sound',
            genderSection:      'Auto PM Genders & Tokens:',
            male:               '♂️ Male',
            female:             '♀️ Female',
            couple:             '👥 Couple',
            trans:              '⚧️ Trans',
            models:             '📷 Models',
            tokenTip:           (g) => `Click to toggle: Require tokens for ${g}`,
            genderTip:          (g) => `Enable PMs to ${g} users`,
            clearLog:           '🗑️ Clear',
            clearLogTip:        'Clear the activity log',
            message:            '✏️ Message',
            messageTip:         'Edit your welcome message',
            list:               (n) => `📋 List (${n})`,
            listTip:            "Manage list of users who won't receive PM popups",
            modeLabel:          'Mode:',
            sentLabel:          'Sent:',
            remainingLabel:     'Remaining:',
            cooldownLabel:      'Cooldown:',
            manual:             'MANUAL',
            auto:               'AUTO',
            logPlaceholder:     'Waiting for users...',
            excludeTitle:       'Exclusion List',
            addExclude:         'Add User to Exclusion:',
            excludePlaceholder: 'Enter username...',
            addBtn:             '🚫 Add',
            addBtnTip:          'Add this user to exclusion list',
            inputTip:           'Type a username to add to exclusion list',
            noExcluded:         'No users in exclusion list',
            removeBtn:          '× Remove',
            pmBtn:              'PM',
            pmBtnTip:           'Send PM to this user',
            excludeBtn:         '🚫',
            excludeBtnTip:      'Add to exclusion list (no PM popups)',
            excludeBtnPopupTip: 'Exclude user (no more popups)',
            confirmClearLog:    'Clear the activity log?',
            msgTitle:           'Welcome Message',
            msgLabel:           'Message sent to new users:',
            msgSave:            '💾 Save',
            msgCancel:          '✕ Cancel',
            langLabel:          '🌐',
            // Log messages
            logModerator:       'moderator (auto-excluded)',
            logNoTokens:        'no tokens (required for',
            logGenderFiltered:  'gender filtered',
            logBroadcasterFiltered: 'broadcaster filtered (models disabled)',
            logExclusionList:   'in exclusion list',
            logAddedExclusion:  'added to exclusion list',
            logAlreadyExcluded: 'already in exclusion list',
            logRemovedExclusion: 'removed from exclusion list',
            logNotInExclusion:  'not found in exclusion list',
            logAutoEnabled:     'Auto mode ENABLED',
            logAutoDisabled:    'Auto mode DISABLED',
            logDetected:        'detected',
            logPMSent:          'PM sent to',
            logPMScheduled:     'Auto PM scheduled',
        },
        es: {
            title:              'Asistente PM',
            autoPMOn:           '🟢 PM Auto',
            autoPMOff:          '⚪ PM Auto',
            sound:              '🔊 Sonido',
            genderSection:      'Géneros y Tokens para PM:',
            male:               '♂️ Hombre',
            female:             '♀️ Mujer',
            couple:             '👥 Pareja',
            trans:              '⚧️ Trans',
            models:             '📷 Modelos',
            tokenTip:           (g) => `Clic para cambiar: Requerir tokens para ${g}`,
            genderTip:          (g) => `Activar PMs para ${g}`,
            clearLog:           '🗑️ Borrar',
            clearLogTip:        'Borrar el registro de actividad',
            message:            '✏️ Mensaje',
            messageTip:         'Editar tu mensaje de bienvenida',
            list:               (n) => `📋 Lista (${n})`,
            listTip:            'Gestionar la lista de usuarios que no recibirán popups de PM',
            modeLabel:          'Modo:',
            sentLabel:          'Enviados:',
            remainingLabel:     'Restantes:',
            cooldownLabel:      'Espera:',
            manual:             'MANUAL',
            auto:               'AUTO',
            logPlaceholder:     'Esperando usuarios...',
            excludeTitle:       'Lista de Exclusión',
            addExclude:         'Añadir usuario a la exclusión:',
            excludePlaceholder: 'Nombre de usuario...',
            addBtn:             '🚫 Añadir',
            addBtnTip:          'Añadir usuario a la lista de exclusión',
            inputTip:           'Escribe un nombre de usuario para añadir a la exclusión',
            noExcluded:         'No hay usuarios en la lista de exclusión',
            removeBtn:          '× Quitar',
            pmBtn:              'PM',
            pmBtnTip:           'Enviar PM a este usuario',
            excludeBtn:         '🚫',
            excludeBtnTip:      'Añadir a exclusión (sin popups de PM)',
            excludeBtnPopupTip: 'Excluir usuario (sin más popups)',
            confirmClearLog:    '¿Borrar el registro de actividad?',
            msgTitle:           'Mensaje de Bienvenida',
            msgLabel:           'Mensaje enviado a nuevos usuarios:',
            msgSave:            '💾 Guardar',
            msgCancel:          '✕ Cancelar',
            langLabel:          '🌐',
            // Log messages
            logModerator:       'moderador (auto-excluido)',
            logNoTokens:        'sin tokens (requerido para',
            logGenderFiltered:  'género filtrado',
            logBroadcasterFiltered: 'modelo filtrado (modelos deshabilitados)',
            logExclusionList:   'en lista de exclusión',
            logAddedExclusion:  'añadido a lista de exclusión',
            logAlreadyExcluded: 'ya está en lista de exclusión',
            logRemovedExclusion: 'eliminado de lista de exclusión',
            logNotInExclusion:  'no encontrado en lista de exclusión',
            logAutoEnabled:     'Modo Auto ACTIVADO',
            logAutoDisabled:    'Modo Auto DESACTIVADO',
            logDetected:        'detectado',
            logPMSent:          'PM enviado a',
            logPMScheduled:     'PM Auto programado',
        }
    };

    // Helper to get current language strings
    function t() {
        return LANG[CONFIG.language] || LANG['en'];
    }

    // ==================== STATE ====================
    let processedUsers = new Set();
    let genderCache = {}; // Store detected genders by username
    let broadcasterCache = {}; // Store broadcaster status by username
    let pendingUsers = new Set(); // Track users waiting for gender detection

    // ==================== UI ELEMENTS ====================
    let controlPanel;
    let statusDisplay;
    let logDisplay;

    // ==================== HELPER: FIND CHAT FUNCTIONS ====================

    function getRoomName() {
        // Extract room name from URL: chaturbate.com/b/ROOMNAME
        const match = window.location.pathname.match(/\/b\/([^\/]+)/);
        return match ? match[1] : null;
    }

    function getBroadcasterUsername() {
        // Same as room name for broadcaster
        return getRoomName();
    }

    function getCSRFToken() {
        // Extract CSRF token from cookie
        const match = document.cookie.match(/csrftoken=([^;]+)/);
        if (match) return match[1];

        // Fallback: try meta tag
        const metaTag = document.querySelector('[name="csrf-token"]');
        if (metaTag) return metaTag.content;

        // Fallback: try hidden input
        const hiddenInput = document.querySelector('input[name="csrfmiddlewaretoken"]');
        if (hiddenInput) return hiddenInput.value;

        console.error('[PM Assistant] CSRF token not found!');
        return null;
    }

    async function sendPrivateMessage(username, message) {
        const room = getRoomName();
        const fromUser = getBroadcasterUsername();
        const csrfToken = getCSRFToken();

        if (!room || !fromUser || !csrfToken) {
            console.error('[PM Assistant] Missing required data:', { room, fromUser, csrfToken });
            return false;
        }

        try {
            // Format message as JSON like Chaturbate expects
            const messageData = JSON.stringify({
                m: message,
                media_id: []
            });

            // Prepare form data
            const formData = new URLSearchParams();
            formData.append('room', room);
            formData.append('to_user', username);
            formData.append('from_user', fromUser);
            formData.append('message', messageData);
            formData.append('csrfmiddlewaretoken', csrfToken);

            // Send PM via Chaturbate API
            const response = await fetch('https://chaturbate.com/api/ts/chatmessages/pm_publish/', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/x-www-form-urlencoded',
                    'X-CSRFToken': csrfToken,
                    'X-Requested-With': 'XMLHttpRequest'
                },
                body: formData.toString(),
                credentials: 'include'
            });

            if (response.ok) {
                console.log(`[PM Assistant] PM sent to ${username}`);
                return true;
            } else {
                const errorText = await response.text();
                console.error(`[PM Assistant] PM failed: ${response.status}`, errorText);
                return false;
            }
        } catch (error) {
            console.error(`[PM Assistant] Error sending PM:`, error);
            return false;
        }
    }

    function isTokenUser(username, userElement) {
        // Token detection based on class name
        // Grey users (no tokens) have class "defaultUser" on their username-label element
        // Token users (blue names) do NOT have this class

        if (!username) {
            return false;
        }

        // Search the entire chat for username-label elements with this username
        const usernameLabels = document.querySelectorAll('[data-testid="username-label"]');

        // Find the one that matches our username
        let matchingLabel = null;
        for (const label of usernameLabels) {
            const labelText = label.textContent?.trim();
            if (labelText === username) {
                matchingLabel = label;
                break;
            }
        }

        if (!matchingLabel) {
            // If we can't find it, assume they have tokens (safer default)
            return true;
        }

        // Check if this element has the "defaultUser" class
        const hasDefaultUser = matchingLabel.classList.contains('defaultUser');

        if (hasDefaultUser) {
            console.log(`[PM Assistant] ${username} - NO TOKENS`);
            return false;
        } else {
            console.log(`[PM Assistant] ${username} - HAS TOKENS`);
            return true;
        }
    }

    // NEW: Check if user is a moderator
    function isModerator(username, userElement) {
        if (userElement) {
            const color = window.getComputedStyle(userElement).color;
            // Moderators typically have red usernames in Chaturbate
            const modColors = [
                'rgb(255, 0, 0)',     // Red
                'rgb(220, 38, 38)',   // Dark red
                'rgb(239, 68, 68)',   // Light red
            ];
            if (modColors.includes(color)) {
                return true;
            }

            // Check for moderator classes
            const classes = userElement.className || '';
            if (classes.includes('moderator') ||
                classes.includes('mod') ||
                classes.includes('is-mod')) {
                return true;
            }

            // Check data attributes
            if (userElement.dataset?.isMod ||
                userElement.dataset?.moderator) {
                return true;
            }
        }

        return false;
    }

    // ==================== POPUP-BASED DETECTION ====================

    // Queue for users waiting to be detected via popup
    const detectionQueue = [];
    let isDetecting = false;

    async function detectUserViaPopup(username) {
        return new Promise((resolve) => {
            // SAFETY: Check if popup is already open (user may have opened one manually)
            const existingPopup = document.getElementById('user-context-menu');
            if (existingPopup) {
                console.log(`[PM Assistant] Popup already open, skipping detection for ${username}`);
                resolve({ gender: 'unknown', isBroadcaster: false });
                return;
            }

            // Find username element to click
            const usernameLabels = document.querySelectorAll('[data-testid="username-label"]');
            let targetLabel = null;

            for (const label of usernameLabels) {
                const usernameSpan = label.querySelector('[data-testid="username"]');
                if (usernameSpan && usernameSpan.textContent.trim() === username) {
                    targetLabel = label;
                    break;
                }
            }

            if (!targetLabel) {
                console.log(`[PM Assistant] Could not find username element for ${username}`);
                resolve({ gender: 'unknown', isBroadcaster: false });
                return;
            }

            // Click the username to open popup
            targetLabel.click();

            // Wait for popup to appear and read it
            const checkPopup = setInterval(() => {
                const popup = document.getElementById('user-context-menu');
                if (!popup) return;

                clearInterval(checkPopup);

                // Move popup OFF-SCREEN immediately (no flash, no conflicts)
                popup.style.position = 'fixed';
                popup.style.left = '-9999px';
                popup.style.top = '-9999px';

                // Extract gender from icon
                const genderIcon = popup.querySelector('[data-testid="gender-icon"]');
                let gender = 'unknown';
                if (genderIcon && genderIcon.src) {
                    if (genderIcon.src.includes('female.svg')) gender = 'female';
                    else if (genderIcon.src.includes('male.svg')) gender = 'male';
                    else if (genderIcon.src.includes('couple.svg')) gender = 'couple';
                    else if (genderIcon.src.includes('trans.svg')) gender = 'trans';
                }

                // Check for broadcaster (preview image exists)
                const previewImage = popup.querySelector('img[src*="thumb.live.mmcdn.com"]');
                const isBroadcaster = !!previewImage;

                // Wait a bit for notes section to load before closing (300ms)
                setTimeout(() => {
                    // Close popup PROPERLY with ESC key (Chaturbate's intended close method)
                    const escEvent = new KeyboardEvent('keydown', {
                        key: 'Escape',
                        code: 'Escape',
                        keyCode: 27,
                        which: 27,
                        bubbles: true,
                        cancelable: true
                    });
                    document.dispatchEvent(escEvent);

                    console.log(`[PM Assistant] Detected ${username}: ${gender}, broadcaster: ${isBroadcaster}`);
                    resolve({ gender, isBroadcaster });
                }, 300); // Wait 300ms for notes to load
            }, 50); // Check every 50ms

            // Timeout after 2 seconds
            setTimeout(() => {
                clearInterval(checkPopup);
                // Try ESC if still open
                const escEvent = new KeyboardEvent('keydown', {
                    key: 'Escape',
                    code: 'Escape',
                    keyCode: 27,
                    which: 27,
                    bubbles: true,
                    cancelable: true
                });
                document.dispatchEvent(escEvent);
                console.log(`[PM Assistant] Popup detection timeout for ${username}`);
                resolve({ gender: 'unknown', isBroadcaster: false });
            }, 2000);
        });
    }

    async function processDetectionQueue() {
        if (isDetecting || detectionQueue.length === 0) return;

        isDetecting = true;

        while (detectionQueue.length > 0) {
            // SAFETY: If user has a popup open, pause processing until it's closed
            const existingPopup = document.getElementById('user-context-menu');
            if (existingPopup) {
                console.log(`[PM Assistant] User has popup open, pausing detection queue...`);
                // Wait 1 second and check again (don't remove from queue yet!)
                await new Promise(resolve => setTimeout(resolve, 1000));
                continue; // Re-check at start of loop
            }

            // Now safe to remove from queue and process
            const { username, userElement } = detectionQueue.shift();

            // Detect via popup
            const { gender, isBroadcaster } = await detectUserViaPopup(username);

            // Remove from pending after detection
            pendingUsers.delete(username);

            // Process the user
            const genderIcon = {
                'male': '♂️',
                'female': '♀️',
                'couple': '👥',
                'trans': '⚧️',
                'unknown': '❓'
            }[gender] || '❓';

            const broadcasterIcon = isBroadcaster ? '📷' : '';

            logMessage(`👤 ${genderIcon}${broadcasterIcon} ${username} detected`, 'info', username, gender, isBroadcaster);

            // Handle the user
            handleNewUser(username, userElement, gender, isBroadcaster);

            // Longer delay between detections to let Chaturbate's popup system reset
            await new Promise(resolve => setTimeout(resolve, 500));
        }

        isDetecting = false;
    }

    // ==================== OLD NOTICE-BASED DETECTION (FALLBACK) ====================

    function detectGender(element) {
        // Get both text content and HTML
        const text = element.textContent || '';
        const html = element.outerHTML || element.innerHTML || '';
        const combined = text + ' ' + html;

        // Chaturbate uses emoticons with gender indicators in the Notice messages
        // Check for avatar emoticons first (most reliable)
        if (combined.includes('avatar_cb_female') || combined.includes('_female')) {
            return 'female';
        }
        if (combined.includes('avatar_cb_male') || combined.includes('_male')) {
            return 'male';
        }
        if (combined.includes('avatar_cb_couple') || combined.includes('_couple')) {
            return 'couple';
        }
        if (combined.includes('avatar_cb_trans') || combined.includes('_trans')) {
            return 'trans';
        }

        // Check element classes
        const classes = element.className || '';
        if (classes.includes('female')) {
            return 'female';
        }
        if (classes.includes('male')) {
            return 'male';
        }
        if (classes.includes('couple')) {
            return 'couple';
        }
        if (classes.includes('trans')) {
            return 'trans';
        }

        return 'unknown';
    }

    // NEW: Detect if user is a broadcaster/model
    function detectBroadcaster(element) {
        const text = element.textContent || '';
        const html = element.outerHTML || element.innerHTML || '';
        const combined = text + ' ' + html;

        // Check for broadcaster badge indicator
        if (combined.includes(':broadcaster_badge') || combined.includes('broadcaster_badge')) {
            return true;
        }

        // Check element classes
        const classes = element.className || '';
        if (classes.includes('broadcaster') || classes.includes('model')) {
            return true;
        }

        return false;
    }

    // ==================== CORE FUNCTIONALITY ====================

    function shouldPMUser(username) {
        // Check blacklist
        if (CONFIG.blacklist.includes(username.toLowerCase())) {
            return { should: false, reason: 'blacklisted' };
        }

        // Check if already processed in this session
        if (processedUsers.has(username)) {
            return { should: false, reason: 'already processed this session' };
        }

        // Check cooldown
        const lastPM = pmHistory[username];
        if (lastPM && (Date.now() - lastPM) < CONFIG.userCooldown) {
            const minutesLeft = Math.ceil((CONFIG.userCooldown - (Date.now() - lastPM)) / 60000);
            return { should: false, reason: `cooldown active (${minutesLeft}min remaining)` };
        }

        // Check rate limits
        if (Date.now() > hourlyResetTime) {
            pmCount = 0;
            hourlyResetTime = Date.now() + 3600000;
        }

        if (pmCount >= CONFIG.maxPMsPerHour) {
            toggleAutoMode(false);
            return { should: false, reason: 'hourly limit reached' };
        }

        const timeSinceLastPM = Date.now() - lastPMTime;
        if (timeSinceLastPM < CONFIG.minDelayBetweenPMs) {
            const secondsLeft = Math.ceil((CONFIG.minDelayBetweenPMs - timeSinceLastPM) / 1000);
            return { should: false, reason: `rate limit (${secondsLeft}s)` };
        }

        return { should: true, reason: null };
    }

    async function sendPM(username, message, isAuto = false) {
        try {
            console.log(`Attempting to send PM to ${username}`);

            const success = await sendPrivateMessage(username, message);

            if (success) {
                // Update tracking
                processedUsers.add(username);
                pmHistory[username] = Date.now();
                GM_setValue('pmHistory', pmHistory);

                lastPMTime = Date.now();
                pmCount++;

                logMessage(`✓ PM sent to ${username}`, 'success');

                // NEW: Show notification popup if auto PM
                if (isAuto) {
                    showAutoPMNotification(username);
                }

                updateStatus();
                return true;
            } else {
                logMessage(`✗ Failed to send PM to ${username}`, 'error');
                return false;
            }
        } catch (error) {
            logMessage(`✗ Error sending PM to ${username}: ${error.message}`, 'error');
            return false;
        }
    }

    // NEW: Play audible notification
    function playNotificationSound() {
        // Check if sound is enabled
        if (!CONFIG.soundEnabled) {
            console.log('[PM Assistant] Audio notification skipped (sound disabled)');
            return;
        }

        try {
            // Create audio context
            const audioContext = new (window.AudioContext || window.webkitAudioContext)();

            // Create oscillator for beep sound
            const oscillator = audioContext.createOscillator();
            const gainNode = audioContext.createGain();

            // Connect nodes
            oscillator.connect(gainNode);
            gainNode.connect(audioContext.destination);

            // Configure sound (pleasant notification beep)
            oscillator.frequency.value = 800; // Hz
            oscillator.type = 'sine'; // Smooth sine wave

            // Volume envelope (fade in/out for smooth sound)
            gainNode.gain.setValueAtTime(0, audioContext.currentTime);
            gainNode.gain.linearRampToValueAtTime(0.3, audioContext.currentTime + 0.01);
            gainNode.gain.linearRampToValueAtTime(0.3, audioContext.currentTime + 0.1);
            gainNode.gain.linearRampToValueAtTime(0, audioContext.currentTime + 0.2);

            // Play sound
            oscillator.start(audioContext.currentTime);
            oscillator.stop(audioContext.currentTime + 0.2);
        } catch (error) {
            console.error('[PM Assistant] Could not play audio notification:', error);
        }
    }

    // NEW: Show notification when auto PM is sent
    function showAutoPMNotification(username) {
        const notification = document.createElement('div');
        notification.style.cssText = `
            position: fixed;
            top: 20px;
            left: 50%;
            transform: translateX(-50%);
            background: linear-gradient(135deg, #4ade80 0%, #22c55e 100%);
            color: white;
            padding: 12px 20px;
            border-radius: 8px;
            box-shadow: 0 4px 12px rgba(0,0,0,0.3);
            z-index: 10002;
            font-family: Arial, sans-serif;
            font-size: 14px;
            font-weight: 600;
            animation: slideDown 0.3s ease;
        `;
        notification.textContent = `✓ Auto PM sent to ${username}`;

        // Add animation
        const style = document.createElement('style');
        style.textContent = `
            @keyframes slideDown {
                from {
                    opacity: 0;
                    transform: translateX(-50%) translateY(-20px);
                }
                to {
                    opacity: 1;
                    transform: translateX(-50%) translateY(0);
                }
            }
            @keyframes slideUp {
                from {
                    opacity: 1;
                    transform: translateX(-50%) translateY(0);
                }
                to {
                    opacity: 0;
                    transform: translateX(-50%) translateY(-20px);
                }
            }
        `;
        document.head.appendChild(style);
        document.body.appendChild(notification);

        // Remove after 3 seconds
        setTimeout(() => {
            notification.style.animation = 'slideUp 0.3s ease';
            setTimeout(() => notification.remove(), 300);
        }, 3000);
    }

    function handleNewUser(username, userElement, gender = 'unknown', isBroadcaster = false) {
        // Check if moderator first
        if (isModerator(username, userElement)) {
            logMessage(`🛡️ ${username} - ${t().logModerator}`, 'info', username, gender, isBroadcaster);
            return;
        }

        // Check token status - use MORE PERMISSIVE rule for broadcasters with known gender
        // If either broadcaster filter OR gender filter allows no tokens, allow PM
        let tokenRequired = false;

        if (isBroadcaster && gender !== 'unknown') {
            // Broadcaster with known gender: require tokens ONLY if BOTH filters require it
            tokenRequired = CONFIG.tokenFilters.broadcaster && CONFIG.tokenFilters[gender];
        } else if (isBroadcaster) {
            // Broadcaster with unknown gender: use broadcaster filter only
            tokenRequired = CONFIG.tokenFilters.broadcaster;
        } else if (gender !== 'unknown') {
            // Non-broadcaster with known gender: use gender filter only
            tokenRequired = CONFIG.tokenFilters[gender];
        }

        if (tokenRequired && !isTokenUser(username, userElement)) {
            const filterType = isBroadcaster && gender !== 'unknown' ? `${gender} broadcasters` :
                              isBroadcaster ? 'broadcasters' : gender;
            logMessage(`⚪ ${username} - ${t().logNoTokens} ${filterType})`, 'info', username, gender, isBroadcaster);
            return;
        }

        // Check gender filter (only filter if gender is known)
        if (gender !== 'unknown' && !CONFIG.genderFilters[gender]) {
            logMessage(`⚧ ${username} - ${t().logGenderFiltered} (${gender})`, 'info', username, gender, isBroadcaster);
            return;
        }

        // Check broadcaster filter
        if (isBroadcaster && !CONFIG.sendToBroadcasters) {
            logMessage(`📷 ${username} - ${t().logBroadcasterFiltered}`, 'info', username, gender, isBroadcaster);
            return;
        }

        // Check if should PM
        const pmCheck = shouldPMUser(username);
        if (!pmCheck.should) {
            logMessage(`⏭️ ${username} - ${pmCheck.reason}`, 'info', username, gender, isBroadcaster);
            return;
        }

        // Check exclusion list (applies to both auto and manual mode)
        if (CONFIG.exclusionList.includes(username)) {
            logMessage(`🚫 ${username} - ${t().logExclusionList}`, 'info', username, gender, isBroadcaster);
            return;
        }

        // Auto mode - send automatically (with 5 second delay)
        if (CONFIG.autoMode) {
            logMessage(`⏱️ ${username} - ${t().logPMScheduled} (5s delay)`, 'info', username, gender, isBroadcaster);
            setTimeout(() => {
                sendPM(username, CONFIG.welcomeMessage, true);
            }, 5000); // 5 second delay
        } else {
            // Manual mode - show button
            showQuickPMButton(username, gender, isBroadcaster);
        }
    }

    // ==================== UI FUNCTIONS ====================

    function createControlPanel() {
        controlPanel = document.createElement('div');
        controlPanel.id = 'pm-assistant-panel';
        controlPanel.className = 'minimized'; // Start minimized
        controlPanel.innerHTML = `
            <style>
                #pm-assistant-panel {
                    position: fixed;
                    top: 10px;
                    right: 10px;
                    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
                    border-radius: 10px;
                    padding: 8px 12px;
                    box-shadow: 0 8px 32px rgba(0,0,0,0.3);
                    z-index: 10000;
                    font-family: Arial, sans-serif;
                    color: white;
                    min-width: 250px;
                    max-width: 350px;
                    display: flex;
                    flex-direction: column;
                    overflow: hidden;
                }
                .pm-content {
                    position: relative;
                    display: flex;
                    flex-direction: column;
                    flex: 1;
                    min-height: 0;
                }
                #pm-assistant-panel h3 {
                    margin: 0 0 12px 0;
                    font-size: 16px;
                    display: flex;
                    justify-content: space-between;
                    align-items: center;
                    gap: 8px;
                    user-select: none;
                    padding: 4px 0;
                }
                #pm-assistant-panel h3 > div {
                    cursor: move;
                }
                #pm-assistant-panel h3 button {
                    cursor: pointer;
                    margin: 0;
                    white-space: nowrap;
                }
                #pm-assistant-panel button {
                    background: rgba(255,255,255,0.2);
                    border: 1px solid rgba(255,255,255,0.3);
                    color: white;
                    padding: 8px 10px;
                    border-radius: 6px;
                    cursor: pointer;
                    font-size: 13px;
                    transition: all 0.3s;
                    margin: 4px 0;
                }
                #pm-assistant-panel .pm-content > button {
                    width: 100%; /* Only buttons directly in content take full width */
                }
                #pm-assistant-panel button:hover {
                    background: rgba(255,255,255,0.3);
                    transform: translateY(-2px);
                }
                #pm-assistant-panel button.active {
                    background: #4ade80;
                    border-color: #22c55e;
                }
                #pm-assistant-panel button.danger {
                    background: #ef4444;
                }
                .pm-button-row {
                    display: grid;
                    grid-template-columns: 1fr 1fr 1fr;
                    gap: 4px;
                    margin: 4px 0;
                }
                .pm-button-row button {
                    padding: 6px 8px;
                    font-size: 11px;
                    white-space: nowrap;
                }
                .pm-settings-header {
                    display: flex;
                    justify-content: space-between;
                    align-items: center;
                    padding: 6px 8px;
                    background: rgba(0,0,0,0.2);
                    border-radius: 6px;
                    margin-bottom: 8px;
                    cursor: pointer;
                    user-select: none;
                    font-size: 12px;
                    font-weight: bold;
                }
                .pm-settings-header:hover {
                    background: rgba(0,0,0,0.3);
                }
                .pm-settings-toggle {
                    font-size: 16px;
                    transition: transform 0.3s;
                    display: inline-block;
                }
                .pm-settings-section {
                    max-height: 500px;
                    overflow: hidden;
                    transition: max-height 0.3s ease;
                }
                .pm-settings-section.collapsed {
                    max-height: 0;
                }
                .pm-resize-handle {
                    position: absolute;
                    bottom: 0;
                    right: 0;
                    width: 20px;
                    height: 20px;
                    cursor: nwse-resize;
                    user-select: none;
                    font-size: 16px;
                    line-height: 20px;
                    text-align: center;
                    color: rgba(255,255,255,0.5);
                }
                .pm-resize-handle:hover {
                    color: rgba(255,255,255,0.8);
                }

                /* Floating PM button with progress bar */
                .pm-floating-button {
                    position: relative;
                    overflow: hidden;
                }
                .pm-floating-button::after {
                    content: '';
                    position: absolute;
                    bottom: 0;
                    left: 0;
                    height: 3px;
                    background: rgba(255, 255, 255, 0.5);
                    animation: progressBar linear forwards;
                    animation-play-state: running;
                }
                .pm-floating-button.paused::after {
                    animation-play-state: paused;
                }
                @keyframes progressBar {
                    from {
                        width: 100%;
                    }
                    to {
                        width: 0%;
                    }
                }
                .pm-status {
                    background: rgba(0,0,0,0.2);
                    padding: 10px;
                    border-radius: 6px;
                    margin: 10px 0;
                    font-size: 12px;
                }
                .pm-status-table {
                    display: grid;
                    grid-template-columns: 1fr 1fr;
                    gap: 6px 12px;
                    width: 100%;
                }
                .pm-status-cell {
                    text-align: left;
                    white-space: nowrap;
                }
                .pm-status-label {
                    font-weight: bold;
                    opacity: 0.8;
                    display: inline;
                }
                .pm-status-value {
                    display: inline;
                    margin-left: 4px;
                }
                .pm-log {
                    background: rgba(0,0,0,0.2);
                    padding: 8px;
                    border-radius: 6px;
                    margin: 10px 0;
                    overflow-y: auto;
                    font-size: 11px;
                    font-family: monospace;
                    line-height: 1.2; /* Compact line spacing */
                    flex: 1;
                    min-height: 100px;
                }
                .pm-log-entry {
                    margin: 2px 0; /* Reduced from 4px */
                    padding: 3px; /* Reduced from 4px */
                    border-radius: 3px;
                    display: flex;
                    justify-content: space-between;
                    align-items: center;
                    gap: 8px;
                }
                .pm-log-entry-text {
                    flex: 1;
                    min-width: 0;
                    overflow: hidden;
                    text-overflow: ellipsis;
                    line-height: 1.2; /* Compact text */
                }
                .pm-log-entry-btn {
                    background: rgba(255,255,255,0.3);
                    border: 1px solid rgba(255,255,255,0.4);
                    color: white;
                    padding: 2px 6px;
                    border-radius: 3px;
                    cursor: pointer;
                    font-size: 9px;
                    white-space: nowrap;
                    transition: all 0.2s;
                    flex-shrink: 0;
                    flex-grow: 0;
                    width: fit-content;
                    max-width: 40px;
                    display: inline-flex;
                    align-items: center;
                    justify-content: center;
                    text-align: center;
                }
                .pm-log-entry-btn:hover {
                    background: rgba(255,255,255,0.5);
                }
                .pm-log-success { background: rgba(74, 222, 128, 0.3); }
                .pm-log-error { background: rgba(239, 68, 68, 0.3); }
                .pm-log-warning { background: rgba(251, 191, 36, 0.3); }
                .pm-log-info { background: rgba(96, 165, 250, 0.3); }
                .pm-toggle {
                    display: flex;
                    align-items: center;
                    gap: 10px;
                    margin: 8px 0;
                }
                .pm-top-row {
                    display: flex;
                    gap: 8px;
                    align-items: center;
                    margin-bottom: 8px;
                }
                .pm-top-row button {
                    flex: 0 0 auto;
                    width: auto;
                    min-width: 70px;
                    padding: 6px 12px;
                }
                .pm-top-row .pm-toggle {
                    flex: 1;
                    justify-content: flex-start;
                    margin: 0;
                }
                .pm-gender-grid {
                    display: grid;
                    grid-template-columns: 1fr 1fr;
                    gap: 2px; /* EDIT THIS: Gap between grid cells (default: 2px) */
                }
                .pm-gender-grid.with-broadcaster {
                    grid-template-columns: 1fr 1fr 1fr;
                }
                .pm-gender-toggle {
                    display: flex;
                    align-items: center;
                    gap: 4px; /* EDIT THIS: Space between checkbox and label (default: 4px) */
                    margin: 0; /* EDIT THIS: Margin around each toggle (default: 0) */
                    padding: 1px 0; /* EDIT THIS: Padding for each row (default: 1px 0) */
                }

                /* Token diamond icon */
                .token-diamond {
                    cursor: pointer;
                    font-size: 14px;
                    margin-left: 4px;
                    opacity: 0.9;
                    transition: opacity 0.2s, transform 0.1s;
                    user-select: none;
                }

                .token-diamond:hover {
                    opacity: 1;
                    transform: scale(1.1);
                }

                .token-diamond.hidden {
                    opacity: 0.3;
                }

                .pm-minimize {
                    cursor: pointer;
                    font-size: 20px;
                    transition: transform 0.3s ease;
                    display: inline-block;
                }
                #pm-assistant-panel.minimized {
                    padding: 8px 12px;
                    min-width: auto;
                    width: auto !important;
                    height: auto !important;
                    max-width: none !important;
                }
                #pm-assistant-panel.minimized .pm-content {
                    display: none;
                }
                #pm-assistant-panel.minimized h3 {
                    margin: 0;
                    font-size: 16px;
                }

                /* NEW: Manual exclusion input */
                .pm-exclude-input {
                    display: flex;
                    gap: 4px;
                    margin: 6px 0;
                }
                .pm-exclude-input input {
                    flex: 1;
                    padding: 6px;
                    border: 1px solid rgba(255,255,255,0.3);
                    background: rgba(0,0,0,0.2);
                    color: white;
                    border-radius: 4px;
                    font-size: 11px;
                }
                .pm-exclude-input input::placeholder {
                    color: rgba(255,255,255,0.5);
                }
                .pm-exclude-input button {
                    padding: 6px 10px;
                    margin: 0;
                    font-size: 11px;
                }

                /* NEW: Exclusion modal */
                .pm-modal {
                    display: none;
                    position: fixed;
                    z-index: 10001;
                    left: 0;
                    top: 0;
                    width: 100%;
                    height: 100%;
                    background-color: rgba(0,0,0,0.6);
                }
                .pm-modal-content {
                    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
                    margin: 5% auto;
                    padding: 20px;
                    border-radius: 10px;
                    max-width: 400px;
                    box-shadow: 0 10px 40px rgba(0,0,0,0.3);
                    color: white;
                }
                .pm-modal-header {
                    display: flex;
                    justify-content: space-between;
                    align-items: center;
                    margin-bottom: 15px;
                }
                .pm-modal-title {
                    font-size: 16px;
                    font-weight: bold;
                }
                .pm-modal-close {
                    background: none;
                    border: none;
                    color: white;
                    font-size: 24px;
                    cursor: pointer;
                    padding: 0;
                    width: 30px;
                    height: 30px;
                    line-height: 30px;
                }
                .pm-modal-close:hover {
                    opacity: 0.7;
                    transform: none;
                }
                .pm-modal-list {
                    max-height: 400px;
                    overflow-y: auto;
                    background: rgba(0,0,0,0.2);
                    padding: 10px;
                    border-radius: 6px;
                }
                .pm-modal-item {
                    display: flex;
                    justify-content: space-between;
                    align-items: center;
                    padding: 6px 8px;
                    margin: 4px 0;
                    background: rgba(255,255,255,0.1);
                    border-radius: 4px;
                }
                .pm-modal-item button {
                    padding: 4px 8px;
                    margin: 0;
                    font-size: 10px;
                    background: rgba(255,255,255,0.2);
                }
                .pm-modal-item button:hover {
                    background: rgba(255,255,255,0.3);
                }
                .pm-modal-empty {
                    padding: 20px;
                    text-align: center;
                    opacity: 0.7;
                    font-style: italic;
                }
            </style>
            <h3>
                <div style="display: flex; align-items: center; gap: 8px;">
                    <span id="pm-title-text">💬 PM Assistant</span>
                    <span class="pm-minimize" id="pm-minimize-btn">V</span>
                </div>
                <button id="pm-auto-toggle" title="${t().autoPMOff}" style="margin: 0; padding: 4px 12px; font-size: 12px;">${CONFIG.autoMode ? t().autoPMOn : t().autoPMOff}</button>
            </h3>
            <div class="pm-content">
                <div class="pm-settings-header" id="pm-settings-header">
                    <span>⚙️ Settings</span>
                    <span class="pm-settings-toggle" id="pm-settings-toggle">V</span>
                </div>
                <div class="pm-settings-section" id="pm-settings-section">
                    <div class="pm-top-row">
                        <div class="pm-toggle" style="margin: 0;">
                            <input type="checkbox" id="pm-sound-enabled" ${CONFIG.soundEnabled ? 'checked' : ''} title="Enable or disable sound notifications when PM popups appear">
                            <label for="pm-sound-enabled" style="font-size: 11px;">${t().sound}</label>
                        </div>
                        <button id="pm-lang-toggle" title="Switch language / Cambiar idioma" style="font-size: 13px; padding: 2px 6px;">${t().langLabel} ${CONFIG.language.toUpperCase()}</button>
                    </div>
                    <div style="margin: 6px 0; padding: 6px; background: rgba(0,0,0,0.2); border-radius: 6px;">
                        <div style="font-weight: bold; margin-bottom: 4px; font-size: 11px;" id="pm-gender-section-label">${t().genderSection}</div>
                        <div class="pm-gender-grid with-broadcaster">
                            <div class="pm-gender-toggle">
                                <input type="checkbox" id="pm-gender-male" ${CONFIG.genderFilters.male ? 'checked' : ''} title="${t().genderTip('male')}">
                                <label for="pm-gender-male" style="font-size: 11px;">${t().male}</label>
                                <span class="token-diamond ${CONFIG.tokenFilters.male ? '' : 'hidden'}" id="token-male" title="${t().tokenTip('males')}">💎</span>
                            </div>
                            <div class="pm-gender-toggle">
                                <input type="checkbox" id="pm-gender-female" ${CONFIG.genderFilters.female ? 'checked' : ''} title="${t().genderTip('female')}">
                                <label for="pm-gender-female" style="font-size: 11px;">${t().female}</label>
                                <span class="token-diamond ${CONFIG.tokenFilters.female ? '' : 'hidden'}" id="token-female" title="${t().tokenTip('females')}">💎</span>
                            </div>
                            <div class="pm-gender-toggle">
                                <input type="checkbox" id="pm-broadcaster" ${CONFIG.sendToBroadcasters ? 'checked' : ''} title="${t().genderTip('broadcaster/model')}">
                                <label for="pm-broadcaster" style="font-size: 11px;">${t().models}</label>
                                <span class="token-diamond ${CONFIG.tokenFilters.broadcaster ? '' : 'hidden'}" id="token-broadcaster" title="${t().tokenTip('broadcasters')}">💎</span>
                            </div>
                            <div class="pm-gender-toggle">
                                <input type="checkbox" id="pm-gender-trans" ${CONFIG.genderFilters.trans ? 'checked' : ''} title="${t().genderTip('trans')}">
                                <label for="pm-gender-trans" style="font-size: 11px;">${t().trans}</label>
                                <span class="token-diamond ${CONFIG.tokenFilters.trans ? '' : 'hidden'}" id="token-trans" title="${t().tokenTip('trans')}">💎</span>
                            </div>
                            <div class="pm-gender-toggle">
                                <input type="checkbox" id="pm-gender-couple" ${CONFIG.genderFilters.couple ? 'checked' : ''} title="${t().genderTip('couple')}">
                                <label for="pm-gender-couple" style="font-size: 11px;">${t().couple}</label>
                                <span class="token-diamond ${CONFIG.tokenFilters.couple ? '' : 'hidden'}" id="token-couple" title="${t().tokenTip('couples')}">💎</span>
                            </div>
                        </div>
                    </div>
                    <div class="pm-button-row">
                        <button id="pm-clear-log" title="${t().clearLogTip}">${t().clearLog}</button>
                        <button id="pm-settings" title="${t().messageTip}">${t().message}</button>
                        <button id="pm-exclusion-list" title="${t().listTip}">${t().list(CONFIG.exclusionList.length)}</button>
                    </div>
                </div>
                <div class="pm-status" id="pm-status">
                    <div class="pm-status-table">
                        <div class="pm-status-cell">
                            <span class="pm-status-label">Mode:</span>
                            <span class="pm-status-value" id="pm-status-mode">MANUAL</span>
                        </div>
                        <div class="pm-status-cell">
                            <span class="pm-status-label">Sent:</span>
                            <span class="pm-status-value" id="pm-status-sent">0/30</span>
                        </div>
                        <div class="pm-status-cell">
                            <span class="pm-status-label">Remaining:</span>
                            <span class="pm-status-value" id="pm-status-remaining">30</span>
                        </div>
                        <div class="pm-status-cell">
                            <span class="pm-status-label">Reset:</span>
                            <span class="pm-status-value" id="pm-status-reset">60min</span>
                        </div>
                    </div>
                </div>
                <div class="pm-log" id="pm-log">
                    <div class="pm-log-entry pm-log-info">
                        <div class="pm-log-entry-text">System ready</div>
                    </div>
                </div>
                <div class="pm-resize-handle" id="pm-resize-handle">⋰</div>
            </div>
        `;

        document.body.appendChild(controlPanel);

        // NEW: Create exclusion modal
        const modal = document.createElement('div');
        modal.id = 'pm-exclusion-modal';
        modal.className = 'pm-modal';
        modal.innerHTML = `
            <div class="pm-modal-content">
                <div class="pm-modal-header">
                    <div class="pm-modal-title" id="pm-modal-title">🚫 ${t().excludeTitle}</div>
                    <button class="pm-modal-close" id="pm-modal-close">×</button>
                </div>
                <div style="margin-bottom: 12px;">
                    <div style="font-weight: bold; margin-bottom: 6px; font-size: 12px;" id="pm-modal-add-label">${t().addExclude}</div>
                    <div class="pm-exclude-input">
                        <input type="text" id="pm-exclude-username" placeholder="${t().excludePlaceholder}" title="${t().inputTip}">
                        <button id="pm-add-exclude" title="${t().addBtnTip}">${t().addBtn}</button>
                    </div>
                </div>
                <div class="pm-modal-list" id="pm-modal-list"></div>
            </div>
        `;
        document.body.appendChild(modal);

        // Event listeners
        document.getElementById('pm-auto-toggle').addEventListener('click', () => {
            toggleAutoMode(!CONFIG.autoMode);
        });

        document.getElementById('pm-sound-enabled').addEventListener('change', (e) => {
            CONFIG.soundEnabled = e.target.checked;
            GM_setValue('soundEnabled', CONFIG.soundEnabled);
            logMessage(`${t().sound}: ${CONFIG.soundEnabled ? 'ON' : 'OFF'}`, 'info');
        });

        document.getElementById('pm-lang-toggle').addEventListener('click', () => {
            const langs = Object.keys(LANG);
            const currentIndex = langs.indexOf(CONFIG.language);
            CONFIG.language = langs[(currentIndex + 1) % langs.length];
            GM_setValue('language', CONFIG.language);
            applyLanguage();
        });

        document.getElementById('pm-clear-log').addEventListener('click', () => {
            if (confirm(t().confirmClearLog)) {
                logDisplay.innerHTML = '';
            }
        });
        ['male', 'female', 'couple', 'trans'].forEach(gender => {
            document.getElementById(`pm-gender-${gender}`).addEventListener('change', (e) => {
                CONFIG.genderFilters[gender] = e.target.checked;
                GM_setValue(`genderFilter_${gender}`, e.target.checked);
                logMessage(`${gender} filter: ${e.target.checked ? 'ON' : 'OFF'}`, 'info');
            });
        });

        // Broadcaster filter checkbox
        document.getElementById('pm-broadcaster').addEventListener('change', (e) => {
            CONFIG.sendToBroadcasters = e.target.checked;
            GM_setValue('sendToBroadcasters', e.target.checked);
            logMessage(`Broadcaster filter: ${e.target.checked ? 'ON' : 'OFF'}`, 'info');
        });

        // Token diamond click handlers
        ['male', 'female', 'couple', 'trans', 'broadcaster'].forEach(type => {
            const diamond = document.getElementById(`token-${type}`);
            if (diamond) {
                diamond.addEventListener('click', () => {
                    CONFIG.tokenFilters[type] = !CONFIG.tokenFilters[type];
                    GM_setValue(`tokenFilter_${type}`, CONFIG.tokenFilters[type]);

                    // Toggle visual state
                    if (CONFIG.tokenFilters[type]) {
                        diamond.classList.remove('hidden');
                    } else {
                        diamond.classList.add('hidden');
                    }

                    const typeName = type === 'broadcaster' ? 'broadcasters' : `${type}s`;
                    logMessage(`Token requirement for ${typeName}: ${CONFIG.tokenFilters[type] ? 'ON' : 'OFF'}`, 'info');
                });
            }
        });

        document.getElementById('pm-settings').addEventListener('click', showSettings);

        // NEW: Manual exclusion input - now in modal, set up in showExclusionModal()

        document.getElementById('pm-exclusion-list').addEventListener('click', () => {
            showExclusionModal();
        });

        // NEW: Modal close handlers
        document.getElementById('pm-modal-close').addEventListener('click', closeExclusionModal);
        modal.addEventListener('click', (e) => {
            if (e.target === modal) {
                closeExclusionModal();
            }
        });

        // Settings toggle
        document.getElementById('pm-settings-header').addEventListener('click', () => {
            const section = document.getElementById('pm-settings-section');
            const toggle = document.getElementById('pm-settings-toggle');

            if (section.classList.contains('collapsed')) {
                section.classList.remove('collapsed');
                toggle.style.transform = 'rotate(180deg)'; // Flip V upside down when expanded
            } else {
                section.classList.add('collapsed');
                toggle.style.transform = 'rotate(0deg)'; // V pointing down when collapsed
            }
        });

        // Minimize/maximize button
        document.getElementById('pm-minimize-btn').addEventListener('click', () => {
            const isMinimized = controlPanel.classList.contains('minimized');
            const btn = document.getElementById('pm-minimize-btn');
            const titleText = document.getElementById('pm-title-text');
            const autoToggleBtn = document.getElementById('pm-auto-toggle');

            if (isMinimized) {
                // Maximizing
                controlPanel.classList.remove('minimized');
                btn.style.transform = 'rotate(180deg)'; // Upside down when maximized
                titleText.textContent = '💬 PM Assistant';

                // Show Auto PM button
                if (autoToggleBtn) autoToggleBtn.style.display = 'block';

                // Restore custom size if it was set
                const savedSize = GM_getValue('panelSize', null);
                if (savedSize) {
                    controlPanel.style.width = savedSize.width;
                    controlPanel.style.height = savedSize.height;
                    controlPanel.style.maxWidth = 'none';
                }
            } else {
                // Minimizing
                controlPanel.classList.add('minimized');
                btn.style.transform = 'rotate(0deg)'; // Normal V when minimized

                // Hide Auto PM button
                if (autoToggleBtn) autoToggleBtn.style.display = 'none';

                // Remove inline size styles so minimized CSS can work
                controlPanel.style.width = '';
                controlPanel.style.height = '';
                controlPanel.style.maxWidth = '';

                // Show auto mode status in title
                const statusIcon = CONFIG.autoMode ? '🟢' : '⚪';
                titleText.textContent = `${statusIcon} PM Assistant`;
            }
        });

        statusDisplay = document.getElementById('pm-status');
        logDisplay = document.getElementById('pm-log');

        // Restore saved position on load
        const savedPos = GM_getValue('panelPosition', null);
        if (savedPos) {
            controlPanel.style.top = savedPos.top;
            controlPanel.style.left = savedPos.left;
            controlPanel.style.right = 'auto';
            controlPanel.style.bottom = 'auto';
        }

        // Update minimized title with auto mode status
        const statusIcon = CONFIG.autoMode ? '🟢' : '⚪';
        document.getElementById('pm-title-text').textContent = `${statusIcon} PM Assistant`;

        // Make panel draggable
        makeDraggable(controlPanel);

        // Set initial minimize button rotation (upside down when maximized)
        const initialMinBtn = document.getElementById('pm-minimize-btn');
        if (initialMinBtn) {
            initialMinBtn.style.transform = 'rotate(180deg)';
        }

        // Make panel resizable
        makeResizable(controlPanel);

        // Restore saved size
        const savedSize = GM_getValue('panelSize', null);
        if (savedSize) {
            controlPanel.style.width = savedSize.width;
            controlPanel.style.height = savedSize.height;
            controlPanel.style.maxWidth = 'none';
        }
    }

    // NEW: Add user manually to exclusion list
    function addManualExclusion() {
        const input = document.getElementById('pm-exclude-username');
        const username = input.value.trim();

        if (!username) {
            logMessage('Please enter a username', 'warning');
            return;
        }

        if (CONFIG.exclusionList.includes(username)) {
            logMessage(`${username} already excluded`, 'warning');
            return;
        }

        CONFIG.exclusionList.push(username);
        GM_setValue('exclusionList', CONFIG.exclusionList);
        input.value = '';
        updateExclusionButton();
        logMessage(`🚫 ${username} added to exclusion list`, 'success');

        // Refresh the modal list
        showExclusionModal();
    }

    // NEW: Show exclusion modal with sorted list
    function showExclusionModal() {
        const modal = document.getElementById('pm-exclusion-modal');
        const list = document.getElementById('pm-modal-list');

        // Update modal text with current language
        const L = t();
        const titleEl = document.getElementById('pm-modal-title');
        if (titleEl) titleEl.textContent = `🚫 ${L.excludeTitle}`;
        const addLabel = document.getElementById('pm-modal-add-label');
        if (addLabel) addLabel.textContent = L.addExclude;
        const input = document.getElementById('pm-exclude-username');
        if (input) input.placeholder = L.excludePlaceholder;
        const addBtn = document.getElementById('pm-add-exclude');
        if (addBtn) addBtn.textContent = L.addBtn;

        if (CONFIG.exclusionList.length === 0) {
            list.innerHTML = `<div class="pm-modal-empty">${L.noExcluded}</div>`;
        } else {
            // Sort alphabetically
            const sorted = [...CONFIG.exclusionList].sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));

            // Clear the list first
            list.innerHTML = '';

            // Create elements with proper event listeners instead of inline onclick
            sorted.forEach(username => {
                const item = document.createElement('div');
                item.className = 'pm-modal-item';

                const nameSpan = document.createElement('span');
                nameSpan.textContent = username;

                const removeBtn = document.createElement('button');
                removeBtn.textContent = L.removeBtn;
                removeBtn.onclick = () => {
                    removeFromExclusionList(username);
                    showExclusionModal(); // Refresh the modal
                };

                item.appendChild(nameSpan);
                item.appendChild(removeBtn);
                list.appendChild(item);
            });
        }

        modal.style.display = 'block';

        // Set up event listeners for the input
        const freshAddBtn = document.getElementById('pm-add-exclude');
        const freshInput = document.getElementById('pm-exclude-username');

        if (freshAddBtn && freshInput) {
            // Remove old listeners if any
            freshAddBtn.replaceWith(freshAddBtn.cloneNode(true));
            freshInput.replaceWith(freshInput.cloneNode(true));

            // Get fresh references
            const newAddBtn = document.getElementById('pm-add-exclude');
            const newInput = document.getElementById('pm-exclude-username');

            newAddBtn.addEventListener('click', addManualExclusion);
            newInput.addEventListener('keypress', (e) => {
                if (e.key === 'Enter') addManualExclusion();
            });
        }
    }

    // NEW: Close exclusion modal
    function closeExclusionModal() {
        document.getElementById('pm-exclusion-modal').style.display = 'none';
    }

    function applyLanguage() {
        const L = t();
        const set = (id, val) => { const el = document.getElementById(id); if (el) el.textContent = val; };
        const setTitle = (id, val) => { const el = document.getElementById(id); if (el) el.title = val; };
        const setAttr = (id, attr, val) => { const el = document.getElementById(id); if (el) el[attr] = val; };

        // Title
        const titleEl = document.querySelector('#pm-assistant-panel .pm-title span');
        if (titleEl) titleEl.textContent = L.title;

        // Top row buttons
        const autoBtn = document.getElementById('pm-auto-toggle');
        if (autoBtn) autoBtn.textContent = CONFIG.autoMode ? L.autoPMOn : L.autoPMOff;

        const langBtn = document.getElementById('pm-lang-toggle');
        if (langBtn) langBtn.textContent = `${L.langLabel} ${CONFIG.language.toUpperCase()}`;

        // Sound label
        const soundLabel = document.querySelector('label[for="pm-sound-enabled"]');
        if (soundLabel) soundLabel.textContent = L.sound;

        // Gender section label
        set('pm-gender-section-label', L.genderSection);

        // Gender labels
        const labelMap = {
            'pm-gender-male':   L.male,
            'pm-gender-female': L.female,
            'pm-gender-couple': L.couple,
            'pm-gender-trans':  L.trans,
            'pm-broadcaster':   L.models,
        };
        Object.entries(labelMap).forEach(([id, text]) => {
            const el = document.querySelector(`label[for="${id}"]`);
            if (el) el.textContent = text;
        });

        // Bottom buttons
        set('pm-clear-log', L.clearLog);
        setTitle('pm-clear-log', L.clearLogTip);
        set('pm-settings', L.message);
        setTitle('pm-settings', L.messageTip);

        // Exclusion list button (preserves count)
        const listBtn = document.getElementById('pm-exclusion-list');
        if (listBtn) listBtn.textContent = L.list(CONFIG.exclusionList.length);
        setTitle('pm-exclusion-list', L.listTip);

        // Status labels
        const statusCells = document.querySelectorAll('.pm-status-label');
        if (statusCells.length >= 4) {
            statusCells[0].textContent = L.modeLabel;
            statusCells[1].textContent = L.sentLabel;
            statusCells[2].textContent = L.remainingLabel;
            statusCells[3].textContent = L.cooldownLabel;
        }

        // Status mode value
        const modeVal = document.getElementById('pm-status-mode');
        if (modeVal) modeVal.textContent = CONFIG.autoMode ? L.auto : L.manual;
    }

    function toggleAutoMode(enabled) {
        CONFIG.autoMode = enabled;
        GM_setValue('autoMode', enabled);

        const button = document.getElementById('pm-auto-toggle');
        if (enabled) {
            button.textContent = t().autoPMOn;
            button.classList.add('active');
            logMessage(t().logAutoEnabled, 'success');
        } else {
            button.textContent = t().autoPMOff;
            button.classList.remove('active');
            logMessage(t().logAutoDisabled, 'info');
        }

        // Update minimized title if panel is minimized
        const panel = document.getElementById('pm-assistant-panel');
        if (panel && panel.classList.contains('minimized')) {
            const statusIcon = enabled ? '🟢' : '⚪';
            document.getElementById('pm-title-text').textContent = `${statusIcon} ${t().title}`;
        }

        updateStatus();
    }

    function makeDraggable(element) {
        let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;

        // Find the h3 title div (contains title and minimize button)
        const h3 = element.querySelector('h3');
        const titleDiv = h3 ? h3.querySelector('div') : null;
        if (!titleDiv) return;

        titleDiv.onmousedown = dragMouseDown;

        function dragMouseDown(e) {
            // Don't drag if clicking on the minimize button
            if (e.target.id === 'pm-minimize-btn' || e.target.className.includes('pm-minimize')) {
                return;
            }

            e.preventDefault();
            pos3 = e.clientX;
            pos4 = e.clientY;
            document.onmouseup = closeDragElement;
            document.onmousemove = elementDrag;
        }

        function elementDrag(e) {
            e.preventDefault();
            pos1 = pos3 - e.clientX;
            pos2 = pos4 - e.clientY;
            pos3 = e.clientX;
            pos4 = e.clientY;

            // Calculate new position
            let newTop = element.offsetTop - pos2;
            let newLeft = element.offsetLeft - pos1;

            // Keep within viewport bounds
            const maxX = window.innerWidth - element.offsetWidth;
            const maxY = window.innerHeight - element.offsetHeight;

            newTop = Math.max(0, Math.min(newTop, maxY));
            newLeft = Math.max(0, Math.min(newLeft, maxX));

            element.style.top = newTop + "px";
            element.style.left = newLeft + "px";
            element.style.right = "auto";
            element.style.bottom = "auto";
        }

        function closeDragElement() {
            document.onmouseup = null;
            document.onmousemove = null;

            // Save position after dragging
            GM_setValue('panelPosition', {
                top: element.style.top,
                left: element.style.left
            });
            console.log('[PM Assistant] Position saved:', element.style.top, element.style.left);
        }
    }

    function makeResizable(element) {
        const resizeHandle = element.querySelector('#pm-resize-handle');
        if (!resizeHandle) return;

        let isResizing = false;
        let startX, startY, startWidth, startHeight;

        resizeHandle.addEventListener('mousedown', (e) => {
            isResizing = true;
            startX = e.clientX;
            startY = e.clientY;
            startWidth = element.offsetWidth;
            startHeight = element.offsetHeight;

            e.preventDefault();
            e.stopPropagation();

            document.addEventListener('mousemove', resize);
            document.addEventListener('mouseup', stopResize);
        });

        function resize(e) {
            if (!isResizing) return;

            const width = startWidth + (e.clientX - startX);
            const height = startHeight + (e.clientY - startY);

            // Min and max dimensions
            const minWidth = 280;
            const maxWidth = 600;
            const minHeight = 200;
            const maxHeight = 800;

            element.style.width = Math.min(Math.max(width, minWidth), maxWidth) + 'px';
            element.style.height = Math.min(Math.max(height, minHeight), maxHeight) + 'px';
            element.style.maxWidth = 'none';
        }

        function stopResize() {
            isResizing = false;
            document.removeEventListener('mousemove', resize);
            document.removeEventListener('mouseup', stopResize);

            // Save size
            GM_setValue('panelSize', {
                width: element.style.width,
                height: element.style.height
            });
        }
    }

    function updateExclusionButton() {
        const btn = document.getElementById('pm-exclusion-list');
        if (btn) btn.textContent = t().list(CONFIG.exclusionList.length);
    }

    function updateStatus() {
        const remaining = CONFIG.maxPMsPerHour - pmCount;
        const nextResetMinutes = Math.ceil((hourlyResetTime - Date.now()) / 60000);

        document.getElementById('pm-status-mode').textContent = CONFIG.autoMode ? t().auto : t().manual;
        document.getElementById('pm-status-sent').textContent = `${pmCount}/${CONFIG.maxPMsPerHour}`;
        document.getElementById('pm-status-remaining').textContent = remaining;
        document.getElementById('pm-status-reset').textContent = `${nextResetMinutes}min`;
    }

    function logMessage(message, type = 'info', username = null, gender = 'unknown', isBroadcaster = false) {
        const entry = document.createElement('div');
        entry.className = `pm-log-entry pm-log-${type}`;

        const textDiv = document.createElement('div');
        textDiv.className = 'pm-log-entry-text';

        // Add broadcaster icon to message if applicable
        let displayMessage = message;
        if (isBroadcaster && !message.includes('📷')) {
            // Find the gender icon and add broadcaster icon after it
            const genderIcons = ['♂️', '♀️', '⚧️', '👥', '❓'];
            for (const icon of genderIcons) {
                if (message.includes(icon)) {
                    displayMessage = message.replace(icon, `${icon}📷`);
                    break;
                }
            }
        }

        textDiv.textContent = `[${new Date().toLocaleTimeString()}] ${displayMessage}`;
        entry.appendChild(textDiv);

        // NEW: Add both PM and Exclude buttons if username provided
        if (username && !message.includes('✓ PM sent')) {
            const btnContainer = document.createElement('div');
            btnContainer.style.display = 'flex';
            btnContainer.style.gap = '4px';

            // PM button
            const pmBtn = document.createElement('button');
            pmBtn.className = 'pm-log-entry-btn';
            pmBtn.textContent = t().pmBtn;
            pmBtn.title = t().pmBtnTip;
            pmBtn.onclick = (e) => {
                e.stopPropagation();
                sendPM(username, CONFIG.welcomeMessage);
            };
            btnContainer.appendChild(pmBtn);

            // Exclude/Unexclude button (toggles based on current status)
            const excludeBtn = document.createElement('button');
            excludeBtn.className = 'pm-log-entry-btn';

            const isExcluded = CONFIG.exclusionList.includes(username);
            if (isExcluded) {
                excludeBtn.textContent = '✅';
                excludeBtn.title = 'Remove from exclusion list';
                excludeBtn.style.background = 'linear-gradient(135deg, #22c55e 0%, #16a34a 100%)';
                excludeBtn.onclick = (e) => {
                    e.stopPropagation();
                    removeFromExclusionList(username);
                };
            } else {
                excludeBtn.textContent = t().excludeBtn;
                excludeBtn.title = t().excludeBtnTip;
                excludeBtn.onclick = (e) => {
                    e.stopPropagation();
                    addToExclusionList(username);
                };
            }
            btnContainer.appendChild(excludeBtn);

            entry.appendChild(btnContainer);
        }

        logDisplay.insertBefore(entry, logDisplay.firstChild);

        // Keep only last 50 entries
        while (logDisplay.children.length > 50) {
            logDisplay.removeChild(logDisplay.lastChild);
        }
    }

    function showQuickPMButton(username, gender = 'unknown', isBroadcaster = false) {
        // Create or get the button container
        let container = document.getElementById('pm-button-container');
        if (!container) {
            container = document.createElement('div');
            container.id = 'pm-button-container';
            document.body.appendChild(container);
        }

        // Position container based on panel position and available space
        const panel = document.getElementById('pm-assistant-panel');
        const panelRect = panel.getBoundingClientRect();
        const viewportWidth = window.innerWidth;

        // Check if there's space on the right side of panel
        const spaceOnRight = viewportWidth - (panelRect.right + 10);
        const spaceOnLeft = panelRect.left - 10;

        let positionStyle;
        if (spaceOnRight > 260) {
            // Position on right side
            positionStyle = `
                position: fixed;
                left: ${panelRect.right + 10}px;
                top: ${panelRect.top}px;
            `;
        } else if (spaceOnLeft > 260) {
            // Position on left side
            positionStyle = `
                position: fixed;
                right: ${viewportWidth - panelRect.left + 10}px;
                top: ${panelRect.top}px;
            `;
        } else {
            // Not enough space on either side, position below
            positionStyle = `
                position: fixed;
                left: ${panelRect.left}px;
                top: ${panelRect.bottom + 10}px;
            `;
        }

        container.style.cssText = `
            ${positionStyle}
            z-index: 9999;
            display: flex;
            flex-direction: column;
            gap: 8px;
            max-width: 250px;
        `;

        // Gender icon for button
        const genderIcon = {
            'male': '♂️',
            'female': '♀️',
            'couple': '👥',
            'trans': '⚧️',
            'unknown': ''
        }[gender] || '';

        const broadcasterIcon = isBroadcaster ? '📷' : '';

        // Create the button
        const button = document.createElement('button');
        button.className = 'pm-floating-button';
        button.textContent = `${genderIcon}${broadcasterIcon}💌 PM ${username}`;
        button.title = `${t().pmBtnTip.replace('this user', username)} (auto-closes in ${Math.round(CONFIG.buttonDisplayTime / 1000)}s)`;
        button.style.cssText = `
            padding: 10px 15px;
            background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
            color: white;
            border: none;
            border-radius: 6px;
            cursor: pointer;
            box-shadow: 0 4px 12px rgba(239, 68, 68, 0.4);
            font-size: 14px;
            font-weight: 500;
            transition: all 0.3s;
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
            position: relative;
            padding-right: 45px;
        `;

        // Track countdown state for pause/resume
        let timeoutId;
        let remainingTime = CONFIG.buttonDisplayTime;
        let startTime = Date.now();
        let isPaused = false;

        const closeBtn = document.createElement('span');
        closeBtn.textContent = t().excludeBtn;
        closeBtn.title = t().excludeBtnPopupTip;
        closeBtn.style.cssText = `
            position: absolute;
            right: 10px;
            top: 50%;
            transform: translateY(-50%);
            font-size: 16px;
            cursor: pointer;
            padding: 2px 4px;
            line-height: 1;
            background: white;
            border-radius: 3px;
            opacity: 0.9;
            transition: opacity 0.2s;
        `;
        closeBtn.onmouseenter = () => closeBtn.style.opacity = '1';
        closeBtn.onmouseleave = () => closeBtn.style.opacity = '0.9';
        closeBtn.onclick = (e) => {
            e.stopPropagation();
            addToExclusionList(username);
            button.remove();
            if (container.children.length === 0) {
                container.remove();
            }
        };
        button.appendChild(closeBtn);

        // Add progress bar animation
        button.style.setProperty('--progress-duration', `${CONFIG.buttonDisplayTime}ms`);
        const style = document.createElement('style');
        style.textContent = `
            .pm-floating-button::after {
                animation-duration: ${CONFIG.buttonDisplayTime}ms !important;
            }
        `;
        document.head.appendChild(style);

        // Hover effect
        button.onmouseenter = () => {
            button.style.transform = 'translateY(-2px)';
            button.style.boxShadow = '0 6px 16px rgba(0,0,0,0.4)';

            // Pause progress bar animation
            button.classList.add('paused');

            // Pause countdown
            if (!isPaused && timeoutId) {
                clearTimeout(timeoutId);
                remainingTime = remainingTime - (Date.now() - startTime);
                isPaused = true;
            }
        };
        button.onmouseleave = () => {
            button.style.transform = 'translateY(0)';
            button.style.boxShadow = '0 4px 12px rgba(0,0,0,0.3)';

            // Resume progress bar animation
            button.classList.remove('paused');

            // Resume countdown
            if (isPaused) {
                startTime = Date.now();
                isPaused = false;
                timeoutId = setTimeout(() => {
                    if (button.parentNode) {
                        button.remove();
                        if (container.children.length === 0) {
                            container.remove();
                        }
                    }
                }, remainingTime);
            }
        };

        // Click handler
        button.onclick = () => {
            sendPM(username, CONFIG.welcomeMessage);
            button.remove();
            // Remove container if no more buttons
            if (container.children.length === 0) {
                container.remove();
            }
        };

        // Add to container
        container.appendChild(button);

        // Play audio notification to alert user that popup appeared
        playNotificationSound();

        // Auto-remove after configured time (with pause/resume support)
        timeoutId = setTimeout(() => {
            if (button.parentNode) {
                button.remove();
                // Remove container if no more buttons
                if (container.children.length === 0) {
                    container.remove();
                }
            }
        }, CONFIG.buttonDisplayTime);
    }

    function showSettings() {
        const newMessage = prompt(`${t().msgLabel}`, CONFIG.welcomeMessage);
        if (newMessage && newMessage.trim()) {
            CONFIG.welcomeMessage = newMessage.trim();
            GM_setValue('welcomeMessage', CONFIG.welcomeMessage);
            logMessage('✓ Welcome message updated', 'success');
        }
    }

    function addToExclusionList(username) {
        if (!CONFIG.exclusionList.includes(username)) {
            CONFIG.exclusionList.push(username);
            GM_setValue('exclusionList', CONFIG.exclusionList);
            logMessage(`🚫 ${username} ${t().logAddedExclusion}`, 'info');
            updateExclusionButton();
        } else {
            logMessage(`ℹ️ ${username} ${t().logAlreadyExcluded}`, 'info');
        }
    }

    function removeFromExclusionList(username) {
        const index = CONFIG.exclusionList.indexOf(username);
        if (index > -1) {
            CONFIG.exclusionList.splice(index, 1);
            GM_setValue('exclusionList', CONFIG.exclusionList);
            logMessage(`✓ ${username} ${t().logRemovedExclusion}`, 'success');
            updateExclusionButton();
        } else {
            logMessage(`⚠️ ${username} ${t().logNotInExclusion}`, 'warning');
        }
    }

    // ==================== CHAT MONITORING ====================

    function findUserListElement() {
        // Try multiple possible selectors for user list
        const selectors = [
            '#user-list',
            '.user-list',
            '#room-user-list',
            '.room-user-list',
            '#chat-list',
            '.chat-list',
            '[data-role="user-list"]',
            '[data-testid="user-list"]',
            '.userlist',
            '#userlist'
        ];

        for (const selector of selectors) {
            const element = document.querySelector(selector);
            if (element) {
                console.log(`[PM Assistant] Found user list: ${selector}`);
                return element;
            }
        }

        console.warn('[PM Assistant] User list not found. Please configure selector manually.');
        return null;
    }

    function extractUsername(element) {
        // Try multiple ways to extract username from element
        let text = (
            element.textContent?.trim() ||
            element.dataset?.username ||
            element.dataset?.user ||
            element.getAttribute('data-username') ||
            element.querySelector('.username')?.textContent?.trim() ||
            element.querySelector('[data-username]')?.dataset.username ||
            null
        );

        if (!text) return null;

        // ALWAYS skip messages that start with "Notice: " (these are bot messages)
        if (text.startsWith('Notice:')) {
            console.log('[PM Assistant] Skipping Notice message');
            return null;
        }

        // Pattern 1: "username has joined the room." (Chaturbate's built-in notification)
        const joinedMatch = text.match(/^(.+?)\s+has joined the room\.?/i);
        if (joinedMatch) {
            const username = joinedMatch[1].trim();
            return username;
        }

        // Pattern 2: "@username ..." (with @ symbol)
        const atIndex = text.indexOf('@');
        if (atIndex !== -1) {
            const afterAt = text.substring(atIndex + 1);
            const spaceIndex = afterAt.indexOf(' ');

            let username;
            if (spaceIndex === -1) {
                username = afterAt.trim();
            } else {
                username = afterAt.substring(0, spaceIndex).trim();
            }

            // Remove trailing special characters
            username = username.replace(/[^\w-]+$/g, '');

            if (username.length >= 2 && username.length <= 25) {
                return username;
            }
        }

        // Pattern 3: Just the username (no special format)
        const username = text.trim();
        if (username.length >= 2 && username.length <= 25 && !username.includes(' ')) {
            console.log(`[PM Assistant] Using raw username: "${username}"`);
            return username;
        }

        console.log(`[PM Assistant] Could not extract username from: "${text}"`);
        return null;
    }

    // cleanUsername is no longer needed with this simpler approach
    function cleanUsername(username) {
        // Keep for backwards compatibility but not used
        return username;
    }

    function startMonitoring() {
        logMessage('Starting chat monitor...', 'info');

        // Find the user list container
        const userList = findUserListElement();

        if (!userList) {
            logMessage('⚠️ User list not found!', 'error');
            logMessage('Click "Test Detection" for help', 'warning');
            console.log('%c[PM Assistant] USER LIST NOT FOUND', 'color: red; font-size: 14px; font-weight: bold;');
            console.log('Click the "Test Detection" button in the panel for diagnostics.');
            return;
        }

        logMessage(`✓ Monitoring started`, 'success');
        console.log('[PM Assistant] Watching element:', userList);

        // Use MutationObserver to watch for new users
        const observer = new MutationObserver((mutations) => {
            mutations.forEach((mutation) => {
                mutation.addedNodes.forEach((node) => {
                    if (node.nodeType !== 1) return; // Only process element nodes

                    // Get the raw text/username from the node
                    const rawText = node.textContent?.trim() ||
                                   node.dataset?.username ||
                                   node.dataset?.user ||
                                   null;

                    if (!rawText) return;

                    // Skip Notice messages - we don't need them anymore with popup detection
                    if (rawText.startsWith('Notice:')) {
                        return;
                    }

                    // Try to extract username from join messages
                    const username = extractUsername(node);

                    if (!username) {
                        return;
                    }

                    // Check if this user is already pending processing
                    if (pendingUsers.has(username)) {
                        return;
                    }

                    // Mark as pending
                    pendingUsers.add(username);

                    // Add to detection queue for popup-based detection
                    detectionQueue.push({ username, userElement: node });

                    // Start processing the queue
                    processDetectionQueue();
                });
            });
        });

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

        logMessage('✓ Observer active', 'success');
    }

    // ==================== DEBUGGING HELPER ====================

    function inspectorMode() {
        console.log('%c=== PM ASSISTANT INSPECTOR ===', 'color: #667eea; font-size: 16px; font-weight: bold;');
        console.log('');

        console.log('%c1. Room Configuration:', 'color: #4ade80; font-weight: bold;');
        console.log('   Room name:', getRoomName());
        console.log('   Broadcaster:', getBroadcasterUsername());
        console.log('   CSRF token:', getCSRFToken() ? '✓ Found' : '✗ Not found');
        console.log('');

        console.log('%c2. User List Detection:', 'color: #4ade80; font-weight: bold;');
        const userList = findUserListElement();
        if (userList) {
            console.log('   ✓ User list found:', userList);
            console.log('   Element ID:', userList.id);
            console.log('   Element classes:', userList.className);
            console.log('   Current children:', userList.children.length);
        } else {
            console.log('   ✗ User list not found');
            console.log('   To configure:');
            console.log('     1. Right-click user list → Inspect');
            console.log('     2. Note the element\'s ID or class');
            console.log('     3. Add selector to findUserListElement()');
        }
        console.log('');

        console.log('%c3. PM Status:', 'color: #4ade80; font-weight: bold;');
        console.log('   Auto mode:', CONFIG.autoMode ? 'ON' : 'OFF');
        console.log('   Token filter:', CONFIG.onlyTokenUsers ? 'ON' : 'OFF');
        console.log('   PMs sent this hour:', pmCount);
        console.log('   Users PMed:', Object.keys(pmHistory).length);
        console.log('');

        console.log('%c4. Test PM Function:', 'color: #4ade80; font-weight: bold;');
        console.log('   To test, run in console:');
        console.log('   sendPrivateMessage("username", "test message")');
        console.log('');

        console.log('%c=== END REPORT ===', 'color: #667eea; font-size: 16px; font-weight: bold;');
    }

    // ==================== INITIALIZATION ====================

    function init() {
        console.log('[PM Assistant] Initializing...');

        // Wait for page to fully load
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', init);
            return;
        }

        // Check if we're on a broadcaster page
        // You may need to adjust this check
        if (!window.location.pathname.includes('/b/')) {
            console.log('[PM Assistant] Not on broadcaster page');
            return;
        }

        // Create UI
        createControlPanel();
        updateStatus();

        // Start monitoring
        setTimeout(startMonitoring, 2000); // Wait 2s for chat to load

        // Debugging helper
        GM_registerMenuCommand('🔍 Inspector Mode', inspectorMode);

        logMessage('PM Assistant loaded! Configure functions for your setup.', 'success');
    }

    // Start the script
    init();

})();