humanized-autotyper 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- humanized_autotyper-1.0.0/PKG-INFO +232 -0
- humanized_autotyper-1.0.0/README.md +222 -0
- humanized_autotyper-1.0.0/humanized_autotyper/__init__.py +12 -0
- humanized_autotyper-1.0.0/humanized_autotyper/__main__.py +7 -0
- humanized_autotyper-1.0.0/humanized_autotyper/buffer.py +98 -0
- humanized_autotyper-1.0.0/humanized_autotyper/config.py +90 -0
- humanized_autotyper-1.0.0/humanized_autotyper/delays.py +130 -0
- humanized_autotyper-1.0.0/humanized_autotyper/executor.py +185 -0
- humanized_autotyper-1.0.0/humanized_autotyper/gui.py +546 -0
- humanized_autotyper-1.0.0/humanized_autotyper/keymap.py +77 -0
- humanized_autotyper-1.0.0/humanized_autotyper/machine.py +441 -0
- humanized_autotyper-1.0.0/humanized_autotyper/main.py +161 -0
- humanized_autotyper-1.0.0/humanized_autotyper/presets.py +62 -0
- humanized_autotyper-1.0.0/humanized_autotyper/report.py +83 -0
- humanized_autotyper-1.0.0/humanized_autotyper/sanity_check.py +329 -0
- humanized_autotyper-1.0.0/humanized_autotyper/typos.py +78 -0
- humanized_autotyper-1.0.0/humanized_autotyper/validate.py +64 -0
- humanized_autotyper-1.0.0/humanized_autotyper.egg-info/PKG-INFO +232 -0
- humanized_autotyper-1.0.0/humanized_autotyper.egg-info/SOURCES.txt +23 -0
- humanized_autotyper-1.0.0/humanized_autotyper.egg-info/dependency_links.txt +1 -0
- humanized_autotyper-1.0.0/humanized_autotyper.egg-info/entry_points.txt +4 -0
- humanized_autotyper-1.0.0/humanized_autotyper.egg-info/requires.txt +1 -0
- humanized_autotyper-1.0.0/humanized_autotyper.egg-info/top_level.txt +1 -0
- humanized_autotyper-1.0.0/pyproject.toml +30 -0
- humanized_autotyper-1.0.0/setup.cfg +4 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: humanized-autotyper
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: 模拟真人打字节奏的自动输入器:对数正态延迟、突发快打、错字回删、长暂停与恢复仪式
|
|
5
|
+
Classifier: Programming Language :: Python :: 3
|
|
6
|
+
Classifier: Topic :: Utilities
|
|
7
|
+
Requires-Python: >=3.8
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: pynput>=1.7
|
|
10
|
+
|
|
11
|
+
# Humanized Autotyper
|
|
12
|
+
|
|
13
|
+
模拟真人打字节奏的自动输入器:对数正态延迟、突发快打、疲劳漂移、内联错字与
|
|
14
|
+
自我纠正、回删爆发、回到前文原位重写、整段重打、长暂停与恢复仪式。
|
|
15
|
+
|
|
16
|
+
## 安装
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
py -m pip install -e . # 可编辑安装(改代码立刻生效,开发用这个)
|
|
20
|
+
py -m pip install . # 普通安装(拷进 site-packages,改源码不再生效)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
安装后有三条命令:
|
|
24
|
+
|
|
25
|
+
| 命令 | 作用 |
|
|
26
|
+
|---|---|
|
|
27
|
+
| `autotyper` | 命令行输入器(默认 dry-run,加 `--live` 才真打字) |
|
|
28
|
+
| `autotyper-gui` | Tk 图形界面 |
|
|
29
|
+
| `autotyper-report` | 审计 CSV 事件日志(观测频率 vs 配置概率) |
|
|
30
|
+
|
|
31
|
+
不想让脚本进 PATH,也可以用模块方式(三者等价):
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
py -m humanized_autotyper # == autotyper
|
|
35
|
+
py -m humanized_autotyper.gui # == autotyper-gui
|
|
36
|
+
py -m humanized_autotyper.report out.csv
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
- 运行时依赖只有 `pynput`(`pip install` 会自动装),它只在**真实输入**时才被
|
|
40
|
+
import;dry-run、自检、日志审计都不需要它。
|
|
41
|
+
- **用户数据都在「当前工作目录」**:`source.txt`(GUI 内容页读取/保存)、
|
|
42
|
+
`gui_settings.json`(滑块记忆)、`autotyper_log.csv`(默认日志路径)。
|
|
43
|
+
`autotyper` 不带 `--file` 时先找 `./source.txt`,找不到才用内置演示代码。
|
|
44
|
+
- 图形界面需要 Python 自带 tkinter(Windows/macOS 官方安装包默认自带;
|
|
45
|
+
部分 Linux 发行版要额外装 `python3-tk`)。
|
|
46
|
+
|
|
47
|
+
## 图形界面(推荐入门方式)
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
autotyper-gui # 或 py -m humanized_autotyper.gui
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
四个标签页:
|
|
54
|
+
|
|
55
|
+
| 页 | 功能 |
|
|
56
|
+
|---|---|
|
|
57
|
+
| 📝 内容 | 直接编辑要输入的文字(保存到 source.txt),或从文件导入 |
|
|
58
|
+
| ⌨️ 节奏与错误 | 滑块调整:基础敲键间隔 / 节奏抖动 / 突发快打 / 错字率 / 连错窗口 / 疲劳 |
|
|
59
|
+
| ⏸️ 停顿与删改 | 滑块调整:回删爆发 / 短停顿 / 长暂停(可开关) / 跳回重写 / 整段重打 |
|
|
60
|
+
| ▶️ 运行 | 预设人设(标准/高效型程序员/手生普通人)、倒计时 |
|
|
61
|
+
|
|
62
|
+
使用流程:**①** 「内容」页写好文字 → **②** 「运行」页设好倒计时秒数与速度
|
|
63
|
+
→ **③** 点「开始真实输入」→ **④** 趁倒计时把光标点进目标窗口。滑块参数会自动
|
|
64
|
+
记忆(gui_settings.json)。命令行高级用法见下。
|
|
65
|
+
|
|
66
|
+
> GUI 只有真实输入(没有演练模式)。想先无副作用地空跑一遍验证文本模型,用
|
|
67
|
+
> CLI 的默认 dry-run:`autotyper --file 你的文件`(不传 `--live`)。
|
|
68
|
+
|
|
69
|
+
## 预设人设(GUI 与 CLI 共用)
|
|
70
|
+
|
|
71
|
+
三个内置人设定义在 `presets.py`,两端行为完全一致:
|
|
72
|
+
|
|
73
|
+
| 预设 | 特点 |
|
|
74
|
+
|---|---|
|
|
75
|
+
| 标准 | config 默认参数 |
|
|
76
|
+
| 高效型程序员 | 中位间隔~80ms、错字率0.5%、节奏稳 |
|
|
77
|
+
| 手生普通人 | 中位间隔~180ms、错字率2.5%、忽快忽慢易连错 |
|
|
78
|
+
|
|
79
|
+
- CLI:`autotyper --preset 手生普通人 --live --window Notepad`
|
|
80
|
+
(`--preset list` 查看全部;预设只覆盖行为参数,`--speed`/窗口等不受影响)
|
|
81
|
+
- GUI:「运行」页下拉框选择即自动换算到滑块
|
|
82
|
+
|
|
83
|
+
### 新增自定义预设
|
|
84
|
+
|
|
85
|
+
在 `presets.py` 的 `PRESETS` 里加一项即可,两个界面自动出现:
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
PRESETS["夜班摸鱼"] = {
|
|
89
|
+
"delay_mu": 0.25, # Config 真实单位:秒 / 概率小数
|
|
90
|
+
"p_typo": 0.02,
|
|
91
|
+
...
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## 文件结构
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
pyproject.toml # 打包配置:元数据、依赖、console scripts
|
|
99
|
+
README.md
|
|
100
|
+
source.txt # 默认输入内容(也可用 --file 指定其他文件)
|
|
101
|
+
humanized_autotyper/ # 包本体
|
|
102
|
+
├── __init__.py # 版本号(pyproject 从这里动态取)
|
|
103
|
+
├── __main__.py # python -m humanized_autotyper
|
|
104
|
+
├── config.py # 全部可调参数(概率、分布参数)
|
|
105
|
+
├── presets.py # 预设人设(GUI 与 CLI 共用)
|
|
106
|
+
├── keymap.py # Shift 符号表 + 邻键表 + 控制字符预检
|
|
107
|
+
├── buffer.py # 虚拟文本缓冲区(唯一真相源,禁止读屏)
|
|
108
|
+
├── delays.py # 延迟采样器(lognormal + 修正因子 + burst/疲劳/预热)
|
|
109
|
+
├── typos.py # 错误生成器(adjacent/double/transposition/random)
|
|
110
|
+
├── executor.py # pynput 键盘层 + dry-run + F10 终止 + CSV 日志
|
|
111
|
+
├── machine.py # 状态机(TYPING/BACKSPACE_BURST/PAUSE_SHORT/
|
|
112
|
+
│ # PAUSE_LONG/EDIT_PREVIOUS/REWRITE_CHUNK)
|
|
113
|
+
├── main.py # CLI 入口(autotyper):倒计时、窗口校验、调度循环
|
|
114
|
+
├── gui.py # 图形界面(autotyper-gui)
|
|
115
|
+
├── report.py # 事件日志频率审计(autotyper-report)
|
|
116
|
+
├── sanity_check.py # 全链路 dry-run 自检 + 单元验证
|
|
117
|
+
└── validate.py # 多种子批量验证 + kill 开关路径测试
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
包内模块之间用相对导入(`from .config import Config`),因此要整体安装后使用;
|
|
121
|
+
单独 `py humanized_autotyper/main.py` 这样直接跑文件是不行的,用命令或 `-m`。
|
|
122
|
+
|
|
123
|
+
## 快速开始
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
autotyper --preset list # 看预设
|
|
127
|
+
|
|
128
|
+
# dry-run 冒烟(无副作用,导出事件日志)
|
|
129
|
+
autotyper --file mysource.py --seed 1 --log out.csv
|
|
130
|
+
|
|
131
|
+
# 审计实际频率是否命中配置概率
|
|
132
|
+
autotyper-report out.csv
|
|
133
|
+
|
|
134
|
+
# 实机输入(见下方安全清单!)
|
|
135
|
+
autotyper --live --window Notepad --file mysource.py
|
|
136
|
+
|
|
137
|
+
# 三条自检入口
|
|
138
|
+
py -m humanized_autotyper.sanity_check # 应输出 ALL SANITY CHECKS PASSED
|
|
139
|
+
py -m humanized_autotyper.validate # 批量种子验证 + kill 开关测试
|
|
140
|
+
autotyper-gui --selftest # 无界面 GUI 自检
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
`autotyper` 参数:
|
|
144
|
+
|
|
145
|
+
| 参数 | 说明 |
|
|
146
|
+
|---|---|
|
|
147
|
+
| `--file` | 要输入的源文本(UTF-8)。缺省时读当前目录 `source.txt`,都没有才用内置演示代码 |
|
|
148
|
+
| `--live` | 发送真实击键(默认 dry-run) |
|
|
149
|
+
| `--speed X` | 整体速度倍率:>1 加速 / <1 减速(只缩放墙钟时间,不改变人类节奏形状) |
|
|
150
|
+
| `--seed` | 固定随机种子(复现行为) |
|
|
151
|
+
| `--window "标题子串"` | 前台窗口校验,不匹配则拒绝启动(强烈建议加) |
|
|
152
|
+
| `--log FILE` | CSV 事件日志路径 |
|
|
153
|
+
| `--verbose` | 逐事件打印到控制台 |
|
|
154
|
+
|
|
155
|
+
## LIVE 模式安全清单
|
|
156
|
+
|
|
157
|
+
1. **目标窗口就位**:先打开并聚焦目标窗口(如记事本),再运行命令。
|
|
158
|
+
必须传 `--window`——启动时与每个行尾都会校验前台窗口标题,不匹配直接中止。
|
|
159
|
+
注意 **GUI 没有这道校验**,只能靠倒计时切焦点 + F10 兜底。
|
|
160
|
+
2. **倒计时 5 秒**:启动后立即把焦点切到目标窗口。
|
|
161
|
+
3. **F10 = 紧急停止**:pynput 监听线程独立置位 kill_flag,分片 sleep 保证
|
|
162
|
+
最长约一个 sleep 片(短停顿 0.25s / 长停顿 2s)内响应。若停止请求正好落在
|
|
163
|
+
"已删除、还没重打"之间,会先把这一小段补完再停,以免文档缺字。
|
|
164
|
+
4. **只在无副作用的编辑器里测**:不要对着聊天框/终端/IDE 测试,
|
|
165
|
+
Edit/Rewrite 状态会移动光标、删除重写前文。
|
|
166
|
+
5. 首次实测先用小文本、把速度倍率调大(GUI 的「整体速度倍率」/ CLI `--speed 3`)
|
|
167
|
+
跑一遍,确认字符映射(尤其符号)无误后再恢复 1.0 倍速。
|
|
168
|
+
6. 结果校验:结束后 `final_matches_source: True` 表示 buffer 与源文本一致;
|
|
169
|
+
若为 False 说明目标应用吞了某个键(如 IME 截走了 shift 组合),查 CSV 里
|
|
170
|
+
出错位置附近的 `detail` 列。
|
|
171
|
+
|
|
172
|
+
## 输出效果(默认参数下的大致画像)
|
|
173
|
+
|
|
174
|
+
- 中位敲键间隔 ~110ms(约 9 字/秒),句末/换行后有顿挫,空格带词边界微停顿
|
|
175
|
+
- ~5% 的字符处于 0.55× 速度的突发段(每分钟几次,6–20 字符一簇)
|
|
176
|
+
- 打字错误率 1.2%/字符(错后 10 字符内翻倍——手滑连错),
|
|
177
|
+
65% 在下一个字符就被发现退格纠正
|
|
178
|
+
- 每 ~4 分钟左右一次 4–35 分钟的长暂停(Pareto 分布,只在词边界打断),
|
|
179
|
+
醒来后有 Left×N → Right×N 的"找回位置"仪式 + 10 字符预热减速
|
|
180
|
+
- 行尾小概率跳回前 5–40 行原位重写单词/短句,或选中 1–3 行整段重打
|
|
181
|
+
|
|
182
|
+
## 光标移动策略(纯方向键)
|
|
183
|
+
|
|
184
|
+
所有导航/选区**只使用 ↑ ↓ ← → 四个方向键**(加 Shift 做选区),不使用
|
|
185
|
+
Home / End / Ctrl 组合,也不用鼠标和剪贴板:
|
|
186
|
+
|
|
187
|
+
- 跨行移动:先 `Up/Down × 行数差`(列粘滞由 buffer 镜像推算),再
|
|
188
|
+
`Right/Left` 校正列差
|
|
189
|
+
- 同行移动:直接 `Right/Left × delta`
|
|
190
|
+
- 选区:`Shift+Down`(整行)/ `Shift+Right`(行内),纯方向键组合
|
|
191
|
+
|
|
192
|
+
## 调参指南(config.py)
|
|
193
|
+
|
|
194
|
+
### 改"打得有多像人"
|
|
195
|
+
|
|
196
|
+
| 旋钮 | 默认 | 效果 |
|
|
197
|
+
|---|---|---|
|
|
198
|
+
| `delay_mu` | 0.11s | 基础节奏中位数(lognormal μ),调慢打字整体放慢 |
|
|
199
|
+
| `delay_ln_sigma` | 0.42 | 节奏抖动;加大更像新手,减小更机械 |
|
|
200
|
+
| `burst_len_range` / `p_burst_enter` | (5,20) / 0.05 | 突发段长度 / 进入概率 |
|
|
201
|
+
| `fatigue_rate` / `fatigue_cap` | 0.005 / 1.25 | 每活跃分钟变慢 0.5%,上限 1.25× |
|
|
202
|
+
| `warmup_factor` / `warmup_chars` | 1.6 / 10 | ≥5s 停顿后的预热减速曲线 |
|
|
203
|
+
|
|
204
|
+
### 改"犯错多少"
|
|
205
|
+
|
|
206
|
+
| 旋钮 | 默认 | 效果 |
|
|
207
|
+
|---|---|---|
|
|
208
|
+
| `p_typo` | 0.012 | 每字符错误率 |
|
|
209
|
+
| `typo_weights` | adjacent 60%… | 错误类型构成(邻键/双打/换序/随机) |
|
|
210
|
+
| `typo_detect_probs` | (.65,.25,.10) | 错后第 0/1/2 个字符发现的比例 |
|
|
211
|
+
| `typo_autocorr_window` | 10 | 手滑连错的窗口长度 |
|
|
212
|
+
|
|
213
|
+
### 改"中间行为"
|
|
214
|
+
|
|
215
|
+
| 旋钮 | 默认 | 效果 |
|
|
216
|
+
|---|---|---|
|
|
217
|
+
| `p_backspace_burst` | 0.008 | 回删爆发频率(每次连删 2–12 字符) |
|
|
218
|
+
| `p_pause_short` / `p_pause_short_sentence` | .008 / .06 | 短停顿频率(后者用于句末字符之后) |
|
|
219
|
+
| `p_edit_previous` / `edit_min_gap_s` | .004 / 90s | 跳回前文重写频率 / 两次最少间隔 |
|
|
220
|
+
| `p_rewrite_chunk` | .006 | 整段重打频率(行尾评估) |
|
|
221
|
+
| `long_pause_xmin` / `alpha` / `cap` | 240s / 1.4 / 2100s | 长停顿时长分布 |
|
|
222
|
+
|
|
223
|
+
## 工作流(蓝图的调参循环)
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
autotyper --file big_source.py --log out.csv # dry-run 导出
|
|
227
|
+
autotyper-report out.csv # obs vs cfg 对照
|
|
228
|
+
# → 调 config.py → 重跑 → 直到各状态观测频率贴近配置值 → 再实机
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
CSV 格式:`ts,state,event,detail,delay_ms`(csv.writer 正确转义逗号)。
|
|
232
|
+
`autotyper-report` 给出 typo/burst/pause 观测率 vs 配置率、逐字符延迟分布。
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# Humanized Autotyper
|
|
2
|
+
|
|
3
|
+
模拟真人打字节奏的自动输入器:对数正态延迟、突发快打、疲劳漂移、内联错字与
|
|
4
|
+
自我纠正、回删爆发、回到前文原位重写、整段重打、长暂停与恢复仪式。
|
|
5
|
+
|
|
6
|
+
## 安装
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
py -m pip install -e . # 可编辑安装(改代码立刻生效,开发用这个)
|
|
10
|
+
py -m pip install . # 普通安装(拷进 site-packages,改源码不再生效)
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
安装后有三条命令:
|
|
14
|
+
|
|
15
|
+
| 命令 | 作用 |
|
|
16
|
+
|---|---|
|
|
17
|
+
| `autotyper` | 命令行输入器(默认 dry-run,加 `--live` 才真打字) |
|
|
18
|
+
| `autotyper-gui` | Tk 图形界面 |
|
|
19
|
+
| `autotyper-report` | 审计 CSV 事件日志(观测频率 vs 配置概率) |
|
|
20
|
+
|
|
21
|
+
不想让脚本进 PATH,也可以用模块方式(三者等价):
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
py -m humanized_autotyper # == autotyper
|
|
25
|
+
py -m humanized_autotyper.gui # == autotyper-gui
|
|
26
|
+
py -m humanized_autotyper.report out.csv
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
- 运行时依赖只有 `pynput`(`pip install` 会自动装),它只在**真实输入**时才被
|
|
30
|
+
import;dry-run、自检、日志审计都不需要它。
|
|
31
|
+
- **用户数据都在「当前工作目录」**:`source.txt`(GUI 内容页读取/保存)、
|
|
32
|
+
`gui_settings.json`(滑块记忆)、`autotyper_log.csv`(默认日志路径)。
|
|
33
|
+
`autotyper` 不带 `--file` 时先找 `./source.txt`,找不到才用内置演示代码。
|
|
34
|
+
- 图形界面需要 Python 自带 tkinter(Windows/macOS 官方安装包默认自带;
|
|
35
|
+
部分 Linux 发行版要额外装 `python3-tk`)。
|
|
36
|
+
|
|
37
|
+
## 图形界面(推荐入门方式)
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
autotyper-gui # 或 py -m humanized_autotyper.gui
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
四个标签页:
|
|
44
|
+
|
|
45
|
+
| 页 | 功能 |
|
|
46
|
+
|---|---|
|
|
47
|
+
| 📝 内容 | 直接编辑要输入的文字(保存到 source.txt),或从文件导入 |
|
|
48
|
+
| ⌨️ 节奏与错误 | 滑块调整:基础敲键间隔 / 节奏抖动 / 突发快打 / 错字率 / 连错窗口 / 疲劳 |
|
|
49
|
+
| ⏸️ 停顿与删改 | 滑块调整:回删爆发 / 短停顿 / 长暂停(可开关) / 跳回重写 / 整段重打 |
|
|
50
|
+
| ▶️ 运行 | 预设人设(标准/高效型程序员/手生普通人)、倒计时 |
|
|
51
|
+
|
|
52
|
+
使用流程:**①** 「内容」页写好文字 → **②** 「运行」页设好倒计时秒数与速度
|
|
53
|
+
→ **③** 点「开始真实输入」→ **④** 趁倒计时把光标点进目标窗口。滑块参数会自动
|
|
54
|
+
记忆(gui_settings.json)。命令行高级用法见下。
|
|
55
|
+
|
|
56
|
+
> GUI 只有真实输入(没有演练模式)。想先无副作用地空跑一遍验证文本模型,用
|
|
57
|
+
> CLI 的默认 dry-run:`autotyper --file 你的文件`(不传 `--live`)。
|
|
58
|
+
|
|
59
|
+
## 预设人设(GUI 与 CLI 共用)
|
|
60
|
+
|
|
61
|
+
三个内置人设定义在 `presets.py`,两端行为完全一致:
|
|
62
|
+
|
|
63
|
+
| 预设 | 特点 |
|
|
64
|
+
|---|---|
|
|
65
|
+
| 标准 | config 默认参数 |
|
|
66
|
+
| 高效型程序员 | 中位间隔~80ms、错字率0.5%、节奏稳 |
|
|
67
|
+
| 手生普通人 | 中位间隔~180ms、错字率2.5%、忽快忽慢易连错 |
|
|
68
|
+
|
|
69
|
+
- CLI:`autotyper --preset 手生普通人 --live --window Notepad`
|
|
70
|
+
(`--preset list` 查看全部;预设只覆盖行为参数,`--speed`/窗口等不受影响)
|
|
71
|
+
- GUI:「运行」页下拉框选择即自动换算到滑块
|
|
72
|
+
|
|
73
|
+
### 新增自定义预设
|
|
74
|
+
|
|
75
|
+
在 `presets.py` 的 `PRESETS` 里加一项即可,两个界面自动出现:
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
PRESETS["夜班摸鱼"] = {
|
|
79
|
+
"delay_mu": 0.25, # Config 真实单位:秒 / 概率小数
|
|
80
|
+
"p_typo": 0.02,
|
|
81
|
+
...
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## 文件结构
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
pyproject.toml # 打包配置:元数据、依赖、console scripts
|
|
89
|
+
README.md
|
|
90
|
+
source.txt # 默认输入内容(也可用 --file 指定其他文件)
|
|
91
|
+
humanized_autotyper/ # 包本体
|
|
92
|
+
├── __init__.py # 版本号(pyproject 从这里动态取)
|
|
93
|
+
├── __main__.py # python -m humanized_autotyper
|
|
94
|
+
├── config.py # 全部可调参数(概率、分布参数)
|
|
95
|
+
├── presets.py # 预设人设(GUI 与 CLI 共用)
|
|
96
|
+
├── keymap.py # Shift 符号表 + 邻键表 + 控制字符预检
|
|
97
|
+
├── buffer.py # 虚拟文本缓冲区(唯一真相源,禁止读屏)
|
|
98
|
+
├── delays.py # 延迟采样器(lognormal + 修正因子 + burst/疲劳/预热)
|
|
99
|
+
├── typos.py # 错误生成器(adjacent/double/transposition/random)
|
|
100
|
+
├── executor.py # pynput 键盘层 + dry-run + F10 终止 + CSV 日志
|
|
101
|
+
├── machine.py # 状态机(TYPING/BACKSPACE_BURST/PAUSE_SHORT/
|
|
102
|
+
│ # PAUSE_LONG/EDIT_PREVIOUS/REWRITE_CHUNK)
|
|
103
|
+
├── main.py # CLI 入口(autotyper):倒计时、窗口校验、调度循环
|
|
104
|
+
├── gui.py # 图形界面(autotyper-gui)
|
|
105
|
+
├── report.py # 事件日志频率审计(autotyper-report)
|
|
106
|
+
├── sanity_check.py # 全链路 dry-run 自检 + 单元验证
|
|
107
|
+
└── validate.py # 多种子批量验证 + kill 开关路径测试
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
包内模块之间用相对导入(`from .config import Config`),因此要整体安装后使用;
|
|
111
|
+
单独 `py humanized_autotyper/main.py` 这样直接跑文件是不行的,用命令或 `-m`。
|
|
112
|
+
|
|
113
|
+
## 快速开始
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
autotyper --preset list # 看预设
|
|
117
|
+
|
|
118
|
+
# dry-run 冒烟(无副作用,导出事件日志)
|
|
119
|
+
autotyper --file mysource.py --seed 1 --log out.csv
|
|
120
|
+
|
|
121
|
+
# 审计实际频率是否命中配置概率
|
|
122
|
+
autotyper-report out.csv
|
|
123
|
+
|
|
124
|
+
# 实机输入(见下方安全清单!)
|
|
125
|
+
autotyper --live --window Notepad --file mysource.py
|
|
126
|
+
|
|
127
|
+
# 三条自检入口
|
|
128
|
+
py -m humanized_autotyper.sanity_check # 应输出 ALL SANITY CHECKS PASSED
|
|
129
|
+
py -m humanized_autotyper.validate # 批量种子验证 + kill 开关测试
|
|
130
|
+
autotyper-gui --selftest # 无界面 GUI 自检
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
`autotyper` 参数:
|
|
134
|
+
|
|
135
|
+
| 参数 | 说明 |
|
|
136
|
+
|---|---|
|
|
137
|
+
| `--file` | 要输入的源文本(UTF-8)。缺省时读当前目录 `source.txt`,都没有才用内置演示代码 |
|
|
138
|
+
| `--live` | 发送真实击键(默认 dry-run) |
|
|
139
|
+
| `--speed X` | 整体速度倍率:>1 加速 / <1 减速(只缩放墙钟时间,不改变人类节奏形状) |
|
|
140
|
+
| `--seed` | 固定随机种子(复现行为) |
|
|
141
|
+
| `--window "标题子串"` | 前台窗口校验,不匹配则拒绝启动(强烈建议加) |
|
|
142
|
+
| `--log FILE` | CSV 事件日志路径 |
|
|
143
|
+
| `--verbose` | 逐事件打印到控制台 |
|
|
144
|
+
|
|
145
|
+
## LIVE 模式安全清单
|
|
146
|
+
|
|
147
|
+
1. **目标窗口就位**:先打开并聚焦目标窗口(如记事本),再运行命令。
|
|
148
|
+
必须传 `--window`——启动时与每个行尾都会校验前台窗口标题,不匹配直接中止。
|
|
149
|
+
注意 **GUI 没有这道校验**,只能靠倒计时切焦点 + F10 兜底。
|
|
150
|
+
2. **倒计时 5 秒**:启动后立即把焦点切到目标窗口。
|
|
151
|
+
3. **F10 = 紧急停止**:pynput 监听线程独立置位 kill_flag,分片 sleep 保证
|
|
152
|
+
最长约一个 sleep 片(短停顿 0.25s / 长停顿 2s)内响应。若停止请求正好落在
|
|
153
|
+
"已删除、还没重打"之间,会先把这一小段补完再停,以免文档缺字。
|
|
154
|
+
4. **只在无副作用的编辑器里测**:不要对着聊天框/终端/IDE 测试,
|
|
155
|
+
Edit/Rewrite 状态会移动光标、删除重写前文。
|
|
156
|
+
5. 首次实测先用小文本、把速度倍率调大(GUI 的「整体速度倍率」/ CLI `--speed 3`)
|
|
157
|
+
跑一遍,确认字符映射(尤其符号)无误后再恢复 1.0 倍速。
|
|
158
|
+
6. 结果校验:结束后 `final_matches_source: True` 表示 buffer 与源文本一致;
|
|
159
|
+
若为 False 说明目标应用吞了某个键(如 IME 截走了 shift 组合),查 CSV 里
|
|
160
|
+
出错位置附近的 `detail` 列。
|
|
161
|
+
|
|
162
|
+
## 输出效果(默认参数下的大致画像)
|
|
163
|
+
|
|
164
|
+
- 中位敲键间隔 ~110ms(约 9 字/秒),句末/换行后有顿挫,空格带词边界微停顿
|
|
165
|
+
- ~5% 的字符处于 0.55× 速度的突发段(每分钟几次,6–20 字符一簇)
|
|
166
|
+
- 打字错误率 1.2%/字符(错后 10 字符内翻倍——手滑连错),
|
|
167
|
+
65% 在下一个字符就被发现退格纠正
|
|
168
|
+
- 每 ~4 分钟左右一次 4–35 分钟的长暂停(Pareto 分布,只在词边界打断),
|
|
169
|
+
醒来后有 Left×N → Right×N 的"找回位置"仪式 + 10 字符预热减速
|
|
170
|
+
- 行尾小概率跳回前 5–40 行原位重写单词/短句,或选中 1–3 行整段重打
|
|
171
|
+
|
|
172
|
+
## 光标移动策略(纯方向键)
|
|
173
|
+
|
|
174
|
+
所有导航/选区**只使用 ↑ ↓ ← → 四个方向键**(加 Shift 做选区),不使用
|
|
175
|
+
Home / End / Ctrl 组合,也不用鼠标和剪贴板:
|
|
176
|
+
|
|
177
|
+
- 跨行移动:先 `Up/Down × 行数差`(列粘滞由 buffer 镜像推算),再
|
|
178
|
+
`Right/Left` 校正列差
|
|
179
|
+
- 同行移动:直接 `Right/Left × delta`
|
|
180
|
+
- 选区:`Shift+Down`(整行)/ `Shift+Right`(行内),纯方向键组合
|
|
181
|
+
|
|
182
|
+
## 调参指南(config.py)
|
|
183
|
+
|
|
184
|
+
### 改"打得有多像人"
|
|
185
|
+
|
|
186
|
+
| 旋钮 | 默认 | 效果 |
|
|
187
|
+
|---|---|---|
|
|
188
|
+
| `delay_mu` | 0.11s | 基础节奏中位数(lognormal μ),调慢打字整体放慢 |
|
|
189
|
+
| `delay_ln_sigma` | 0.42 | 节奏抖动;加大更像新手,减小更机械 |
|
|
190
|
+
| `burst_len_range` / `p_burst_enter` | (5,20) / 0.05 | 突发段长度 / 进入概率 |
|
|
191
|
+
| `fatigue_rate` / `fatigue_cap` | 0.005 / 1.25 | 每活跃分钟变慢 0.5%,上限 1.25× |
|
|
192
|
+
| `warmup_factor` / `warmup_chars` | 1.6 / 10 | ≥5s 停顿后的预热减速曲线 |
|
|
193
|
+
|
|
194
|
+
### 改"犯错多少"
|
|
195
|
+
|
|
196
|
+
| 旋钮 | 默认 | 效果 |
|
|
197
|
+
|---|---|---|
|
|
198
|
+
| `p_typo` | 0.012 | 每字符错误率 |
|
|
199
|
+
| `typo_weights` | adjacent 60%… | 错误类型构成(邻键/双打/换序/随机) |
|
|
200
|
+
| `typo_detect_probs` | (.65,.25,.10) | 错后第 0/1/2 个字符发现的比例 |
|
|
201
|
+
| `typo_autocorr_window` | 10 | 手滑连错的窗口长度 |
|
|
202
|
+
|
|
203
|
+
### 改"中间行为"
|
|
204
|
+
|
|
205
|
+
| 旋钮 | 默认 | 效果 |
|
|
206
|
+
|---|---|---|
|
|
207
|
+
| `p_backspace_burst` | 0.008 | 回删爆发频率(每次连删 2–12 字符) |
|
|
208
|
+
| `p_pause_short` / `p_pause_short_sentence` | .008 / .06 | 短停顿频率(后者用于句末字符之后) |
|
|
209
|
+
| `p_edit_previous` / `edit_min_gap_s` | .004 / 90s | 跳回前文重写频率 / 两次最少间隔 |
|
|
210
|
+
| `p_rewrite_chunk` | .006 | 整段重打频率(行尾评估) |
|
|
211
|
+
| `long_pause_xmin` / `alpha` / `cap` | 240s / 1.4 / 2100s | 长停顿时长分布 |
|
|
212
|
+
|
|
213
|
+
## 工作流(蓝图的调参循环)
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
autotyper --file big_source.py --log out.csv # dry-run 导出
|
|
217
|
+
autotyper-report out.csv # obs vs cfg 对照
|
|
218
|
+
# → 调 config.py → 重跑 → 直到各状态观测频率贴近配置值 → 再实机
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
CSV 格式:`ts,state,event,detail,delay_ms`(csv.writer 正确转义逗号)。
|
|
222
|
+
`autotyper-report` 给出 typo/burst/pause 观测率 vs 配置率、逐字符延迟分布。
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Humanized Autotyper —— 模拟真人打字节奏的自动输入器。
|
|
2
|
+
|
|
3
|
+
对外只暴露版本号;功能通过子模块或命令行入口使用:
|
|
4
|
+
|
|
5
|
+
autotyper # 命令行输入器(默认 dry-run,加 --live 才真打字)
|
|
6
|
+
autotyper-gui # Tk 图形界面
|
|
7
|
+
autotyper-report # 审计 CSV 事件日志
|
|
8
|
+
|
|
9
|
+
也可以 `python -m humanized_autotyper`(等同 `autotyper`)。
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
__version__ = "1.0.0"
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Buffer:
|
|
5
|
+
def __init__(self, text=""):
|
|
6
|
+
self.text = text
|
|
7
|
+
self.cursor = len(text)
|
|
8
|
+
|
|
9
|
+
def type_char(self, c):
|
|
10
|
+
self.text = self.text[: self.cursor] + c + self.text[self.cursor :]
|
|
11
|
+
self.cursor += 1
|
|
12
|
+
|
|
13
|
+
def backspace(self):
|
|
14
|
+
if self.cursor > 0:
|
|
15
|
+
self.text = self.text[: self.cursor - 1] + self.text[self.cursor :]
|
|
16
|
+
self.cursor -= 1
|
|
17
|
+
|
|
18
|
+
def delete_forward(self, n):
|
|
19
|
+
n = min(n, len(self.text) - self.cursor)
|
|
20
|
+
removed = self.text[self.cursor : self.cursor + n]
|
|
21
|
+
self.text = self.text[: self.cursor] + self.text[self.cursor + n :]
|
|
22
|
+
return removed
|
|
23
|
+
|
|
24
|
+
def move_left(self, n=1):
|
|
25
|
+
self.cursor = max(0, self.cursor - n)
|
|
26
|
+
|
|
27
|
+
def move_right(self, n=1):
|
|
28
|
+
self.cursor = min(len(self.text), self.cursor + n)
|
|
29
|
+
|
|
30
|
+
def to_line_start(self):
|
|
31
|
+
self.cursor = self.line_start(self.current_line())
|
|
32
|
+
|
|
33
|
+
def to_doc_end(self):
|
|
34
|
+
self.cursor = len(self.text)
|
|
35
|
+
|
|
36
|
+
def _line_starts(self):
|
|
37
|
+
starts = [0]
|
|
38
|
+
for i, ch in enumerate(self.text):
|
|
39
|
+
if ch == "\n":
|
|
40
|
+
starts.append(i + 1)
|
|
41
|
+
return starts
|
|
42
|
+
|
|
43
|
+
def line_count(self):
|
|
44
|
+
return len(self._line_starts())
|
|
45
|
+
|
|
46
|
+
def current_line(self):
|
|
47
|
+
return self.text.count("\n", 0, self.cursor)
|
|
48
|
+
|
|
49
|
+
def line_start(self, li):
|
|
50
|
+
return self._line_starts()[li]
|
|
51
|
+
|
|
52
|
+
def line_end_excl_newline(self, li):
|
|
53
|
+
starts = self._line_starts()
|
|
54
|
+
start = starts[li]
|
|
55
|
+
if li + 1 < len(starts):
|
|
56
|
+
return starts[li + 1] - 1
|
|
57
|
+
return len(self.text)
|
|
58
|
+
|
|
59
|
+
def line_text(self, li):
|
|
60
|
+
return self.text[self.line_start(li) : self.line_end_excl_newline(li)]
|
|
61
|
+
|
|
62
|
+
def line_col(self):
|
|
63
|
+
li = self.current_line()
|
|
64
|
+
return li, self.cursor - self.line_start(li)
|
|
65
|
+
|
|
66
|
+
def line_col_of(self, pos):
|
|
67
|
+
pos = max(0, min(len(self.text), pos))
|
|
68
|
+
li = self.text.count("\n", 0, pos)
|
|
69
|
+
return li, pos - self.line_start(li)
|
|
70
|
+
|
|
71
|
+
def apply_up(self, n=1):
|
|
72
|
+
li, col = self.line_col()
|
|
73
|
+
tl = max(0, li - n)
|
|
74
|
+
start = self.line_start(tl)
|
|
75
|
+
length = self.line_end_excl_newline(tl) - start
|
|
76
|
+
self.cursor = start + min(col, length)
|
|
77
|
+
|
|
78
|
+
def apply_down(self, n=1):
|
|
79
|
+
li, col = self.line_col()
|
|
80
|
+
starts = self._line_starts()
|
|
81
|
+
tl = min(li + n, len(starts) - 1)
|
|
82
|
+
start = starts[tl]
|
|
83
|
+
length = self.line_end_excl_newline(tl) - start
|
|
84
|
+
self.cursor = start + min(col, length)
|
|
85
|
+
|
|
86
|
+
def complete_line_count(self):
|
|
87
|
+
return self.text.count("\n")
|
|
88
|
+
|
|
89
|
+
def words_on_line(self, li):
|
|
90
|
+
line = self.line_text(li)
|
|
91
|
+
base = self.line_start(li)
|
|
92
|
+
out = []
|
|
93
|
+
for m in re.finditer(r"[A-Za-z_][A-Za-z0-9_]{2,}", line):
|
|
94
|
+
out.append((base + m.start(), base + m.end(), m.group()))
|
|
95
|
+
return out
|
|
96
|
+
|
|
97
|
+
def seg(self, a, b):
|
|
98
|
+
return self.text[a:b]
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import math
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def _ln(x):
|
|
6
|
+
return math.log(x)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class Config:
|
|
11
|
+
dry_run: bool = True
|
|
12
|
+
speed_multiplier: float = 1.0
|
|
13
|
+
countdown_seconds: int = 5
|
|
14
|
+
kill_hotkey: str = "f10"
|
|
15
|
+
expected_window_title: str = ""
|
|
16
|
+
log_file: str = "autotyper_log.csv"
|
|
17
|
+
verbose: bool = False
|
|
18
|
+
|
|
19
|
+
p_typo: float = 0.012
|
|
20
|
+
typo_weights: tuple = (
|
|
21
|
+
("adjacent", 0.60),
|
|
22
|
+
("double", 0.20),
|
|
23
|
+
("transposition", 0.15),
|
|
24
|
+
("random", 0.05),
|
|
25
|
+
)
|
|
26
|
+
typo_detect_probs: tuple = (0.65, 0.25, 0.10)
|
|
27
|
+
typo_autocorr_boost: float = 2.0
|
|
28
|
+
typo_autocorr_window: int = 10
|
|
29
|
+
|
|
30
|
+
p_burst_enter: float = 0.05
|
|
31
|
+
burst_len_range: tuple = (5, 20)
|
|
32
|
+
burst_speed_factor: float = 0.55
|
|
33
|
+
|
|
34
|
+
p_backspace_burst: float = 0.008
|
|
35
|
+
backspace_n_range: tuple = (2, 12)
|
|
36
|
+
backspace_delay_mu: float = 0.090
|
|
37
|
+
backspace_delay_sigma: float = 0.030
|
|
38
|
+
|
|
39
|
+
p_pause_short: float = 0.008
|
|
40
|
+
p_pause_short_sentence: float = 0.06
|
|
41
|
+
sentence_end_chars: str = ".:;)}"
|
|
42
|
+
pause_short_ln_mu: float = _ln(2.5)
|
|
43
|
+
pause_short_ln_sigma: float = 0.9
|
|
44
|
+
pause_short_clip: tuple = (0.8, 10.0)
|
|
45
|
+
line_end_think_prob: float = 0.35
|
|
46
|
+
line_end_think_range: tuple = (0.3, 1.5)
|
|
47
|
+
|
|
48
|
+
p_edit_previous: float = 0.004
|
|
49
|
+
edit_min_gap_s: float = 90.0
|
|
50
|
+
edit_lookback_range: tuple = (5, 40)
|
|
51
|
+
edit_op_weights: tuple = (
|
|
52
|
+
("replace_word", 0.35),
|
|
53
|
+
("retype_clause", 0.40),
|
|
54
|
+
("fake_delete_insert", 0.25),
|
|
55
|
+
)
|
|
56
|
+
edit_return_pause: tuple = (0.8, 3.0)
|
|
57
|
+
|
|
58
|
+
p_rewrite_chunk: float = 0.006
|
|
59
|
+
rewrite_lines_range: tuple = (1, 3)
|
|
60
|
+
|
|
61
|
+
delay_mu: float = 0.11
|
|
62
|
+
delay_ln_sigma: float = 0.42
|
|
63
|
+
delay_clip: tuple = (0.04, 0.40)
|
|
64
|
+
factor_after_newline: float = 2.5
|
|
65
|
+
factor_after_sentence: float = 1.8
|
|
66
|
+
factor_shift: float = 1.4
|
|
67
|
+
factor_space: float = 0.9
|
|
68
|
+
factor_repeat: float = 0.85
|
|
69
|
+
word_gap_mu: float = 0.06
|
|
70
|
+
word_gap_sigma: float = 0.02
|
|
71
|
+
|
|
72
|
+
fatigue_rate: float = 0.005
|
|
73
|
+
fatigue_cap: float = 1.25
|
|
74
|
+
warmup_min_pause: float = 5.0
|
|
75
|
+
warmup_chars: int = 10
|
|
76
|
+
warmup_factor: float = 1.6
|
|
77
|
+
|
|
78
|
+
hold_mu: float = 0.075
|
|
79
|
+
hold_sigma: float = 0.020
|
|
80
|
+
# 收到停止请求后,"补完已删除片段"用的按键保持时间(不再引入拟人延时)
|
|
81
|
+
urgent_hold: float = 0.008
|
|
82
|
+
nav_delay_mu: float = 0.070
|
|
83
|
+
nav_delay_sigma: float = 0.020
|
|
84
|
+
|
|
85
|
+
long_pause_xmin: float = 240.0
|
|
86
|
+
long_pause_alpha: float = 1.4
|
|
87
|
+
long_pause_cap: float = 2100.0
|
|
88
|
+
long_sleep_chunk: float = 2.0
|
|
89
|
+
resume_left_range: tuple = (0, 60)
|
|
90
|
+
resume_reorient_range: tuple = (0.5, 2.0)
|