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