Greasy Fork is available in English.

SOOP 다시보기 라이브 당시 시간 표시

SOOP 다시보기에서 생방송 당시 시간을 표시하고 특정 시간으로 이동합니다.

// ==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
// ==/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 = '<strong>⇋</strong>';
            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 = `<span style='color: #9196a1;'>Live 당시 시간⠀</span> <span style='color: #FF2F00;'>${currentLiveTime}</span>`;
    };

    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);
})();