npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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);