您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
在Coupang商品详情页定位指定reviewId的评论,滚动并高亮。新增功能:1. 在侧边面板显示所有评论ID并支持点击跳转。 2. 快速查找并定位带有"TOP"标识的优质评论。
// ==UserScript== // @name Coupang 评论助手 // @name:zh-CN Coupang 评论助手 // @namespace http://tampermonkey.net/ // @version 1.0 // @description 在Coupang商品详情页定位指定reviewId的评论,滚动并高亮。新增功能:1. 在侧边面板显示所有评论ID并支持点击跳转。 2. 快速查找并定位带有"TOP"标识的优质评论。 // @description:zh-CN 在Coupang商品详情页定位指定reviewId的评论,滚动并高亮。新增功能:1. 在侧边面板显示所有评论ID并支持点击跳转。 2. 快速查找并定位带有"TOP"标识的优质评论。 // @license MIT // @author nobody // @match https://www.coupang.com/vp/products/* // @grant GM_addStyle // ==/UserScript== (function() { 'use strict'; console.log('[Coupang Review Finder] 脚本运行中...'); const SINGLE_REVIEW_BLOCK_SELECTOR = 'article.sdp-review__article__list.js_reviewArticleReviewList'; const REVIEW_ID_CONTAINER_SELECTOR = '.sdp-review__article__list__help'; // 这个元素通常包含data-review-id const REVIEW_ID_ATTRIBUTE = 'data-review-id'; const HIGHLIGHT_CLASS = 'tm-review-highlight'; // 高亮样式类名 const REVIEW_ID_LIST_PANEL_ID = 'tm-review-id-list-panel'; // ID 列表面板的ID const TOP_REVIEW_BADGE_SELECTOR = '.sdp-review__article__list__info__top-badge'; // Top评论徽章选择器 let highlightTimeoutId = null; // 用于存储高亮setTimeout的ID let currentHighlightedElement = null; // 用于追踪当前高亮的元素(临时高亮结束后会清空) let topReviewElements = []; // 存储所有Top评论元素 let currentTopReviewIndex = -1; // 当前高亮的Top评论索引 // 注入CSS用于评论高亮和按钮/面板样式 GM_addStyle(` .${HIGHLIGHT_CLASS} { border: 2px solid #007bff !important; /* 蓝色边框 */ box-shadow: 0 0 10px rgba(0, 123, 255, 0.5) !important; /* 蓝色阴影 */ transition: all 0.2s ease-in-out; /* 平滑过渡效果 */ } /* 保持按钮美观的共同样式 */ .tm-userscript-button { position: fixed; padding: 10px; background-color: #007bff; color: white; border: none; border-radius: 5px; cursor: pointer; font-size: 14px; box-shadow: 0 2px 5px rgba(0,0,0,0.2); font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; z-index: 99999; transition: background-color 0.2s ease; text-align: center; } .tm-userscript-button:hover { background-color: #0056b3; } .tm-userscript-button:active { box-shadow: 0 1px 3px rgba(0,0,0,0.2) inset; } /* 评论ID列表面板样式 */ #${REVIEW_ID_LIST_PANEL_ID} { position: fixed; top: 160px; /* 在所有按钮下方 */ left: 10px; z-index: 99998; /* 比按钮低一点 */ width: 200px; /* 固定宽度 */ max-height: 400px; /* 最大高度,允许滚动 */ overflow-y: auto; /* 超出则垂直滚动 */ background-color: #f9f9f9; border: 1px solid #ddd; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); padding: 10px; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; font-size: 13px; line-height: 1.6; color: #333; display: none; /* 默认隐藏 */ } #${REVIEW_ID_LIST_PANEL_ID} .tm-review-id-item { display: block; padding: 6px 8px; margin-bottom: 4px; background-color: #e9ecef; border-radius: 4px; cursor: pointer; transition: background-color 0.2s ease, transform 0.1s ease; word-break: break-all; /* 防止长ID溢出 */ text-decoration: none; color: #212529; } #${REVIEW_ID_LIST_PANEL_ID} .tm-review-id-item:hover { background-color: #dee2e6; transform: translateY(-1px); } `); /** * 在评论列表中查找指定 reviewId 的评论元素。 * @param {string} reviewId 要查找的评论ID。 * @returns {HTMLElement|null} 找到的评论元素,如果未找到则返回 null。 */ function findReviewElementById(reviewId) { const reviewBlocks = document.querySelectorAll(SINGLE_REVIEW_BLOCK_SELECTOR); console.log(`[Coupang Review Finder] 发现 ${reviewBlocks.length} 个评论块进行检查。`); for (let i = 0; i < reviewBlocks.length; i++) { const reviewBlock = reviewBlocks[i]; let currentReviewId = reviewBlock.getAttribute(REVIEW_ID_ATTRIBUTE); if (!currentReviewId) { const idContainer = reviewBlock.querySelector(REVIEW_ID_CONTAINER_SELECTOR); if (idContainer) { currentReviewId = idContainer.getAttribute(REVIEW_ID_ATTRIBUTE); } } if (currentReviewId === reviewId) { return reviewBlock; } } return null; } /** * 清除所有正在进行的高亮效果和计时器。 */ function clearHighlight() { if (highlightTimeoutId) { clearTimeout(highlightTimeoutId); highlightTimeoutId = null; } if (currentHighlightedElement) { currentHighlightedElement.classList.remove(HIGHLIGHT_CLASS); currentHighlightedElement = null; } console.log('[Coupang Review Finder] 已清除之前的高亮。'); } /** * 滚动到指定评论元素并给予短暂的高亮效果。 * @param {HTMLElement} element 要高亮的评论元素。 * @param {string} reviewId 评论ID,用于日志记录。 */ function highlightAndScrollToReview(element, reviewId) { clearHighlight(); // 在高亮新元素前清除所有旧的 console.log(`[Coupang Review Finder] 滚动并高亮评论: ${reviewId}`); element.scrollIntoView({ behavior: 'smooth', block: 'center' }); // 给一个短暂的延迟,确保滚动完成后再添加高亮 setTimeout(() => { if (element && document.body.contains(element)) { // 再次检查元素是否存在于DOM中 element.classList.add(HIGHLIGHT_CLASS); currentHighlightedElement = element; // 标记当前高亮的元素 // 设置一个计时器,在高亮2秒后自动移除 highlightTimeoutId = setTimeout(() => { if (currentHighlightedElement) { currentHighlightedElement.classList.remove(HIGHLIGHT_CLASS); currentHighlightedElement = null; console.log(`[Coupang Review Finder] 评论 ${reviewId} 高亮已移除。`); } }, 2000); // 2秒后移除高亮 } else { console.warn(`[Coupang Review Finder] 尝试高亮评论 ${reviewId} 失败:元素不再存在于DOM。`); } }, 500); // 0.5秒延迟 } /** * 创建并添加一个浮动按钮到页面,用于触发评论查找和高亮功能。 */ function createFindAndHighlightButton() { const button = document.createElement('button'); button.className = 'tm-userscript-button'; button.innerText = '查找并高亮评论'; button.style.top = '10px'; button.style.left = '10px'; document.body.appendChild(button); button.addEventListener('click', () => { hideReviewIdListPanel(); // 隐藏ID列表面板 const reviewId = prompt('请输入要查找的评论 reviewId (例如: 756185841):'); if (!reviewId) { console.log('[Coupang Review Finder] 用户未输入 reviewId。操作取消。'); return; } console.log(`[Coupang Review Finder] 开始查找 reviewId: ${reviewId}`); const targetReviewElement = findReviewElementById(reviewId); if (targetReviewElement) { highlightAndScrollToReview(targetReviewElement, reviewId); // 临时改变按钮文本,告知用户 button.innerText = `找到并高亮评论 ${reviewId}!`; setTimeout(() => { button.innerText = '查找并高亮评论'; // 恢复按钮文本 }, 3000); // 3秒后恢复 } else { console.warn(`[Coupang Review Finder] 未找到 reviewId 为 ${reviewId} 的评论。`); button.innerText = `未找到评论 ${reviewId}!`; setTimeout(() => { button.innerText = '查找并高亮评论'; // 恢复按钮文本 }, 3000); // 3秒后恢复 } }); } let reviewIdListPanel = null; // 存储评论ID列表面板元素 /** * 创建并获取评论ID列表面板元素。 * @returns {HTMLElement} 评论ID列表面板。 */ function getOrCreateReviewIdListPanel() { if (reviewIdListPanel) return reviewIdListPanel; reviewIdListPanel = document.createElement('div'); reviewIdListPanel.id = REVIEW_ID_LIST_PANEL_ID; document.body.appendChild(reviewIdListPanel); // 点击面板外部关闭面板 document.addEventListener('click', (event) => { const listButton = document.querySelector('[data-role="show-review-ids-button"]'); if (reviewIdListPanel && reviewIdListPanel.style.display !== 'none' && !reviewIdListPanel.contains(event.target) && !listButton.contains(event.target)) { hideReviewIdListPanel(); } }); return reviewIdListPanel; } /** * 生成并显示评论ID到面板中。 * @param {Array<string>} reviewIds 评论ID数组。 */ function displayReviewIdsInPanel(reviewIds) { const panel = getOrCreateReviewIdListPanel(); panel.innerHTML = ''; // 清空旧内容 if (reviewIds.length === 0) { panel.innerHTML = '<p style="margin:0; text-align: center;">未找到评论ID。</p>'; } else { reviewIds.forEach(id => { const idItem = document.createElement('a'); // 使用a标签更像链接 idItem.href = '#'; // 无实际跳转 idItem.className = 'tm-review-id-item'; idItem.textContent = id; idItem.title = `点击跳转到评论 ${id}`; idItem.addEventListener('click', (event) => { event.preventDefault(); // 阻止默认的链接跳转行为 const targetElement = findReviewElementById(id); if (targetElement) { highlightAndScrollToReview(targetElement, id); hideReviewIdListPanel(); // 点击后隐藏面板 } else { console.warn(`[Coupang Review Finder] 评论ID ${id} 已失效或未加载。`); const originalText = idItem.textContent; idItem.textContent = `${originalText} (未找到)`; idItem.style.backgroundColor = '#ffc0cb'; // 红色背景提示 setTimeout(() => { idItem.textContent = originalText; idItem.style.backgroundColor = ''; }, 2000); } }); panel.appendChild(idItem); }); } panel.style.display = 'block'; // 显示面板 } /** * 隐藏评论ID列表面板。 */ function hideReviewIdListPanel() { if (reviewIdListPanel) { reviewIdListPanel.style.display = 'none'; } } /** * 创建并添加一个浮动按钮到页面,用于显示所有评论ID。 */ function createListReviewIdsButton() { const button = document.createElement('button'); button.className = 'tm-userscript-button'; button.innerText = '显示所有评论ID'; button.style.top = '60px'; // 放置在第一个按钮下方 button.style.left = '10px'; button.setAttribute('data-role', 'show-review-ids-button'); // 用于判断点击是否在按钮上 document.body.appendChild(button); button.addEventListener('click', (event) => { event.stopPropagation(); // 阻止事件冒泡到document的点击监听器 if (reviewIdListPanel && reviewIdListPanel.style.display === 'block') { hideReviewIdListPanel(); // 如果已经显示,则隐藏 // 恢复按钮文本 button.innerText = '显示所有评论ID'; } else { clearHighlight(); // 显示面板时清除高亮 const reviewBlocks = document.querySelectorAll(SINGLE_REVIEW_BLOCK_SELECTOR); const reviewIds = []; reviewBlocks.forEach(reviewBlock => { let currentReviewId = reviewBlock.getAttribute(REVIEW_ID_ATTRIBUTE); if (!currentReviewId) { const idContainer = reviewBlock.querySelector(REVIEW_ID_CONTAINER_SELECTOR); if (idContainer) { currentReviewId = idContainer.getAttribute(REVIEW_ID_ATTRIBUTE); } } if (currentReviewId) { reviewIds.push(currentReviewId); } }); displayReviewIdsInPanel(reviewIds); if (reviewIds.length > 0) { console.log(`%c[Coupang Review Finder] 当前页面找到 ${reviewIds.length} 个评论ID (已显示在面板中):`, 'color: green; font-weight: bold;'); console.log(reviewIds.join(', ')); button.innerText = `已找到 ${reviewIds.length} 个ID`; } else { console.log('%c[Coupang Review Finder] 当前页面未找到任何评论ID。', 'color: orange; font-weight: bold;'); button.innerText = '未找到评论ID!'; } } }); } /** * 创建并添加一个浮动按钮到页面,用于查找Top评论。 */ function createFindTopReviewButton() { const button = document.createElement('button'); button.className = 'tm-userscript-button'; button.innerText = '查找Top评论'; button.style.top = '110px'; // 放置在第三个位置 button.style.left = '10px'; document.body.appendChild(button); button.addEventListener('click', () => { // 每次点击时都重新查找,以应对动态加载的评论 topReviewElements = Array.from(document.querySelectorAll(SINGLE_REVIEW_BLOCK_SELECTOR)) .filter(el => el.querySelector(TOP_REVIEW_BADGE_SELECTOR)); if (topReviewElements.length === 0) { button.innerText = '未找到Top评论'; setTimeout(() => { button.innerText = '查找Top评论'; }, 2000); return; } currentTopReviewIndex++; if (currentTopReviewIndex >= topReviewElements.length) { currentTopReviewIndex = 0; // 从头开始循环 } const targetReview = topReviewElements[currentTopReviewIndex]; const reviewId = targetReview.querySelector(REVIEW_ID_CONTAINER_SELECTOR)?.getAttribute(REVIEW_ID_ATTRIBUTE) || `Top评论 #${currentTopReviewIndex + 1}`; highlightAndScrollToReview(targetReview, reviewId); button.innerText = `高亮Top评论 (${currentTopReviewIndex + 1}/${topReviewElements.length})`; }); } // A small initial delay to allow the page to render basic structure window.addEventListener('load', () => { // 使用 MutationObserver 来监听 DOM 变化,更可靠地等待评论区出现 const targetNode = document.body; const config = { childList: true, subtree: true }; const createButtonsOnce = () => { // 检查是否已经创建过按钮 if (document.querySelector('.tm-userscript-button')) { return; } console.log('[Coupang Review Finder] 评论区域检测到,创建按钮。'); createFindAndHighlightButton(); createListReviewIdsButton(); createFindTopReviewButton(); // 立即创建面板容器,但默认隐藏 getOrCreateReviewIdListPanel(); } const callback = function(mutationsList, observer) { const existingReviewBlock = document.querySelector(SINGLE_REVIEW_BLOCK_SELECTOR); if (existingReviewBlock) { observer.disconnect(); // 找到后停止观察,避免重复创建 createButtonsOnce(); } }; // 仅在脚本加载后等待一小段时间,然后再启动观察,以避免在页面初次加载时过早触发 setTimeout(() => { const initialReviewBlocks = document.querySelector(SINGLE_REVIEW_BLOCK_SELECTOR); if (initialReviewBlocks) { console.log('[Coupang Review Finder] 评论区域已在初始加载时存在,直接创建按钮。'); createButtonsOnce(); } else { console.log('[Coupang Review Finder] 评论区域尚未加载,启动 MutationObserver 监听。'); const observer = new MutationObserver(callback); observer.observe(targetNode, config); // 设置一个备用超时,以防 MutationObserver 错过了(不太可能,但有个保险) setTimeout(() => { if (!document.querySelector('.tm-userscript-button')) { // 检查按钮是否已创建 console.warn('[Coupang Review Finder] MutationObserver 超时或未检测到评论,尝试强制创建按钮。'); createButtonsOnce(); observer.disconnect(); // 停止观察 } }, 10000); // 10秒后强制创建 } }, 1000); // 页面加载后1秒再启动检查 }); })();