lck

롤캐 숙제 바로가기+직링보조

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         lck
// @namespace    lck
// @version      2.05
// @description  롤캐 숙제 바로가기+직링보조
// @match        https://lolcast.kr/*
// @exclude      https://lolcast.kr/main/page/chat-popout.php
// @grant        GM_xmlhttpRequest
// @grant        GM_getValue
// @grant        GM_setValue
// ==/UserScript==

(function() {
    'use strict';

    // 특정 페이지에서 스크립트 실행 방지
    if (window.location.href.includes('https://lolcast.kr/main/page/chat-popout.php')) {
        return; // 스크립트 실행 중지
    }

    // 기본 설정
    const DEFAULT_SETTINGS = {
        youtube: true,
        chzzk: true,
        afreeca: true,
        flow: true,
        customInput: true, // 주소 입력창 표시 여부
        lockSettingsPanel: false // 설정 패널 잠금 여부
    };

    // 설정 불러오기
    let settings = JSON.parse(GM_getValue('settings', JSON.stringify(DEFAULT_SETTINGS)));

    // 채널 정보
    const CHANNELS = {
        youtube: {
            id: 'UCw1DsweY9b2AKGjV4kGJP1A',
            buttonLabel: '유튜브',
            color: '#FF0000',
            url: (id) => `/#/player/youtube/${id}`
        },
        chzzk: {
            buttonLabel: '치지직',
            color: '#00FFA3',
            url: () => '/#/player/chzzk/9381e7d6816e6d915a44a13c0195b202'
        },
        afreeca: {
            buttonLabel: '숲',
            color: '#2970B6',
            url: () => '/#/player/afreeca/aflol'
        },
        flow: {
            buttonLabel: 'Flow',
            color: '#6A5ACD',
            url: () => '/#/player/flow'
        }
    };

    // 유튜브 라이브 영상 ID 가져오기
    async function fetchLiveVideoId(channelId) {
        const YOUTUBE_LIVE_URL = `https://www.youtube.com/channel/${channelId}/live`;
        return new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: "GET",
                url: YOUTUBE_LIVE_URL,
                onload: function(response) {
                    const videoIdMatch = response.responseText.match(/"videoId":"([\w-]+)"/);
                    const isLiveNow = response.responseText.includes('"isLiveNow":true') || response.responseText.includes('"isLive":true');
                    const liveBroadcastContentMatch = response.responseText.match(/"liveBroadcastContent":"(\w+)"/);
                    const isLiveBroadcast = liveBroadcastContentMatch && liveBroadcastContentMatch[1] === 'live';

                    if (videoIdMatch && videoIdMatch[1] && (isLiveNow || isLiveBroadcast)) {
                        resolve(videoIdMatch[1]);
                    } else {
                        reject('No live video found.');
                    }
                },
                onerror: reject
            });
        });
    }

    // URL 분석 및 리디렉션
    function parseAndRedirect(url) {
        const youtubeShortRegex = /https:\/\/youtu\.be\/([\w-]+)/;
        const youtubeLongRegex = /https:\/\/www\.youtube\.com\/watch\?v=([\w-]+)/;
        const kickRegex = /https:\/\/kick\.com\/([\w-]+)/;
        const chzzkRegex = /https:\/\/chzzk\.naver\.com\/([\w-]+)/;
        const twitchRegex = /https:\/\/twitch\.tv\/([\w-]+)/;
        const afreecaRegex = /https:\/\/play\.sooplive\.co\.kr\/([\w-]+)/;

        if (youtubeShortRegex.test(url)) {
            const videoId = url.match(youtubeShortRegex)[1];
            window.location.href = `https://lolcast.kr/#/player/youtube/${videoId}`;
        } else if (youtubeLongRegex.test(url)) {
            const videoId = url.match(youtubeLongRegex)[1];
            window.location.href = `https://lolcast.kr/#/player/youtube/${videoId}`;
        } else if (kickRegex.test(url)) {
            const channelId = url.match(kickRegex)[1];
            window.location.href = `https://lolcast.kr/#/player/kick/${channelId}`;
        } else if (chzzkRegex.test(url)) {
            const channelId = url.match(chzzkRegex)[1];
            window.location.href = `https://lolcast.kr/#/player/chzzk/${channelId}`;
        } else if (twitchRegex.test(url)) {
            const channelId = url.match(twitchRegex)[1];
            window.location.href = `https://lolcast.kr/#/player/twitch/${channelId}`;
        } else if (afreecaRegex.test(url)) {
            const channelId = url.match(afreecaRegex)[1];
            window.location.href = `https://lolcast.kr/#/player/afreeca/${channelId}`;
        } else {
            alert('지원하지 않는 URL 형식입니다.');
        }
    }

    // 설정 패널 생성
    function createSettingsPanel() {
        const modal = document.createElement('div');
        modal.style.position = 'fixed';
        modal.style.top = '50%';
        modal.style.left = '50%';
        modal.style.transform = 'translate(-50%, -50%)';
        modal.style.backgroundColor = 'rgba(255, 255, 255, 0.95)';
        modal.style.padding = '20px';
        modal.style.borderRadius = '8px';
        modal.style.boxShadow = '0 0 10px rgba(0,0,0,0.3)';
        modal.style.zIndex = '10000';
        modal.style.display = 'none';
        modal.id = 'settingsModal';

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

        const title = document.createElement('h3');
        title.textContent = '채널 설정';
        title.style.margin = '0';

        const closeBtn = document.createElement('button');
        closeBtn.textContent = '×';
        closeBtn.style.background = 'none';
        closeBtn.style.border = 'none';
        closeBtn.style.fontSize = '20px';
        closeBtn.style.cursor = 'pointer';
        closeBtn.onclick = () => modal.style.display = 'none';

        header.appendChild(title);
        header.appendChild(closeBtn);

        const content = document.createElement('div');
        content.style.display = 'flex';
        content.style.flexDirection = 'column';
        content.style.gap = '10px';

        Object.keys(CHANNELS).forEach(channel => {
            const label = document.createElement('label');
            label.style.display = 'flex';
            label.style.alignItems = 'center';
            label.style.gap = '8px';

            const checkbox = document.createElement('input');
            checkbox.type = 'checkbox';
            checkbox.checked = settings[channel];
            checkbox.onchange = (e) => {
                settings[channel] = e.target.checked;
                GM_setValue('settings', JSON.stringify(settings));
                refreshControlPanel();
            };

            const text = document.createTextNode(CHANNELS[channel].buttonLabel);

            label.appendChild(checkbox);
            label.appendChild(text);
            content.appendChild(label);
        });

        // 주소 입력창 표시 여부 설정 추가
        const customInputLabel = document.createElement('label');
        customInputLabel.style.display = 'flex';
        customInputLabel.style.alignItems = 'center';
        customInputLabel.style.gap = '8px';

        const customInputCheckbox = document.createElement('input');
        customInputCheckbox.type = 'checkbox';
        customInputCheckbox.checked = settings.customInput;
        customInputCheckbox.onchange = (e) => {
            settings.customInput = e.target.checked;
            GM_setValue('settings', JSON.stringify(settings));
            refreshControlPanel();
        };

        const customInputText = document.createTextNode('주소입력창');

        customInputLabel.appendChild(customInputCheckbox);
        customInputLabel.appendChild(customInputText);
        content.appendChild(customInputLabel);

        // 잠금 설정 추가
        const lockLabel = document.createElement('label');
        lockLabel.style.display = 'flex';
        lockLabel.style.alignItems = 'center';
        lockLabel.style.gap = '8px';

        const lockCheckbox = document.createElement('input');
        lockCheckbox.type = 'checkbox';
        lockCheckbox.checked = settings.lockSettingsPanel;
        lockCheckbox.onchange = (e) => {
            settings.lockSettingsPanel = e.target.checked;
            GM_setValue('settings', JSON.stringify(settings));
        };

        const lockText = document.createTextNode('위치 잠금');

        lockLabel.appendChild(lockCheckbox);
        lockLabel.appendChild(lockText);
        content.appendChild(lockLabel);

        modal.appendChild(header);
        modal.appendChild(content);
        document.body.appendChild(modal);

        // 드래그 기능
        let isDragging = false;
        let offsetX, offsetY;

        header.addEventListener('mousedown', (e) => {
            if (!settings.lockSettingsPanel) { // 잠금 상태가 아닐 때만 드래그 가능
                isDragging = true;
                offsetX = e.clientX - modal.offsetLeft;
                offsetY = e.clientY - modal.offsetTop;
            }
        });

        document.addEventListener('mousemove', (e) => {
            if (isDragging && !settings.lockSettingsPanel) { // 잠금 상태가 아닐 때만 드래그 가능
                modal.style.left = `${e.clientX - offsetX}px`;
                modal.style.top = `${e.clientY - offsetY}px`;
            }
        });

        document.addEventListener('mouseup', () => {
            isDragging = false;
        });

        return modal;
    }

    // 컨트롤 패널 새로고침
    function refreshControlPanel() {
        const oldPanel = document.getElementById('controlPanel');
        if (oldPanel) oldPanel.remove();
        createControlPanel();
    }

    // 컨트롤 패널 생성
    function createControlPanel() {
        const controlPanel = document.createElement('div');
        controlPanel.id = 'controlPanel';
        controlPanel.style.position = 'fixed';
        controlPanel.style.top = '380px';
        controlPanel.style.left = '0';
        controlPanel.style.padding = '7px';
        controlPanel.style.borderRadius = '0 4px 4px 0';
        controlPanel.style.zIndex = '9999';
        controlPanel.style.display = 'flex';
        controlPanel.style.flexDirection = 'row';
        controlPanel.style.flexWrap = 'wrap';
        controlPanel.style.gap = '6px';
        controlPanel.style.background = 'transparent';
        controlPanel.style.transition = 'all 0.3s ease';
        controlPanel.style.maxWidth = '200px'; // 컨테이너 최대 너비 설정

        // 위치 불러오기
        const savedPosition = JSON.parse(localStorage.getItem('lckControlPanelPosition'));
        if (savedPosition) {
            controlPanel.style.left = savedPosition.left;
            controlPanel.style.top = savedPosition.top;
        }

        // 드래그 기능
        let isDragging = false;
        let offsetX = 0;
        let offsetY = 0;

        controlPanel.addEventListener('mousedown', (e) => {
            isDragging = true;
            offsetX = e.clientX - controlPanel.offsetLeft;
            offsetY = e.clientY - controlPanel.offsetTop;
        });

        document.addEventListener('mousemove', (e) => {
            if (isDragging) {
                controlPanel.style.left = (e.clientX - offsetX) + 'px';
                controlPanel.style.top = (e.clientY - offsetY) + 'px';
            }
        });

        document.addEventListener('mouseup', () => {
            if (isDragging) {
                isDragging = false;
                localStorage.setItem('lckControlPanelPosition', JSON.stringify({
                    left: controlPanel.style.left,
                    top: controlPanel.style.top
                }));
            }
        });

        // 버튼 생성 함수
        function createChannelButton(channel) {
            const button = document.createElement('button');
            button.textContent = channel.buttonLabel;
            button.style.padding = '5px 11px';
            button.style.color = channel.color;
            button.style.border = `1px solid ${channel.color}`;
            button.style.borderRadius = '3px';
            button.style.cursor = 'pointer';
            button.style.fontSize = '13px';
            button.style.transition = 'all 0.2s';
            button.style.background = 'transparent';

            button.onmouseover = () => {
                button.style.background = channel.color;
                button.style.color = 'white';
            };
            button.onmouseout = () => {
                button.style.background = 'transparent';
                button.style.color = channel.color;
            };
            button.onclick = async () => {
                if (channel.buttonLabel === '유튜브') {
                    try {
                        const liveVideoId = await fetchLiveVideoId(channel.id);
                        window.location.href = channel.url(liveVideoId);
                    } catch {
                        alert('유튜브 라이브 방송이 없습니다.');
                    }
                } else {
                    window.location.href = channel.url();
                }
            };
            return button;
        }

        // 토글 버튼 생성
        function createToggleButton() {
            const toggleButton = document.createElement('button');
            toggleButton.textContent = '◀';
            toggleButton.style.padding = '5px 11px';
            toggleButton.style.color = '#888';
            toggleButton.style.border = '1px solid #888';
            toggleButton.style.borderRadius = '3px';
            toggleButton.style.cursor = 'pointer';
            toggleButton.style.fontSize = '13px';
            toggleButton.style.transition = 'all 0.2s';
            toggleButton.style.background = 'transparent';

            toggleButton.onmouseover = () => {
                toggleButton.style.background = '#888';
                toggleButton.style.color = 'white';
            };
            toggleButton.onmouseout = () => {
                toggleButton.style.background = 'transparent';
                toggleButton.style.color = '#888';
            };
            toggleButton.onclick = () => {
                controlPanel.style.display = 'none';
            };
            return toggleButton;
        }

        // 설정 버튼 생성
        const settingsButton = document.createElement('button');
        settingsButton.textContent = '⚙';
        settingsButton.style.padding = '5px 11px';
        settingsButton.style.color = '#666';
        settingsButton.style.border = '1px solid #666';
        settingsButton.style.borderRadius = '3px';
        settingsButton.style.cursor = 'pointer';
        settingsButton.style.background = 'transparent';
        settingsButton.onclick = () => {
            document.getElementById('settingsModal').style.display = 'block';
        };

        // 커스텀 입력 섹션
        const customSection = document.createElement('div');
        customSection.style.display = 'flex';
        customSection.style.flexDirection = 'column';
        customSection.style.gap = '5px';
        customSection.style.marginTop = '10px';
        customSection.style.width = '100%';

        // 주소 입력창 표시 여부 설정
        if (settings.customInput) {
            // 입력 필드 및 Go 버튼
            const inputRow = document.createElement('div');
            inputRow.style.display = 'flex';
            inputRow.style.gap = '5px';
            inputRow.style.width = '100%';

            const input = document.createElement('input');
            input.type = 'text';
            input.placeholder = 'URL 입력';
            input.style.padding = '5px';
            input.style.border = '1px solid #ddd';
            input.style.borderRadius = '3px';
            input.style.flexGrow = '1';
           
            const goBtn = document.createElement('button');
            goBtn.textContent = 'Go';
            goBtn.style.padding = '5px 11px';
            goBtn.style.background = '#666';
            goBtn.style.color = 'white';
            goBtn.style.border = 'none';
            goBtn.style.borderRadius = '3px';
            goBtn.style.cursor = 'pointer';

            goBtn.onclick = () => {
                const url = input.value.trim();
                if (!url) {
                    alert('URL을 입력해주세요.');
                    return;
                }
                parseAndRedirect(url);
            };

            inputRow.appendChild(input);
            inputRow.appendChild(goBtn);

            customSection.appendChild(inputRow);
        }

        // 기존 버튼 추가
        if (settings.youtube) controlPanel.appendChild(createChannelButton(CHANNELS.youtube));
        if (settings.chzzk) controlPanel.appendChild(createChannelButton(CHANNELS.chzzk));
        if (settings.afreeca) controlPanel.appendChild(createChannelButton(CHANNELS.afreeca));
        if (settings.flow) controlPanel.appendChild(createChannelButton(CHANNELS.flow));
        controlPanel.appendChild(settingsButton);
        controlPanel.appendChild(createToggleButton());
        controlPanel.appendChild(customSection);

        document.body.appendChild(controlPanel);
    }

    // 초기화
    createSettingsPanel();
    createControlPanel();
})();