Greasy Fork

Greasy Fork is available in English.

NodeSeek <-> DeepFlood 联合访问

Visit nodeseek.com and deepflood.com at the same time and push hot posts

// ==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
// ==/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 = `
<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 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: `<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 = '';
                  newUserHtml = `<div><a href="/new-discussion" class="btn new-discussion"><svg class="iconpark-icon"><use href="#plus-cross-725o7jdo"></use></svg> <span style="vertical-align: middle;">${NameA}发帖</span></a></div>`;
                  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 =
                        '<img src="/static/image/favicon/android-chrome-192x192.png" width="16" height="16"> ' +
                        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 =
                          `<img src="/static/image/favicon/android-chrome-192x192.png" width="16" height="16" style="vertical-align: middle;"> ` +
                          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 = `
                              <div class="nsk-panel">
                                <h4><img src="${baseB}/static/image/favicon/android-chrome-192x192.png" width="16" height="16"> 你好啊,陌生人!</h4>
                                <div class="small-margin">我的朋友,看起来你是新来的,如果想参与到讨论中,点击下面的按钮!</div>
                                <div class="small-margin">
                                  <a href="${baseB}/signIn.html" rel="nofollow" class="btn" style="color:white;margin-right:5px;">登录</a>
                                  <a href="${baseB}/register.html" rel="nofollow" class="btn" style="color:white">注册</a>
                                </div>
                              </div>`;
                          } else {
                            const notifyHtml = user.unViewedCount && user.unViewedCount.all > 0
                              ? `<span data-v-244123cf="" class="notify-count">${user.unViewedCount.all}</span>` : '';
                            htmlToInsert = `
                              <div data-v-244123cf="" class="user-card">
                                <div data-v-244123cf="" class="user-head">
                                  <a data-v-244123cf="" title="${user.member_name}" href="${baseB}/space/${user.member_id}">
                                    <img data-v-244123cf="" src="${baseB}/avatar/${user.member_id}.png" alt="${user.member_name}" class="avatar-normal skeleton">
                                  </a>
                                  <div data-v-244123cf="" class="menu">
                                    <img src="${baseB}/static/image/favicon/android-chrome-192x192.png" width="16" height="16" style="vertical-align: middle;">
                                    <a data-v-244123cf="" href="${baseB}/space/${user.member_id}" class="Username">${user.member_name}</a>
                                    <div data-v-244123cf="">
                                      <a data-v-244123cf="" href="${baseB}/board" title="签到"><svg data-v-244123cf="" class="iconpark-icon"><use data-v-244123cf="" href="#plan"></use></svg></a>
                                      <a data-v-244123cf="" href="${baseB}/setting" title="设置"><svg data-v-244123cf="" class="iconpark-icon"><use data-v-244123cf="" href="#setting-two"></use></svg></a>
                                      <a data-v-244123cf="" href="javascript:void(0)" title="切换主题模式"></a>
                                      <a data-v-244123cf="" href="#" title="临时显示Block内容"><svg data-v-244123cf="" class="iconpark-icon"><use data-v-244123cf="" href="#vr-glasses"></use></svg></a>
                                      <a data-v-244123cf="" href="${baseB}/api/account/signOut" title="登出"><svg data-v-244123cf="" class="iconpark-icon"><use data-v-244123cf="" href="#logout"></use></svg></a>
                                    </div>
                                  </div>
                                </div>
                                <div data-v-244123cf="" class="user-stat">
                                  <div data-v-244123cf="" class="stat-block">
                                    <div data-v-244123cf=""><a data-v-244123cf="" href="${baseB}/progress"><svg data-v-244123cf="" class="iconpark-icon"><use data-v-244123cf="" href="#level"></use></svg> <span data-v-244123cf="">等级 Lv ${user.rank}</span></a></div>
                                    <div data-v-244123cf=""><a data-v-244123cf="" href="${baseB}/credit"><svg data-v-244123cf="" class="iconpark-icon"><use data-v-244123cf="" href="#chicken-leg"></use></svg> <span data-v-244123cf="">鸡腿 ${user.coin}</span></a></div>
                                    <div data-v-244123cf=""><a data-v-244123cf="" href="${baseB}/fans?type=follow"><svg data-v-244123cf="" class="iconpark-icon"><use data-v-244123cf="" href="#personal-collection"></use></svg> <span data-v-244123cf="">关注 ${user.follows}</span></a></div>
                                    <div data-v-244123cf=""><a data-v-244123cf="" href="${baseB}/notification"><svg data-v-244123cf="" class="iconpark-icon"><use data-v-244123cf="" href="#remind-6nce9p47"></use></svg> <span data-v-244123cf="">通知 </span>${notifyHtml}</a></div>
                                  </div>
                                  <div data-v-244123cf="" class="stat-block">
                                    <div data-v-244123cf=""><a data-v-244123cf="" href="${baseB}/space/${user.member_id}#discussions"><svg data-v-244123cf="" class="iconpark-icon"><use data-v-244123cf="" href="#write-6ncdp62p"></use></svg> <span data-v-244123cf="">主题帖 ${user.nPost}</span></a></div>
                                    <div data-v-244123cf=""><a data-v-244123cf="" href="${baseB}/space/${user.member_id}#comments"><svg data-v-244123cf="" class="iconpark-icon"><use data-v-244123cf="" href="#comments-6ncdh3ka"></use></svg> <span data-v-244123cf="">评论数 ${user.nComment}</span></a></div>
                                    <div data-v-244123cf=""><a data-v-244123cf="" href="${baseB}/fans?type=fans"><svg data-v-244123cf="" class="iconpark-icon"><use data-v-244123cf="" href="#concern"></use></svg> <span data-v-244123cf="">粉丝 ${user.fans}</span></a></div>
                                    <div data-v-244123cf=""><a data-v-244123cf="" href="${baseB}/space/${user.member_id}#collections"><svg data-v-244123cf="" class="iconpark-icon"><use data-v-244123cf="" href="#folder-focus"></use></svg> <span data-v-244123cf="">收藏 ${user.collectionCount}</span></a></div>
                                  </div>
                                </div>
                              </div>
                              <div>
                                <a href="${baseB}/new-discussion" class="btn new-discussion"><svg class="iconpark-icon"><use href="#plus-cross-725o7jdo"></use></svg> <span style="vertical-align: middle;">${NameB}发帖</span></a>
                              </div>`;
                          }
                          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();
})();