MBSSNameMentionCounter

Счетчик сообщений без упоминания имени

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         MBSSNameMentionCounter
// @version      1.7
// @description  Счетчик сообщений без упоминания имени
// @match        https://support-admin-common-master.mbss.maxbit.private/*
// @author       a.golovin
// @run-at       document-idle
// @namespace    Violentmonkey Scripts
// ==/UserScript==


(function() {
    'use strict';

    const CHAT_SEL = '.vac-room-item';
    const ROOM_HEADER_SEL = '.vac-room-header';
    const MY_MSG_CLS = 'vac-message-current';
    const MSG_CARD_SEL = '.vac-message-card';
    const STORAGE_PREFIX = 'NameMentionCounter:';
    
    let debugMode = true;
    
    function log(...args) {
        if (debugMode) {
            console.log('MBSSNameMentionCounter:', ...args);
        }
    }
    
    // Счетчики для каждого чата
    const mentionCounters = new WeakMap();
    
    // Получаем ключ для хранения в localStorage
    function getStorageKey(room) {
        const roomId = room.id || room.getAttribute('data-room-id');
        const roomName = room.querySelector('.vac-room-name')?.textContent.trim();
        return STORAGE_PREFIX + (roomId || roomName || 'default');
    }
    
    // Сохраняем счетчик в localStorage
    function saveCounter(room, count) {
        try {
            const key = getStorageKey(room);
            localStorage.setItem(key, count.toString());
            log(`Счетчик сохранен: ${count} для ключа ${key}`);
        } catch (e) {
            log('Ошибка при сохранении счетчика:', e);
        }
    }
    
    // Загружаем счетчик из localStorage
    function loadCounter(room) {
        try {
            const key = getStorageKey(room);
            const saved = localStorage.getItem(key);
            const count = saved ? parseInt(saved, 10) : 0;
            log(`Счетчик загружен: ${count} для ключа ${key}`);
            return count;
        } catch (e) {
            log('Ошибка при загрузке счетчика:', e);
            return 0;
        }
    }
    
    // Инициализация счетчика для чата
    function initCounter(room) {
        if (mentionCounters.has(room)) {
            return;
        }
        
        log('Инициализация счетчика для комнаты', room);
        
        // Загружаем сохраненное значение
        const savedCount = loadCounter(room);
        
        const counter = {
            count: savedCount,
            element: null,
            headerElement: null,
            room: room
        };
        
        mentionCounters.set(room, counter);
        injectCounterElement(room, counter);
        updateCounterDisplay(room);
        
        log(`Счетчик инициализирован со значением: ${savedCount}`);
    }
    
    // Встраиваем элемент счетчика в список чатов
    function injectCounterElement(room, counter) {
        if (counter.element) {
            return;
        }
        
        const el = document.createElement('div');
        el.className = 'name-mention-counter';
        el.style.cssText = `
            display: inline-block;
            margin: 0 8px;
            width: 20px;
            height: 20px;
            border-radius: 50%;
            text-align: center;
            line-height: 20px;
            font-size: 12px;
            font-weight: bold;
            color: #000;
            border: 2px solid #ccc;
            background-color: transparent;
        `;
        el.title = 'Счетчик сообщений без упоминания имени';
        
        // Пробуем разные места для вставки
        const roomInfo = room.querySelector('.vac-room-info');
        if (roomInfo) {
            roomInfo.prepend(el);
            counter.element = el;
            return;
        }
        
        const roomName = room.querySelector('.vac-room-name');
        if (roomName) {
            roomName.parentNode.insertBefore(el, roomName.nextSibling);
            counter.element = el;
            return;
        }
        
        room.prepend(el);
        counter.element = el;
    }
    
    // Встраиваем счетчик в header чата
    function injectHeaderCounterElement() {
        const roomHeader = document.querySelector(ROOM_HEADER_SEL);
        if (!roomHeader) {
            return null;
        }
        
        const oldCounter = roomHeader.querySelector('.name-mention-counter-header');
        if (oldCounter) {
            oldCounter.remove();
        }
        
        const headerEl = document.createElement('div');
        headerEl.className = 'name-mention-counter-header';
        headerEl.style.cssText = `
            display: inline-flex;
            align-items: center;
            justify-content: center;
            margin: 0 12px;
            width: 24px;
            height: 24px;
            border-radius: 50%;
            font-size: 14px;
            font-weight: bold;
            color: #000;
            border: 2px solid #666;
            background-color: transparent;
        `;
        headerEl.title = 'Счетчик сообщений без упоминания имени';
        
        const tagsContainer = roomHeader.querySelector('.vac-info-tags');
        if (tagsContainer) {
            tagsContainer.prepend(headerEl);
            return headerEl;
        }
        
        const roomWrapper = roomHeader.querySelector('.vac-room-wrapper');
        if (roomWrapper) {
            roomWrapper.appendChild(headerEl);
            return headerEl;
        }
        
        roomHeader.prepend(headerEl);
        return headerEl;
    }
    
    // Получаем имя оператора из комнаты
    function getOperatorName(room) {
        const nameElement = room.querySelector('.vac-room-name.vac-text-ellipsis');
        if (!nameElement) {
            return null;
        }
        
        const fullName = nameElement.textContent.trim();
        const firstName = fullName.split(' ')[0];
        return firstName;
    }
    
    // Получаем текст сообщения из карточки
    function getMessageText(messageCard) {
        // Пробуем разные селекторы для текста сообщения
        const selectors = [
            '.vac-text-message',
            '.vac-message-text',
            '.message-text',
            '.text-content',
            'div[class*="message"]',
            'div[class*="text"]',
            'span[class*="message"]',
            'span[class*="text"]'
        ];
        
        for (const selector of selectors) {
            try {
                const element = messageCard.querySelector(selector);
                if (element && element.textContent && element.textContent.trim()) {
                    const text = element.textContent.trim();
                    log(`Текст сообщения найден с селектором ${selector}:`, text);
                    return text;
                }
            } catch (e) {
                log(`Ошибка с селектором ${selector}:`, e.message);
            }
        }
        
        // Если стандартные селекторы не работают, ищем любой текст в карточке
        try {
            const allText = messageCard.textContent || messageCard.innerText;
            if (allText) {
                // Ищем текст который не является временем или служебной информацией
                const textLines = allText.split('\n').filter(line => 
                    line.trim() && 
                    !line.match(/\d{1,2}:\d{2}/) && // не время
                    !line.match(/^(Today|Вчера|Yesterday|Сегодня)/i) && // не дата
                    !line.match(/^\d{1,2}\.\d{1,2}\.\d{4}/) && // не дата
                    line.trim().length > 2 // не слишком короткий текст
                );
                
                if (textLines.length > 0) {
                    const text = textLines[0].trim();
                    log('Текст сообщения найден в общем содержимом:', text);
                    return text;
                }
            }
        } catch (e) {
            log('Ошибка при анализе общего текста:', e.message);
        }
        
        return '';
    }
    
    // Проверяем, содержит ли сообщение упоминание имени
    function containsNameMention(messageText, operatorName) {
        if (!messageText || !operatorName) {
            return false;
        }
        
        const cleanText = messageText.toLowerCase().trim();
        const name = operatorName.toLowerCase();
        
        const patterns = [
            name + ',',
            name + '!', 
            name + '?',
            name + '...',
            name + ' ',
            ' ' + name + ' ',
            ' ' + name + ',',
            ' ' + name + '!',
            ' ' + name + '?',
            name + '.',
            name + ':'
        ];
        
        for (const pattern of patterns) {
            if (cleanText.includes(pattern)) {
                log(`Упоминание найдено: содержит "${pattern}"`);
                return true;
            }
        }
        
        if (cleanText.startsWith(name)) {
            log(`Упоминание в начале: начинается с "${name}"`);
            return true;
        }
        
        log(`Упоминание не найдено: не содержит "${name}"`);
        return false;
    }
    
    // Находим комнату по активному чату
    function findRoomForActiveChat() {
        const activeRoom = document.querySelector('.vac-room-item.vac-room-selected');
        if (activeRoom) {
            return activeRoom;
        }
        
        const headerName = document.querySelector('.vac-room-header .vac-room-name');
        if (headerName) {
            const roomName = headerName.textContent.trim();
            const rooms = document.querySelectorAll(CHAT_SEL);
            for (const room of rooms) {
                const roomNameEl = room.querySelector('.vac-room-name');
                if (roomNameEl && roomNameEl.textContent.trim() === roomName) {
                    return room;
                }
            }
        }
        
        return null;
    }
    
    // Обрабатываем новое сообщение
    function processNewMessage(messageCard) {
        log('Обработка нового сообщения');
        
        const room = findRoomForActiveChat();
        if (!room) {
            log('Комната не найдена для сообщения');
            return;
        }
        
        if (!mentionCounters.has(room)) {
            log('Инициализируем счетчик для комнаты');
            initCounter(room);
        }
        
        const counter = mentionCounters.get(room);
        if (!counter) {
            log('Счетчик не найден');
            return;
        }
        
        const isMyMessage = messageCard.classList.contains(MY_MSG_CLS);
        log(`Сообщение мое: ${isMyMessage}`);
        
        if (!isMyMessage) {
            return;
        }
        
        const messageText = getMessageText(messageCard);
        if (!messageText) {
            log('Текст сообщения пустой');
            return;
        }
        
        const operatorName = getOperatorName(room);
        if (!operatorName) {
            log('Имя оператора не найдено');
            return;
        }
        
        log(`Проверяем сообщение: "${messageText}" на имя: "${operatorName}"`);
        
        if (containsNameMention(messageText, operatorName)) {
            log('Имя упомянуто, сбрасываем счетчик');
            counter.count = 0;
        } else {
            log('Имя не упомянуто, увеличиваем счетчик');
            counter.count++;
        }
        
        // Сохраняем новое значение
        saveCounter(room, counter.count);
        
        log(`Новое значение счетчика: ${counter.count}`);
        updateCounterDisplay(room);
    }
    
    // Обновляем отображение счетчика
    function updateCounterDisplay(room) {
        const counter = mentionCounters.get(room);
        if (!counter) {
            return;
        }
        
        const count = counter.count;
        log(`Обновление счетчика: ${count} сообщений без имени`);
        
        if (counter.element) {
            counter.element.textContent = count > 0 ? count.toString() : '';
            updateCounterStyle(counter.element, count);
        }
        
        updateHeaderCounter(count);
    }
    
    // Обновляем счетчик в header'е
    function updateHeaderCounter(count) {
        let headerCounter = document.querySelector('.name-mention-counter-header');
        
        if (!headerCounter) {
            headerCounter = injectHeaderCounterElement();
            if (!headerCounter) return;
        }
        
        headerCounter.textContent = count > 0 ? count.toString() : '';
        updateCounterStyle(headerCounter, count);
    }
    
    // Обновляем стили счетчика
    function updateCounterStyle(element, count) {
        if (count === 0) {
            element.style.backgroundColor = 'transparent';
            element.style.borderColor = '#ccc';
            element.style.color = '#000';
        } else if (count === 1) {
            element.style.backgroundColor = '#9ACD32';
            element.style.borderColor = '#9ACD32';
            element.style.color = '#000';
        } else if (count === 2) {
            element.style.backgroundColor = '#F0E68C';
            element.style.borderColor = '#F0E68C';
            element.style.color = '#000';
        } else if (count >= 3) {
            element.style.backgroundColor = '#CD5C5C';
            element.style.borderColor = '#CD5C5C';
            element.style.color = '#fff';
        }
    }
    
    // Восстанавливаем историю сообщений при загрузке
    function restoreMessageHistory() {
        log('Восстановление истории сообщений...');
        
        const activeRoom = findRoomForActiveChat();
        if (!activeRoom) {
            log('Активная комната не найдена для восстановления истории');
            return;
        }
        
        // Инициализируем счетчик для активной комнаты
        if (!mentionCounters.has(activeRoom)) {
            initCounter(activeRoom);
        }
        
        const counter = mentionCounters.get(activeRoom);
        if (!counter) {
            return;
        }
        
        // Ищем все мои сообщения в истории
        const myMessages = document.querySelectorAll(`${MSG_CARD_SEL}.${MY_MSG_CLS}`);
        log(`Найдено моих сообщений в истории: ${myMessages.length}`);
        
        let consecutiveWithoutMention = 0;
        const operatorName = getOperatorName(activeRoom);
        
        if (!operatorName) {
            log('Имя оператора не найдено для восстановления истории');
            return;
        }
        
        // Проходим по всем сообщениям с конца (от новых к старым)
        for (let i = myMessages.length - 1; i >= 0; i--) {
            const messageCard = myMessages[i];
            const messageText = getMessageText(messageCard);
            
            if (!messageText) continue;
            
            if (containsNameMention(messageText, operatorName)) {
                // Нашли сообщение с упоминанием - останавливаемся
                log(`Найдено сообщение с упоминанием, сбрасываем счетчик`);
                break;
            } else {
                consecutiveWithoutMention++;
                log(`Сообщение без упоминания: ${consecutiveWithoutMention}`);
            }
        }
        
        // Обновляем счетчик если нашли сообщения без упоминания
        if (consecutiveWithoutMention > 0 && consecutiveWithoutMention !== counter.count) {
            log(`Восстановлен счетчик: ${consecutiveWithoutMention} сообщений без упоминания`);
            counter.count = consecutiveWithoutMention;
            saveCounter(activeRoom, counter.count);
            updateCounterDisplay(activeRoom);
        }
    }
    
    // Инициализация всех чатов
    function initChats() {
        const chats = document.querySelectorAll(CHAT_SEL);
        log(`Найдено чатов: ${chats.length}`);
        
        chats.forEach((room) => {
            initCounter(room);
        });
        
        // Восстанавливаем историю для активного чата
        setTimeout(restoreMessageHistory, 1000);
    }
    
    // Наблюдатель за новыми сообщениями
    function setupMessageObserver() {
        const observer = new MutationObserver((mutations) => {
            for (const mutation of mutations) {
                if (mutation.type === 'childList') {
                    for (const node of mutation.addedNodes) {
                        if (!(node instanceof Element)) continue;
                        
                        let messageCard = null;
                        if (node.matches(MSG_CARD_SEL)) {
                            messageCard = node;
                        } else {
                            messageCard = node.querySelector(MSG_CARD_SEL);
                        }
                        
                        if (messageCard) {
                            log('Найдена новая карточка сообщения');
                            // Даем время для рендера текста
                            setTimeout(() => {
                                try {
                                    processNewMessage(messageCard);
                                } catch (error) {
                                    log('Ошибка при обработке сообщения:', error);
                                }
                            }, 300);
                        }
                    }
                }
            }
        });
        
        const messageContainer = document.querySelector('.vac-room-container') || document.body;
        observer.observe(messageContainer, {
            childList: true,
            subtree: true
        });
        
        log('Наблюдатель за сообщениями запущен');
    }
    
    // Наблюдатель за изменениями DOM
    function setupDOMObserver() {
        const observer = new MutationObserver((mutations) => {
            for (const mutation of mutations) {
                if (mutation.type === 'childList') {
                    for (const node of mutation.addedNodes) {
                        if (node instanceof Element) {
                            if (node.matches(CHAT_SEL) || node.querySelector(CHAT_SEL)) {
                                setTimeout(initChats, 1000);
                                break;
                            }
                        }
                    }
                }
            }
        });
        
        observer.observe(document.body, {
            childList: true,
            subtree: true
        });
    }
    
    // Запуск
    function start() {
        log('Запуск скрипта MBSSNameMentionCounter v1.7...');
        
        setTimeout(() => {
            try {
                initChats();
                setupMessageObserver();
                setupDOMObserver();
                injectHeaderCounterElement();
                
                log('Скрипт полностью инициализирован');
            } catch (error) {
                log('Ошибка при инициализации:', error);
            }
        }, 2000);
    }
    
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', start);
    } else {
        start();
    }
    
})();