@aspiresys/visor 1.0.0

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.
@@ -0,0 +1 @@
1
+ export declare function log(...args: any[]): void;
package/dist/logger.js ADDED
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.log = log;
4
+ const config_1 = require("./config");
5
+ function log(...args) {
6
+ if (config_1.visorConfig.debug) {
7
+ console.log(...args);
8
+ }
9
+ }
@@ -0,0 +1,4 @@
1
+ import { Region } from "./types";
2
+ export declare function loadTemplate(path: string): any;
3
+ export declare function matchTemplate(screen: any, template: any, confidence?: number): Region | null;
4
+ export declare function findAllMatches(screen: any, template: any, confidence?: number): Region[];
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.loadTemplate = loadTemplate;
7
+ exports.matchTemplate = matchTemplate;
8
+ exports.findAllMatches = findAllMatches;
9
+ const fs_1 = __importDefault(require("fs"));
10
+ const pngjs_1 = require("pngjs");
11
+ const cv = require("@techstark/opencv-js");
12
+ function loadTemplate(path) {
13
+ const buffer = fs_1.default.readFileSync(path);
14
+ const png = pngjs_1.PNG.sync.read(buffer);
15
+ return cv.matFromImageData({
16
+ data: png.data,
17
+ width: png.width,
18
+ height: png.height
19
+ });
20
+ }
21
+ function matchTemplate(screen, template, confidence = 0.8) {
22
+ const result = new cv.Mat();
23
+ cv.matchTemplate(screen, template, result, cv.TM_CCOEFF_NORMED);
24
+ const { maxLoc, maxVal } = cv.minMaxLoc(result);
25
+ result.delete();
26
+ if (maxVal < confidence) {
27
+ return null;
28
+ }
29
+ return {
30
+ x: maxLoc.x,
31
+ y: maxLoc.y,
32
+ width: template.cols,
33
+ height: template.rows,
34
+ confidence: maxVal
35
+ };
36
+ }
37
+ function findAllMatches(screen, template, confidence = 0.8) {
38
+ const result = new cv.Mat();
39
+ cv.matchTemplate(screen, template, result, cv.TM_CCOEFF_NORMED);
40
+ const matches = [];
41
+ for (let y = 0; y < result.rows; y++) {
42
+ for (let x = 0; x < result.cols; x++) {
43
+ const score = result.floatAt(y, x);
44
+ if (score >= confidence) {
45
+ matches.push({
46
+ x,
47
+ y,
48
+ width: template.cols,
49
+ height: template.rows,
50
+ confidence: score
51
+ });
52
+ }
53
+ }
54
+ }
55
+ matches.sort((a, b) => b.confidence - a.confidence);
56
+ const filtered = [];
57
+ for (const match of matches) {
58
+ const tooClose = filtered.some(existing => {
59
+ const dx = Math.abs(existing.x - match.x);
60
+ const dy = Math.abs(existing.y - match.y);
61
+ return dx < 10 && dy < 10;
62
+ });
63
+ if (!tooClose) {
64
+ filtered.push(match);
65
+ }
66
+ }
67
+ result.delete();
68
+ return filtered;
69
+ }
@@ -0,0 +1,3 @@
1
+ import { Region } from "./types";
2
+ export declare function moveToRegion(region: Region): Promise<void>;
3
+ export declare function clickRegion(region: Region): Promise<void>;
package/dist/mouse.js ADDED
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.moveToRegion = moveToRegion;
4
+ exports.clickRegion = clickRegion;
5
+ const nut_js_1 = require("@nut-tree-fork/nut-js");
6
+ const config_1 = require("./config");
7
+ const logger_1 = require("./logger");
8
+ async function moveToRegion(region) {
9
+ const scaleFactor = config_1.visorConfig.scaleFactor;
10
+ const centerX = Math.floor((region.x + region.width / 2)
11
+ / scaleFactor);
12
+ const centerY = Math.floor((region.y + region.height / 2)
13
+ / scaleFactor);
14
+ (0, logger_1.log)("[MOUSE] Moving mouse to:", centerX, centerY);
15
+ await nut_js_1.mouse.move([
16
+ new nut_js_1.Point(centerX, centerY)
17
+ ]);
18
+ }
19
+ async function clickRegion(region) {
20
+ await moveToRegion(region);
21
+ await new Promise(r => setTimeout(r, 200));
22
+ await nut_js_1.mouse.click(nut_js_1.Button.LEFT);
23
+ (0, logger_1.log)("[MOUSE] Left click completed");
24
+ }
package/dist/ocr.d.ts ADDED
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Terminates OCR worker manually.
3
+ *
4
+ * Useful for cleanup after
5
+ * large automation sessions.
6
+ */
7
+ export declare function terminateOCR(): Promise<void>;
8
+ /**
9
+ * Extracts OCR text from either:
10
+ * - entire screen
11
+ * - specific screen region
12
+ *
13
+ * Uses:
14
+ * - screenshot-desktop
15
+ * - sharp preprocessing
16
+ * - Tesseract OCR
17
+ */
18
+ export declare function extractTextFromRegion(region?: {
19
+ x: number;
20
+ y: number;
21
+ width: number;
22
+ height: number;
23
+ }): Promise<any>;
package/dist/ocr.js ADDED
@@ -0,0 +1,83 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.terminateOCR = terminateOCR;
7
+ exports.extractTextFromRegion = extractTextFromRegion;
8
+ const screenshot_desktop_1 = __importDefault(require("screenshot-desktop"));
9
+ const sharp_1 = __importDefault(require("sharp"));
10
+ const tesseract_js_1 = require("tesseract.js");
11
+ const config_1 = require("./config");
12
+ const logger_1 = require("./logger");
13
+ let worker = null;
14
+ /**
15
+ * Returns shared OCR worker.
16
+ *
17
+ * Worker is initialized only once
18
+ * and reused across all OCR calls.
19
+ */
20
+ async function getWorker() {
21
+ if (!worker) {
22
+ (0, logger_1.log)("[OCR] Initializing worker...");
23
+ worker = await (0, tesseract_js_1.createWorker)("eng");
24
+ (0, logger_1.log)("[OCR] Worker initialized");
25
+ }
26
+ return worker;
27
+ }
28
+ /**
29
+ * Terminates OCR worker manually.
30
+ *
31
+ * Useful for cleanup after
32
+ * large automation sessions.
33
+ */
34
+ async function terminateOCR() {
35
+ if (worker) {
36
+ (0, logger_1.log)("[OCR] Terminating worker");
37
+ await worker.terminate();
38
+ worker = null;
39
+ }
40
+ }
41
+ /**
42
+ * Extracts OCR text from either:
43
+ * - entire screen
44
+ * - specific screen region
45
+ *
46
+ * Uses:
47
+ * - screenshot-desktop
48
+ * - sharp preprocessing
49
+ * - Tesseract OCR
50
+ */
51
+ async function extractTextFromRegion(region) {
52
+ const buffer = await (0, screenshot_desktop_1.default)({
53
+ format: "png"
54
+ });
55
+ let image = (0, sharp_1.default)(buffer);
56
+ if (region) {
57
+ image = image.extract({
58
+ left: Math.floor(region.x *
59
+ config_1.visorConfig.scaleFactor),
60
+ top: Math.floor(region.y *
61
+ config_1.visorConfig.scaleFactor),
62
+ width: Math.floor(region.width *
63
+ config_1.visorConfig.scaleFactor),
64
+ height: Math.floor(region.height *
65
+ config_1.visorConfig.scaleFactor)
66
+ });
67
+ }
68
+ const processed = await image
69
+ .grayscale()
70
+ .normalize()
71
+ .sharpen()
72
+ .png()
73
+ .toBuffer();
74
+ const worker = await getWorker();
75
+ const result = await worker.recognize(processed, {}, {
76
+ blocks: true,
77
+ hocr: true,
78
+ tsv: true
79
+ });
80
+ (0, logger_1.log)("[OCR] Extracted Text:");
81
+ (0, logger_1.log)(result.data.text);
82
+ return result.data;
83
+ }
package/dist/path.d.ts ADDED
@@ -0,0 +1 @@
1
+ export declare function resolveImagePath(image: string): string;
package/dist/path.js ADDED
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.resolveImagePath = resolveImagePath;
7
+ const path_1 = __importDefault(require("path"));
8
+ const config_1 = require("./config");
9
+ function resolveImagePath(image) {
10
+ if (image.includes("/") ||
11
+ image.includes("\\")) {
12
+ return image;
13
+ }
14
+ return path_1.default.join(config_1.visorConfig.imagePath, image);
15
+ }
@@ -0,0 +1,2 @@
1
+ export declare function captureScreen(): Promise<any>;
2
+ export declare function saveScreenshot(path: string): Promise<void>;
package/dist/screen.js ADDED
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.captureScreen = captureScreen;
7
+ exports.saveScreenshot = saveScreenshot;
8
+ const screenshot_desktop_1 = __importDefault(require("screenshot-desktop"));
9
+ const pngjs_1 = require("pngjs");
10
+ const fs_1 = __importDefault(require("fs"));
11
+ const cv = require("@techstark/opencv-js");
12
+ async function captureScreen() {
13
+ const buffer = await (0, screenshot_desktop_1.default)({
14
+ format: "png"
15
+ });
16
+ const png = pngjs_1.PNG.sync.read(buffer);
17
+ return cv.matFromImageData({
18
+ data: png.data,
19
+ width: png.width,
20
+ height: png.height
21
+ });
22
+ }
23
+ async function saveScreenshot(path) {
24
+ const img = await (0, screenshot_desktop_1.default)({
25
+ format: "png"
26
+ });
27
+ fs_1.default.writeFileSync(path, img);
28
+ }
@@ -0,0 +1,6 @@
1
+ export declare class Visor {
2
+ click(image: string, confidence?: number): Promise<void>;
3
+ exists(image: string, confidence?: number): Promise<boolean>;
4
+ wait(image: string, timeout?: number): Promise<boolean>;
5
+ }
6
+ export declare const visor: Visor;
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.visor = exports.Visor = void 0;
4
+ const screen_1 = require("./screen");
5
+ const matcher_1 = require("./matcher");
6
+ const mouse_1 = require("./mouse");
7
+ class Visor {
8
+ async click(image, confidence = 0.8) {
9
+ console.log("Capturing screen...");
10
+ const screen = await (0, screen_1.captureScreen)();
11
+ console.log("Loading template...");
12
+ const template = (0, matcher_1.loadTemplate)(`src/images/${image}`);
13
+ console.log("Matching image...");
14
+ const region = (0, matcher_1.matchTemplate)(screen, template, confidence);
15
+ if (!region) {
16
+ console.log("Image not found");
17
+ throw new Error("Image not found");
18
+ }
19
+ console.log("Match found:", region);
20
+ await (0, mouse_1.clickRegion)(region);
21
+ console.log("Click completed");
22
+ }
23
+ async exists(image, confidence = 0.8) {
24
+ const screen = await (0, screen_1.captureScreen)();
25
+ const template = (0, matcher_1.loadTemplate)(`src/images/${image}`);
26
+ return (0, matcher_1.matchTemplate)(screen, template, confidence) !== null;
27
+ }
28
+ async wait(image, timeout = 5000) {
29
+ const start = Date.now();
30
+ while (Date.now() - start < timeout) {
31
+ if (await this.exists(image))
32
+ return true;
33
+ await new Promise(r => setTimeout(r, 300));
34
+ }
35
+ throw new Error("Timeout waiting for image");
36
+ }
37
+ }
38
+ exports.Visor = Visor;
39
+ exports.visor = new Visor();
@@ -0,0 +1,3 @@
1
+ import { Region } from "./types";
2
+ export declare function loadTemplate(path: string): any;
3
+ export declare function matchTemplate(screen: any, template: any, confidence?: number): Region | null;
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.loadTemplate = loadTemplate;
7
+ exports.matchTemplate = matchTemplate;
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const pngjs_1 = require("pngjs");
10
+ const cv = require("opencv.js");
11
+ function loadTemplate(path) {
12
+ const buffer = fs_1.default.readFileSync(path);
13
+ const png = pngjs_1.PNG.sync.read(buffer);
14
+ return cv.matFromImageData({
15
+ data: png.data,
16
+ width: png.width,
17
+ height: png.height
18
+ });
19
+ }
20
+ function matchTemplate(screen, template, confidence = 0.8) {
21
+ const result = new cv.Mat();
22
+ cv.matchTemplate(screen, template, result, cv.TM_CCOEFF_NORMED);
23
+ const { maxLoc, maxVal } = cv.minMaxLoc(result);
24
+ if (maxVal < confidence)
25
+ return null;
26
+ return {
27
+ x: maxLoc.x,
28
+ y: maxLoc.y,
29
+ width: template.cols,
30
+ height: template.rows,
31
+ confidence: maxVal
32
+ };
33
+ }
@@ -0,0 +1,2 @@
1
+ import { Region } from "./types";
2
+ export declare function clickRegion(region: Region): Promise<void>;
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.clickRegion = clickRegion;
4
+ const nut_js_1 = require("@nut-tree-fork/nut-js");
5
+ async function clickRegion(region) {
6
+ const scaleFactor = 1.5;
7
+ const centerX = Math.floor((region.x + region.width / 2) / scaleFactor);
8
+ const centerY = Math.floor((region.y + region.height / 2) / scaleFactor);
9
+ console.log("Moving mouse to:", centerX, centerY);
10
+ await nut_js_1.mouse.move([
11
+ new nut_js_1.Point(centerX, centerY)
12
+ ]);
13
+ console.log("Mouse moved");
14
+ await new Promise(r => setTimeout(r, 2000));
15
+ console.log("Clicking now");
16
+ await nut_js_1.mouse.click(nut_js_1.Button.LEFT);
17
+ console.log("Click done");
18
+ }
@@ -0,0 +1 @@
1
+ export declare function captureScreen(): Promise<any>;
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.captureScreen = captureScreen;
7
+ const screenshot_desktop_1 = __importDefault(require("screenshot-desktop"));
8
+ const pngjs_1 = require("pngjs");
9
+ const cv = require("opencv.js");
10
+ async function captureScreen() {
11
+ const buffer = await (0, screenshot_desktop_1.default)({
12
+ format: "png"
13
+ });
14
+ const png = pngjs_1.PNG.sync.read(buffer);
15
+ return cv.matFromImageData({
16
+ data: png.data,
17
+ width: png.width,
18
+ height: png.height
19
+ });
20
+ }
@@ -0,0 +1,7 @@
1
+ export interface Region {
2
+ x: number;
3
+ y: number;
4
+ width: number;
5
+ height: number;
6
+ confidence: number;
7
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/dist/text.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ import { Region } from "./types";
2
+ export declare function findText(text: string, region?: {
3
+ x: number;
4
+ y: number;
5
+ width: number;
6
+ height: number;
7
+ }): Promise<Region | null>;
8
+ export declare function existsText(text: string, region?: {
9
+ x: number;
10
+ y: number;
11
+ width: number;
12
+ height: number;
13
+ }): Promise<boolean>;
package/dist/text.js ADDED
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.findText = findText;
4
+ exports.existsText = existsText;
5
+ const ocr_1 = require("./ocr");
6
+ async function findText(text, region) {
7
+ const result = await (0, ocr_1.extractTextFromRegion)(region);
8
+ const tsv = result.tsv;
9
+ if (!tsv) {
10
+ return null;
11
+ }
12
+ const lines = tsv.split("\n");
13
+ const words = [];
14
+ for (const line of lines) {
15
+ const cols = line.split("\t");
16
+ if (cols[0] !== "5")
17
+ continue;
18
+ const wordText = cols[11];
19
+ if (!wordText)
20
+ continue;
21
+ const confidence = Number(cols[10]);
22
+ if (confidence < 40)
23
+ continue;
24
+ words.push({
25
+ text: wordText,
26
+ x: Number(cols[6]),
27
+ y: Number(cols[7]),
28
+ width: Number(cols[8]),
29
+ height: Number(cols[9]),
30
+ confidence
31
+ });
32
+ }
33
+ //console.log(words);
34
+ const match = words.find(w => w.text
35
+ .toLowerCase()
36
+ .includes(text.toLowerCase()));
37
+ if (!match) {
38
+ return null;
39
+ }
40
+ return {
41
+ x: match.x,
42
+ y: match.y,
43
+ width: match.width,
44
+ height: match.height,
45
+ confidence: match.confidence
46
+ };
47
+ }
48
+ async function existsText(text, region) {
49
+ const match = await findText(text, region);
50
+ return match !== null;
51
+ }
@@ -0,0 +1,7 @@
1
+ export interface Region {
2
+ x: number;
3
+ y: number;
4
+ width: number;
5
+ height: number;
6
+ confidence: number;
7
+ }
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@aspiresys/visor",
3
+ "version": "1.0.0",
4
+ "main": "dist/index.js",
5
+ "types": "dist/index.d.ts",
6
+ "scripts": {
7
+ "build": "tsc",
8
+ "start": "ts-node src/test.ts"
9
+ },
10
+ "dependencies": {
11
+ "@nut-tree-fork/nut-js": "^4.1.0",
12
+ "@techstark/opencv-js": "^4.12.0-release.1",
13
+ "pngjs": "^7.0.0",
14
+ "screenshot-desktop": "^1.15.1",
15
+ "sharp": "^0.34.5",
16
+ "tesseract.js": "^7.0.0"
17
+ },
18
+ "devDependencies": {
19
+ "@types/node": "^24.0.0",
20
+ "@types/pngjs": "^6.0.5",
21
+ "ts-node": "^10.9.2",
22
+ "typescript": "^5.9.0"
23
+ },
24
+ "description": "Desktop visual automation framework using OpenCV, OCR, and desktop interaction APIs.",
25
+
26
+ "keywords": [
27
+ "automation",
28
+ "desktop-automation",
29
+ "opencv",
30
+ "ocr",
31
+ "playwright",
32
+ "testing",
33
+ "computer-vision"
34
+ ],
35
+
36
+ "license": "MIT",
37
+
38
+ "files": [
39
+ "dist"
40
+ ]
41
+ }