Greasy Fork 支持简体中文。

Switcher Stream Channel

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

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

(function () {
    'use strict';

    let channelName = 'tapa_tapa_mateo';
    let favoriteChannels = JSON.parse(localStorage.getItem('favoriteChannels')) || [];
    let channelHistory = JSON.parse(localStorage.getItem('channelHistory')) || [];
    let panelColor = localStorage.getItem('panelColor') || '#333';
    let panelPosition = JSON.parse(localStorage.getItem('panelPosition')) || { top: '8px', left: '10px' };

    // Initialize Panel UI
    const panel = createPanel();
    document.body.appendChild(panel);

    // Set initial panel position
    setPanelPosition(panel, panelPosition);

    // Create and position toggle button
    const toggleButton = createToggleButton();
    document.body.appendChild(toggleButton);

    // Enable drag functionality for the panel
    enableDrag(panel);

    // Dynamically reposition the toggle button on window resize
    repositionToggleButton(toggleButton);

    // Reposition toggle button on resize
    window.addEventListener('resize', () => repositionToggleButton(toggleButton));

    // Stream loader
    window.addEventListener('load', loadStream);

    /** Panel and UI Elements */
    function createPanel() {
        const panel = document.createElement('div');
        Object.assign(panel.style, {
            position: 'fixed', width: '322px', padding: '10px',
            border: '2px solid #ffffff',
            backgroundColor: panelColor, color: '#fff', borderRadius: '5px', zIndex: '9999',
            display: 'flex', flexDirection: 'row-reverse', flexWrap: 'wrap', alignContent: 'stretch',
            justifyContent: 'flex-end', alignItems: 'center', transition: 'all 0.3s ease', cursor: 'move'
        });

        panel.append(
            createTitle("Switcher channel."),
            createChannelInput(),
            createButton('Play', loadInputChannel),
            createSelect(favoriteChannels, 'Select from favorites'),
            createSelect(channelHistory, 'Channel history'),
            createButton('Play selected channel', loadSelectedChannel),
            createButton('Add to Favorites', addChannelToFavorites),
            createButton('Remove from Favorites', removeChannelFromFavorites),
            createButton('Clear History', clearHistory),
            createColorPicker()
        );

        return panel;
    }

    /** Toggle Button UI */
    function createToggleButton() {
        const toggleButton = document.createElement('button');
        Object.assign(toggleButton.style, {
            position: 'fixed', // Fixed position for the toggle button
            top: '25%', right: '420px',
            transform: 'translateY(-50%)', // Center vertically
            backgroundColor: '#2b675f', color: '#fff', borderRadius: '10%',
            width: '40px', height: '40px', cursor: 'pointer', zIndex: '10000',
            display: 'flex', alignItems: 'center', justifyContent: 'center', border: 'none'
        });

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

        toggleButton.addEventListener('click', () => {
            const isHidden = panel.style.display === 'none';
            panel.style.display = isHidden ? 'block' : 'none';
            img.src = isHidden
                ? 'https://raw.githubusercontent.com/sopernik566/icons/4986e623628f56c95bd45004d6794820d874266d/eye_show.svg'
                : 'https://raw.githubusercontent.com/sopernik566/icons/4986e623628f56c95bd45004d6794820d874266d/eye_hidden_1024.svg';
        });

        toggleButton.addEventListener('mouseover', () => toggleButton.style.backgroundColor = '#3aa39b');
        toggleButton.addEventListener('mouseout', () => toggleButton.style.backgroundColor = '#2b675f');

        return toggleButton;
    }

    /** Panel Elements */
    function createTitle(text) {
        const title = document.createElement('h3');
        title.innerText = text;
        return title;
    }

    function createChannelInput() {
        const input = document.createElement('input');
        Object.assign(input.style, { width: '60%', marginRight: '5px' });
        input.type = 'text';
        input.placeholder = 'Type channel name';
        input.addEventListener('input', (e) => { channelName = e.target.value.trim(); });
        return input;
    }

    function createSelect(options, label) {
        const select = document.createElement('select');
        Object.assign(select.style, { width: '100%', marginBottom: '5px' });
        updateOptions(select, options);
        select.addEventListener('change', () => { channelName = select.value; });
        return select;
    }

    function createButton(text, onClick) {
        const button = document.createElement('button');
        button.innerText = text;
        Object.assign(button.style, {
            width: '100%', margin: '5px 0', padding: '10px', borderRadius: '5px',
            backgroundColor: '#2b675f', color: '#fff', border: 'none', cursor: 'pointer'
        });
        button.addEventListener('click', onClick);

        button.addEventListener('mouseover', () => {
            button.style.backgroundColor = '#6da7a8';
            button.style.boxShadow = '0px 4px 6px rgba(14, 15, 15, 0.5)';
        });
        button.addEventListener('mouseout', () => {
            button.style.backgroundColor = '#2b675f';
            button.style.boxShadow = 'none';
        });

        return button;
    }

    function createColorPicker() {
        const colorPicker = document.createElement('input');
        colorPicker.type = 'color';
        colorPicker.value = panelColor;
        colorPicker.style.width = '100%';
        colorPicker.addEventListener('input', (e) => {
            panel.style.backgroundColor = e.target.value;
            localStorage.setItem('panelColor', e.target.value);
        });
        return colorPicker;
    }

    /** Helper Functions */
    function repositionToggleButton(button) {
        const windowHeight = window.innerHeight;
        const buttonHeight = button.offsetHeight;

        // Ensure the button stays visible vertically
        let topPosition = parseInt(button.style.top, 10);
        if (isNaN(topPosition) || topPosition < 0) {
            topPosition = 0;
        } else if (topPosition + buttonHeight > windowHeight) {
            topPosition = windowHeight - buttonHeight;
        }

        button.style.top = `${topPosition}px`;
    }

    function loadInputChannel() {
        if (channelName) {
            loadStream();
            addChannelToHistory(channelName);
        }
    }

    function loadSelectedChannel() {
        loadStream();
        addChannelToHistory(channelName);
    }

    function addChannelToFavorites() {
        if (channelName && !favoriteChannels.includes(channelName)) {
            favoriteChannels.push(channelName);
            localStorage.setItem('favoriteChannels', JSON.stringify(favoriteChannels));
            alert(`Added ${channelName} to favorites!`);
            updateOptions(document.querySelectorAll('select')[0], favoriteChannels);
        }
    }

    function removeChannelFromFavorites() {
        favoriteChannels = favoriteChannels.filter(channel => channel !== channelName);
        localStorage.setItem('favoriteChannels', JSON.stringify(favoriteChannels));
        updateOptions(document.querySelectorAll('select')[0], favoriteChannels);
    }

    function clearHistory() {
        channelHistory = [];
        localStorage.setItem('channelHistory', JSON.stringify(channelHistory));
        updateOptions(document.querySelectorAll('select')[1], 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=${channelName}&parent=twitch.tv&quality=1080p&muted=false`;
                iframe.width = '100%';
                iframe.height = '100%';
                iframe.allowFullscreen = true;
                player.appendChild(iframe);
            }
        }, 2000);
    }

    function addChannelToHistory(channel) {
        if (!channelHistory.includes(channel)) {
            channelHistory.push(channel);
            localStorage.setItem('channelHistory', JSON.stringify(channelHistory));
            updateOptions(document.querySelectorAll('select')[1], channelHistory);
        }
    }

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

    /** Draggable Panel */
    function enableDrag(panel) {
        let isDragging = false, offsetX, offsetY;
        panel.addEventListener('mousedown', (e) => {
            isDragging = true;
            offsetX = e.clientX - panel.getBoundingClientRect().left;
            offsetY = e.clientY - panel.getBoundingClientRect().top;
            panel.style.transition = 'none';
        });
        document.addEventListener('mousemove', (e) => {
            if (isDragging) {
                const newLeft = e.clientX - offsetX;
                const newTop = e.clientY - offsetY;
                panel.style.left = `${newLeft}px`;
                panel.style.top = `${newTop}px`;

                // Save position in localStorage
                localStorage.setItem('panelPosition', JSON.stringify({ top: `${newTop}px`, left: `${newLeft}px` }));
            }
        });
        document.addEventListener('mouseup', () => {
            isDragging = false;
            panel.style.transition = 'all 0.3s ease';
        });
    }

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