Greasy Fork

Greasy Fork is available in English.

Hide NDM Video Download Button

在不禁用 Neat Download Manager 插件的情况下,移除其注入到页面中的下载浮层

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Hide NDM Video Download Button
// @namespace    https://tampermonkey.net/
// @version      1.0
// @description  在不禁用 Neat Download Manager 插件的情况下,移除其注入到页面中的下载浮层
// @match        *://*/*
// @run-at       document-start
// @grant        none
// @license      MIT
// ==/UserScript==

(() => {
  'use strict';

  const EXT_ID = 'pbghcbaeehloijjcebiflemhcebmlnke';
  const EXT_PREFIX = `chrome-extension://${EXT_ID}/`;

  const DEBUG = false; // 改 true 会在控制台打印日志
  const log = (...a) => DEBUG && console.log('[KillNDM]', ...a);

  // 可选兜底(有误伤风险,默认关闭)
  const ENABLE_TEXT_FALLBACK = false;

  function zIndexOf(el) {
    try {
      const z = getComputedStyle(el).zIndex;
      const n = Number(z);
      return Number.isFinite(n) ? n : 0;
    } catch {
      return 0;
    }
  }

  // 从 table 往上找更像“浮层容器”的父节点(fixed/absolute + 高 z-index)
  function findOverlayContainer(el) {
    let cur = el;
    for (let i = 0; i < 8 && cur && cur !== document.documentElement; i++) {
      try {
        const cs = getComputedStyle(cur);
        const pos = cs.position;
        const zi = zIndexOf(cur);
        if ((pos === 'fixed' || pos === 'absolute') && zi >= 100) return cur;
      } catch {}
      cur = cur.parentElement;
    }
    return el;
  }

  function kill(el) {
    if (!el || el.nodeType !== 1) return;
    try {
      // 先隐藏再移除,降低某些页面报错概率
      el.style.setProperty('display', 'none', 'important');
      el.style.setProperty('visibility', 'hidden', 'important');
      el.style.setProperty('opacity', '0', 'important');
      el.remove();
      log('removed:', el);
    } catch {}
  }

  function isNDMTable(tableEl) {
    if (!tableEl || tableEl.nodeType !== 1 || tableEl.tagName !== 'TABLE') return false;

    // 1) table 内含扩展资源(你给的 icon)
    try {
      const hitExt = tableEl.querySelector(
        `img[src^="${EXT_PREFIX}"], [style*="${EXT_PREFIX}"], [src^="${EXT_PREFIX}"], [href^="${EXT_PREFIX}"]`
      );
      if (hitExt) return true;
    } catch {}

    // 2) neatHCell* 这个特征(你贴出来的 td id)
    try {
      if (tableEl.querySelector('[id^="neatHCell"]')) return true;
    } catch {}

    // 3) 文本兜底(默认关闭)
    if (ENABLE_TEXT_FALLBACK) {
      const t = (tableEl.textContent || '').toLowerCase();
      if (t.includes('mp4 file') && t.includes('mb')) return true;
    }

    return false;
  }

  function sweep(root) {
    const scope = root && root.querySelectorAll ? root : document;

    // 只扫 table,性能好很多
    let tables = [];
    try {
      tables = scope.querySelectorAll('table');
    } catch {
      return;
    }

    for (const tb of tables) {
      if (isNDMTable(tb)) {
        const victim = findOverlayContainer(tb);
        kill(victim);
      }
    }

    // root 自己可能就是 table
    if (root && root.nodeType === 1 && root.tagName === 'TABLE' && isNDMTable(root)) {
      kill(findOverlayContainer(root));
    }
  }

  const mo = new MutationObserver(muts => {
    for (const m of muts) {
      if (m.type === 'childList') {
        m.addedNodes?.forEach(n => {
          if (n && n.nodeType === 1) sweep(n);
        });
      } else if (m.type === 'attributes') {
        const t = m.target;
        if (!t || t.nodeType !== 1) continue;

        // 如果某个 table 的属性变化(比如 style/src)导致命中,也处理
        const tb = t.tagName === 'TABLE' ? t : t.closest?.('table');
        if (tb && isNDMTable(tb)) kill(findOverlayContainer(tb));
      }
    }
  });

  function start() {
    sweep(document);
    mo.observe(document.documentElement, {
      subtree: true,
      childList: true,
      attributes: true,
      attributeFilter: ['id', 'class', 'style', 'src', 'href']
    });
    log('observer started');
  }

  if (document.documentElement) start();
  else window.addEventListener('DOMContentLoaded', start, { once: true });
})();