// ==UserScript== // @name 아프리카TV - 현재 방송을 보고 있는 BJ 목록 // @name:ko 아프리카TV - 현재 방송을 보고 있는 BJ 목록 // @namespace https://www.afreecatv.com/ // @version 20240129 // @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 // @downloadURL none // ==/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 = `${username}`; 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 intervalTime = prompt('1 이상의 숫자를 입력', 3); if (parseInt(intervalTime) > 0){ GM_setValue("intervalTime", 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); }); })();