@bettergi/utils 0.1.18 → 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 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 (scrollAmountInClicks, times) => {
75
+ const simulateScroll = async (wheelDelta, times) => {
76
76
  const script = {
77
- macroEvents: Array(times).fill({ type: 6, mouseX: 0, mouseY: scrollAmountInClicks, time: 0 }),
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 / 18)) => {
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 / 18)) => {
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
- const findFirst = (ir, ro, predicate) => {
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 findFirst(ir, ro, region => {
117
- const itemText = ignoreCase ? region.text.toLowerCase() : region.text;
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 findFirst(ir, ro, region => {
150
- const itemText = ignoreCase ? region.text.toLowerCase() : region.text;
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 text 待搜索文本
201
+ * 在列表视图中滚动搜索区域
202
+ * @param condition 查找条件
191
203
  * @param listView 列表视图参数
192
- * @param matchOptions 搜索选项
193
204
  * @param retryOptions 重试选项
194
- * @param config 识别对象配置
195
- * @returns 如果找到匹配的文本区域,则返回该区域,否则返回 undefined
205
+ * @param sampling 区域采样函数,通过采样区域画面变化判断列表是否触底(默认:取上半部分)
206
+ * @returns 如果找到匹配的区域,则返回该区域,否则返回 undefined
196
207
  */
197
- export const findTextWithinListView = async (text, listView, matchOptions, retryOptions, config = {}) => {
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 = 30, retryInterval = 1000 } = retryOptions || {};
200
- const findTargetText = () => findTextWithinBounds(text, x, y, w, h, matchOptions, config);
201
- let lastTextRegion;
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 textRegion = findFirst(captureGameRegion(), RecognitionObject.ocr(x, y, w, h), region => {
204
- return region.isExist() && region.text.trim().length > 0;
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
- lastTextRegion = textRegion;
221
+ _lvr = region;
213
222
  return false;
214
223
  }
215
224
  }
216
- // 异常情况: 找不到任何文本
217
- return true;
225
+ return true; // 异常情况: 无法获取列表视图截图
218
226
  };
219
- const isTextFoundOrBottomReached = await waitForAction(() => findTargetText() != undefined || isReachedBottom(), async () => {
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 isTextFoundOrBottomReached ? findTargetText() : undefined;
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/dist/store.js CHANGED
@@ -10,6 +10,9 @@ export const useStore = (name) => {
10
10
  // 读取文件数据
11
11
  const obj = (() => {
12
12
  try {
13
+ const storeFiles = [...file.readPathSync("store")].map(path => path.replace(/\\/g, "/"));
14
+ if (!storeFiles.includes(filePath))
15
+ throw new Error("File does not exist");
13
16
  const text = file.readTextSync(filePath);
14
17
  return JSON.parse(text);
15
18
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bettergi/utils",
3
- "version": "0.1.18",
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:latest",
37
- "rimraf": "^6.1.0",
36
+ "@bettergi/types": "workspace:^",
37
+ "rimraf": "^6.1.2",
38
38
  "typescript": "^5.9.3"
39
39
  }
40
40
  }