From 9fdefe24c22bf4fd9302502ccfe625980b7b67a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=91=E7=AB=AF=E5=AE=A2?= <107686912+Kirito520Asuna@users.noreply.github.com> Date: Sat, 25 Apr 2026 12:28:42 +0800 Subject: [PATCH] main-asn (#3156) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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确保资源被正确释放 - 更新了提醒消息文本,明确标识阈值设置参数 --- repo/js/ActivitySwitchNotice/README.md | 3 + .../assets/holyRelics.jpg | Bin 0 -> 1471 bytes repo/js/ActivitySwitchNotice/main.js | 54 ++-- repo/js/ActivitySwitchNotice/manifest.json | 2 +- repo/js/ActivitySwitchNotice/settings.json | 27 ++ .../ActivitySwitchNotice/utils/HolyRelics.js | 59 ++++ .../js/ActivitySwitchNotice/utils/activity.js | 9 +- .../utils/campaignArea.js | 42 +-- .../ActivitySwitchNotice/utils/mapMission.js | 17 +- repo/js/ActivitySwitchNotice/utils/notice.js | 11 +- repo/js/ActivitySwitchNotice/utils/tool.js | 274 +++++++++++++++++- repo/js/ActivitySwitchNotice/utils/uid.js | 69 +---- repo/js/ActivitySwitchNotice/utils/ws.js | 12 +- 13 files changed, 427 insertions(+), 152 deletions(-) create mode 100644 repo/js/ActivitySwitchNotice/assets/holyRelics.jpg create mode 100644 repo/js/ActivitySwitchNotice/utils/HolyRelics.js diff --git a/repo/js/ActivitySwitchNotice/README.md b/repo/js/ActivitySwitchNotice/README.md index 236b39d68..c83ff1e5d 100644 --- a/repo/js/ActivitySwitchNotice/README.md +++ b/repo/js/ActivitySwitchNotice/README.md @@ -511,6 +511,9 @@ ActivitySwitchNotice/ ## 版本历史 +### 0.1.5 (2026-04-24) + +- 新增圣遗物剩余空间检测提醒 ### 0.1.4 (2026-04-23) - 单例模式 配置唯一实例 diff --git a/repo/js/ActivitySwitchNotice/assets/holyRelics.jpg b/repo/js/ActivitySwitchNotice/assets/holyRelics.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1852e9a85a6d683d87dbb07968770eb90da6b13d GIT binary patch literal 1471 zcmex=OKUqj zJ5@awPiGqsb6Y!WMlNn{K3+Z<0Rb6n4J8e0lEME241ydCAq*kRjDieIf{e_9jQ@`? zE&@89l@SaWpn#EynT3^&or9B$TY!Oyk(rr^g_)I=g$1ax7AVKSBFHMFXz0i$9GJ+i zR48K9IB_9|veU+cqCpows2C>|HF0u@iAzXIsj8`KXlj|5nweWzS~We&gn?hmRgVdHU@6i$mSee*R)) zV1{@HNJ2b>K@1BV z9gZ$wU8b;v(Sc2~A;4e>Qvg!{qXV~t)Gzk-b)~N-O`NZ%B3bG2po%MKYNPg)^>6e{ zZg1bZ(`l39@#}Yj{T_q^O<-jZVF)nX!T{9B=aQbVYRTjaTnu09xxAixd&K)^J2lUr z&iMB=XI_wDP5JW}rQ>q5j<^-&m3{lNs=>JFnANo z+=8Lpgz@D*`43m5Ynk4m?wPnZ%dqa@vAn+o}J0|`#MVOmV`~5?&j^z9W@E)UPcEV zpr_9*Kd{R?F6+?S%ZAaPC*S$DV)liM_vu%3-lWZZw@yfb;rH9VyQ{U$0-xX0`Dp&_ z%4ybpVb_!=&VOvTbxm?owCNw^M=uh$`Pv6gPVk>*8NJMwSJQnJ%ej4=y6%aMil0yUJaRdJS0_R1Ks(SQexwKDaLs!@Z(pc}kzy z!^i-(^Vj#LYkZYTPp@8(xvQr-@9nkl{|xim+yA^aRqNXLe4qM35*|sk$JA!W~tUgEeovnDkw;H9B`pChFL>gKWj}^7Gd} zj=a06-|!>fR=c%Thn!lKlLT5ACcewgSS@gKVu->5rUpp|P6z(B@TQb$W?C)tz2<~*J5S*+_pRQe3s|8`_!4Z-n^5u zH&rDl(&eMxky{2!lJhryS#sd%p7=*A5*xp~7Uwta@px6?`10%Oma0wBHovy20Mi|# z2ipZuy5s9}>vAngTC-fCxr^b;diHyfuRPM7UY82xlw@-A2w6)03N!K*>&X|-GP_oM zIyL3f&vQqYCMq79ROBG5wP^inVT*rzvz?q8xDtRd%$&)i!KlHofOkH3RXqtmZ{sZa?&m-W4-k1*)!EAJbjXM&I0+^By { - let captureRegion = captureGameRegion(); - let res = captureRegion.Find(RecognitionObject.TemplateMatch( - file.ReadImageMatSync("assets/paimon_menu.png"), - 0, - 0, - 640, - 216 - )); - captureRegion.dispose(); - return !res.isEmpty(); -}; - -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 - } -} - (async function () { await init(); if (settings.toMainUi) { @@ -159,6 +133,28 @@ async function main() { } } + const checkHolyRelic=settings.checkHolyRelic||false + if (checkHolyRelic) { + const DEFAULT_THRESHOLD = 400 + const raw = settings.holyRelicsDiffCountThreshold + let threshold = DEFAULT_THRESHOLD + try { + const digits = ('' + (raw ?? '')).replace(/[^0-9]/g, '').trim() + const parsed = digits ? parseInt(digits, 10) : NaN + threshold = Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_THRESHOLD + if (!Number.isFinite(parsed)) { + log.warn(`圣遗物剩余空间阈值格式错误,默认 ${DEFAULT_THRESHOLD}`) + } + } catch (e) { + log.warn(`圣遗物剩余空间阈值解析异常,默认 ${DEFAULT_THRESHOLD}: ${e.message}`) + } + try { + await checkHolyRelicsKey(threshold) + }finally { + await toMainUi() + } + } + let openKey = true try { diff --git a/repo/js/ActivitySwitchNotice/manifest.json b/repo/js/ActivitySwitchNotice/manifest.json index 9bbdf014e..e63dbd28b 100644 --- a/repo/js/ActivitySwitchNotice/manifest.json +++ b/repo/js/ActivitySwitchNotice/manifest.json @@ -1,6 +1,6 @@ { "name": "活动期限/周本通知器", - "version": "0.1.3", + "version": "0.1.5", "description": "", "settings_ui": "settings.json", "main": "main.js", diff --git a/repo/js/ActivitySwitchNotice/settings.json b/repo/js/ActivitySwitchNotice/settings.json index d401b7c2a..82017e8ad 100644 --- a/repo/js/ActivitySwitchNotice/settings.json +++ b/repo/js/ActivitySwitchNotice/settings.json @@ -98,6 +98,33 @@ "label": "打开冒险之证按键(不填,默认:F1)", "default": "F1" }, + { + "name": "openBagKey", + "type": "input-text", + "label": "打开背包按键(不填,默认:B)", + "default": "B" + }, + { + "type": "separator" + }, + { + "type": "separator" + }, + { + "type": "separator" + }, + { + "name": "checkHolyRelic", + "type": "checkbox", + "label": "启用圣遗物空间提醒", + "default": false + }, + { + "name": "holyRelicsDiffCountThreshold", + "type": "input-text", + "label": "圣遗物剩余空间阈值", + "default": "400" + }, { "type": "separator" }, diff --git a/repo/js/ActivitySwitchNotice/utils/HolyRelics.js b/repo/js/ActivitySwitchNotice/utils/HolyRelics.js new file mode 100644 index 000000000..092cbe1dc --- /dev/null +++ b/repo/js/ActivitySwitchNotice/utils/HolyRelics.js @@ -0,0 +1,59 @@ +import {toMainUi, openBag, findImgAndClick, OcrFind, findText} from "./tool"; +import {sendText} from "./notice" + +/** + * 检查圣遗物背包中剩余空间是否达到阈值,如果达到阈值则发送提醒 + * @param {number} threshold - 圣遗物数量差阈值,默认为400 + */ +export async function checkHolyRelicsKey(threshold = 400) { + const ms = 300 + log.info("开始圣遗物数量检查") // 记录开始检查圣遗物数量 + // const threshold = settings.threshold || 100 // 注释掉的阈值设置代码 + await openBag() // 打开背包 + await sleep(ms) + const textFind = await findText("圣遗物"); // 查找"圣遗物"文本 + log.debug("textFind:" + textFind) // 记录查找结果 + if (textFind === null) { // 如果未找到"圣遗物"文本 + await sleep(ms) // 等待1秒 + log.info("进入圣遗物背包") // 记录准备进入圣遗物背包 + // 点击圣遗物背包 + const clicked = await findImgAndClick('assets/holyRelics.jpg') + if (!clicked) { + log.error("未能点击进入圣遗物背包,终止检查") + return + } // 通过图片点击进入圣遗物背包 + } + log.info("已进入圣遗物背包") // 记录已进入圣遗物背包 + await sleep(ms) + const OcrText = await OcrFind(1612, 34, 192, 31); // 使用OCR识别指定区域的文本 + if (!(OcrText?.text)) { // 如果OCR识别失败 + log.error("识别异常") // 记录错误信息 + return // 返回,终止函数执行 + } + const text = OcrText.text.trim() // 去除识别文本的前后空格 + const HolyRelics = text.replace(/[^0-9/]/g, '') // 只保留数字和斜杠 + const strings = HolyRelics.split('/', 2); // 按斜杠分割字符串 + if (strings.length < 2) { + log.error(`圣遗物数量解析失败,OCR 原始文本:${text}`) + return + } + const count = parseInt(strings[0], 10);// 解析当前数量 + const total = parseInt(strings[1], 10);// 解析总容量 + if (!Number.isFinite(count) || !Number.isFinite(total)) { + log.error(`圣遗物数量解析异常,count=${count}, total=${total}, 原文本:${text}`) + return + } + const diff = total - count // 计算剩余空间数量 + + log.debug(`text:${text}`) // 记录原始识别文本 + log.debug(`HolyRelics:${HolyRelics}`) // 记录处理后的文本 + log.debug(`count:${count}`) // 记录当前数量 + log.debug(`total:${total}`) // 记录总容量 + log.debug(`diff:${diff}`) // 记录剩余空间数量 + + if (diff <= threshold) { // 如果剩余空间小于等于阈值 + log.info(`背包圣遗物数量:${count}/${total},相差${diff}个,<=${threshold}个,提醒清理`) // 记录需要清理的信息 + // 发送提醒消息 + await sendText(`背包圣遗物 剩余空间不足(设置阈值):${threshold},剩余:${diff},请前往清理!`, "背包圣遗物剩余空间检查") // 发送提醒文本 + } +} \ No newline at end of file diff --git a/repo/js/ActivitySwitchNotice/utils/activity.js b/repo/js/ActivitySwitchNotice/utils/activity.js index 753ec68ec..e2133a1d5 100644 --- a/repo/js/ActivitySwitchNotice/utils/activity.js +++ b/repo/js/ActivitySwitchNotice/utils/activity.js @@ -464,7 +464,7 @@ async function init() { /** * 活动主函数:扫描所有活动页面,识别剩余时间,最后统一发送通知 */ - async function activityMain(newActivityNotice = true) { +export async function activityMain(newActivityNotice = true) { await init(); const ms = 1000; await sleep(ms); @@ -781,9 +781,4 @@ async function init() { } } -// this.activityUtil = { -// // config, -// activityMain, -// // OcrRemainingTime, -// } -export {activityMain} +// export {activityMain} diff --git a/repo/js/ActivitySwitchNotice/utils/campaignArea.js b/repo/js/ActivitySwitchNotice/utils/campaignArea.js index 05cbd5cd8..862cde21a 100644 --- a/repo/js/ActivitySwitchNotice/utils/campaignArea.js +++ b/repo/js/ActivitySwitchNotice/utils/campaignArea.js @@ -35,7 +35,7 @@ const xyConfig = { * @param {Object} ocrRegion - OCR识别区域配置,默认为ocrRegionConfig.dailyCommission * @returns {Object} 返回包含每日委托和体力使用情况的对象 */ -async function ocrDailyCommission(ocrRegion = ocrRegionConfig.dailyCommission) { +export async function ocrDailyCommission(ocrRegion = ocrRegionConfig.dailyCommission) { let captureRegion = captureGameRegion(); // 获取游戏区域截图 try { const ocrObject = RecognitionObject.Ocr(ocrRegion.x, ocrRegion.y, ocrRegion.width, ocrRegion.height); // 创建OCR识别对象 @@ -74,7 +74,7 @@ async function ocrDailyCommission(ocrRegion = ocrRegionConfig.dailyCommission) { * @returns {Object} 返回包含周计数信息的JSON对象,包含text、total和count属性 * @throws {Error} 当OCR识别失败时抛出错误 */ -async function ocrWeeklyCount(ocrRegion = ocrRegionConfig.weeklyCount) { +export async function ocrWeeklyCount(ocrRegion = ocrRegionConfig.weeklyCount) { let captureRegion = captureGameRegion(); // 获取游戏区域截图 try { const ocrObject = RecognitionObject.Ocr(ocrRegion.x, ocrRegion.y, ocrRegion.width, ocrRegion.height); // 创建OCR识别对象 @@ -104,37 +104,12 @@ async function ocrWeeklyCount(ocrRegion = ocrRegionConfig.weeklyCount) { } } -// /** -// * 获取当前日期的星期信息 -// * @param {boolean} [calibrationGameRefreshTime=true] 是否进行游戏刷新时间校准 -// * @returns {Object} 返回包含星期数字和星期名称的对象 -// */ -// 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 -// } -// } /** * 执行秘境征讨剩余次数提醒的主函数 * 该函数会在每周日执行,检查秘境征讨的剩余次数并发送提醒 */ -async function campaignAreaMain(openKey = true) { +export async function campaignAreaMain(openKey = true) { // 获取当前星期信息 let dayOfWeek = await getDayOfWeek(); // 如果不是周日(0代表周日),则直接返回 @@ -184,7 +159,7 @@ async function campaignAreaMain(openKey = true) { * @param {boolean} openKey - 是否开启热键功能,默认为true * @returns {Promise} */ -async function dailyCommissionMain(openKey = true) { +export async function dailyCommissionMain(openKey = true) { // 获取当前星期信息 let dayOfWeek = await getDayOfWeek(); // 如果不是周日(0代表周日), @@ -213,12 +188,7 @@ async function dailyCommissionMain(openKey = true) { } } -// this.campaignAreaUtil = { +// export { // campaignAreaMain, // dailyCommissionMain, -// } - -export { - campaignAreaMain, - dailyCommissionMain, -} \ No newline at end of file +// } \ No newline at end of file diff --git a/repo/js/ActivitySwitchNotice/utils/mapMission.js b/repo/js/ActivitySwitchNotice/utils/mapMission.js index 5764904fa..fbfdd5a9e 100644 --- a/repo/js/ActivitySwitchNotice/utils/mapMission.js +++ b/repo/js/ActivitySwitchNotice/utils/mapMission.js @@ -12,7 +12,7 @@ const ocrRegionConfig = { * @param {Object} [regionConfig=ocrRegionConfig.mapMission] - OCR识别区域配置对象,包含x、y、width、height属性 * @returns {Promise>} 返回识别结果数组,每个元素包含ok(boolean)和text(string)属性 */ -async function ocrMapMission(missionNameList = [], regionConfig = ocrRegionConfig.mapMission) { +export async function ocrMapMission(missionNameList = [], regionConfig = ocrRegionConfig.mapMission) { let jsonList = []; let region = null; @@ -69,13 +69,13 @@ async function ocrMapMission(missionNameList = [], regionConfig = ocrRegionConfi //伴月纪闻任务待完成 // 通过地图识别任务 -async function openMap() { +export async function openMap() { const key = settings.mapKey || 'M' await sleep(200) await keyPress(key) } -async function mapMission(list = [], toOpenMap = true) { +export async function mapMission(list = [], toOpenMap = true) { let ms = 600 if (toOpenMap) { await openMap(); @@ -95,14 +95,9 @@ async function mapMission(list = [], toOpenMap = true) { await sendText(text, `UID:${uid}\n地图任务`) } -// this.mapUtil = { + +// export { // mapMission, // ocrMapMission, // openMap, -// } - -export { - mapMission, - ocrMapMission, - openMap, -} \ No newline at end of file +// } \ No newline at end of file diff --git a/repo/js/ActivitySwitchNotice/utils/notice.js b/repo/js/ActivitySwitchNotice/utils/notice.js index a5eef3440..062cb7147 100644 --- a/repo/js/ActivitySwitchNotice/utils/notice.js +++ b/repo/js/ActivitySwitchNotice/utils/notice.js @@ -11,10 +11,14 @@ const NoticeMap = new Map([ ['独立通知', [{type: NoticeType.independence}]], ['独立通知和BGI通知', [{type: NoticeType.independence}, {type: NoticeType.bgi}]], ]) -const configNotice = { +let configNotice = { noticeList: NoticeMap.get(settings.noticeType), } - +async function buildConfigNotice() { + configNotice = { + noticeList: NoticeMap.get(settings.noticeType), + } +} /** * 发送通知的异步函数 * @param {Map} map - 包含通知内容键值对的Map对象 @@ -23,7 +27,7 @@ const configNotice = { */ async function sendNotice(map = new Map(), title, noNotice = false) { log.debug(`sendNotice: map.size=${map.size}, noNotice=${noNotice}`); - + await buildConfigNotice() // 如果设置了不发送通知且map为空,则记录日志并返回 if ((map.size <= 0) || noNotice) { log.debug(`if sendNotice: map.size=${map.size}, noNotice=${noNotice}`); @@ -67,6 +71,7 @@ async function sendText(noticeText, title, noNotice = false) { log.info(`sendText 无通知内容`) // 记录日志信息 return // 直接返回,不执行后续操作 } + await buildConfigNotice() // 构建通知文本,如果有标题则先添加标题 let text = title ? title + "\n======\n" : "\n" // 添加通知内容 diff --git a/repo/js/ActivitySwitchNotice/utils/tool.js b/repo/js/ActivitySwitchNotice/utils/tool.js index 56cf203af..96dd232ae 100644 --- a/repo/js/ActivitySwitchNotice/utils/tool.js +++ b/repo/js/ActivitySwitchNotice/utils/tool.js @@ -1,3 +1,54 @@ +/** + * 通用找文本(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 目标文本(单个文本) @@ -13,7 +64,7 @@ * @returns * - RecognitionResult | null */ -async function findTextAndClick( +export async function findTextAndClick( text, x = 0, y = 0, @@ -54,12 +105,177 @@ async function findTextAndClick( 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} 返回找到的文本,如果没有找到则返回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} 返回找到的文本内容数组 + */ +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} 返回包含星期数字和星期名称的对象 */ -async function getDayOfWeek(calibrationGameRefreshTime = true) { +export async function getDayOfWeek(calibrationGameRefreshTime = true) { // 获取当前日期对象 let today = new Date();//4点刷新 所以要减去4小时 if (calibrationGameRefreshTime) { @@ -79,4 +295,56 @@ async function getDayOfWeek(calibrationGameRefreshTime = true) { dayOfWeek: weekDay } } -export { findTextAndClick,getDayOfWeek} \ No newline at end of file + +// 判断是否在主界面的函数 +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); +} diff --git a/repo/js/ActivitySwitchNotice/utils/uid.js b/repo/js/ActivitySwitchNotice/utils/uid.js index 5104edb4f..163738dbd 100644 --- a/repo/js/ActivitySwitchNotice/utils/uid.js +++ b/repo/js/ActivitySwitchNotice/utils/uid.js @@ -1,3 +1,4 @@ +/* const commonPath = 'assets/' const commonMap = new Map([ ['main_ui', { @@ -11,16 +12,17 @@ const genshinJson = { height: 1080,//genshin.height, } -/** +/!** * 根据键值获取JSON路径 * @param {string} key - 要查找的键值 * @returns {any} 返回与键值对应的JSON路径值 - */ + *!/ function getJsonPath(key) { return commonMap.get(key); // 通过commonMap的get方法获取指定键对应的值 } - -function saveOnlyNumber(str) { +*/ +import {toMainUi,isInMainUI} from "./tool" +export function saveOnlyNumber(str) { str = str ? str : ''; // 使用正则表达式匹配字符串中的所有数字 // \d+ 匹配一个或多个数字 @@ -29,7 +31,7 @@ function saveOnlyNumber(str) { return parseInt(str.match(/\d+/g).join('')); } -async function ocrUID() { +export async function ocrUID() { let uid_json = { x: 1683, y: 1051, @@ -57,41 +59,7 @@ async function ocrUID() { } } -// 判断是否在主界面的函数 -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) { +export async function compareUid(UID = settings.uid) { let uid = await ocrUID() let setUid = 0 try { @@ -106,7 +74,7 @@ async function compareUid(UID = settings.uid) { return compare } -async function checkUid() { +export async function checkUid() { let reJson = { inMainUI: false, isUid: false @@ -117,7 +85,7 @@ async function checkUid() { return reJson } -async function check() { +export async function check() { let check = false if (settings.uid) { try { @@ -135,19 +103,12 @@ async function check() { return check } -// this.uidUtil = { -// toMainUi, -// isInMainUI, + +// export { +// // toMainUi, +// // isInMainUI, // checkUid, // ocrUID, // check, // compareUid, -// } -export { - toMainUi, - isInMainUI, - checkUid, - ocrUID, - check, - compareUid, -} \ No newline at end of file +// } \ No newline at end of file diff --git a/repo/js/ActivitySwitchNotice/utils/ws.js b/repo/js/ActivitySwitchNotice/utils/ws.js index bb1f8ad13..72d573807 100644 --- a/repo/js/ActivitySwitchNotice/utils/ws.js +++ b/repo/js/ActivitySwitchNotice/utils/ws.js @@ -164,7 +164,7 @@ export async function pullAccessWsProxyConfig(uid, http_api) { * @param {Array} atList - @用户列表 * @returns {Promise} 无返回值 */ -async function send(wsProxyUrl, wsUrl, wsToken, action, group_id, user_id, textList, atList) { +export async function send(wsProxyUrl, wsUrl, wsToken, action, group_id, user_id, textList, atList) { const uid = local.uid // 构建基础JSON对象 let json = { @@ -239,7 +239,7 @@ async function send(wsProxyUrl, wsUrl, wsToken, action, group_id, user_id, textL } } -async function sendText(text) { +export async function sendText(text) { await init(); let action = configWs.action; let group_id = configWs.group_id; @@ -253,11 +253,7 @@ async function sendText(text) { await send(wsProxyUrl, wsUrl, ws_token, action, group_id, user_id, textList, atList) } -// this.wsUtil = { +// export { // send, // sendText -// } -export { - send, - sendText -} \ No newline at end of file +// } \ No newline at end of file