Greasy Fork

아프리카TV - 현재 방송을 보고 있는 BJ 목록

현재 방송을 보고 있는 방송인을 찾아서 보여줍니다

目前为 2024-01-27 提交的版本。查看 最新版本

// ==UserScript==
// @name         아프리카TV - 현재 방송을 보고 있는 BJ 목록
// @name:ko         아프리카TV - 현재 방송을 보고 있는 BJ 목록
// @namespace    https://www.afreecatv.com/
// @version      20240127
// @description  현재 방송을 보고 있는 방송인을 찾아서 보여줍니다
// @description:ko  현재 방송을 보고 있는 방송인을 찾아서 보여줍니다
// @author       You
// @match        https://play.afreecatv.com/*/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=afreecatv.com
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_addStyle
// @grant        GM_registerMenuCommand
// @grant        GM_unregisterMenuCommand
// @run-at       document-end
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';
    const intervalTime = GM_getValue("intervalTime", 3);
    let registeredUsers = GM_getValue('registeredUsers', []);
    let best_bj_data = GM_getValue("best_bj_data");
    let apply_best_bj_data = GM_getValue("apply_best_bj_data",0);
    let menuIds = {};

    GM_addStyle(`
.profile-picture-chat {
    grid-area: profile-picture;
    width: 40px;
    height: 40px;
    border-radius: 50%;
    padding: 0 2px;
    cursor: pointer;
}
.text_information .view_bj {
    display: flex !important;
    flex-wrap: wrap;
    gap: 6px;
    padding-top:10px;
}
    `);

    function waitForElement(elementSelector, callBack, attempts = 0, maxAttempts = 100) {
        const element = document.querySelector(elementSelector);

        if (element) {
            callBack(elementSelector, element);
        } else {
            if (attempts < maxAttempts) {
                setTimeout(function () {
                    waitForElement(elementSelector, callBack, attempts + 1, maxAttempts);
                }, 200);
            } else {
                console.error('Reached maximum attempts. Element not found.');
            }
        }
    }
    // 등록 목록을 저장합니다.
    function saveRegisteredUsers() {
        GM_setValue('registeredUsers', registeredUsers);
    }

    // 사용자를 등록 목록에 추가합니다.
    function registerUser(userName, userId) {
        // 이미 등록된 사용자인지 확인
        if (!isUserRegistered(userId)) {
            registeredUsers.push({ userName, userId });
            saveRegisteredUsers();
            alert(`사용자 ${userName}(${userId})를 등록했습니다.`);
            registerUnregisterMenu({ userName, userId });
        } else {
            alert(`사용자 ${userName}(${userId})는 이미 등록되어 있습니다.`);
        }
    }

    // 함수: 사용자 등록 해제
    function unregisterUser(userId) {
        // 등록된 사용자 목록에서 해당 사용자 찾기
        let unregisteredUser = registeredUsers.find(user => user.userId === userId);

        // 사용자를 찾았을 때만 등록 해제 및 메뉴 삭제 수행
        if (unregisteredUser) {
            // 등록된 사용자 목록에서 해당 사용자 제거
            registeredUsers = registeredUsers.filter(user => user.userId !== userId);

            // 변경된 목록을 저장
            GM_setValue('registeredUsers', registeredUsers);

            alert(`사용자 ${userId}의 등록이 해제되었습니다.`);

            unregisterUnregisterMenu(unregisteredUser.userName);
        }
    }

    // 사용자가 이미 등록되어 있는지 확인합니다.
    function isUserRegistered(userId) {
        return registeredUsers.some(user => user.userId === userId);
    }

    // 함수: 동적으로 메뉴 등록
    function registerUnregisterMenu(user) {
        // GM_registerMenuCommand로 메뉴를 등록하고 메뉴 ID를 기록
        let menuId = GM_registerMenuCommand(`💔 등록 해제 - ${user.userName}`, function() {
            unregisterUser(user.userId);
        });

        // 메뉴 ID를 기록
        menuIds[user.userName] = menuId;
    }

    // 함수: 동적으로 메뉴 삭제
    function unregisterUnregisterMenu(userName) {
        // userName을 기반으로 저장된 메뉴 ID를 가져와서 삭제
        let menuId = menuIds[userName];
        if (menuId) {
            GM_unregisterMenuCommand(menuId);
            delete menuIds[userName]; // 삭제된 메뉴 ID를 객체에서도 제거
        }
    }

    function getBestbjList() {
        // 이전에 저장된 정보 가져오기
        var storedData = GM_getValue("best_bj_data");
        var lastExecutedTime = GM_getValue("last_executed_time");

        // 현재 시간 가져오기
        var currentTime = new Date().getTime();

        // 1시간(밀리초 단위) 설정
        var oneHour = 60 * 60 * 1000;

        // 이전에 함수가 실행된 적이 없거나 마지막 실행 시간으로부터 12 지났는지 확인
        if (!lastExecutedTime || (currentTime - lastExecutedTime) >= 12*oneHour) {
            var num;
            if (storedData) {
                num = 100;
            } else {
                num = 10000;
            }
            // 새로운 데이터 가져오기
            fetch(`https://afevent2.afreecatv.com/app/star_bj/bestbj/api/api.php?contentsNum=${num}&page=1&searchWord=`)
                .then(response => response.json())
                .then(data => {
                var userList = [];

                // 새로 받은 데이터에서 idx, user_id, user_nick 값만 추출하여 저장
                var newData = data.list.map(item => ({
                    idx: item.idx,
                    user_id: item.user_id,
                    user_nick: item.user_nick
                }));

                // 이전에 저장된 데이터가 있는 경우
                if (storedData) {
                    // 이전 데이터의 idx 값 추출
                    var storedIdx = storedData.list.map(item => item.idx);

                    // 새로운 데이터 중 이전 데이터에 없는 것만 선택
                    var uniqueNewData = newData.filter(item => !storedIdx.includes(item.idx));

                    // 이전 데이터와 새로운 데이터 합치기
                    userList = storedData.list.concat(uniqueNewData);

                    // 저장할 데이터 구성
                    var mergedData = {
                        cnt: data.cnt,
                        list: userList
                    };

                    // 새로운 데이터 저장
                    GM_setValue("best_bj_data", mergedData);
                    console.log("데이터가 업데이트되었습니다.");
                } else {
                    // 이전에 저장된 데이터가 없는 경우, 새로 받은 데이터 전체를 userList에 추가
                    GM_setValue("best_bj_data", { cnt: data.cnt, list: newData });
                    console.log("처음으로 데이터를 저장했습니다.");
                }
                console.log(GM_getValue("best_bj_data"));
                GM_setValue("last_executed_time",currentTime);
            })
                .catch(error => console.error('데이터를 가져오는 중 오류가 발생했습니다:', error));

        } else {
            console.log("이전에 함수가 실행된 지 12시간이 경과하지 않았습니다.");
        }
    }

    function findNickname(id, viewerList) {
        for (const grade in viewerList) {
            if (Object.prototype.hasOwnProperty.call(viewerList, grade)) {
                const gradeList = viewerList[grade];
                for (const viewer of gradeList) {
                    const userId = viewer.id.split('(')[0];
                    if (userId === id) {
                        return viewer.nickname;
                    }
                }
            }
        }
        return null;
    }

    function makeUserlistbox() {
        const textInformationElement = document.querySelector('.text_information');
        const newElement = document.createElement('div');
        newElement.classList.add('view_bj');
        newElement.id = 'view_bj';
        const sixthChildElement = textInformationElement.children[4];
        if (sixthChildElement) {
            sixthChildElement.insertAdjacentElement('afterend', newElement);
        }
    }

    function makeUserIcon(userId, username) {
        const imageUrl = `https://stimg.afreecatv.com/LOGO/${userId.substring(0, 2)}/${userId}/m/${userId}.webp`;
        const usericon = `<img title="${username}" src="${imageUrl}" alt="${username}" class="profile-picture-chat" onclick="window.open('https://bj.afreecatv.com/${userId}', '_blank');" onerror="this.onerror=null; this.src='https://profile.img.afreecatv.com/LOGO/s'">`;
        return usericon;
    }


    async function getCurrentViewerList() {
        const viewerElement = document.getElementById('nAllViewer');
        const delay = async (milliseconds) => {
            return new Promise(resolve => setTimeout(resolve, milliseconds));
        };

        try {
            liveView.playerController.sendChUser();
            await delay(300);

            const userList = liveView.Chat.chatUserListLayer;
            const userListCount = Object.values(userList.userListSeparatedByGrade).reduce((acc, grade) => acc + Object.keys(grade).length - 1, 0);
            const viewerCount = Number(viewerElement.textContent.replace(/,/g, '')) || 5;

            if (userListCount < 0.5 * viewerCount) {
                let retryCount = 0;
                const retrySendingUserList = async () => {
                    if (retryCount > 100) return;
                    await delay(100);
                    retryCount++;
                    return retrySendingUserList();
                };
                await retrySendingUserList();
            }

            return userList.getUserListForSDK();
        } catch (error) {
            console.error(error);
            return null;
        }
    }

    async function fetchUserList() {
        let targetdiv = document.querySelector('#view_bj');

        try {
            let viewerList = await getCurrentViewerList();
            if (viewerList) {
                let uniqueEntries = new Map();

                if (apply_best_bj_data) {
                    await processBjList(viewerList, uniqueEntries, best_bj_data.list);
                }

                await processRegisteredUsers(viewerList, uniqueEntries, registeredUsers);

                let html = generateHTMLFromMap(uniqueEntries);
                updateHTML(targetdiv, html);
            }
        } catch (error) {
            console.error(error);
        }
    }

    async function processBjList(viewerList, uniqueEntries, bjList) {
        for (const item of bjList) {
            let bj_id = item.user_id;
            if (szBjId !== bj_id) {
                let matchedUserName = findNickname(bj_id, viewerList);
                if (matchedUserName && !uniqueEntries.has(bj_id)) {
                    uniqueEntries.set(bj_id, matchedUserName);
                }
            }
        }
    }

    async function processRegisteredUsers(viewerList, uniqueEntries, users) {
        for (const user of users) {
            let bj_id = user.userId;
            if (szBjId !== bj_id) {
                let matchedUserName = findNickname(bj_id, viewerList);
                if (matchedUserName && !uniqueEntries.has(bj_id)) {
                    uniqueEntries.set(bj_id, matchedUserName);
                }
            }
        }
    }

    function generateHTMLFromMap(uniqueEntries) {
        let html = '';
        for (const [bj_id, matchedUserName] of uniqueEntries) {
            let usericon = makeUserIcon(bj_id, matchedUserName);
            html += usericon;
        }
        return html;
    }

    function updateHTML(targetdiv, html) {
        targetdiv.innerHTML = html;
    }


    function detectSmode() {
        var target = document.querySelector('body');
        var webplayerScroll1 = document.getElementById('view_bj');
        var images;
        var observer = new MutationObserver(function(mutations) {
            var bodyClasses = target.classList;
            if (bodyClasses.contains('smode')){
                webplayerScroll1.style.display = 'none';
                images = webplayerScroll1.querySelectorAll('img');
                images.forEach(function(image) {
                    image.style.display = 'none';
                });
            } else {
                webplayerScroll1.style.display = '';
                // smode 클래스가 존재하지 않으면 모든 하위 이미지 보이기
                images = webplayerScroll1.querySelectorAll('img');
                images.forEach(function(image) {
                    image.style.display = '';
                });
            }
        });

        observer.observe(target, {
            attributeFilter: ['class']
        });
    }

    getBestbjList();
    waitForElement('#actionbox', function (elementSelector, element) {
        makeUserlistbox();
        setTimeout(fetchUserList,10*1000);
        setInterval(fetchUserList,intervalTime*60*1000);
    });
    detectSmode();


    GM_registerMenuCommand(`검색 빈도 설정 (${intervalTime}분)`, function() {
        var num_top = prompt('1 이상의 숫자를 입력', 3);
        if (parseInt(num_top) > 0){
            GM_setValue("display_top", parseInt(intervalTime));
            alert("설정 값이 변경되었습니다. 새로고침 후 적용됩니다.");
        } else {
            alert("유효한 숫자를 입력해주세요");
        }
    });
    GM_registerMenuCommand(`개별 모니터링할 유저 등록하기`, function() {
        var userid_input = prompt('유저 아이디 입력', '');
        if (userid_input) {
            fetch(`https://st.afreecatv.com/api/get_station_status.php?szBjId=${userid_input}`)
                .then(response => response.json()) // 응답 데이터를 JSON으로 파싱
                .then(data => {
                if (data.RESULT === 0) {
                    // 결과가 실패인 경우
                    alert('서버에서 유효하지 않은 응답을 받았습니다.');
                } else {
                    // 결과가 성공인 경우
                    console.log(data.DATA.user_nick, data.DATA.user_id);
                    if (userid_input === data.DATA.user_id) {
                        registerUser(data.DATA.user_nick, data.DATA.user_id);
                    } else {
                        alert("유효한 아이디를 입력해주세요");
                    }
                }
            })
                .catch(error => {
                // fetch 요청 실패 등의 오류 처리
                console.error('fetch 요청에 실패했습니다:', error);
            });
        } else {
            alert("유효한 아이디를 입력해주세요");
        }
    });
    if(best_bj_data){
        GM_registerMenuCommand(`다운로드 된 BJ 목록 ${best_bj_data.list.length}명 모니터링` + (apply_best_bj_data ? "(ON → OFF)" : "(OFF → ON)"), function() {

            // coloring_live 값 변경
            apply_best_bj_data = apply_best_bj_data ? 0 : 1;

            // 변경된 값 저장
            GM_setValue("apply_best_bj_data", apply_best_bj_data);
            alert("설정 값이 변경되었습니다. 새로고침 후 적용됩니다.");
        });
    }

    registeredUsers.forEach(function(user) {
        registerUnregisterMenu(user);
    });
})();