您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
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; // Возвращаем исходный цвет }; }); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址