Files
bettergi-scripts-list/repo/js/ActivitySwitchNotice/utils/tool.js
云端客 9fdefe24c2 main-asn (#3156)
* refactor(ActivitySwitchNotice): 优化通知配置构建逻辑

- 将 configNotice 变量改为 let 声明以支持动态更新
- 新增 async 函数 buildConfigNotice 用于构建通知配置
- 在 sendNotice 函数中调用 buildConfigNotice 确保配置最新
- 在 sendText 函数中添加 buildConfigNotice 调用保证配置同步

* refactor(utils): 导出工具函数并移除全局挂载

- 将 activity.js 中的 activityMain 函数改为导出函数
- 将 campaignArea.js 中的 ocrDailyCommission、ocrWeeklyCount、campaignAreaMain、dailyCommissionMain 函数改为导出函数
- 将 mapMission.js 中的 ocrMapMission、openMap、mapMission 函数改为导出函数
- 将 tool.js 中的 findTextAndClick、getDayOfWeek 函数改为导出函数
- 将 uid.js 中的 saveOnlyNumber、ocrUID、compareUid、checkUid、check 函数改为导出函数
- 将 ws.js 中的 send、sendText 函数改为导出函数
- 移除所有 utils 文件中对 this 对象的挂载操作
- 在 main.js 中导入新的工具函数并移除重复定义的函数

* fix(ActivitySwitchNotice): 修复圣遗物空间检查功能中的显示和逻辑问题

- 修正圣遗物空间不足提醒消息中的标点符号格式
- 修复圣遗物空间阈值错误日志中的数字格式
- 移除不必要的延时操作以优化执行效率
- 调整代码结构以提高运行性能

fix(ActivitySwitchNotice): 修复圣遗物空间检查功能中的显示和逻辑问题

- 修正圣遗物空间不足提醒消息中的标点符号格式
- 修复圣遗物空间阈值错误日志中的数字格式
- 移除不必要的延时操作以优化执行效率
- 调整代码结构以提高运行性能

refactor(ActivitySwitchNotice): 优化圣遗物检查功能中的延迟配置

- 将硬编码的延迟时间替换为可配置的常量
- 统一延迟时间管理,提高代码可维护性
- 保持原有功能逻辑不变的情况下提升代码质量

feat(ActivitySwitchNotice): 更新版本并新增圣遗物空间检测提醒功能

- 将插件版本从 0.1.3 更新至 0.1.5
- 新增圣遗物剩余空间检测提醒功能
- 在版本历史中添加 0.1.5 版本记录

fix(ActivitySwitchNotice): 修复圣遗物剩余空间阈值解析错误

- 添加 try-catch 块处理 parseInt 异常情况
- 当阈值格式错误时默认使用 400 的阈值
- 添加警告日志记录格式错误的阈值设置
- 确保程序在无效配置下仍能正常运行

feat(bag): 添加圣遗物背包空间检查功能

- 新增 HolyRelics.js 工具模块实现圣遗物数量检查逻辑
- 集成 OCR 识别功能用于获取圣遗物数量信息
- 添加背包空间不足提醒功能,可自定义阈值
- 在 main.js 中集成圣遗物检查流程
- 添加新的配置选项包括打开背包按键和圣遗物阈值设置
- 扩展工具类增加 findText、findImg 和 OcrFind 等通用识别方法
- 实现自动打开背包并处理过期物品弹窗功能

* fix(HolyRelics): 修复圣遗物背包空间检测功能

- 添加了进入圣遗物背包的点击状态验证,避免无法进入时继续执行
- 增强了OCR文本解析逻辑,添加了字符串分割长度验证和数值解析校验
- 优化了阈值参数解析,在main.js中添加了更安全的数值转换和错误处理
- 修复了工具函数中资源释放问题,在isInMainUI函数中使用try-finally确保资源被正确释放
- 更新了提醒消息文本,明确标识阈值设置参数
2026-04-25 12:28:42 +08:00

351 lines
10 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 通用找文本OCR
* @param {string|string[]} text 目标文本(单个文本或文本列表,列表时需全部匹配)
* @param {number} [x=0] OCR 区域左上角 X
* @param {number} [y=0] OCR 区域左上角 Y
* @param {number} [w=1920] OCR 区域宽度
* @param {number} [h=1080] OCR 区域高度
* @param {number} [attempts=5] OCR 尝试次数
* @param {number} [interval=50] 每次 OCR 之间的等待间隔(毫秒)
*
* @returns
* - RecognitionResult | null
*/
export async function findText(
text,
x = 0,
y = 0,
w = 1920,
h = 1080,
attempts = 5,
interval = 50
) {
const keywords = (Array.isArray(text) ? text : [text])
.map(t => t.toLowerCase());
for (let i = 0; i < attempts; i++) {
const gameRegion = captureGameRegion();
try {
const ro = RecognitionObject.Ocr(x, y, w, h);
const results = gameRegion.findMulti(ro);
for (let j = 0; j < results.count; j++) {
const res = results[j];
if (!res.isExist() || !res.text) continue;
const ocrText = res.text.toLowerCase();
const matched = keywords.every(k => ocrText.includes(k));
if (matched) {
return res;
}
}
} finally {
gameRegion.dispose();
}
await sleep(interval);
}
return null;
}
/**
* 通用找文本并点击OCR
* @param {string} text 目标文本(单个文本)
* @param {number} [x=0] OCR 区域左上角 X
* @param {number} [y=0] OCR 区域左上角 Y
* @param {number} [w=1920] OCR 区域宽度
* @param {number} [h=1080] OCR 区域高度
* @param {number} [attempts=5] OCR 尝试次数
* @param {number} [interval=50] 每次 OCR 之间的等待间隔(毫秒)
* @param {number} [preClickDelay=50] 点击前等待时间(毫秒)
* @param {number} [postClickDelay=50] 点击后等待时间(毫秒)
*
* @returns
* - RecognitionResult | null
*/
export async function findTextAndClick(
text,
x = 0,
y = 0,
w = 1920,
h = 1080,
attempts = 5,
interval = 50,
preClickDelay = 50,
postClickDelay = 50
) {
const keyword = text.toLowerCase();
for (let i = 0; i < attempts; i++) {
const gameRegion = captureGameRegion();
try {
const ro = RecognitionObject.Ocr(x, y, w, h);
const results = gameRegion.findMulti(ro);
for (let j = 0; j < results.count; j++) {
const res = results[j];
if (
res.isExist() &&
res.text &&
res.text.toLowerCase().includes(keyword)
) {
await sleep(preClickDelay);
res.click();
await sleep(postClickDelay);
return res;
}
}
} finally {
gameRegion.dispose();
}
await sleep(interval);
}
return null;
}
/**
* 通用找图/找RO支持图片文件路径、Mat
* @param {string|Mat} target 图片路径或已构造的 Mat
* @param {number} [x=0] 识别区域左上角 X
* @param {number} [y=0] 识别区域左上角 Y
* @param {number} [w=1920] 识别区域宽度
* @param {number} [h=1080] 识别区域高度
* @param {number} [timeout=1000] 识别时间上限(毫秒)
* @param {number} [interval=50] 每次识别之间的等待间隔(毫秒)
*
* @returns
* - RecognitionResult | null
*/
export async function findImg(
target,
x = 0,
y = 0,
w = 1920,
h = 1080,
timeout = 1000,
interval = 50
) {
const ro =
typeof target === 'string'
? RecognitionObject.TemplateMatch(
file.readImageMatSync(target),
x, y, w, h
)
: RecognitionObject.TemplateMatch(
target,
x, y, w, h
);
const start = Date.now();
while (Date.now() - start <= timeout) {
const gameRegion = captureGameRegion();
try {
const res = gameRegion.find(ro);
if (!res.isEmpty()) {
return res;
}
} catch (e) {
log.error(e.toString());
} finally {
gameRegion.dispose();
}
await sleep(interval);
}
return null;
}
/**
* 通用找图并点击支持图片文件路径、Mat
* @param {string|Mat} target 图片路径或已构造的 Mat
* @param {number} [x=0] 识别区域左上角 X
* @param {number} [y=0] 识别区域左上角 Y
* @param {number} [w=1920] 识别区域宽度
* @param {number} [h=1080] 识别区域高度
* @param {number} [timeout=1000] 识别时间上限(毫秒)
* @param {number} [interval=50] 每次识别之间的等待间隔(毫秒)
* @param {number} [preClickDelay=50] 点击前等待时间(毫秒)
* @param {number} [postClickDelay=50] 点击后等待时间(毫秒)
*
* @returns
* - RecognitionResult | null
*/
export async function findImgAndClick(
target,
x = 0,
y = 0,
w = 1920,
h = 1080,
timeout = 1000,
interval = 50,
preClickDelay = 50,
postClickDelay = 50
) {
const ro =
typeof target === 'string'
? RecognitionObject.TemplateMatch(
file.readImageMatSync(target),
x, y, w, h
)
: RecognitionObject.TemplateMatch(
target,
x, y, w, h
);
const start = Date.now();
while (Date.now() - start <= timeout) {
const gameRegion = captureGameRegion();
try {
const res = gameRegion.find(ro);
if (!res.isEmpty()) {
await sleep(preClickDelay);
res.click();
await sleep(postClickDelay);
return res;
}
} finally {
gameRegion.dispose();
}
await sleep(interval);
}
return null;
}
/**
* 使用OCR技术在指定区域内查找文本
* @param {number} x - 区域左上角x坐标
* @param {number} y - 区域左上角y坐标
* @param {number} width - 区域宽度
* @param {number} height - 区域高度
* @returns {Promise<Region|null>} 返回找到的文本如果没有找到则返回null
*/
export async function OcrFind(x, y, width, height) {
let captureRegion = captureGameRegion(); // 获取游戏区域截图
try {
// 创建OCR识别对象指定识别区域
const recognitionObject = RecognitionObject.Ocr(x, y, width, height);
// 在截图上执行OCR识别
const result = captureRegion.find(recognitionObject);
// 返回识别到的文本如果未识别到则返回undefined
return result
} finally {
// 确保截图资源被正确释放,防止内存泄漏
if (captureRegion) {
captureRegion.dispose(); // 释放截图资源
}
}
}
/**
* 使用OCR技术在指定区域内查找文本内容
* @param {number} x - 区域左上角x坐标
* @param {number} y - 区域左上角y坐标
* @param {number} width - 区域宽度
* @param {number} height - 区域高度
* @returns {Promise<Region[]>} 返回找到的文本内容数组
*/
export async function OcrFindList(x, y, width, height) {
let captureRegion = captureGameRegion(); // 获取游戏区域截图
try {
// 创建OCR识别对象指定识别区域
const recognitionObject = RecognitionObject.Ocr(x, y, width, height);
// 在截图区域中查找多个匹配项
const resList = captureRegion.findMulti(recognitionObject);
return resList // 返回找到的文本列表
} finally {
// 确保释放截图资源,防止内存泄漏
if (captureRegion) {
captureRegion.dispose(); // 释放截图资源
}
}
}
/**
* 获取当前日期的星期信息
* @param {boolean} [calibrationGameRefreshTime=true] 是否进行游戏刷新时间校准
* @returns {Object} 返回包含星期数字和星期名称的对象
*/
export async function getDayOfWeek(calibrationGameRefreshTime = true) {
// 获取当前日期对象
let today = new Date();//4点刷新 所以要减去4小时
if (calibrationGameRefreshTime) {
today.setHours(today.getHours() - 4); // 减去 4 小
}
// 获取当前日期是星期几0代表星期日1代表星期一以此类推
const day = today.getDay();
// 创建包含星期名称的数组
const weekDays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
let weekDay = `${weekDays[day]}`;
log.debug(`今天是[{day}]`, day)
log.debug(`今天是[{weekDays}]`, weekDay)
// 返回包含星期数字和对应星期名称的对象
return {
day: day,
dayOfWeek: weekDay
}
}
// 判断是否在主界面的函数
export const isInMainUI = () => {
let captureRegion = captureGameRegion();
try {
const res = captureRegion.Find(RecognitionObject.TemplateMatch(
file.ReadImageMatSync("assets/paimon_menu.png"),
0,
0,
640,
216
));
return !res.isEmpty();
} finally {
if (captureRegion) {
captureRegion.dispose();
}
}
};
export async function toMainUi() {
let ms = 300
let index = 1
await sleep(ms);
while (!isInMainUI()) {
await sleep(ms);
await genshin.returnMainUi(); // 如果未启用,则返回游戏主界面
await sleep(ms);
if (index > 3) {
throw new Error(`多次尝试返回主界面失败`);
}
index += 1
}
}
/**
* 打开背包(检测过期物品)
*/
export async function openBag() {
const openBagKey = settings.openBagKey || "B";
await toMainUi();
await keyPress(openBagKey);
await sleep(500);
const expiredText = await findText("物品过期", 870, 280, 170, 40, 2);
if (expiredText) {
log.info("检测到过期物品,关闭弹窗");
await sleep(500);
await click(980, 750);
}
await sleep(50);
}