| # 华为云考试绕过切屏检测方法(非虚拟机) > 在浏览器中安装油猴拓展
 > 在油猴拓展里添加下面的脚本即可
 > 实测可用:
 > (除了华为云考试,雨课堂应该也可以用)
 >
 
 复制代码> // ==UserScript==
> // [url=home.php?mod=space&uid=5839]@name[/url]         通用阻止切屏检测
> // @namespace    http://tampermonkey.net/
> // [url=home.php?mod=space&uid=73703]@version[/url]      0.1.0
> // @description  尝试阻止各类网站的切屏、焦点丢失等检测
> // @author       nodeseek@小号 && Gemini
> // @match        http://*/*
> // @match        https://*/*
> // @run-at       document-start
> // @grant        unsafeWindow
> // @license      GPL-3.0
> // ==/UserScript==
> 
> (function () {
>     'use strict';
>     const window = unsafeWindow; // 使用原始 window 对象
> 
>     // 黑名单事件,这些事件的监听器将被阻止
>     const blackListedEvents = new Set([
>         "visibilitychange", // 页面可见性改变
>         "blur",             // 元素或窗口失去焦点
>         "focus",            // 元素或窗口获得焦点 (某些检测可能反向利用focus)
>         "pagehide",         // 页面隐藏(例如导航到其他页面)
>         "freeze",           // 页面被冻结 (较新的事件)
>         "resume",           // 页面从冻结状态恢复 (较新的事件)
>         "mouseleave",       // 鼠标移出元素(通常是 document 或 body)
>         "mouseout",         // 鼠标移出元素(更通用的移出,但要小心副作用)
>         // "focusout",      // 元素将要失去焦点(与blur类似,但更通用,看情况添加)
>         // "focusin",       // 元素将要获得焦点(与focus类似,看情况添加)
>     ]);
> 
>     // 白名单属性,这些属性在 document 对象上将被伪造
>     const spoofedDocumentProperties = {
>         hidden: { value: false, configurable: true },
>         mozHidden: { value: false, configurable: true }, // Firefox (旧版)
>         msHidden: { value: false, configurable: true },  // Internet Explorer
>         webkitHidden: { value: false, configurable: true }, // Chrome, Safari, Opera (旧版 Blink/WebKit)
>         visibilityState: { value: "visible", configurable: true },
>         hasFocus: { value: () => true, configurable: true }
>     };
> 
>     // 需要清空/置空的事件处理器属性 (on-event handlers)
>     const eventHandlersToNullifyDocument = [
>         "onvisibilitychange",
>         "onblur",
>         "onfocus",
>         "onmouseleave",
>         "onmouseout",
>         // "onfocusout",
>         // "onfocusin",
>         "onpagehide",
>         "onfreeze",
>         "onresume"
>     ];
> 
>     const eventHandlersToNullifyWindow = [
>         "onblur",
>         "onfocus",
>         "onpagehide",
>         "onpageshow", // 有些检测可能通过 pageshow 结合 persisted 属性判断
>         "onfreeze",
>         "onresume",
>         "onmouseleave", // window 也有 onmouseleave
>         "onmouseout"
>     ];
> 
>     const isDebug = false; // 设置为 true 以启用调试日志
>     const scriptPrefix = "[通用阻止切屏检测]";
>     const log = console.log.bind(console, `%c${scriptPrefix}`, 'color: #4CAF50; font-weight: bold;');
>     const warn = console.warn.bind(console, `%c${scriptPrefix}`, 'color: #FFC107; font-weight: bold;');
>     const error = console.error.bind(console, `%c${scriptPrefix}`, 'color: #F44336; font-weight: bold;');
>     const debug = isDebug ? log : () => { };
> 
>     /**
>      * 伪装函数的 toString 方法,使其看起来像原始函数。
>      * @param {Function} modifiedFunction 被修改的函数
>      * @param {Function} originalFunction 原始函数
>      */
>     function patchToString(modifiedFunction, originalFunction) {
>         if (typeof modifiedFunction !== 'function' || typeof originalFunction !== 'function') {
>             warn("patchToString: 传入的参数不是函数。", modifiedFunction, originalFunction);
>             return;
>         }
>         try {
>             const originalToStringSource = Function.prototype.toString.call(originalFunction);
>             modifiedFunction.toString = () => originalToStringSource;
> 
>             // 进一步伪装 toString.toString
>             const originalToStringToStringSource = Function.prototype.toString.call(originalFunction.toString);
>             Object.defineProperty(modifiedFunction.toString, 'toString', {
>                 value: () => originalToStringToStringSource,
>                 enumerable: false,
>                 configurable: true, // 保持可配置,以防万一
>                 writable: false
>             });
>             debug(`patchToString applied for: ${originalFunction.name || 'anonymous function'}`);
>         } catch (e) {
>             error("patchToString failed:", e, "for function:", originalFunction.name);
>         }
>     }
> 
> 
>     /**
>      * 劫持并修改对象的 addEventListener 方法。
>      * @param {EventTarget} targetObject 要劫持的对象 (window, document, Element)
>      * @param {string} objectName 用于日志记录的对象名称
>      */
>     function patchAddEventListener(targetObject, objectName) {
>         if (!targetObject || typeof targetObject.addEventListener !== 'function') {
>             warn(`Cannot patch addEventListener for invalid target: ${objectName}`);
>             return;
>         }
>         const originalAddEventListener = targetObject.addEventListener;
> 
>         targetObject.addEventListener = function (type, listener, optionsOrCapture) {
>             if (blackListedEvents.has(type.toLowerCase())) {
>                 log(`BLOCKED ${objectName}.addEventListener: ${type}`);
>                 return undefined; // 阻止添加黑名单中的事件监听器
>             }
>             debug(`ALLOWED ${objectName}.addEventListener: ${type}`, listener, optionsOrCapture);
>             return originalAddEventListener.call(this, type, listener, optionsOrCapture);
>         };
> 
>         patchToString(targetObject.addEventListener, originalAddEventListener);
>         log(`${objectName}.addEventListener patched.`);
>     }
> 
>     /**
>      * 劫持并修改对象的 removeEventListener 方法 (可选,但建议一起修改)。
>      * @param {EventTarget} targetObject 要劫持的对象
>      * @param {string} objectName 用于日志记录的对象名称
>      */
>     function patchRemoveEventListener(targetObject, objectName) {
>         if (!targetObject || typeof targetObject.removeEventListener !== 'function') {
>             warn(`Cannot patch removeEventListener for invalid target: ${objectName}`);
>             return;
>         }
>         const originalRemoveEventListener = targetObject.removeEventListener;
> 
>         targetObject.removeEventListener = function (type, listener, optionsOrCapture) {
>             if (blackListedEvents.has(type.toLowerCase())) {
>                 log(`Original call to ${objectName}.removeEventListener for blacklisted event '${type}' would have been ignored by our addEventListener patch anyway. Allowing native call if needed.`);
>                 // 即使我们阻止了 addEventListener,原始的 removeEventListener 仍然应该能安全调用
>                 // 因为如果监听器从未被添加,调用 remove 也无害。
>             }
>             debug(`PASSTHROUGH ${objectName}.removeEventListener: ${type}`, listener, optionsOrCapture);
>             return originalRemoveEventListener.call(this, type, listener, optionsOrCapture);
>         };
>         patchToString(targetObject.removeEventListener, originalRemoveEventListener);
>         log(`${objectName}.removeEventListener patched.`);
>     }
> 
> 
>     /**
>      * 修改对象上的属性,使其返回伪造的值。
>      * @param {object} targetObject 目标对象 (e.g., document)
>      * @param {object} propertiesToSpoof 属性描述对象
>      * @param {string} objectName 对象名称
>      */
>     function spoofProperties(targetObject, propertiesToSpoof, objectName) {
>         if (!targetObject) {
>             warn(`Cannot spoof properties for invalid target: ${objectName}`);
>             return;
>         }
>         for (const prop in propertiesToSpoof) {
>             if (Object.prototype.hasOwnProperty.call(propertiesToSpoof, prop)) {
>                 try {
>                     Object.defineProperty(targetObject, prop, propertiesToSpoof[prop]);
>                     debug(`Spoofed ${objectName}.${prop}`);
>                 } catch (e) {
>                     error(`Failed to spoof ${objectName}.${prop}:`, e);
>                 }
>             }
>         }
>         log(`${objectName} properties spoofed.`);
>     }
> 
>     /**
>      * 清空或置空对象上的事件处理器属性。
>      * @param {object} targetObject 目标对象
>      * @param {string[]} eventHandlerNames 事件处理器名称数组
>      * @param {string} objectName 对象名称
>      */
> })();
 |