您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
네이버 카페 게시물에서 특정 유저 댓글을 모아보기
// ==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 }); }); })();