Switcher Stream Channel 1.10.16

Replace video feed with specified channel's video stream and provide draggable control panel functionality

目前為 2025-03-22 提交的版本,檢視 最新版本

// ==UserScript==
// @name         Switcher Stream Channel 1.10.16
// @namespace    http://tampermonkey.net/
// @version      1.10.16
// @description  Replace video feed with specified channel's video stream and provide draggable control panel functionality
// @author       Gullampis810
// @license      MIT
// @match        https://www.twitch.tv/*
// @icon         https://github.com/sopernik566/icons/blob/main/switcher%20player%20icon.png?raw=true
// @run-at       document-end
// ==/UserScript==

(function () {
    'use strict';

    // Хранилище данных
    const state = {
        channelName: 'tapa_tapa_mateo',
        favoriteChannels: JSON.parse(localStorage.getItem('favoriteChannels')) || [],
        channelHistory: JSON.parse(localStorage.getItem('channelHistory')) || [],
        panelColor: localStorage.getItem('panelColor') || '#333',
        buttonColor: localStorage.getItem('buttonColor') || '#2b675f',
        panelPosition: JSON.parse(localStorage.getItem('panelPosition')) || { top: '8px', left: '10px' },
        isPanelHidden: false
    };

    // Сортировка массивов по алфавиту при инициализации
    state.favoriteChannels.sort((a, b) => a.localeCompare(b));
    state.channelHistory.sort((a, b) => a.localeCompare(b));

    // Инициализация UI
    const panel = createControlPanel();
    const toggleButton = createToggleButton();
    document.body.appendChild(panel);
    document.body.appendChild(toggleButton);

    // Настройка функционала
    setPanelPosition(panel, state.panelPosition);
    enableDrag(panel);
    window.addEventListener('load', loadStream);

    // Создание панели управления
    function createControlPanel() {
        const panel = document.createElement('div');
        panel.className = 'switcher-panel';
        Object.assign(panel.style, {
            position: 'fixed',
            width: '322px',
            padding: '10px',
            backgroundColor: state.panelColor,
            color: '#fff',
            border: '2px solid #ffffff',
            borderRadius: '5px',
            zIndex: '9999',
            transition: 'opacity 0.3s ease, visibility 0s linear 0.3s',
            cursor: 'move'
        });

        const content = document.createElement('div');
        content.className = 'panel-content';

        const header = document.createElement('div');
        header.style.display = 'flex';
        header.style.justifyContent = 'space-between';
        header.style.alignItems = 'center';

        const title = createTitle('Switcher Channel');
        const hideBtn = document.createElement('button');
        hideBtn.textContent = '×';
        hideBtn.style.cssText = `
            background: none;
            border: none;
            color: #fff;
            font-size: 20px;
            cursor: pointer;
            padding: 0 10px;
        `;

        hideBtn.addEventListener('click', togglePanel);

        header.append(title, hideBtn);

        content.append(
            createChannelInput(),
            createButton('Play Channel', loadInputChannel, 'play-btn'),
            createSelect(state.favoriteChannels, 'Favorites:', 'favorites-select'),
            createSelect(state.channelHistory, 'History:', 'history-select'),
            createButton('Play Selected', loadSelectedChannel, 'play-selected-btn'),
            createButton('Add to Favorites', addChannelToFavorites, 'add-fav-btn'),
            createButton('Remove from Favorites', removeChannelFromFavorites, 'remove-fav-btn'),
            createButton('Clear History', clearHistory, 'clear-history-btn'),
            createColorPicker('Panel Color:', 'panel-color-picker', updatePanelColor),
            createColorPicker('Button Color:', 'button-color-picker', updateButtonColor)
        );

        panel.append(header, content);
        return panel;
    }

    // Кнопка переключения видимости с подсказкой
    function createToggleButton() {
        const button = document.createElement('button');
        button.className = 'toggle-visibility';
        Object.assign(button.style, {
            position: 'fixed',
            top: '9px',
            left: '28%',
            width: '44px',
            height: '39px',
            backgroundColor: state.buttonColor,
            borderRadius: '10%',
            border: 'none',
            cursor: 'pointer',
            zIndex: '10000',
            display: 'flex',
            alignItems: 'center',
            justifyContent: 'center'
        });

        const img = document.createElement('img');
        img.src = 'https://raw.githubusercontent.com/sopernik566/icons/4986e623628f56c95bd45004d6794820d874266d/eye_show.svg';
        img.alt = 'Toggle visibility';
        img.style.cssText = 'width: 25px; height: 25px;';
        button.appendChild(img);

        // Создаем элемент для подсказки
        const tooltip = document.createElement('span');
        tooltip.textContent = 'Show  panel Switcher with channels ';
        tooltip.style.cssText = `
            visibility: hidden;
            background-color: #ededed ;
            color: #0c0e0e;
            text-align: center;
            border-radius: 5px;
            padding: 5px;
            position: absolute;
            z-index: 10001;
            top: 50px;
            left: 50%;
            transform: translateX(-50%);
            white-space: nowrap;
            font-size: 12px;
        `;
        button.appendChild(tooltip);

        // Показываем/скрываем подсказку при наведении
        button.addEventListener('mouseover', () => {
            tooltip.style.visibility = 'visible';
            button.style.backgroundColor = '#3aa39b';
        });
        button.addEventListener('mouseout', () => {
            tooltip.style.visibility = 'hidden';
            button.style.backgroundColor = '#2b675f';
        });

        button.addEventListener('click', togglePanelVisibility);

        return button;
    }

    // Элементы интерфейса
    function createTitle(text) {
        const title = document.createElement('h3');
        title.textContent = text;
        title.style.margin = '0';
        return title;
    }

    function createChannelInput() {
        const input = document.createElement('input');
        input.type = 'text';
        input.placeholder = 'type channel name';
        input.style.cssText = `
            width: 100%;
            margin: 5px 0;
            padding: 8px;
            border-radius: 4px;
            border: 1px solid #fff;
            background-color: #1a3c34;
            color: #fff;
        `;
        input.addEventListener('input', (e) => state.channelName = e.target.value.trim());
        return input;
    }

    function createSelect(options, labelText, className) {
        const container = document.createElement('div');
        container.style.margin = '5px 0';

        const label = document.createElement('label');
        label.textContent = labelText;
        label.style.display = 'block';
        label.style.marginBottom = '2px';

        const select = document.createElement('select');
        select.className = className;
        select.style.cssText = `
            width: 100%;
            padding: 8px;
            border-radius: 4px;
            background-color: #1a3c34;
            color: #fff;
            border: 1px solid #fff;
        `;
        updateOptions(select, options);
        select.addEventListener('change', () => state.channelName = select.value);

        container.append(label, select);
        return container;
    }

    function createButton(text, onClick, className) {
        const button = document.createElement('button');
        button.textContent = text;
        button.className = className;
        button.style.cssText = `
            width: 100%;
            margin: 5px 0;
            padding: 10px;
            background-color: ${state.buttonColor};
            color: #fff;
            border: none;
            border-radius: 5px;
            cursor: pointer;
            transition: all 0.2s ease;
        `;
        button.addEventListener('click', onClick);
        button.addEventListener('mouseover', () => {
            button.style.backgroundColor = lightenColor(state.buttonColor, 20);
            button.style.boxShadow = '0 4px 6px rgba(0,0,0,0.2)';
        });
        button.addEventListener('mouseout', () => {
            button.style.backgroundColor = state.buttonColor;
            button.style.boxShadow = 'none';
        });
        return button;
    }

    function createColorPicker(labelText, className, onChange) {
        const container = document.createElement('div');
        container.style.margin = '5px 0';

        const label = document.createElement('label');
        label.textContent = labelText;
        label.style.display = 'block';
        label.style.marginBottom = '2px';

        const picker = document.createElement('input');
        picker.type = 'color';
        picker.className = className;
        picker.value = labelText.includes('Panel') ? state.panelColor : state.buttonColor;
        picker.style.width = '100%';
        picker.addEventListener('input', onChange);

        container.append(label, picker);
        return container;
    }

    // Функции обновления цветов
    function updatePanelColor(e) {
        state.panelColor = e.target.value;
        panel.style.backgroundColor = state.panelColor;
        localStorage.setItem('panelColor', state.panelColor);
    }

    function updateButtonColor(e) {
        state.buttonColor = e.target.value;
        localStorage.setItem('buttonColor', state.buttonColor);
        updatePanelButtonsColor(); // Обновляем только кнопки внутри панели
    }

    function updatePanelButtonsColor() {
        // Обновляем цвет только для кнопок внутри панели
        panel.querySelectorAll('button').forEach(button => {
            button.style.backgroundColor = state.buttonColor;
        });
    }

    // Вспомогательная функция для осветления цвета
    function lightenColor(hex, percent) {
        const num = parseInt(hex.replace('#', ''), 16);
        const r = Math.min(255, (num >> 16) + (255 * percent / 100));
        const g = Math.min(255, ((num >> 8) & 0x00FF) + (255 * percent / 100));
        const b = Math.min(255, (num & 0x0000FF) + (255 * percent / 100));
        return `#${Math.round(r).toString(16).padStart(2, '0')}${Math.round(g).toString(16).padStart(2, '0')}${Math.round(b).toString(16).padStart(2, '0')}`;
    }

    // Функциональность
    function togglePanel() {
        state.isPanelHidden = !state.isPanelHidden;
        panel.style.opacity = state.isPanelHidden ? '0' : '1';
        panel.style.visibility = state.isPanelHidden ? 'hidden' : 'visible';
        panel.style.transition = state.isPanelHidden
            ? 'opacity 0.3s ease, visibility 0s linear 0.3s'
            : 'opacity 0.3s ease, visibility 0s linear 0s';
    }

    function togglePanelVisibility() {
        const img = toggleButton.querySelector('img');
        const isHidden = panel.style.visibility === 'hidden' || panel.style.display === 'none';

        if (isHidden) {
            panel.style.display = 'block';
            panel.style.opacity = '1';
            panel.style.visibility = 'visible';
            state.isPanelHidden = false;
            img.src = 'https://raw.githubusercontent.com/sopernik566/icons/4986e623628f56c95bd45004d6794820d874266d/eye_show.svg';
        } else {
            panel.style.opacity = '0';
            panel.style.visibility = 'hidden';
            state.isPanelHidden = true;
            img.src = 'https://raw.githubusercontent.com/sopernik566/icons/4986e623628f56c95bd45004d6794820d874266d/eye_hidden_1024.svg';
        }
    }

    function loadInputChannel() {
        if (state.channelName) {
            loadStream();
            addChannelToHistory(state.channelName);
        } else {
            alert('Type a channel name.');
        }
    }

    function loadSelectedChannel() {
        if (state.channelName) {
            loadStream();
            addChannelToHistory(state.channelName);
        } else {
            alert('Select a channel.');
        }
    }

    function addChannelToFavorites() {
        if (state.channelName && !state.favoriteChannels.includes(state.channelName)) {
            state.favoriteChannels.push(state.channelName);
            state.favoriteChannels.sort((a, b) => a.localeCompare(b));
            localStorage.setItem('favoriteChannels', JSON.stringify(state.favoriteChannels));
            alert(`Added ${state.channelName} to favorites!`);
            updateOptions(document.querySelector('.favorites-select'), state.favoriteChannels);
        } else if (!state.channelName) {
            alert('Please enter a channel name to add to favorites.');
        }
    }

    function removeChannelFromFavorites() {
        if (!state.channelName) {
            alert('Please select a channel to remove from favorites.');
            return;
        }
        if (!state.favoriteChannels.includes(state.channelName)) {
            alert(`${state.channelName} is not in your favorites.`);
            return;
        }
        state.favoriteChannels = state.favoriteChannels.filter(ch => ch !== state.channelName);
        state.favoriteChannels.sort((a, b) => a.localeCompare(b));
        localStorage.setItem('favoriteChannels', JSON.stringify(state.favoriteChannels));
        updateOptions(document.querySelector('.favorites-select'), state.favoriteChannels);
    }

    function clearHistory() {
        state.channelHistory = [];
        localStorage.setItem('channelHistory', JSON.stringify(state.channelHistory));
        updateOptions(document.querySelector('.history-select'), state.channelHistory);
    }

    function loadStream() {
        setTimeout(() => {
            const player = document.querySelector('.video-player__container');
            if (player) {
                player.innerHTML = '';
                const iframe = document.createElement('iframe');
                iframe.src = `https://player.twitch.tv/?channel=${state.channelName}&parent=twitch.tv&quality=1080p&muted=false`;
                iframe.style.cssText = 'width: 100%; height: 100%;';
                iframe.allowFullscreen = true;
                player.appendChild(iframe);
            }
        }, 2000);
    }

    function addChannelToHistory(channel) {
        if (channel && !state.channelHistory.includes(channel)) {
            state.channelHistory.push(channel);
            state.channelHistory.sort((a, b) => a.localeCompare(b));
            localStorage.setItem('channelHistory', JSON.stringify(state.channelHistory));
            updateOptions(document.querySelector('.history-select'), state.channelHistory);
        }
    }

    function updateOptions(select, options) {
        select.innerHTML = '';
        options.forEach(option => {
            const opt = document.createElement('option');
            opt.value = option;
            opt.text = option;
            select.appendChild(opt);
        });
    }

    function enableDrag(element) {
        let isDragging = false, offsetX, offsetY;
        element.addEventListener('mousedown', (e) => {
            if (e.target.tagName !== 'BUTTON' && e.target.tagName !== 'INPUT' && e.target.tagName !== 'SELECT') {
                isDragging = true;
                offsetX = e.clientX - element.getBoundingClientRect().left;
                offsetY = e.clientY - element.getBoundingClientRect().top;
                element.style.transition = 'none';
            }
        });

        document.addEventListener('mousemove', (e) => {
            if (isDragging) {
                const newLeft = e.clientX - offsetX;
                const newTop = e.clientY - offsetY;
                element.style.left = `${newLeft}px`;
                element.style.top = `${newTop}px`;
                localStorage.setItem('panelPosition', JSON.stringify({ top: `${newTop}px`, left: `${newLeft}px` }));
            }
        });

        document.addEventListener('mouseup', () => {
            isDragging = false;
            element.style.transition = 'opacity 0.3s ease, visibility 0s linear 0.3s';
        });
    }

    function setPanelPosition(element, position) {
        element.style.top = position.top;
        element.style.left = position.left;
    }
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址