DevTools Bypass

Bypass for website restrictions on DevTools with enhanced protection

Verzia zo dňa 05.12.2024. Pozri najnovšiu verziu.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, Greasemonkey alebo Violentmonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, % alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey alebo Userscripts.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie správcu používateľských skriptov.

(Už mám správcu používateľských skriptov, nechajte ma ho nainštalovať!)

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

(Už mám správcu používateľských štýlov, nechajte ma ho nainštalovať!)

// ==UserScript==
// @name         DevTools Bypass
// @name:vi      Bỏ Qua Chặn DevTools
// @name:zh-CN   开发工具限制绕过
// @name:ru       Разблокировка DevTools
// @namespace    https://greasyfork.org/vi/users/1195312-renji-yuusei
// @version      3.5
// @description  Bypass for website restrictions on DevTools with enhanced protection
// @description:vi Bỏ qua các hạn chế của trang web về DevTools với bảo vệ nâng cao
// @description:zh-CN 绕过网站对开发工具的限制,具有增强的保护功能
// @description:ru   Разблокировка DevTools с усиленной защитой
// @author       Yuusei
// @match        *://*/*
// @grant        unsafeWindow
// @run-at       document-start
// @license      GPL-3.0-only
// ==/UserScript==

(function () {
	'use strict';

	const CONSTANTS = {
		PREFIX: '[DevTools Bypass]',
		LOG_LEVELS: {
			INFO: 'info',
			WARN: 'warn',
			ERROR: 'error',
		},
		TIME_THRESHOLDS: {
			DEBUGGER: 100,
			CACHE: 60000,
		},
	};

	const config = {
		debugPatterns: {
			basic: /;\s*(?:debugger|debug(?:ger)?|breakpoint)\s*;?/gi,
			advanced: /(?:debugger|debug(?:ger)?|breakpoint)[\s;]*(?:\{[\s\S]*?\})?/gi,
			timing: /(?:performance|Date)\.now\(\)|new\s+Date(?:\(\))?\.getTime\(\)/gi,
			eval: /eval\(.*?(?:debugger|debug|breakpoint).*?\)/gi,
			devtools: /(?:isDevTools|devtools|debugMode|debug_mode)\s*[=:]\s*(?:true|1)/gi,
			consoleCheck: /console\.[a-zA-Z]+\s*\(.*?\)/gi,
			functionDebug: /function.*?\{[\s\S]*?debugger[\s\S]*?\}/gi,
			sourceMap: /\/\/[#@]\s*source(?:Mapping)?URL=.*?$/gm,
			debugKeywords: /\b(?:debug|debugger|breakpoint|devtools)\b/gi,
			debugStrings: /"(?:[^"\\]|\\.)*(?:debug|debugger|breakpoint|devtools)(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*(?:debug|debugger|breakpoint|devtools)(?:[^'\\]|\\.)*'/gi,
			debugComments: /\/\/.*(?:debug|debugger|breakpoint|devtools).*$|\/\*[\s\S]*?(?:debug|debugger|breakpoint|devtools)[\s\S]*?\*\//gim,
			checkStats: /\b(?:checkStatus|_checkDevToolsBypassStatus|getStatus|checkDevTools)\b/gi,
			debuggerCheck: /\b(?:isDebuggerEnabled|hasDebugger|checkDebugger)\b/gi,
			debuggerTiming: /(?:performance\.timing|performance\.now|Date\.now)\(\)/gi,
			debuggerEval: /(?:eval|new Function)\s*\([^)]*(?:debugger|debug|breakpoint)[^)]*\)/gi,
			debuggerAsync: /setTimeout\s*\(\s*(?:function\s*\(\)\s*\{[\s\S]*?debugger[\s\S]*?\}|[^,]+,\s*[0-9]+\s*\))/gi,
		},
		consoleProps: ['log', 'warn', 'error', 'info', 'debug', 'trace', 'dir', 'dirxml', 'table', 'profile'],
		cutoffs: {
			debugger: { amount: 50, within: CONSTANTS.TIME_THRESHOLDS.CACHE },
			debuggerThrow: { amount: 50, within: CONSTANTS.TIME_THRESHOLDS.CACHE },
		},
		bypassTriggers: {
			timeThreshold: CONSTANTS.TIME_THRESHOLDS.DEBUGGER,
			stackDepth: 50,
			recursionLimit: 100,
		},
		logging: {
			enabled: false,
			prefix: CONSTANTS.PREFIX,
			levels: Object.values(CONSTANTS.LOG_LEVELS),
			detailedErrors: false,
		},
		protection: {
			preventDevToolsKeys: true,
			hideStackTraces: true,
			sanitizeErrors: true,
			obfuscateTimers: true,
			preventRightClick: true,
			preventViewSource: true,
		},
	};

	class Logger {
		static #instance;
		#lastLog = 0;
		#logCount = 0;

		constructor() {
			if (Logger.#instance) {
				return Logger.#instance;
			}
			Logger.#instance = this;
		}

		#shouldLog() {
			const now = Date.now();
			if (now - this.#lastLog > 1000) {
				this.#logCount = 0;
			}
			this.#lastLog = now;
			return ++this.#logCount <= 5;
		}

		#log(level, ...args) {
			if (!config.logging.enabled || !this.#shouldLog()) return;
			console[level](config.logging.prefix, ...args);
		}

		info(...args) {
			this.#log(CONSTANTS.LOG_LEVELS.INFO, ...args);
		}

		warn(...args) {
			this.#log(CONSTANTS.LOG_LEVELS.WARN, ...args);
		}

		error(...args) {
			this.#log(CONSTANTS.LOG_LEVELS.ERROR, ...args);
		}
	}

	const logger = new Logger();

	class OriginalFunctions {
		static defineProperty = Object.defineProperty;
		static getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
		static setTimeout = window.setTimeout;
		static setInterval = window.setInterval;
		static Date = window.Date;
		static now = Date.now;
		static performance = window.performance;
		static Function = window.Function;
		static eval = window.eval;
		static console = {};
		static toString = Function.prototype.toString;
		static preventDefault = Event.prototype.preventDefault;
		static getComputedStyle = window.getComputedStyle;
		static addEventListener = window.addEventListener;
		static removeEventListener = window.removeEventListener;

		static initConsole() {
			config.consoleProps.forEach(prop => {
				if (console[prop]) {
					this.console[prop] = console[prop].bind(console);
				}
			});
		}
	}

	OriginalFunctions.initConsole();

	class DebuggerDetector {
		static #detectionCache = new Map();

		static isPresent() {
			try {
				const cacheKey = 'debugger_check';
				const cached = this.#detectionCache.get(cacheKey);
				if (cached && Date.now() - cached.timestamp < 1000) {
					return cached.result;
				}

				const startTime = OriginalFunctions.now.call(Date);
				new Function('debugger;')();
				const timeDiff = OriginalFunctions.now.call(Date) - startTime;

				const result = timeDiff > config.bypassTriggers.timeThreshold;
				this.#detectionCache.set(cacheKey, {
					result,
					timestamp: Date.now(),
				});

				return result;
			} catch (e) {
				return false;
			}
		}

		static analyzeStack() {
			try {
				const stack = new Error().stack;
				const frames = stack.split('\n');
				const uniqueFrames = new Set(frames);

				return {
					depth: frames.length,
					hasDebugKeywords: frames.some(frame => Object.values(config.debugPatterns).some(pattern => pattern.test(frame))),
					isRecursive: uniqueFrames.size < frames.length,
					suspiciousPatterns: this.#detectSuspiciousPatterns(stack),
				};
			} catch (e) {
				return {
					depth: 0,
					hasDebugKeywords: false,
					isRecursive: false,
					suspiciousPatterns: [],
				};
			}
		}

		static #detectSuspiciousPatterns(stack) {
			const patterns = [/eval.*?\(/g, /Function.*?\(/g, /debugger/g, /debug/g];
			return patterns.map(pattern => pattern.test(stack)).filter(Boolean);
		}
	}

	class Protection {
		static applyAll() {
			this.#protectTimers();
			this.#protectTiming();
			this.#protectFunction();
			this.#protectStack();
			this.#protectEval();
			this.#protectConsole();
			this.#setupMutationObserver();
			this.#protectDevToolsKeys();
			this.#protectRightClick();
			this.#protectViewSource();
		}

		static #protectTimers() {
			const wrapTimer = original => {
				return function (handler, timeout, ...args) {
					if (typeof handler !== 'function') return original.apply(this, arguments);

					const wrappedHandler = function () {
						try {
							if (DebuggerDetector.isPresent()) return;
							return handler.apply(this, arguments);
						} catch (e) {
							if (e.message?.includes('debugger')) return;
							throw e;
						}
					};

					if (config.protection.obfuscateTimers) {
						timeout = Math.max(1, timeout + Math.random() * 10 - 5);
					}

					return original.call(this, wrappedHandler, timeout, ...args);
				};
			};

			window.setTimeout = wrapTimer(OriginalFunctions.setTimeout);
			window.setInterval = wrapTimer(OriginalFunctions.setInterval);
		}

		static #protectTiming() {
			const timeOffset = Math.random() * 15;
			const safeNow = () => OriginalFunctions.now.call(Date) + timeOffset;

			Object.defineProperty(Date, 'now', {
				value: safeNow,
				configurable: true,
				writable: true,
			});

			if (window.performance?.now) {
				Object.defineProperty(window.performance, 'now', {
					value: safeNow,
					configurable: true,
					writable: true,
				});
			}
		}

		static #protectFunction() {
			const handler = {
				apply(target, thisArg, args) {
					if (typeof args[0] === 'string') {
						args[0] = Protection.#cleanCode(args[0]);
					}
					return Reflect.apply(target, thisArg, args);
				},
				construct(target, args) {
					if (typeof args[0] === 'string') {
						args[0] = Protection.#cleanCode(args[0]);
					}
					return Reflect.construct(target, args);
				},
			};

			window.Function = new Proxy(OriginalFunctions.Function, handler);
			if (typeof unsafeWindow !== 'undefined') {
				unsafeWindow.Function = window.Function;
			}
		}

		static #protectStack() {
			if (!config.protection.hideStackTraces) return;

			const errorHandler = {
				get(target, prop) {
					if (prop === 'stack') {
						const stack = target.stack;
						return Protection.#cleanCode(stack);
					}
					return target[prop];
				},
			};

			const originalErrorPrototype = Error.prototype;
			const proxyErrorPrototype = Object.create(originalErrorPrototype);

			Object.defineProperty(proxyErrorPrototype, 'stack', {
				get() {
					const error = new Error();
					return Protection.#cleanCode(error.stack);
				},
				configurable: true,
			});

			try {
				Error.prototype = proxyErrorPrototype;
			} catch (e) {}
		}

		static #protectEval() {
			const safeEval = function (code) {
				if (typeof code === 'string') {
					if (DebuggerDetector.isPresent()) return;
					return OriginalFunctions.eval.call(this, Protection.#cleanCode(code));
				}
				return OriginalFunctions.eval.apply(this, arguments);
			};

			Object.defineProperty(window, 'eval', {
				value: safeEval,
				configurable: true,
				writable: true,
			});

			if (typeof unsafeWindow !== 'undefined') {
				unsafeWindow.eval = safeEval;
			}
		}

		static #protectConsole() {
			const consoleHandler = {
				get(target, prop) {
					if (!config.consoleProps.includes(prop)) return target[prop];

					return function (...args) {
						if (DebuggerDetector.isPresent()) return;
						return OriginalFunctions.console[prop]?.apply(console, args);
					};
				},
				set(target, prop, value) {
					if (config.consoleProps.includes(prop)) return true;
					target[prop] = value;
					return true;
				},
			};

			window.console = new Proxy(console, consoleHandler);
		}

		static #setupMutationObserver() {
			new MutationObserver(mutations => {
				mutations.forEach(mutation => {
					if (mutation.type === 'childList') {
						mutation.addedNodes.forEach(node => {
							if (node.tagName === 'SCRIPT') {
								const originalContent = node.textContent;
								const cleanedContent = Protection.#cleanCode(originalContent);
								if (originalContent !== cleanedContent) {
									node.textContent = cleanedContent;
								}
							}
						});
					}
				});
			}).observe(document, {
				childList: true,
				subtree: true,
			});
		}

		static #protectDevToolsKeys() {
			if (!config.protection.preventDevToolsKeys) return;

			const handler = e => {
				const { keyCode, ctrlKey, shiftKey } = e;
				if (
					keyCode === 123 || // F12
					(ctrlKey && shiftKey && keyCode === 73) || // Ctrl+Shift+I
					(ctrlKey && shiftKey && keyCode === 74) || // Ctrl+Shift+J
					(ctrlKey && keyCode === 85) // Ctrl+U
				) {
					e.preventDefault();
				}
			};

			window.addEventListener('keydown', handler, true);
		}

		static #protectRightClick() {
			if (!config.protection.preventRightClick) return;

			window.addEventListener(
				'contextmenu',
				e => {
					e.preventDefault();
				},
				true
			);
		}

		static #protectViewSource() {
			if (!config.protection.preventViewSource) return;

			window.addEventListener(
				'keydown',
				e => {
					if (e.ctrlKey && e.key === 'u') {
						e.preventDefault();
					}
				},
				true
			);
		}

		static #cleanCode(code) {
			if (typeof code !== 'string') return code;

			let cleanCode = code;
			Object.entries(config.debugPatterns).forEach(([, pattern]) => {
				cleanCode = cleanCode.replace(pattern, '');
			});
			return cleanCode;
		}
	}

	class DevToolsBypass {
		static init() {
			try {
				Protection.applyAll();
			} catch (e) {}
		}
	}

	DevToolsBypass.init();
})();