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.

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==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();

})();