Sleazy Fork is available in English.

Control Emotes Panel - iOS

Twitch emoji blocking via a channel with a management interface and context menu (via Tampermonkey)

// ==UserScript==
// @name         Control Emotes Panel - iOS 
// @version      2.3.6
// @description  Twitch emoji blocking via a channel with a management interface and context menu (via Tampermonkey)
// @author       Gullampis810
// @license      MIT
// @match        https://www.twitch.tv/*
// @grant        GM_registerMenuCommand
// @grant        GM_unregisterMenuCommand
// @grant        GM_setValue
// @grant        GM_getValue
// @icon         https://yt3.googleusercontent.com/ytc/AOPolaSnw1gT34BbZnQ1Bh7UHcbyrKjbFw_0fS6AlR4juw=s900-c-k-c0x00ffffff-no-rj
// @namespace http://tampermonkey.net/
// ==/UserScript==

(function () {
    'use strict';

    // Загружаем список заблокированных каналов и состояние панели из хранилища
    let blockedChannels = (GM_getValue( "blockedChannels", "[]"));// Массив заблокированных каналов
    let isPanelVisible = GM_getValue('isPanelVisible', false); // Флаг, указывающий, видна ли панель

//----------------------------------- Панель управления -----------------------------------------//
    const controlPanel = document.createElement('div');
    controlPanel.style.position = 'fixed'; // Фиксируем панель на экране
    controlPanel.style.bottom = '124px'; // Располагаем панель на 124px от нижней границы экрана
    controlPanel.style.right = '380px'; // Располагаем панель на 310px от правой границы экрана
    controlPanel.style.width = '662px'; // Ширина панели
    controlPanel.style.height = '480px'; // Высота панели
    controlPanel.style.backgroundColor = '#5c5065'; // Цвет фона панели
    controlPanel.style.background = '-webkit-linear-gradient(270deg, hsla(50, 76%, 56%, 1) 0%, hsla(32, 83%, 49%, 1) 25%, hsla(0, 37%, 37%, 1) 59%, hsla(276, 47%, 24%, 1) 79%, hsla(261, 11%, 53%, 1) 100%)'; // Применяем градиентный фон

    controlPanel.style.border = '1px solid #ccc'; // Цвет и стиль границы панели
    controlPanel.style.borderRadius = '8px'; // Скругляем углы панели
    controlPanel.style.padding = '10px'; // Отступы внутри панели
    controlPanel.style.boxShadow = '0 4px 6px rgba(0, 0, 0, 0.1)'; // Добавляем тень панели
    controlPanel.style.zIndex = 10000; // Устанавливаем высокий z-index, чтобы панель была поверх других элементов
    controlPanel.style.fontFamily = 'Arial, sans-serif'; // Шрифт текста на панели
    controlPanel.style.transition = 'height 0.3s ease'; // Плавное изменение высоты при изменении
    controlPanel.style.overflow = 'hidden'; // Скрытие содержимого, если оно выходит за пределы панели


//--------------- Название листа список ------------------------//
const title = document.createElement('h4');
title.innerText = 'list of BlockedEmotes';
title.style.margin = '-5px 0px 10px'; // Обновленный стиль margin
title.style.color = '#2a1e38'; // Обновленный цвет
title.style.position = 'relative'; // Устанавливаем позицию относительно
title.style.top = '-51px'; // Сдвиг по вертикали
controlPanel.appendChild(title);



//--------------- Список заблокированных каналов ------------------//
const list = document.createElement('ul');
list.id = 'blockedChannelsList';
list.style.listStyle = 'none'; // Убираем стандартные маркеры списка
list.style.padding = '0'; // Убираем отступы
list.style.margin = '-14px 0px 10px'; // Отступ снизу
list.style.maxHeight = '290px'; // Устанавливаем максимальную высоту
list.style.height = '245px'; // Высота списка
list.style.overflowY = 'auto'; // Включаем вертикальную прокрутку

// Добавление границы
list.style.border = '1px solid #ffffff'; // Белая граница
list.style.borderRadius = '0px 0px 6px 6px;'; // Скругление углов

// Добавление тени
list.style.boxShadow = 'rgb(31 24 35) 0px 20px 17px 0px inset'; // Вставка тени в контейнер

//==================================== ГРАДИЕНТ ФОН СПИСОК =================================================//
// Добавляем линейный градиент фона с кроссбраузерностью
list.style.background = 'linear-gradient(45deg, hsla(292, 44%, 16%, 1) 0%, hsla(173, 29%, 48%, 1) 100%)';
list.style.background = '-moz-linear-gradient(45deg, hsla(292, 44%, 16%, 1) 0%, hsla(173, 29%, 48%, 1) 100%)'; // Для Firefox
list.style.background = '-webkit-linear-gradient(45deg, hsla(292, 44%, 16%, 1) 0%, hsla(173, 29%, 48%, 1) 100%)'; // Для Safari и Chrome
list.style.filter = 'progid: DXImageTransform.Microsoft.gradient(startColorstr="#36173b", endColorstr="#589F97", GradientType=1)'; // Для старых версий IE
list.style.color = '#fff'; // Белый цвет текста

//========== scroll bar кастомный скролл бар для списка =============//
const style = document.createElement('style');
style.innerHTML = `
#blockedChannelsList::-webkit-scrollbar {
  width: 36px; /* Ширина скроллбара */
}

#blockedChannelsList::-webkit-scrollbar-thumb {
  background-color: #907cad; /* Цвет бегунка */
  border-radius: 8px; /* Скругление бегунка */
  border: 4px solid #2a1e38; /* Внутренний отступ (цвет трека) */
  height: 80px; /* Высота бегунка */
}

#blockedChannelsList::-webkit-scrollbar-thumb:hover {
  background-color: #b299d6; /* Цвет бегунка при наведении */
}

#blockedChannelsList::-webkit-scrollbar-track {
  background: #544564; /* Цвет трека */
  border-radius: 0px; /* Скругление трека */
}

#blockedChannelsList::-webkit-scrollbar-track:hover {
  background: #544564; /* Цвет трека при наведении */
}
`;
document.head.appendChild(style);

const buttonColor = '#907cad'; // Общий цвет для кнопок
const buttonShadow = '0 4px 8px rgba(0, 0, 0, 0.6)'; // Тень для кнопок (60% прозрачности)

// Функция для обновления списка заблокированных каналов
function updateChannelList() {
    list.innerHTML = ''; // Очищаем список перед обновлением

    blockedChannels.forEach(channel => {
        const item = document.createElement('li');
        item.style.display = 'flex'; // Используем flex для выравнивания элементов в строку
        item.style.justifyContent = 'space-between'; // Разделяем элементы по краям
        item.style.padding = '5px'; // Добавляем отступы
        item.style.borderBottom = '1px solid #eee'; // Легкая граница между элементами списка

        const channelInfo = document.createElement('span');
        channelInfo.innerText = `${channel.platform} > ${channel.emoteName} (prefix: ${channel.name})`;
        item.appendChild(channelInfo);

       const dateInfo = document.createElement('span');
       const date = new Date(channel.date);
       dateInfo.innerText = isNaN(date.getTime())
      ? 'Unknown Date'
      : date.toLocaleString('en-GB', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' });
        dateInfo.style.flex = '1'; // Добавляем flex для выравнивания
        dateInfo.style.textAlign = 'right'; // Выравниваем текст по правому краю
        dateInfo.style.marginRight = '3px'; // Сдвигаем текст на 3px влево
        item.appendChild(dateInfo);
        dateInfo.style.flex = '1'; // Добавляем flex для выравнивания
        dateInfo.style.textAlign = 'right'; // Выравниваем текст по правому краю
        dateInfo.style.marginRight = '3px'; // Сдвигаем текст на 3px влево
        item.appendChild(dateInfo);

        const removeButton = document.createElement('button');
        removeButton.innerText = 'Delete';
        removeButton.style.background = '#ff4d4d'; // Цвет кнопки удаления
        removeButton.style.color = '#fff';
        removeButton.style.height = '35px';
        removeButton.style.width = '75px';
        removeButton.style.fontWeight = 'bold'; // Жирный текст для кнопки удаления
        removeButton.style.fontSize = '16px'; // Увеличиваем размер текста для кнопки
        removeButton.style.border = 'none';
        removeButton.style.borderRadius = '4px';
        removeButton.style.padding = '2px 6px';
        removeButton.style.cursor = 'pointer';
        removeButton.style.boxShadow = buttonShadow; // Тень для кнопки удаления

        // Добавляем подсветку при наведении
        removeButton.onmouseover = function() {
        removeButton.style.background = '-webkit-linear-gradient(135deg, hsla(359, 91%, 65%, 1) 0%, hsla(358, 76%, 16%, 1) 56%, hsla(359, 61%,                 19%, 1) 98%, hsla(0, 100%, 65%, 1) 100%)'; // Изменение фона при наведении
        };
        removeButton.onmouseout = function() {
            removeButton.style.background = '#ff4d4d'; // Возвращаем исходный цвет
        };

        // Обработчик клика для удаления канала
        removeButton.onclick = function () {
            blockedChannels = blockedChannels.filter(c => c !== channel);
            GM_setValue("blockedChannels", blockedChannels); // Сохраняем обновленный список
            updateChannelList(); // Обновляем список на экране
            updateCounter(); // Обновляем счетчик
            showEmoteForChannel(channel); // Показать смайл в чате
        };

        item.appendChild(removeButton);
        list.appendChild(item); // Добавляем элемент в список
    });
}

// Добавляем список в панель управления
controlPanel.appendChild(list);




//================= Функционал для добавления нового канала в список заблокированных -----------//
const inputContainer = document.createElement('div');
inputContainer.style.display = 'flex';
inputContainer.style.gap = '5px';

const input = document.createElement('input');
input.type = 'text';
input.placeholder = 'type to add channel ';
input.style.flex = '1';
input.style.fontWeight = 'bold'; // Жирный текст

input.style.height = '35px'; // Отступ между кнопкой и поисковой строкой
input.style.padding = '5px';
input.style.border = '1px solid #ccc';
input.style.borderRadius = '4px';
input.style.marginTop = '15px'; // Отступ между кнопкой и поисковой строкой


// Добавление тени с фиолетовым цветом (35% прозрачности) внутрь
input.style.boxShadow = '#4c2a5e 0px 4px 6px inset'; // Тень фиолетового цвета внутри


//----------------- Add it Button ----------------------//
const addButton = document.createElement('button');
addButton.innerText = 'Add it';
addButton.style.background = buttonColor;
addButton.style.marginTop = '15px'; // Отступ между кнопкой и поисковой строкой

addButton.style.color = '#fff';
addButton.style.border = 'none';
addButton.style.width= '72px';
addButton.style.borderRadius = '4px';
addButton.style.padding = '5px 10px';
addButton.style.cursor = 'pointer';
addButton.style.boxShadow = buttonShadow; // Тень для кнопки "Add it"

// Увеличиваем размер текста и делаем его жирным
addButton.style.fontSize = '16px'; // Увеличиваем размер текста
addButton.style.fontWeight = 'bold'; // Жирный текст

addButton.onclick = (event) => {
    event.preventDefault();
    const channel = input.value.trim();
    const platform = platformSelect.value;
    if (channel) {
        let emoteName = channel;
        let emoteUrl = channel;
        if (platform === '7tv' || platform === 'bttTV') {
            const img = document.querySelector(`img[src="${channel}"]`);
            if (img) {
                emoteName = img.alt;
                emoteUrl = img.src;
            }
        } else if (platform === 'TwitchChannel') {
            const prefix = channel.split(/[^a-zA-Z0-9]/)[0];
            emoteUrl = prefix;
        }
        if (!blockedChannels.some(c => c.name === emoteUrl && c.platform === platform)) {
            blockedChannels.push({ name: emoteUrl, platform: platform, emoteName: emoteName, date: new Date().toISOString() });
            GM_setValue("blockedChannels", blockedChannels);
            updateChannelList();
            updateCounter();
            input.value = '';
        }
    }
};

// Создаем выпадающий список для выбора платформы
const platformSelect = document.createElement('select');
platformSelect.style.marginTop = '15px'; // Отступ между кнопкой и поисковой строкой
platformSelect.style.height = '35px'; // Высота выпадающего списка
platformSelect.style.border = '1px solid #ccc';
platformSelect.style.borderRadius = '4px';
platformSelect.style.padding = '5px';
platformSelect.style.fontWeight = 'bold'; // Жирный текст

const platforms = ['TwitchChannel', '7tv', 'bttTV'];
platforms.forEach(platform => {
    const option = document.createElement('option');
    option.value = platform;
    option.innerText = platform;
    platformSelect.appendChild(option);
});

// Добавляем подсказки для выбора платформы
platformSelect.addEventListener('change', () => {
    const placeholderText = {
    'TwitchChannel': 'insert channel prefix',
   '7tv': 'please put here link: https://cdn.7tv.app/emote/00000000000000000000000000/2x.webp',
   'bttTV': 'please put here link: https://cdn.betterttv.net/emote/000000000000000000000000/2x.webp'
    };
    input.placeholder = placeholderText[platformSelect.value];
});

// Добавляем выпадающий список в контейнер ввода
inputContainer.appendChild(platformSelect);

//----------------Единый контейнер для кнопок -------------------------//
const buttonContainer = document.createElement('div');
buttonContainer.style.display = 'flex'; // Используем flexbox для расположения кнопок в строку
buttonContainer.style.gap = '6px'; // Задаем промежуток между кнопками
buttonContainer.style.marginTop = '12px'; // Отступ сверху для контейнера кнопок
buttonContainer.style.fontWeight = 'bold'; // Жирный текст для контейнера кнопок
buttonContainer.style.fontSize = '16px'; // Размер шрифта для кнопок
buttonContainer.style.width = '190%'; // Ширина кнопок (увеличена для эффекта растяжения




//-------------- Кнопка "Delete all" ------------------------//
const clearAllButton = document.createElement('button');
clearAllButton.innerText = 'Delete all'; // Текст на кнопке
clearAllButton.style.background = buttonColor; // Цвет фона кнопки
clearAllButton.style.color = '#fff'; // Цвет текста кнопки
clearAllButton.style.border = 'none'; // Убираем бордер у кнопки
clearAllButton.style.borderRadius = '4px'; // Скругленные углы кнопки
clearAllButton.style.padding = '5px 10px'; // Отступы внутри кнопки
clearAllButton.style.cursor = 'pointer'; // Курсор в виде руки при наведении
clearAllButton.style.boxShadow = buttonShadow; // Тень для кнопки "Delete all"

buttonContainer.appendChild(clearAllButton); // Добавляем кнопку в контейнер

// Обработчик события для кнопки "Delete all"
clearAllButton.onclick = () => {
    blockedChannels = []; // Очищаем массив заблокированных каналов
    GM_setValue("blockedChannels", blockedChannels); // Сохраняем обновленный массив в хранилище
    updateChannelList(); // Обновляем список каналов
    updateCounter(); // Обновляем счетчик
};


//----------------- export Button --------------------//
const exportButton = document.createElement('button');
exportButton.innerText = 'Export';
exportButton.style.background = buttonColor;
exportButton.style.color = '#fff';
exportButton.style.border = 'none';
exportButton.style.borderRadius = '4px';
exportButton.style.padding = '5px 10px';
exportButton.style.cursor = 'pointer';
exportButton.style.boxShadow = buttonShadow; // Тень для кнопки "Export"
buttonContainer.appendChild(exportButton);
exportButton.onclick = () => {
    const blob = new Blob([JSON.stringify(blockedChannels, null, 2)], { type: 'application/json' });
    const url = URL.createObjectURL(blob);
    const link = document.createElement('a');
    link.href = url;
    link.download = 'blocked_channels.json';
    link.click();
    URL.revokeObjectURL(url);
};

//----------------- importButton --------------------//
const importButton = document.createElement('button');
importButton.innerText = 'Import';
importButton.style.background = buttonColor;
importButton.style.color = '#fff';
importButton.style.border = 'none';
importButton.style.borderRadius = '4px';
importButton.style.padding = '5px 10px';
importButton.style.cursor = 'pointer';
importButton.style.boxShadow = buttonShadow; // Тень для кнопки "Import"
buttonContainer.appendChild(importButton);
importButton.onclick = () => {
    const fileInput = document.createElement('input');
    fileInput.type = 'file';
    fileInput.accept = 'application/json';
    fileInput.onchange = (event) => {
        const file = event.target.files[0];
        const reader = new FileReader();
        reader.onload = (e) => {
            try {
                const importedChannels = JSON.parse(e.target.result);

                if (Array.isArray(importedChannels)) {
                    importedChannels.forEach(channel => {
                        // Устанавливаем дату, если она отсутствует
                        if (!channel.date) {
                            channel.date = new Date().toISOString();
                        }

                        // Восстанавливаем имя эмодзи, если оно отсутствует
                        if (!channel.emoteName) {
                            if (channel.platform === '7tv' || channel.platform === 'bttTV') {
                                channel.emoteName = channel.name.split('/').slice(-2, -1)[0] || 'No Name';
                            } else if (channel.platform === 'TwitchChannel') {
                                channel.emoteName = channel.name.split(/[^a-zA-Z0-9]/)[0] || 'No Name';
                            } else {
                                channel.emoteName = 'No Name';
                            }
                        }
                    });

                    // Добавляем уникальные записи
                    blockedChannels = Array.from(
                        new Set([...blockedChannels, ...importedChannels].map(JSON.stringify))
                    ).map(JSON.parse);

                    // Сохраняем в память
                    GM_setValue("blockedChannels", blockedChannels);

                    // Обновляем интерфейс
                    updateChannelList();
                    updateCounter();
                } else {
                    alert('Invalid file format!');
                }
            } catch (err) {
                console.error(err);
                alert('Error reading file!');
            }
        };
        reader.readAsText(file);
    };
    fileInput.click();
};


// Добавляем кнопку "Unblock All Emotes" в контейнер кнопок
const unblockAllButton = document.createElement('button');
unblockAllButton.innerText = 'Unblock All Emotes';
unblockAllButton.style.background = buttonColor;
unblockAllButton.style.color = '#fff';
unblockAllButton.style.border = 'none';
unblockAllButton.style.borderRadius = '4px';
unblockAllButton.style.padding = '5px 10px';
unblockAllButton.style.cursor = 'pointer';
unblockAllButton.style.boxShadow = buttonShadow; // Тень для кнопки "Unblock All Emotes"
buttonContainer.appendChild(unblockAllButton);

// Добавляем кнопку "Back To Block All Emotes" в контейнер кнопок
const blockAllButton = document.createElement('button');
blockAllButton.innerText = 'Back To Block All Emotes';
blockAllButton.style.background = buttonColor;
blockAllButton.style.color = '#fff';
blockAllButton.style.border = 'none';
blockAllButton.style.borderRadius = '4px';
blockAllButton.style.padding = '5px 10px';
blockAllButton.style.cursor = 'pointer';
blockAllButton.style.boxShadow = buttonShadow; // Тень для кнопки "Back To Block All Emotes"
buttonContainer.appendChild(blockAllButton);

// Обработчик события для кнопки "Unblock All Emotes"
unblockAllButton.onclick = () => {
    const unblockedChannels = GM_getValue('unblockedChannels', []);
    if (blockedChannels.length > 0) {
        GM_setValue('unblockedChannels', blockedChannels);
        blockedChannels = [];
        GM_setValue('blockedChannels', blockedChannels);
        updateChannelList();
        updateCounter();
        showAllEmotes(); // Показать все смайлы в чате
    }
};

// Функция для отображения всех смайлов в чате
function showAllEmotes() {
    const chatContainer = document.querySelector('.chat-scrollable-area__message-container');
    if (chatContainer) {
        const emotes = chatContainer.querySelectorAll('.chat-line__message img, .chat-line__message .emote, .chat-line__message .bttv-emote, .chat-line__message .seventv-emote');
        emotes.forEach(emote => {
            emote.style.display = ''; // Сбросить стиль display для отображения смайлов
        });
    }
}

// Обработчик события для кнопки "Back To Block All Emotes"
blockAllButton.onclick = () => {
    const unblockedChannels = GM_getValue('unblockedChannels', []);
    if (unblockedChannels.length > 0) {
        blockedChannels = unblockedChannels;
        GM_setValue('blockedChannels', blockedChannels);
        GM_setValue('unblockedChannels', []);
        updateChannelList();
        updateCounter();
    }
};


//----------------- Счётчик ---------------------//
const counter = document.createElement('div');
counter.style.display = 'inline-block';
counter.style.backgroundColor = '#b69dcf'; // Белый фон
counter.style.color = '#4c2a5e'; // Цвет текста (темно-фиолетовый)
counter.style.border = '3px solid #4c2a5e'; // Граница того же цвета, что и текст
counter.style.borderRadius = '8px'; // Радиус скругления границы
counter.style.padding = '6px 12px'; // Отступы для удобства
counter.style.marginLeft = '6px'; // Отступ слева для отделения от других элементов
counter.style.fontWeight = 'bold'; // Жирное начертание текста
counter.style.fontSize = '16px'; // Устанавливаем размер шрифта для лучшей видимости

counter.style.top = '-394px'; // Обновленное положение сверху
counter.style.right = '356px'; // Обновленное положение справа
counter.style.position = 'relative'; // Относительное позиционирование для точного расположения

buttonContainer.appendChild(counter);

// Функция для обновления счётчика
function updateCounter() {
    const twitchCount = blockedChannels.filter(channel => channel.platform === 'TwitchChannel').length;
    const bttvCount = blockedChannels.filter(channel => channel.platform === 'bttTV').length;
    const tv7Count = blockedChannels.filter(channel => channel.platform === '7tv').length;

    // Считаем общее количество каналов
    const totalCount = twitchCount + bttvCount + tv7Count;

    // Обновляем текст счетчика
    counter.innerText = `Twitch: ${twitchCount} | BTTV: ${bttvCount} | 7TV: ${tv7Count} | Total: ${totalCount}`;
}

// Добавляем элементы на страницу
inputContainer.appendChild(input);
inputContainer.appendChild(addButton);
controlPanel.appendChild(inputContainer);

// Перемещаем контейнер кнопок вниз
controlPanel.appendChild(buttonContainer);

document.body.appendChild(controlPanel);

// Вызываем функцию обновления счётчика
updateCounter();





// Создаем кнопку "Open Blocker Emote"
const openPanelButton = document.createElement('button');
openPanelButton.innerText = 'Open Blocker Emote';
openPanelButton.style.fontWeight = 'bold';
openPanelButton.style.top = '22px';
openPanelButton.style.right = '1344px';
openPanelButton.style.position = 'fixed'; // Фиксированное положение
openPanelButton.style.width = '200px'; // Фиксированная ширина кнопки
openPanelButton.style.height = '41px'; // Фиксированная высота кнопки
openPanelButton.style.background = '#4c2a5e'; // Цвет кнопки в выключенном состоянии
openPanelButton.style.color = '#bda3d7';
openPanelButton.style.border = 'none'; // Без границ
openPanelButton.style.borderRadius = '20px'; // Закругленные углы
openPanelButton.style.padding = '10px';
openPanelButton.style.cursor = 'pointer';
openPanelButton.style.zIndex = 10000; // Высокий z-index
openPanelButton.style.transition = 'background 0.3s ease'; // Плавное изменение фона
openPanelButton.style.display = 'flex';
openPanelButton.style.alignItems = 'center';
openPanelButton.style.justifyContent = 'space-between'; // Чтобы текст и переключатель были по разным краям

// Создаем контейнер для переключателя (темная рамка)
const switchContainer = document.createElement('div');
switchContainer.style.width = '44px'; // Увеличиваем ширину контейнера на 6px
switchContainer.style.height = '27px'; // Увеличиваем высоту контейнера на 6px
switchContainer.style.borderRadius = '13px'; // Скругленные углы
switchContainer.style.backgroundColor = '#ccb8eb5c'; // Темно-зеленая рамка для кружка
switchContainer.style.position = 'relative'; // Для абсолютного позиционирования кружка
switchContainer.style.transition = 'background 0.3s ease'; // Плавное изменение фона контейнера
openPanelButton.appendChild(switchContainer);

// Создаем фиолетовый кружок (переключатель)
const switchCircle = document.createElement('div');
switchCircle.style.width = '19px'; // Увеличиваем ширину кружка на 3px
switchCircle.style.height = '19px'; // Увеличиваем высоту кружка на 3px
switchCircle.style.borderRadius = '50%'; // Кружок
switchCircle.style.backgroundColor = '#4c2a5e'; // фиолетовый цвет кружка
switchCircle.style.boxShadow = '0 2px 6px rgba(0, 0, 0, 0.8)'; // Тень для кружка
switchCircle.style.position = 'absolute'; // Абсолютное позиционирование внутри контейнера
switchCircle.style.top = '3px'; // Отступ сверху
switchCircle.style.left = '3px'; // Отступ слева
switchCircle.style.transition = 'transform 0.3s ease'; // Плавное движение
switchContainer.appendChild(switchCircle);



// Устанавливаем начальное состояние Панели //
let isPanelOpen = false;

// Обработчик клика для изменения состояния Панели//
openPanelButton.onclick = () => {
    isPanelOpen = !isPanelOpen;
    if (isPanelOpen) {
        openPanelButton.style.background = '#4c2a5e'; // Включено
        switchCircle.style.transform = 'translateX(20px)'; // Перемещаем кружок вправо
        switchContainer.style.backgroundColor = '#3c995d'; // Цвет контейнера в включенном состоянии
        controlPanel.style.display = 'block'; // Показать панель
    } else {
        openPanelButton.style.background = '#4c2a5e'; // Выключено
        switchCircle.style.transform = 'translateX(0)'; // Перемещаем кружок влево
        switchContainer.style.backgroundColor = '#ccb8eb5c'; // Цвет контейнера в выключенном состоянии
        controlPanel.style.display = 'none'; // Скрыть панель
    }
    GM_setValue('isPanelOpen', isPanelOpen); // Сохранить состояние
};

//---------- Добавляем кнопку в DOM только после полной загрузки страницы -------//
window.addEventListener('load', () => {
    document.body.appendChild(openPanelButton);

    // Устанавливаем начальное положение кнопки
    const updateButtonPosition = () => {
        const windowWidth = window.innerWidth;
        const windowHeight = window.innerHeight;

        // Позиция кнопки (например, 5% от высоты и 10% от ширины)
        openPanelButton.style.top = `${windowHeight * 0.005}px`; // 5% от высоты окна
        openPanelButton.style.right = `${windowWidth * 0.2}px`; // 20% от ширины окна
    };

    // Устанавливаем положение кнопки сразу
    updateButtonPosition();

    // Обновляем положение кнопки при изменении размеров окна
    window.addEventListener('resize', updateButtonPosition);
});


//----------------------- Функции для скрытия смайлов ---------------//
function hideEmotesForChannel(node) {
    const emotes = node.querySelectorAll('.chat-line__message img, .chat-line__message .emote, .chat-line__message .bttv-emote, .chat-line__message .seventv-emote');
    emotes.forEach(emote => {
        const emoteUrl = emote.src;
        const emoteAlt = emote.getAttribute('alt');
        if (isEmoteFromBlockedChannel(emoteUrl, emoteAlt)) {
            emote.style.display = 'none';
        }
    });
}

function isEmoteFromBlockedChannel(emoteUrl, emoteAlt) {
    return blockedChannels.some(channel => {
        if (channel.platform === 'TwitchChannel') {
            return emoteAlt.startsWith(channel.name);
        } else if (channel.platform === '7tv' || channel.platform === 'bttTV') {
            return emoteUrl.includes(channel.name);
        }
        return false;
    });
}

const observer = new MutationObserver(mutations => {
    mutations.forEach(mutation => {
        mutation.addedNodes.forEach(node => {
            if (node.nodeType === 1) hideEmotesForChannel(node);
        });
    });
});



//--------------- Функция ожидания появления контейнера чата для отслеживания изменений -----------//
function waitForChatContainerAndObserve() {
    const chatContainer = document.querySelector('.chat-scrollable-area__message-container');
    if (chatContainer) {
        // Запуск наблюдателя, если контейнер чата найден
        observer.observe(chatContainer, { childList: true, subtree: true });
    } else {
        // Повторная попытка через 10 миллисекунд, если контейнер не найден
        setTimeout(waitForChatContainerAndObserve, 1);
    }
}

waitForChatContainerAndObserve();

// --------------- Периодическая проверка скрыта ли эмодзи, которые могли быть перерисованы ----------------//
setInterval(() => {
    const chatContainer = document.querySelector('.chat-scrollable-area__message-container');
    if (chatContainer) {
        const nodes = chatContainer.querySelectorAll('.chat-line__message');
        nodes.forEach(node => hideEmotesForChannel(node));
    }
}, 1); // Проверка каждую секунду //



//----------------- Анимация сворачивания панели-------------------------//
function openPanel() {
    isPanelVisible = true;
    GM_setValue('isPanelVisible', isPanelVisible);
    controlPanel.style.display = 'block'; // Делаем панель видимой
    setTimeout(() => {
        controlPanel.style.height = '470px'; // Плавно увеличиваем высоту //
    }, 0); // Устанавливаем высоту с задержкой для работы анимации //
}

//-------------------------------- Управление высотой панели ---------------//
function closePanel() {
    isPanelVisible = false;
    GM_setValue('isPanelVisible', isPanelVisible);
    controlPanel.style.height = '0px'; // Плавно уменьшаем высоту
    setTimeout(() => {
        if (!isPanelVisible) controlPanel.style.display = 'none'; // Полностью скрываем после завершения анимации
    }, 150); // Таймер соответствует времени анимации //
}

openPanelButton.onclick = () => {
    if (isPanelVisible) {
        closePanel();
    } else {
        openPanel();
    }
};


if (isPanelVisible) {
    controlPanel.style.display = 'block';
    controlPanel.style.height = '470px'; // Высота открытой панели //
} else {
    controlPanel.style.display = 'block'; // Оставляем display: block для анимации //
    controlPanel.style.height = '0px'; // Высота скрытой панели //
}


    if (isPanelVisible) controlPanel.style.display = 'block';
    else controlPanel.style.display = 'none';

    updateChannelList();
    updateCounter();


//============ Контекстное меню пкм кнопка Bblock Emote =========================//
const ContextMenuManager = {
    menu: null, // Ссылка на меню

    //--------- Создать контекстное меню ------------//
    createMenu(event, emotePrefix, platform, emoteName) {
        this.removeMenu(); // Удалить существующее меню, если есть

        // Создание нового контекстного меню
        const menu = document.createElement('div'); // Элемент меню
        menu.className = 'custom-context-menu'; // Класс для меню
        menu.style.position = 'absolute'; // Позиционирование
        menu.style.top = `${event.pageY}px`; // Позиция по вертикали
        menu.style.left = `${event.pageX}px`; // Позиция по горизонтали
        menu.style.background = '#4c2a5e'; // Цвет фона
        menu.style.border = '1px solid #ccc'; // Граница меню
        menu.style.padding = '5px'; // Отступы
        menu.style.zIndex = 10001; // Слой
        menu.style.cursor = 'pointer'; // Курсор
        menu.innerText = `Block Emote (${emoteName})`; // Текст меню

        document.body.appendChild(menu); // Добавление в тело документа
        this.menu = menu; // Сохранение ссылки на меню

        //------ Обработчик клика на меню -------------//
        menu.addEventListener('click', () => {
            this.blockEmote(emotePrefix, platform, emoteName); // Блокировка эмотикона при клике
        });

        //---------- Удалить меню при клике в любое другое место --------------------//
        document.addEventListener('click', () => this.removeMenu(), { once: true }); // Удаление меню при клике в любом месте
    },

    //---------- Удалить контекстное меню ------------//
    removeMenu() {
        if (this.menu) { // Если меню существует
            this.menu.remove(); // Удалить меню
            this.menu = null; // Обнулить ссылку на меню
        }
    },

    //--------------- Логика блокировки эмодзи ----------------//
    blockEmote(emotePrefix, platform, emoteName) {
        const currentDateTime = new Date().toLocaleString(); // Текущая дата и время
        // Проверка, не заблокирован ли уже канал с таким префиксом и платформой
        if (!blockedChannels.some(channel => channel.name === emotePrefix && channel.platform === platform)) {
            // Добавляем канал в список заблокированных
            blockedChannels.push({
                name: emotePrefix,
                platform: platform,
                emoteName: emoteName,
                blockedAt: currentDateTime // Добавление даты и времени блокировки
            });
            // Сохраняем в localStorage
            GM_setValue("blockedChannels", blockedChannels);
            // Обновляем список каналов и счетчик
            updateChannelList();
            updateCounter();
        }
        this.removeMenu();
    }
};


//-------------- Основной обработчик контекстного меню ---------------------//
document.addEventListener('contextmenu', (event) => {
    const target = event.target; // Цель клика
    if (target.tagName === 'IMG' && target.closest('.chat-line__message')) { // Проверка, что это изображение эмотикона
        event.preventDefault(); // Отключаем стандартное меню

        const emoteUrl = target.src; // URL эмотикона
        const emoteAlt = target.getAttribute('alt'); // Атрибут alt для имени эмотикона
        let emotePrefix = ''; // Префикс эмотикона
        let platform = ''; // Платформа эмотикона
        let emoteName = emoteAlt; // Имя эмотикона

        // Определяем платформу и префикс эмодзи по URL
        if (emoteUrl.includes('7tv.app')) {
            emotePrefix = emoteUrl; // Префикс для 7tv
            platform = '7tv'; // Платформа 7tv
        } else if (emoteUrl.includes('betterttv.net')) {
            emotePrefix = emoteUrl; // Префикс для bttv
            platform = 'bttTV'; // Платформа bttv
        } else if (emoteAlt) {
            const channelPrefix = emoteAlt.split(/[^a-zA-Z0-9]/)[0]; // Префикс канала для Twitch
            emotePrefix = channelPrefix;
            platform = 'TwitchChannel'; // Платформа Twitch
        }

        // Создание меню, если есть префикс и платформа
        if (emotePrefix && platform) {
            ContextMenuManager.createMenu(event, emotePrefix, platform, emoteName); // Вызов метода создания меню
        }
    }
});




     openPanelButton.onclick = () => {
    isPanelOpen = !isPanelOpen;

    // Переключаем фоновый цвет кнопки
    openPanelButton.style.background = isPanelOpen ? '#4c2a5e' : '#4c2a5e'; // Цвет кнопки для каждого состояния
    // Перемещаем переключатель
    switchCircle.style.transform = isPanelOpen ? 'translateX(20px)' : 'translateX(0)';
    // Меняем цвет фона контейнера
    switchContainer.style.backgroundColor = isPanelOpen ? '#bda3d7' : '#ccb8eb5c';

    // Переключаем состояние панели
    if (isPanelOpen) {
        openPanel(); // Разворачиваем панель
    } else {
        closePanel(); // Сворачиваем панель
    }

    // Сохраняем состояние панели в localStorage
    GM_setValue('isPanelOpen', isPanelOpen);
};

console.log(getComputedStyle(controlPanel).display);
console.log("[DEBUG] Creating control panel...");
console.log("[DEBUG] Adding button...");
console.log("[DEBUG] Updating channel list...");

function showEmoteForChannel(channel) {
    const chatContainer = document.querySelector('.chat-scrollable-area__message-container');
    if (chatContainer) {
        const emotes = chatContainer.querySelectorAll('.chat-line__message img, .chat-line__message .emote, .chat-line__message .bttv-emote, .chat-line__message .seventv-emote');
        emotes.forEach(emote => {
            const emoteUrl = emote.src;
            const emoteAlt = emote.getAttribute('alt');
            if (channel.platform === 'TwitchChannel' && emoteAlt.startsWith(channel.name)) {
                emote.style.display = ''; // Сбросить стиль display для отображения смайлов
            } else if ((channel.platform === '7tv' || channel.platform === 'bttTV') && emoteUrl.includes(channel.name)) {
                emote.style.display = ''; // Сбросить стиль display для отображения смайлов
            }
        });
    }
}



//============== минипанель с кнопками сортировка по категориям =================//
const sortContainer = document.createElement('div');
sortContainer.style.display = 'flex';
sortContainer.style.justifyContent = 'space-between';
sortContainer.style.backgroundColor = 'rgb(89 51 114)';
sortContainer.style.padding = '5px';
sortContainer.style.marginBottom = '37px';
sortContainer.style.position = 'relative';
sortContainer.style.top = '57px';
sortContainer.style.borderRadius = '6px 6px 0 0'; // Закругление только верхних углов
sortContainer.style.border = '1px solid rgb(255, 255, 255)';
sortContainer.style.boxShadow = 'rgb(25 19 28 / 85%) 0px 15px 6px 0px'; // Использование RGBA для прозрачности
sortContainer.style.zIndex = 'inherit'; // Наследует z-index от родителя

// Функция для сортировки списка
function sortBlockedChannels(criteria, order) {
    blockedChannels.sort((a, b) => {
        let comparison = 0;
        if (criteria === 'name') {
            comparison = a.emoteName.localeCompare(b.emoteName);
        } else if (criteria === 'platform') {
            comparison = a.platform.localeCompare(b.platform);
        } else if (criteria === 'date') {
            comparison = new Date(a.date) - new Date(b.date);
        }
        return order === 'asc' ? comparison : -comparison;
    });
    updateChannelList();
}

// Текущий порядок сортировки
let currentSortOrder = {
    name: 'asc',
    platform: 'asc',
    date: 'asc'
};

// Кнопки сортировки по имени, платформе и дате
const sortByNameButton = document.createElement('button');
sortByNameButton.innerHTML = 'Name ▲';
sortByNameButton.style.cursor = 'pointer';
sortByNameButton.onclick = () => {
    const order = currentSortOrder.name === 'asc' ? 'desc' : 'asc';
    currentSortOrder.name = order;
    sortByNameButton.innerHTML = `Name ${order === 'asc' ? '▲' : '▼'}`; // Переключение стрелочки
    sortBlockedChannels('name', order);
};
sortContainer.appendChild(sortByNameButton);

const sortByPlatformButton = document.createElement('button');
sortByPlatformButton.innerHTML = 'Platform ▲';
sortByPlatformButton.style.cursor = 'pointer';
sortByPlatformButton.onclick = () => {
    const order = currentSortOrder.platform === 'asc' ? 'desc' : 'asc';
    currentSortOrder.platform = order;
    sortByPlatformButton.innerHTML = `Platform ${order === 'asc' ? '▲' : '▼'}`;
    sortBlockedChannels('platform', order);
};
sortContainer.appendChild(sortByPlatformButton);

const sortByDateButton = document.createElement('button');
sortByDateButton.innerHTML = 'Date ▲';
sortByDateButton.style.cursor = 'pointer';
sortByDateButton.onclick = () => {
    const order = currentSortOrder.date === 'asc' ? 'desc' : 'asc';
    currentSortOrder.date = order;
    sortByDateButton.innerHTML = `Date ${order === 'asc' ? '▲' : '▼'}`;
    sortBlockedChannels('date', order);
};
sortContainer.appendChild(sortByDateButton);

// Добавляем контейнер сортировки в панель управления
controlPanel.insertBefore(sortContainer, title);

// Стиль кнопок
const buttons = [addButton, clearAllButton, exportButton, importButton, unblockAllButton, blockAllButton];
buttons.forEach(button => {
    button.onmouseover = function() {
        button.style.background = '-webkit-linear-gradient(135deg, #443157 0%,rgb(90, 69, 122) 56%, #443157 98%, #443157 100%)'; // Изменение фона при наведении
    };
    button.onmouseout = function() {
        button.style.background = buttonColor; // Возвращаем исходный цвет
    };
});

})();