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