SOOP (숲) - 다시보기 고유 조회수

생방송 시청자 수를 제외한 다시보기 VOD의 고유 조회수를 표시합니다

// ==UserScript==
// @name         SOOP (숲) - 다시보기 고유 조회수
// @name:ko      SOOP (숲) - 다시보기 고유 조회수
// @namespace    https://greasyfork.org/ko/users/651882-askld
// @version      20250717
// @description  생방송 시청자 수를 제외한 다시보기 VOD의 고유 조회수를 표시합니다
// @author       askld
// @match        https://ch.sooplive.co.kr/*
// @icon         https://res.sooplive.co.kr/afreeca.ico
// @run-at       document-start
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    const updateDOMWithViewCounts = (apiData) => {
        if (!apiData || !apiData.data) return;
        const vodDataMap = new Map(apiData.data.map(item => [item.title_no.toString(), item.count]));
        const vodElements = document.querySelectorAll('.vod-list li');

        vodElements.forEach(item => {
            const linkElement = item.querySelector('.thum a');
            if (!linkElement) return;
            const vodId = linkElement.href.split('/').pop();
            if (vodDataMap.has(vodId)) {
                const counts = vodDataMap.get(vodId);
                const viewsElement = item.querySelector('.info .views');
                if (viewsElement) {
                    const formattedReadCnt = counts.read_cnt.toLocaleString('en-US');
                    const formattedVodReadCnt = counts.vod_read_cnt.toLocaleString('en-US');
                    viewsElement.innerHTML = `<em></em> ${formattedReadCnt} (${formattedVodReadCnt})`;
                }
            }
        });
    };

    const originalOpen = XMLHttpRequest.prototype.open;
    XMLHttpRequest.prototype.open = function(method, url, ...args) {
        // 이 XHR 인스턴스에 url 정보를 저장해 둡니다.
        this._url = url;
        return originalOpen.apply(this, [method, url, ...args]);
    };

    const originalSend = XMLHttpRequest.prototype.send;
    XMLHttpRequest.prototype.send = function(...args) {
        const xhr = this;
        const originalOnLoad = xhr.onload;

        // load 이벤트 (요청이 성공적으로 완료되었을 때)를 가로챕니다.
        xhr.onload = function(...loadArgs) {
            // 저장된 url이 우리가 감시하려는 API가 맞는지 확인합니다.
            if (typeof xhr._url === 'string' && xhr._url.includes('/api/') && xhr._url.includes('/vods/review') && xhr.status === 200) {
                try {
                    const apiData = JSON.parse(xhr.responseText);
                    // DOM이 업데이트된 후 값을 덮어쓰기 위해 약간의 지연을 줍니다.
                    setTimeout(() => updateDOMWithViewCounts(apiData), 500);
                } catch (e) {
                    console.error('[XHR] JSON 파싱 오류', e);
                }
            }
            // 원래의 onload 핸들러가 있었다면 실행시켜 줍니다.
            if (originalOnLoad) {
                return originalOnLoad.apply(this, loadArgs);
            }
        };
        return originalSend.apply(this, args);
    };
})();