DevTools Unlocked

Neutralizes devtools detection and blocking by libraries like disable-devtool and devtools-detector.

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 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         DevTools Unlocked
// @namespace    groknt
// @version      3.6.2
// @description  Neutralizes devtools detection and blocking by libraries like disable-devtool and devtools-detector.
// @author       groknt
// @license      MIT
// @match        *://*/*
// @match        file:///*
// @inject-into  auto
// @run-at       document-start
// @grant        none
// ==/UserScript==

(function () {
  "use strict";

  const BLOCKED_EVENTS = ["contextmenu", "selectstart", "copy", "cut", "paste"];

  const DEBUGGER_BODY_RE = /^\s*debugger\s*;?\s*$/;
  const DEVTOOL_WARN_RE = /permission.+DEVTOOL/i;

  function coreSetup(env) {
    let detectorPresent = false;
    let recentTrap = false;
    let recentTrapTimer = 0;

    const onTrapDetected = () => {
      detectorPresent = true;
      recentTrap = true;
      env.clearTimeout(recentTrapTimer);
      recentTrapTimer = env.setTimeout(() => {
        recentTrap = false;
      }, 100);
    };

    const hasOwn = (obj, prop) =>
      env.apply(env.ObjectProto.hasOwnProperty, obj, [prop]);

    function isTrap(value, depth) {
      if (value == null) return false;
      const type = typeof value;
      if (type !== "object" && type !== "function") return false;
      try {
        if (
          hasOwn(value, "toString") &&
          (env.isDate(value) || env.isRegExp(value) || type === "function")
        ) {
          return true;
        }
        if (env.isElement(value)) {
          const desc = env.desc(value, "id");
          if (desc && typeof desc.get === "function") return true;
        }
        if (env.isArray(value) && value.length >= 50 && value[0] === value[1]) {
          const first = value[0];
          if (
            first != null &&
            typeof first === "object" &&
            !env.isArray(first) &&
            env.keys(first).length >= 100
          ) {
            return true;
          }
        }
        if (
          !depth &&
          type === "object" &&
          !env.isArray(value) &&
          !env.isElement(value)
        ) {
          const keys = env.keys(value);
          if (keys.length > 0 && keys.length <= 10) {
            for (let i = 0; i < keys.length; i++) {
              const desc = env.desc(value, keys[i]);
              if (desc && "value" in desc && isTrap(desc.value, 1)) return true;
            }
          }
        }
      } catch {
        return true;
      }
      return false;
    }

    function hasTrap(args) {
      for (let i = 0; i < args.length; i++) {
        if (isTrap(args[i])) return true;
      }
      return false;
    }

    const isDetectorNoise = (args) =>
      detectorPresent &&
      args.length === 1 &&
      args[0] != null &&
      typeof args[0] === "object" &&
      !env.isArray(args[0]) &&
      env.getProto(args[0]) === env.ObjectProto &&
      env.keys(args[0]).length === 0;

    const nativeConsole = {
      log: env.console.log,
      table: env.console.table,
      warn: env.console.warn,
      clear: env.console.clear,
      dir: env.console.dir,
      dirxml: env.console.dirxml,
    };

    const deferNative = (nativeFn, args) =>
      env.setTimeout(() => env.apply(nativeFn, env.console, args), 0);

    env.console.log = env.func(function log(...args) {
      if (hasTrap(args)) return onTrapDetected();
      if (isDetectorNoise(args)) return;
      deferNative(nativeConsole.log, args);
    }, "log");

    env.console.table = env.func(function table(...args) {
      if (hasTrap(args)) return onTrapDetected();
      if (isDetectorNoise(args)) return;
      deferNative(nativeConsole.table, args);
    }, "table");

    env.console.warn = env.func(function warn(...args) {
      if (
        args.length &&
        typeof args[0] === "string" &&
        DEVTOOL_WARN_RE.test(args[0])
      ) {
        detectorPresent = true;
        return;
      }
      deferNative(nativeConsole.warn, args);
    }, "warn");

    env.console.clear = env.func(function clear() {
      if (recentTrap || detectorPresent) return;
      deferNative(nativeConsole.clear, []);
    }, "clear");

    if (nativeConsole.dir) {
      env.console.dir = env.func(function dir(...args) {
        if (hasTrap(args)) return onTrapDetected();
        if (isDetectorNoise(args)) return;
        deferNative(nativeConsole.dir, args);
      }, "dir");
    }

    if (nativeConsole.dirxml) {
      env.console.dirxml = env.func(function dirxml(...args) {
        if (hasTrap(args)) return onTrapDetected();
        if (isDetectorNoise(args)) return;
        deferNative(nativeConsole.dirxml, args);
      }, "dirxml");
    }

    const isTrapFormatter = (formatter) =>
      formatter != null &&
      typeof formatter.header === "function" &&
      formatter.header.length === 0 &&
      typeof formatter.hasBody !== "function";

    let realFormatters = env.win.devtoolsFormatters;
    try {
      env.def(env.win, "devtoolsFormatters", {
        configurable: true,
        get: env.wrap(() => realFormatters),
        set: env.wrap((val) => {
          if (env.isArray(val)) {
            const clean = env.newArray();
            let trapped = false;
            for (let i = 0; i < val.length; i++) {
              if (isTrapFormatter(val[i])) trapped = true;
              else clean.push(val[i]);
            }
            if (trapped) detectorPresent = true;
            val = clean;

            const origPush = val.push;
            val.push = env.func(function push(...items) {
              const cleanItems = env.newArray();
              for (let i = 0; i < items.length; i++) {
                if (isTrapFormatter(items[i])) detectorPresent = true;
                else cleanItems.push(items[i]);
              }
              return cleanItems.length > 0
                ? env.apply(origPush, this, cleanItems)
                : this.length;
            }, "push");
          }
          realFormatters = val;
        }),
      });
    } catch {}

    if (env.patchDescriptor) {
      env.patchDescriptor("devtoolsFormatters", () => realFormatters);
    }

    try {
      const outerWidthDesc =
        env.desc(env.win, "outerWidth") ||
        env.desc(env.WindowProto, "outerWidth");
      const outerHeightDesc =
        env.desc(env.win, "outerHeight") ||
        env.desc(env.WindowProto, "outerHeight");

      const getReal = (desc) =>
        desc?.get ? env.apply(desc.get, env.win, []) : 0;

      env.def(
        env.win,
        "outerWidth",
        env.getter(function outerWidth() {
          const real = getReal(outerWidthDesc);
          const inner = env.win.innerWidth;
          if (real - inner > 100) return Math.round(inner + 16);
          return real;
        }, "get outerWidth"),
      );

      env.def(
        env.win,
        "outerHeight",
        env.getter(function outerHeight() {
          const real = getReal(outerHeightDesc);
          const inner = env.win.innerHeight;
          if (real - inner > 100) return Math.round(inner + 16);
          return real;
        }, "get outerHeight"),
      );
    } catch {}

    function isDevToolKey(e) {
      const keyCode = e.keyCode || e.which;
      if (keyCode === 123) return true;
      const ctrl = e.ctrlKey,
        meta = e.metaKey,
        shift = e.shiftKey,
        alt = e.altKey;
      return (
        (ctrl && shift && (keyCode === 73 || keyCode === 74)) ||
        (meta && alt && (keyCode === 73 || keyCode === 74)) ||
        (ctrl && keyCode === 85) ||
        (meta && alt && keyCode === 85)
      );
    }

    env.on(
      env.win,
      "keydown",
      env.wrap((e) => {
        if (isDevToolKey(e)) env.stop(e);
      }),
      true,
    );

    const origFunction = env.win.Function;
    const isDebuggerBody = (body) =>
      typeof body === "string" && DEBUGGER_BODY_RE.test(body);
    const noopFn = env.func(() => {}, "anonymous");

    const funcHandler = env.newObject();
    funcHandler.apply = env.wrap((target, thisArg, args) => {
      if (args.length && isDebuggerBody(args[args.length - 1])) return noopFn;
      return env.reflectApply(target, thisArg, args);
    });
    funcHandler.construct = env.wrap((target, args, newTarget) => {
      if (args.length && isDebuggerBody(args[args.length - 1])) return noopFn;
      return env.reflectConstruct(target, args, newTarget);
    });

    const FunctionProxy = env.proxy(origFunction, funcHandler, "Function");
    env.win.Function = FunctionProxy;
    origFunction.prototype.constructor = FunctionProxy;

    const nativeAddListener = env.EventTargetProto.addEventListener;
    const nativeRemoveListener = env.EventTargetProto.removeEventListener;
    const blockedEvents = env.newSet(env.blockedEvents);
    const listenerMap = env.newWeakMap();

    const isWindow = (obj) => {
      try {
        return obj.self === obj;
      } catch {
        return false;
      }
    };

    env.EventTargetProto.addEventListener = env.func(function addEventListener(
      type,
      listener,
      options,
    ) {
      if (
        !blockedEvents.has(type) ||
        typeof listener !== "function" ||
        !isWindow(this)
      ) {
        return env.apply(nativeAddListener, this, [type, listener, options]);
      }
      let wrapped = listenerMap.get(listener);
      if (!wrapped) {
        wrapped = env.wrap(function (e) {
          if (!detectorPresent) return env.apply(listener, this, [e]);
          const realPreventDefault = e.preventDefault;
          env.def(e, "preventDefault", {
            value: env.func(() => {}, "preventDefault"),
            writable: true,
            configurable: true,
          });
          const returnValueDesc = env.desc(e, "returnValue");
          env.def(e, "returnValue", {
            get: env.wrap(() => true),
            set: env.wrap(() => {}),
            configurable: true,
          });
          try {
            return env.apply(listener, this, [e]);
          } finally {
            env.def(e, "preventDefault", {
              value: realPreventDefault,
              writable: true,
              configurable: true,
            });
            if (returnValueDesc) env.def(e, "returnValue", returnValueDesc);
            else delete e.returnValue;
          }
        });
        listenerMap.set(listener, wrapped);
      }
      return env.apply(nativeAddListener, this, [type, wrapped, options]);
    }, "addEventListener");

    env.EventTargetProto.removeEventListener = env.func(
      function removeEventListener(type, listener, options) {
        if (
          blockedEvents.has(type) &&
          typeof listener === "function" &&
          isWindow(this)
        ) {
          const wrapped = listenerMap.get(listener);
          if (wrapped)
            return env.apply(nativeRemoveListener, this, [
              type,
              wrapped,
              options,
            ]);
        }
        return env.apply(nativeRemoveListener, this, [type, listener, options]);
      },
      "removeEventListener",
    );

    const nativeQuerySelector = env.DocumentProto.querySelector;
    env.DocumentProto.querySelector = env.func(function querySelector(
      selector,
    ) {
      if (selector === "[disable-devtool-auto]") {
        detectorPresent = true;
        return null;
      }
      return env.apply(nativeQuerySelector, this, [selector]);
    }, "querySelector");
  }

  function hasXray() {
    try {
      const page = window.wrappedJSObject;
      if (!page || page === window) return false;
      if (typeof exportFunction !== "function") return false;
      const fn = exportFunction(() => true, page);
      page.__xtest = fn;
      const success = page.__xtest() === true;
      delete page.__xtest;
      return success;
    } catch {
      return false;
    }
  }

  if (hasXray()) xraySetup();
  else pageSetup();

  function xraySetup() {
    const page = window.wrappedJSObject;
    const exportFn = (fn) => exportFunction(fn, page);
    const pageObj = () => new page.Object();
    const pageArr = () => new page.Array();

    coreSetup({
      blockedEvents: BLOCKED_EVENTS,
      win: page,
      doc: page.document,
      console: page.console,
      WindowProto: page.Window.prototype,
      DocumentProto: page.Document.prototype,
      EventTargetProto: page.EventTarget.prototype,
      ObjectProto: page.Object.prototype,
      apply: (fn, ctx, args) => Reflect.apply(fn, ctx, args),
      clearTimeout: (id) => page.clearTimeout(id),
      setTimeout: (fn, delay) => page.setTimeout(exportFn(fn), delay),
      isDate: (val) => val instanceof page.Date,
      isRegExp: (val) => val instanceof page.RegExp,
      isElement: (val) => val instanceof page.Element,
      isArray: (val) => page.Array.isArray(val),
      keys: (obj) => page.Object.keys(obj),
      desc: (obj, prop) => page.Object.getOwnPropertyDescriptor(obj, prop),
      def: (obj, prop, descriptor) =>
        page.Object.defineProperty(obj, prop, descriptor),
      getProto: (obj) => page.Object.getPrototypeOf(obj),
      newArray: pageArr,
      newObject: pageObj,
      newSet: (items) => new Set(items),
      newWeakMap: () => new WeakMap(),
      wrap: exportFn,
      func: exportFn,
      getter: (fn) => {
        const descriptor = pageObj();
        descriptor.get = exportFn(fn);
        descriptor.configurable = true;
        descriptor.enumerable = true;
        return descriptor;
      },
      proxy: (target, handler) => new page.Proxy(target, handler),
      reflectApply: (target, ctx, args) => Reflect.apply(target, ctx, args),
      reflectConstruct: (target, args, newTarget) =>
        Reflect.construct(target, args, newTarget),
      stop: (e) => e.stopImmediatePropagation(),
      on: (target, evt, fn, capture) =>
        target.addEventListener(evt, fn, capture),
      patchDescriptor: (prop, getValue) => {
        const nativeDesc = (obj, key) =>
          page.Object.getOwnPropertyDescriptor(obj, key);
        const patched = exportFn(function getOwnPropertyDescriptor(obj, key) {
          const descriptor = nativeDesc(obj, key);
          if (
            descriptor &&
            (descriptor.get || descriptor.set) &&
            key === prop
          ) {
            return {
              value: getValue(),
              writable: true,
              configurable: true,
              enumerable: true,
            };
          }
          return descriptor;
        });
        page.Object.getOwnPropertyDescriptor = patched;
        page.Reflect.getOwnPropertyDescriptor = patched;
      },
    });
  }

  function pageSetup() {
    const nativeStrings = new Map();
    const origToString = Function.prototype.toString;
    const nativeTemplate = origToString.call(document.getElementById);
    const toStringHandler = {
      apply(target, thisArg, args) {
        return nativeStrings.has(thisArg)
          ? nativeStrings.get(thisArg)
          : Reflect.apply(target, thisArg, args);
      },
    };
    const toStringProxy = new Proxy(origToString, toStringHandler);
    Function.prototype.toString = toStringProxy;
    nativeStrings.set(
      toStringProxy,
      nativeTemplate.replace("getElementById", "toString"),
    );

    const markNative = (fn, name) => {
      nativeStrings.set(
        fn,
        nativeTemplate.replace("getElementById", name || ""),
      );
      return fn;
    };

    coreSetup({
      blockedEvents: BLOCKED_EVENTS,
      win: window,
      doc: document,
      console: console,
      WindowProto: Window.prototype,
      DocumentProto: Document.prototype,
      EventTargetProto: EventTarget.prototype,
      ObjectProto: Object.prototype,
      apply: Reflect.apply,
      clearTimeout: clearTimeout.bind(window),
      setTimeout: setTimeout.bind(window),
      isDate: (val) => val instanceof Date,
      isRegExp: (val) => val instanceof RegExp,
      isElement: (val) => val instanceof Element,
      isArray: Array.isArray,
      keys: Object.keys,
      desc: Object.getOwnPropertyDescriptor,
      def: Object.defineProperty,
      getProto: Object.getPrototypeOf,
      newArray: () => [],
      newObject: () => ({}),
      newSet: (items) => new Set(items),
      newWeakMap: () => new WeakMap(),
      wrap: (fn) => fn,
      func: (fn, name) => markNative(fn, name),
      getter: (fn, name) => ({
        get: markNative(fn, name),
        configurable: true,
        enumerable: true,
      }),
      proxy: (target, handler, name) => {
        const proxy = new Proxy(target, handler);
        if (name) markNative(proxy, name);
        return proxy;
      },
      reflectApply: Reflect.apply,
      reflectConstruct: Reflect.construct,
      stop: (e) => e.stopImmediatePropagation(),
      on: (target, evt, fn, capture) =>
        EventTarget.prototype.addEventListener.call(target, evt, fn, capture),
      patchDescriptor: (prop, getValue) => {
        const nativeDesc = Object.getOwnPropertyDescriptor;
        const patched = markNative(function getOwnPropertyDescriptor(obj, key) {
          const descriptor = nativeDesc(obj, key);
          if (
            descriptor &&
            (descriptor.get || descriptor.set) &&
            key === prop
          ) {
            return {
              value: getValue(),
              writable: true,
              configurable: true,
              enumerable: true,
            };
          }
          return descriptor;
        }, "getOwnPropertyDescriptor");
        Object.getOwnPropertyDescriptor = patched;
        Reflect.getOwnPropertyDescriptor = patched;
      },
    });
  }
})();