Files
bettergi-scripts-list/packages/utils/tool.js
2026-02-05 14:08:13 +08:00

561 lines
13 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.
import paimon from '../assets/imgs/paimon_menu.png';
import girl_moon from '../assets/imgs/girl_moon.png';
import primogem from '../assets/imgs/primogem.png';
import welkin_moon_logo from '../assets/imgs/welkin_moon_logo.png';
/**
* 获取图片 Mat支持单路径 / 路径数组)
*
* @param {string|string[]} path 图片路径或路径数组
* @returns {Mat|Mat[]} OpenCV Mat 或 Mat 数组
*/
function getImgMat(path) {
if (path == null) {
throw new Error('getImgMat: path 不能为空');
}
// 数组形式
if (Array.isArray(path)) {
return path.map((p, index) => {
if (typeof p !== 'string' || !p) {
throw new Error(`getImgMat: path[${index}] 不是有效字符串`);
}
return file.readImageMatSync(p);
});
}
// 单个路径
if (typeof path !== 'string') {
throw new Error('getImgMat: path 必须是字符串或字符串数组');
}
return file.readImageMatSync(path);
}
/**
* 通用找图/找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
*/
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
*/
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 {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
*/
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|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
*/
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;
}
/**
* 执行操作直到图片出现
* @param {string|Mat} target 目标图片路径或 Mat
* @param {() => Promise<void>} action 执行的操作函数
* @param {number} [x=0] 识别区域左上角 X
* @param {number} [y=0] 识别区域左上角 Y
* @param {number} [w=1920] 识别区域宽度
* @param {number} [h=1080] 识别区域高度
* @param {number} [timeout=5000] 超时时间(毫秒)
* @param {number} [interval=50] 操作和识别间隔(毫秒)
*
* @returns
* - RecognitionResult | null
*/
async function waitUntilImgAppear(
target,
action,
x = 0,
y = 0,
w = 1920,
h = 1080,
timeout = 5000,
interval = 50
) {
const start = Date.now();
while (Date.now() - start <= timeout) {
await action();
const res = await findImg(target, x, y, w, h, interval);
if (res) return res;
await sleep(interval);
}
return null;
}
/**
* 执行操作直到图片消失
* @param {string|Mat} target 目标图片路径或 Mat
* @param {() => Promise<void>} action 执行的操作函数
* @param {number} [x=0] 识别区域左上角 X
* @param {number} [y=0] 识别区域左上角 Y
* @param {number} [w=1920] 识别区域宽度
* @param {number} [h=1080] 识别区域高度
* @param {number} [timeout=5000] 超时时间(毫秒)
* @param {number} [interval=50] 操作和识别间隔(毫秒)
*
* @returns
* - true: 图片已消失, false: 超时
*/
async function waitUntilImgDisappear(
target,
action,
x = 0,
y = 0,
w = 1920,
h = 1080,
timeout = 5000,
interval = 50
) {
const start = Date.now();
while (Date.now() - start <= timeout) {
await action();
const res = await findImg(target, x, y, w, h, interval);
if (!res) return true;
await sleep(interval);
}
return false;
}
/**
* 执行操作直到文本出现
* @param {string|string[]} text 目标文本(单个文本或文本列表,列表时需全部匹配)
* @param {() => Promise<void>} action 执行的操作函数
* @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] 操作和识别间隔(毫秒)
*
* @returns
* - RecognitionResult | null
*/
async function waitUntilTextAppear(
text,
action,
x = 0,
y = 0,
w = 1920,
h = 1080,
attempts = 5,
interval = 50
) {
const start = Date.now();
while (Date.now() - start <= attempts * interval) {
await action();
const res = await findText(text, x, y, w, h, 1, interval);
if (res) return res;
await sleep(interval);
}
return null;
}
/**
* 执行操作直到文本消失
* @param {string} text 目标文本
* @param {() => Promise<void>} action 执行的操作函数
* @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] 操作和识别间隔(毫秒)
*
* @returns
* - true: 文本已消失, false: 超时
*/
async function waitUntilTextDisappear(
text,
action,
x = 0,
y = 0,
w = 1920,
h = 1080,
attempts = 5,
interval = 50
) {
const start = Date.now();
while (Date.now() - start <= attempts * interval) {
await action();
const res = await findText(text, x, y, w, h, 1, interval); // 每次只试 1 次 OCR
if (!res) return true;
await sleep(interval);
}
return false;
}
/**
* 根据派蒙图标判断当前是否位于主页面
* @return {Promise<boolean>}
* - true: 位于主页面, false: 不在主页面
*/
async function isInMainUI() {
try {
return !!(await findImg(paimon));
} catch (e) {
log.error("判断是否位于主页面时出错", e);
return false;
}
}
/**
* 启动一个后台任务,用于自动月卡点击
*
* 使用示例:
*
* const watcher = startMonthCardWatcher();
*
* // 执行你的操作
*
* await watcher.cancel();
*
* @return {function} cancel(): 取消监听方法,调用后可以停止检测事件运行(异步)
*/
function startMonthCardWatcher() {
let cancelled = false;
const task = (async () => {
try {
while (!cancelled) {
const [girl, common] = await Promise.all([
findImg(girl_moon),
findImg(welkin_moon_logo)
]);
if (girl || common) {
log.info("检测到月卡");
await sleep(200);
click(100, 100);
await sleep(200);
}
const stone = await findImg(primogem);
if (stone) {
log.info("点击原石");
while (!cancelled) {
await sleep(200);
click(100, 100);
if (await isInMainUI()) {
log.info("已进入主页面");
cancelled = true;
break;
}
}
}
await sleep(1000);
}
} catch (e) {
log.error("月卡监听异常", e);
} finally {
log.info("月卡监听任务结束");
}
})();
return {
cancel() {
cancelled = true;
return task;
}
};
}
/**
* 打开背包(检测过期物品)
*/
async function openBag() {
await genshin.returnMainUi();
keyPress("B");
await sleep(500);
const expiredText = await findText("物品过期", 870, 280, 170, 40, 2);
if (expiredText) {
log.info("检测到过期物品,关闭弹窗");
await sleep(500);
click(980, 750);
}
await sleep(50);
}
// /**
// * 修改分辨率为1080p(会导致截图器重启,任务全部清空,暂时无法使用,仅供参考)
// * @return {Promise<void>}
// */
// async function changeTo1080P() {
// await genshin.returnMainUi();
// const settings_button = await waitUntilImgAppear(
// esc_settings,
// async () => {
// keyPress("ESCAPE");
// await sleep(1500);
// }
// );
// if (settings_button) {
// await sleep(500);
// await waitUntilImgAppear(
// page_close_white,
// async () => {
// settings_button.click();
// await sleep(500);
// }
// );
// await sleep(1000);
// } else {
// throw new Error("打开菜单超时");
// }
// await findTextAndClick("图像", 100, 200, 200, 300, 10);
// const view_mode = await findText("显示模式", 450, 200, 200, 200, 10);
// await sleep(500);
// click(view_mode.x + 1100, view_mode.y + 20);
// await sleep(200);
// moveMouseBy(0, 100);
// await sleep(200);
// for (let count = 0; count < 20; count++) {
// verticalScroll(100);
// await sleep(50);
// }
// const text_1080p = await waitUntilTextAppear(
// ["1920", "1080"],
// () => {
// verticalScroll(-100);
// },
// 1400,
// 300,
// 400,
// 600,
// 20,
// 1000
// );
// await sleep(200);
// log.info("已切换至1080P");
// click(text_1080p.x + 100, text_1080p.y + 10);
// }
export {
getImgMat,
findImg,
findImgAndClick,
findText,
findTextAndClick,
waitUntilImgAppear,
waitUntilImgDisappear,
waitUntilTextAppear,
waitUntilTextDisappear,
isInMainUI,
startMonthCardWatcher,
openBag
};