1use std::time::Duration;
7
8use rand::RngExt;
12
13use crate::config::HumanTypingConfig;
14use crate::error::Result;
15
16pub struct HumanTyper {
18 config: HumanTypingConfig,
20 rng: rand::rngs::ThreadRng,
22}
23
24impl HumanTyper {
25 #[must_use]
27 pub fn new() -> Self {
28 Self {
29 config: HumanTypingConfig::default(),
30 rng: rand::rng(),
31 }
32 }
33
34 #[must_use]
36 pub fn with_config(config: HumanTypingConfig) -> Self {
37 Self {
38 config,
39 rng: rand::rng(),
40 }
41 }
42
43 #[must_use]
45 pub const fn config(&self) -> &HumanTypingConfig {
46 &self.config
47 }
48
49 pub const fn set_config(&mut self, config: HumanTypingConfig) {
51 self.config = config;
52 }
53
54 pub fn next_delay(&mut self) -> Duration {
56 let base = self.config.base_delay.as_millis() as f64;
57 let variance = self.config.variance.as_millis() as f64;
58
59 let offset = self.rng.random_range(-1.0..1.0) * variance;
61 let delay_ms = (base + offset).max(10.0);
62
63 Duration::from_millis(delay_ms as u64)
64 }
65
66 pub fn should_make_typo(&mut self) -> bool {
68 self.config.typo_chance > 0.0 && self.rng.random::<f32>() < self.config.typo_chance
69 }
70
71 pub fn make_typo(&mut self, c: char) -> (char, bool) {
75 let nearby = get_nearby_keys(c);
77
78 if nearby.is_empty() {
79 return (c, false);
80 }
81
82 let idx = self.rng.random_range(0..nearby.len());
83 let typo = nearby[idx];
84 let should_correct = self.config.correction_chance > 0.0
85 && self.rng.random::<f32>() < self.config.correction_chance;
86
87 (typo, should_correct)
88 }
89
90 pub fn thinking_pause(&mut self) -> Duration {
92 let base_ms: f64 = 500.0;
93 let variance_ms: f64 = 300.0;
94 let offset: f64 = self.rng.random_range(-1.0..1.0) * variance_ms;
95 Duration::from_millis((base_ms + offset).max(100.0) as u64)
96 }
97
98 pub fn plan_typing(&mut self, text: &str) -> Vec<TypeEvent> {
100 let mut events = Vec::new();
101
102 for c in text.chars() {
103 if self.should_make_typo() && c.is_alphabetic() {
105 let (typo, should_correct) = self.make_typo(c);
106
107 events.push(TypeEvent::Char(typo));
108 events.push(TypeEvent::Delay(self.next_delay()));
109
110 if should_correct {
111 events.push(TypeEvent::Delay(self.thinking_pause()));
113 events.push(TypeEvent::Backspace);
115 events.push(TypeEvent::Delay(self.next_delay()));
116 events.push(TypeEvent::Char(c));
118 events.push(TypeEvent::Delay(self.next_delay()));
119 }
120 } else {
121 events.push(TypeEvent::Char(c));
122 events.push(TypeEvent::Delay(self.next_delay()));
123 }
124
125 if c == ' ' || c == '.' || c == ',' || c == '\n' {
127 events.push(TypeEvent::Delay(Duration::from_millis(
128 self.rng.random_range(50..150),
129 )));
130 }
131 }
132
133 events
134 }
135}
136
137impl Default for HumanTyper {
138 fn default() -> Self {
139 Self::new()
140 }
141}
142
143#[derive(Debug, Clone)]
145pub enum TypeEvent {
146 Char(char),
148 Delay(Duration),
150 Backspace,
152 Control(u8),
154}
155
156impl TypeEvent {
157 #[must_use]
159 pub fn as_bytes(&self) -> Option<Vec<u8>> {
160 match self {
161 Self::Char(c) => {
162 let mut buf = [0u8; 4];
163 let s = c.encode_utf8(&mut buf);
164 Some(s.as_bytes().to_vec())
165 }
166 Self::Backspace => Some(vec![0x7f]),
167 Self::Control(c) => Some(vec![*c]),
168 Self::Delay(_) => None,
169 }
170 }
171}
172
173fn get_nearby_keys(c: char) -> Vec<char> {
175 let c_lower = c.to_ascii_lowercase();
176
177 let nearby = match c_lower {
178 'q' => vec!['w', 'a', 's'],
179 'w' => vec!['q', 'e', 'a', 's', 'd'],
180 'e' => vec!['w', 'r', 's', 'd', 'f'],
181 'r' => vec!['e', 't', 'd', 'f', 'g'],
182 't' => vec!['r', 'y', 'f', 'g', 'h'],
183 'y' => vec!['t', 'u', 'g', 'h', 'j'],
184 'u' => vec!['y', 'i', 'h', 'j', 'k'],
185 'i' => vec!['u', 'o', 'j', 'k', 'l'],
186 'o' => vec!['i', 'p', 'k', 'l'],
187 'p' => vec!['o', 'l'],
188 'a' => vec!['q', 'w', 's', 'z'],
189 's' => vec!['q', 'w', 'e', 'a', 'd', 'z', 'x'],
190 'd' => vec!['w', 'e', 'r', 's', 'f', 'x', 'c'],
191 'f' => vec!['e', 'r', 't', 'd', 'g', 'c', 'v'],
192 'g' => vec!['r', 't', 'y', 'f', 'h', 'v', 'b'],
193 'h' => vec!['t', 'y', 'u', 'g', 'j', 'b', 'n'],
194 'j' => vec!['y', 'u', 'i', 'h', 'k', 'n', 'm'],
195 'k' => vec!['u', 'i', 'o', 'j', 'l', 'm'],
196 'l' => vec!['i', 'o', 'p', 'k'],
197 'z' => vec!['a', 's', 'x'],
198 'x' => vec!['s', 'd', 'z', 'c'],
199 'c' => vec!['d', 'f', 'x', 'v'],
200 'v' => vec!['f', 'g', 'c', 'b'],
201 'b' => vec!['g', 'h', 'v', 'n'],
202 'n' => vec!['h', 'j', 'b', 'm'],
203 'm' => vec!['j', 'k', 'n'],
204 _ => vec![],
205 };
206
207 if c.is_uppercase() {
209 nearby.into_iter().map(|c| c.to_ascii_uppercase()).collect()
210 } else {
211 nearby
212 }
213}
214
215#[derive(Debug, Clone, Copy, PartialEq, Eq)]
217pub enum TypingSpeed {
218 VerySlow,
220 Slow,
222 Normal,
224 Fast,
226 VeryFast,
228}
229
230impl TypingSpeed {
231 #[must_use]
233 pub fn config(self) -> HumanTypingConfig {
234 match self {
235 Self::VerySlow => HumanTypingConfig {
236 base_delay: Duration::from_millis(300),
237 variance: Duration::from_millis(150),
238 typo_chance: 0.03,
239 correction_chance: 0.95,
240 },
241 Self::Slow => HumanTypingConfig {
242 base_delay: Duration::from_millis(180),
243 variance: Duration::from_millis(80),
244 typo_chance: 0.02,
245 correction_chance: 0.9,
246 },
247 Self::Normal => HumanTypingConfig::default(),
248 Self::Fast => HumanTypingConfig {
249 base_delay: Duration::from_millis(60),
250 variance: Duration::from_millis(30),
251 typo_chance: 0.02,
252 correction_chance: 0.8,
253 },
254 Self::VeryFast => HumanTypingConfig {
255 base_delay: Duration::from_millis(30),
256 variance: Duration::from_millis(15),
257 typo_chance: 0.03,
258 correction_chance: 0.7,
259 },
260 }
261 }
262}
263
264pub trait HumanSend {
266 fn send_human(
268 &mut self,
269 text: &str,
270 config: HumanTypingConfig,
271 ) -> impl std::future::Future<Output = Result<()>> + Send;
272
273 fn send_human_speed(
275 &mut self,
276 text: &str,
277 speed: TypingSpeed,
278 ) -> impl std::future::Future<Output = Result<()>> + Send {
279 self.send_human(text, speed.config())
280 }
281}
282
283#[cfg(test)]
284mod tests {
285 use super::*;
286
287 #[test]
288 fn human_typer_delay() {
289 let mut typer = HumanTyper::new();
290
291 let delay = typer.next_delay();
292 assert!(delay.as_millis() >= 10);
293 }
294
295 #[test]
296 fn human_typer_plan() {
297 let mut typer = HumanTyper::with_config(HumanTypingConfig {
298 typo_chance: 0.0, ..Default::default()
300 });
301
302 let events = typer.plan_typing("hi");
303
304 assert!(events.len() >= 4);
306 assert!(matches!(events[0], TypeEvent::Char('h')));
307 assert!(matches!(events[2], TypeEvent::Char('i')));
308 }
309
310 #[test]
311 fn nearby_keys() {
312 let nearby = get_nearby_keys('f');
313 assert!(nearby.contains(&'d'));
314 assert!(nearby.contains(&'g'));
315 assert!(!nearby.contains(&'z'));
316
317 let nearby_upper = get_nearby_keys('F');
318 assert!(nearby_upper.contains(&'D'));
319 assert!(nearby_upper.contains(&'G'));
320 }
321
322 #[test]
323 fn typing_speed_config() {
324 let slow = TypingSpeed::Slow.config();
325 let fast = TypingSpeed::Fast.config();
326
327 assert!(slow.base_delay > fast.base_delay);
328 }
329}