// ==UserScript== // @name SOOP 다시보기 라이브 당시 시간 표시 // @namespace http://tampermonkey.net/ // @version 4.3.4 // @description SOOP 다시보기에서 생방송 당시 시간을 표시하고 특정 시간으로 이동합니다. // @author WakViewer // @match https://vod.sooplive.co.kr/player/* // @icon https://www.google.com/s2/favicons?sz=64&domain=www.sooplive.co.kr // @grant unsafeWindow // @grant GM_addStyle // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @grant GM_unregisterMenuCommand // @run-at document-end // @license MIT // @downloadURL https://update.greasyfork.icu/scripts/521331/SOOP%20%EB%8B%A4%EC%8B%9C%EB%B3%B4%EA%B8%B0%20%EB%9D%BC%EC%9D%B4%EB%B8%8C%20%EB%8B%B9%EC%8B%9C%20%EC%8B%9C%EA%B0%84%20%ED%91%9C%EC%8B%9C.user.js // @updateURL https://update.greasyfork.icu/scripts/521331/SOOP%20%EB%8B%A4%EC%8B%9C%EB%B3%B4%EA%B8%B0%20%EB%9D%BC%EC%9D%B4%EB%B8%8C%20%EB%8B%B9%EC%8B%9C%20%EC%8B%9C%EA%B0%84%20%ED%91%9C%EC%8B%9C.meta.js // ==/UserScript== (function () { 'use strict'; const startTimeSelector = "#player_area > div.wrapping.player_bottom > div.broadcast_information > div:nth-child(2) > div.cnt_info > ul > li:nth-child(2) > span.broad_time"; const currentTimeSelector = "span.time-current"; const durationSelector = "#player > div.player_ctrlBox > div.ctrlBox > div.ctrl > div.time_display > span.time-duration"; let startTime = null; let endTime = null; let currentLiveTime = ''; const waitForElement = (selector, callback) => { const observer = new MutationObserver(() => { const element = document.querySelector(selector); if (element) { observer.disconnect(); callback(element); } }); observer.observe(document.body, { childList: true, subtree: true }); }; const init = () => { waitForElement(startTimeSelector, (startTimeElement) => { const tipContent = startTimeElement.getAttribute('tip'); const timeMatch = tipContent.match(/방송시간 : (\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) ~ (\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})/); if (!timeMatch) { return; } startTime = new Date(timeMatch[1]); endTime = new Date(timeMatch[2]); setInterval(() => { updateLiveTime(startTime); }, 500); waitForElement(durationSelector, checkEditNotice); addDisplayAndButton(); }); }; const checkEditNotice = (durationElement) => { const durationParts = durationElement.textContent.split(':').map(Number); const totalDuration = (durationParts[0] * 3600) + (durationParts[1] * 60) + durationParts[2]; const expectedDuration = (endTime - startTime) / 1000; if (totalDuration < expectedDuration - 60) { // 편집된 경우 const liveTimeDisplay = document.getElementById('live-time-display'); if (!liveTimeDisplay) return; let editNotice = document.getElementById('edit-notice'); if (!editNotice) { editNotice = document.createElement('strong'); editNotice.id = 'edit-notice'; editNotice.textContent = '[같이보기 진행 또는 편집된 영상일 수 있습니다.]'; editNotice.style.fontSize = '14px'; editNotice.style.lineHeight = '28px'; editNotice.style.color = '#9196a1'; editNotice.style.marginRight = '10px'; liveTimeDisplay.parentNode.insertBefore(editNotice, liveTimeDisplay); } } }; const addDisplayAndButton = () => { const upCntElement = document.querySelector("#player_area > div.wrapping.player_bottom > div.broadcast_information > div:nth-child(2) > div.cnt_info > ul"); if (!upCntElement) return; // 가로 길이 조정 upCntElement.style.width = '180px'; // 라이브 당시 시간 표시 let liveTimeDisplay = document.getElementById('live-time-display'); if (!liveTimeDisplay) { liveTimeDisplay = document.createElement('span'); liveTimeDisplay.id = 'live-time-display'; liveTimeDisplay.style.fontSize = '14px'; liveTimeDisplay.style.lineHeight = '28px'; liveTimeDisplay.title = '라이브 당시 시간 복사'; liveTimeDisplay.addEventListener('click', () => { navigator.clipboard.writeText(currentLiveTime).then(() => { showToastMessage(`복사 완료: ${currentLiveTime}`); }); }); upCntElement.parentNode.insertBefore(liveTimeDisplay, upCntElement); } // 점프 버튼 추가 let jumpButton = document.getElementById('jump-button'); if (!jumpButton) { jumpButton = document.createElement('button'); jumpButton.id = 'jump-button'; jumpButton.innerHTML = ''; jumpButton.style.marginLeft = '10px'; jumpButton.style.color = '#FF2F00'; jumpButton.style.background = 'transparent'; jumpButton.style.border = 'none'; jumpButton.style.cursor = 'pointer'; jumpButton.title = '특정 시간으로 이동'; jumpButton.addEventListener('click', () => { const userInput = prompt(`이동할 라이브 당시 시간을 입력하세요.\n(예: YYYY-MM-DD, HH:mm:ss)\n\n[방송정보]\n시작: ${formatDate(startTime)}\n종료: ${formatDate(endTime)}`); const targetTime = new Date(userInput); if (isNaN(targetTime)) { showToastMessage('올바른 형식이 아닙니다!', true); return; } if (targetTime < startTime || targetTime > endTime) { showToastMessage('해당 방송이 진행된 시간이 아닙니다!', true); return; } const diffInSeconds = Math.floor((targetTime - startTime) / 1000); const newUrl = `${window.location.origin}${window.location.pathname}?change_second=${diffInSeconds}`; window.location.href = newUrl; }); liveTimeDisplay.insertAdjacentElement('afterend', jumpButton); } }; const updateLiveTime = (startTime) => { const currentTimeElement = document.querySelector(currentTimeSelector); if (!currentTimeElement) return; const [hours, minutes, seconds] = currentTimeElement.textContent.trim().split(':').map(Number); const liveTime = new Date(startTime); liveTime.setSeconds(liveTime.getSeconds() + hours * 3600 + minutes * 60 + seconds); currentLiveTime = formatDate(liveTime); document.getElementById('live-time-display').innerHTML = `Live 당시 시간⠀ ${currentLiveTime}`; }; function formatDate(date) { return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}, ${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}:${String(date.getSeconds()).padStart(2, '0')}`; } function showToastMessage(message, isError = false) { const toastContainer = document.querySelector('#toastMessage'); if (toastContainer) { const toastWrapper = document.createElement('div'); const toastContent = document.createElement('p'); toastContent.textContent = message; toastWrapper.appendChild(toastContent); toastContainer.appendChild(toastWrapper); setTimeout(() => { toastContainer.removeChild(toastWrapper); }, 2000); } else { alert(message); } } window.addEventListener('load', init); })();