☰

PMVHaven

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

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

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

Tendrás que instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Tendrás que instalar una extensión como Tampermonkey antes de poder instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

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