Greasy Fork

숲 멀티뷰 시청자 제거기

숲 생방송 시청자 목록을 가져오고 멀티뷰를 제거합니다.

目前为 2025-01-21 提交的版本。查看 最新版本

// ==UserScript==
// @name         숲 멀티뷰 시청자 제거기
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  숲 생방송 시청자 목록을 가져오고 멀티뷰를 제거합니다.
// @author       asdi
// @match        https://play.sooplive.co.kr/*
// @grant        none
// ==/UserScript==

(function () {
    'use strict';

    // 팝업창 생성
    const popup = document.createElement('div');
    popup.style.position = 'fixed';
    popup.style.top = '50%'; // 세로 중앙에 위치하도록
    popup.style.left = '0'; // 가로 왼쪽에 위치하도록
    popup.style.transform = 'translateY(-50%)'; // 세로 중앙 정렬
    popup.style.zIndex = '1000';
    popup.style.width = '350px';
    popup.style.padding = '15px';
    popup.style.backgroundColor = '#f8f9fa';
    popup.style.border = '1px solid #ccc';
    popup.style.borderRadius = '8px';
    popup.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.1)';
    popup.style.fontSize = '14px';
    popup.style.fontFamily = 'Arial, sans-serif';
    popup.style.color = '#333';

    popup.innerHTML = `
<div id="buttonContainer" style="display: flex; justify-content: space-between; margin-bottom: 10px;">
    <button id="getNickName" style="padding: 8px 12px; background-color: #007BFF; color: white; border: none; border-radius: 4px; cursor: pointer; flex: 1; margin-right: 5px;">시청자 목록 가져오기</button>
    <button id="totalViewerCount" style="padding: 8px 12px; background-color: #28a745; color: white; border: none; border-radius: 4px; cursor: pointer; flex: 1; margin-right: 5px;">멀티뷰 중복 제거</button>
    <button id="resetButton" style="padding: 8px 12px; background-color: #ffc107; color: #212529; border: none; border-radius: 4px; cursor: pointer; flex: 1; margin-right: 5px;">초기화</button>
    <button id="closePopup" style="padding: 8px 12px; background-color: #dc3545; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 12px;">닫기</button>
</div>
<div id="nicknameSection">
    <div id="nicknameDisplay" style="margin-top: 10px; padding: 10px; background-color: #e9ecef; border-radius: 4px; text-align: center; font-weight: bold;">닉네임이 여기에 표시됩니다.</div>
    <div id="nicknameResults" style="margin-top: 10px; padding: 10px; background-color: #d1e7dd; border-radius: 4px; font-size: 12px; white-space: pre-wrap;">결과가 여기에 표시됩니다.</div>
</div>
<hr style="margin: 15px 0; border-top: 1px solid #ddd;">
<div id="totalViewerSection">
    <div id="totalViewerResults" style="margin-top: 10px; padding: 10px; background-color: #d1e7dd; border-radius: 4px; font-size: 12px; white-space: pre-wrap;">결과가 여기에 표시됩니다.</div>
</div>
`;

    document.body.appendChild(popup);

    // 닫기 버튼 핸들러
    document.getElementById('closePopup').addEventListener('click', () => {
        document.body.removeChild(popup); // 팝업창 삭제
    });

    // userIdList에서 괄호 부분을 무시하고 중복 없는 아이디 리스트를 구하는 함수
    const getUniqueUserIdList = (userIdList) => {
        const uniqueUserIds = new Set();
        userIdList.forEach(userId => {
            const baseUserId = userId.replace(/\(\d+\)$/, '');
            uniqueUserIds.add(baseUserId);
        });
        return [...uniqueUserIds];
    };

    // 닉네임 가져오기 버튼 핸들러
    document.getElementById('getNickName').addEventListener('click', async () => {
        const nicknameElement = document.querySelector('a#infoNickName');
        const nicknameDisplay = document.getElementById('nicknameDisplay');
        const nicknameResults = document.getElementById('nicknameResults');

        if (nicknameElement) {
            const nickname = nicknameElement.textContent.trim();
            nicknameDisplay.textContent = `닉네임: ${nickname}`;

            try {
                liveView.Chat.chatUserListLayer.reconnect();
                liveView.playerController.sendChUser();

                const viewerText = document.getElementById('nAllViewer').textContent;
                const viewerNumber = parseInt(viewerText.replace(/,/g, ''));
                console.log('Viewer Number:', viewerNumber);

                // 3초 대기
                await new Promise(resolve => setTimeout(resolve, 3000));

                const userList = liveView.Chat.chatUserListLayer;
                const userIdList = [
                    ...Object.keys(userList.userListSeparatedByGrade.fan),
                    ...Object.keys(userList.userListSeparatedByGrade.manager),
                    ...Object.keys(userList.userListSeparatedByGrade.normal),
                    ...Object.keys(userList.userListSeparatedByGrade.subscription),
                    ...Object.keys(userList.userListSeparatedByGrade.vip)
                ];

                const uniqueUserIdList = getUniqueUserIdList(userIdList);
                console.log('Unique User ID List:', uniqueUserIdList);

                const loginRatio = (uniqueUserIdList.length / viewerNumber * 100).toFixed(2);
                const resultMessage = `${nickname} - 현재 시청자 수 : ${viewerNumber}, 중복 제거한 로그인 시청자 : ${uniqueUserIdList.length} (로그인 비율 : ${loginRatio}%)`;

                const nicknameResultsContent = document.getElementById('nicknameResults');
                const previousResults = nicknameResultsContent.textContent.trim();
                const updatedResults = previousResults ? previousResults + `\n\n${resultMessage}` : resultMessage;

                nicknameResultsContent.textContent = updatedResults;

                // localStorage에 저장
                const savedResults = localStorage.getItem('allResults') || '';
                localStorage.setItem('allResults', savedResults + `\n${resultMessage}`);
                localStorage.setItem(`uniqueUserIdList_${nickname}`, JSON.stringify(uniqueUserIdList));

            } catch (error) {
                console.error('Error:', error);
                nicknameResults.textContent = `Error: ${error.message}`;
            }
        } else {
            nicknameDisplay.textContent = '닉네임을 찾을 수 없습니다.';
        }
    });

    // 총 시청자 수 구하기 버튼 핸들러
    document.getElementById('totalViewerCount').addEventListener('click', () => {
        const totalViewerResults = document.getElementById('totalViewerResults');

        const nicknameKeys = Object.keys(localStorage).filter(key => key.startsWith('uniqueUserIdList_'));

        if (nicknameKeys.length > 0) {
            const uniqueViewerLists = [];
            const nicknames = [];
            let totalViewerNumber = 0;

            nicknameKeys.forEach(key => {
                const nickname = key.replace('uniqueUserIdList_', '');
                const uniqueUserIdList = JSON.parse(localStorage.getItem(key));

                uniqueViewerLists.push(new Set(uniqueUserIdList));
                nicknames.push(nickname);
                totalViewerNumber += uniqueUserIdList.length;
            });

            const unionSet = new Set();
            uniqueViewerLists.forEach(set => set.forEach(viewer => unionSet.add(viewer)));

            const multiViewExcludedCount = unionSet.size;
            const nicknameList = nicknames.join(', ');

            const idCounts = {};
            uniqueViewerLists.forEach(set => {
                set.forEach(viewer => {
                    idCounts[viewer] = (idCounts[viewer] || 0) + 1;
                });
            });

            const multiSetUsers = Object.keys(idCounts).filter(viewer => idCounts[viewer] > 1);
            const multiSetCount = multiSetUsers.length;

            const now = new Date();
            const formattedDate = `${now.getFullYear()}년 ${now.getMonth() + 1}월 ${now.getDate()}일 ${['일', '월', '화', '수', '목', '금', '토'][now.getDay()]} ${now.getHours()}:${now.getMinutes().toString().padStart(2, '0')}`;

            totalViewerResults.textContent = `집계 시각 : ${formattedDate}
${nicknameList} 의 멀티뷰 제외 로그인 시청자 수 : ${multiViewExcludedCount}명
( 멀티뷰 포함 : ${totalViewerNumber}, 멀티뷰 중인 시청자 수 : ${multiSetCount} )`;
        } else {
            totalViewerResults.textContent = '저장된 결과가 없습니다.';
        }
    });

    // 리셋 버튼 핸들러
    document.getElementById('resetButton').addEventListener('click', () => {
        localStorage.removeItem('allResults');
        Object.keys(localStorage).forEach(key => {
            if (key.startsWith('uniqueUserIdList_')) {
                localStorage.removeItem(key);
            }
        });

        document.getElementById('nicknameResults').textContent = '결과가 여기에 표시됩니다.';
        document.getElementById('totalViewerResults').textContent = '결과가 여기에 표시됩니다.';

        alert('모든 결과가 리셋되었습니다.');
    });

    // localStorage에서 저장된 모든 결과 가져오기
    const savedResults = localStorage.getItem('allResults');
    if (savedResults) {
        document.getElementById('nicknameResults').textContent = savedResults;
    }
})();