// ==UserScript== // @name 小红书点赞收藏读取器-悬浮窗修复(保留原版样式) // @namespace http://tampermonkey.net/ // @version 7.3 // @description 手机端专用,从__INITIAL_STATE__和DOM双重读取点赞和收藏数据,防止PC重定向|悬浮窗防消失,不改动原有界面样式 // @author You // @match https://www.xiaohongshu.com/* // @match https://m.xiaohongshu.com/* // @match http://www.xiaohongshu.com/* // @match http://m.xiaohongshu.com/* // @run-at document-start // @grant none // @license All Rights Reserved // ==/UserScript== (function() { 'use strict'; var LOG_PREFIX = '[小红书数据]'; var cachedInitialState = null; var isInitialized = false; var pollTimer = null; var observer = null; var pageSourceFetched = false; var lastResult = null; // ===================== 悬浮窗防护新增逻辑(不干扰UI) ===================== const FLOAT_PANEL_ID = "xhs-stats-float-panel"; const DEBUG_PANEL_ID = "xhs-debug-panel"; let floatRebuildTimer = null; let floatGuardObserver = null; const GUARD_INTERVAL = 600; // 防抖重建悬浮窗,不改动样式 function debounceRebuildFloat() { clearTimeout(floatRebuildTimer); floatRebuildTimer = setTimeout(() => { const exist = document.getElementById(FLOAT_PANEL_ID); if (!exist || !exist.parentElement) { createFloatPanel(); logDebug("悬浮窗丢失自动重建"); } }, 120); } // 拦截原生删除方法,仅拦截移除悬浮窗,不影响页面其他元素 const originElRemove = Element.prototype.remove; Element.prototype.remove = function () { if (this.id === FLOAT_PANEL_ID || this.id === DEBUG_PANEL_ID || this.querySelector(`#${FLOAT_PANEL_ID}`)) { debounceRebuildFloat(); return; } return originElRemove.call(this); }; const originRemoveChild = Node.prototype.removeChild; Node.prototype.removeChild = function (child) { const res = originRemoveChild.call(this, child); if (child.id === FLOAT_PANEL_ID || child.id === DEBUG_PANEL_ID) { debounceRebuildFloat(); } return res; }; // 拦截body清空innerHTML const originInnerHTML = Object.getOwnPropertyDescriptor(Element.prototype, "innerHTML").set; Object.defineProperty(Element.prototype, "innerHTML", { set: function (val) { originInnerHTML.call(this, val); if (this.tagName === "BODY") debounceRebuildFloat(); } }); // 全局DOM守护监听 function initFloatGuard() { if (floatGuardObserver) floatGuardObserver.disconnect(); floatGuardObserver = new MutationObserver(mutList => { for (const mut of mutList) { if (mut.removedNodes.length > 0) { debounceRebuildFloat(); break; } } }); floatGuardObserver.observe(document.documentElement, { childList: true, subtree: true }); // 定时巡检兜底 setInterval(debounceRebuildFloat, GUARD_INTERVAL); } // ================================================================ // 1. URL 保护(原版原样) // ================================================================ var originalPath = location.pathname; var originalURL = location.href; var _pushState = history.pushState; var _replaceState = history.replaceState; function shouldBlockURL(url) { if (!url || typeof url !== 'string') return false; try { var newURL = new URL(url, location.origin); if (originalPath.indexOf('/discovery/item/') !== -1 && newURL.pathname.indexOf('/discovery/item/') === -1) { return true; } return false; } catch(e) { return false; } } try { history.pushState = function(state, title, url) { if (shouldBlockURL(url)) { console.log(LOG_PREFIX + ' 拦截URL变更(pushState): ' + url); return; } return _pushState.apply(this, arguments); }; history.replaceState = function(state, title, url) { if (shouldBlockURL(url)) { console.log(LOG_PREFIX + ' 拦截URL变更(replaceState): ' + url); return; } return _replaceState.apply(this, arguments); }; window.addEventListener('popstate', function(e) { if (location.pathname.indexOf('/discovery/item/') === -1 && originalPath.indexOf('/discovery/item/') !== -1) { history.replaceState(null, '', originalURL); } debounceRebuildFloat(); }); } catch(e) { console.log(LOG_PREFIX + ' URL保护初始化失败: ' + e.message); } // ================================================================ // 2. 捕获 __INITIAL_STATE__(原版原样) // ================================================================ function deepCloneState(state) { try { return JSON.parse(JSON.stringify(state, function(k, v) { return v === undefined ? null : v; })); } catch(e) { return null; } } function tryCaptureState() { if (cachedInitialState) return true; try { if (window.__INITIAL_STATE__) { cachedInitialState = deepCloneState(window.__INITIAL_STATE__); if (cachedInitialState) { console.log(LOG_PREFIX + ' 已捕获 __INITIAL_STATE__'); return true; } } } catch(e) {} return false; } try { if (typeof window.__INITIAL_STATE__ === 'undefined') { var _state = undefined; Object.defineProperty(window, '__INITIAL_STATE__', { get: function() { return _state; }, set: function(v) { _state = v; cachedInitialState = deepCloneState(v); if (cachedInitialState) { console.log(LOG_PREFIX + ' 拦截捕获 __INITIAL_STATE__'); } }, configurable: true, enumerable: true }); } else { tryCaptureState(); } } catch(e) { tryCaptureState(); } var captureAttempts = 0; var captureInterval = setInterval(function() { if (tryCaptureState() || ++captureAttempts > 120) { clearInterval(captureInterval); } }, 50); // ================================================================ // 3. 工具函数(原版原样) // ================================================================ function parseCount(text) { if (!text) return null; text = String(text).trim().replace(/,/g, ''); if (text.indexOf('\u4e07') !== -1) { var num = parseFloat(text.replace('\u4e07', '').replace('+', '')); return Math.round(num * 10000); } if (/[wW]/.test(text)) { var num2 = parseFloat(text.replace(/[wW]/, '').replace('+', '')); return Math.round(num2 * 10000); } var n = parseInt(text); return isNaN(n) ? null : n; } function isValidNumberText(text) { if (!text) return false; text = text.trim(); return /^[\d\u96f6\u4e00\u4e8c\u4e09\u56db\u4e94\u516d\u4e03\u516b\u4e5d\u5341\u767e\u5343]+/.test(text) || /^\d/.test(text.replace(/[\u4e07wW\+,]/g, '')); } function safeClassName(el) { if (!el || !el.className) return ''; if (typeof el.className === 'string') return el.className; if (el.className.baseVal) return el.className.baseVal; return ''; } // ================================================================ // 4. 调试日志(原版原样) // ================================================================ var debugPanel = null; function logDebug(msg) { var time = new Date().toLocaleTimeString(); console.log(LOG_PREFIX + ' ' + msg); if (debugPanel) { var line = document.createElement('div'); line.textContent = '[' + time + '] ' + msg; line.style.cssText = 'font-size:11px;color:#666;border-bottom:1px solid #eee;padding:2px 0;word-break:break-all;'; debugPanel.appendChild(line); if (debugPanel.children.length > 80) debugPanel.removeChild(debugPanel.children[1]); debugPanel.scrollTop = debugPanel.scrollHeight; } } // ================================================================ // 5. 悬浮窗UI【100%还原你原始样式,仅外层加防覆盖层级,不修改内部布局】 // ================================================================ var floatPanel = null; var isMinimized = false; var dragOffset = { x: 0, y: 0 }; var isDragging = false; var BRIDGE_ID = 'xhs-autojs-bridge'; function createFloatPanel() { const oldPanel = document.getElementById(FLOAT_PANEL_ID); if (oldPanel) oldPanel.remove(); if (!document.documentElement) return null; var panel = document.createElement('div'); panel.id = FLOAT_PANEL_ID; panel.innerHTML = '
' + '小红书数据' + '' + '?' + '-' + '
' + '
' + '
' + '点赞' + '-
' + '
' + '收藏' + '-
' + '
💬' + '评论' + '-
' + '
' + '
'; var bridge = document.createElement('div'); bridge.id = BRIDGE_ID; bridge.textContent = 'XHS_PLUGIN_STATS|LIKE=-1|FAVORITE=-1|COMMENT=-1|SOURCE=init'; var style = document.createElement('style'); // 核心:只给最外层容器加防覆盖属性,内部全部使用你原版CSS,不添加all:unset破坏样式 style.textContent = // 仅外层防护,不污染内部元素 `#${FLOAT_PANEL_ID}{position:fixed;top:100px;right:10px;width:160px; background:rgba(255,255,255,0.98);border-radius:10px; box-shadow:0 3px 14px rgba(0,0,0,0.18); font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif; z-index:2147483647;user-select:none;overflow:hidden; border:1px solid rgba(0,0,0,0.05);pointer-events:auto; transform:translateZ(99999px);-webkit-transform:translateZ(99999px);will-change:transform;} #${FLOAT_PANEL_ID}.xhs-minimized{width:100px} #${FLOAT_PANEL_ID}.xhs-minimized .xhs-float-body{display:none} .xhs-float-header{display:flex;justify-content:space-between;align-items:center; padding:8px 12px;background:linear-gradient(135deg,#ff2442 0%,#ff6b81 100%); color:#fff;font-size:13px;font-weight:600;cursor:move} .xhs-float-title{pointer-events:none} .xhs-float-btns{display:flex;gap:5px;align-items:center} .xhs-float-toggle,.xhs-float-debug{cursor:pointer;width:20px;height:20px; display:flex;align-items:center;justify-content:center; border-radius:3px;font-size:13px;transition:background 0.2s} .xhs-float-toggle:hover,.xhs-float-debug:hover{background:rgba(255,255,255,0.2)} .xhs-float-body{padding:8px 12px} .xhs-float-row{display:flex;align-items:center;gap:6px; padding:6px 0;border-bottom:1px solid #f0f0f0} .xhs-float-row:last-of-type{border-bottom:none} .xhs-float-icon{font-size:14px;width:20px;text-align:center} .xhs-float-row:nth-child(1) .xhs-float-icon{color:#ff2442} .xhs-float-row:nth-child(2) .xhs-float-icon{color:#ffb800} .xhs-float-row:nth-child(3) .xhs-float-icon{color:#5b8def} .xhs-float-label{font-size:12px;color:#666;flex:1} .xhs-float-value{font-size:14px;font-weight:700;color:#333;min-width:24px;text-align:right} .xhs-float-tip{font-size:10px;color:#999;margin-top:4px;padding-top:4px; border-top:1px dashed #eee;min-height:14px} #${BRIDGE_ID}{position:fixed;top:56px;left:8px;max-width:95vw;z-index:2147483645; background:rgba(255,244,180,0.96);color:#111;border:1px solid rgba(0,0,0,0.12); border-radius:6px;padding:4px 8px;font-size:12px;font-weight:700;line-height:1.35; white-space:nowrap;pointer-events:none;transform:translateZ(99999px)} #${DEBUG_PANEL_ID}{position:fixed;bottom:10px;left:10px;width:95%;max-width:400px; max-height:220px;background:rgba(255,255,255,0.98);border:1px solid #ddd; border-radius:8px;box-shadow:0 3px 12px rgba(0,0,0,0.12); z-index:2147483646;font-family:monospace;font-size:11px; padding:8px;overflow-y:auto;display:none;transform:translateZ(99999px);} #${DEBUG_PANEL_ID}.show{display:block} #xhs-debug-panel-title{font-weight:bold;color:#ff2442;margin-bottom:4px; border-bottom:1px solid #eee;padding-bottom:3px}`; document.head.appendChild(style); // 挂载html根节点,避免body刷新带走 document.documentElement.appendChild(panel); document.documentElement.appendChild(bridge); floatPanel = panel; // 拖拽逻辑原版不变 var header = panel.querySelector('.xhs-float-header'); var startDrag = function(e) { if (e.target.closest('.xhs-float-toggle,.xhs-float-debug')) return; isDragging = true; var clientX = e.touches ? e.touches[0].clientX : e.clientX; var clientY = e.touches ? e.touches[0].clientY : e.clientY; var rect = panel.getBoundingClientRect(); dragOffset.x = clientX - rect.left; dragOffset.y = clientY - rect.top; }; var moveDrag = function(e) { if (!isDragging) return; var clientX = e.touches ? e.touches[0].clientX : e.clientX; var clientY = e.touches ? e.touches[0].clientY : e.clientY; var x = clientX - dragOffset.x; var y = clientY - dragOffset.y; var maxX = window.innerWidth - panel.offsetWidth; var maxY = window.innerHeight - panel.offsetHeight; x = Math.max(0, Math.min(x, maxX)); y = Math.max(0, Math.min(y, maxY)); panel.style.left = x + 'px'; panel.style.top = y + 'px'; panel.style.right = 'auto'; }; var endDrag = function() { isDragging = false; }; header.addEventListener('mousedown', startDrag); header.addEventListener('touchstart', startDrag, { passive: true }); document.addEventListener('mousemove', moveDrag); document.addEventListener('touchmove', moveDrag, { passive: true }); document.addEventListener('mouseup', endDrag); document.addEventListener('touchend', endDrag); // 最小化按钮 var toggleBtn = panel.querySelector('.xhs-float-toggle'); toggleBtn.addEventListener('click', function() { isMinimized = !isMinimized; panel.classList.toggle('xhs-minimized', isMinimized); toggleBtn.textContent = isMinimized ? '+' : '-'; }); // 调试按钮 var debugBtn = panel.querySelector('.xhs-float-debug'); debugBtn.addEventListener('click', function() { var dp = document.getElementById(DEBUG_PANEL_ID); if (dp) dp.classList.toggle('show'); }); // 调试面板 var dp = document.createElement('div'); dp.id = DEBUG_PANEL_ID; dp.innerHTML = '
调试日志 (点 ? 关闭)
'; document.documentElement.appendChild(dp); debugPanel = dp; return panel; } function updateBridgeOutput(likeVal, collectVal, commentVal, source, tip) { var bridge = document.getElementById(BRIDGE_ID); if (!bridge && document.documentElement) { bridge = document.createElement('div'); bridge.id = BRIDGE_ID; document.documentElement.appendChild(bridge); } var likeOut = likeVal !== null && likeVal !== undefined ? likeVal : -1; var collectOut = collectVal !== null && collectVal !== undefined ? collectVal : -1; var commentOut = commentVal !== null && commentVal !== undefined ? commentVal : -1; var srcOut = source || 'unknown'; var payload = 'XHS_PLUGIN_STATS|LIKE=' + likeOut + '|FAVORITE=' + collectOut + '|COMMENT=' + commentOut + '|SOURCE=' + srcOut; if (bridge) bridge.textContent = payload; try { document.documentElement.setAttribute('data-xhs-plugin-stats', payload); } catch(e) {} try { window.__XHS_PLUGIN_STATS__ = { like: likeOut, favorite: collectOut, comment: commentOut, source: srcOut, tip: tip || '' }; } catch(e2) {} } function updateFloatPanel(likeVal, collectVal, commentVal, tip, source) { var panel = floatPanel || createFloatPanel(); if (!panel) return; var likeEl = panel.querySelector('#xhs-like-value'); var collectEl = panel.querySelector('#xhs-collect-value'); var commentEl = panel.querySelector('#xhs-comment-value'); var tipEl = panel.querySelector('#xhs-float-tip'); if (likeEl) likeEl.textContent = likeVal !== null && likeVal !== undefined ? likeVal : '-'; if (collectEl) collectEl.textContent = collectVal !== null && collectVal !== undefined ? collectVal : '-'; if (commentEl) commentEl.textContent = commentVal !== null && commentVal !== undefined ? commentVal : '-'; if (tipEl) tipEl.textContent = tip || ''; updateBridgeOutput(likeVal, collectVal, commentVal, source || 'unknown', tip || ''); } // ================================================================ // 6~12 数据读取、探测、轮询全部原版代码无修改 // ================================================================ function findInteractInfo(obj, depth) { depth = depth || 0; if (!obj || typeof obj !== 'object' || depth > 15) return null; if (obj.interactInfo && typeof obj.interactInfo === 'object') { var ii = obj.interactInfo; if (ii.likedCount !== undefined || ii.collectedCount !== undefined) return ii; } if (Array.isArray(obj)) { for (var i = 0; i < obj.length; i++) { var result = findInteractInfo(obj[i], depth + 1); if (result) return result; } } else { var keys = Object.keys(obj); for (var j = 0; j < keys.length; j++) { var result = findInteractInfo(obj[keys[j]], depth + 1); if (result) return result; } } return null; } function readFromInitialState() { var state = cachedInitialState; if (!state) { try { state = window.__INITIAL_STATE__; } catch(e) {} } if (!state) return null; var interact = findInteractInfo(state); if (!interact) return null; var likeVal = null, collectVal = null, commentVal = null; if (interact.likedCount !== undefined && interact.likedCount !== null) likeVal = parseCount(String(interact.likedCount)); if (interact.collectedCount !== undefined && interact.collectedCount !== null) collectVal = parseCount(String(interact.collectedCount)); if (interact.commentCount !== undefined && interact.commentCount !== null) commentVal = parseCount(String(interact.commentCount)); if (likeVal === null && collectVal === null && commentVal === null) return null; return { likeVal, collectVal, commentVal, source: '__INITIAL_STATE__' }; } function readFromPageSource() { if (pageSourceFetched) return; pageSourceFetched = true; fetch(location.href, { credentials: 'include' }) .then(function(resp) { return resp.text(); }) .then(function(html) { var m = html.match(/"interactInfo"\s*:\s*\{[^}]*?"likedCount"\s*:\s*"?(\d+)"?[^}]*?"collectedCount"\s*:\s*"?(\d+)"?/); if (!m) { m = html.match(/"interactInfo"\s*:\s*\{[^}]*?"collectedCount"\s*:\s*"?(\d+)"?[^}]*?"likedCount"\s*:\s*"?(\d+)"?/); if (m) { var tmp = m[1]; m[1] = m[2]; m[2] = tmp; } } if (m) { var likeVal = parseInt(m[1]); var collectVal = parseInt(m[2]); var cm = html.match(/"interactInfo"\s*:\s*\{[^}]*?"commentCount"\s*:\s*"?(\d+)"?/); var commentVal = cm ? parseInt(cm[1]) : null; if (!lastResult || (lastResult.likeVal === null && likeVal)) { lastResult = { likeVal, collectVal, commentVal, source: 'page-source' }; logDebug('页面源码读取成功: 点赞=' + likeVal + ', 收藏=' + collectVal + ', 评论=' + commentVal); updateFloatPanel(likeVal, collectVal, commentVal, '数据来源: 页面源码', 'page-source'); } } else logDebug('页面源码中未找到 interactInfo'); }) .catch(function(e) { logDebug('页面源码读取失败: ' + e.message); pageSourceFetched = false; }); } function getButtonTypeBySvg(btn) { var path = btn.querySelector('svg path'); var pathD = path ? (path.getAttribute('d') || '') : ''; if (pathD.indexOf('8.791') !== -1) return 'like'; if (pathD.indexOf('4.816') !== -1) return 'collect'; if (pathD.indexOf('10.625') !== -1) return 'comment'; if (pathD.indexOf('M6.105') !== -1) return 'like'; if (pathD.indexOf('M20.91') !== -1) return 'collect'; return null; } function findStatsFromDOM() { var likeCandidates = []; var collectCandidates = []; var commentCandidates = []; var likeWrappers = document.querySelectorAll('.like-wrapper'); for (var i = 0; i < likeWrappers.length; i++) { var count = likeWrappers[i].querySelector('.count'); if (count && isValidNumberText(count.textContent)) likeCandidates.push({ text: count.textContent.trim(), source: 'like-wrapper' }); } var collectWrappers = document.querySelectorAll('.collect-wrapper'); for (var i = 0; i < collectWrappers.length; i++) { var count = collectWrappers[i].querySelector('.count'); if (count && isValidNumberText(count.textContent)) collectCandidates.push({ text: count.textContent.trim(), source: 'collect-wrapper' }); } var noteCollect = document.querySelector('#note-page-collect-board-guide'); if (noteCollect) { var count = noteCollect.querySelector('.count'); if (count && isValidNumberText(count.textContent)) collectCandidates.push({ text: count.textContent.trim(), source: 'note-page-collect' }); } var actionBtns = document.querySelectorAll('.action-button'); for (var i = 0; i < actionBtns.length; i++) { var btn = actionBtns[i]; var rfN = btn.querySelector('.rf-n'); if (!rfN) continue; var text = rfN.textContent.trim(); var type = getButtonTypeBySvg(btn); if (type === 'like') likeCandidates.push({ text, source: 'action-button-like' }); else if (type === 'collect') collectCandidates.push({ text, source: 'action-button-collect' }); else if (type === 'comment') commentCandidates.push({ text, source: 'action-button-comment' }); else { var parent = btn.parentElement; if (parent) { var siblings = []; for (var j = 0; j < parent.children.length; j++) { if (parent.children[j].classList.contains('action-button')) siblings.push(parent.children[j]); } var idx = siblings.indexOf(btn); if (idx === 0) likeCandidates.push({ text, source: 'action-button-order0' }); else if (idx === 1) collectCandidates.push({ text, source: 'action-button-order1' }); else if (idx === 2) commentCandidates.push({ text, source: 'action-button-order2' }); } } } if (likeCandidates.length === 0 && collectCandidates.length === 0) { var actionBtns2 = document.querySelectorAll('.action-buttons .action-button'); for (var i = 0; i < actionBtns2.length && i < 3; i++) { var span = actionBtns2[i].querySelector('strong span, strong'); if (span && isValidNumberText(span.textContent)) { var text2 = span.textContent.trim(); if (i === 0) likeCandidates.push({ text: text2, source: 'ssr-strong-0' }); else if (i === 1) collectCandidates.push({ text: text2, source: 'ssr-strong-1' }); else if (i === 2) commentCandidates.push({ text: text2, source: 'ssr-strong-2' }); } } } return { likeCandidates, collectCandidates, commentCandidates }; } function readFromDOM() { var stats = findStatsFromDOM(); var likeVal = null, collectVal = null, commentVal = null; for (var i = 0; i < stats.likeCandidates.length; i++) { var val = parseCount(stats.likeCandidates[i].text); if (val !== null) { likeVal = val; logDebug(' DOM点赞(' + stats.likeCandidates[i].source + '): ' + stats.likeCandidates[i].text + ' -> ' + val); break; } } for (var i = 0; i < stats.collectCandidates.length; i++) { var val = parseCount(stats.collectCandidates[i].text); if (val !== null) { collectVal = val; logDebug(' DOM收藏(' + stats.collectCandidates[i].source + '): ' + stats.collectCandidates[i].text + ' -> ' + val); break; } } for (var i = 0; i < stats.commentCandidates.length; i++) { var val = parseCount(stats.commentCandidates[i].text); if (val !== null) { commentVal = val; break; } } if (likeVal === null && collectVal === null) return null; return { likeVal, collectVal, commentVal, source: 'DOM' }; } function readStats() { if (!cachedInitialState) tryCaptureState(); var result = null; result = readFromInitialState(); if (result && (result.likeVal !== null || result.collectVal !== null)) { lastResult = result; logDebug('读取成功(' + result.source + '): 点赞=' + result.likeVal + ', 收藏=' + result.collectVal + ', 评论=' + result.commentVal); updateFloatPanel(result.likeVal, result.collectVal, result.commentVal, '', result.source || 'unknown'); return result; } result = readFromDOM(); if (result && (result.likeVal !== null || result.collectVal !== null)) { lastResult = result; logDebug('读取成功(' + result.source + '): 点赞=' + result.likeVal + ', 收藏=' + result.collectVal); updateFloatPanel(result.likeVal, result.collectVal, result.commentVal, '', result.source || 'unknown'); return result; } if (!pageSourceFetched && !lastResult) readFromPageSource(); if (lastResult) return lastResult; if (floatPanel) { var tip = '未检测到数据,点 ? 看日志'; if (!cachedInitialState) tip += ' (__INITIAL_STATE__ 未捕获)'; updateFloatPanel(null, null, null, tip, 'empty'); } return null; } function startObserver() { if (observer) observer.disconnect(); observer = new MutationObserver(function(mutations) { var shouldUpdate = false; for (var i = 0; i < mutations.length; i++) { var m = mutations[i]; if (m.type !== 'childList') continue; for (var j = 0; j < m.addedNodes.length; j++) { var node = m.addedNodes[j]; if (node.nodeType !== Node.ELEMENT_NODE) continue; if (node.matches && node.matches('.like-wrapper,.collect-wrapper,.action-button,.action-buttons,.count,.rf-n,strong')) { shouldUpdate = true; break; } if (node.querySelector && node.querySelector('.like-wrapper,.collect-wrapper,.action-button,.action-buttons,.count,.rf-n')) { shouldUpdate = true; break; } } if (shouldUpdate) break; } if (shouldUpdate) { clearTimeout(window._xhsDebounce); window._xhsDebounce = setTimeout(readStats, 600); debounceRebuildFloat(); } }); observer.observe(document.body, { childList: true, subtree: true }); logDebug('数据DOM监听已启动'); } function startPolling() { if (pollTimer) clearInterval(pollTimer); pollTimer = setInterval(() => { readStats(); debounceRebuildFloat(); }, 2500); logDebug('数据轮询已启动 (2.5s)'); } function probePage() { logDebug('===== 探测 ====='); logDebug('URL: ' + location.href); logDebug('原始URL: ' + originalURL); logDebug('UA: ' + navigator.userAgent.substring(0, 60)); if (cachedInitialState) { var interact = findInteractInfo(cachedInitialState); if (interact) logDebug('__INITIAL_STATE__ interactInfo: 点赞=' + interact.likedCount + ', 收藏=' + interact.collectedCount + ', 评论=' + interact.commentCount); else { logDebug('__INITIAL_STATE__ 已捕获但未找到 interactInfo'); logDebug('顶层键: ' + Object.keys(cachedInitialState).join(', ')); } } else { logDebug('__INITIAL_STATE__ 未捕获'); try { logDebug(window.__INITIAL_STATE__ ? '存在' : '不存在'); } catch(e) { logDebug('访问失败:'+e.message); } } var likeWrappers = document.querySelectorAll('.like-wrapper'); var collectWrappers = document.querySelectorAll('.collect-wrapper'); var actionBtns = document.querySelectorAll('.action-button'); var counts = document.querySelectorAll('.count'); var rfns = document.querySelectorAll('.rf-n'); logDebug('DOM: .like-wrapper=' + likeWrappers.length + ', .collect-wrapper=' + collectWrappers.length); logDebug('DOM: .action-button=' + actionBtns.length + ', .count=' + counts.length + ', .rf-n=' + rfns.length); for (var i = 0; i < rfns.length && i < 5; i++) { var p = rfns[i].parentElement; var cls = p ? safeClassName(p).substring(0, 40) : 'none'; logDebug(' rf-n[' + i + ']: "' + rfns[i].textContent.trim() + '" | parent: ' + cls); } for (var i = 0; i < actionBtns.length && i < 5; i++) { var path = actionBtns[i].querySelector('svg path'); var d = path ? (path.getAttribute('d') || '').substring(0, 30) : 'none'; var rfN = actionBtns[i].querySelector('.rf-n'); logDebug(' action[' + i + ']: rf-n="' + (rfN ? rfN.textContent.trim() : 'none') + '" svg="' + d + '"'); } readStats(); logDebug('===== 探测结束 ====='); } // 初始化 function tryInit() { if (isInitialized) return; if (!document.documentElement) { setTimeout(tryInit, 300); return; } isInitialized = true; logDebug('v7.3 初始化,原版悬浮窗样式保留'); logDebug('URL保护: ' + (originalPath.indexOf('/discovery/item/') !== -1 ? '已启用(拦截/explore/重定向)' : '未启用(非discovery路径)')); initFloatGuard(); createFloatPanel(); readStats(); startObserver(); startPolling(); setTimeout(probePage, 1500); setTimeout(probePage, 4000); setTimeout(probePage, 8000); window.XHSStats = { read: readStats, probe: probePage }; logDebug('初始化完成,悬浮窗防丢失保护已开启'); } function init() { console.log(LOG_PREFIX + ' v7.3 悬浮修复版(保留原始UI) 已加载 (document-start)'); if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', tryInit); } else { tryInit(); } setTimeout(function() { if (!isInitialized) tryInit(); }, 3000); window.addEventListener('load', debounceRebuildFloat); window.addEventListener('scroll', debounceRebuildFloat); window.addEventListener('resize', debounceRebuildFloat); window.addEventListener('popstate', debounceRebuildFloat); } init(); })();