您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
在NodeSeek和DeepFlood之间阅读对方站点的帖子
// ==UserScript== // @name NodeSeek & DeepFlood 双边会晤 // @namespace http://www.nodeseek.com/ // @version 1.0.2 // @description 在NodeSeek和DeepFlood之间阅读对方站点的帖子 // @author dabao // @match *://www.nodeseek.com/* // @match *://www.deepflood.com/* // @grant GM_xmlhttpRequest // @grant GM_addStyle // @run-at document-end // @license GPL-3.0 License // ==/UserScript== (function() { 'use strict'; const path = window.location.pathname + window.location.search; // 包含 ?query // 定义允许的 path 前缀/模式 const allowedPatterns = [ /^\/$/, /^\?sortBy=/, /^\/page-\d+/, /^\/post-\d+/, /^\/categories\//, /^\/award/ ]; const matched = allowedPatterns.some(re => re.test(path)); if (!matched) return; // 不匹配则不执行 const currentHost = window.location.hostname; const targetSite = currentHost === 'www.nodeseek.com' ? { name: 'DeepFlood', url: 'https://www.deepflood.com' } : { name: 'NodeSeek', url: 'https://www.nodeseek.com' }; GM_addStyle(` #dual-site-modal { position: fixed; top: 40px; right: 0; bottom: 0; width: 400px; background: #fff; border: 1px solid #ddd; border-radius: 8px 0 0 0; box-shadow: 0 4px 12px rgba(0,0,0,0.15); opacity: 0.6;transition: opacity 0.3s ease; z-index: 10000; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; } #dual-site-modal:hover { opacity: 1; } #dual-site-header { padding: 8px 16px; background: #f8f9fa; border-bottom: 1px solid #eee; border-radius: 8px 0 0 0; display: flex; justify-content: space-between; align-items: center; } #dual-site-title { font-weight: 600; font-size: 14px; color: #333; margin: 0; } #dual-site-refresh:disabled { background: #6c757d; cursor: not-allowed; } #dual-site-iframe { width: 100%; height: calc(100% - 49px); border: none; overflow: hidden; } #dual-site-loading { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: #666; font-size: 14px; } #fast-nav-button-group { right: calc(50% - 540px) !important; } `); function createModal() { const modal = document.createElement('div'); modal.id = 'dual-site-modal'; modal.innerHTML = ` <div id="dual-site-header"> <h3 id="dual-site-title"><a href="${targetSite.url}" target="_blank">${targetSite.name}</a></h3> <button id="dual-site-refresh" class="btn">刷新</button> </div> <iframe id="dual-site-iframe" style="display:none"></iframe> <div id="dual-site-loading">加载中...</div> `; document.body.appendChild(modal); const iframe = modal.querySelector('#dual-site-iframe'); const loading = modal.querySelector('#dual-site-loading'); const refreshBtn = modal.querySelector('#dual-site-refresh'); refreshBtn.addEventListener('click', loadData); return { iframe, loading, refreshBtn }; } function setLinksTarget(doc) { try { doc.querySelectorAll('a[href]').forEach(a => a.target = '_blank'); } catch (e) { console.error('无法修改 iframe 内部链接:', e); } } function attachSorterHandlers(docIframe) { const sorter = docIframe.querySelector('div.sorter'); if (!sorter) return; sorter.querySelectorAll('a[data-sort]').forEach(a => { a.addEventListener('click', e => { e.preventDefault(); e.stopImmediatePropagation(); const sortBy = a.dataset.sort; loadData(sortBy); }, true); }); } function loadData(sortBy) { const { iframe, loading, refreshBtn } = elements; loading.style.display = 'block'; iframe.style.display = 'none'; refreshBtn.disabled = true; refreshBtn.textContent = '加载中...'; const initUrl = `${targetSite.url}${sortBy?.trim() ? `?sortBy=${sortBy}` : ''}`; GM_xmlhttpRequest({ method: 'GET', url: initUrl, onload: function(res) { const parser = new DOMParser(); const doc = parser.parseFromString(res.responseText, "text/html"); // 插入 <base>,保证相对路径资源能正确加载 const base = doc.createElement("base"); base.href = `${targetSite.url}/`; const head = doc.querySelector("head") || doc.documentElement; head.insertBefore(base, head.firstChild); // 移除头尾及左右侧栏 doc.querySelectorAll("body > header, body > footer, #nsk-left-panel-container, #nsk-right-panel-container").forEach(e => e.remove()); const htmlStr = '<!DOCTYPE html>\n' + doc.documentElement.outerHTML; const blob = new Blob([htmlStr], { type: "text/html" }); const blobUrl = URL.createObjectURL(blob); iframe.src = blobUrl; iframe.onload = () => { loading.style.display = 'none'; iframe.style.display = 'block'; refreshBtn.disabled = false; refreshBtn.textContent = '刷新'; // 接管内部超链接 setLinksTarget(iframe.contentDocument); attachSorterHandlers(iframe.contentDocument); // ✅ 分页逻辑 const doc = iframe.contentDocument; const postList = doc.querySelector('ul.post-list'); const topPager = doc.querySelector('div.nsk-pager.pager-top'); const bottomPager = doc.querySelector('div.nsk-pager.pager-bottom'); if (!postList) return; const state = { page: 2, isLoading: false }; const loadMore = async () => { if (state.isLoading) return; state.isLoading = true; GM_xmlhttpRequest({ method: 'GET', url: `${targetSite.url}/page-${state.page}`, onload: function(res) { const parser = new DOMParser(); const newDoc = parser.parseFromString(res.responseText, "text/html"); setLinksTarget(newDoc); const newList = newDoc.querySelector('ul.post-list'); const newTopPager = newDoc.querySelector('div.nsk-pager.pager-top'); const newBottomPager = newDoc.querySelector('div.nsk-pager.pager-bottom'); if (newList) { newList.querySelectorAll('li').forEach(li => postList.appendChild(li)); topPager.innerHTML = newTopPager.innerHTML; bottomPager.innerHTML = newBottomPager.innerHTML; state.page++; } state.isLoading = false; }, onerror: () => { console.error("分页加载失败"); state.isLoading = false; } }); }; const checkShouldLoad = () => { const { scrollTop, clientHeight, scrollHeight } = doc.documentElement; if (scrollTop + clientHeight >= scrollHeight - 690) { loadMore(); } }; const throttle = (fn, delay) => { let last = 0; return (...args) => { const now = Date.now(); if (now - last >= delay) { last = now; fn(...args); } }; }; const debounced = (() => { let timer; return (fn, delay) => (...args) => { clearTimeout(timer); timer = setTimeout(() => fn(...args), delay); }; })(); const throttledCheck = throttle(checkShouldLoad, 200); const debouncedCheck = debounced(checkShouldLoad, 300); doc.addEventListener('scroll', () => { throttledCheck(); debouncedCheck(); }); setTimeout(() => URL.revokeObjectURL(blobUrl), 1000); }; }, onerror: () => { loading.innerHTML = '<span style="color: #dc3545;">加载失败</span>'; refreshBtn.disabled = false; refreshBtn.textContent = '重试'; } }); } const elements = createModal(); setTimeout(loadData, 1000); })();