// ==UserScript==
// @name SOOP 채널 게시글 댓글 엑셀로 추출
// @namespace http://tampermonkey.net/
// @version 20241214
// @description Extract and save SOOP post comments as an Excel file
// @author 0hawawa
// @match https://ch.sooplive.co.kr/*
// @icon https://res.sooplive.co.kr/afreeca.ico
// @grant GM_xmlhttpRequest
// @grant GM_registerMenuCommand
// @require https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js
// @license MIT
// ==/UserScript==
(function () {
'use strict';
const bjApi = "https://bjapi.afreecatv.com/api";
function sanitizeFilename(filename) {
const invalidChars = /[\\/:*?"<>|]/g;
return filename.replace(invalidChars, '');
}
function getTitleInfo(bjId, titleNo) {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "GET",
url: `${bjApi}/${bjId}/title/${titleNo}`,
headers: { "User-Agent": "Mozilla/5.0" },
onload: (response) => {
const data = JSON.parse(response.responseText);
console.log(`글 제목: ${data.title_name}`);
console.log(`작성자: ${data.user_nick} (${data.user_id})`);
resolve(data);
},
onerror: reject,
});
});
}
function getCommentInfo(bjId, titleNo) {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "GET",
url: `${bjApi}/${bjId}/title/${titleNo}/comment`,
headers: { "User-Agent": "Mozilla/5.0" },
onload: (response) => {
const data = JSON.parse(response.responseText);
resolve(data);
},
onerror: reject,
});
});
}
function fetchComments(bjId, titleNo, lastPage) {
const allComments = [];
let requests = [];
for (let page = 1; page <= lastPage; page++) {
requests.push(
new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "GET",
url: `${bjApi}/${bjId}/title/${titleNo}/comment?page=${page}`,
headers: { "User-Agent": "Mozilla/5.0" },
onload: (response) => {
const data = JSON.parse(response.responseText);
allComments.push(...data.data);
const progress = Math.round((page / lastPage) * 100)
console.log(`진행률: ${progress}%`);
document.title = `진행률: ${progress}% - 댓글 추출 중`;
resolve();
},
onerror: reject,
});
})
);
}
Promise.all(requests).then(() => {
document.title = "댓글 추출 완료 - 작업이 완료되었습니다";
}).catch(() => {
document.title = "오류 발생 - 댓글 추출 실패";
});
return Promise.all(requests).then(() => allComments);
}
function saveToExcel(bjId, titleNo, comments, titleName, posterNick, posterId) {
const sanitizedTitle = sanitizeFilename(titleName);
const isSame = bjId === posterId;
const fileName = `${isSame ? `[${bjId}]` : `[${bjId}]${posterNick}(${posterId})`}_${titleNo}_${sanitizedTitle}.xlsx`;
const sheetData = comments.map((comment, index) => ({
번호: index + 1,
고유번호: comment.p_comment_no,
인기댓글: comment.is_best_top ? 1 : 0,
닉네임: comment.user_nick,
아이디: comment.user_id,
댓글: comment.comment,
좋아요: comment.like_cnt,
등록시간: comment.reg_date,
매니저: comment.badge?.is_manager ? 1 : 0,
열혈: comment.badge?.is_top_fan ? 1 : 0,
팬: comment.badge?.is_fan ? 1 : 0,
구독: comment.badge?.is_subscribe ? 1 : 0,
서포터: comment.badge?.is_support ? 1 : 0,
}));
const workbook = XLSX.utils.book_new();
const worksheet = XLSX.utils.json_to_sheet(sheetData);
XLSX.utils.book_append_sheet(workbook, worksheet, "Comments");
const excelBuffer = XLSX.write(workbook, { bookType: "xlsx", type: "array" });
const blob = new Blob([excelBuffer], { type: "application/octet-stream" });
saveAs(blob, fileName);
console.log(`Saved file: ${fileName}`);
}
async function main() {
const url = window.location.href;
const match = url.match(/https:\/\/ch\.sooplive\.co\.kr\/([^/]+)\/post\/(\d+)/);
if (!match) {
console.error("URL 패턴이 맞지 않습니다.");
return;
}
const bjId = match[1];
const titleNo = match[2];
try {
const { title_name, user_nick, user_id } = await getTitleInfo(bjId, titleNo);
const commentData = await getCommentInfo(bjId, titleNo);
const { comment_count, meta: { last_page } } = commentData;
console.log(`타이틀 넘버: ${titleNo}`);
console.log(`총 댓글수: ${comment_count}, 마지막 페이지: ${last_page}`);
const comments = await fetchComments(bjId, titleNo, last_page);
saveToExcel(bjId, titleNo, comments, title_name, user_nick, user_id);
} catch (error) {
console.error("Error occurred:", error);
}
}
GM_registerMenuCommand('Excel로 댓글 추출하기', function() {
main();
});
})();