@bettergi/utils 0.1.3 → 0.1.5

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/game.d.ts CHANGED
@@ -53,4 +53,29 @@ export declare const setTimeTo: (hour: number, minute: number, options?: ClockOp
53
53
  * @param options 时钟参数
54
54
  */
55
55
  export declare const setTime: (period: "night" | "morning" | "noon" | "evening", options?: ClockOptions) => Promise<void>;
56
+ /** tab 翻页配置 */
57
+ type TabNavigationOptions = {
58
+ /** 标签页图标宽度(默认:96) */
59
+ tabIconWidth?: number;
60
+ /** 翻页按钮垂直偏移(默认:540) */
61
+ verticalOffset?: number;
62
+ /** 翻页按钮左侧内边距(默认:72) */
63
+ paddingLeft?: number;
64
+ /** 翻页按钮右侧内边距(默认:72) */
65
+ paddingRight?: number;
66
+ /** 是否向前翻页(默认:false) */
67
+ backwards?: boolean;
68
+ /** 最大尝试次数(默认:屏幕宽度 / 标签页图标宽度) */
69
+ maxAttempts?: number;
70
+ /** 每次尝试间隔时间(毫秒,默认:1000) */
71
+ retryInterval?: number;
72
+ };
73
+ /**
74
+ * 导航到指定标签页
75
+ * @param condition 导航成功的条件
76
+ * @param options 翻页配置
77
+ * @returns - true 条件满足
78
+ * - false 达到最大重试次数
79
+ */
80
+ export declare const navigateToTab: (condition: () => boolean, options?: TabNavigationOptions) => Promise<boolean>;
56
81
  export {};
package/dist/game.js CHANGED
@@ -10,13 +10,13 @@ import { findTextInDirection, findTextWithinBounds, findTextWithinListView } fro
10
10
  * @param action 执行动作
11
11
  */
12
12
  export const withGameMetrics = async (w, h, dpi, action) => {
13
- const { width, height, screenDpiScale } = genshin;
13
+ const [_w, _h, _dpi] = globalThis["getGameMetrics"] ? getGameMetrics() : [];
14
14
  try {
15
15
  setGameMetrics(w, h, dpi);
16
16
  return await action();
17
17
  }
18
18
  finally {
19
- setGameMetrics(width, height, screenDpiScale);
19
+ globalThis["getGameMetrics"] && setGameMetrics(_w, _h, _dpi);
20
20
  }
21
21
  };
22
22
  /**
@@ -39,13 +39,13 @@ export const openMenu = async (name, stepBackwards = false, options = {}) => {
39
39
  await openPaimonMenu();
40
40
  // 2.步进搜索菜单按钮
41
41
  const { navWidth = 95, steps: step = 30 } = options;
42
- const findTooltip = () => findTextWithinBounds(name, navWidth, 0, 20 + name.length * 20, genshin.height);
42
+ const findTooltip = () => findTextWithinBounds(name, navWidth, 0, 20 + name.length * 20, 1080);
43
43
  const tooltip = await withGameMetrics(1920, 1080, 1.5, async () => {
44
44
  let result = undefined;
45
- const steps = Math.ceil(genshin.height / step);
45
+ const steps = Math.ceil(1080 / step);
46
46
  for (let i = 0; i < steps; i++) {
47
47
  const o = i * step;
48
- const y = stepBackwards ? genshin.height - o : o;
48
+ const y = stepBackwards ? 1080 - o : o;
49
49
  moveMouseTo(Math.round(navWidth / 2), y);
50
50
  await sleep(30); // 等待提示文字出现
51
51
  if ((result = findTooltip()) !== undefined)
@@ -130,3 +130,23 @@ export const setTime = async (period, options) => {
130
130
  return setTimeTo(18, 5, options);
131
131
  }
132
132
  };
133
+ /**
134
+ * 导航到指定标签页
135
+ * @param condition 导航成功的条件
136
+ * @param options 翻页配置
137
+ * @returns - true 条件满足
138
+ * - false 达到最大重试次数
139
+ */
140
+ export const navigateToTab = async (condition, options) => {
141
+ const { tabIconWidth = 96, verticalOffset = 540, paddingLeft = 72, paddingRight = 72, backwards = false, retryInterval = 1000 } = options || {};
142
+ const attempts = Math.floor(options?.maxAttempts ?? 1920 / tabIconWidth);
143
+ for (let i = 0; i < attempts; i++) {
144
+ if (i === 0 && condition())
145
+ return true; // fast path
146
+ click(backwards ? paddingLeft : 1920 - paddingRight, verticalOffset); // prev or next
147
+ await sleep(retryInterval);
148
+ if (condition())
149
+ return true;
150
+ }
151
+ return false;
152
+ };
package/dist/ocr.js CHANGED
@@ -13,7 +13,13 @@ const directionToBounds = (direction) => {
13
13
  const y = direction.includes("south") ? genshin.height / 2 : 0;
14
14
  const w = direction === "north" || direction === "south" ? genshin.width : genshin.width / 2;
15
15
  const h = direction === "west" || direction === "east" ? genshin.height : genshin.height / 2;
16
- return { x, y, w, h };
16
+ const scale = genshin.width / 1920;
17
+ if (scale <= 1) {
18
+ return { x, y, w, h };
19
+ }
20
+ else {
21
+ return { x: x / scale, y: y / scale, w: w / scale, h: h / scale };
22
+ }
17
23
  };
18
24
  /**
19
25
  * 在整个画面内搜索图片
@@ -21,13 +27,16 @@ const directionToBounds = (direction) => {
21
27
  * @returns 如果找到匹配的图片区域,则返回该区域
22
28
  */
23
29
  export const findImage = (path) => {
30
+ const ir = captureGameRegion();
24
31
  try {
25
- const ir = captureGameRegion();
26
32
  const ro = RecognitionObject.templateMatch(file.readImageMatSync(path));
27
33
  return findFirst(ir, ro, region => region.isExist());
28
34
  }
29
35
  catch (err) {
30
- err?.message && log.warn(`${err.message}`);
36
+ log.warn(`${err.message || err}`);
37
+ }
38
+ finally {
39
+ ir.dispose();
31
40
  }
32
41
  };
33
42
  /**
@@ -40,13 +49,16 @@ export const findImage = (path) => {
40
49
  * @returns 如果找到匹配的图片区域,则返回该区域
41
50
  */
42
51
  export const findImageWithinBounds = (path, x, y, w, h) => {
52
+ const ir = captureGameRegion();
43
53
  try {
44
- const ir = captureGameRegion();
45
54
  const ro = RecognitionObject.templateMatch(file.readImageMatSync(path), x, y, w, h);
46
55
  return findFirst(ir, ro, region => region.isExist());
47
56
  }
48
57
  catch (err) {
49
- err?.message && log.warn(`${err.message}`);
58
+ log.warn(`${err.message || err}`);
59
+ }
60
+ finally {
61
+ ir.dispose();
50
62
  }
51
63
  };
52
64
  /**
@@ -69,12 +81,20 @@ export const findText = (text, options) => {
69
81
  const { ignoreCase = true, contains = false } = options || {};
70
82
  const searchText = ignoreCase ? text.toLowerCase() : text;
71
83
  const ir = captureGameRegion();
72
- const ro = RecognitionObject.ocrThis;
73
- return findFirst(ir, ro, region => {
74
- const itemText = ignoreCase ? region.text.toLowerCase() : region.text;
75
- const isMatch = contains ? itemText.includes(searchText) : itemText === searchText;
76
- return isMatch && region.isExist();
77
- });
84
+ try {
85
+ const ro = RecognitionObject.ocrThis;
86
+ return findFirst(ir, ro, region => {
87
+ const itemText = ignoreCase ? region.text.toLowerCase() : region.text;
88
+ const isMatch = contains ? itemText.includes(searchText) : itemText === searchText;
89
+ return isMatch && region.isExist();
90
+ });
91
+ }
92
+ catch (err) {
93
+ log.warn(`${err.message || err}`);
94
+ }
95
+ finally {
96
+ ir.dispose();
97
+ }
78
98
  };
79
99
  /**
80
100
  * 在指定区域内搜索文本
@@ -90,12 +110,20 @@ export const findTextWithinBounds = (text, x, y, w, h, options) => {
90
110
  const { ignoreCase = true, contains = false } = options || {};
91
111
  const searchText = ignoreCase ? text.toLowerCase() : text;
92
112
  const ir = captureGameRegion();
93
- const ro = RecognitionObject.ocr(x, y, w, h);
94
- return findFirst(ir, ro, region => {
95
- const itemText = ignoreCase ? region.text.toLowerCase() : region.text;
96
- const isMatch = contains ? itemText.includes(searchText) : itemText === searchText;
97
- return isMatch && region.isExist();
98
- });
113
+ try {
114
+ const ro = RecognitionObject.ocr(x, y, w, h);
115
+ return findFirst(ir, ro, region => {
116
+ const itemText = ignoreCase ? region.text.toLowerCase() : region.text;
117
+ const isMatch = contains ? itemText.includes(searchText) : itemText === searchText;
118
+ return isMatch && region.isExist();
119
+ });
120
+ }
121
+ catch (err) {
122
+ log.warn(`${err.message || err}`);
123
+ }
124
+ finally {
125
+ ir.dispose();
126
+ }
99
127
  };
100
128
  /**
101
129
  * 在指定方向上搜索文本
package/dist/time.js CHANGED
@@ -19,7 +19,7 @@ export const getNextMonday4AM = () => {
19
19
  result.setHours(4, 0, 0, 0);
20
20
  // 如果当前为周一且时间在4点前,则返回今天4点,否则返回下一个周一的4点
21
21
  const currentDay = now.getDay();
22
- const daysUntilNextMonday = currentDay === 1 && now.getHours() < 4 ? 0 : (8 - currentDay) % 7;
22
+ const daysUntilNextMonday = currentDay === 1 && now.getHours() < 4 ? 0 : 8 - currentDay;
23
23
  result.setDate(now.getDate() + daysUntilNextMonday);
24
24
  return result;
25
25
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bettergi/utils",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "开发 BetterGI 脚本常用工具集",
5
5
  "type": "module",
6
6
  "author": "Bread Grocery<https://github.com/breadgrocery>",
@@ -33,7 +33,7 @@
33
33
  "build": "tsc"
34
34
  },
35
35
  "devDependencies": {
36
- "@bettergi/types": "^0.1.1",
36
+ "@bettergi/types": "^0.1.3",
37
37
  "typescript": "^5.9.3"
38
38
  }
39
39
  }