@energypatrikhu/keysender
Version:
Node.js keyboard and mouse inputs emulator, global hotkey register for Windows
527 lines (406 loc) • 15.1 kB
text/typescript
import { _Worker, _Hardware, _Virtual } from "./addon";
import {
bindPick,
lazyGetters,
makeCancelable,
normalizeWindowInfo,
random,
sleep,
stringsToBuffers,
} from "./utils";
import { Keyboard, KeyboardButton, Mouse, RGB, Workwindow } from "./types";
import { DEFAULT_DELAY, MICRO_DELAY } from "./constants";
import { SetWorkwindow } from "./types/utils";
declare class Worker {
/** Provides methods to synthesize keystrokes */
readonly keyboard: Keyboard;
/** Provides methods to synthesize mouse motions, and button clicks */
readonly mouse: Mouse;
/** Provides methods to work with workwindow */
readonly workwindow: Workwindow;
/** Use entire desktop as workwindow */
constructor();
/** Use the first window with {@link handle} */
constructor(handle: number);
/** Use the first window with {@link title} and/or {@link className} and sets it as current workwindow */
constructor(title: string | null, className?: string | null);
/** Use the first child window with {@link childClassName} and/or {@link childTitle} of window with {@link parentHandle} and sets it as current workwindow */
constructor(
parentHandle: number,
childClassName: string | null,
childTitle?: string | null
);
/** Use the first child window with {@link childClassName} and/or {@link childTitle} of the first found window with {@link parentTitle} and/or {@link parentClassName} and sets it as current workwindow */
constructor(
parentTitle: string | null,
parentClassName: string | null,
childClassName: string | null,
childTitle?: string | null
);
}
const handleSetWorkwindow =
(worker: _Worker): SetWorkwindow =>
(...args: any[]) => {
worker.setWorkwindow(...stringsToBuffers(args));
};
const handleWorker = (WorkerClass: typeof _Worker): typeof Worker =>
class {
declare readonly keyboard: Keyboard;
declare readonly mouse: Mouse;
declare readonly workwindow: Workwindow;
constructor(...args: any[]) {
const worker = new WorkerClass();
handleSetWorkwindow(worker).apply(null, args);
lazyGetters(this, {
keyboard() {
const _toggleKey = (key: KeyboardButton, state: boolean) => {
worker.toggleKey(key, state);
return sleep(MICRO_DELAY);
};
const _toggleKeys = async (
keys: KeyboardButton[],
state: boolean
) => {
const l = keys.length - 1;
let i: number;
if (state) {
for (i = 0; i < l; i++) {
await _toggleKey(keys[i], true);
}
} else {
for (i = l; i > 0; i--) {
await _toggleKey(keys[i], false);
}
}
worker.toggleKey(keys[i], state);
};
const sendKey: Keyboard["sendKey"] = async (
key,
delayAfterPress = DEFAULT_DELAY,
delayAfterRelease = 0
) => {
if (Array.isArray(key)) {
await _toggleKeys(key, true);
await sleep(delayAfterPress);
await _toggleKeys(key, false);
} else {
worker.toggleKey(key, true);
await sleep(delayAfterPress);
worker.toggleKey(key, false);
}
return sleep(delayAfterRelease);
};
return {
printText: makeCancelable<Keyboard["printText"]>(async function (
text,
delayAfterCharTyping = 0,
delay = 0
) {
if (text) {
if (delayAfterCharTyping) {
this.isCancelable = true;
const it = new Intl.Segmenter()
.segment(text)
[Symbol.iterator]();
let curr = it.next();
for (let next = it.next(); !next.done; next = it.next()) {
if (this.isCanceled()) {
return;
}
worker.printChar(Buffer.from(curr.value.segment, "ucs2"));
await sleep(delayAfterCharTyping);
curr = next;
}
if (this.isCanceled()) {
return;
}
delete this.isCancelable;
worker.printChar(Buffer.from(curr.value.segment, "ucs2"));
} else {
worker.printChar(Buffer.from(text, "ucs2"));
}
}
return sleep(delay);
}),
async toggleKey(key, state, delay = 0) {
if (Array.isArray(key)) {
await _toggleKeys(key, state);
} else {
worker.toggleKey(key, state);
}
return sleep(delay);
},
sendKey,
sendKeys: makeCancelable<Keyboard["sendKeys"]>(async function (
keys,
delayAfterPress = DEFAULT_DELAY,
delayAfterRelease = DEFAULT_DELAY,
delay = 0
) {
this.isCancelable = true;
const l = keys.length - 1;
for (let i = 0; i < l; i++) {
if (this.isCanceled()) {
return;
}
await sendKey(keys[i], delayAfterPress, delayAfterRelease);
}
if (this.isCanceled()) {
return;
}
delete this.isCancelable;
return sendKey(keys[l], delayAfterPress, delay);
}),
};
},
mouse() {
const _getSign = () => (Math.random() > 0.5 ? 1 : -1);
const _tremor = (probability: number) =>
Math.random() <= probability ? _getSign() : 0;
const _curveMaker = (
t: number,
start: number,
curveDot1: number,
curveDot2: number,
end: number
) => {
const invertT = 1 - t;
const invertT2 = invertT * invertT;
const t2 = t * t;
return Math.floor(
invertT2 * invertT * start +
3 * invertT2 * t * curveDot1 +
3 * invertT * t2 * curveDot2 +
t2 * t * end
);
};
const _curveDotMaker = (
start: number,
end: number,
deviation: number
) =>
Math.round(start + (end - start) * (0.5 + _getSign() * deviation));
const _firstCurveDotMaker = (
start: number,
end: number,
deviation: number,
sign: 1 | -1
) => Math.round(start + sign * (end - start) * deviation);
const moveTo: Mouse["moveTo"] = (x, y, delay = 0) => {
worker.move(x, y, true);
return sleep(delay);
};
const toggle: Mouse["toggle"] = (button, state, delay = 0) => {
worker.toggleMb(button, state);
return sleep(delay);
};
return {
...bindPick(worker, ["getPos"]),
toggle,
async click(
button = "left",
delayAfterPress = DEFAULT_DELAY,
delayAfterRelease = 0
) {
await toggle(button, true, delayAfterPress);
return toggle(button, false, delayAfterRelease);
},
moveTo,
humanMoveTo: makeCancelable<Mouse["humanMoveTo"]>(async function (
xE,
yE,
speed = 5,
deviation = 30,
delay = 0
) {
deviation /= 100;
const sleepTime = speed >= 1 ? 1 : Math.round(1 / speed);
const { x, y } = worker.lastCoords;
if (x === xE && y === yE) {
return;
}
this.isCancelable = true;
const partLength = random(50, 200) / 2;
const partsTotal = Math.ceil(
Math.pow(Math.pow(xE - x, 2) + Math.pow(yE - y, 2), 0.5) /
partLength
);
const xPartLength = (xE - x) / partsTotal;
const yPartLength = (yE - y) / partsTotal;
const speedMultiplier = (speed > 1 ? speed + 2 : 3) / partLength;
let partsLeft = partsTotal;
let isLastOne = partsTotal === 1;
let parts: number;
let xPartEnd: number;
let yPartEnd: number;
let xPartStart = x;
let yPartStart = y;
if (!isLastOne) {
parts = random(1, Math.ceil(partsTotal / 2));
xPartEnd = x + xPartLength * parts;
yPartEnd = y + yPartLength * parts;
} else {
parts = 1;
xPartEnd = xE;
yPartEnd = yE;
}
let getCurveDots = () => {
getCurveDots = () => ({
curveDotX1: _curveDotMaker(
xPartStart,
xPartEnd,
random(deviation / 3, deviation)
),
curveDotY1: _curveDotMaker(
yPartStart,
yPartEnd,
random(deviation / 3, deviation / 2)
),
curveDotX2: _curveDotMaker(
xPartStart,
xPartEnd,
random(0, deviation)
),
curveDotY2: _curveDotMaker(
yPartStart,
yPartEnd,
random(0, deviation / 2)
),
});
return {
curveDotX1: _firstCurveDotMaker(
xPartStart,
xPartEnd,
random(deviation / 2, deviation),
1
),
curveDotY1: _firstCurveDotMaker(
yPartStart,
yPartEnd,
random(deviation / 4, deviation / 3),
1
),
curveDotX2: _firstCurveDotMaker(
xPartStart,
xPartEnd,
random(deviation / 2, deviation),
_getSign()
),
curveDotY2: _firstCurveDotMaker(
yPartStart,
yPartEnd,
random(deviation / 2, deviation),
_getSign()
),
};
};
const tremorProbability = speed / 15;
while (true) {
const { curveDotX1, curveDotX2, curveDotY1, curveDotY2 } =
getCurveDots();
const dotIterator = speedMultiplier / parts;
const count = 1 / dotIterator;
for (let i = 1; i < count; i++) {
if (this.isCanceled()) {
return;
}
const t = i * dotIterator;
await moveTo(
_curveMaker(
t,
xPartStart,
curveDotX1,
curveDotX2,
xPartEnd
),
_curveMaker(
t,
yPartStart,
curveDotY1,
curveDotY2,
yPartEnd
) + _tremor(tremorProbability),
sleepTime
);
}
if (this.isCanceled()) {
return;
}
if (isLastOne) {
delete this.isCancelable;
await moveTo(xE, yE, delay);
return;
}
await moveTo(
xPartEnd,
yPartEnd + _tremor(tremorProbability),
sleepTime
);
partsLeft -= parts;
xPartStart = xPartEnd;
yPartStart = yPartEnd;
if (partsLeft > 2) {
parts = random(1, partsLeft - 1);
xPartEnd += xPartLength * parts;
yPartEnd += yPartLength * parts;
} else {
parts = partsLeft;
xPartEnd = xE;
yPartEnd = yE;
isLastOne = true;
}
}
}),
move(x, y, delay = 0) {
worker.move(x, y, false);
return sleep(delay);
},
scrollWheel(amount, delay = 0) {
worker.scrollWheel(amount);
return sleep(delay);
},
};
},
workwindow() {
const _add0 = (item: string) => (item.length > 1 ? item : "0" + item);
const _hex = (...rgb: RGB) =>
rgb.reduce((hex, color) => hex + _add0(color.toString(16)), "");
return {
...bindPick(worker, [
"refresh",
"setForeground",
"isForeground",
"isOpen",
"capture",
"kill",
"close",
"getView",
"setView",
]),
set: handleSetWorkwindow(worker),
get: () => normalizeWindowInfo(worker.getWorkwindow()),
colorAt(x, y, format = "string"): any {
const bgr = worker.getColor(x, y);
const r = bgr & 0xff;
const g = (bgr >> 8) & 0xff;
const b = (bgr >> 16) & 0xff;
switch (format) {
case "array":
return [r, g, b];
case "number":
return (r << 16) | (g << 8) | b;
case "string":
return _hex(r, g, b);
default:
throw new Error("wrong format");
}
},
};
},
});
}
};
/** Provides methods implementations on hardware level. */
export const Hardware = handleWorker(_Hardware);
/** Provides methods implementations on virtual level. */
export const Virtual = handleWorker(_Virtual);