@bettergi/utils 0.0.5 → 0.0.7
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 +33 -0
- package/dist/flow.d.ts +10 -0
- package/dist/flow.js +20 -0
- package/dist/game.d.ts +38 -0
- package/dist/game.js +103 -0
- package/dist/http.d.ts +29 -0
- package/dist/http.js +37 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/ocr.d.ts +18 -0
- package/dist/ocr.js +39 -0
- package/dist/time.d.ts +0 -0
- package/dist/time.js +0 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -8,6 +8,26 @@ npm install @bettergi/utils
|
|
|
8
8
|
|
|
9
9
|
## 函数清单
|
|
10
10
|
|
|
11
|
+
### 游戏内操作
|
|
12
|
+
|
|
13
|
+
> 常见游戏内操作封装,省去手动实现的繁琐。
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { openMenu, openMenuPage, openPaimonMenu, setTime } from "@bettergi/utils";
|
|
17
|
+
|
|
18
|
+
// 打开派蒙菜单
|
|
19
|
+
await openPaimonMenu();
|
|
20
|
+
|
|
21
|
+
// 打开菜单
|
|
22
|
+
await openMenu("邮件");
|
|
23
|
+
|
|
24
|
+
// 打开菜单页面
|
|
25
|
+
await openMenuPage("问卷");
|
|
26
|
+
|
|
27
|
+
// 调整游戏时间
|
|
28
|
+
await setTime("evening");
|
|
29
|
+
```
|
|
30
|
+
|
|
11
31
|
### 图文识别
|
|
12
32
|
|
|
13
33
|
> 对 RecognitionObject 代码的封装,对于简单的找图、找字操作,不再需要编写复杂的代码。
|
|
@@ -116,3 +136,16 @@ try {
|
|
|
116
136
|
state.lastUsedTime = Date.now(); // 自动同步保存到文件
|
|
117
137
|
}
|
|
118
138
|
```
|
|
139
|
+
|
|
140
|
+
### 网络请求
|
|
141
|
+
|
|
142
|
+
> 对网络请求的简易封装。
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
import { getForBody } from "@bettergi/utils";
|
|
146
|
+
|
|
147
|
+
// 发送 GET 请求获取响应体内容
|
|
148
|
+
// 提示:需要在 `manifest.json` 文件中配置 `http_allowed_urls`,并在 调度器 -> 修改通用配置 中启用
|
|
149
|
+
const body = await getForBody("https://example.com/", undefined, { "User-Agent": "BetterGI" });
|
|
150
|
+
log.info(`响应体内容${body}`);
|
|
151
|
+
```
|
package/dist/flow.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 等待直到条件满足或超时
|
|
3
|
+
* @param condition 等待的条件判断函数,返回 true 表示条件满足
|
|
4
|
+
* @param timeout 超时时间(毫秒),默认 3000 毫秒
|
|
5
|
+
* @param interval 等待间隔(毫秒),默认 300 毫秒
|
|
6
|
+
* @param action 每次等待循环中执行的操作(可选)
|
|
7
|
+
* @returns - true 在超时前条件已满足
|
|
8
|
+
* - false 在超时后条件仍未满足
|
|
9
|
+
*/
|
|
10
|
+
export declare const waitUntil: (condition: (context: Record<string, any>) => boolean, timeout?: number, interval?: number, action?: (context: Record<string, any>) => void) => Promise<boolean>;
|
package/dist/flow.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 等待直到条件满足或超时
|
|
3
|
+
* @param condition 等待的条件判断函数,返回 true 表示条件满足
|
|
4
|
+
* @param timeout 超时时间(毫秒),默认 3000 毫秒
|
|
5
|
+
* @param interval 等待间隔(毫秒),默认 300 毫秒
|
|
6
|
+
* @param action 每次等待循环中执行的操作(可选)
|
|
7
|
+
* @returns - true 在超时前条件已满足
|
|
8
|
+
* - false 在超时后条件仍未满足
|
|
9
|
+
*/
|
|
10
|
+
export const waitUntil = async (condition, timeout, interval, action) => {
|
|
11
|
+
const context = {};
|
|
12
|
+
const deadline = Date.now() + (timeout ?? 3 * 1000);
|
|
13
|
+
while (Date.now() < deadline) {
|
|
14
|
+
if (condition(context))
|
|
15
|
+
return true;
|
|
16
|
+
action?.(context);
|
|
17
|
+
await sleep(interval ?? 300);
|
|
18
|
+
}
|
|
19
|
+
return false;
|
|
20
|
+
};
|
package/dist/game.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 打开派蒙菜单
|
|
3
|
+
*/
|
|
4
|
+
export declare const openPaimonMenu: () => Promise<void>;
|
|
5
|
+
/**
|
|
6
|
+
* 打开游戏菜单(左侧按钮)
|
|
7
|
+
* @param name 菜单名称
|
|
8
|
+
* @param reverse 是否反向搜索(可选)
|
|
9
|
+
* @param config 搜索参数(可选)
|
|
10
|
+
*/
|
|
11
|
+
export declare const openMenu: (name: "\u62CD\u7167" | "\u516C\u544A" | "\u90AE\u4EF6" | "\u65F6\u95F4" | "\u8BBE\u7F6E" | (string & {}), reverse?: boolean, config?: {
|
|
12
|
+
x?: number;
|
|
13
|
+
step?: number;
|
|
14
|
+
timeout?: number;
|
|
15
|
+
}) => Promise<void>;
|
|
16
|
+
/**
|
|
17
|
+
* 打开游戏菜单页面(菜单按钮列表)
|
|
18
|
+
* @param name 菜单页面名称
|
|
19
|
+
* @param config 菜单页面视图参数
|
|
20
|
+
*/
|
|
21
|
+
export declare const openMenuPage: (name: string, config?: {
|
|
22
|
+
x?: number;
|
|
23
|
+
y?: number;
|
|
24
|
+
w?: number;
|
|
25
|
+
h?: number;
|
|
26
|
+
lineHeight?: number;
|
|
27
|
+
}) => Promise<void>;
|
|
28
|
+
/**
|
|
29
|
+
* 调整游戏时间
|
|
30
|
+
* @param period 时间段
|
|
31
|
+
* @param config 时钟参数
|
|
32
|
+
*/
|
|
33
|
+
export declare const setTime: (period: "night" | "morning" | "noon" | "evening", config?: {
|
|
34
|
+
centerX?: number;
|
|
35
|
+
centerY?: number;
|
|
36
|
+
radius?: number;
|
|
37
|
+
offset?: number;
|
|
38
|
+
}) => Promise<void>;
|
package/dist/game.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { waitUntil } from "./flow";
|
|
2
|
+
import { mouseSlide } from "./mouse";
|
|
3
|
+
import { findTextInDirection, findTextWithinBounds, findTextWithinListView } from "./ocr";
|
|
4
|
+
/**
|
|
5
|
+
* 打开派蒙菜单
|
|
6
|
+
*/
|
|
7
|
+
export const openPaimonMenu = async () => {
|
|
8
|
+
// 1.返回主界面
|
|
9
|
+
await genshin.returnMainUi();
|
|
10
|
+
// 2.打开派蒙菜单
|
|
11
|
+
const findWorldLevel = () => findTextInDirection("世界等级", false, true, "north-west");
|
|
12
|
+
const ok = await waitUntil(() => findWorldLevel() !== undefined, 5000, 1000, () => keyPress("ESCAPE"));
|
|
13
|
+
if (!ok)
|
|
14
|
+
throw new Error("打开派蒙菜单超时");
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* 打开游戏菜单(左侧按钮)
|
|
18
|
+
* @param name 菜单名称
|
|
19
|
+
* @param reverse 是否反向搜索(可选)
|
|
20
|
+
* @param config 搜索参数(可选)
|
|
21
|
+
*/
|
|
22
|
+
export const openMenu = async (name, reverse, config) => {
|
|
23
|
+
// 1.打开派蒙菜单
|
|
24
|
+
await openPaimonMenu();
|
|
25
|
+
// 2.搜索菜单按钮
|
|
26
|
+
const { x = 50, step = 30, timeout = 3000 } = config || {};
|
|
27
|
+
const findTooltip = () => findTextWithinBounds(name, false, true, 0, 0, x + 150, genshin.height);
|
|
28
|
+
let result = undefined;
|
|
29
|
+
const steps = Math.ceil(genshin.height / step);
|
|
30
|
+
for (let i = 0; i < steps; i++) {
|
|
31
|
+
const o = i * step;
|
|
32
|
+
const y = reverse ? genshin.height - o : o;
|
|
33
|
+
moveMouseTo(x, y);
|
|
34
|
+
await sleep(30);
|
|
35
|
+
if ((result = findTooltip()) !== undefined)
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
// 3.点击菜单按钮
|
|
39
|
+
if (result != undefined) {
|
|
40
|
+
const ok = await waitUntil(() => findTooltip() === undefined, timeout, 1000, () => {
|
|
41
|
+
click(x, result.y);
|
|
42
|
+
});
|
|
43
|
+
if (!ok)
|
|
44
|
+
throw new Error(`打开菜单 ${name} 超时`);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
throw new Error(`打开菜单 ${name} 失败`);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* 打开游戏菜单页面(菜单按钮列表)
|
|
52
|
+
* @param name 菜单页面名称
|
|
53
|
+
* @param config 菜单页面视图参数
|
|
54
|
+
*/
|
|
55
|
+
export const openMenuPage = async (name, config) => {
|
|
56
|
+
// 1.打开派蒙菜单
|
|
57
|
+
await openPaimonMenu();
|
|
58
|
+
// 2.搜索菜单页面
|
|
59
|
+
const { x = 100, y = 330, w = 670, h = 730, lineHeight = 142 } = config || {};
|
|
60
|
+
const pageButton = await findTextWithinListView(name, false, true, {
|
|
61
|
+
x,
|
|
62
|
+
y,
|
|
63
|
+
w,
|
|
64
|
+
h,
|
|
65
|
+
maxListItems: 5,
|
|
66
|
+
lineHeight
|
|
67
|
+
});
|
|
68
|
+
if (!pageButton)
|
|
69
|
+
throw new Error(`搜索菜单页面 ${name} 失败`);
|
|
70
|
+
// 3.点击菜单页面
|
|
71
|
+
pageButton.click();
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* 调整游戏时间
|
|
75
|
+
* @param period 时间段
|
|
76
|
+
* @param config 时钟参数
|
|
77
|
+
*/
|
|
78
|
+
export const setTime = async (period, config) => {
|
|
79
|
+
// 1.打开时间页面
|
|
80
|
+
await openMenu("时间", true);
|
|
81
|
+
// 2.拨动指针
|
|
82
|
+
const { centerX = 1440, centerY = 502, radius = 400, offset = 5 } = config || {};
|
|
83
|
+
const index = ["night", "morning", "noon", "evening"].indexOf(period);
|
|
84
|
+
const periodsDirections = [
|
|
85
|
+
() => mouseSlide(centerX, centerY, centerX - offset, centerY + radius),
|
|
86
|
+
() => mouseSlide(centerX, centerY, centerX - radius, centerY - offset),
|
|
87
|
+
() => mouseSlide(centerX, centerY, centerX + offset, centerY - radius),
|
|
88
|
+
() => mouseSlide(centerX, centerY, centerX + radius, centerY + offset)
|
|
89
|
+
];
|
|
90
|
+
const jobs = Array.from({ length: 4 }, (_, i) => periodsDirections[(index + i + 1) % periodsDirections.length]);
|
|
91
|
+
for (const job of jobs)
|
|
92
|
+
await job();
|
|
93
|
+
// 3.点击确认按钮,等待调整结束
|
|
94
|
+
const findTooShort = () => findTextInDirection("时间少于", true, true, "south-east");
|
|
95
|
+
const findConfirmButton = () => findTextInDirection("确认", false, true, "south-east");
|
|
96
|
+
const ok = await waitUntil(() => findTooShort() !== undefined, 20 * 1000, 1000, () => {
|
|
97
|
+
findConfirmButton()?.click();
|
|
98
|
+
});
|
|
99
|
+
if (!ok)
|
|
100
|
+
throw new Error("调整时间超时");
|
|
101
|
+
// 4.返回主界面
|
|
102
|
+
await genshin.returnMainUi();
|
|
103
|
+
};
|
package/dist/http.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 发送 HTTP 请求,获取响应体内容
|
|
3
|
+
* @param method 请求方法
|
|
4
|
+
* @param url 请求 URL
|
|
5
|
+
* @param body 请求体
|
|
6
|
+
* @param headers 请求头
|
|
7
|
+
* @returns 响应体内容
|
|
8
|
+
*/
|
|
9
|
+
export declare const requestForBody: (method: Parameters<typeof http.request>[0], url: string, body?: string, headers?: Record<string, any>) => Promise<string>;
|
|
10
|
+
/**
|
|
11
|
+
* 发送 HTTP GET 请求,获取响应体内容
|
|
12
|
+
* @param url 请求 URL
|
|
13
|
+
* @param body 请求体
|
|
14
|
+
* @param headers 请求头
|
|
15
|
+
* @returns 响应体内容
|
|
16
|
+
*/
|
|
17
|
+
export declare const getForBody: (url: string, body?: string, headers?: Record<string, any>) => Promise<string>;
|
|
18
|
+
/**
|
|
19
|
+
* 发送 HTTP POST 请求,获取响应体内容
|
|
20
|
+
* @param url 请求 URL
|
|
21
|
+
* @param body 请求体
|
|
22
|
+
* @param headers 请求头
|
|
23
|
+
* @returns 响应体内容
|
|
24
|
+
*/
|
|
25
|
+
export declare const postForBody: (url: string, body?: string, headers?: Record<string, any>) => Promise<{
|
|
26
|
+
status_code: number;
|
|
27
|
+
headers: Record<string, string>;
|
|
28
|
+
body: string;
|
|
29
|
+
}>;
|
package/dist/http.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 发送 HTTP 请求,获取响应体内容
|
|
3
|
+
* @param method 请求方法
|
|
4
|
+
* @param url 请求 URL
|
|
5
|
+
* @param body 请求体
|
|
6
|
+
* @param headers 请求头
|
|
7
|
+
* @returns 响应体内容
|
|
8
|
+
*/
|
|
9
|
+
export const requestForBody = async (method, url, body, headers) => {
|
|
10
|
+
const resp = await http.request(method, url, body ?? "null", headers ? JSON.stringify(headers) : "null");
|
|
11
|
+
if (resp.status_code >= 200 && resp.status_code < 400) {
|
|
12
|
+
return resp.body;
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
throw new Error(`HTTP request failed with status ${resp.status_code}`);
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* 发送 HTTP GET 请求,获取响应体内容
|
|
20
|
+
* @param url 请求 URL
|
|
21
|
+
* @param body 请求体
|
|
22
|
+
* @param headers 请求头
|
|
23
|
+
* @returns 响应体内容
|
|
24
|
+
*/
|
|
25
|
+
export const getForBody = async (url, body, headers) => {
|
|
26
|
+
return requestForBody("GET", url, body, headers);
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* 发送 HTTP POST 请求,获取响应体内容
|
|
30
|
+
* @param url 请求 URL
|
|
31
|
+
* @param body 请求体
|
|
32
|
+
* @param headers 请求头
|
|
33
|
+
* @returns 响应体内容
|
|
34
|
+
*/
|
|
35
|
+
export const postForBody = (url, body, headers) => {
|
|
36
|
+
return http.request("POST", url, body, headers ? JSON.stringify(headers) : undefined);
|
|
37
|
+
};
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/ocr.d.ts
CHANGED
|
@@ -53,4 +53,22 @@ export declare const findTextWithinBounds: (text: string, contains: boolean, ign
|
|
|
53
53
|
* @returns 如果找到匹配的文本区域,则返回该区域,否则返回 undefined
|
|
54
54
|
*/
|
|
55
55
|
export declare const findTextInDirection: (text: string, contains: boolean, ignoreCase: boolean, direction: Direction) => Region | undefined;
|
|
56
|
+
/**
|
|
57
|
+
* 在列表视图中滚动搜索文本
|
|
58
|
+
* @param text 待搜索文本
|
|
59
|
+
* @param contains 是否包含
|
|
60
|
+
* @param ignoreCase 是否忽略大小写
|
|
61
|
+
* @param listView 列表视图参数
|
|
62
|
+
* @param timeout 搜索超时
|
|
63
|
+
* @returns 如果找到匹配的文本区域,则返回该区域,否则返回 undefined
|
|
64
|
+
*/
|
|
65
|
+
export declare const findTextWithinListView: (text: string, contains: boolean, ignoreCase: boolean, listView: {
|
|
66
|
+
x: number;
|
|
67
|
+
y: number;
|
|
68
|
+
w: number;
|
|
69
|
+
h: number;
|
|
70
|
+
maxListItems: number;
|
|
71
|
+
lineHeight: number;
|
|
72
|
+
padding?: number;
|
|
73
|
+
}, timeout?: number) => Promise<Region | undefined>;
|
|
56
74
|
export {};
|
package/dist/ocr.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { waitUntil } from "./flow";
|
|
2
|
+
import { mouseScrollDownLines } from "./mouse";
|
|
1
3
|
const findFirst = (ir, ro, predicate) => {
|
|
2
4
|
const candidates = ir.findMulti(ro);
|
|
3
5
|
for (let i = 0; i < candidates.count; i++) {
|
|
@@ -108,3 +110,40 @@ export const findTextInDirection = (text, contains, ignoreCase, direction) => {
|
|
|
108
110
|
const { x, y, w, h } = directionToBounds(direction);
|
|
109
111
|
return findTextWithinBounds(text, contains, ignoreCase, x, y, w, h);
|
|
110
112
|
};
|
|
113
|
+
/**
|
|
114
|
+
* 在列表视图中滚动搜索文本
|
|
115
|
+
* @param text 待搜索文本
|
|
116
|
+
* @param contains 是否包含
|
|
117
|
+
* @param ignoreCase 是否忽略大小写
|
|
118
|
+
* @param listView 列表视图参数
|
|
119
|
+
* @param timeout 搜索超时
|
|
120
|
+
* @returns 如果找到匹配的文本区域,则返回该区域,否则返回 undefined
|
|
121
|
+
*/
|
|
122
|
+
export const findTextWithinListView = async (text, contains, ignoreCase, listView, timeout) => {
|
|
123
|
+
const { x, y, w, h, maxListItems, lineHeight, padding = 10 } = listView;
|
|
124
|
+
const find = () => {
|
|
125
|
+
return findTextWithinBounds(text, contains, ignoreCase, x, y, w, h);
|
|
126
|
+
};
|
|
127
|
+
let firstRegion;
|
|
128
|
+
const isBottomTouched = () => {
|
|
129
|
+
const ro = RecognitionObject.ocr(x, y, w, h);
|
|
130
|
+
const list = captureGameRegion().findMulti(ro);
|
|
131
|
+
if (list.count > 0) {
|
|
132
|
+
if (firstRegion?.text === list[0].text && Math.abs(list[0].y - firstRegion.y) < lineHeight) {
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
firstRegion = list[0];
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
const ok = await waitUntil(() => find() != undefined || isBottomTouched(), timeout ?? 30 * 1000, 1000, async () => {
|
|
145
|
+
moveMouseTo(x + w - padding, y + padding);
|
|
146
|
+
await mouseScrollDownLines(maxListItems, lineHeight);
|
|
147
|
+
});
|
|
148
|
+
return ok ? find() : undefined;
|
|
149
|
+
};
|
package/dist/time.d.ts
ADDED
|
File without changes
|
package/dist/time.js
ADDED
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bettergi/utils",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"description": "Utils for BetterGI JavaScript Development",
|
|
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.0.
|
|
37
|
-
"typescript": "
|
|
36
|
+
"@bettergi/types": "^0.0.11",
|
|
37
|
+
"typescript": "5.6.3"
|
|
38
38
|
}
|
|
39
39
|
}
|