@aspiresys/visor 1.3.5 → 1.4.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.
package/dist/types.js CHANGED
@@ -1,4 +1,7 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.Region = void 0;
4
7
  const nut_js_1 = require("@nut-tree-fork/nut-js");
@@ -11,6 +14,9 @@ const path_1 = require("./path");
11
14
  const config_1 = require("./config");
12
15
  const console_1 = require("console");
13
16
  const display_1 = require("./display");
17
+ const templateMetadata_1 = require("./templateMetadata");
18
+ const opencv_js_1 = __importDefault(require("@techstark/opencv-js"));
19
+ const fs_1 = __importDefault(require("fs"));
14
20
  /**
15
21
  * Represents a rectangular screen region.
16
22
  *
@@ -48,90 +54,134 @@ class Region {
48
54
  }
49
55
  const missing = [];
50
56
  if (!config_1.visorConfig.imagePath) {
51
- missing.push("imagePath");
57
+ missing.push('imagePath');
52
58
  }
53
59
  if (!config_1.visorConfig.outputPath) {
54
- missing.push("outputPath");
60
+ missing.push('outputPath');
55
61
  }
56
62
  if (missing.length > 0) {
57
63
  (0, console_1.warn)(`[VISOR] Configuration incomplete.\n` +
58
- `Missing: ${missing.join(", ")}\n\n` +
64
+ `Missing: ${missing.join(', ')}\n\n` +
59
65
  `Relative paths may not work correctly.\n` +
60
66
  `Use visor.loadConfig(...) or provide absolute paths directly to functions.`);
61
67
  }
62
68
  if (!config_1.visorConfig.initialized) {
63
69
  config_1.visorConfig.scaleFactor = (0, display_1.getWindowsScaleFactor)();
64
- (0, console_1.warn)(`[VISOR] Configuration not loaded.\n` +
65
- `Auto-detected scaleFactor=${config_1.visorConfig.scaleFactor}.\n`);
70
+ (0, console_1.warn)(`[VISOR] Configuration not loaded.\n` + `Auto-detected scaleFactor=${config_1.visorConfig.scaleFactor}.\n`);
66
71
  }
67
72
  if (missing.length > 0 || !config_1.visorConfig.initialized) {
68
73
  Region.configWarningShown = true;
69
74
  }
70
75
  }
71
76
  /**
72
- * Returns the center point of the region.
73
- *
74
- * Useful for:
75
- * - mouse movement
76
- * - coordinate calculations
77
- * - custom interactions
78
- */
77
+ * Captures a screenshot of the
78
+ * region and saves it
79
+ * to the specified path.
80
+ *
81
+ * Supported formats depend on
82
+ * the output file extension.
83
+ *
84
+ * @param path Output image path.
85
+ *
86
+ * @example
87
+ * await region.capture(
88
+ * "./screenshots/output.png"
89
+ * );
90
+ */
91
+ async capture(path) {
92
+ this.checkConfig();
93
+ const screenshot = await (0, screen_1.captureScreen)();
94
+ path = config_1.visorConfig.outputPath + '/' + path;
95
+ try {
96
+ const cropped = (0, matcher_1.cropMat)(screenshot, this);
97
+ const png = new opencv_js_1.default.Mat();
98
+ opencv_js_1.default.cvtColor(cropped, png, opencv_js_1.default.COLOR_RGBA2BGRA);
99
+ const imageData = {
100
+ width: png.cols,
101
+ height: png.rows,
102
+ data: Buffer.from(png.data),
103
+ };
104
+ const { PNG } = require('pngjs');
105
+ const output = new PNG({
106
+ width: imageData.width,
107
+ height: imageData.height,
108
+ });
109
+ output.data = imageData.data;
110
+ await new Promise((resolve) => {
111
+ output.pack().pipe(fs_1.default.createWriteStream(path)).on('finish', resolve);
112
+ });
113
+ png.delete();
114
+ cropped.delete();
115
+ }
116
+ finally {
117
+ screenshot.delete();
118
+ }
119
+ }
120
+ /**
121
+ * Returns the center point of the region.
122
+ *
123
+ * Useful for:
124
+ * - mouse movement
125
+ * - coordinate calculations
126
+ * - custom interactions
127
+ */
79
128
  center() {
80
129
  return {
81
130
  x: this.x + Math.floor(this.width / 2),
82
- y: this.y + Math.floor(this.height / 2)
131
+ y: this.y + Math.floor(this.height / 2),
83
132
  };
84
133
  }
85
134
  /**
86
- * Moves the mouse to the center of the region.
87
- *
88
- * Mouse coordinates are automatically
89
- * adjusted for display scaling.
90
- */
135
+ * Moves the mouse to the center of the region.
136
+ *
137
+ * Mouse coordinates are automatically
138
+ * adjusted for display scaling.
139
+ */
91
140
  async move() {
92
141
  await (0, mouse_1.moveToRegion)(this);
93
142
  }
94
143
  /**
95
- * Performs a left click on the region.
96
- *
97
- * Clicks the center of the region by default.
98
- *
99
- * Optional offset can be supplied to click
100
- * a specific position relative to the center.
101
- */
144
+ * Performs a left click on the region.
145
+ *
146
+ * Clicks the center of the region by default.
147
+ *
148
+ * Optional offset can be supplied to click
149
+ * a specific position relative to the center.
150
+ */
102
151
  async click(offset) {
103
152
  await (0, mouse_1.clickRegion)(this, offset);
153
+ console.log(`[CLICK] ${this.constructor.name} at center (${this.center().x}, ${this.center().y}) with confidence ${this.confidence.toFixed(2)})`);
104
154
  }
105
155
  /**
106
- * Finds an image in the Region using OpenCV
107
- * and performs a left mouse click
108
- * on the matched region.
109
- *
110
- * @param image Optional image filename or path.
111
- *
112
- * @param confidence Match confidence threshold.
113
- *
114
- * Accepted range:
115
- * 0.0 to 1.0
116
- *
117
- * Default:
118
- * 0.8
119
- *
120
- * Recommended values:
121
- * - 0.7 for dynamic UI
122
- * - 0.8 for standard usage
123
- * - 0.9+ for strict matching
124
- *
125
- * Lower confidence increases
126
- * flexibility but may increase
127
- * false positives.
128
- *
129
- * @example
130
- * await region.clickImg("save.png");
131
- *
132
- * @example
133
- * await region.clickImg("./images/save.png", 0.9);
134
- */
156
+ * Finds an image in the Region using OpenCV
157
+ * and performs a left mouse click
158
+ * on the matched region.
159
+ *
160
+ * @param image Optional image filename or path.
161
+ *
162
+ * @param confidence Match confidence threshold.
163
+ *
164
+ * Accepted range:
165
+ * 0.0 to 1.0
166
+ *
167
+ * Default:
168
+ * 0.8
169
+ *
170
+ * Recommended values:
171
+ * - 0.7 for dynamic UI
172
+ * - 0.8 for standard usage
173
+ * - 0.9+ for strict matching
174
+ *
175
+ * Lower confidence increases
176
+ * flexibility but may increase
177
+ * false positives.
178
+ *
179
+ * @example
180
+ * await region.clickImg("save.png");
181
+ *
182
+ * @example
183
+ * await region.clickImg("./images/save.png", 0.9);
184
+ */
135
185
  async clickImg(image, confidence = 0.8, offset) {
136
186
  this.checkConfig();
137
187
  const region = await this.find(image, confidence);
@@ -211,130 +261,230 @@ class Region {
211
261
  await region.doubleClick(offset);
212
262
  }
213
263
  /**
214
- * Waits until an image appears
215
- * in the Region.
216
- *
217
- * @param image Image filename or path.
218
- *
219
- * @param confidence Match confidence threshold.
220
- *
221
- * Accepted range:
222
- * 0.0 to 1.0
223
- *
224
- * Default:
225
- * 0.8
226
- *
227
- * Recommended values:
228
- * - 0.7 for dynamic UI
229
- * - 0.8 for standard usage
230
- * - 0.9+ for strict matching
231
- *
232
- * @param timeout Timeout duration
233
- * in milliseconds.
234
- *
235
- * Default:
236
- * 5000ms
237
- *
238
- * Recommended range:
239
- * 1000ms to 30000ms
240
- *
241
- * @throws Error if timeout occurs.
242
- *
243
- * @example
244
- * await region.waitImg("loading-done.png");
245
- */
264
+ * Waits until an image appears
265
+ * in the Region.
266
+ *
267
+ * @param image Image filename or path.
268
+ *
269
+ * @param confidence Match confidence threshold.
270
+ *
271
+ * Accepted range:
272
+ * 0.0 to 1.0
273
+ *
274
+ * Default:
275
+ * 0.8
276
+ *
277
+ * Recommended values:
278
+ * - 0.7 for dynamic UI
279
+ * - 0.8 for standard usage
280
+ * - 0.9+ for strict matching
281
+ *
282
+ * @param timeout Timeout duration
283
+ * in milliseconds.
284
+ *
285
+ * Default:
286
+ * 5000ms
287
+ *
288
+ * Recommended range:
289
+ * 1000ms to 30000ms
290
+ *
291
+ * @throws Error if timeout occurs.
292
+ *
293
+ * @example
294
+ * await region.waitImg("loading-done.png");
295
+ */
246
296
  async waitImg(image, { confidence = 0.8, timeout = 5000 } = {}) {
247
297
  this.checkConfig();
248
298
  const interval = 300;
249
299
  const attempts = Math.ceil(timeout / interval);
250
300
  for (let i = 0; i < attempts; i++) {
301
+ (0, console_1.log)(`[WAIT] ${image} (${i + 1}/${attempts})`);
251
302
  if (await this.exists(image, confidence)) {
303
+ (0, console_1.log)(`[WAIT] Found: ${image}`);
252
304
  return true;
253
305
  }
254
306
  await this.sleep(interval);
255
307
  }
308
+ (0, console_1.log)(`[WAIT] Timeout: ${image}`);
256
309
  throw new Error(`Timeout waiting for image: ${image}`);
257
310
  }
258
311
  /**
259
- * Performs a double left click on the region.
260
- *
261
- * Optional offset can be supplied to click
262
- * a specific position relative to the center.
263
- */
312
+ * Waits until any image from
313
+ * a list appears in the Region.
314
+ *
315
+ * Useful for handling:
316
+ * - multiple themes
317
+ * - alternate UI layouts
318
+ * - dynamic application states
319
+ *
320
+ * @param images Array of image
321
+ * filenames or paths.
322
+ *
323
+ * @param confidence Match confidence threshold.
324
+ *
325
+ * Accepted range:
326
+ * 0.0 to 1.0
327
+ *
328
+ * Default:
329
+ * 0.8
330
+ *
331
+ * Recommended values:
332
+ * - 0.7 for dynamic UI
333
+ * - 0.8 for standard usage
334
+ * - 0.9+ for strict matching
335
+ *
336
+ * Lower confidence increases
337
+ * flexibility but may increase
338
+ * false positives.
339
+ *
340
+ * @param timeout Timeout duration
341
+ * in milliseconds.
342
+ *
343
+ * Default:
344
+ * 5000ms
345
+ *
346
+ * Recommended range:
347
+ * 1000ms to 30000ms
348
+ *
349
+ * @throws Error if timeout occurs.
350
+ *
351
+ * @example
352
+ * await visor.waitAnyImg([
353
+ * "home-light.png",
354
+ * "home-dark.png"
355
+ * ]);
356
+ */
357
+ async waitAnyImg(images, { confidence = 0.8, timeout = 5000 } = {}) {
358
+ this.checkConfig();
359
+ const interval = 300;
360
+ const attempts = Math.ceil(timeout / interval);
361
+ for (let i = 0; i < attempts; i++) {
362
+ for (const image of images) {
363
+ const region = await this.find(image, confidence);
364
+ if (region) {
365
+ return region;
366
+ }
367
+ }
368
+ await this.sleep(interval);
369
+ }
370
+ throw new Error(`Timeout waiting for images: ${images.join(', ')}`);
371
+ }
372
+ /**
373
+ * Finds the first matching image
374
+ * within the current Region and
375
+ * performs a click.
376
+ *
377
+ * Useful for:
378
+ * - theme variations
379
+ * - alternate UI states
380
+ * - dynamic controls
381
+ *
382
+ * @param images Array of image paths.
383
+ * @param confidence Match confidence.
384
+ * @param offset Optional click offset.
385
+ *
386
+ * @returns Matched Region.
387
+ *
388
+ * @example
389
+ * await region.clickAny([
390
+ * "save-light.png",
391
+ * "save-dark.png"
392
+ * ]);
393
+ */
394
+ async clickAny(images, confidence = 0.8, offset) {
395
+ this.checkConfig();
396
+ for (const image of images) {
397
+ const region = await this.find(image, confidence);
398
+ if (region) {
399
+ await region.click(offset);
400
+ return region;
401
+ }
402
+ }
403
+ throw new Error(`None of the images were found: ${images.join(', ')}`);
404
+ }
405
+ /**
406
+ * Performs a double left click on the region.
407
+ *
408
+ * Optional offset can be supplied to click
409
+ * a specific position relative to the center.
410
+ */
264
411
  async doubleClick(offset) {
265
412
  await (0, mouse_1.moveToRegion)(this, offset);
266
413
  await nut_js_1.mouse.doubleClick(nut_js_1.Button.LEFT);
267
414
  }
268
415
  /**
269
- * Performs a right click on the region.
270
- *
271
- * Optional offset can be supplied to click
272
- * a specific position relative to the center.
273
- */
416
+ * Performs a right click on the region.
417
+ *
418
+ * Optional offset can be supplied to click
419
+ * a specific position relative to the center.
420
+ */
274
421
  async rightClick(offset) {
275
422
  await (0, mouse_1.moveToRegion)(this, offset);
276
423
  await nut_js_1.mouse.click(nut_js_1.Button.RIGHT);
277
424
  }
278
425
  /**
279
- * Checks whether the supplied coordinates
280
- * are located inside this region.
281
- *
282
- * Returns:
283
- * - true if inside
284
- * - false otherwise
285
- */
426
+ * Checks whether the supplied coordinates
427
+ * are located inside this region.
428
+ *
429
+ * Returns:
430
+ * - true if inside
431
+ * - false otherwise
432
+ */
286
433
  contains(x, y) {
287
- return (x >= this.x) && (x <= this.x + this.width) && (y >= this.y) && (y <= this.y + this.height);
434
+ return x >= this.x && x <= this.x + this.width && y >= this.y && y <= this.y + this.height;
288
435
  }
289
436
  /**
290
- * Creates a new Region shifted by the
291
- * supplied x and y offsets.
292
- *
293
- * Original region remains unchanged.
294
- */
437
+ * Creates a new Region shifted by the
438
+ * supplied x and y offsets.
439
+ *
440
+ * Original region remains unchanged.
441
+ */
295
442
  offset(x, y) {
296
443
  return new Region(this.x + x, this.y + y, this.width, this.height, this.confidence);
297
444
  }
298
445
  /**
299
- * Checks whether this region overlaps
300
- * with another region.
301
- *
302
- * Returns:
303
- * - true if regions intersect
304
- * - false otherwise
305
- */
446
+ * Checks whether this region overlaps
447
+ * with another region.
448
+ *
449
+ * Returns:
450
+ * - true if regions intersect
451
+ * - false otherwise
452
+ */
306
453
  intersects(region) {
307
- return (this.x <= region.x + region.width) && (this.x + this.width >= region.x) && (this.y <= region.y + region.height) && (this.y + this.height >= region.y);
454
+ return (this.x <= region.x + region.width &&
455
+ this.x + this.width >= region.x &&
456
+ this.y <= region.y + region.height &&
457
+ this.y + this.height >= region.y);
308
458
  }
309
459
  /**
310
- * Returns the total area of the region
311
- * in pixels.
312
- */
460
+ * Returns the total area of the region
461
+ * in pixels.
462
+ */
313
463
  area() {
314
464
  return this.width * this.height;
315
465
  }
316
466
  /**
317
- * Extracts OCR text from this region.
318
- *
319
- * Returns full OCR output including:
320
- * - text
321
- * - TSV data
322
- * - HOCR data
323
- * - block information
324
- */
467
+ * Extracts OCR text from this region.
468
+ *
469
+ * Returns full OCR output including:
470
+ * - text
471
+ * - TSV data
472
+ * - HOCR data
473
+ * - block information
474
+ */
325
475
  async readText() {
326
476
  this.checkConfig();
327
477
  return await (0, ocr_1.extractTextFromRegion)(this);
328
478
  }
329
479
  /**
330
- * Searches for text within this region.
331
- *
332
- * Returns the matching text region
333
- * if found, otherwise null.
334
- *
335
- * Supports selecting a specific match
336
- * using the index parameter.
337
- */
480
+ * Searches for text within this region.
481
+ *
482
+ * Returns the matching text region
483
+ * if found, otherwise null.
484
+ *
485
+ * Supports selecting a specific match
486
+ * using the index parameter.
487
+ */
338
488
  async findText(text, index = 0) {
339
489
  this.checkConfig();
340
490
  const match = await (0, text_1.findText)(text, index, this);
@@ -344,35 +494,42 @@ class Region {
344
494
  return match.offset(this.x, this.y);
345
495
  }
346
496
  /**
347
- * Checks whether the supplied text
348
- * exists within this region.
349
- *
350
- * Returns:
351
- * - true if found
352
- * - false otherwise
353
- */
497
+ * Checks whether the supplied text
498
+ * exists within this region.
499
+ *
500
+ * Returns:
501
+ * - true if found
502
+ * - false otherwise
503
+ */
354
504
  async existsText(text) {
355
505
  this.checkConfig();
356
506
  return await (0, text_1.existsText)(text, this);
357
507
  }
358
508
  /**
359
- * Searches for an image within this region.
360
- *
361
- * Matching is performed only inside
362
- * the current region, improving speed
363
- * and reducing false positives.
364
- *
365
- * Returns:
366
- * - matching Region if found
367
- * - null otherwise
368
- */
509
+ * Searches for an image within this region.
510
+ *
511
+ * Matching is performed only inside
512
+ * the current region, improving speed
513
+ * and reducing false positives.
514
+ *
515
+ * Returns:
516
+ * - matching Region if found
517
+ * - null otherwise
518
+ */
369
519
  async find(image, confidence = 0.8) {
370
520
  this.checkConfig();
371
521
  const screenshot = await (0, screen_1.captureScreen)();
372
522
  const croppedScreenshot = await (0, matcher_1.cropMat)(screenshot, this);
373
523
  const template = await (0, matcher_1.loadTemplate)((0, path_1.resolveImagePath)(image));
524
+ const metadata = (0, templateMetadata_1.loadTemplateMetadata)((0, path_1.resolveImagePath)(image));
374
525
  try {
375
- const match = (0, matcher_1.matchTemplate)(croppedScreenshot, template, confidence);
526
+ if (metadata) {
527
+ metadata.currentResolution = {
528
+ width: screenshot.cols,
529
+ height: screenshot.rows,
530
+ };
531
+ }
532
+ const match = (0, matcher_1.matchTemplate)(croppedScreenshot, template, confidence, metadata);
376
533
  if (!match) {
377
534
  return null;
378
535
  }
@@ -384,14 +541,17 @@ class Region {
384
541
  template.delete();
385
542
  }
386
543
  }
544
+ expand(pixels) {
545
+ return new Region(this.x - pixels, this.y - pixels, this.width + pixels * 2, this.height + pixels * 2, this.confidence);
546
+ }
387
547
  /**
388
- * Finds all image matches within this region.
389
- *
390
- * Matching is performed only inside
391
- * the current region.
392
- *
393
- * Returns an array of matching regions.
394
- */
548
+ * Finds all image matches within this region.
549
+ *
550
+ * Matching is performed only inside
551
+ * the current region.
552
+ *
553
+ * Returns an array of matching regions.
554
+ */
395
555
  async findAll(image, confidence = 0.8) {
396
556
  this.checkConfig();
397
557
  const screenshot = await (0, screen_1.captureScreen)();
@@ -399,7 +559,7 @@ class Region {
399
559
  const template = await (0, matcher_1.loadTemplate)((0, path_1.resolveImagePath)(image));
400
560
  try {
401
561
  const matches = (0, matcher_1.findAllMatches)(croppedScreenshot, template, confidence);
402
- return matches.map(match => match.offset(this.x, this.y));
562
+ return matches.map((match) => match.offset(this.x, this.y));
403
563
  }
404
564
  finally {
405
565
  screenshot.delete();
@@ -408,15 +568,15 @@ class Region {
408
568
  }
409
569
  }
410
570
  /**
411
- * Checks whether an image exists
412
- * within this region.
413
- *
414
- * Returns:
415
- * - true if found
416
- * - false otherwise
417
- */
571
+ * Checks whether an image exists
572
+ * within this region.
573
+ *
574
+ * Returns:
575
+ * - true if found
576
+ * - false otherwise
577
+ */
418
578
  async exists(image, confidence = 0.8) {
419
- return await this.find(image, confidence) !== null;
579
+ return (await this.find(image, confidence)) !== null;
420
580
  }
421
581
  }
422
582
  exports.Region = Region;
@@ -0,0 +1 @@
1
+ export declare function getVersion(): string;
@@ -0,0 +1,18 @@
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.getVersion = getVersion;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ function getVersion() {
10
+ try {
11
+ const packageJsonPath = path_1.default.join(__dirname, '..', 'package.json');
12
+ const packageJson = JSON.parse(fs_1.default.readFileSync(packageJsonPath, 'utf8'));
13
+ return packageJson.version;
14
+ }
15
+ catch (error) {
16
+ return 'unknown';
17
+ }
18
+ }