nut.js 是 Node.js 的桌面自動化架構,允許您使用 JavaScript 或 TypeScript 對滑鼠和鍵盤進行程式設計!
第一步
nut.js 的第一步——從安裝到自動化滑鼠移動
先決條件
nut.js 為您各自的目标平台提供了預建構版本的 OpenCV。為了使用這些預編譯的綁定,必須滿足某些運作時條件。
Windows
如果您運作的是 Windows 10 N 并且想要使用 ImageFinder 插件,請確定安裝了媒體功能包。
macOS
在 macOS 上,需要 Xcode 指令行工具。您可以通過運作以下指令來安裝它們:
xcode-select --install
注意:
如果您遇到滑鼠不動或鍵盤不打字等問題,請確定為您執行測試的程序提供可通路權限。
如果缺少權限,nut.js 會給你一個提示:
##### WARNING! The application running this script is not a trusted process! Please visit https://github.com/nut-tree/nut.js#macos #####
當應用程式想要使用輔助功能時,應該顯示權限彈出視窗。如果沒有,您可以嘗試手動添加運作腳本的應用程式。
設定 -> 安全和隐私 -> 隐私頁籤 -> 輔助功能 -> 添加...
例如,如果你想在iTerm2執行你的node腳本,您必須将 iTerm.app 添加到清單中。從内置終端運作腳本時,例如 VSCode 或 IntelliJ,您必須添加相應的 IDE。
Linux
根據您的發行版,Linux 設定可能會有所不同。
一般來說,nut.js 需要 libXtst
在 *buntu 發行版上安裝:
sudo apt-get install build-essential libxtst-dev
其他linux發行版的設定可能不同。
node.js
nut.js 針對各種版本的node.js建構和測試的。但是,為了獲得最佳相容性,建議運作node.js的最新可用 LTS 版本(在撰寫本文時為 lts/gallium)并使用像 nvm 這樣的版本管理器。
安裝
滿足我們的先決條件後,讓我們繼續安裝。以下步驟将在您選擇的目錄中執行。我們将此目錄簡稱為工作目錄。
npm project
讓我們首先通過執行以下指令在我們的工作目錄中初始化一個新的 npm 項目:
npm init
随意填寫互動式對話,但這并不是繼續初始設定的硬性要求。
(你可以通過運作 npm init -y 來接受所有預設值)
安裝nut.js
運作
npm i @nut-tree/nut-js
或
yarn add @nut-tree/nut-js
在我們新建立的 npm 項目中,将安裝 nut.js 及其所需的依賴項。
快照釋出版
nut.js 還提供快照版本,允許測試即将推出的功能。
運作
npm i @nut-tree/nut-js@next
或者
yarn add @nut-tree/nut-js@next
将安裝最新的 nut.js 開發版本。
注意:雖然快照版本非常适合在新的穩定版本之前與即将推出的功能一起使用,但它仍然是快照版本。請記住,快照版本在将來可能會發生變化和/或中斷,是以不建議在生産中使用它們 .
動起來
現在都已準備就緒,是時候開始行動了!
在我們的工作目錄中,讓我們建立一個新檔案 index.js。
在您喜歡的編輯器中打開它并添加以下行以開始使用:
const { mouse } = require("@nut-tree/nut-js");
(async () => {
})();
mouse 讓您可以控制您的滑鼠,讓我們來玩一玩吧!
注意:nut.js 是完全異步的,是以在大多數示例中你會看到類似上面代碼片段的内容,這是一個異步操作 IIFE,用作在頂層使用 async / await 的變通方法。
簡單移動滑鼠
const { mouse, left, right, up, down } = require("@nut-tree/nut-js");
(async () => {
await mouse.move(left(500));
await mouse.move(up(500));
await mouse.move(right(500));
await mouse.move(down(500))
})();
nut.js 提供了一個聲明式 API,是以我們可以使用 MovementApi 函數将光标相對于目前位置移動,而不是明确指明光标移動到的位置。
當通過 node index.js 執行時,你會看到你的光标沿着一個正方形的變移動,并結束在它的初始位置。
針對特定點
能夠向上、向下、向左或向右移動光标是一個好的開始,但我們并不是坐在畫闆前素描。讓我們看看如何在螢幕上定位特定點。
const { mouse, straightTo, Point } = require("@nut-tree/nut-js");
(async () => {
const target = new Point(500, 350);
await mouse.move(straightTo(target));
})();
straightTo 是另一個 MovementApi 函數,它接受一個目标點并計算一條朝向它的直線,從我們目前的光标位置開始到目标點。
加速/減速
如果您想将滑鼠移動速度配置得更快/更慢,nut.js 公開的每個執行個體都提供了一個配置對象。
滑鼠配置對象允許您配置以像素/秒為機關的移動速度。
const { mouse, straightTo, Point } = require("@nut-tree/nut-js");
(async () => {
mouse.config.mouseSpeed = 2000;
const fast = new Point(500, 350);
await mouse.move(straightTo(fast));
mouse.config.mouseSpeed = 100;
const slow = new Point(100, 150);
await mouse.move(straightTo(slow));
})();
瞬移
有時我們不想沿着路徑移動到某個點。
在這種情況下,我們可以依靠 setPosition 立即将光标位置更改為提供的點。
const { mouse, Point } = require("@nut-tree/nut-js");
(async () => {
const target = new Point(500, 350);
await mouse.setPosition(target);
})();
總結
- nut.js 提供了mouse執行個體來控制你的光标。
- 以像素/秒為機關的移動速度可通過配置對象進行配置。
- 它提供了一個進階 MovementApi 以友善相對滑鼠起始點移動。
- 對于快速更改光标位置,setPosition 是正确的方法。
螢幕搜尋
了解如何在螢幕上搜尋圖像以将其用于自動化
圖像搜尋
“張圖檔勝過千言萬語”
nut.js 允許您在螢幕上定位圖像,這是自動化的一個關鍵功能。
ImageFinder 提供程式
為此,我們必須安裝一個額外的包,提供執行圖像比較的功能。否則,所有依賴圖像比對的函數都會抛出錯誤,如 Error: No ImageFinder registered。
一個可用的選項是@nut-tree/template-matcher:
npm i @nut-tree/template-matcher
要使用此提供程式包,隻需在您的代碼中引入它,例如 index.js:
const { screen, imageResource } = require("@nut-tree/nut-js");
require("@nut-tree/template-matcher"); // 這是新加的
(async () => {
try {
await screen.find(imageResource("img.png"));
} catch (e) {
console.error(e);
}
})();
提供程式的功能
find、findAll 和 waitFor 是圖像搜尋的主要函數。
當 find 和 findAll 嘗試立即在螢幕上定位圖像時,waitFor 将重複掃描螢幕以查找圖像,直到達到特定的逾時時間。
預設情況下,圖像搜尋是在多個尺度上進行的。
在自動化更複雜的任務時,上述所有功能都是非常強大的幫手,是以讓我們看看如何利用它們來發揮我們的優勢!
使用模闆圖像
為了在您的螢幕上搜尋圖像,我們必須提供模闆圖像。
這些圖像可以通過它們的完整路徑和 loadImage 加載,也可以相對于可配置的資源目錄加載。
資源目錄
使用資源目錄時,您可以通過檔案名引用模闆圖像,省略完整路徑。在加載模闆圖像時,這些檔案名是相對于 screen.config.resourceDirectory 的。
screen.config.resourceDirectory = "/path/to/my/template/images"
如果未明确配置,screen.config.resourceDirectory 将設定為目前工作目錄。
從資源目錄加載圖像
不使用 loadImage,而是通過 imageResource 加載所謂的圖像資源。
從遠端主機擷取圖像
fetchFromUrl 允許您将 URL 傳遞給位于遠端主機上的圖像,該圖像将被擷取并作為 nut.js 圖像傳回。
find
模闆圖像是使用其完整路徑直接加載的圖像,相對于可配置的資源目錄或通過 fetchFromUrl 從遠端主機加載。
尋找圖像
讓我們通過檢視示例片段來剖析 screen.find 的工作原理:
const { screen, imageResource } = require("@nut-tree/nut-js");
require("@nut-tree/template-matcher");
(async () => {
screen.config.resourceDirectory = "/resouce/path";
try {
const region = await screen.find(imageResource("mouse.png"));
console.log(region);
} catch (e) {
console.error(e);
}
})();
首先,在第 1 行和第 2 行設定導入。
第 5 行設定我們的資源目錄,盡管最有趣的事情發生在第 7 行:實際搜尋圖像。
screen.find 将掃描您的主螢幕以查找提供的模闆圖像,如果找到比對項,它将傳回模闆圖像所在的區域。圖像以每個像素為基礎進行比對。比對像素的數量是可配置的, 通過配置對象的 confidence 屬性設定。confidence 應為 0 到 1 之間的值,預設為 0.99(對應于 99% 比對)。
nut.js 目前不支援多顯示器設定
跨平台技巧
資源目錄起初可能看起來讓你很混亂,但它實際上有一個非常好的副作用。想象一下編寫一個跨平台的自動化腳本,我們正在處理不同的 UI,是以需要處理不同的模闆圖像。
使用資源目錄,我們可以根據目前平台配置目錄:
screen.config.resourceDirectory = `/path/to/the/project/${process.platform}`;
這樣,我們可以将所有特定于平台的模闆圖像儲存在單獨的檔案夾中,這樣我們不必關心我們的代碼了。
通過使用依賴于平台的資源目錄,我們不必處理平台特定的檔案名。相同的檔案名将為目前平台加載正确的模闆圖像,無需進一步操作!
故障排除
萬一我們搞砸了,nut.js 會通過拒絕抛出錯誤讓我們知道。
錯誤的資源目錄
Searching for mouse.png failed. Reason: 'Error: Failed to load /foo/bar/mouse.png. Reason: 'Failed to load image from '/foo/bar/mouse.png''.'
未找到比對圖像
Searching for mouse.png failed. Reason: 'Error: No match with required confidence 0.99. Best match: 0 at (0, 0, 477, 328)'
總結
- nut.js 提供了一個螢幕執行個體來搜尋螢幕上的模闆圖像。
- 從中加載模闆圖像的目錄可通過配置對象進行配置。
- 它将在您的主螢幕上搜尋模闆圖像,如果找到比對項,它将傳回模闆圖像所在的區域。
- 比對像素的數量可通過配置對象的置信度屬性進行配置。
findAll
findAll 與 find 的用法非常相似。兩者之間的主要差別在于 findAll 将在主螢幕上傳回所有檢測到的比對項的清單。
find 中提到的所有其他内容也适用于 findAll。
waitFor
能夠在我們的螢幕上定位圖像在自動化時是一個巨大的有事,但實際上,我們必須處理時間逾時。waitFor 在這裡幫助我們指定一個逾時時長,希望多久時間内我們的模闆圖像出現在螢幕上 !
模闆圖像是使用其完整路徑直接加載的圖像,相對于可配置的資源目錄或通過 fetchFromUrl 從遠端主機加載。
等待圖像
讓我們稍微調整一下查找示例中使用的代碼片段:
const { screen, imageResource } = require("@nut-tree/nut-js");
require("@nut-tree/template-matcher");
(async () => {
screen.config.resourceDirectory = "/resouce/path";
try {
const region = await screen.waitFor(imageResource("mouse.png"));
console.log(region);
} catch (e) {
console.error(e);
}
})();
waitFor 基本上與 find 完全相同,但在指定的時間段内執行多次。
它會掃描您的主螢幕以查找給定的模闆圖像,但如果找不到它,它會再試一次。這些重試發生的時間間隔也是可配置的。
const { screen, imageResource } = require("@nut-tree/nut-js");
require("@nut-tree/template-matcher");
(async () => {
screen.config.resourceDirectory = "/resouce/path";
try {
const region = await screen.waitFor(imageResource("mouse.png"), 5000, 1000);
console.log(region);
} catch (e) {
console.error(e);
}
})();
在上面的代碼片段中,我們告訴 waitFor 最多尋找模闆圖像五秒鐘,每秒重試一次。
如果在配置的毫秒逾時後仍然找不到圖像,它将拒絕。否則,它将傳回它定位圖像的區域,就像查找一樣。
故障排除
find 中提到的所有内容也适用于 waitFor。
逾時
操作在 5000 毫秒後逾時
總結
- waitFor 将重複搜尋您的主螢幕以查找模闆圖像,如果找到比對項,它将傳回模闆圖像所在的區域。
- 如果找不到圖像,它将以可配置的時間間隔重試搜尋,直到達到配置的逾時(以毫秒為機關)。
取消waitFor
正如我們之前了解到的,waitFor 将重複搜尋我們的螢幕以查找給定的模闆圖像。
這種巨大的靈活性不是免費的,是以我們可能不想等待逾時觸發才能取消正在進行的搜尋。nut.js 遵循與浏覽器擷取 API 相同的取消方法,使用 AbortController。
在我們實際檢視示例之前,我們必須為我們的項目安裝一個額外的包:
npm i node-abort-controller
現在,讓我們看一個(人為的)例子:
const { screen, Region, imageResource } = require("@nut-tree/nut-js");
require("@nut-tree/template-matcher");
const { AbortController } = require("node-abort-controller");
(async () => {
const controller = new AbortController();
screen.waitFor(imageResource("test.png"), 5000, 1000, {abort: controller.signal});
setTimeout(() => controller.abort(), 2000);
})();
我們在第 6 行執行個體化我們的 AbortController 并将其信号作為 OptionalSearchParameter 傳遞給 waitFor。
waitFor 配置了 5000 毫秒的逾時,1000 毫秒後重試,但 2000 毫秒後,我們在 AbortController 上調用 abort() ,這将取消正在進行的搜尋:
Action aborted by signal
總結
- waitFor 可以使用 AbortController 取消。
突出顯示
特别是在開發過程中,我們可能希望直覺地跟蹤執行腳本時發生的情況。尤其是在當涉及到圖像搜尋時。 我們找到比對項的日志,但視覺訓示器會更好。
高亮就是用來解決這個問題的!
配置
通過用不透明的突出顯示視窗覆寫感興趣區域來突出顯示區域。
高亮顯示持續時間和不透明度成為 screen.config 對象上的可配置屬性。
- highlightDurationMs
- highlightOpacity
突出顯示區域
highlight 接收一個 Region 指定要突出顯示的區域。然後它将用不透明的突出顯示視窗覆寫給定的區域。
const { screen, Region } = require("@nut-tree/nut-js");
(async () => {
screen.config.highlightDurationMs = 3000;
const highlightRegion = new Region(50, 100, 200, 300);
await screen.highlight(highlightRegion);
})();
自動突出顯示
API 的結構方式非常容易突出顯示位于例如位置的區域。 尋找:
const { screen, imageResource } = require("@nut-tree/nut-js");
require("@nut-tree/template-matcher");
(async () => {
screen.config.resourceDirectory = "/resouce/path";
screen.config.highlightDurationMs = 3000;
await screen.highlight(screen.find(imageResource("image.png")));
})();
然而,手動添加突出顯示不僅麻煩,而且還需要額外的工作,因為我們在生産環境中運作腳本之前要再次删除它。
是以,nut.js 提供了一種自動高亮機制,可通過配置屬性切換。在開發期間突出顯示,在生産中禁用它!
const { screen, imageResource } = require("@nut-tree/nut-js");
require("@nut-tree/template-matcher");
(async () => {
screen.config.resourceDirectory = "/resouce/path";
screen.config.autoHighlight = true;
screen.config.highlightDurationMs = 1500;
await screen.find(imageResource("test.png"));
})();
開啟自動高亮後,我們再也不用關心手動高亮查找結果了。一旦find傳回一個有效的Region,它就會被高亮顯示。而且由于waitFor重用了find,自動高亮也在那裡起作用!
Summary
- nut.js 提供了一種可視化調試圖像搜尋結果的方法。
- 突出顯示持續時間和突出顯示視窗不透明度都可以通過配置對象進行配置。
- 自動突出顯示将自動突出顯示從查找傳回的結果。
參數化搜尋
find、findAll 和 waitFor 接受 OptionalSearchParameters 以微調搜尋。
這允許例如 将搜尋空間限制在螢幕的特定部分:
const { screen, Region, OptionalSearchParameters, imageResource } = require("@nut-tree/nut-js");
require("@nut-tree/template-matcher");
const { AbortController } = require("node-abort-controller");
(async () => {
// 配置你希望搜尋的區域的位置和大小
const searchRegion = new Region(10, 10, 500, 500);
// 配置您希望 Nut 在找到比對項之前擁有的相似度比率
const confidence = 0.88;
// 配置一個 Abort controller,這樣可以随時取消查找操作
const controller = new AbortController();
const { signal } = controller;
// 将您的參數輸入 OptionalSearchParameters 構造函數以確定它們符合規範
const fullSearchOptionsConfiguration = new OptionalSearchParameters(searchRegion, confidence, signal);
//.find() 将根據您的搜尋參數和提供的圖像資料傳回找到比對項的區域
const matchRegion = await screen.find(imageResource("image.png"), fullSearchOptionsConfiguration);
const cancelFindTimeout = setTimeout(() => {
controller.abort();
}, 5000);
})();
單一與多尺度搜尋
多尺度圖像搜尋讓您在多個螢幕分辨率之間切換時具有彈性,但也有代價。與在單一尺度上搜尋相比,在多個尺度上搜尋時可能需要更長的時間。
根據您手頭的任務,您可能不需要這種額外的靈活性,而是希望從更快的執行中獲益。有關示例,請參見此示例測試結果:
hyperfine --warmup 3 'node multi-scale.js' 'node single-scale.js' --show-output
Benchmark 1: node multi-scale.js
Time (mean ± σ): 933.5 ms ± 10.4 ms [User: 1647.4 ms, System: 433.8 ms]
Range (min … max): 920.9 ms … 948.4 ms 10 runs
Benchmark 2: node single-scale.js
Time (mean ± σ): 526.8 ms ± 9.3 ms [User: 400.2 ms, System: 108.4 ms]
Range (min … max): 514.3 ms … 544.4 ms 10 runs
Summary
'node single-scale.js' ran
1.77 ± 0.04 times faster than 'node multi-scale.js'
使用視窗
了解如何通過使用視窗來增強您的工作流程
getActiveWindow 允許您在函數調用時擷取系統焦點視窗的 Window 引用。getActiveWindow 将作為 Promise 傳回。
const { getActiveWindow } = require('@nut-tree/nut-js');
(async () => {
const windowRef = await getActiveWindow();
})();
現在單獨輸出這個不會很有用,記住它隻是一個參考:
// source:
// ...
const windowRef = await getActiveWindow();
console.log(windowRef);
// ...
// output:
Window {
providerRegistry: DefaultProviderRegistry {
// ...
},
windowHandle: 2165090
}
相反,我們想利用視窗的标題和區域屬性。 不過要小心,這些是 getter 屬性,每個屬性都傳回一個 Promise 而不是一個普通值——是以我們必須使用 await :
const { getActiveWindow } = require('@nut-tree/nut-js');
(async () => {
const windowRef = await getActiveWindow();
const title = await windowRef.title
const region = await windowRef.region
console.log(title, region)
})();
您還可以并行而不是順序等待這些值,如下所示:
const { getActiveWindow } = require('@nut-tree/nut-js');
(async () => {
const windowRef = await getActiveWindow();
const [title, region] = await Promise.all([windowRef.title, windowRef.region])
console.log(title, region)
})();
此外,請注意此腳本将始終詳細說明您從中運作它的 shell 的視窗資訊。 要嘗試擷取不同視窗的詳細資訊,請考慮在調用 getActiveWindow 之前添加延遲。 我們可以使用 nut.js 自帶的 sleep 輔助函數來實作:
const { getActiveWindow, sleep } = require('@nut-tree/nut-js');
(async () => {
await sleep(4000) //延遲 4 秒再繼續
const windowRef = await getActiveWindow();
const [title, region] = await Promise.all([windowRef.title, windowRef.region])
console.log(title, region)
})();
示例
在激活視窗内點選
const { getActiveWindow, centerOf, randomPointIn, mouse, sleep } = require('@nut-tree/nut-js');
(async () => {
const windowRef = await getActiveWindow();
const region = await windowRef.region
await mouse.setPosition(await centerOf(region))
await mouse.leftClick()
await sleep(1000)
await mouse.setPosition(await randomPointIn(region))
await mouse.leftClick()
})();
重複記錄活動視窗資訊
const { getActiveWindow } = require('@nut-tree/nut-js');
(async () => {
setInterval(async () => {
const windowRef = await getActiveWindow()
const [title, region] = await Promise.all([windowRef.title, windowRef.region])
console.log(title, region.toString())
}, 2000)
})();