DevTools Unlocked

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

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Greasemonkey lub Violentmonkey.

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

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Violentmonkey.

Aby zainstalować ten skrypt, wymagana będzie instalacja rozszerzenia Tampermonkey lub Userscripts.

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

Aby zainstalować ten skrypt, musisz zainstalować rozszerzenie menedżera skryptów użytkownika.

(Mam już menedżera skryptów użytkownika, pozwól mi to zainstalować!)

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.

Będziesz musiał zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

Będziesz musiał zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

Musisz zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

(Mam już menedżera stylów użytkownika, pozwól mi to zainstalować!)

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