feat(义父立本): 添加UID识别和防重复执行功能 (#2633)

* feat(义父立本): 添加UID识别和防重复执行功能

- 集成OCR UID识别功能,自动获取当前游戏UID
- 添加execute.json文件记录执行状态,防止同一天重复执行
- 实现日期检查逻辑,已执行的UID当天不再运行
- 新增utils/uid.js工具模块,包含完整的UID识别和验证功能
- 升级版本号从1.0到1.1
- 添加云端客作为贡献者
- 优化代码格式和错误处理机制

* refactor(date): 调整日期获取逻辑以支持时区偏移

- 将 getCurrentDate 函数重命名为 getAdjustedDate
- 实现4小时时区偏移调整功能(立本4点更新)
- 使用手动格式化替代 toLocaleDateString 方法
- 更新函数调用以使用新的日期调整逻辑
This commit is contained in:
云端客
2026-01-09 00:29:20 +08:00
committed by GitHub
parent 9665552d39
commit 025cf2cf8c
5 changed files with 216 additions and 3 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -0,0 +1,7 @@
[
{
"uid": 0,
"success": false,
"date": ""
}
]

View File

@@ -1,5 +1,35 @@
eval(file.readTextSync(`utils/uid.js`));
let checkInterval = +settings.checkInterval || 50;
let waitTime = 40;
function getAdjustedDate() {
const now = new Date();
// 减去4小时4 * 60 * 60 * 1000 毫秒)
now.setHours(now.getHours() - 4);
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
const day = String(now.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
// 更新json文件
async function updateFile(settingsArray, path) {
let json = JSON.stringify(settingsArray, null, 2)
.replaceAll(']"', ']')
.replaceAll('"[', '[')
.replaceAll('\\"', '"')
.replaceAll('\\\\n', '\\n')
// warn("settings==>"+json, true)
// 写入更新后的设置
const success = file.writeTextSync(path, json);
if (!success) {
throw new Error("写入设置文件失败");
}
}
(async function () {
//启用自动剧情
//dispatcher.addTimer(new RealtimeTimer("AutoSkip"));
@@ -9,6 +39,18 @@ let waitTime = 40;
log.warn("游戏窗口非 1920×1080可能导致图像识别失败如果执意使用可能造成异常后果自负");
await sleep(5000);
}
let executeJson = "execute.json"
let execute = JSON.parse(file.readTextSync(executeJson));
let uid = await uidUtil.ocrUID();
let currentDate = getAdjustedDate();
for (const uidElement of execute) {
if (uidElement.uid === uid && uidElement.date === currentDate) {
log.info("UID:{uid} 今天已经立本过了", uid);
return
}
}
let executeList = execute ? execute : new Array()
while (attempts < 3) {
attempts++;
await pathingScript.runFile(`assets/前往立本.json`);
@@ -36,6 +78,13 @@ let waitTime = 40;
}
}
executeList.push({
uid: uid,
success: success,//冗余
date: currentDate
})
await updateFile(executeList, executeJson)
if (!success) {
log.error("3次重试均失败");
}
@@ -62,8 +111,16 @@ async function findAndClick(target, doClick = true, maxAttempts = 60) {
const rg = captureGameRegion();
try {
const res = rg.find(target);
if (res.isExist()) { await sleep(checkInterval * 2 + 50); if (doClick) { res.click(); } return true; }
} finally { rg.dispose(); }
if (res.isExist()) {
await sleep(checkInterval * 2 + 50);
if (doClick) {
res.click();
}
return true;
}
} finally {
rg.dispose();
}
if (i < maxAttempts - 1) await sleep(checkInterval);
}
return false;

View File

@@ -1,7 +1,7 @@
{
"manifest_version": 1,
"name": "义父立本",
"version": "1.0",
"version": "1.1",
"bgi_version": "0.44.8",
"description": "谢谢老板",
"saved_files": [],
@@ -9,6 +9,10 @@
{
"name": "mno",
"links": "https://github.com/Bedrockx"
},
{
"name": "云端客",
"links": "https://github.com/Kirito520Asuna"
}
],
"settings_ui": "settings.json",

View File

@@ -0,0 +1,145 @@
const commonPath = 'assets/RecognitionObject/'
const commonMap = new Map([
['main_ui', {
path: `${commonPath}`,
name: 'paimon_menu',
type: '.png',
}],
])
const genshinJson = {
width: 1920,//genshin.width,
height: 1080,//genshin.height,
}
/**
* 根据键值获取JSON路径
* @param {string} key - 要查找的键值
* @returns {any} 返回与键值对应的JSON路径值
*/
function getJsonPath(key) {
return commonMap.get(key); // 通过commonMap的get方法获取指定键对应的值
}
function saveOnlyNumber(str) {
str = str ? str : '';
// 使用正则表达式匹配字符串中的所有数字
// \d+ 匹配一个或多个数字
// .join('') 将匹配到的数字数组连接成一个字符串
// parseInt 将连接后的字符串转换为整数
return parseInt(str.match(/\d+/g).join(''));
}
async function ocrUID() {
let uid_json = {
x: 1683,
y: 1051,
width: 234,
height: 28,
}
let recognitionObjectOcr = RecognitionObject.Ocr(uid_json.x, uid_json.y, uid_json.width, uid_json.height);
let region3 = captureGameRegion()
try {
let res = region3.find(recognitionObjectOcr);
log.debug(`[OCR识别UID]识别结果: ${res.text}, 原始坐标: x=${res.x}, y=${res.y},width:${res.width},height:${res.height}`);
//只保留数字
let uid
try {
uid = saveOnlyNumber(res.text)
} catch (e) {
log.debug(`UID未识别`)
uid = 0
}
log.debug(`[OCR识别UID]识别结果: {uid}`, uid);
return uid
}finally {
region3.dispose()
}
}
// 判断是否在主界面的函数
const isInMainUI = () => {
// let name = '主界面'
let main_ui = getJsonPath('main_ui');
// 定义识别对象
let paimonMenuRo = RecognitionObject.TemplateMatch(
file.ReadImageMatSync(`${main_ui.path}${main_ui.name}${main_ui.type}`),
0,
0,
genshinJson.width / 3.0,
genshinJson.width / 5.0
);
let captureRegion = captureGameRegion();
let res = captureRegion.find(paimonMenuRo);
captureRegion.Dispose()
return !res.isEmpty();
};
async function toMainUi() {
let ms = 1000
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
}
}
async function compareUid(UID = settings.uid) {
let uid = await ocrUID()
let setUid = 0
try {
setUid = saveOnlyNumber(UID)
} catch (e) {
// log.warn(`UID未设置`)
}
let compare = uid === setUid
if (compare) {
log.info(`[OCR识别UID]识别结果: {uid} 与设置UID相同`, uid);
}
return compare
}
async function checkUid() {
let reJson = {
inMainUI: false,
isUid: false
}
if (isInMainUI()) {
reJson.isUid = await compareUid()
}
return reJson
}
async function check() {
let check = false
if (settings.uid) {
try {
await toMainUi();
} catch (e) {
log.warn("多次尝试返回主界面失败")
}
let checkJson = await checkUid()
if ((!checkJson.inMainUI) && (!checkJson.isUid)) {
//尝试直接识别
checkJson.isUid = await compareUid()
}
check = checkJson.isUid
}
return check
}
this.uidUtil = {
toMainUi,
isInMainUI,
checkUid,
ocrUID,
check,
compareUid,
}