原帖子:https://www.nodeseek.com/post-464053-1

出现问题的地方

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