☰

PMVHaven

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

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

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==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 }
  );
})();