243 lines
7.0 KiB
PHP
Executable File
243 lines
7.0 KiB
PHP
Executable File
<?php
|
|
/** 虚拟设备检测规则引擎(IS_VIRTUAL 1=简单 2=标准 3=严格) */
|
|
?>
|
|
<script>
|
|
(function (window) {
|
|
'use strict';
|
|
|
|
var BOT_UA = /googlebot|bingbot|yandexbot|baiduspider|facebookexternalhit|twitterbot|rogerbot|linkedinbot|embedly|quora link preview|showyoubot|outbrain|slackbot|vkshare|w3c_validator|whatsapp|telegrambot|applebot|petalbot|semrushbot|ahrefsbot|mj12bot|dotbot|bytespider|gptbot|claudebot|amazonbot/i;
|
|
var AUTO_UA = /HeadlessChrome|PhantomJS|Puppeteer|Selenium|WebDriver|playwright|AutomationControlled/i;
|
|
var VM_UA = /virtualbox|vmware|qemu|genymotion|\bemulator\b|sdk_gphone|android sdk built for x86|simulator/i;
|
|
|
|
var _glCache = null;
|
|
|
|
function getWebglRenderer() {
|
|
if (_glCache !== null) {
|
|
return _glCache;
|
|
}
|
|
try {
|
|
var canvas = document.createElement('canvas');
|
|
var gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
|
|
if (!gl) {
|
|
_glCache = '';
|
|
return _glCache;
|
|
}
|
|
var dbg = gl.getExtension('WEBGL_debug_renderer_info');
|
|
_glCache = dbg ? String(gl.getParameter(dbg.UNMASKED_RENDERER_WEBGL) || '') : '';
|
|
} catch (e) {
|
|
_glCache = '';
|
|
}
|
|
return _glCache;
|
|
}
|
|
|
|
function rule(scope, id, mmr, testFn) {
|
|
return { scope: scope, id: id, mmr: mmr, test: testFn };
|
|
}
|
|
|
|
var SIMPLE_RULES = [
|
|
rule('all', 'V01', 12, function (ctx) {
|
|
return !!navigator.webdriver;
|
|
}),
|
|
rule('all', 'V02', 12, function (ctx) {
|
|
return AUTO_UA.test(ctx.ua);
|
|
}),
|
|
rule('all', 'V03', 12, function (ctx) {
|
|
return !!(
|
|
window.callPhantom ||
|
|
window._phantom ||
|
|
window.__nightmare ||
|
|
document.__selenium_unwrapped ||
|
|
document.__webdriver_evaluate ||
|
|
window.domAutomation ||
|
|
window.domAutomationController
|
|
);
|
|
}),
|
|
rule('all', 'V04', 13, function (ctx) {
|
|
return VM_UA.test(ctx.ua);
|
|
}),
|
|
rule('all', 'V05', 14, function (ctx) {
|
|
return BOT_UA.test(ctx.ua);
|
|
}),
|
|
];
|
|
|
|
var STANDARD_RULES = [
|
|
rule('all', 'V10', 15, function (ctx) {
|
|
var r = getWebglRenderer();
|
|
return r !== '' && /llvmpipe|swiftshader|software renderer|mesa offscreen/i.test(r);
|
|
}),
|
|
rule('all', 'V11', 16, function (ctx) {
|
|
return window.outerWidth === 0 || window.outerHeight === 0;
|
|
}),
|
|
rule('all', 'V12', 16, function (ctx) {
|
|
var plat = ctx.platform;
|
|
if (ctx.isMobile) {
|
|
return plat === 'Win32' || plat === 'Win64';
|
|
}
|
|
return /iPhone|iPod|Android/i.test(plat);
|
|
}),
|
|
rule('mobileOnly', 'V13', 16, function (ctx) {
|
|
var tp = typeof navigator.maxTouchPoints === 'number' ? navigator.maxTouchPoints : 0;
|
|
return tp === 0 && !('ontouchstart' in window);
|
|
}),
|
|
];
|
|
|
|
var STRICT_RULES = [
|
|
rule('mobileOnly', 'V24', 17, function (ctx) {
|
|
var r = getWebglRenderer().toLowerCase();
|
|
if (!r) {
|
|
return false;
|
|
}
|
|
var mobileUa = /iPhone|iPad|iPod|Android/i.test(ctx.ua);
|
|
if (!mobileUa) {
|
|
return false;
|
|
}
|
|
return /angle|nvidia|amd|radeon|intel hd|intel\(r\)|llvmpipe|mesa|swiftshader|google swiftshader|microsoft basic render|vmware|virtualbox|citrix/i.test(r);
|
|
}),
|
|
];
|
|
|
|
function shouldRunRule(ruleDef, ctx) {
|
|
if (ruleDef.scope === 'mobileOnly' && !ctx.isMobile) {
|
|
return false;
|
|
}
|
|
if (ruleDef.scope === 'desktopOnly' && ctx.isMobile) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function evaluateRules(rules, ctx) {
|
|
for (var i = 0; i < rules.length; i++) {
|
|
var rd = rules[i];
|
|
if (!shouldRunRule(rd, ctx)) {
|
|
continue;
|
|
}
|
|
try {
|
|
if (rd.test(ctx)) {
|
|
return { blocked: true, ruleId: rd.id, mmr: rd.mmr };
|
|
}
|
|
} catch (e) {}
|
|
}
|
|
return { blocked: false };
|
|
}
|
|
|
|
function collectRules(level) {
|
|
var list = SIMPLE_RULES.slice();
|
|
if (level >= 2) {
|
|
list = list.concat(STANDARD_RULES);
|
|
}
|
|
if (level >= 3) {
|
|
list = list.concat(STRICT_RULES);
|
|
}
|
|
return list;
|
|
}
|
|
|
|
function hasAccelSample(event) {
|
|
var a = event && event.accelerationIncludingGravity;
|
|
if (!a) {
|
|
return false;
|
|
}
|
|
if (a.x == null && a.y == null && a.z == null) {
|
|
return false;
|
|
}
|
|
var mag = Math.abs(a.x || 0) + Math.abs(a.y || 0) + Math.abs(a.z || 0);
|
|
return mag > 0.5;
|
|
}
|
|
|
|
function hasOrientSample(event) {
|
|
if (!event) {
|
|
return false;
|
|
}
|
|
return event.alpha !== null || event.beta !== null || event.gamma !== null;
|
|
}
|
|
|
|
function checkMotionSensors(timeoutMs) {
|
|
timeoutMs = timeoutMs || 180;
|
|
return new Promise(function (resolve) {
|
|
if (typeof window.DeviceMotionEvent === 'undefined' && typeof window.DeviceOrientationEvent === 'undefined') {
|
|
resolve({ blocked: true, ruleId: 'V27', mmr: 18, detail: 'no_sensor_api' });
|
|
return;
|
|
}
|
|
|
|
var settled = false;
|
|
var gotAccel = false;
|
|
var gotOrient = false;
|
|
|
|
function finish(result) {
|
|
if (settled) {
|
|
return;
|
|
}
|
|
settled = true;
|
|
window.removeEventListener('devicemotion', onMotion);
|
|
window.removeEventListener('deviceorientation', onOrient);
|
|
resolve(result);
|
|
}
|
|
|
|
function onMotion(e) {
|
|
if (hasAccelSample(e)) {
|
|
gotAccel = true;
|
|
}
|
|
}
|
|
|
|
function onOrient(e) {
|
|
if (hasOrientSample(e)) {
|
|
gotOrient = true;
|
|
}
|
|
}
|
|
|
|
function listenAndWait() {
|
|
window.addEventListener('devicemotion', onMotion, { passive: true });
|
|
window.addEventListener('deviceorientation', onOrient, { passive: true });
|
|
setTimeout(function () {
|
|
if (gotAccel || gotOrient) {
|
|
finish({ blocked: false });
|
|
} else {
|
|
finish({ blocked: true, ruleId: 'V27', mmr: 18, detail: 'no_sensor_data' });
|
|
}
|
|
}, timeoutMs);
|
|
}
|
|
|
|
if (typeof DeviceMotionEvent !== 'undefined' && typeof DeviceMotionEvent.requestPermission === 'function') {
|
|
DeviceMotionEvent.requestPermission()
|
|
.then(function (state) {
|
|
if (state === 'granted') {
|
|
listenAndWait();
|
|
} else {
|
|
finish({ blocked: true, ruleId: 'V28', mmr: 18, detail: 'sensor_permission_denied' });
|
|
}
|
|
})
|
|
.catch(function () {
|
|
finish({ blocked: true, ruleId: 'V28', mmr: 18, detail: 'sensor_permission_error' });
|
|
});
|
|
return;
|
|
}
|
|
|
|
listenAndWait();
|
|
});
|
|
}
|
|
|
|
window.CloakVirtualDetect = {
|
|
run: function (level, ctx) {
|
|
level = parseInt(level, 10) || 0;
|
|
if (level <= 0) {
|
|
return Promise.resolve({ blocked: false });
|
|
}
|
|
|
|
ctx = ctx || {};
|
|
ctx.ua = ctx.ua || navigator.userAgent || '';
|
|
ctx.platform = ctx.platform || navigator.platform || '';
|
|
|
|
var syncResult = evaluateRules(collectRules(level), ctx);
|
|
if (syncResult.blocked) {
|
|
return Promise.resolve(syncResult);
|
|
}
|
|
|
|
if (level >= 3 && ctx.isMobile) {
|
|
return checkMotionSensors(180);
|
|
}
|
|
|
|
return Promise.resolve({ blocked: false });
|
|
},
|
|
};
|
|
})(window);
|
|
</script>
|