fix: support non-ASCII characters (Cyrillic, CJK, emoji) in humanized typing

This commit is contained in:
lilos
2026-03-13 00:34:01 +01:00
parent 1bef989404
commit 1af25d67bc
5 changed files with 236 additions and 7 deletions
+22 -5
View File
@@ -22,6 +22,11 @@ const NEARBY_KEYS: Record<string, string> = {
'6': '57ty', '7': '68yu', '8': '79ui', '9': '80io', '0': '9p',
};
function isAscii(ch: string): boolean {
const code = ch.codePointAt(0);
return code !== undefined && code < 128;
}
function getNearbyKey(ch: string): string {
const lower = ch.toLowerCase();
if (lower in NEARBY_KEYS) {
@@ -38,11 +43,23 @@ export async function humanType(
text: string,
cfg: HumanConfig,
): Promise<void> {
for (let i = 0; i < text.length; i++) {
const ch = text[i];
const chars = [...text]; // Handle emoji surrogate pairs correctly
// Mistype chance — press wrong key, notice, backspace, then correct
if (Math.random() < cfg.mistype_chance && /[a-zA-Z0-9]/.test(ch)) {
for (let i = 0; i < chars.length; i++) {
const ch = chars[i];
// Non-ASCII characters (Cyrillic, CJK, emoji) — use insertText
if (!isAscii(ch)) {
await sleep(randRange(cfg.key_hold));
await raw.insertText(ch);
if (i < chars.length - 1) {
await interCharDelay(cfg);
}
continue;
}
// Mistype chance — only for ASCII alphanumeric
if (Math.random() < cfg.mistype_chance && /^[a-zA-Z0-9]$/.test(ch)) {
const wrong = getNearbyKey(ch);
await typeNormalChar(raw, wrong, cfg);
await sleep(randRange(cfg.mistype_delay_notice));
@@ -60,7 +77,7 @@ export async function humanType(
await typeNormalChar(raw, ch, cfg);
}
if (i < text.length - 1) {
if (i < chars.length - 1) {
await interCharDelay(cfg);
}
}
+75
View File
@@ -530,6 +530,81 @@ function buildMockPage(overrides: Record<string, any> = {}): any {
return page;
}
// =========================================================================
// humanType non-ASCII
// =========================================================================
describe("humanType non-ASCII", () => {
function makeRawKeyboardMock() {
const downKeys: string[] = [];
const insertedChars: string[] = [];
const raw = {
down: vi.fn(async (k: string) => { downKeys.push(k); }),
up: vi.fn(async () => {}),
type: vi.fn(async () => {}),
insertText: vi.fn(async (t: string) => { insertedChars.push(t); }),
};
return { raw, downKeys, insertedChars };
}
it("types Cyrillic via insertText, not down", async () => {
const { humanType } = await import("../src/human/keyboard.js");
const cfg = resolveConfig("default", { mistype_chance: 0 });
const { raw, downKeys, insertedChars } = makeRawKeyboardMock();
await humanType({} as any, raw, "Привет", cfg);
expect(insertedChars.join("")).toBe("Привет");
for (const k of downKeys) {
expect(k.charCodeAt(0)).toBeLessThan(128);
}
});
it("types mixed ASCII + Cyrillic correctly", async () => {
const { humanType } = await import("../src/human/keyboard.js");
const cfg = resolveConfig("default", { mistype_chance: 0 });
const { raw, downKeys, insertedChars } = makeRawKeyboardMock();
await humanType({} as any, raw, "Hi Мир", cfg);
expect(downKeys).toContain("H");
expect(downKeys).toContain("i");
expect(insertedChars.join("")).toContain("М");
expect(insertedChars.join("")).toContain("и");
expect(insertedChars.join("")).toContain("р");
});
it("types CJK via insertText", async () => {
const { humanType } = await import("../src/human/keyboard.js");
const cfg = resolveConfig("default", { mistype_chance: 0 });
const { raw, insertedChars } = makeRawKeyboardMock();
await humanType({} as any, raw, "你好", cfg);
expect(insertedChars.join("")).toBe("你好");
});
it("types emoji via insertText", async () => {
const { humanType } = await import("../src/human/keyboard.js");
const cfg = resolveConfig("default", { mistype_chance: 0 });
const { raw, insertedChars } = makeRawKeyboardMock();
await humanType({} as any, raw, "Hi 👋", cfg);
expect(insertedChars.join("")).toContain("👋");
});
it("mistype only triggers for ASCII, not Cyrillic", async () => {
const { humanType } = await import("../src/human/keyboard.js");
const cfg = resolveConfig("default", { mistype_chance: 1.0 });
const { raw, downKeys } = makeRawKeyboardMock();
await humanType({} as any, raw, "AБ", cfg);
expect(downKeys).toContain("Backspace");
});
});
function buildMockFrame(): any {
return {