// ==UserScript== // @name lck // @namespace lck // @version 1.8 // @description 롤캐 숙제 바로가기 // @match https://lolcast.kr/* // @grant GM_xmlhttpRequest // @downloadURL none // ==/UserScript== (function() { 'use strict'; // 채널 정보 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 }); }); } // 컨트롤 패널 생성 const controlPanel = document.createElement('div'); controlPanel.style.position = 'fixed'; // 기본 위치 (left: 0, top: 380px) - 이후 드래그가 가능하므로 원하는 위치로 옮길 수 있습니다. 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 = 'column'; // 세로 방향으로 배치 controlPanel.style.gap = '6px'; controlPanel.style.background = 'transparent'; controlPanel.style.transition = 'all 0.3s ease'; // 이전 위치 불러오기(localStorage) const savedPosition = localStorage.getItem('lckControlPanelPosition'); if (savedPosition) { try { const { left, top } = JSON.parse(savedPosition); controlPanel.style.left = left; controlPanel.style.top = top; } catch(e) { console.error('Failed to parse saved control panel position:', e); } } // 드래그 기능 추가 let isDragging = false; let offsetX = 0; let offsetY = 0; controlPanel.addEventListener('mousedown', (e) => { // 버튼 위에서도 이동 가능하도록 하려면, e.target이 버튼이면 return을 제거하거나 조정하세요. // 현재는 패널의 빈 영역에서만 드래그가 되는 것이 일반적이므로 조건을 사용하지 않습니다. 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 })); } }); // 버튼 생성 함수 (크기 1pt씩 작게 조정) 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; } // 토글 버튼 생성 (크기 1pt씩 작게 조정) 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 youtubeButton = createChannelButton(CHANNELS.youtube); const chzzkButton = createChannelButton(CHANNELS.chzzk); const afreecaButton = createChannelButton(CHANNELS.afreeca); const flowButton = createChannelButton(CHANNELS.flow); // flow 버튼 추가 const toggleButton = createToggleButton(); // 첫 번째 줄: 유튜브, 치지직, 숲 const firstLine = document.createElement('div'); firstLine.style.display = 'flex'; firstLine.style.gap = '6px'; firstLine.appendChild(youtubeButton); firstLine.appendChild(chzzkButton); firstLine.appendChild(afreecaButton); // 두 번째 줄: Flow, 토글 버튼 const secondLine = document.createElement('div'); secondLine.style.display = 'flex'; secondLine.style.gap = '6px'; secondLine.appendChild(flowButton); secondLine.appendChild(toggleButton); // 패널에 줄 추가 controlPanel.appendChild(firstLine); controlPanel.appendChild(secondLine); document.body.appendChild(controlPanel); })();