아이디, 등록(ID), 권한 추가일 정보만 수집하며, 등록(ID)는 괄호 안의 순수 ID만 수집하여 더 가볍고 빠르게 동작하는 최종 버전입니다.
当前为
// ==UserScript==
// @name SOOP 블랙리스트 내보내기 (아이디 전용, 최종 안정화)
// @name:en SOOP Blacklist Export Tool (ID Only)
// @namespace https://www.sooplive.co.kr/
// @version 1.0
// @description 아이디, 등록(ID), 권한 추가일 정보만 수집하며, 등록(ID)는 괄호 안의 순수 ID만 수집하여 더 가볍고 빠르게 동작하는 최종 버전입니다.
// @description:en Exports SOOP blacklist data (User ID, Admin ID, and Date) to a CSV file. Optimized for speed and minimal data collection.
// @author Lmayo
// @match https://*.sooplive.co.kr/*/setting/blacklist
// @icon https://www.google.com/s2/favicons?sz=64&domain=www.sooplive.co.kr
// @grant unsafeWindow
// @license MIT
// ==/UserScript==
(function() {
'use strict';
let collectedData = [];
let isMonitoring = false;
const originalOpen = XMLHttpRequest.prototype.open;
const originalSend = XMLHttpRequest.prototype.send;
let observer;
// --- XHR 통신 감시 로직 (기존 유지) ---
XMLHttpRequest.prototype.open = function(method, url) { this._requestURL = url; originalOpen.apply(this, arguments); };
XMLHttpRequest.prototype.send = function() {
if (isMonitoring) {
this.addEventListener('load', function() {
if (this.status === 200 && String(this._requestURL).includes('/blacklist')) {
try {
const json = JSON.parse(this.responseText);
const dataList = json.data || [];
if (dataList.length > 0) {
updateButtonStatus();
}
} catch (e) { /* 무시 */ }
}
});
}
originalSend.apply(this, arguments);
};
// --- 기능 함수 ---
function scrapeCurrentPageAndStore() {
const rows = document.querySelectorAll('.Tbody_tbody__CUfRE tr');
if (rows.length === 0) {
updateButtonStatus();
return;
}
const idRegex = /\(([^)]+)\)/;
const pageData = [];
rows.forEach(row => {
const cells = row.querySelectorAll('td');
if (cells.length < 8) return;
const blacklistUserCell = cells[2]; // 블랙리스트 유저 닉네임(ID) 셀
const registratorCell = cells[6]; // 등록자 닉네임(ID) 셀 (인덱스 6)
const dateCell = cells[7]; // 권한 추가일 셀 (인덱스 7)
if (blacklistUserCell && registratorCell && dateCell) {
const fullText = blacklistUserCell.textContent || '';
const match = fullText.match(idRegex);
const id = match ? match[1] : ''; // 블랙리스트 유저 ID
// 등록자 ID 추출: 텍스트에서 괄호 안의 값만 추출
const registratorFullText = registratorCell.textContent || '';
const registratorMatch = registratorFullText.match(idRegex);
const registratorId = registratorMatch ? registratorMatch[1] : registratorFullText.trim(); // 괄호 없으면 전체 텍스트
const dateText = dateCell.textContent ? dateCell.textContent.trim() : '';
// collectedData에 아이디 기반으로 저장
if(id && !collectedData.some(d => d['아이디'] === id)) {
pageData.push({
'아이디': id,
'등록(ID)': registratorId, // ✨ 괄호 안의 ID 값만 사용
'권한 추가일': dateText
});
}
}
});
collectedData.push(...pageData);
updateButtonStatus();
}
function updateButtonStatus() {
const exportLabel = document.getElementById('blacklist-export-label');
if (exportLabel) {
const uniqueCount = new Map(collectedData.map(item => [item['아이디'], item])).size;
exportLabel.textContent = `수집 데이터 ${uniqueCount}개 내보내기`;
exportLabel.parentElement.style.borderColor = '#1e8e3e';
exportLabel.parentElement.style.backgroundColor = '#e6f4ea';
exportLabel.parentElement.style.color = '#1e8e3e';
}
}
function clickNextPage() {
const paginationWrapper = document.querySelector('div[class*="Pagination_pagination__"], div[class*="Pagination-module__wrapper"]');
if (!paginationWrapper) return;
const activeButton = paginationWrapper.querySelector('button[class*="defaultPrimary"]');
if (activeButton) {
const nextButton = activeButton.nextElementSibling;
if (nextButton && nextButton.tagName === 'BUTTON') {
nextButton.click();
// 페이지 전환 후 DOM 업데이트를 기다린 다음 데이터 수집 실행
if (isMonitoring) {
setTimeout(scrapeCurrentPageAndStore, 500);
}
}
}
}
function toggleMonitoring() {
isMonitoring = !isMonitoring;
rebuildButtons();
if (isMonitoring) {
collectedData = [];
scrapeCurrentPageAndStore();
alert('데이터 수집을 시작합니다.\n이제 [다음 페이지] 버튼을 끝까지 눌러주세요.');
}
}
function downloadCollectedData() {
if (collectedData.length === 0) { alert('수집된 데이터가 없습니다.'); return; }
const headers = ['No', '아이디', '등록(ID)', '권한 추가일'];
const csvRows = [headers.join(',')];
const uniqueData = Array.from(new Map(collectedData.map(item => [item['아이디'], item])).values());
uniqueData.forEach((item, index) => {
const values = [
`"${index + 1}"`,
`"${item['아이디']}"`,
`"${item['등록(ID)']}"`,
`"${item['권한 추가일']}"`
];
csvRows.push(values.join(','));
});
const csvString = csvRows.join('\n');
const blob = new Blob(['\uFEFF' + csvString], { type: 'text/csv;charset=utf-8;' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url; a.download = 'blacklist_collected_id_only.csv';
a.click(); URL.revokeObjectURL(url);
}
function rebuildButtons() {
if (observer) observer.disconnect();
const titleContainer = document.querySelector('.SettingContainer_titleBox__0bRU4 .Title_sectionTitle__QOjq9');
if (!titleContainer) {
if (observer) observer.observe(document.body, { childList: true, subtree: true });
return;
}
document.getElementById('blacklist-toggle-btn')?.remove();
document.getElementById('blacklist-next-page-btn')?.remove();
document.getElementById('blacklist-export-btn')?.remove();
const buttonStyle = `margin-left: 10px; padding: 4px 8px; font-size: 12px; border: 1px solid; border-radius: 4px; cursor: pointer; font-weight: 500;`;
const toggleButton = document.createElement('button');
toggleButton.id = 'blacklist-toggle-btn';
toggleButton.style.cssText = buttonStyle;
toggleButton.onclick = toggleMonitoring;
if (isMonitoring) {
toggleButton.textContent = '■ 수집 중지';
toggleButton.style.backgroundColor = '#fce8e6'; toggleButton.style.color = '#d93025'; toggleButton.style.borderColor = '#d93025';
} else {
toggleButton.textContent = '▶ 데이터 수집 시작';
toggleButton.style.backgroundColor = '#e8f0fe'; toggleButton.style.color = '#1a73e8'; toggleButton.style.borderColor = '#1a73e8';
}
const nextPageButton = document.createElement('button');
nextPageButton.id = 'blacklist-next-page-btn';
nextPageButton.textContent = '다음 페이지';
nextPageButton.style.cssText = buttonStyle + `border-color: #5f6368; background-color: #f1f3f4; color: #3c4043;`;
nextPageButton.onclick = clickNextPage;
const paginationWrapper = document.querySelector('div[class*="Pagination_pagination__"], div[class*="Pagination-module__wrapper"]');
const activeButton = paginationWrapper ? paginationWrapper.querySelector('button[class*="defaultPrimary"]') : null;
const nextButtonExists = activeButton ? activeButton.nextElementSibling : null;
if (!nextButtonExists || (nextButtonExists.tagName === 'BUTTON' && nextButtonExists.disabled)) {
nextPageButton.disabled = true;
nextPageButton.textContent = '마지막 페이지';
nextPageButton.style.opacity = '0.5'; nextPageButton.style.cursor = 'not-allowed';
}
const exportButton = document.createElement('button');
exportButton.id = 'blacklist-export-btn';
exportButton.style.cssText = buttonStyle;
exportButton.onclick = downloadCollectedData;
const exportLabel = document.createElement('span');
exportLabel.id = 'blacklist-export-label';
exportButton.appendChild(exportLabel);
titleContainer.appendChild(toggleButton);
titleContainer.appendChild(nextPageButton);
titleContainer.appendChild(exportButton);
updateButtonStatus();
if (observer) observer.observe(document.body, { childList: true, subtree: true });
}
observer = new MutationObserver(() => {
rebuildButtons();
});
observer.observe(document.body, { childList: true, subtree: true });
})();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址