☰

PMVHaven

Removes invasive ads, blocks popunders/popups, restores the native context menu, and reduces unnecessary CSS/GPU work.

スクリプトをインストールするには、Tampermonkey, Greasemonkey や Violentmonkey のような拡張機能のインストールが必要です。

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

スクリプトをインストールするには、Tampermonkey や Violentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、Tampermonkey や Userscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら、通報はこちらへお寄せください
// ==UserScript==
// @name         PMVHaven
// @namespace    https://pmvhaven.com/
// @version      1.2.0
// @description  Removes invasive ads, blocks popunders/popups, restores the native context menu, and reduces unnecessary CSS/GPU work.
// @match        https://pmvhaven.com/*
// @match        https://*.pmvhaven.com/*
// @run-at       document-start
// @author       Cat-Ling
// @license      MIT
// @grant        none
// ==/UserScript==

(function () {
  'use strict';

  const AD_HOST_PATTERNS = [
    /(?:^|\.)s\.eunow4u\.com$/i,
    /(?:^|\.)eunow4u\.com$/i,
    /(?:^|\.)rndomnm\.com$/i,
    /(?:^|\.)fluxion-lab88\.com$/i,
    /(?:^|\.)sadbaguette\.com$/i,
    /(?:^|\.)clearsky-rhythm\.com$/i,
    /(?:^|\.)exacdn\.com$/i,
    /(?:^|\.)happyleafmotion\.com$/i,
    /(?:^|\.)realxxx\.com$/i,
    /(?:^|\.)ab1n\.net$/i
  ];

  const AD_URL_PATTERNS = [
    /popunder1000\.js(?:[?#]|$)/i
  ];

  const AD_CLASS_PATTERNS = [
    /(?:^|\s)ad-link(?:\s|$)/i,
    /(?:^|\s)native-ad-card(?:\s|$)/i,
    /(?:^|\s)inline-scroll-banner(?:\s|$)/i,
    /(?:^|\s)ad-banner(?:\s|$)/i,
    /(?:^|\s)undress-ai-animated-btn(?:\s|$)/i,
    /(?:^|\s)mobile-undress-ad-banner(?:\s|$)/i,
    /(?:^|\s)mobile-undress-ad-container(?:\s|$)/i,
    /(?:^|\s)ads-container(?:\s|$)/i,
    /(?:^|\s)video-player-banner(?:\s|$)/i,
    /(?:^|\s)sidebar-ad-container(?:\s|$)/i,
    /(?:^|\s)sidebar-ad-wrapper(?:\s|$)/i,
    /(?:^|\s)sidebar-ad-iframe(?:\s|$)/i,
    /(?:^|\s)ad-content-mobile(?:\s|$)/i,
    /(?:^|\s)ad-content(?:\s|$)/i
  ];

  const getURL = (value) => {
    if (!value) {
      return null;
    }

    try {
      return new URL(String(value), location.href);
    } catch {
      return null;
    }
  };

  const isAdURL = (value) => {
    const url = getURL(value);

    if (!url) {
      return false;
    }

    if (
      AD_HOST_PATTERNS.some((pattern) =>
        pattern.test(url.hostname)
      )
    ) {
      return true;
    }

    return AD_URL_PATTERNS.some((pattern) =>
      pattern.test(url.href)
    );
  };

  const classLooksAdRelated = (element) => {
    if (!(element instanceof Element)) {
      return false;
    }

    const className =
      typeof element.className === 'string'
        ? element.className
        : '';

    return AD_CLASS_PATTERNS.some((pattern) =>
      pattern.test(className)
    );
  };

  const elementHasAdURL = (element) => {
    if (!(element instanceof Element)) {
      return false;
    }

    if (element instanceof HTMLAnchorElement) {
      return isAdURL(element.href);
    }

    if (element instanceof HTMLIFrameElement) {
      return isAdURL(element.src);
    }

    if (element instanceof HTMLScriptElement) {
      return isAdURL(element.src);
    }

    return false;
  };

  const isAdElement = (element) => {
    if (!(element instanceof Element)) {
      return false;
    }

    if (elementHasAdURL(element)) {
      return true;
    }

    if (classLooksAdRelated(element)) {
      return true;
    }

    if (
      element.matches(
        'a[rel~="sponsored"], [data-ad], [data-advertisement]'
      )
    ) {
      return true;
    }

    if (
      element.closest(
        '.ads-container, .native-ad-card, .inline-scroll-banner, .ad-link, .ad-banner, ' +
        '.undress-ai-animated-btn, .mobile-undress-ad-banner, .mobile-undress-ad-container, ' +
        '.video-player-banner, .sidebar-ad-container, .sidebar-ad-wrapper, .sidebar-ad-iframe'
      )
    ) {
      return true;
    }

    return false;
  };

  const blockWindowOpen = () => {
    const blockedOpen = function () {
      return null;
    };

    try {
      Object.defineProperty(blockedOpen, 'name', {
        value: 'open',
        configurable: true
      });
    } catch {}

    const targets = [];

    if (
      typeof Window !== 'undefined' &&
      Window.prototype
    ) {
      targets.push({
        object: Window.prototype,
        property: 'open'
      });
    }

    if (typeof window !== 'undefined') {
      targets.push({
        object: window,
        property: 'open'
      });
    }

    for (const target of targets) {
      const object = target.object;
      const property = target.property;

      try {
        const descriptor =
          Object.getOwnPropertyDescriptor(
            object,
            property
          );

        if (
          descriptor &&
          descriptor.writable === false &&
          descriptor.configurable === false
        ) {
          continue;
        }

        Object.defineProperty(
          object,
          property,
          {
            value: blockedOpen,
            writable: true,
            configurable: true,
            enumerable: descriptor
              ? descriptor.enumerable
              : false
          }
        );
      } catch {
        try {
          object[property] = blockedOpen;
        } catch {}
      }
    }
  };

  const installInteractionBlocker = () => {
    const eventTypes = [
      'pointerdown',
      'pointerup',
      'pointercancel',
      'touchstart',
      'touchend',
      'touchcancel',
      'mousedown',
      'mouseup',
      'click',
      'auxclick'
    ];

    const block = (event) => {
      const target = event.target;

      if (!(target instanceof Element)) {
        return;
      }

      const adTarget = target.closest(
        'a.ad-link, a.native-ad-card, a.inline-scroll-banner, a.ad-banner, ' +
        'a.undress-ai-animated-btn, a.mobile-undress-ad-banner, ' +
        'iframe[src], [data-ad], [data-advertisement]'
      );

      if (
        adTarget &&
        isAdElement(adTarget)
      ) {
        event.preventDefault();
        event.stopImmediatePropagation();
        event.stopPropagation();
      }
    };

    for (const type of eventTypes) {
      window.addEventListener(
        type,
        block,
        {
          capture: true,
          passive: false
        }
      );

      document.addEventListener(
        type,
        block,
        {
          capture: true,
          passive: false
        }
      );
    }
  };

  const patchAnchorClick = () => {
    if (
      typeof HTMLAnchorElement === 'undefined'
    ) {
      return;
    }

    const proto = HTMLAnchorElement.prototype;
    const originalClick = proto.click;

    if (
      typeof originalClick !== 'function' ||
      originalClick.__pmvhavenPatched
    ) {
      return;
    }

    const patchedClick = function (...args) {
      if (isAdElement(this)) {
        return undefined;
      }

      return originalClick.apply(this, args);
    };

    try {
      Object.defineProperty(
        patchedClick,
        '__pmvhavenPatched',
        {
          value: true,
          configurable: false
        }
      );

      Object.defineProperty(
        proto,
        'click',
        {
          value: patchedClick,
          writable: true,
          configurable: true
        }
      );
    } catch {}
  };

  const blockInjectedNodes = () => {
    if (
      typeof Node === 'undefined' ||
      !Node.prototype
    ) {
      return;
    }

    const methods = [
      'appendChild',
      'insertBefore',
      'replaceChild'
    ];

    for (const methodName of methods) {
      const original =
        Node.prototype[methodName];

      if (
        typeof original !== 'function' ||
        original.__pmvhavenPatched
      ) {
        continue;
      }

      const patched = function (node, ...args) {
        if (node instanceof Element) {
          if (
            node instanceof HTMLScriptElement &&
            isAdURL(node.src)
          ) {
            try {
              node.remove();
            } catch {}

            return node;
          }

          if (
            node instanceof HTMLIFrameElement &&
            isAdURL(node.src)
          ) {
            try {
              node.remove();
            } catch {}

            return node;
          }
        }

        return original.call(
          this,
          node,
          ...args
        );
      };

      try {
        Object.defineProperty(
          patched,
          '__pmvhavenPatched',
          {
            value: true,
            configurable: false
          }
        );

        Object.defineProperty(
          Node.prototype,
          methodName,
          {
            value: patched,
            writable: true,
            configurable: true
          }
        );
      } catch {}
    }
  };

  const patchAttributeSetter = () => {
    if (
      typeof Element === 'undefined' ||
      !Element.prototype
    ) {
      return;
    }

    const originalSetAttribute =
      Element.prototype.setAttribute;

    if (
      typeof originalSetAttribute !== 'function' ||
      originalSetAttribute.__pmvhavenPatched
    ) {
      return;
    }

    const patchedSetAttribute = function (
      name,
      value
    ) {
      const attr =
        String(name).toLowerCase();

      if (
        (
          this instanceof HTMLScriptElement ||
          this instanceof HTMLIFrameElement
        ) &&
        attr === 'src' &&
        isAdURL(value)
      ) {
        return originalSetAttribute.call(
          this,
          'src',
          'about:blank'
        );
      }

      if (
        this instanceof HTMLAnchorElement &&
        attr === 'href' &&
        isAdURL(value)
      ) {
        originalSetAttribute.call(
          this,
          'href',
          '#'
        );

        originalSetAttribute.call(
          this,
          'data-pmvhaven-blocked-ad',
          '1'
        );

        return undefined;
      }

      return originalSetAttribute.call(
        this,
        name,
        value
      );
    };

    try {
      Object.defineProperty(
        patchedSetAttribute,
        '__pmvhavenPatched',
        {
          value: true,
          configurable: false
        }
      );

      Object.defineProperty(
        Element.prototype,
        'setAttribute',
        {
          value: patchedSetAttribute,
          writable: true,
          configurable: true
        }
      );
    } catch {}
  };

  const neutralizeAdElement = (element) => {
    if (!(element instanceof Element)) {
      return;
    }

    if (
      element instanceof HTMLAnchorElement &&
      element.hasAttribute('data-video-id') &&
      !classLooksAdRelated(element)
    ) {
      return;
    }

    if (!isAdElement(element)) {
      return;
    }

    if (element instanceof HTMLAnchorElement) {
      element.removeAttribute('href');
      element.removeAttribute('target');
      element.setAttribute(
        'aria-hidden',
        'true'
      );
      element.setAttribute(
        'data-pmvhaven-blocked-ad',
        '1'
      );
    }

    if (
      element instanceof HTMLIFrameElement
    ) {
      element.removeAttribute('src');
      element.setAttribute(
        'sandbox',
        ''
      );
      element.setAttribute(
        'data-pmvhaven-blocked-ad',
        '1'
      );
    }

    if (
      element instanceof HTMLScriptElement
    ) {
      element.remove();
      return;
    }

    try {
      element.remove();
    } catch {}
  };

  const purgeAds = (root = document) => {
    if (
      !root ||
      !root.querySelectorAll
    ) {
      return;
    }

    const selectors = [
      'script#popmagicldr',
      'script[src*="popunder1000.js"]',
      'script[src*="exacdn.com"]',
      'script[src*="eunow4u.com"]',
      'script[src*="rndomnm.com"]',
      'script[src*="sadbaguette.com"]',
      'script[src*="clearsky-rhythm.com"]',
      'script[src*="happyleafmotion.com"]',

      'iframe[src*="sadbaguette.com"]',
      'iframe[src*="clearsky-rhythm.com"]',
      'iframe[src*="exacdn.com"]',
      'iframe[src*="happyleafmotion.com"]',
      'iframe[src*="fluxion-lab88.com"]',

      'a[href*="s.eunow4u.com"]',
      'a[href*="eunow4u.com"]',
      'a[href*="rr.rndomnm.com"]',
      'a[href*="fluxion-lab88.com"]',
      'a[href*="v6.realxxx.com"]',

      '.ads-container',
      'a.ad-link',
      'a.native-ad-card',
      'a.inline-scroll-banner',
      'a.ad-banner',
      'a.undress-ai-animated-btn',
      'a.mobile-undress-ad-banner',
      '.mobile-undress-ad-container',
      '.video-player-banner',
      '.sidebar-ad-container',
      '.sidebar-ad-wrapper',
      '.sidebar-ad-iframe'
    ];

    for (
      const element of root.querySelectorAll(
        selectors.join(',')
      )
    ) {
      neutralizeAdElement(element);
    }
  };

  const installDOMObserver = () => {
    const observer =
      new MutationObserver(
        (mutations) => {
          for (
            const mutation of mutations
          ) {
            if (
              mutation.type ===
              'attributes'
            ) {
              const target =
                mutation.target;

              if (
                target instanceof Element
              ) {
                if (
                  target instanceof
                  HTMLAnchorElement
                ) {
                  const href =
                    target.getAttribute(
                      'href'
                    );

                  if (
                    isAdURL(href) ||
                    classLooksAdRelated(
                      target
                    )
                  ) {
                    neutralizeAdElement(
                      target
                    );
                  }
                } else if (
                  target instanceof
                    HTMLIFrameElement ||
                  target instanceof
                    HTMLScriptElement
                ) {
                  if (
                    isAdURL(
                      target.getAttribute(
                        'src'
                      )
                    )
                  ) {
                    neutralizeAdElement(
                      target
                    );
                  }
                }
              }

              continue;
            }

            for (
              const node of
                mutation.addedNodes
            ) {
              if (
                !(node instanceof Element)
              ) {
                continue;
              }

              neutralizeAdElement(node);

              if (
                node.querySelectorAll
              ) {
                purgeAds(node);
              }
            }
          }
        }
      );

    observer.observe(
      document,
      {
        childList: true,
        subtree: true,
        attributes: true,
        attributeFilter: [
          'href',
          'src',
          'target',
          'class',
          'id'
        ]
      }
    );
  };

  const restoreNativeContextMenu = () => {
    const handleContextMenu = (event) => {
      event.stopImmediatePropagation();
    };

    window.addEventListener(
      'contextmenu',
      handleContextMenu,
      true
    );

    document.addEventListener(
      'contextmenu',
      handleContextMenu,
      true
    );

    const preventSiteOverrides = (object) => {
      try {
        Object.defineProperty(
          object,
          'oncontextmenu',
          {
            get: () => null,
            set: () => true,
            configurable: true,
            enumerable: true
          }
        );
      } catch {}
    };

    preventSiteOverrides(window);
    preventSiteOverrides(document);

    if (
      typeof Document !== 'undefined' &&
      Document.prototype
    ) {
      preventSiteOverrides(
        Document.prototype
      );
    }

    if (
      typeof HTMLElement !== 'undefined' &&
      HTMLElement.prototype
    ) {
      preventSiteOverrides(
        HTMLElement.prototype
      );
    }
  };

  const injectOptimizations = () => {
    const adSelectors = [
      'a.native-ad-card:not([data-video-id])',
      'a.ad-link:not([data-video-id]):not([href^="/video/"])',
      'a[rel*="sponsored"]:not([data-video-id]):not([href^="/video/"])',
      'a.inline-scroll-banner',
      'a.undress-ai-animated-btn',
      '.mobile-undress-ad-container',
      '.mobile-undress-ad-banner',
      '.mobile-header-ad',
      '.ads-container',
      '.video-player-banner',
      '.sidebar-ad-container',
      '.sidebar-ad-wrapper',
      '.sidebar-ad-iframe',
      '.ad-banner',
      '.ad-content-mobile',
      'iframe[src*="sadbaguette.com"]',
      'iframe[src*="clearsky-rhythm.com"]',
      'iframe[src*="exacdn.com"]',
      'iframe[src*="happyleafmotion.com"]',
      'iframe[src*="fluxion-lab88.com"]',
      '[data-reka-context-menu-content]',
      '[data-radix-context-menu-content]'
    ];

    const css = `
      ${adSelectors.join(',\\n')} {
        display: none !important;
        visibility: hidden !important;
        height: 0 !important;
        min-height: 0 !important;
        max-height: 0 !important;
        margin: 0 !important;
        padding: 0 !important;
        border: none !important;
        pointer-events: none !important;
        user-select: none !important;
      }

      div:has(> .grid[class*="cols-4-1500"]),
      div:has(> .grid[class*="cols-3-1200"]) {
        display: grid !important;
        grid-template-columns:
          repeat(1, minmax(0, 1fr)) !important;
        gap: 1rem !important;
      }

      @media (min-width: 640px) {
        div:has(> .grid[class*="cols-4-1500"]),
        div:has(> .grid[class*="cols-3-1200"]) {
          grid-template-columns:
            repeat(2, minmax(0, 1fr)) !important;
        }
      }

      @media (min-width: 1200px) and (max-width: 1499.98px) {
        div:has(> .grid[class*="cols-4-1500"]),
        div:has(> .grid[class*="cols-3-1200"]) {
          grid-template-columns:
            repeat(3, minmax(0, 1fr)) !important;
        }
      }

      @media (min-width: 1500px) {
        div:has(> .grid[class*="cols-4-1500"]),
        div:has(> .grid[class*="cols-3-1200"]) {
          grid-template-columns:
            repeat(4, minmax(0, 1fr)) !important;
        }
      }

      div:has(> .grid[class*="cols-4-1500"]) > .grid,
      div:has(> .grid[class*="cols-3-1200"]) > .grid {
        display: contents !important;
        contain: none !important;
      }

      div:has(> .grid[class*="cols-4-1500"]) > div.my-6,
      div:has(> .grid[class*="cols-3-1200"]) > div.my-6,
      div.my-6:not(:has([data-video-id])) {
        display: none !important;
        margin: 0 !important;
        height: 0 !important;
      }

      .videos-grid-tailwind > *,
      .grid > a,
      .grid > * {
        transform: none !important;
        will-change: auto !important;
        backface-visibility: visible !important;
      }

      [class*="backdrop-blur"],
      .backdrop-blur-sm,
      .backdrop-blur-md,
      .backdrop-blur {
        backdrop-filter: none !important;
        -webkit-backdrop-filter: none !important;
      }

      body.loaded * {
        transition-property: none !important;
      }

      a[data-video-id],
      button,
      input,
      .transition-colors {
        transition-property:
          color,
          background-color,
          border-color !important;
        transition-duration: 0.15s !important;
      }

      .route-loading-bar,
      .ssr-loading-bar,
      .loading-bar,
      [class*="loading-bar"] {
        display: none !important;
        animation: none !important;
      }

      .ongoing-event-banner__icon {
        animation: none !important;
      }
    `;

    const inject = () => {
      if (
        document.getElementById(
          'pmvhaven-optimizer-styles'
        )
      ) {
        return;
      }

      const style =
        document.createElement(
          'style'
        );

      style.id =
        'pmvhaven-optimizer-styles';

      style.textContent = css;

      (
        document.head ||
        document.documentElement
      ).appendChild(style);
    };

    if (
      document.head ||
      document.documentElement
    ) {
      inject();
    } else {
      document.addEventListener(
        'DOMContentLoaded',
        inject,
        { once: true }
      );
    }
  };

  blockWindowOpen();
  blockInjectedNodes();
  patchAttributeSetter();
  patchAnchorClick();

  restoreNativeContextMenu();
  installInteractionBlocker();
  injectOptimizations();
  installDOMObserver();

  purgeAds();

  document.addEventListener(
    'DOMContentLoaded',
    () => {
      purgeAds();
      injectOptimizations();
    },
    { once: true }
  );
})();