Greasy Fork

Greasy Fork is available in English.

lck

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

当前为 2025-01-31 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         lck
// @namespace    lck
// @version      2.03
// @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';

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

    // 설정 불러오기
    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);

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

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

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

        document.addEventListener('mousemove', (e) => {
            if (isDragging) {
                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();
})();