From cac08d4f158b883a433015621c8b1ab7b766c263 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: Tue, 23 Dec 2025 18:23:58 +0800 Subject: [PATCH] =?UTF-8?q?[=E6=B4=BB=E5=8A=A8=E6=9C=9F=E9=99=90/=E5=91=A8?= =?UTF-8?q?=E6=9C=AC=E9=80=9A=E7=9F=A5=E5=99=A8]=20=E8=BF=AD=E4=BB=A3=200.?= =?UTF-8?q?0.2=20=E7=89=88=E6=9C=AC=20(#2552)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(ActivitySwitchNotice): 添加异步通知发送功能 - 新增异步发送通知函数 send,支持标题和内容拼接 - 添加通知发送前的日志记录和条件判断 - 导出新的 send 函数供外部调用 - 保留原有 sendNotice 函数兼容性 * feat(activity): 新增征讨领域次数识别与通知功能 - 实现征讨领域 OCR 识别逻辑,用于获取每周剩余次数 - 添加秘境与征讨领域的点击坐标配置 - 集成通知工具,发送剩余次数提醒 - 支持自动按键进入活动界面并执行点击操作 - 增加星期判断逻辑,跳过周日执行 - 提供字符串与整数解析工具函数,增强配置容错性 * fix(campaignArea): 修复周日不执行秘境征讨提醒的问题 - 将判断条件从等于0修改为不等于0,确保周日可以执行提醒逻辑 - 添加日志记录,便于追踪执行情况和调试 - 保留原有的延迟和按键操作逻辑 * feat(activity): 添加活动切换通知功能 - 引入 campaignArea.js 工具模块 - 在主流程中调用 toMainUi 函数 - 执行活动区域主逻辑处理 - 整合活动切换与通知机制 - 增强主界面判断逻辑 - 优化异步流程控制 * feat(activity): 实现秘境征讨剩余次数提醒功能 - 添加了每周日自动检查秘境征讨剩余次数的功能 - 实现了通过OCR识别剩余次数的逻辑 - 集成了日志记录和通知发送机制 - 添加了完整的操作延迟和点击坐标配置 - 实现了热键触发和界面点击的自动化流程 - 增加了详细的函数注释和执行日志 * feat(campaignArea): 更新征讨领域坐标并优化OCR识别逻辑 - 调整征讨领域点击坐标为{x: 493, y: 537} - 新增ocrWeeklyCount函数用于OCR识别周计数信息 - 增强OCR识别后的文本处理与日志记录 - 修复周日判断逻辑,确保仅在周日执行特定操作 - 调整主流程顺序,先执行征讨领域再返回主界面 * refactor(campaignArea): 优化星期名称获取逻辑 - 提取星期名称到独立变量以提高可读性 - 更新日志记录以使用新的变量名 - 确保返回对象中的星期名称正确引用新变量 * feat(ActivitySwitchNotice): 新增征讨领域每周提醒功能 - 在 README 中新增“每周日自动提醒征讨领域剩余次数”特性说明 - 更新 settings.json 配置项表格,增加 toTopCount、scrollPageCount 和 campaignAreaKey 参数 - 新增 campaignArea.js 模块,实现 OCR 识别与周日提醒逻辑 - 调整目录结构说明,加入 campaignArea.js 文件介绍 - 修改工作原理部分,补充征讨领域提醒的执行流程 - 统一 README 中代码引用格式为反引号包裹 * docs(ActivitySwitchNotice): 更新 README 版本历史记录 - 新增 0.0.2 版本的征讨领域周次数提醒功能 - 新增 campaignArea.js 模块及相关配置选项 - 改进 滚动到顶部功能的稳定性并新增相关配置 - 新增 0.1 版本的活动检测、OCR识别及通知功能 - 新增 多种智能解析与防重复检测机制 - 新增 异常处理和错误恢复机制 * fix(ActivitySwitchNotice): 调整日志级别与周日判断逻辑 - 将 info 级别日志调整为 debug 级别 - 修正周日判断条件,确保仅周日执行提醒 - 增强周日判断日志描述 - 更新剩余次数提示文案,明确显示“本周剩余消耗减半次数” - 在通知消息前添加 Markdown 格式符号 `>` 以突出显示 * refactor(campaignArea): 将日志级别从 info 调整为 debug - 修改日志记录方式,将 info 级别调整为 debug - 减少生产环境中的日志噪音 - 提高调试信息的可读性与准确性 * feat(ActivitySwitchNotice): 新增征讨领域模块和配置选项 - 新增 campaignArea.js 模块,包含征讨领域相关功能 - 新增 campaignAreaKey 配置选项,用于自定义征讨领域页面快捷键 - 改进增强滚动到顶部功能的稳定性 - 新增 toTopCount 和 scrollPageCount 配置选项,提供更多滚动控制参数 - 新增活动期限检测与通知功能 - 新增 OCR 识别活动列表和剩余时间功能 * docs: 更新活动通知器功能说明 * feat(ActivitySwitchNotice): 支持征讨领域周次数提醒功能 - 更新插件名称以明确支持活动期限与周本提醒 - 提升版本号至 0.0.2 - 新增 campaignAreaKey 配置项用于自定义征讨领域页面快捷键 - 在 README 中更新 campaignAreaKey 的使用状态为启用 - 更新版本历史记录日期及新增功能说明 - 新增 campaignArea.js 模块实现相关功能逻辑 * feat(ActivitySwitchNotice): 支持自定义征讨领域提醒日 - 新增配置项 campaignAreaReminderDay,用于设置提醒日期 - 修改判断逻辑,使用配置的提醒日替代固定周日判断 - 添加相关注释说明配置用途 * feat(settings): 添加周本提醒日设置选项 - 在设置中新增周本提醒日选择器 - 支持设置提醒日为周日至周六任意一天 - 默认值设为周日 - 保留原有冒险之证按键设置功能 * docs: 更新文档,新增征讨领域提醒日配置选项说明 * fix: 修改周本提醒日配置值为字符串格式 * feat(activity): 添加活动描述字段支持 - 在活动映射中新增 desc 字段,默认值为 null - 更新通知文本生成逻辑,支持显示活动描述信息 - 优化剩余时间文本格式,增强可读性 - 保持现有功能兼容性,不影响无描述场景显示 * feat(activity): 增加活动时间转换和OCR功能 - 添加日期枚举类型DATE_ENUM及反向映射方法 - 新增活动周期转换映射表activityTermConversionMap - 新增特定活动OCR内容映射表needOcrOtherMap - 实现根据活动名称获取日期枚举值的函数getDATE_ENUM - 添加将总小时数转换为周/天/小时格式的函数convertHoursToWeeksDaysHours - 在活动时间处理中增加对不同时间单位的支持 - 增加对特定活动额外OCR识别内容的支持 - 修复数组遍历时的缩进问题 * feat(activity): 更新活动时间显示逻辑 - 修改"砺行修远"活动的时间枚举为周 - 调整剩余时间文本的显示格式 - 优化通知文本的排版和分隔符 - 增强日期枚举获取函数的返回值结构 - 添加调试日志用于追踪活动时间和枚举值 - 改进OCR识别时间的显示方式 * feat(ActivitySwitchNotice): 添加黑名单活动名称过滤功能 - 在配置中新增 blackActivityNameList 字段,支持通过 | 分割多个活动名称 - 实现活动黑名单过滤逻辑,排除黑名单中的活动名称 - 更新设置界面,增加黑名单活动名称输入框 - 完善活动筛选流程,优先过滤黑名单活动再判断剩余时间阈值 * feat(ActivitySwitchNotice): 新增活动黑名单过滤功能 - 在 settings.json 中新增 blackActivityNameList 配置项 - 支持通过黑名单排除不关心的活动提醒 - 更新文档说明,添加黑名单使用示例 - 增强活动过滤逻辑,提高匹配准确性 - 在核心扫描流程中集成黑名单过滤机制 - 优化通知显示格式,增加活动描述信息 - 修复若干已知问题,提升脚本稳定性 * fix(activity): 修复活动过滤逻辑 - 修改黑名单活动名称过滤方式,从完全匹配改为包含匹配 - 确保活动名称中包含黑名单关键词时能被正确过滤 - 保持小时数阈值过滤逻辑不变 - 维持扫描完成后统一发送通知的机制 * fix(activity): 修复活动黑名单过滤逻辑及通知文本 - 修正黑名单关键词过滤条件判断 - 优化通知消息文本格式,增加黑名单提示信息 * feat(activity): 支持多个OCR识别键值 - 修改needOcrOtherMap结构以支持数组形式的键值 - 更新OCR识别逻辑以遍历多个键值并拼接结果 - 为"砺行修远"活动添加"完成进度"作为新的OCR识别目标 * fix(activity): 修复OCR时间和活动过滤逻辑 - 修复OCR剩余时间函数调用参数错误,从keys改为key - 优化活动黑名单过滤逻辑,提高过滤准确性 - 增强活动名称关键字匹配的判断条件 - 修复过滤器提前返回导致的逻辑中断问题 * fix(ActivitySwitchNotice): 优化活动黑名单过滤和日期枚举匹配逻辑 - 黑名单活动名称过滤时增加去除空字符串逻辑 - 日期枚举匹配改为模糊包含匹配,提升识别准确率 - 修复黑名单提示条件判断错误导致的消息格式问题 * docs: 更新活动模块文档,添加配置项说明 * feat(activity): 添加黑名单活动名称过滤功能 - 在活动点击前增加黑名单关键词匹配逻辑 - 跳过匹配黑名单的活动,避免无效点击 - 移除原有冗余的活动过滤逻辑 - 优化活动重复点击判断流程 * style(docs): 格式化 README.md 中的表格样式 - 调整表格列对齐方式,使用冒号对齐格式 - 统一表格分隔符的格式和间距 - 修复表格列宽和对齐问题 - 优化表格的视觉呈现效果 --- repo/js/ActivitySwitchNotice/README.md | 119 ++++++++++++----- repo/js/ActivitySwitchNotice/main.js | 3 + repo/js/ActivitySwitchNotice/manifest.json | 4 +- repo/js/ActivitySwitchNotice/settings.json | 23 +++- .../js/ActivitySwitchNotice/utils/activity.js | 94 +++++++++++++- .../utils/campaignArea.js | 121 ++++++++++++++++++ repo/js/ActivitySwitchNotice/utils/notice.js | 26 +++- 7 files changed, 350 insertions(+), 40 deletions(-) create mode 100644 repo/js/ActivitySwitchNotice/utils/campaignArea.js diff --git a/repo/js/ActivitySwitchNotice/README.md b/repo/js/ActivitySwitchNotice/README.md index 305d6431e..90e5d6ba0 100644 --- a/repo/js/ActivitySwitchNotice/README.md +++ b/repo/js/ActivitySwitchNotice/README.md @@ -1,8 +1,8 @@ -# 活动期限通知器 +# 活动期限/周本通知器 ## 项目概述 -这是一个用于《原神》游戏的自动化脚本工具,主要功能是自动检测游戏内活动的剩余时间,并在活动即将结束时发送通知提醒玩家。 +这是一个用于《原神》游戏的自动化脚本工具,主要功能是自动检测游戏内活动的剩余时间,并在活动即将结束时/每周指定日期自动提醒征讨领域减半剩余次数发送通知提醒玩家。 ## 功能特性 @@ -12,8 +12,10 @@ - ✅ 智能解析剩余时间(支持"22天14小时"等格式) - ✅ 可配置的通知阈值(默认8760小时内结束的活动) - ✅ 支持指定特定活动进行监控 +- ✅ 支持活动黑名单过滤功能 - ✅ 防重复检测机制 - ✅ 异常处理和错误恢复 +- ✅ 自动提醒征讨领域减半剩余次数(默认`周日`提醒可配置) --- @@ -22,23 +24,31 @@ ### 快速开始 #### 1. 安装与配置 + - 确保游戏分辨率为 **1920×1080**(推荐分辨率) - 将脚本导入到 BetterGI 脚本管理器中 - 在脚本设置界面进行个性化配置 #### 2. 基础设置 -在 [settings.json]() 中可以配置以下参数: -| 设置项 | 说明 | 默认值 | -|--------|------|--------| -| `toMainUi` | 执行前是否自动返回游戏主界面 | true | -| `activityNameList` | 监控的特定活动名称(用\|分隔) | 空(监控所有活动) | -| `notifyHoursThreshold` | 通知时间阈值(小时) | 8760(365天) | -| `activityKey` | 打开活动页面的快捷键 | F5 | +在 `settings.json` 中可以配置以下参数: + +| 设置项 | 说明 | 默认值 | 开放 | +|:---------------------------|:-----------------------------------------------|:------------|:--:| +| `toMainUi` | 执行前是否自动返回游戏主界面 | true | v | +| `activityNameList` | 监控的特定活动名称(用\|分隔) | 空(监控所有活动) | v | +| `blackActivityNameList` | 黑名单活动名称(用\|分隔) | 空(无黑名单活动) | v | +| `notifyHoursThreshold` | 通知时间阈值(小时) | 8760(365天) | v | +| `activityKey` | 打开活动页面的快捷键 | F5 | v | +| `toTopCount` | 滑动到顶最大尝试次数 | 10 | x | +| `scrollPageCount` | 滑动次数/页 | 4 | x | +| `campaignAreaKey` | 打开征讨领域页面的快捷键 | F1 | v | +| `campaignAreaReminderDay` | 周本提醒日(0-6,0=周日,1=周一,2=周二,3=周三,4=周四,5=周五,6=周六) | 0 | v | ### 使用流程 #### 自动模式(推荐) + 1. 启动脚本后,程序会自动: - 检测当前是否在游戏主界面 - 如未在主界面,自动返回主界面 @@ -46,6 +56,7 @@ - 开始扫描所有活动 #### 手动模式 + 1. 关闭 `toMainUi` 选项 2. 确保游戏处于主界面状态 3. 启动脚本开始扫描 @@ -53,15 +64,19 @@ ### 功能详解 #### 活动筛选 + - **全部活动监控**:`activityNameList` 保持空值,监控所有有剩余时间的活动 - **指定活动监控**:填写活动关键词,如 `海灯节\|盛典`,只监控包含这些关键词的活动 +- **黑名单过滤**:`blackActivityNameList` 可以设置不想接收提醒的活动名称,多个活动用`|`分隔 #### 时间通知机制 + - 默认监控所有活动(`notifyHoursThreshold`=8760小时) - 可设置阈值,如设置为24,则只通知剩余时间≤24小时的活动 -- 即将结束的活动会在通知中标记 `<即将结束>` +- 即将结束(24小时内)的活动会在通知中标记 `<即将结束>` #### 智能防重复 + - 自动识别已扫描过的活动页面 - 防止因页面滚动不准确造成的重复识别 - 自动判断是否已滚动到页面底部 @@ -69,11 +84,13 @@ ### 注意事项 #### 使用环境要求 + - ✅ 游戏分辨率为 1920×1080(最佳兼容性) - ✅ 游戏处于前台运行状态 - ✅ 活动页面可通过设置的快捷键正常打开 #### 运行期间注意事项 + - 🚫 运行期间请勿手动操作鼠标 - 🚫 避免切换窗口或最小化游戏 - ⚠️ 如遇异常可重新启动脚本 @@ -81,10 +98,14 @@ ### 高级配置 #### 自定义快捷键 + 如活动页面不是F5打开,可在 `activityKey` 中修改为对应按键。 +如冒险之书页面不是F1打开,可在 `campaignAreaKey` 中修改为对应按键。 #### 时间阈值设置 + 根据个人需求设置 `notifyHoursThreshold`: + - 24:只关注24小时内结束的活动 - 168:关注一周内结束的活动 - 720:关注一个月内结束的活动 @@ -92,22 +113,25 @@ ### 输出示例 通知消息格式如下: + ``` 原神活动剩余时间提醒: > 海灯节庆典 剩余时间:3天14小时<还剩 86 小时> > 风花节活动 剩余时间:1天5小时<还剩 29 小时><即将结束> ``` + + **`以上为用户使用指南全部内容`** --- - ## 文件结构 ``` ActivitySwitchNotice/ ├── utils/ │ ├── activity.js # 核心活动处理逻辑 +│ ├── campaignArea.js # 征讨领域提醒功能 │ └── notice.js # 通知发送功能 ├── main.js # 主入口文件 ├── manifest.json # 插件配置文件 @@ -116,9 +140,10 @@ ActivitySwitchNotice/ ``` + ## 核心模块 -### [activity.js]() - 活动处理核心 +### `activity.js` - 活动处理核心 主要包含以下功能函数: @@ -132,33 +157,40 @@ ActivitySwitchNotice/ ### `notice.js` - 通知模块 - `sendNotice()` - 发送活动提醒通知,按剩余时间排序 +- `send()` - 发送普通通知 -### [main.js]() - 主程序入口 +### `campaignArea.js` - 征讨领域模块 -- 检测是否在主界面 -- 返回主界面功能 -- 执行主流程 +- `ocrWeeklyCount()` - OCR识别征讨领域周次数 +- `getDayOfWeek()` - 获取当前星期信息 +- `campaignAreaMain()` - 征讨领域提醒主函数 ## 配置选项 -在 [settings.json]() 中可配置以下参数: +在 `settings.json` 中可配置以下参数: -| 配置项 | 类型 | 说明 | -|--------|------|------| -| `toMainUi` | Boolean | 是否先返回主界面再执行 | -| `activityNameList` | String | 指定活动名称(用\|分隔) | -| `notifyHoursThreshold` | Number | 通知阈值(小时) | -| `activityKey` | String | 打开活动页面的快捷键 | +| 配置项 | 类型 | 说明 | +|:---------------------------|:-------:|:--| +| `toMainUi` | Boolean | 是否先返回主界面再执行 | +| `activityNameList` | String | 指定活动名称(用\|分隔) | +| `blackActivityNameList` | String | 黑名单活动名称(用\|分隔) | +| `notifyHoursThreshold` | Number | 通知阈值(小时) | +| `activityKey` | String | 打开活动页面的快捷键 | +| `toTopCount` | Number | 滑动到顶最大尝试次数 | +| `scrollPageCount` | Number | 滑动次数/页 | +| `campaignAreaKey` | String | 打开冒险之书页面的快捷键 | +| `campaignAreaReminderDay` | Number | 周本提醒日(0-6,0=周日,1=周一,2=周二,3=周三,4=周四,5=周五,6=周六) | ## 工作原理 1. 自动返回游戏主界面 -2. 按配置快捷键打开活动页面 -3. 滚动到活动列表顶部 -4. 逐页扫描所有活动 -5. OCR识别每个活动的剩余时间 -6. 解析时间为小时数并过滤 -7. 发送符合条件的活动提醒 +2. 检查是否为设置的提醒日(默认周日),如果是则执行征讨领域提醒功能 +3. 按配置快捷键打开活动页面 +4. 滚动到活动列表顶部 +5. 逐页扫描所有活动 +6. OCR识别每个活动的剩余时间 +7. 解析时间为小时数并过滤(包括黑名单过滤) +8. 发送符合条件的活动提醒 ## 注意事项 @@ -169,6 +201,35 @@ ActivitySwitchNotice/ --- +## 版本历史 + +### 0.0.2 (2025-12-22) + +- 新增 征讨领域周次数提醒功能 +- 新增 `campaignArea.js` 模块,包含征讨领域相关功能 +- 新增 `campaignAreaKey` 配置选项,用于自定义冒险之书页面快捷键 +- 新增 `campaignAreaReminderDay` 配置选项,用于配置提醒日 +- 改进 增强滚动到顶部功能的稳定性 +- 新增 活动黑名单过滤功能,支持通过 `blackActivityNameList` 配置项排除不关心的活动 +- 新增 特殊活动时间格式支持,针对"砺行修远"等活动提供周数显示 +- 新增 额外OCR识别支持,可识别特定活动的附加信息(如"本周进度") +- 改进 活动过滤逻辑,增强黑名单匹配准确性 +- 改进 通知显示格式,增加活动描述信息展示 +- 修复 若干已知问题,提升脚本稳定性 + +### 0.0.1 (2025-12-21) + +- 新增 活动期限检测与通知功能 +- 新增 OCR识别活动列表和剩余时间 +- 新增 自动滚动浏览所有活动页面 +- 新增 智能解析剩余时间(支持"22天14小时"等格式) +- 新增 可配置的通知阈值功能 +- 新增 指定特定活动监控功能 +- 新增 防重复检测机制 +- 新增 异常处理和错误恢复机制 + +--- + ## 其它 作者:云端客 diff --git a/repo/js/ActivitySwitchNotice/main.js b/repo/js/ActivitySwitchNotice/main.js index cf88b1920..5a647dd23 100644 --- a/repo/js/ActivitySwitchNotice/main.js +++ b/repo/js/ActivitySwitchNotice/main.js @@ -1,5 +1,6 @@ eval(file.readTextSync(`utils/activity.js`)); eval(file.readTextSync(`utils/notice.js`)); +eval(file.readTextSync(`utils/campaignArea.js`)); // 判断是否在主界面的函数 const isInMainUI = () => { @@ -42,5 +43,7 @@ async function toMainUi() { * @returns {Promise} */ async function main() { + await campaignAreaUtil.campaignAreaMain() + await toMainUi() await activityUtil.activityMain() } \ No newline at end of file diff --git a/repo/js/ActivitySwitchNotice/manifest.json b/repo/js/ActivitySwitchNotice/manifest.json index 67e54cb94..36a15f237 100644 --- a/repo/js/ActivitySwitchNotice/manifest.json +++ b/repo/js/ActivitySwitchNotice/manifest.json @@ -1,6 +1,6 @@ { - "name": "活动期限通知器", - "version": "0.0.1", + "name": "活动期限/周本通知器", + "version": "0.0.2", "description": "", "settings_ui": "settings.json", "main": "main.js", diff --git a/repo/js/ActivitySwitchNotice/settings.json b/repo/js/ActivitySwitchNotice/settings.json index e2d093dec..289b46f59 100644 --- a/repo/js/ActivitySwitchNotice/settings.json +++ b/repo/js/ActivitySwitchNotice/settings.json @@ -10,6 +10,11 @@ "type": "input-text", "label": "活动名称(使用|分割)<可不填 默认推送所有有剩余时间的活动>" }, + { + "name": "blackActivityNameList", + "type": "input-text", + "label": "黑名单活动名称(使用|分割)<可不填,默认没有不推送的活动>" + }, { "name": "notifyHoursThreshold", "type": "input-text", @@ -21,5 +26,21 @@ "type": "input-text", "label": "打开活动页面按键(不填,默认:F5)", "default": "F5" - } + }, + { + "name": "campaignAreaReminderDay", + "type": "select", + "label": "周本提醒日(0-6,0=周日,1=周一,2=周二,3=周三,4=周四,5=周五,6=周六)", + "options": [ + "0","1","2","3","4","5","6" + ], + "default": "0" + }, + { + "name": "campaignAreaKey", + "type": "input-text", + "label": "打开冒险之证按键(不填,默认:F1)", + "default": "F1" + }, + ] diff --git a/repo/js/ActivitySwitchNotice/utils/activity.js b/repo/js/ActivitySwitchNotice/utils/activity.js index 1698768f9..1d56832a4 100644 --- a/repo/js/ActivitySwitchNotice/utils/activity.js +++ b/repo/js/ActivitySwitchNotice/utils/activity.js @@ -10,9 +10,10 @@ function settingsParseInt(str, defaultValue) { const config = { activityNameList: (settings.activityNameList ? settings.activityNameList.split('|') : []), activityKey: (settings.activityKey ? settings.activityKey : 'F5'), - toTopCount: settingsParseInt(settings.toTopCount,10),//滑动到顶最大尝试次数 - scrollPageCount: settingsParseInt(settings.scrollPageCount,4),//滑动次数/页 + toTopCount: settingsParseInt(settings.toTopCount, 10),//滑动到顶最大尝试次数 + scrollPageCount: settingsParseInt(settings.scrollPageCount, 4),//滑动次数/页 notifyHoursThreshold: settingsParseInt(settings.notifyHoursThreshold, 8760),//剩余时间阈值(默认 8760小时=365天) + blackActivityNameList: (settings.blackActivityNameList ? settings.blackActivityNameList.split('|').filter(s => s.trim()) : []),//黑名单活动名称 } const ocrRegionConfig = { activity: {x: 267, y: 197, width: 226, height: 616},//活动识别区域坐标和尺寸 @@ -22,12 +23,37 @@ const xyConfig = { top: {x: 344, y: 273}, bottom: {x: 342, y: 791}, } - +const DATE_ENUM = Object.freeze({ + YEAR: '年', + MON: '月', + WEEK: '周', + DAY: '天', + HOUR: '小时', + // 添加反向映射(可选) + fromValue(value) { + return Object.keys(this).find(key => this[key] === value); + } +}); +const activityTermConversionMap = new Map([ + ["砺行修远", {dateEnum: DATE_ENUM.WEEK}], +]); +const needOcrOtherMap = new Map([ + ["砺行修远", ["本周进度", "完成进度"]], +]); const genshinJson = { width: 1920,//genshin.width, height: 1080,//genshin.height, } + +function getDATE_ENUM(activityName) { + for (let key of activityTermConversionMap.keys()) { + if (activityName.includes(key)) + return activityTermConversionMap.get(key) + } + return {dateEnum: DATE_ENUM.HOUR} +} + /** * 滚动页面的异步函数 * @param {number} totalDistance - 总滚动距离 @@ -263,6 +289,32 @@ function formatRemainingTime(timeText) { return `${totalHours}小时(${days > 0 ? days + '天' : ''}${hours > 0 ? hours + '小时' : ''})`; } +/** + * 将总小时数转换为周、天和小时的组合表示 + * @param {number} totalHours - 需要转换的总小时数 + * @returns {string} 返回格式为"X周 Y天 Z小时"的字符串 + */ +function convertHoursToWeeksDaysHours(totalHours) { + // 1周 = 168小时 (7 * 24) + const hoursPerWeek = 168; + const hoursPerDay = 24; + + // 计算整周数 - 使用Math.floor获取完整的周数 + const weeks = Math.floor(totalHours / hoursPerWeek); + + // 剩余小时 - 总小时数减去完整周数对应的小时数 + let remainingHours = totalHours % hoursPerWeek; + + // 从剩余小时中计算天数 - 使用Math.floor获取完整的天数 + const days = Math.floor(remainingHours / hoursPerDay); + + // 剩余的小时 - 剩余小时数减去完整天数对应的小时数 + const hours = remainingHours % hoursPerDay; + + // 返回格式化后的字符串,包含周、天和小时 + return `${weeks}周 ${days}天 ${hours}小时`; +} + /** * OCR识别活动剩余时间的函数 * @param {Object} ocrRegion - OCR识别的区域坐标和尺寸 @@ -378,6 +430,13 @@ async function activityMain() { } } + if (config.blackActivityNameList.length > 0) { + const matched = config.blackActivityNameList.some(keyword => activityName.includes(keyword)); + if (matched) { + continue; // 不关心的活动,跳过不点击 + } + } + // 避免重复点击同一个活动(防止 OCR 误识别或页面抖动) if (activityMap.has(activityName)) { log.info(`活动已记录,跳过重复点击: ${activityName}`); @@ -391,9 +450,32 @@ async function activityMain() { if (totalHours <= 24 && totalHours > 0) { remainingTimeText += '<即将结束>' } + let desc = "" + + let dateEnum = getDATE_ENUM(activityName); + log.debug(`activityName:{activityName},dateEnum:{dateenum.dateEnum}`, activityName, dateEnum.dateEnum) + switch (dateEnum.dateEnum) { + case DATE_ENUM.WEEK: + desc += "|==>" + convertHoursToWeeksDaysHours(totalHours) + "<==|"; + break; + case DATE_ENUM.HOUR: + break; + default: + break; + } + if (needOcrOtherMap.has(activityName)) { + const keys = needOcrOtherMap.get(activityName); + for (const key of keys) { + let text = await OcrRemainingTime(activityName, key); + if (text) { + remainingTimeText += " [" + text + "] " + } + } + } activityMap.set(activityName, { text: remainingTimeText, - hours: totalHours + hours: totalHours, + desc: desc }); log.info(`成功记录 → {activityName} {remainingTime} 共计: {hours} 小时`, activityName, remainingTimeText, totalHours); newActivityCountThisPage++; @@ -427,13 +509,13 @@ async function activityMain() { await sleep(ms); } let activityMapFilter = new Map(); - Array.from(activityMap.entries()) + Array.from(activityMap.entries()) .filter(([name, info]) => info.hours <= config.notifyHoursThreshold) .forEach(([name, info]) => activityMapFilter.set(name, info)); // 7. 全部扫描完毕,统一发送通知(只发一次!) if (activityMapFilter.size > 0) { log.info(`扫描完成,共记录 {activityMap.size} 个活动,即将发送通知`, activityMapFilter.size); - await noticeUtil.sendNotice(activityMapFilter, `原神活动剩余时间提醒(仅显示剩余 ≤ ${config.notifyHoursThreshold} 小时的活动):`); + await noticeUtil.sendNotice(activityMapFilter, `原神活动剩余时间提醒(仅显示剩余 ≤ ${config.notifyHoursThreshold} 小时的活动)${config.blackActivityNameList.length <= 0 ? "" : "|==>已开启黑名单:" + config.blackActivityNameList.join(",") + "<==|"}`); } else { log.warn("未识别到任何活动,未发送通知"); } diff --git a/repo/js/ActivitySwitchNotice/utils/campaignArea.js b/repo/js/ActivitySwitchNotice/utils/campaignArea.js new file mode 100644 index 000000000..f2216f7c6 --- /dev/null +++ b/repo/js/ActivitySwitchNotice/utils/campaignArea.js @@ -0,0 +1,121 @@ +function settingsParseInt(str, defaultValue) { + try { + return str ? parseInt('' + str) : defaultValue; + } catch (e) { + log.warn(`settingsParseInt error:${e}`) + return defaultValue; + } +} + +function settingsParseStr(str, defaultValue) { + return '' + (str ? str : defaultValue); +} + +const config = { + campaignAreaKey: settingsParseStr(settings.campaignAreaKey, 'F1'), + campaignAreaReminderDay: settingsParseInt(settings.campaignAreaReminderDay, 0),//征讨领域提醒日 +} +const ocrRegionConfig = { + weeklyCount: {x: 809, y: 258, width: 277, height: 37},//征讨领域减半次数识别区域坐标和尺寸 +} +const xyConfig = { + campaignArea: {x: 493, y: 537},//征讨领域坐标 + secretRealm: {x: 304, y: 448},//秘境坐标 +} + +/** + * OCR识别周计数函数 + * @param {Object} ocrRegion - OCR识别区域配置,默认为ocrRegionConfig.weeklyCount + * @returns {Object} 返回包含周计数信息的JSON对象,包含text、total和count属性 + * @throws {Error} 当OCR识别失败时抛出错误 + */ +async function ocrWeeklyCount(ocrRegion = ocrRegionConfig.weeklyCount) { + let captureRegion = captureGameRegion(); // 获取游戏区域截图 + const ocrObject = RecognitionObject.Ocr(ocrRegion.x, ocrRegion.y, ocrRegion.width, ocrRegion.height); // 创建OCR识别对象 + // ocrObject.threshold = 1.0; + let res = captureRegion.find(ocrObject); // 在指定区域进行OCR识别 + captureRegion.dispose(); // 释放截图资源 + if (!res.isExist()) { + log.error(`ocrWeeklyCount not found`) // 记录错误日志 + throw new Error(`ocrWeeklyCount not found`) // 抛出错误异常 + } + let weekJson = { // 初始化周计数JSON对象 + text: res.text, + total: 3, + count: 3, + } + let weekCountText = res.text // 获取OCR识别的文本结果 + let result = weekCountText.match(/[0-9/]+/g)?.join('') || ''; // 使用正则表达式提取数字和斜杠 + + log.debug(`识别结果:{weekCountText}`, weekCountText) // 记录原始识别结果 + log.debug(`处理结果:{result}`, result) // 记录处理后的结果 + const numbers = result.split('/').map((item) => parseInt(item)); // 分割字符串并转换为数字数组 + weekJson.total = numbers[1] // 设置总数 + weekJson.count = numbers[0] // 设置当前计数 + log.debug(`Json:{weekJson}`, weekJson) // 记录最终JSON结果 + return weekJson // 返回处理后的周计数JSON对象 +} + +/** + * 获取当前日期的星期信息 + * @returns {Object} 返回包含星期数字和星期名称的对象 + */ +async function getDayOfWeek() { + // 获取当前日期对象 + const today = new Date(); + // 获取当前日期是星期几(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() { + // 获取当前星期信息 + let dayOfWeek = await getDayOfWeek(); + // 如果不是周日(0代表周日),则直接返回 + if (dayOfWeek.day != config.campaignAreaReminderDay) { + log.info(`[{dayOfWeek.dayOfWeek}],跳过执行秘境征讨剩余次数提醒`, dayOfWeek.dayOfWeek) + return + } + // 记录开始执行秘境征讨提醒的日志 + log.info(`[{dayOfWeek.dayOfWeek}],开始执行秘境征讨剩余次数提醒`, dayOfWeek.dayOfWeek) + // 设置操作间隔时间(毫秒) + let ms = 600 + // 等待一段时间 + await sleep(ms) + // 按下配置的热键 + await keyPress(config.campaignAreaKey) + await sleep(ms * 2) + // 点击秘境入口坐标 + await click(xyConfig.secretRealm.x, xyConfig.secretRealm.y) + await sleep(ms * 2) + // 点击秘境征讨坐标 + await click(xyConfig.campaignArea.x, xyConfig.campaignArea.y) + await sleep(ms * 2) + // 使用OCR识别本周秘境征讨剩余次数 + let weekJson = await ocrWeeklyCount(); + + // 如果有剩余次数,则记录日志并发送通知 + if (weekJson.count > 0) { + log.info(`本周剩余消耗减半次数:${weekJson.count}`) + await noticeUtil.send(`>|本周剩余消耗减半次数:${weekJson.count}`, '秘境征讨') + } + +} + +this.campaignAreaUtil = { + campaignAreaMain, +} \ No newline at end of file diff --git a/repo/js/ActivitySwitchNotice/utils/notice.js b/repo/js/ActivitySwitchNotice/utils/notice.js index 2270fca24..2637aa166 100644 --- a/repo/js/ActivitySwitchNotice/utils/notice.js +++ b/repo/js/ActivitySwitchNotice/utils/notice.js @@ -14,14 +14,36 @@ async function sendNotice(map, title, noNotice) { const sortedEntries = Array.from(map.entries()) .sort((a, b) => a[1].hours - b[1].hours); - let noticeText = title ? title + "\n" : "\n" + let noticeText = title ? title + "\n======\n" : "\n" for (const [name, info] of sortedEntries) { - noticeText += `> ${name} ${info.text}<还剩 ${info.hours} 小时>\n`; + noticeText += `> ${name} ${info.text} (还剩 ${info.hours} 小时) ${info.desc}\n----\n`; } // 发送通知 notification.send(noticeText) } +/** + * 异步发送通知的函数 + * @param {string} noticeText - 通知内容文本 + * @param {string} title - 通知标题 + * @param {boolean} noNotice - 是否不发送通知的标志 + */ +async function send(noticeText, title, noNotice) { + // 检查是否有通知内容且设置了不发送通知的标志 + if (noticeText&&noNotice) { + log.info(`无通知内容`) // 记录日志信息 + return // 直接返回,不执行后续操作 + } + // 构建通知文本,如果有标题则先添加标题 + let text = title ? title + "\n======\n" : "\n" + // 添加通知内容 + text += noticeText + // 发送通知 + notification.send(text) + +} + this.noticeUtil = { sendNotice, + send, } \ No newline at end of file