feat: initial commit
This commit is contained in:
@@ -0,0 +1,242 @@
|
||||
<?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>
|
||||
Reference in New Issue
Block a user