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