- // ==UserScript==
- // @name Switcher Stream Channel 1.10.17
- // @namespace http://tampermonkey.net/
- // @version 1.10.17
- // @license MIT
- // @description Replace video feed with specified channel's video stream and provide draggable control panel functionality
- // @author Gullampis810
- // @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') || 'rgba(255, 255, 255, 0.15)',
- buttonColor: localStorage.getItem('buttonColor') || 'rgba(255, 255, 255, 0.3)',
- panelPosition: JSON.parse(localStorage.getItem('panelPosition')) || { top: '20px', left: '20px' },
- isPanelHidden: false
- };
-
- state.favoriteChannels.sort((a, b) => a.localeCompare(b));
- state.channelHistory.sort((a, b) => a.localeCompare(b));
-
- 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: '340px',
- padding: '20px',
- backgroundColor: state.panelColor,
- backdropFilter: 'blur(10px)',
- borderRadius: '20px',
- border: '1px solid rgba(255, 255, 255, 0.2)',
- boxShadow: '0 8px 32px rgba(0, 0, 0, 0.1)',
- zIndex: '9999',
- transition: 'transform 0.3s ease, opacity 0.3s ease',
- cursor: 'move'
- });
-
- const content = document.createElement('div');
-
- const header = document.createElement('div');
- Object.assign(header.style, {
- display: 'flex',
- justifyContent: 'space-between',
- alignItems: 'center',
- marginBottom: '20px',
- });
-
- const title = createTitle('Channel Switcher v1.10.17');
- const hideBtn = document.createElement('button');
- hideBtn.textContent = '×';
- Object.assign(hideBtn.style, {
- width: '40px',
- height: '40px',
- border: 'none',
- borderRadius: '50%',
- fontSize: '24px',
- color: 'rgba(255, 255, 255, 0.8)',
- cursor: 'pointer',
- backdropFilter: 'blur(10px)',
- boxShadow: '0 4px 15px rgba(0, 0, 0, 0.1)',
- transition: 'all 0.2s ease',
- display: 'flex',
- justifyContent: 'center'
- });
- hideBtn.addEventListener('click', togglePanel);
- hideBtn.addEventListener('mouseover', () => hideBtn.style.backgroundColor = 'rgba(255, 255, 255, 0.3)');
- hideBtn.addEventListener('mouseout', () => hideBtn.style.backgroundColor = 'rgba(255, 255, 255, 0.2)');
-
- 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 Favorite', 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: '16px',
- left: '490px',
- width: '40px',
- height: '40px',
- backgroundColor: ' #8b008b00;',
- borderRadius: '50%',
- border: '1px solid rgba(255, 255, 255, 0.2)',
- cursor: 'pointer',
- zIndex: '10000',
- display: 'flex',
- alignItems: 'center',
- justifyContent: 'center',
- backdropFilter: 'blur(10px)',
- boxShadow: '0 4px 15px rgba(0, 0, 0, 0.1)',
- transition: 'all 0.2s ease'
- });
-
- 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: 28px; height: 28px; filter: brightness(100);';
-
- button.appendChild(img);
- button.addEventListener('click', togglePanelVisibility);
- button.addEventListener('mouseover', () => button.style.backgroundColor = 'rgba(255, 255, 255, 0.3)');
- button.addEventListener('mouseout', () => button.style.backgroundColor = 'rgba(255, 255, 255, 0.2)');
-
- return button;
- }
-
- function createTitle(text) {
- const title = document.createElement('h3');
- title.textContent = text;
- Object.assign(title.style, {
- margin: '0',
- fontSize: '20px',
- fontWeight: '500',
- color: 'rgba(255, 255, 255, 0.9)'
- });
- return title;
- }
-
- function createChannelInput() {
- const input = document.createElement('input');
- input.type = 'text';
- input.placeholder = 'Enter channel name';
- Object.assign(input.style, {
- width: '100%',
- marginBottom: '16px',
- padding: '12px 16px',
- borderRadius: '12px',
- border: '1px solid rgba(255, 255, 255, 0.2)',
- backgroundColor: 'rgba(255, 255, 255, 0.1)',
- color: 'rgba(255, 255, 255, 0.9)',
- fontSize: '16px',
- backdropFilter: 'blur(10px)',
- boxShadow: '0 4px 15px rgba(0, 0, 0, 0.05)',
- transition: 'all 0.2s ease'
- });
- input.addEventListener('input', (e) => state.channelName = e.target.value.trim());
- input.addEventListener('focus', () => input.style.borderColor = 'rgba(255, 255, 255, 0.4)');
- input.addEventListener('blur', () => input.style.borderColor = 'rgba(255, 255, 255, 0.2)');
- return input;
- }
-
- function createSelect(options, labelText, className) {
- const container = document.createElement('div');
- container.style.marginBottom = '16px';
- container.style.position = 'relative';
-
- const label = document.createElement('label');
- label.textContent = labelText;
- Object.assign(label.style, {
- display: 'block',
- marginBottom: '4px',
- fontSize: '12px',
- color: 'rgba(255, 255, 255, 0.7)',
- fontWeight: '500'
- });
-
- const selectBox = document.createElement('div');
- Object.assign(selectBox.style, {
- width: '100%',
- padding: '12px 16px',
- borderRadius: '12px',
- backgroundColor: 'rgba(255, 255, 255, 0.05)',
- color: 'rgba(255, 255, 255, 0.9)',
- border: '1px solid rgba(255, 255, 255, 0.2)',
- fontSize: '16px',
- backdropFilter: 'blur(15px)',
- boxShadow: '0 4px 15px rgba(0, 0, 0, 0.05)',
- cursor: 'pointer',
- display: 'flex',
- justifyContent: 'space-between',
- alignItems: 'center'
- });
-
- const selectedText = document.createElement('span');
- selectedText.textContent = options.length ? options[0] : 'No items';
-
- const arrow = document.createElement('span');
- arrow.innerHTML = '<svg width="12" height="12" viewBox="0 0 16 16" fill="rgba(255,255,255,0.7)"><path d="M7.247 11.14 2.451 5.658C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z"/></svg>';
-
- const dropdown = document.createElement('div');
- Object.assign(dropdown.style, {
- position: 'absolute',
- top: '100%',
- left: '0',
- width: '100%',
- maxHeight: '200px',
- overflowY: 'auto',
- backgroundColor: 'rgba(255, 255, 255, 0.1)',
- borderRadius: '12px',
- border: '1px solid rgba(255, 255, 255, 0.2)',
- backdropFilter: 'blur(15px)',
- boxShadow: '0 4px 15px rgba(0, 0, 0, 0.1)',
- display: 'none',
- zIndex: '10001'
- });
-
- options.forEach(option => {
- const item = document.createElement('div');
- Object.assign(item.style, {
- padding: '10px 16px',
- color: 'rgba(255, 255, 255, 0.9)',
- cursor: 'pointer',
- transition: 'background-color 0.2s ease'
- });
- item.textContent = option;
- item.addEventListener('click', () => {
- state.channelName = option;
- selectedText.textContent = option;
- dropdown.style.display = 'none';
- });
- item.addEventListener('mouseover', () => item.style.backgroundColor = 'rgba(255, 255, 255, 0.2)');
- item.addEventListener('mouseout', () => item.style.backgroundColor = 'transparent');
- dropdown.appendChild(item);
- });
-
- selectBox.append(selectedText, arrow);
- container.append(label, selectBox, dropdown);
-
- selectBox.addEventListener('click', () => {
- dropdown.style.display = dropdown.style.display === 'block' ? 'none' : 'block';
- });
-
- document.addEventListener('click', (e) => {
- if (!container.contains(e.target)) {
- dropdown.style.display = 'none';
- }
- });
-
- return container;
- }
-
- function createButton(text, onClick, className) {
- const button = document.createElement('button');
- button.textContent = text;
- button.className = className;
- Object.assign(button.style, {
- width: '100%',
- marginBottom: '12px',
- padding: '14px 16px',
- backgroundColor: state.buttonColor,
- color: 'rgba(255, 255, 255, 0.9)',
- border: '1px solid rgba(255, 255, 255, 0.2)',
- borderRadius: '12px',
- fontSize: '16px',
- fontWeight: '500',
- cursor: 'pointer',
- backdropFilter: 'blur(10px)',
- boxShadow: '0 4px 15px rgba(0, 0, 0, 0.1)',
- transition: 'all 0.2s ease'
- });
- button.addEventListener('click', onClick);
- button.addEventListener('mouseover', () => button.style.backgroundColor = 'rgba(255, 255, 255, 0.4)');
- button.addEventListener('mouseout', () => button.style.backgroundColor = state.buttonColor);
- button.addEventListener('mousedown', () => button.style.transform = 'scale(0.98)');
- button.addEventListener('mouseup', () => button.style.transform = 'scale(1)');
- return button;
- }
-
- function createColorPicker(labelText, className, onChange) {
- const container = document.createElement('div');
- container.style.marginBottom = '16px';
-
- const label = document.createElement('label');
- label.textContent = labelText;
- Object.assign(label.style, {
- display: 'block',
- marginBottom: '4px',
- fontSize: '12px',
- color: 'rgba(255, 255, 255, 0.7)',
- fontWeight: '500'
- });
-
- const picker = document.createElement('input');
- picker.type = 'color';
- picker.className = className;
- picker.value = labelText.includes('Panel') ? rgbaToHex(state.panelColor) : rgbaToHex(state.buttonColor);
- Object.assign(picker.style, {
- width: '100%',
- height: '48px',
- padding: '0',
- border: '1px solid rgba(255, 255, 255, 0.2)',
- borderRadius: '12px',
- cursor: 'pointer',
- backdropFilter: 'blur(10px)',
- boxShadow: '0 4px 15px rgba(0, 0, 0, 0.05)',
- transition: 'all 0.2s ease',
- background: '#8b008b00',
- });
- picker.addEventListener('input', onChange);
-
- container.append(label, picker);
- return container;
- }
-
- function rgbaToHex(rgba) {
- const match = rgba.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*[\d.]+)?\)/);
- if (!match) return rgba;
- const r = parseInt(match[1]).toString(16).padStart(2, '0');
- const g = parseInt(match[2]).toString(16).padStart(2, '0');
- const b = parseInt(match[3]).toString(16).padStart(2, '0');
- return `#${r}${g}${b}`;
- }
-
- function updatePanelColor(e) {
- const hex = e.target.value;
- state.panelColor = `${hex}33`; // Добавляем прозрачность (0.2 в hex = 33)
- panel.style.backgroundColor = state.panelColor;
- localStorage.setItem('panelColor', state.panelColor);
- }
-
- function updateButtonColor(e) {
- const hex = e.target.value;
- state.buttonColor = `${hex}4D`; // Прозрачность 0.3 в hex = 4D
- localStorage.setItem('buttonColor', state.buttonColor);
- panel.querySelectorAll('button:not(.toggle-visibility)').forEach(button => {
- button.style.backgroundColor = state.buttonColor;
- });
- }
-
- function togglePanel() {
- state.isPanelHidden = !state.isPanelHidden;
- panel.style.transform = state.isPanelHidden ? 'scale(0.95)' : 'scale(1)';
- panel.style.opacity = state.isPanelHidden ? '0' : '1';
- panel.style.pointerEvents = state.isPanelHidden ? 'none' : 'auto';
- }
-
- function togglePanelVisibility() {
- const img = toggleButton.querySelector('img');
- const isHidden = panel.style.opacity === '0' || !panel.style.opacity;
-
- if (isHidden) {
- panel.style.transform = 'scale(1)';
- panel.style.opacity = '1';
- panel.style.pointerEvents = 'auto';
- state.isPanelHidden = false;
- img.src = 'https://raw.githubusercontent.com/sopernik566/icons/4986e623628f56c95bd45004d6794820d874266d/eye_show.svg';
- } else {
- panel.style.transform = 'scale(0.95)';
- panel.style.opacity = '0';
- panel.style.pointerEvents = 'none';
- 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('Please enter a channel name.');
- }
- }
-
- function loadSelectedChannel() {
- if (state.channelName) {
- loadStream();
- addChannelToHistory(state.channelName);
- } else {
- alert('Please 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.');
- }
- }
-
- function removeChannelFromFavorites() {
- if (!state.channelName) {
- alert('Please select a channel to remove.');
- return;
- }
- if (!state.favoriteChannels.includes(state.channelName)) {
- alert(`${state.channelName} is not in 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%; border-radius: 12px;';
- 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.length ? '' : '<option>No items</option>';
- 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 = 'transform 0.3s ease, opacity 0.3s ease';
- });
- }
-
- function setPanelPosition(element, position) {
- element.style.top = position.top;
- element.style.left = position.left;
- }
- })();