出现问题的地方
3840 * 2160 分辨率上,左侧导航栏会呈现重复部分,应该是直接 append 导致的
找AI修改了下,测试可去重
// ==UserScript==// @name NodeSeek <-> DeepFlood 联合访问// @namespace http://tampermonkey.net/// @license AGPL-3.0// @version 2025-09-28// @description Visit nodeseek.com and deepflood.com at the same time and push hot posts// @author xykt// @match https://nodeseek.com/// @match https://www.nodeseek.com/// @match https://nodeseek.com/page-*// @match https://www.nodeseek.com/page-*// @match https://deepflood.com/// @match https://www.deepflood.com/// @match https://deepflood.com/page-*// @match https://www.deepflood.com/page-*// @grant GM_xmlhttpRequest// @connect nodeseek.com// @connect www.nodeseek.com// @connect deepflood.com// @connect www.deepflood.com// @icon https://www.nodeseek.com/static/image/favicon/android-chrome-192x192.png// @run-at document-start// @downloadURL https://update.greasyfork.org/scripts/550955/NodeSeek%20%3C-%3E%20DeepFlood%20%E8%81%94%E5%90%88%E8%AE%BF%E9%97%AE.user.js// @updateURL https://update.greasyfork.org/scripts/550955/NodeSeek%20%3C-%3E%20DeepFlood%20%E8%81%94%E5%90%88%E8%AE%BF%E9%97%AE.meta.js// ==/UserScript==(function () { 'use strict'; const host = location.hostname.replace(/^www\./, ''); const A = host; const B = A === 'nodeseek.com' ? 'deepflood.com' : 'nodeseek.com'; const scheme = location.protocol; const baseA = scheme + '//' + A; const baseB = 'https://' + B; const pathMatch = location.pathname.match(/^\/(page-\d+)?\/?$/); if (!pathMatch) return; const currentPath = pathMatch[1] ? '/' + pathMatch[1] : '/'; document.documentElement.style.visibility = 'hidden'; function parseHTML(html) { const parser = new DOMParser(); return parser.parseFromString(html, 'text/html'); } function isRelative(url) { if (!url || typeof url !== 'string') return false; url = url.trim(); const lower = url.toLowerCase(); if (lower.startsWith('http://') || lower.startsWith('https://') || lower.startsWith('//') || lower.startsWith('mailto:') || lower.startsWith('tel:') || lower.startsWith('#') || lower.startsWith('data:')) { return false; } return true; } function absolutizeUrl(url, base) { if (!url) return url; if (!isRelative(url)) return url; if (url.startsWith('/')) return base + url; return base + '/' + url; } function convertRelativePaths(doc, base) { const attrList = ['href', 'src', 'action', 'poster', 'data-src', 'data-href']; attrList.forEach(attr => { doc.querySelectorAll('[' + attr + ']').forEach(el => { const val = el.getAttribute(attr); if (isRelative(val)) el.setAttribute(attr, absolutizeUrl(val, base)); }); }); doc.querySelectorAll('[srcset]').forEach(el => { const ss = el.getAttribute('srcset') || ''; const parts = ss.split(',').map(p => { const seg = p.trim(); const spaceIdx = seg.indexOf(' '); if (spaceIdx === -1) { return isRelative(seg) ? absolutizeUrl(seg, base) : seg; } else { const u = seg.slice(0, spaceIdx); const rest = seg.slice(spaceIdx + 1); return (isRelative(u) ? absolutizeUrl(u, base) : u) + ' ' + rest; } }); el.setAttribute('srcset', parts.join(', ')); }); doc.querySelectorAll('[style]').forEach(el => { let s = el.getAttribute('style'); if (!s) return; s = s.replace(/url\((['"]?)(?!https?:|\/\/|data:|#)([^'")]+)\1\)/g, (m, q, p1) => 'url(' + absolutizeUrl(p1, base) + ')'); el.setAttribute('style', s); }); doc.querySelectorAll('style').forEach(st => { let txt = st.textContent; if (!txt) return; txt = txt.replace(/url\((['"]?)(?!https?:|\/\/|data:|#)([^'")]+)\1\)/g, (m, q, p1) => 'url(' + absolutizeUrl(p1, base) + ')'); st.textContent = txt; }); return doc; } function fetchBAndMerge() { GM_xmlhttpRequest({ method: 'GET', url: 'https://' + B + currentPath, responseType: 'text', onload: function (res) { if (res.status >= 200 && res.status < 400 && res.responseText) { try { const docB = parseHTML(res.responseText); convertRelativePaths(docB, baseB); function doMerge() { try { const headA = document.querySelector('div#nsk-head.nsk-container, div#nsk-head'); if (headA) { const strong = headA.querySelector('strong.site-title'); let newInner = ''; if (A === 'nodeseek.com') { newInner = `<a href="https://nodeseek.com/"><img src="https://nodeseek.com/static/image/favicon/android-chrome-192x192.png" alt="logo" style="max-height: 36px;vertical-align: middle;"> <span class="title-text" style="vertical-align: middle;">NodeSeek</span><span class="beta-icon">beta</span></a><span class="title-text" style="vertical-align: middle;">+</span><a href="https://deepflood.com/"><span class="title-text" style="vertical-align: middle;"><span style="color:#0084ff">Deep</span><span style="color:#00a3a3">Flood</span></span><span class="beta-icon">beta</span></a>`; } else { newInner = `<a href="https://deepflood.com/"><span class="title-text" style="vertical-align: middle;"><span style="color:#0084ff">Deep</span><span style="color:#00a3a3">Flood</span></span><span class="beta-icon">beta</span></a><span class="title-text" style="vertical-align: middle;">+</span><a href="https://nodeseek.com/"><img src="https://nodeseek.com/static/image/favicon/android-chrome-192x192.png" alt="logo" style="max-height: 36px;vertical-align: middle;"> <span class="title-text" style="vertical-align: middle;">NodeSeek</span><span class="beta-icon">beta</span></a>`; } if (strong) strong.innerHTML = newInner; } const containerA = document.querySelector('#nsk-left-panel-container'); const panelsB = Array.from(docB.querySelectorAll('div.nsk-panel.category-list')); if (containerA && panelsB.length > 0) { // 当前页面已有的面板 const panelsA = Array.from(containerA.querySelectorAll('div.nsk-panel.category-list')); // 生成面板签名:标题(归一化) + 前若干链接的 pathname const panelSig = (panel, baseForUrl) => { const titleEl = panel.querySelector('.panel-title, .nsk-panel-title, h2, h3, .title') || null; const title = (titleEl ? titleEl.textContent : '') .trim() .toLowerCase() .replace(/\s+/g, ' '); const links = Array.from(panel.querySelectorAll('a[href]')).map(a => { let href = a.getAttribute('href') || ''; try { // 统一成 pathname,避免域名不同导致的误判 href = new URL(href, baseForUrl).pathname || href; } catch (e) { } return href; }); // 只取前 5 个链接参与签名,既稳定又避免过长 return title + '||' + links.slice(0, 5).join('|'); }; const existing = new Set(panelsA.map(p => panelSig(p, baseA))); panelsB.forEach(bPanel => { const sig = panelSig(bPanel, baseB); if (!existing.has(sig)) { const cloned = document.importNode(bPanel, true); containerA.appendChild(cloned); existing.add(sig); } }); } (function handlePostList(docA, docB, site) { const listA = Array.from(docA.querySelectorAll('ul.post-list > li.post-list-item:not([class*="topic-carousel"])')); const listB = Array.from(docB.querySelectorAll('ul.post-list > li.post-list-item:not([class*="topic-carousel"])')); const icons = { nodeseek: `<span class="info-item info-site"><img src="https://www.nodeseek.com/static/image/favicon/android-chrome-192x192.png" width="12" height="12"></span> `, deepflood: `<span class="info-item info-site"><img src="https://www.deepflood.com/static/image/favicon/android-chrome-192x192.png" width="12" height="12"></span>` }; const siteShort = (typeof site === 'string' && site.indexOf('nodeseek') !== -1) ? 'nodeseek' : 'deepflood'; const siteOther = siteShort === 'nodeseek' ? 'deepflood' : 'nodeseek'; function markSite(posts, siteFrom) { posts.forEach(li => { const info = li.querySelector(".post-info"); if (info) { if (!info.querySelector('.info-item.info-site')) { info.insertAdjacentHTML("afterbegin", icons[siteFrom]); } } }); } markSite(listA, siteShort); markSite(listB, siteOther); let allPosts = [...listA, ...listB]; function parsePost(li) { let timeTitle = ""; const timeEl = li.querySelector(".post-info time[title]"); if (timeEl) timeTitle = timeEl.getAttribute("title") || ""; let time = 0; if (timeTitle) { const parsed = Date.parse(timeTitle); time = isNaN(parsed) ? (new Date(timeTitle)).getTime() || 0 : parsed; } let views = 0; const viewsSpan = li.querySelector(".post-info .info-views span[title], .post-info .info-views span"); if (viewsSpan) { const vt = viewsSpan.getAttribute("title") || viewsSpan.textContent || ""; const m = vt.match(/(\d[\d,]*)/); if (m) views = parseInt(m[1].replace(/,/g, ''), 10) || 0; } let comments = 0; const commentsSpan = li.querySelector(".post-info .info-comments-count span[title], .post-info .info-comments-count span"); if (commentsSpan) { const ct = commentsSpan.getAttribute("title") || commentsSpan.textContent || ""; const m2 = ct.match(/(\d[\d,]*)/); if (m2) comments = parseInt(m2[1].replace(/,/g, ''), 10) || 0; } const sticky = !!li.querySelector('use[href="#pin"], use[href="#pin"]'); return { el: li, time: time || 0, weight: (views || 0) + (comments || 0) * 5, sticky: !!sticky }; } let postsData = allPosts.map(parsePost); let stickyPosts = postsData.filter(p => p.sticky).sort((a, b) => b.time - a.time); let normalPosts = postsData.filter(p => !p.sticky); normalPosts.sort((a, b) => b.weight - a.weight); let hotPosts = normalPosts.slice(0, 5); hotPosts.forEach(p => { const info = p.el.querySelector(".post-info"); if (info) { if (!info.querySelector('.info-item.info-hot')) { info.insertAdjacentHTML("beforeend", `<span class="info-item info-hot"><svg class="iconpark-icon"><use href="#rocket" style="color: red;"></use></svg><a style="color: red;"> 热帖</a></span>`); } } }); let otherPosts = normalPosts.slice(5).sort((a, b) => b.time - a.time); let finalPosts = [...stickyPosts, ...hotPosts, ...otherPosts]; const postListA = docA.querySelector('ul.post-list:not([class*="topic-carousel"])'); if (postListA) { postListA.innerHTML = ""; finalPosts.forEach(p => { const nodeToInsert = (p.el.ownerDocument === document) ? p.el.cloneNode(true) : document.importNode(p.el, true); postListA.appendChild(nodeToInsert); }); } })(document, docB, A); const userCard = document.querySelector('div[data-v-244123cf].user-card, div.user-card[data-v-244123cf]'); if (userCard) { let newUserHtml = ''; if (A === 'nodeseek.com') { newUserHtml = `<a href="/new-discussion" class="btn new-discussion"><svg class="iconpark-icon"><use href="#plus-cross-725o7jdo"></use></svg> <span style="vertical-align: middle;">Nodeseek发帖</span></a><a href="https://deepflood.com/new-discussion" class="btn new-discussion"><svg class="iconpark-icon"><use href="#plus-cross-725o7jdo"></use></svg> <span style="vertical-align: middle;">DeepFlood发帖</span></a>`; } else { newUserHtml = `<a href="/new-discussion" class="btn new-discussion"><svg class="iconpark-icon"><use href="#plus-cross-725o7jdo"></use></svg> <span style="vertical-align: middle;">DeepFlood发帖</span></a><a href="https://nodeseek.com/new-discussion" class="btn new-discussion"><svg class="iconpark-icon"><use href="#plus-cross-725o7jdo"></use></svg> <span style="vertical-align: middle;">Nodeseek发帖</span></a>`; } const nextDiv = userCard.nextElementSibling; if (nextDiv && nextDiv.tagName.toLowerCase() === 'div') { nextDiv.innerHTML = newUserHtml; } } document.documentElement.style.visibility = ''; } catch (err) { console.error('合并出错', err); document.documentElement.style.visibility = ''; } } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', doMerge, { once: true }); } else { doMerge(); } } catch (e) { console.error('解析失败', e); document.documentElement.style.visibility = ''; } } else { document.documentElement.style.visibility = ''; } }, onerror: function () { document.documentElement.style.visibility = ''; } }); } fetchBAndMerge();})();
评论 (0)