// ==UserScript==
// @name Control Emotes Panel - iOS 2.1.0.
// @version 2.1.0
// @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 = '425px'; // Высота панели
controlPanel.style.backgroundColor = '#5c5065'; // Цвет фона панели
controlPanel.style.background = 'linear-gradient(180deg, hsla(265, 53%, 29%, 1) 0%, hsla(24, 93%, 73%, 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 channels';
title.style.margin = '0 0 10px 0';
title.style.color = '#fff';
controlPanel.appendChild(title);
//--------------- Список заблокированных каналов ------------------//
const list = document.createElement('ul');
list.id = 'blockedChannelsList';
list.style.listStyle = 'none';
list.style.padding = '0';
list.style.margin = '0 0 10px 0';
list.style.maxHeight = '280px';
list.style.height = '245px';
list.style.overflowY = 'auto';
list.style.border = '1px solid #ddd';
list.style.borderRadius = '4px';
list.style.maxHeight = '290px'; // Установите максимальную высоту для списка
list.style.overflowY = 'auto'; // Включите вертикальную прокрутку
// Добавление тени
list.style.boxShadow = 'rgb(47 36 53) -1px 11px 11px 6px 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';
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';
item.style.justifyContent = 'space-between';
item.style.padding = '5px';
item.style.borderBottom = '1px solid #eee';
const channelName = document.createElement('span');
if (channel.platform === 'TwitchChannel') {
const prefix = channel.name.split(/[^a-zA-Z0-9]/)[0];
channelName.innerText = `Twitch > emote ${channel.name} prefix <${prefix}>`;
} else {
const img = document.querySelector(`img[src*="${channel.name}"]`);
const emoteName = img ? img.alt : channel.name.split('/').slice(-2, -1)[0];
channelName.innerText = `${channel.platform} > ${emoteName}`;
}
item.appendChild(channelName);
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 });
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 = '10px'; // Отступ сверху для контейнера кнопок
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)) {
blockedChannels = Array.from(new Set([...blockedChannels, ...importedChannels]));
GM_setValue("blockedChannels", blockedChannels);
updateChannelList();
updateCounter();
} else {
alert('Invalid file format!');
}
} catch {
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 = '#fff';
counter.style.color = '#4c2a5e';
counter.style.border = '3px solid rgb(76, 42, 94)'; // Обновленный стиль границы
counter.style.borderRadius = '8px';
counter.style.padding = '5px 10px';
counter.style.marginLeft = '6px';
counter.style.fontWeight = 'bold';
counter.style.top = '-358px'; // Расположение сверху
counter.style.right = '352px'; // Расположение справа
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();
const ContextMenuManager = {
menu: null,
//--------- Создать контекстное меню ------------//
createMenu(event, emotePrefix, platform) {
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 (${emotePrefix})`;
document.body.appendChild(menu);
this.menu = menu;
//------ Обработчик клика на меню -------------//
menu.addEventListener('click', () => {
this.blockEmote(emotePrefix, platform);
});
//---------- Удалить меню при клике в любое другое место --------------------//
document.addEventListener('click', () => this.removeMenu(), { once: true });
},
//---------- Удалить контекстное меню ------------//
removeMenu() {
if (this.menu) {
this.menu.remove();
this.menu = null;
}
},
//--------------- Логика блокировки эмодзи ----------------//
blockEmote(emotePrefix, platform) {
if (!blockedChannels.some(channel => channel.name === emotePrefix && channel.platform === platform)) {
blockedChannels.push({ name: emotePrefix, platform: platform });
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;
const emoteAlt = target.getAttribute('alt');
let emotePrefix = '';
let platform = '';
if (emoteUrl.includes('7tv.app')) {
emotePrefix = emoteUrl;
platform = '7tv';
} else if (emoteUrl.includes('betterttv.net')) {
emotePrefix = emoteUrl;
platform = 'bttTV';
} else if (emoteAlt) {
const channelPrefix = emoteAlt.split(/[^a-zA-Z0-9]/)[0];
emotePrefix = channelPrefix;
platform = 'TwitchChannel';
}
if (emotePrefix && platform) {
ContextMenuManager.createMenu(event, emotePrefix, platform);
}
}
});
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 для отображения смайлов
}
});
}
}
})();