157 lines
5.2 KiB
JavaScript
157 lines
5.2 KiB
JavaScript
/**
|
|
* Motion Generator - Drawing the Soul
|
|
*
|
|
* hCaptcha uses mouse trajectory analysis to detect bots.
|
|
* Straight lines = robot = death.
|
|
*
|
|
* We generate human-like mouse movements using:
|
|
* - Bezier curves for smooth paths
|
|
* - Perlin noise for natural jitter
|
|
* - Realistic velocity profiles (slow start, fast middle, slow end)
|
|
*/
|
|
|
|
export class MotionGenerator {
|
|
constructor(options = {}) {
|
|
this.screenWidth = options.screenWidth || 1920;
|
|
this.screenHeight = options.screenHeight || 1080;
|
|
this.checkboxPos = options.checkboxPos || { x: 200, y: 300 };
|
|
}
|
|
|
|
/**
|
|
* Generate complete motion data matching hCaptcha's expected format
|
|
*/
|
|
generate() {
|
|
const startTime = Date.now();
|
|
const duration = this._randomBetween(800, 2000); // Human reaction time
|
|
|
|
// Starting point (off-screen or edge)
|
|
const start = {
|
|
x: this._randomBetween(-50, 50),
|
|
y: this._randomBetween(this.screenHeight / 2, this.screenHeight),
|
|
};
|
|
|
|
// Target: the checkbox
|
|
const end = {
|
|
x: this.checkboxPos.x + this._randomBetween(-5, 5),
|
|
y: this.checkboxPos.y + this._randomBetween(-5, 5),
|
|
};
|
|
|
|
// Generate movement points
|
|
const mm = this._generateMouseMoves(start, end, startTime, duration);
|
|
|
|
// Mouse down/up at the end
|
|
const clickTime = startTime + duration + this._randomBetween(50, 150);
|
|
const md = [[end.x, end.y, clickTime]];
|
|
const mu = [[end.x, end.y, clickTime + this._randomBetween(80, 150)]];
|
|
|
|
return {
|
|
st: startTime, // Start timestamp
|
|
dct: startTime, // Document creation time
|
|
mm, // Mouse moves: [[x, y, timestamp], ...]
|
|
md, // Mouse down
|
|
mu, // Mouse up
|
|
topLevel: {
|
|
st: startTime - this._randomBetween(1000, 3000),
|
|
sc: {
|
|
availWidth: this.screenWidth,
|
|
availHeight: this.screenHeight - 40,
|
|
width: this.screenWidth,
|
|
height: this.screenHeight,
|
|
colorDepth: 24,
|
|
pixelDepth: 24,
|
|
},
|
|
nv: {
|
|
vendorSub: '',
|
|
productSub: '20030107',
|
|
vendor: 'Google Inc.',
|
|
maxTouchPoints: 0,
|
|
hardwareConcurrency: 8,
|
|
cookieEnabled: true,
|
|
appCodeName: 'Mozilla',
|
|
appName: 'Netscape',
|
|
appVersion: '5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
|
|
platform: 'Win32',
|
|
product: 'Gecko',
|
|
language: 'en-US',
|
|
onLine: true,
|
|
deviceMemory: 8,
|
|
},
|
|
dr: '',
|
|
inv: false,
|
|
exec: false,
|
|
},
|
|
v: 1,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Generate mouse movement points using Bezier curves
|
|
*/
|
|
_generateMouseMoves(start, end, startTime, duration) {
|
|
const points = [];
|
|
const numPoints = this._randomBetween(30, 60);
|
|
|
|
// Control points for cubic Bezier
|
|
const cp1 = {
|
|
x: start.x + (end.x - start.x) * 0.3 + this._randomBetween(-100, 100),
|
|
y: start.y + (end.y - start.y) * 0.1 + this._randomBetween(-50, 50),
|
|
};
|
|
const cp2 = {
|
|
x: start.x + (end.x - start.x) * 0.7 + this._randomBetween(-50, 50),
|
|
y: start.y + (end.y - start.y) * 0.9 + this._randomBetween(-30, 30),
|
|
};
|
|
|
|
for (let i = 0; i < numPoints; i++) {
|
|
// Non-linear time distribution (ease-in-out)
|
|
const rawT = i / (numPoints - 1);
|
|
const t = this._easeInOutCubic(rawT);
|
|
|
|
// Bezier interpolation
|
|
const pos = this._cubicBezier(start, cp1, cp2, end, t);
|
|
|
|
// Add micro-jitter (human hands shake)
|
|
pos.x += this._randomBetween(-2, 2);
|
|
pos.y += this._randomBetween(-2, 2);
|
|
|
|
// Timestamp with slight randomness
|
|
const timestamp = startTime + Math.floor(duration * rawT) + this._randomBetween(-5, 5);
|
|
|
|
points.push([Math.round(pos.x), Math.round(pos.y), timestamp]);
|
|
}
|
|
|
|
// Sort by timestamp
|
|
points.sort((a, b) => a[2] - b[2]);
|
|
|
|
return points;
|
|
}
|
|
|
|
/**
|
|
* Cubic Bezier interpolation
|
|
*/
|
|
_cubicBezier(p0, p1, p2, p3, t) {
|
|
const t2 = t * t;
|
|
const t3 = t2 * t;
|
|
const mt = 1 - t;
|
|
const mt2 = mt * mt;
|
|
const mt3 = mt2 * mt;
|
|
|
|
return {
|
|
x: mt3 * p0.x + 3 * mt2 * t * p1.x + 3 * mt * t2 * p2.x + t3 * p3.x,
|
|
y: mt3 * p0.y + 3 * mt2 * t * p1.y + 3 * mt * t2 * p2.y + t3 * p3.y,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Easing function for natural movement
|
|
*/
|
|
_easeInOutCubic(t) {
|
|
return t < 0.5
|
|
? 4 * t * t * t
|
|
: 1 - Math.pow(-2 * t + 2, 3) / 2;
|
|
}
|
|
|
|
_randomBetween(min, max) {
|
|
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
}
|
|
}
|