네이버 카페 댓글 모아보기

네이버 카페 게시물에서 특정 유저 댓글을 모아보기

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         네이버 카페 댓글 모아보기
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  네이버 카페 게시물에서 특정 유저 댓글을 모아보기 
// @author       로시커여워
// @match        https://cafe.naver.com/*
// @icon         https://littledeep.com/wp-content/uploads/2020/09/naver-icon-style.png
// @license      MIT
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    const DEFAULT_USERS = ["챈나", "주머기", "정키마"];

    function loadUsers() {
        const saved = localStorage.getItem("targetUsers");
        return saved ? JSON.parse(saved) : [...DEFAULT_USERS];
    }

    function saveUsers(users) {
        localStorage.setItem("targetUsers", JSON.stringify(users));
    }

    let targetUsers = loadUsers();
    let listContainer = null;
    const seenComments = new Set();
    const userColors = new Map();
    let collapsed = false;

    function getUserColor(name) {
        if (userColors.has(name)) return userColors.get(name);
        const color = `hsl(${Math.floor(Math.random()*360)}, 70%, 50%)`;
        userColors.set(name, color);
        return color;
    }

    function removeModal() {
        const modal = window.top.document.querySelector("#commentModal");
        if (modal) modal.remove();
        const settings = window.top.document.querySelector("#settingsModal");
        if (settings) settings.remove();
        listContainer = null;
        seenComments.clear();
    }

    function openSettings() {
        if (window.top.document.querySelector("#settingsModal")) return;

        const settingsBox = window.top.document.createElement("div");
        settingsBox.id = "settingsModal";
        settingsBox.style.cssText = `
            position: fixed !important;
            top: 160px;
            left: 50%;
            transform: translateX(-50%);
            width: 360px;
            height: 280px;
            background: #fff;
            border: 2px solid #444;
            border-radius: 8px;
            box-shadow: 0 4px 15px rgba(0,0,0,0.3);
            z-index: 2147483647 !important;
            display: flex;
            flex-direction: column;
            font-family: sans-serif;
        `;

        settingsBox.innerHTML = `
            <div id="settingsHeader" style="
                cursor: move;
                padding: 10px;
                background: #2c7;
                color: #fff;
                font-weight: bold;
                display: flex;
                justify-content: space-between;
                align-items: center;
                user-select: none;
            ">
                <span>멤버 설정</span>
                <button id="closeSettingsBtn" style="background:none;border:none;color:#fff;font-size:14px;cursor:pointer;">✕</button>
            </div>
            <div style="flex:1; padding:10px; display:flex; flex-direction:column; gap:10px;">
                <textarea id="userListInput" style="flex:1; resize:none; font-size:14px; padding:5px;">${targetUsers.join("\n")}</textarea>
                <button id="saveUsersBtn" style="align-self:flex-end; padding:5px 12px; cursor:pointer; border:1px solid #ccc; border-radius:4px; background:#f5f5f5;">저장</button>
            </div>
        `;

        window.top.document.body.appendChild(settingsBox);

        settingsBox.querySelector("#saveUsersBtn").addEventListener("click", () => {
            const newList = settingsBox.querySelector("#userListInput").value
                .split("\n")
                .map(s => s.trim())
                .filter(Boolean);
            targetUsers = newList;
            saveUsers(newList);
            settingsBox.remove();
        });

        settingsBox.querySelector("#closeSettingsBtn").addEventListener("click", () => {
            settingsBox.remove();
        });

        const header = settingsBox.querySelector("#settingsHeader");
        let isDragging = false, offsetX = 0, offsetY = 0;

        header.addEventListener("mousedown", (e) => {
            if (e.target.tagName === "BUTTON") return;
            isDragging = true;
            const rect = settingsBox.getBoundingClientRect();
            offsetX = e.clientX - rect.left;
            offsetY = e.clientY - rect.top;
            window.top.document.body.style.userSelect = "none";
        });

        window.top.document.addEventListener("mousemove", (e) => {
            if (isDragging) {
                settingsBox.style.left = `${e.clientX - offsetX}px`;
                settingsBox.style.top = `${e.clientY - offsetY}px`;
                settingsBox.style.right = "auto";
                settingsBox.style.transform = "none";
            }
        });

        window.top.document.addEventListener("mouseup", () => {
            isDragging = false;
            window.top.document.body.style.userSelect = "";
        });
    }

    function openModal() {
        if (window.top.document.querySelector("#commentModal")) return;

        const modalBox = window.top.document.createElement("div");
        modalBox.id = "commentModal";
        modalBox.style.cssText = `
            position: fixed !important;
            top: 100px;
            right: 10px;
            width: 420px;
            height: 600px;
            background: #fff;
            border: 2px solid #444;
            border-radius: 8px;
            box-shadow: 0 4px 15px rgba(0,0,0,0.3);
            z-index: 2147483647 !important;
            display: flex;
            flex-direction: column;
            font-family: sans-serif;
            transition: height 0.3s ease;
        `;

        modalBox.innerHTML = `
            <div id="modalHeader" style="
                cursor: move;
                padding: 10px;
                background: #2c7;
                color: #fff;
                font-weight: bold;
                display: flex;
                justify-content: space-between;
                align-items: center;
                user-select: none;
            ">
                <span>멤버 댓글 모아보기</span>
                <div>
                    <button id="openSettingsBtn" style="background:none;border:none;color:#fff;font-size:14px;cursor:pointer;margin-right:5px;">멤버설정⚙</button>
                    <button id="toggleModal" style="background:none;border:none;color:#fff;font-size:14px;cursor:pointer;">⬆숨기기</button>
                </div>
            </div>
            <div id="comment-list" style="flex:1; overflow-y:auto; padding:5px; font-size:14px; line-height:1.4;"></div>
        `;

        window.top.document.body.appendChild(modalBox);

        listContainer = modalBox.querySelector("#comment-list");

        modalBox.querySelector("#openSettingsBtn").addEventListener("click", openSettings);

        modalBox.querySelector("#toggleModal").addEventListener("click", (e) => {
            collapsed = !collapsed;
            if (collapsed) {
                modalBox.style.height = "50px";
                modalBox.style.overflow = "hidden";
                e.target.innerText = "⬇펼치기";
            } else {
                modalBox.style.height = "600px";
                modalBox.style.overflow = "visible";
                e.target.innerText = "⬆숨기기";
            }
        });

        const header = modalBox.querySelector("#modalHeader");
        let isDragging = false, offsetX = 0, offsetY = 0;

        header.addEventListener("mousedown", (e) => {
            if (e.target.tagName === "BUTTON") return;
            isDragging = true;
            const rect = modalBox.getBoundingClientRect();
            offsetX = e.clientX - rect.left;
            offsetY = e.clientY - rect.top;
            window.top.document.body.style.userSelect = "none";
        });

        window.top.document.addEventListener("mousemove", (e) => {
            if (isDragging) {
                modalBox.style.left = `${e.clientX - offsetX}px`;
                modalBox.style.top = `${e.clientY - offsetY}px`;
                modalBox.style.right = "auto";
            }
        });

        window.top.document.addEventListener("mouseup", () => {
            isDragging = false;
            window.top.document.body.style.userSelect = "";
        });
    }

    function collectComments() {
        openModal();

        const comments = document.querySelectorAll(".comment_area, li.CommentItem, div.CommentItem");

        comments.forEach(comment => {
            const nicknameEl = comment.querySelector('[class*="nickname"], .comment_nickname');
            const textEl = comment.querySelector('[class*="text"], .text_comment');
            const timeEl = comment.querySelector('[class*="date"], .comment_date, .comment_info_date');
            const avatarEl = comment.querySelector("img");

            const nickname = (nicknameEl?.innerText || "").trim();
            const content = (textEl?.innerText || "").trim();
            const time = (timeEl?.innerText || "").trim();

            if (nickname && content && targetUsers.includes(nickname)) {
                const key = nickname + "::" + content + "::" + time;
                if (seenComments.has(key)) return;
                seenComments.add(key);

                const color = getUserColor(nickname);

                const item = document.createElement("div");
                item.style.borderBottom = "1px solid #eee";
                item.style.padding = "5px 0";

                const id = "comment-" + Math.random().toString(36).slice(2);
                comment.setAttribute("data-comment-id", id);

                let avatarHTML = "";
                if (avatarEl) {
                    avatarHTML = `<img src="${avatarEl.src}" style="width:25px;height:25px;border-radius:50%;margin-right:3px;">`;
                }

                item.innerHTML = `
                    <div style="font-weight:bold;margin-bottom:3px;display:flex;align-items:center;gap:6px;">
                        ${avatarHTML}
                        <span style="color:${color};">${nickname}</span>
                        <span style="color:#888;font-size:12px;margin-left:auto;">${time}</span>
                        <button style="margin-left:6px;padding:2px 6px;font-size:12px;cursor:pointer;border:1px solid #ccc;border-radius:4px;background:#f5f5f5;" data-target="${id}">이동</button>
                    </div>
                    <div>${content}</div>
                `;

                listContainer.appendChild(item);

                item.querySelector("button").addEventListener("click", (e) => {
                    const targetId = e.target.getAttribute("data-target");
                    const targetEl = document.querySelector(`[data-comment-id="${targetId}"]`);
                    if (targetEl) {
                        targetEl.scrollIntoView({behavior: "smooth", block: "center"});
                        targetEl.style.backgroundColor = "yellow";
                        setTimeout(() => targetEl.style.backgroundColor = "", 1000);
                    }
                });
            }
        });
    }

    function debounce(func, delay) {
        let timer;
        return function(...args) {
            clearTimeout(timer);
            timer = setTimeout(() => func.apply(this, args), delay);
        };
    }

    let lastUrl = location.href;
    new MutationObserver(() => {
        if (location.href !== lastUrl) {
            lastUrl = location.href;
            removeModal();
        }
    }).observe(document, {subtree: true, childList: true});

    window.addEventListener("load", () => {
        openModal();
        collectComments();

        const commentContainer = document.querySelector(".comment_list_area") || document.body;
        const observer = new MutationObserver(
            debounce(() => {
                collectComments();
            }, 500)
        );
        observer.observe(commentContainer, { childList: true, subtree: true });
    });
})();