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