@bettergi/utils
v0.1.29
Published
开发 BetterGI 脚本常用工具集
Readme
本项目是一个为Better Genshin Impact 设计的 JavaScript 开发工具集,旨在帮助开发者简化代码、不用重复造轮子。工具函数支持按需引入,轻量依赖。
安装
使用 npm
npm install @bettergi/utils使用 pnpm
pnpm install @bettergi/utils函数清单
游戏操作
常见游戏内操作封装,省去手动实现的繁琐。
// 打开派蒙菜单
await openPaimonMenu();
// 打开菜单
await openMenu("邮件");
// 打开菜单页面
await openMenuPage("问卷");
// 调整游戏到指定时间
await setTimeTo(12, 35);
// 调整游戏时间段
await setTime("evening");
// tab 翻页到指定页面(假设当前已打开背包)
await navigateToTab(() => {
return findTextWithinBounds("小道具", 0, 0, 220, 96) !== undefined;
});图文识别
对 RecognitionObject 代码的封装,对于简单的找图、找字操作,不再需要编写复杂的代码。
// 在整个画面内搜索图片,找不到返回 undefined
const i1 = findImage("assets/关闭.png", { use3Channels: true }); // 匹配颜色
// 在指定区域内搜索图片,找不到返回 undefined
const i2 = findImageWithinBounds("assets/关闭.png", 960, 0, 960, 1080, { threshold: 0.75 }); // 阈值0.75
// 在指定坐标范围内搜索图片,找不到返回 undefined
const i3 = findImageBetweenCoordinates("assets/关闭.png", 960, 0, 1920, 1080);
// 在指定方向上搜索图片,找不到返回 undefined
const i4 = findImageInDirection("assets/关闭.png", "north-east");
// 在列表视图中查找图片,并滚动列表视图直到找到目标图片 或 列表视图触底
const i5 = await findImageWithinListView("assets/foo.png", {
x: 115,
y: 95,
w: 1157,
h: 906,
lineHeight: 175,
scrollLines: 4
});
// 在整个画面内搜索文本(不包含、忽略大小写),找不到返回 undefined
const t1 = findText("购买");
// 在指定区域内搜索文本(不包含、忽略大小写),找不到返回 undefined
const t2 = findTextWithinBounds("确认", 960, 540, 960, 540);
// 在指定坐标范围内搜索文本(不包含、忽略大小写),找不到返回 undefined
const t3 = findTextBetweenCoordinates("确认", 960, 540, 1920, 1080);
// 在指定方向上搜索文本(包含、忽略大小写),找不到返回 undefined
const t4 = findTextInDirection("师傅", "east", { contains: true, ignoreCase: true });
// 在列表视图中查找文本,并滚动列表视图直到找到目标文本 或 列表视图触底
const t5 = await findTextWithinListView(
"小麦",
{
x: 120,
y: 95,
w: 1045,
h: 865,
lineHeight: 115,
scrollLines: 7
},
{ contains: true }
);行为流程
对脚本开发过程中常见工作流的抽象,例如: 等待/断言 操作/元素/区域 完成/出现/消失。
// 等待直到找不到 [关闭按钮] ,重试5次,每隔1秒重试一次(默认参数),期间按 Esc 键
const done = await waitForAction(
() => findImageInDirection("assets/关闭.png", "north-east") === undefined,
() => keyPress("ESCAPE")
);
if (!done) throw new Error("关闭页面超时");
// 断言 "生日" 文字区域即将出现,重试10次,每隔1秒重试一次,期间按 Esc 键
await assertRegionAppearing(
() => findTextInDirection("生日", "north-west"),
"打开派蒙菜单超时",
() => keyPress("ESCAPE"),
{ maxAttempts: 10, retryInterval: 1000 }
);
// 断言 "购买" 文字区域即将消失,重试5次,每隔1秒重试一次(默认参数),期间如果存在 "购买" 按钮则点击
const findButton = () => findTextWithinBounds("购买", 500, 740, 900, 110);
await assertRegionDisappearing(findButton, "点击购买按钮超时", () => findButton()?.click());鼠标操作
对常见鼠标操作的封装,如鼠标的平滑移动、鼠标滚轮滚动、鼠标拖拽等。
// 鼠标沿路径点移动并拖拽
await mouseMoveAlongWaypoints(
[
{ x: 100, y: 100 },
{ x: 200, y: 200 },
{ x: 300, y: 300 }
],
{ shouldDrag: true }
);
// 鼠标从 (745, 610) 拖拽到 (1280, 610)
await mouseDrag(745, 610, 1280, 610);
// 鼠标从 (745, 610) 平滑自然地移动 (1920, 1080)
await naturalMouseMove(745, 610, 1920, 1080);
// 鼠标滚轮向上滚动 175 像素
await mouseScrollUp(175);
// 鼠标滚轮向下滚动 175 像素
await mouseScrollDown(175);
// 鼠标滚轮向上滚动 99 行(默认: 175为背包物品行高)
await mouseScrollUpLines(99);
// 鼠标滚轮向下滚动 1 行(自定义: 115为商店物品行高)
await mouseScrollDownLines(1, 115);状态管理和持久化
基于深度 Proxy 实现的对象数据持久化,能够在数据被修改时自动同步至文件。使开发者能够像操作普通对象一样进行数据读写,而无需关心底层的持久化细节。
// 创建/读取存储对象,保存到存储文件 store/my-data.json 中
const store = useStore<{ lastUsedTime?: number; count: number }>("my-data");
// 默认值版本
// const store = useStoreWithDefaults("my-data", { lastUsedTime: 0, count: 0 });
if (store?.lastUsedTime) {
log.info(`欢迎回来!上次使用时间: ${store.lastUsedTime},计数器已累计至: ${store.count}`);
}
try {
// 模拟脚本运行期间状态的变化
for (let i = 0; i < Math.floor(Math.random() * 100); i++) {
store.count = (store.count || 0) + 1; // 自动保存到文件
}
} finally {
store.lastUsedTime = Date.now(); // 自动保存到文件
}进度追踪
创建进度追踪器并设置总进度,通过递进函数增加进度,开发者可以获取当前进度、当前耗时、平均耗时、预估剩余时间等数据。
const total = 100;
// 创建进度追踪器,使用默认日志记录器(可配置),打印间隔最小3秒
const tracker = new ProgressTracker(total, { interval: 3000 });
for (let i = 0; i < total; i++) {
await sleep(Math.round(Math.random() * 200));
// 仅递进+1
tracker.tick();
// 递进+3,并尝试打印当前进度和消息
tracker.tick({ message: "等待任务完成...", increment: 3 });
// 不递进,尝试打印当前进度和消息
tracker.print("等待任务完成...");
// 不递进,强制打印当前进度和警告消息
tracker.print("等待任务完成...", true, log.warn);
// 自定义模板
const progress = tracker.getProgress();
log.info("[{current}/{total}]: {msg}", progress.current, progress.total, "消息");
}
tracker.complete(`任务完成`);网络请求
对网络请求的简易封装。
提示:需要在
manifest.json文件中配置http_allowed_urls,并在调度器->修改通用配置中启用。
// 发送 GET 请求获取响应体内容
const body1 = await getForBody("https://jsonplaceholder.typicode.com/todos/1");
// 发送 POST 请求获取响应体内容
const body2 = await postForBody("https://jsonplaceholder.typicode.com/posts", {
title: "foo",
body: "bar",
userId: 1
});文件操作
// 列出指定文件夹内所有文件路径
const files = listFiles("assets");
// 读取文件文本行(非空白、去除首尾空白、去重)
const lines = readLinesSync("assets/data.txt", { notBlank: true, trim: true, distinct: true });日期时间
// 获取下一个(含当日)凌晨4点的时间
const d1 = getNextDay4AM();
// 获取下一个(含当日)周一凌晨4点的时间
const d2 = getNextMonday4AM();异常
// 获取异常信息字符串
const message = getErrorMessage(err);
// 判断是否为任务取消异常
const isTaskCanceled = isTaskCanceledException(err);
// 重复执行某个可能失败的异步操作,但是发生主机异常(如任务取消)时停止
for (let i = 0; i < 1000; i++) {
try {
await sleep(i);
} catch (err: any) {
if (isHostException(err)) throw err;
log.info(`第 ${i} 次运行失败,错误信息:${err.message}`);
}
}杂项
// 生成UUID(不带连字符)
const uuid = generateUUID(false);
// 数组洗牌
const arr = [1, 2, 3, 4, 5];
const shuffled = shuffleArray(arr);
// 深度合并多个对象:{ x: 1, y: { a: 2, b: 4, c: 5 }, d: 6 }
const result = deepMerge({ x: 1, y: { a: 2, b: 3 } }, { y: { b: 4, c: 5 }, d: 6 });
// 同步休眠3秒
sleepSync(3000);