// ==UserScript== // @name NodeSeek <-> DeepFlood 联合访问 // @namespace http://tampermonkey.net/ // @license AGPL-3.0 // @version 2025-09-30 // @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://nodeseek.com/search?* // @match https://www.nodeseek.com/search?* // @match https://deepflood.com/ // @match https://www.deepflood.com/ // @match https://deepflood.com/page-* // @match https://www.deepflood.com/page-* // @match https://deepflood.com/search?* // @match https://www.deepflood.com/search?* // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @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.icu/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.icu/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; let NameA, NameB; if (A === 'nodeseek.com') { NameA = 'NodeSeek'; NameB = 'DeepFlood'; } else { NameA = 'DeepFlood'; NameB = 'NodeSeek'; } const pathMatch = location.pathname.match(/^\/(page-\d+)?\/?$/); let currentPath = null; if (pathMatch) { currentPath = pathMatch[1] ? '/' + pathMatch[1] : '/'; } else if (location.pathname.startsWith('/search')) { currentPath = location.pathname + location.search; } else { return; } 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 = ` logo NodeSeekbeta + DeepFloodbeta`; } else { newInner = ` DeepFloodbeta + logo NodeSeekbeta`; } if (strong) strong.innerHTML = newInner; } const secA = document.querySelectorAll(".nsk-panel.category-list")[0]; const secB = docB.querySelectorAll(".nsk-panel.category-list")[0]; if (secA && secB) { secA.style.lineHeight = "0"; secA.style.padding = "0"; secB.style.lineHeight = "0"; secB.style.padding = "0"; secA.insertAdjacentHTML("afterend", secB.outerHTML); } (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: ` `, deepflood: `` }; 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", ` 热帖`); } } }); 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 = ''; newUserHtml = `
${NameA}发帖
`; const nextDiv = userCard.nextElementSibling; if (nextDiv && nextDiv.tagName.toLowerCase() === 'div') { nextDiv.innerHTML = newUserHtml; } } (function handleUserConfig(docB) { try { const tempScript = docB.querySelector('#temp-script[type="application/json"]'); let insertTarget = document.querySelector('#nsk-right-panel-container > div:nth-of-type(2)'); const container = document.querySelector('#nsk-right-panel-container .nsk-panel h4'); if (container && container.textContent.trim() === '你好啊,陌生人!') { container.innerHTML = ' ' + container.textContent.trim(); insertTarget = document.querySelector('#nsk-right-panel-container > div:nth-of-type(1)'); } else { const userLink = document.querySelector('a.Username[class="Username"][data-v-244123cf]'); if (userLink) { userLink.innerHTML = ` ` + userLink.innerHTML; } } if (tempScript) { function b64DecodeUnicode(str) { return decodeURIComponent(atob(str).split('').map(function (c) { return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); }).join('')); } const jsonText = tempScript.textContent; if (jsonText) { const config = JSON.parse(b64DecodeUnicode(jsonText)); const user = config.user; if (insertTarget) { let htmlToInsert = ''; if (!user) { htmlToInsert = `

你好啊,陌生人!

我的朋友,看起来你是新来的,如果想参与到讨论中,点击下面的按钮!
登录 注册
`; } else { const notifyHtml = user.unViewedCount && user.unViewedCount.all > 0 ? `${user.unViewedCount.all}` : ''; htmlToInsert = `
${user.member_name}
${NameB}发帖
`; } insertTarget.insertAdjacentHTML('afterend', htmlToInsert); } } } } catch (e) { console.error('解析window.__config__失败', e); } })(docB); document.documentElement.style.visibility = ''; } catch (err) { console.error('合并出错', err); document.documentElement.style.visibility = ''; } initSearchHelper(docB); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', doMerge, {once:true}); } else { doMerge(); } } catch (e) { console.error('解析B失败', e); document.documentElement.style.visibility = ''; } } else { document.documentElement.style.visibility = ''; } }, onerror: function() { document.documentElement.style.visibility = ''; } }); } function initSearchHelper(docB = null) { function getCategoriesFromPage(docA = document, docB = null) { function extractCategories(doc) { const container = doc.querySelector('#nsk-left-panel-container'); if (!container) return []; const lis = container.querySelectorAll('li a span'); const cats = []; lis.forEach(span => { const text = span.textContent.trim(); if (text && !cats.includes(text)) { cats.push(text); } }); return cats; } const catsA = extractCategories(docA); let mergedCats = ['全部显示', ...catsA]; if (docB) { const catsB = extractCategories(docB); catsB.forEach(c => { if (!mergedCats.includes(c)) mergedCats.push(c); }); } return mergedCats; } const style = document.createElement('style'); style.textContent = ` .category-filter-container { position: fixed; top: 55px; right: 20px; z-index: 9999; gap: 6px; padding: 8px; width: 200px; display: flex; flex-direction: column; background: inherit; background-color: var(--bg-main-color); background-image: none; border-radius: 5px; box-shadow: 0 2px 10px rgba(0,0,0,0.2); font-size: 14px; } .filter-row { display: flex; align-items: center; gap: 4px; } .filter-label { white-space: nowrap; width: 50px; } .category-filter { padding: 4px; border-radius: 3px; border: 1px solid #ddd; width: 100%; font-size: 14px; } .text-filter { padding: 4px; border-radius: 3px; border: 1px solid #ddd; width: 100%; font-size: 14px; box-sizing: border-box; } .filter-option { display: flex; align-items: center; gap: 4px; margin-top: 2px; } .blocked-post { display: none !important; } .post-list-item { transition: opacity 0.3s; } .reset-btn { padding: 4px; background: #f0f0f0; border: 1px solid #ddd; border-radius: 3px; cursor: pointer; text-align: center; margin-top: 4px; font-size: 14px; width: 94.8%; } .reset-btn:hover { background: #e0e0e0; } .award-icon { width: 14px; height: 14px; vertical-align: middle; } `; document.head.appendChild(style); const filterContainer = document.createElement('div'); filterContainer.className = 'category-filter-container'; const STORAGE_KEY = 'POST_FILTER_SETTINGS'; let currentSettings = GM_getValue(STORAGE_KEY, { category: '全部显示', recommendedOnly: false, authorFilter: '', titleFilter: '', excludeFilter: '', showNodeSeek: true, showDeepFlood: true }); let isFirstLoad = true; const lastUrl = GM_getValue('LAST_MATCHED_URL', ''); const currentUrl = window.location.href; if (!currentUrl.startsWith((lastUrl || '').split('?')[0])) { currentSettings = { category: '全部显示', recommendedOnly: false, authorFilter: '', titleFilter: '', excludeFilter: '', showNodeSeek: true, showDeepFlood: true }; isFirstLoad = true; } GM_setValue('LAST_MATCHED_URL', currentUrl); const categories = getCategoriesFromPage(document, docB); const categoryRow = document.createElement('div'); categoryRow.className = 'filter-row'; const categoryLabel = document.createElement('label'); categoryLabel.className = 'filter-label'; categoryLabel.textContent = '分类'; categoryLabel.htmlFor = 'categoryFilter'; const select = document.createElement('select'); select.className = 'category-filter'; select.id = 'categoryFilter'; categories.forEach(category => { const option = document.createElement('option'); option.value = category; option.textContent = category; select.appendChild(option); }); if (currentSettings.category && Array.from(select.options).some(o => o.value === currentSettings.category)) { select.value = currentSettings.category; } else { select.value = '全部显示'; } categoryRow.appendChild(categoryLabel); categoryRow.appendChild(select); filterContainer.appendChild(categoryRow); const titleRow = document.createElement('div'); titleRow.className = 'filter-row'; const titleLabel = document.createElement('label'); titleLabel.className = 'filter-label'; titleLabel.textContent = '标题'; titleLabel.htmlFor = 'titleFilter'; const titleInput = document.createElement('input'); titleInput.type = 'text'; titleInput.className = 'text-filter'; titleInput.id = 'titleFilter'; titleInput.placeholder = ' 包含关键字'; titleInput.value = currentSettings.titleFilter; titleRow.appendChild(titleLabel); titleRow.appendChild(titleInput); filterContainer.appendChild(titleRow); const excludeRow = document.createElement('div'); excludeRow.className = 'filter-row'; const excludeLabel = document.createElement('label'); excludeLabel.className = 'filter-label'; excludeLabel.textContent = '标题'; excludeLabel.htmlFor = 'excludeFilter'; const excludeInput = document.createElement('input'); excludeInput.type = 'text'; excludeInput.className = 'text-filter'; excludeInput.id = 'excludeFilter'; excludeInput.placeholder = ' 排除关键字'; excludeInput.value = currentSettings.excludeFilter; excludeRow.appendChild(excludeLabel); excludeRow.appendChild(excludeInput); filterContainer.appendChild(excludeRow); const authorRow = document.createElement('div'); authorRow.className = 'filter-row'; const authorLabel = document.createElement('label'); authorLabel.className = 'filter-label'; authorLabel.textContent = '作者'; authorLabel.htmlFor = 'authorFilter'; const authorInput = document.createElement('input'); authorInput.type = 'text'; authorInput.className = 'text-filter'; authorInput.id = 'authorFilter'; authorInput.placeholder = ' ID / 昵称'; authorInput.value = currentSettings.authorFilter; authorRow.appendChild(authorLabel); authorRow.appendChild(authorInput); filterContainer.appendChild(authorRow); const recommendedContainer = document.createElement('div'); recommendedContainer.className = 'filter-option'; const recommendedCheckbox = document.createElement('input'); recommendedCheckbox.type = 'checkbox'; recommendedCheckbox.id = 'recommendedOnly'; recommendedCheckbox.checked = currentSettings.recommendedOnly; const awardIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); awardIcon.setAttribute('class', 'iconpark-icon award award-icon'); awardIcon.setAttribute('style', 'width:14px;height:14px'); const useElement = document.createElementNS('http://www.w3.org/2000/svg', 'use'); useElement.setAttribute('href', '#diamonds'); awardIcon.appendChild(useElement); const recommendedLabel = document.createElement('label'); recommendedLabel.htmlFor = 'recommendedOnly'; recommendedLabel.appendChild(document.createTextNode('仅显示推荐阅读 ')); recommendedLabel.appendChild(awardIcon); recommendedContainer.appendChild(recommendedCheckbox); recommendedContainer.appendChild(recommendedLabel); filterContainer.appendChild(recommendedContainer); const nsRow = document.createElement('div'); nsRow.className = 'filter-row'; const nsCheckbox = document.createElement('input'); nsCheckbox.type = 'checkbox'; nsCheckbox.id = 'filterNodeSeek'; nsCheckbox.checked = currentSettings.showNodeSeek; const nsLabel = document.createElement('label'); nsLabel.htmlFor = 'filterNodeSeek'; nsLabel.style.display = 'flex'; nsLabel.style.alignItems = 'center'; nsLabel.style.gap = '4px'; const nsIcon = document.createElement('img'); nsIcon.src = 'https://nodeseek.com/static/image/favicon/android-chrome-192x192.png'; nsIcon.width = 12; nsIcon.height = 12; nsLabel.appendChild(nsIcon); nsLabel.appendChild(document.createTextNode('NodeSeek')); nsRow.appendChild(nsCheckbox); nsRow.appendChild(nsLabel); filterContainer.appendChild(nsRow); const dfRow = document.createElement('div'); dfRow.className = 'filter-row'; const dfCheckbox = document.createElement('input'); dfCheckbox.type = 'checkbox'; dfCheckbox.id = 'filterDeepFlood'; dfCheckbox.checked = currentSettings.showDeepFlood; const dfLabel = document.createElement('label'); dfLabel.htmlFor = 'filterDeepFlood'; dfLabel.style.display = 'flex'; dfLabel.style.alignItems = 'center'; dfLabel.style.gap = '4px'; const dfIcon = document.createElement('img'); dfIcon.src = 'https://deepflood.com/static/image/favicon/android-chrome-192x192.png'; dfIcon.width = 12; dfIcon.height = 12; dfLabel.appendChild(dfIcon); dfLabel.appendChild(document.createTextNode('DeepFlood')); dfRow.appendChild(dfCheckbox); dfRow.appendChild(dfLabel); filterContainer.appendChild(dfRow); const resetBtn = document.createElement('div'); resetBtn.className = 'reset-btn'; resetBtn.textContent = '重置筛选'; resetBtn.addEventListener('click', function() { select.value = '全部显示'; recommendedCheckbox.checked = false; authorInput.value = ''; titleInput.value = ''; excludeInput.value = ''; nsCheckbox.checked = true; dfCheckbox.checked = true; saveSettings(); filterPosts(); }); filterContainer.appendChild(resetBtn); function saveSettings() { currentSettings = { category: select.value, recommendedOnly: recommendedCheckbox.checked, authorFilter: authorInput.value.trim(), titleFilter: titleInput.value.trim(), excludeFilter: excludeInput.value.trim(), showNodeSeek: nsCheckbox.checked, showDeepFlood: dfCheckbox.checked }; GM_setValue(STORAGE_KEY, currentSettings); GM_setValue('LAST_MATCHED_URL', window.location.href); } function filterPosts() { const selectedCategory = select.value; const showRecommendedOnly = recommendedCheckbox.checked; const authorFilterText = authorInput.value.trim().toLowerCase(); const titleFilterText = titleInput.value.trim().toLowerCase(); const excludeFilterText = excludeInput.value.trim().toLowerCase(); const showNS = nsCheckbox.checked; const showDF = dfCheckbox.checked; document.querySelectorAll('li.post-list-item').forEach(post => { post.classList.remove('blocked-post'); const categoryElement = post.querySelector('.post-category'); const postCategory = categoryElement ? categoryElement.textContent.trim() : ''; const isRecommended = post.querySelector('a[href="/award"][title="推荐阅读"]') !== null; const authorLink = post.querySelector('.info-author a'); const authorName = authorLink ? authorLink.textContent.trim().toLowerCase() : ''; const authorImg = post.querySelector('img.avatar-normal'); const authorAlt = authorImg ? authorImg.alt.toLowerCase() : ''; const titleElement = post.querySelector('.post-title a'); const postTitle = titleElement ? titleElement.textContent.trim().toLowerCase() : ''; const postHasNSIcon = !!post.querySelector('img[src="https://www.nodeseek.com/static/image/favicon/android-chrome-192x192.png"]'); const postHasDFIcon = !!post.querySelector('img[src="https://www.deepflood.com/static/image/favicon/android-chrome-192x192.png"]'); const sourceBlocked = (!showNS && postHasNSIcon) || (!showDF && postHasDFIcon); const categoryMatch = selectedCategory === '全部显示' || selectedCategory === '' || postCategory === selectedCategory; const recommendedMatch = !showRecommendedOnly || isRecommended; const authorMatch = authorFilterText === '' || authorName.includes(authorFilterText) || authorAlt.includes(authorFilterText); const titleMatch = titleFilterText === '' || postTitle.includes(titleFilterText); const excludeMatch = excludeFilterText === '' || !postTitle.includes(excludeFilterText); if (!categoryMatch || !recommendedMatch || !authorMatch || !titleMatch || !excludeMatch || sourceBlocked) { post.classList.add('blocked-post'); } }); } select.addEventListener('change', () => { saveSettings(); filterPosts(); }); recommendedCheckbox.addEventListener('change', () => { saveSettings(); filterPosts(); }); authorInput.addEventListener('input', () => { saveSettings(); filterPosts(); }); titleInput.addEventListener('input', () => { saveSettings(); filterPosts(); }); excludeInput.addEventListener('input', () => { saveSettings(); filterPosts(); }); nsCheckbox.addEventListener('change', () => { saveSettings(); filterPosts(); }); dfCheckbox.addEventListener('change', () => { saveSettings(); filterPosts(); }); document.body.appendChild(filterContainer); if (isFirstLoad) { setTimeout(filterPosts, 500); } else { filterPosts(); } const observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { if (mutation.addedNodes.length) { filterPosts(); } }); }); observer.observe(document.body, { childList: true, subtree: true }); window.addEventListener('beforeunload', function() { GM_setValue('LAST_MATCHED_URL', window.location.href); }); } fetchBAndMerge(); })();