SOOP 채널 게시글 댓글 엑셀로 추출

Extract and save SOOP post comments as an Excel file

目前為 2024-12-14 提交的版本,檢視 最新版本

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


QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址