Skip to content

Instantly share code, notes, and snippets.

@tobwen
Last active November 4, 2024 18:40
Show Gist options
  • Save tobwen/9c5d109e7bdfce163e4315a10c199f5d to your computer and use it in GitHub Desktop.
Save tobwen/9c5d109e7bdfce163e4315a10c199f5d to your computer and use it in GitHub Desktop.
human typing simulator
# This code is under license CC0.
import argparse
import random
import sys
import time
from typing import List, Tuple
import datetime
# Keyboard layout mapping for common typos (QWERTY layout)
ADJACENT_KEYS = {
'a': ['q', 'w', 's', 'z'],
'b': ['v', 'n', 'h', 'g'],
'c': ['x', 'v', 'f', 'd'],
'd': ['s', 'f', 'g', 'e', 'r'],
'e': ['w', 'r', 'd', 's'],
'f': ['d', 'g', 'v', 'c', 'r', 't'],
'g': ['f', 'h', 'b', 'v', 't', 'y'],
'h': ['g', 'j', 'n', 'b', 'y', 'u'],
'i': ['u', 'o', 'k', 'j'],
'j': ['h', 'k', 'm', 'n', 'u', 'i'],
'k': ['j', 'l', 'm', 'i', 'o'],
'l': ['k', 'o', 'p'],
'm': ['n', 'j', 'k'],
'n': ['b', 'h', 'j', 'm'],
'o': ['i', 'p', 'l', 'k'],
'p': ['o', 'l'],
'q': ['w', 'a'],
'r': ['e', 't', 'f', 'd'],
's': ['a', 'd', 'x', 'w'],
't': ['r', 'y', 'g', 'f'],
'u': ['y', 'i', 'j', 'h'],
'v': ['c', 'b', 'f', 'g'],
'w': ['q', 'e', 's', 'a'],
'x': ['z', 'c', 's', 'd'],
'y': ['t', 'u', 'h', 'g'],
'z': ['a', 's', 'x'],
' ': ['b', 'n', 'm']
}
class HumanTypingSimulator:
def __init__(self,
typo_probability: float = 0.05,
correction_probability: float = 0.9,
word_dropout_rate: float = 0.1,
delay_range: Tuple[float, float] = (0.05, 0.3)):
self.typo_probability = typo_probability
self.correction_probability = correction_probability
self.word_dropout_rate = word_dropout_rate
self.delay_min, self.delay_max = delay_range
def get_typing_delay(self, char: str) -> float:
"""Generate a human-like typing delay with context-based variability"""
random.seed()
base_delay = random.gauss(
(self.delay_min + self.delay_max) / 2,
(self.delay_max - self.delay_min) / 6
)
jitter = random.uniform(-0.02, 0.02)
if char.isspace():
pause = random.uniform(0.3, 0.6)
elif char in ",.!?":
pause = random.uniform(0.2, 0.4)
else:
pause = 0
final_delay = base_delay + jitter + pause
return max(self.delay_min, min(final_delay, self.delay_max))
def get_typo(self, char: str) -> str:
"""Get a possible typo for the given character"""
random.seed()
char = char.lower()
if char in ADJACENT_KEYS:
return random.choice(ADJACENT_KEYS[char])
return char
def simulate_typing(self, text: str) -> None:
"""Simulate human typing with varied delays, contextual pauses, and word dropouts"""
words = text.split()
if not words:
return
# Determine if and which word to drop
dropped_word = None
dropped_index = -1
if len(words) > 1 and random.random() < self.word_dropout_rate:
dropped_index = random.randint(0, len(words) - 1)
dropped_word = words.pop(dropped_index)
# Join the remaining words
current_text = ' '.join(words)
# Pre-calculate typos for the text
make_typos = [random.random() < self.typo_probability for _ in current_text]
typed_chars = 0
buffer: List[str] = []
# Type the main text
for i, char in enumerate(current_text):
time.sleep(self.get_typing_delay(char))
if make_typos[i]:
typo = self.get_typo(char)
buffer.append(typo)
sys.stdout.write(typo)
sys.stdout.flush()
typed_chars += 1
# Always correct typos
time.sleep(self.get_typing_delay(char))
sys.stdout.write('\b \b')
sys.stdout.flush()
buffer.pop()
typed_chars -= 1
time.sleep(self.get_typing_delay(char))
buffer.append(char)
sys.stdout.write(char)
sys.stdout.flush()
typed_chars += 1
else:
buffer.append(char)
sys.stdout.write(char)
sys.stdout.flush()
typed_chars += 1
# If we dropped a word, go back and insert it
if dropped_word:
# Simulate realizing the mistake
time.sleep(random.uniform(0.8, 1.5))
# Calculate position to insert the word
if dropped_index == 0:
insert_position = 0
else:
insert_position = sum(len(words[i]) + 1 for i in range(dropped_index))
# Move cursor back to insertion point
for _ in range(typed_chars - insert_position):
sys.stdout.write('\b')
sys.stdout.flush()
# Type the dropped word and a space
for char in dropped_word + ' ':
time.sleep(self.get_typing_delay(char))
sys.stdout.write(char)
sys.stdout.flush()
# Retype the rest of the text
rest_of_text = current_text[insert_position:]
for char in rest_of_text:
time.sleep(self.get_typing_delay(char))
sys.stdout.write(char)
sys.stdout.flush()
def parse_delay_range(delay_range_str: str) -> Tuple[float, float]:
"""Parse delay range string in format 'min-max'"""
try:
min_delay, max_delay = map(float, delay_range_str.split('-'))
if min_delay < 0 or max_delay < 0 or min_delay >= max_delay:
raise ValueError
return (min_delay, max_delay)
except ValueError:
raise argparse.ArgumentTypeError(
"Delay range must be in format 'min-max' where min and max are positive numbers and min < max"
)
def main():
parser = argparse.ArgumentParser(description='Simulate human-like typing with typos and delays')
parser.add_argument('text', help='Text to type')
parser.add_argument('--typo-probability', type=float, default=0.05,
help='Probability of making a typo (default: 0.05)')
parser.add_argument('--correction-probability', type=float, default=0.9,
help='Probability of correcting a typo (default: 0.9)')
parser.add_argument('--word-dropout-rate', type=float, default=0.1,
help='Probability of dropping a word and adding it later (default: 0.1)')
parser.add_argument('--delay-range', type=parse_delay_range, default='0.05-0.3',
help='Delay range in seconds (format: min-max, default: 0.05-0.3)')
args = parser.parse_args()
simulator = HumanTypingSimulator(
typo_probability=args.typo_probability,
correction_probability=args.correction_probability,
word_dropout_rate=args.word_dropout_rate,
delay_range=args.delay_range
)
simulator.simulate_typing(args.text)
sys.stdout.write('\n')
if __name__ == '__main__':
main()
"""
Example usages:
# Default usage
python3 typing_simulator.py "Hello, World!"
# Fast typing (50-150ms between keystrokes)
python3 typing_simulator.py --delay-range 0.05-0.15 "Hello, World!"
# Very fast typing (20-80ms between keystrokes)
python3 typing_simulator.py --delay-range 0.02-0.08 "Hello, World!"
# Slow typing (200-500ms between keystrokes)
python3 typing_simulator.py --delay-range 0.2-0.5 "Hello, World!"
# Higher word dropout rate (30% chance to drop a word)
python3 typing_simulator.py --word-dropout-rate 0.3 "The quick brown fox jumps over the lazy dog"
# Fast typing with high typo rate and word dropouts
python3 typing_simulator.py --delay-range 0.02-0.05 --typo-probability 0.2 --word-dropout-rate 0.9 "The quick brown fox jumps over the lazy dog"
"""
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment