@bettergi/utils 0.1.19 → 0.1.20
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/README.md +24 -0
- package/dist/mouse.js +4 -4
- package/dist/ocr.d.ts +24 -6
- package/dist/ocr.js +84 -37
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -59,6 +59,16 @@ const i3 = findImageBetweenCoordinates("assets/关闭.png", 960, 0, 1920, 1080);
|
|
|
59
59
|
// 在指定方向上搜索图片,找不到返回 undefined
|
|
60
60
|
const i4 = findImageInDirection("assets/关闭.png", "north-east");
|
|
61
61
|
|
|
62
|
+
// 在列表视图中查找图片,并滚动列表视图直到找到目标图片 或 列表视图触底
|
|
63
|
+
const i5 = await findImageWithinListView("assets/foo.png", {
|
|
64
|
+
x: 115,
|
|
65
|
+
y: 95,
|
|
66
|
+
w: 1157,
|
|
67
|
+
h: 906,
|
|
68
|
+
lineHeight: 175,
|
|
69
|
+
scrollLines: 4
|
|
70
|
+
});
|
|
71
|
+
|
|
62
72
|
// 在整个画面内搜索文本(不包含、忽略大小写),找不到返回 undefined
|
|
63
73
|
const t1 = findText("购买");
|
|
64
74
|
|
|
@@ -70,6 +80,20 @@ const t3 = findTextBetweenCoordinates("确认", 960, 540, 1920, 1080);
|
|
|
70
80
|
|
|
71
81
|
// 在指定方向上搜索文本(包含、忽略大小写),找不到返回 undefined
|
|
72
82
|
const t4 = findTextInDirection("师傅", "east", { contains: true, ignoreCase: true });
|
|
83
|
+
|
|
84
|
+
// 在列表视图中查找文本,并滚动列表视图直到找到目标文本 或 列表视图触底
|
|
85
|
+
const t5 = await findTextWithinListView(
|
|
86
|
+
"小麦",
|
|
87
|
+
{
|
|
88
|
+
x: 120,
|
|
89
|
+
y: 95,
|
|
90
|
+
w: 1045,
|
|
91
|
+
h: 865,
|
|
92
|
+
lineHeight: 115,
|
|
93
|
+
scrollLines: 7
|
|
94
|
+
},
|
|
95
|
+
{ contains: true }
|
|
96
|
+
);
|
|
73
97
|
```
|
|
74
98
|
|
|
75
99
|
### 行为流程
|
package/dist/mouse.js
CHANGED
|
@@ -72,9 +72,9 @@ export const naturalMouseMove = async (x1, y1, x2, y2, options) => {
|
|
|
72
72
|
return mouseMoveAlongWaypoints(waypoints, options);
|
|
73
73
|
};
|
|
74
74
|
/** 使用回放脚本模拟滚动 */
|
|
75
|
-
const simulateScroll = async (
|
|
75
|
+
const simulateScroll = async (wheelDelta, times) => {
|
|
76
76
|
const script = {
|
|
77
|
-
macroEvents: Array(times).fill({ type: 6, mouseX: 0, mouseY:
|
|
77
|
+
macroEvents: Array(times).fill({ type: 6, mouseX: 0, mouseY: wheelDelta, time: 0 }),
|
|
78
78
|
info: { name: "", description: "", x: 0, y: 0, width: 1920, height: 1080, recordDpi: 1.5 }
|
|
79
79
|
};
|
|
80
80
|
await keyMouseScript.run(JSON.stringify(script));
|
|
@@ -84,7 +84,7 @@ const simulateScroll = async (scrollAmountInClicks, times) => {
|
|
|
84
84
|
* @param height 滚动高度
|
|
85
85
|
* @param algorithm 自定义滚动算法函数,接收高度参数并返回滚动次数(默认算法为每18像素滚动一次)
|
|
86
86
|
*/
|
|
87
|
-
export const mouseScrollUp = (height, algorithm = h => Math.floor(h /
|
|
87
|
+
export const mouseScrollUp = (height, algorithm = h => Math.floor(h / 17.9795)) => {
|
|
88
88
|
return simulateScroll(120, algorithm(height));
|
|
89
89
|
};
|
|
90
90
|
/**
|
|
@@ -92,7 +92,7 @@ export const mouseScrollUp = (height, algorithm = h => Math.floor(h / 18)) => {
|
|
|
92
92
|
* @param height 滚动高度
|
|
93
93
|
* @param algorithm 自定义滚动算法函数,接收高度参数并返回滚动次数(默认算法为每18像素滚动一次)
|
|
94
94
|
*/
|
|
95
|
-
export const mouseScrollDown = (height, algorithm = h => Math.floor(h /
|
|
95
|
+
export const mouseScrollDown = (height, algorithm = h => Math.floor(h / 17.9795)) => {
|
|
96
96
|
return simulateScroll(-120, algorithm(height));
|
|
97
97
|
};
|
|
98
98
|
/**
|
package/dist/ocr.d.ts
CHANGED
|
@@ -9,14 +9,14 @@ export type ImageMat = ReturnType<typeof file.readImageMatSync>;
|
|
|
9
9
|
export type MatchDirection = "north" /** 上半边 */ | "north-east" /** 右上四分之一 */ | "east" /** 右半边 */ | "south-east" /** 右下四分之一 */ | "south" /** 下半边 */ | "south-west" /** 左下四分之一 */ | "west" /** 左半边 */ | "north-west"; /** 左上四分之一 */
|
|
10
10
|
/**
|
|
11
11
|
* 在整个画面内搜索图片
|
|
12
|
-
* @param image 图片路径 或
|
|
12
|
+
* @param image 图片路径 或 图片Mat
|
|
13
13
|
* @param config 识别对象配置
|
|
14
14
|
* @returns 如果找到匹配的图片区域,则返回该区域
|
|
15
15
|
*/
|
|
16
16
|
export declare const findImage: (image: string | ImageMat, config?: ROConfig) => Region | undefined;
|
|
17
17
|
/**
|
|
18
18
|
* 在指定区域内搜索图片
|
|
19
|
-
* @param image 图片路径 或
|
|
19
|
+
* @param image 图片路径 或 图片Mat
|
|
20
20
|
* @param x 水平方向偏移量(像素)
|
|
21
21
|
* @param y 垂直方向偏移量(像素)
|
|
22
22
|
* @param w 宽度
|
|
@@ -27,7 +27,7 @@ export declare const findImage: (image: string | ImageMat, config?: ROConfig) =>
|
|
|
27
27
|
export declare const findImageWithinBounds: (image: string | ImageMat, x: number, y: number, w: number, h: number, config?: ROConfig) => Region | undefined;
|
|
28
28
|
/**
|
|
29
29
|
* 在指定坐标范围内搜索图片
|
|
30
|
-
* @param image 图片路径 或
|
|
30
|
+
* @param image 图片路径 或 图片Mat
|
|
31
31
|
* @param left 左边界偏移量(像素)
|
|
32
32
|
* @param top 上边界偏移量(像素)
|
|
33
33
|
* @param right 右边界偏移量(像素)
|
|
@@ -38,13 +38,13 @@ export declare const findImageWithinBounds: (image: string | ImageMat, x: number
|
|
|
38
38
|
export declare const findImageBetweenCoordinates: (image: string | ImageMat, left: number, top: number, right: number, bottom: number, config?: ROConfig) => Region | undefined;
|
|
39
39
|
/**
|
|
40
40
|
* 在指定方向上搜索图片
|
|
41
|
-
* @param image 图片路径 或
|
|
41
|
+
* @param image 图片路径 或 图片Mat
|
|
42
42
|
* @param direction 搜索方向
|
|
43
43
|
* @param config 识别对象配置
|
|
44
44
|
* @returns 如果找到匹配的图片区域,则返回该区域
|
|
45
45
|
*/
|
|
46
46
|
export declare const findImageInDirection: (image: string | ImageMat, direction: MatchDirection, config?: ROConfig) => Region | undefined;
|
|
47
|
-
/**
|
|
47
|
+
/** 文本匹配选项 */
|
|
48
48
|
export type TextMatchOptions = {
|
|
49
49
|
/** 是否忽略大小写(默认: 是) */
|
|
50
50
|
ignoreCase?: boolean;
|
|
@@ -107,6 +107,15 @@ export type ListView = {
|
|
|
107
107
|
/** 纵向内边距 (默认: 10) */
|
|
108
108
|
paddingY?: number;
|
|
109
109
|
};
|
|
110
|
+
/**
|
|
111
|
+
* 在列表视图中滚动搜索区域
|
|
112
|
+
* @param condition 查找条件
|
|
113
|
+
* @param listView 列表视图参数
|
|
114
|
+
* @param retryOptions 重试选项
|
|
115
|
+
* @param sampling 区域采样函数,通过采样区域画面变化判断列表是否触底(默认:取上半部分)
|
|
116
|
+
* @returns 如果找到匹配的区域,则返回该区域,否则返回 undefined
|
|
117
|
+
*/
|
|
118
|
+
export declare const findWithinListView: (condition: (listViewRegion: ImageRegion) => Region | undefined, listView: ListView, retryOptions?: RetryOptions, sampling?: (listViewRegion: ImageRegion) => ImageRegion) => Promise<Region | undefined>;
|
|
110
119
|
/**
|
|
111
120
|
* 在列表视图中滚动搜索文本
|
|
112
121
|
* @param text 待搜索文本
|
|
@@ -116,4 +125,13 @@ export type ListView = {
|
|
|
116
125
|
* @param config 识别对象配置
|
|
117
126
|
* @returns 如果找到匹配的文本区域,则返回该区域,否则返回 undefined
|
|
118
127
|
*/
|
|
119
|
-
export declare const findTextWithinListView: (text: string, listView: ListView, matchOptions?: TextMatchOptions, retryOptions?: RetryOptions, config?: ROConfig) => Promise<Region | undefined>;
|
|
128
|
+
export declare const findTextWithinListView: (text: string, listView: ListView, matchOptions?: TextMatchOptions, retryOptions?: RetryOptions, config?: ROConfig, sampling?: (listViewRegion: ImageRegion) => ImageRegion) => Promise<Region | undefined>;
|
|
129
|
+
/**
|
|
130
|
+
* 在列表视图中查找图像
|
|
131
|
+
* @param image 图片路径 或 图片Mat
|
|
132
|
+
* @param listView 列表视图参数
|
|
133
|
+
* @param config 识别对象配置
|
|
134
|
+
* @param retryOptions 重试选项
|
|
135
|
+
* @returns 如果找到匹配的区域,则返回该区域,否则返回 undefined
|
|
136
|
+
*/
|
|
137
|
+
export declare const findImageWithinListView: (image: string | ImageMat, listView: ListView, config?: ROConfig, retryOptions?: RetryOptions, sampling?: (listViewRegion: ImageRegion) => ImageRegion) => Promise<Region | undefined>;
|
package/dist/ocr.js
CHANGED
|
@@ -15,7 +15,7 @@ const directionToBounds = (direction) => {
|
|
|
15
15
|
};
|
|
16
16
|
/**
|
|
17
17
|
* 在整个画面内搜索图片
|
|
18
|
-
* @param image 图片路径 或
|
|
18
|
+
* @param image 图片路径 或 图片Mat
|
|
19
19
|
* @param config 识别对象配置
|
|
20
20
|
* @returns 如果找到匹配的图片区域,则返回该区域
|
|
21
21
|
*/
|
|
@@ -39,7 +39,7 @@ export const findImage = (image, config = {}) => {
|
|
|
39
39
|
};
|
|
40
40
|
/**
|
|
41
41
|
* 在指定区域内搜索图片
|
|
42
|
-
* @param image 图片路径 或
|
|
42
|
+
* @param image 图片路径 或 图片Mat
|
|
43
43
|
* @param x 水平方向偏移量(像素)
|
|
44
44
|
* @param y 垂直方向偏移量(像素)
|
|
45
45
|
* @param w 宽度
|
|
@@ -67,7 +67,7 @@ export const findImageWithinBounds = (image, x, y, w, h, config = {}) => {
|
|
|
67
67
|
};
|
|
68
68
|
/**
|
|
69
69
|
* 在指定坐标范围内搜索图片
|
|
70
|
-
* @param image 图片路径 或
|
|
70
|
+
* @param image 图片路径 或 图片Mat
|
|
71
71
|
* @param left 左边界偏移量(像素)
|
|
72
72
|
* @param top 上边界偏移量(像素)
|
|
73
73
|
* @param right 右边界偏移量(像素)
|
|
@@ -80,7 +80,7 @@ export const findImageBetweenCoordinates = (image, left, top, right, bottom, con
|
|
|
80
80
|
};
|
|
81
81
|
/**
|
|
82
82
|
* 在指定方向上搜索图片
|
|
83
|
-
* @param image 图片路径 或
|
|
83
|
+
* @param image 图片路径 或 图片Mat
|
|
84
84
|
* @param direction 搜索方向
|
|
85
85
|
* @param config 识别对象配置
|
|
86
86
|
* @returns 如果找到匹配的图片区域,则返回该区域
|
|
@@ -89,7 +89,14 @@ export const findImageInDirection = (image, direction, config = {}) => {
|
|
|
89
89
|
const { x, y, w, h } = directionToBounds(direction);
|
|
90
90
|
return findImageWithinBounds(image, x, y, w, h, config);
|
|
91
91
|
};
|
|
92
|
-
|
|
92
|
+
/**
|
|
93
|
+
* 在图像区域内查找第一个符合条件的识别区域
|
|
94
|
+
* @param ir 图像区域
|
|
95
|
+
* @param ro 识别对象
|
|
96
|
+
* @param predicate 筛选条件
|
|
97
|
+
* @returns 第一个符合条件的识别区域,未找到则返回 undefined
|
|
98
|
+
*/
|
|
99
|
+
const findFirstRegion = (ir, ro, predicate) => {
|
|
93
100
|
const candidates = ir.findMulti(ro);
|
|
94
101
|
for (let i = 0; i < candidates.count; i++) {
|
|
95
102
|
if (predicate(candidates[i]))
|
|
@@ -97,6 +104,19 @@ const findFirst = (ir, ro, predicate) => {
|
|
|
97
104
|
}
|
|
98
105
|
return undefined;
|
|
99
106
|
};
|
|
107
|
+
/**
|
|
108
|
+
* 文本匹配
|
|
109
|
+
* @param text 待匹配文本
|
|
110
|
+
* @param searchText 待搜索文本
|
|
111
|
+
* @param options 搜索选项
|
|
112
|
+
* @returns 是否匹配
|
|
113
|
+
*/
|
|
114
|
+
const textMatch = (text, searchText, options) => {
|
|
115
|
+
const { ignoreCase = true, contains = false } = options || {};
|
|
116
|
+
text = ignoreCase ? text.toLowerCase() : text;
|
|
117
|
+
searchText = ignoreCase ? searchText.toLowerCase() : searchText;
|
|
118
|
+
return contains ? text.includes(searchText) : text === searchText;
|
|
119
|
+
};
|
|
100
120
|
/**
|
|
101
121
|
* 在整个画面内搜索文本
|
|
102
122
|
* @param text 待搜索文本
|
|
@@ -105,18 +125,14 @@ const findFirst = (ir, ro, predicate) => {
|
|
|
105
125
|
* @returns 如果找到匹配的文本区域,则返回该区域
|
|
106
126
|
*/
|
|
107
127
|
export const findText = (text, options, config = {}) => {
|
|
108
|
-
const { ignoreCase = true, contains = false } = options || {};
|
|
109
|
-
const searchText = ignoreCase ? text.toLowerCase() : text;
|
|
110
128
|
const ir = captureGameRegion();
|
|
111
129
|
try {
|
|
112
130
|
const ro = RecognitionObject.ocrThis;
|
|
113
131
|
if (Object.keys(config).length > 0) {
|
|
114
132
|
Object.assign(ro, config) && ro.initTemplate();
|
|
115
133
|
}
|
|
116
|
-
return
|
|
117
|
-
|
|
118
|
-
const isMatch = contains ? itemText.includes(searchText) : itemText === searchText;
|
|
119
|
-
return isMatch && region.isExist();
|
|
134
|
+
return findFirstRegion(ir, ro, region => {
|
|
135
|
+
return region.isExist() && textMatch(region.text, text, options);
|
|
120
136
|
});
|
|
121
137
|
}
|
|
122
138
|
catch (err) {
|
|
@@ -138,18 +154,14 @@ export const findText = (text, options, config = {}) => {
|
|
|
138
154
|
* @returns 如果找到匹配的文本区域,则返回该区域
|
|
139
155
|
*/
|
|
140
156
|
export const findTextWithinBounds = (text, x, y, w, h, options, config = {}) => {
|
|
141
|
-
const { ignoreCase = true, contains = false } = options || {};
|
|
142
|
-
const searchText = ignoreCase ? text.toLowerCase() : text;
|
|
143
157
|
const ir = captureGameRegion();
|
|
144
158
|
try {
|
|
145
159
|
const ro = RecognitionObject.ocr(x, y, w, h);
|
|
146
160
|
if (Object.keys(config).length > 0) {
|
|
147
161
|
Object.assign(ro, config) && ro.initTemplate();
|
|
148
162
|
}
|
|
149
|
-
return
|
|
150
|
-
|
|
151
|
-
const isMatch = contains ? itemText.includes(searchText) : itemText === searchText;
|
|
152
|
-
return isMatch && region.isExist();
|
|
163
|
+
return findFirstRegion(ir, ro, region => {
|
|
164
|
+
return region.isExist() && textMatch(region.text, text, options);
|
|
153
165
|
});
|
|
154
166
|
}
|
|
155
167
|
catch (err) {
|
|
@@ -186,40 +198,75 @@ export const findTextInDirection = (text, direction, options, config = {}) => {
|
|
|
186
198
|
return findTextWithinBounds(text, x, y, w, h, options, config);
|
|
187
199
|
};
|
|
188
200
|
/**
|
|
189
|
-
*
|
|
190
|
-
* @param
|
|
201
|
+
* 在列表视图中滚动搜索区域
|
|
202
|
+
* @param condition 查找条件
|
|
191
203
|
* @param listView 列表视图参数
|
|
192
|
-
* @param matchOptions 搜索选项
|
|
193
204
|
* @param retryOptions 重试选项
|
|
194
|
-
* @param
|
|
195
|
-
* @returns
|
|
205
|
+
* @param sampling 区域采样函数,通过采样区域画面变化判断列表是否触底(默认:取上半部分)
|
|
206
|
+
* @returns 如果找到匹配的区域,则返回该区域,否则返回 undefined
|
|
196
207
|
*/
|
|
197
|
-
export const
|
|
208
|
+
export const findWithinListView = async (condition, listView, retryOptions, sampling) => {
|
|
198
209
|
const { x, y, w, h, lineHeight, scrollLines = 1, paddingX = 10, paddingY = 10 } = listView;
|
|
199
|
-
const { maxAttempts =
|
|
200
|
-
|
|
201
|
-
|
|
210
|
+
const { maxAttempts = 99, retryInterval = 1000 } = retryOptions || {};
|
|
211
|
+
sampling = sampling || (r => r.deriveCrop(0, 0, r.width, Math.floor(r.height / 2)));
|
|
212
|
+
const captureListViewRegion = () => captureGameRegion().deriveCrop(x, y, w, h);
|
|
213
|
+
let _lvr;
|
|
202
214
|
const isReachedBottom = () => {
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
if (textRegion) {
|
|
207
|
-
if (lastTextRegion?.text === textRegion.text &&
|
|
208
|
-
Math.abs(textRegion.y - lastTextRegion.y) < lineHeight) {
|
|
215
|
+
const region = sampling(captureListViewRegion());
|
|
216
|
+
if (region?.isExist()) {
|
|
217
|
+
if (_lvr?.find(RecognitionObject.templateMatch(region.srcMat))) {
|
|
209
218
|
return true;
|
|
210
219
|
}
|
|
211
220
|
else {
|
|
212
|
-
|
|
221
|
+
_lvr = region;
|
|
213
222
|
return false;
|
|
214
223
|
}
|
|
215
224
|
}
|
|
216
|
-
// 异常情况:
|
|
217
|
-
return true;
|
|
225
|
+
return true; // 异常情况: 无法获取列表视图截图
|
|
218
226
|
};
|
|
219
|
-
const
|
|
227
|
+
const isFoundOrReachedBottom = await waitForAction(() => condition(captureListViewRegion())?.isExist() || isReachedBottom(), async () => {
|
|
220
228
|
moveMouseTo(x + w - paddingX, y + paddingY); // 移动到滚动条附近
|
|
221
229
|
await sleep(50);
|
|
222
230
|
await mouseScrollDownLines(scrollLines, lineHeight); // 滚动指定行数
|
|
223
231
|
}, { maxAttempts, retryInterval });
|
|
224
|
-
return
|
|
232
|
+
return isFoundOrReachedBottom ? condition(captureListViewRegion()) : undefined;
|
|
233
|
+
};
|
|
234
|
+
/**
|
|
235
|
+
* 在列表视图中滚动搜索文本
|
|
236
|
+
* @param text 待搜索文本
|
|
237
|
+
* @param listView 列表视图参数
|
|
238
|
+
* @param matchOptions 搜索选项
|
|
239
|
+
* @param retryOptions 重试选项
|
|
240
|
+
* @param config 识别对象配置
|
|
241
|
+
* @returns 如果找到匹配的文本区域,则返回该区域,否则返回 undefined
|
|
242
|
+
*/
|
|
243
|
+
export const findTextWithinListView = async (text, listView, matchOptions, retryOptions, config = {}, sampling) => {
|
|
244
|
+
const ro = RecognitionObject.ocrThis;
|
|
245
|
+
if (Object.keys(config).length > 0) {
|
|
246
|
+
Object.assign(ro, config) && ro.initTemplate();
|
|
247
|
+
}
|
|
248
|
+
return findWithinListView(lvr => {
|
|
249
|
+
return findFirstRegion(lvr, ro, region => {
|
|
250
|
+
return region.isExist() && textMatch(region.text, text, matchOptions);
|
|
251
|
+
});
|
|
252
|
+
}, listView, retryOptions, sampling);
|
|
253
|
+
};
|
|
254
|
+
/**
|
|
255
|
+
* 在列表视图中查找图像
|
|
256
|
+
* @param image 图片路径 或 图片Mat
|
|
257
|
+
* @param listView 列表视图参数
|
|
258
|
+
* @param config 识别对象配置
|
|
259
|
+
* @param retryOptions 重试选项
|
|
260
|
+
* @returns 如果找到匹配的区域,则返回该区域,否则返回 undefined
|
|
261
|
+
*/
|
|
262
|
+
export const findImageWithinListView = async (image, listView, config = {}, retryOptions, sampling) => {
|
|
263
|
+
const mat = typeof image === "string" ? file.readImageMatSync(image) : image;
|
|
264
|
+
const ro = RecognitionObject.templateMatch(mat);
|
|
265
|
+
if (Object.keys(config).length > 0) {
|
|
266
|
+
Object.assign(ro, config) && ro.initTemplate();
|
|
267
|
+
}
|
|
268
|
+
return findWithinListView(ir => {
|
|
269
|
+
const region = ir.find(ro);
|
|
270
|
+
return region.isExist() ? region : undefined;
|
|
271
|
+
}, listView, retryOptions, sampling);
|
|
225
272
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bettergi/utils",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.20",
|
|
4
4
|
"description": "开发 BetterGI 脚本常用工具集",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "Bread Grocery<https://github.com/breadgrocery>",
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
"build": "rimraf dist && tsc"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
|
-
"@bettergi/types": "workspace
|
|
37
|
-
"rimraf": "^6.1.
|
|
36
|
+
"@bettergi/types": "workspace:^",
|
|
37
|
+
"rimraf": "^6.1.2",
|
|
38
38
|
"typescript": "^5.9.3"
|
|
39
39
|
}
|
|
40
40
|
}
|