Initial project structure: reusable isometric bot engine with D2R implementation
This commit is contained in:
commit
e0282a7111
44 changed files with 3433 additions and 0 deletions
112
engine/input/humanize.py
Normal file
112
engine/input/humanize.py
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
"""Human-like behavior patterns for input simulation.
|
||||
|
||||
Provides randomization utilities to make bot inputs appear natural,
|
||||
including variable delays, mouse jitter, and activity scheduling.
|
||||
"""
|
||||
|
||||
import random
|
||||
import time
|
||||
import logging
|
||||
from typing import Tuple, Optional
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class HumanProfile:
|
||||
"""Defines a human behavior profile for input randomization."""
|
||||
|
||||
# Reaction time range in seconds
|
||||
reaction_min: float = 0.15
|
||||
reaction_max: float = 0.45
|
||||
|
||||
# Mouse movement speed range (pixels per second)
|
||||
mouse_speed_min: float = 400.0
|
||||
mouse_speed_max: float = 1200.0
|
||||
|
||||
# Click position jitter in pixels
|
||||
click_jitter: int = 3
|
||||
|
||||
# Chance of double-reading (hesitation before action)
|
||||
hesitation_chance: float = 0.1
|
||||
hesitation_duration: Tuple[float, float] = (0.3, 1.2)
|
||||
|
||||
# Break scheduling
|
||||
micro_break_interval: Tuple[int, int] = (120, 300) # seconds
|
||||
micro_break_duration: Tuple[int, int] = (2, 8) # seconds
|
||||
long_break_interval: Tuple[int, int] = (1800, 3600) # seconds
|
||||
long_break_duration: Tuple[int, int] = (60, 300) # seconds
|
||||
|
||||
|
||||
class Humanizer:
|
||||
"""Applies human-like randomization to bot actions."""
|
||||
|
||||
def __init__(self, profile: Optional[HumanProfile] = None):
|
||||
self.profile = profile or HumanProfile()
|
||||
self._last_micro_break = time.time()
|
||||
self._last_long_break = time.time()
|
||||
self._next_micro_break = self._schedule_break(self.profile.micro_break_interval)
|
||||
self._next_long_break = self._schedule_break(self.profile.long_break_interval)
|
||||
self._action_count = 0
|
||||
|
||||
def reaction_delay(self) -> float:
|
||||
"""Generate a human-like reaction delay."""
|
||||
base = random.uniform(self.profile.reaction_min, self.profile.reaction_max)
|
||||
|
||||
# Occasionally add hesitation
|
||||
if random.random() < self.profile.hesitation_chance:
|
||||
base += random.uniform(*self.profile.hesitation_duration)
|
||||
|
||||
# Slight fatigue factor based on actions performed
|
||||
fatigue = min(self._action_count / 1000, 0.3)
|
||||
base *= (1 + fatigue * random.random())
|
||||
|
||||
return base
|
||||
|
||||
def jitter_position(self, x: int, y: int) -> Tuple[int, int]:
|
||||
"""Add small random offset to click position."""
|
||||
jitter = self.profile.click_jitter
|
||||
return (
|
||||
x + random.randint(-jitter, jitter),
|
||||
y + random.randint(-jitter, jitter),
|
||||
)
|
||||
|
||||
def mouse_speed(self) -> float:
|
||||
"""Get randomized mouse movement speed."""
|
||||
return random.uniform(
|
||||
self.profile.mouse_speed_min,
|
||||
self.profile.mouse_speed_max,
|
||||
)
|
||||
|
||||
def should_take_break(self) -> Optional[float]:
|
||||
"""Check if it's time for a break. Returns break duration or None."""
|
||||
now = time.time()
|
||||
|
||||
if now >= self._next_long_break:
|
||||
duration = random.uniform(*self.profile.long_break_duration)
|
||||
self._next_long_break = now + duration + self._schedule_break(
|
||||
self.profile.long_break_interval
|
||||
)
|
||||
logger.info(f"Long break: {duration:.0f}s")
|
||||
return duration
|
||||
|
||||
if now >= self._next_micro_break:
|
||||
duration = random.uniform(*self.profile.micro_break_duration)
|
||||
self._next_micro_break = now + duration + self._schedule_break(
|
||||
self.profile.micro_break_interval
|
||||
)
|
||||
logger.debug(f"Micro break: {duration:.1f}s")
|
||||
return duration
|
||||
|
||||
return None
|
||||
|
||||
def wait(self) -> None:
|
||||
"""Wait for a human-like reaction delay."""
|
||||
delay = self.reaction_delay()
|
||||
time.sleep(delay)
|
||||
self._action_count += 1
|
||||
|
||||
def _schedule_break(self, interval: Tuple[int, int]) -> float:
|
||||
"""Schedule next break with randomized interval."""
|
||||
return time.time() + random.uniform(*interval)
|
||||
Loading…
Add table
Add a link
Reference in a new issue