diff --git a/repo/js/ActivitySwitchNotice/README.md b/repo/js/ActivitySwitchNotice/README.md index 73c149c16..48082b3a1 100644 --- a/repo/js/ActivitySwitchNotice/README.md +++ b/repo/js/ActivitySwitchNotice/README.md @@ -511,6 +511,11 @@ ActivitySwitchNotice/ ## 版本历史 +### 0.0.8 (2026-01-22) +- 新增 地图识别任务提醒 如: + + ![0.0.8-00](md/0.0.8-map-00.jpg) + ![0.0.8](md/0.0.8-map.jpg) ### 0.0.7 (2026-01-19) - 新增每日委托提醒 ### 0.0.6 (2026-01-06) diff --git a/repo/js/ActivitySwitchNotice/main.js b/repo/js/ActivitySwitchNotice/main.js index b4595058f..0d1b43508 100644 --- a/repo/js/ActivitySwitchNotice/main.js +++ b/repo/js/ActivitySwitchNotice/main.js @@ -1,18 +1,108 @@ let manifest = {}; +let manifest_json = "manifest.json"; +let configSettings = undefined + +/** + * 初始化设置函数 + * 从配置文件中读取设置信息并返回 + * @returns {Object} 返回解析后的JSON设置对象 + */ +async function initSettings() { + // 默认设置文件路径 + let settings_ui = "settings.json"; + try { + // 读取并解析manifest.json文件 + manifest = JSON.parse(file.readTextSync(manifest_json)); + // 调试日志:输出manifest内容 + log.debug("manifest={key}", manifest); + // 调试日志:输出manifest中的settings_ui配置 + log.debug("settings_ui={key}", manifest.settings_ui); + log.info(`|脚本名称:{name},版本:{version}`, manifest.name, manifest.version); + if (manifest.bgi_version) { + log.info(`|最小可执行BGI版本:{bgi_version}`, manifest.bgi_version); + } + log.info(`|脚本作者:{authors}\n`, manifest.authors.map(a => a.name).join(",")); + // 更新settings_ui变量为manifest中指定的路径 + settings_ui = manifest.settings_ui + } catch (error) { + // 捕获并记录可能的错误 + log.warn("{error}", error.message); + } + // 读取并解析设置文件 + const settingsJson = JSON.parse(file.readTextSync(settings_ui)); + // 如果configSettings未定义,则将其设置为解析后的设置对象 + if (!configSettings) { + configSettings = settingsJson + } + // 调试日志:输出最终解析的设置对象 + log.debug("settingsJson={key}", settingsJson); + // 返回设置对象 + return settingsJson +} +/** + * 获取多复选框的映射表 + * 该函数会从初始化的设置中提取所有类型为"multi-checkbox"的条目, + * 并将这些条目的名称和对应的选项值存储在一个Map对象中返回 + * @returns {Promise} 返回一个Promise对象,解析为包含多复选框配置的Map + */ +async function getMultiCheckboxMap() { + // 如果configSettings存在则使用它,否则调用initSettings()函数获取 + const settingsJson = configSettings ? configSettings : await initSettings(); + // 创建一个新的Map对象用于存储多复选框的配置 + // Map结构为: {名称: 选项数组} + let multiCheckboxMap = new Map(); + // 遍历设置JSON中的每个条目 + settingsJson.forEach((entry) => { + // 如果条目没有name属性或者类型不是"multi-checkbox",则跳过该条目 + if (!entry.name || entry.type !== "multi-checkbox") return; + // 解构条目中的name和label属性,便于后续使用 + const {name, label} = entry; + // 获取当前name对应的设置值,如果存在则转换为数组,否则使用空数组 + const options = settings[name] ? Array.from(settings[name]) : []; + // 记录调试信息,包含名称、标签、选项和选项数量 + log.debug("name={key1},label={key2},options={key3},length={key4}", name, label, JSON.stringify(options), options.length); + // 将名称和对应的选项数组存入Map + multiCheckboxMap.set(name, options); + }) + // 返回包含多复选框配置的Map + return multiCheckboxMap +} + +/** + * 根据复选框组名称获取对应的值 + * 这是一个异步函数,用于从复选框映射中获取指定名称的值 + * @param {string} name - 复选框组的名称 + * @returns {Promise} 返回一个Promise,解析为复选框组对应的值 + */ +async function getValueByMultiCheckboxName(name) { + // 获取复选框映射表,这是一个异步操作 + let multiCheckboxMap = await getMultiCheckboxMap() + // log.debug("multiCheckboxMap={key}", JSON.stringify(multiCheckboxMap)) + // multiCheckboxMap.entries().forEach(([name, options]) => { + // log.debug("name={key1},options={key2}", name, JSON.stringify(options)) + // }) + // 从映射表中获取并返回指定名称对应的值 + let values = multiCheckboxMap.get(name); + log.debug("values={key}", JSON.stringify(values)) + return values +} async function init() { - let utils=[ + let utils = [ "uid", "ws", "notice", "campaignArea", "activity", + "mapMission", ] for (let util of utils) { eval(file.readTextSync(`utils/${util}.js`)); } - manifest = JSON.parse(file.readTextSync("manifest.json")); + // manifest = JSON.parse(file.readTextSync("manifest.json")); + await initSettings(); log.debug("main 初始化完成"); } + // 判断是否在主界面的函数 const isInMainUI = () => { let captureRegion = captureGameRegion(); @@ -44,8 +134,7 @@ async function toMainUi() { (async function () { await init(); - log.info(`版本:{version}`,manifest.version) - if (settings.toMainUi){ + if (settings.toMainUi) { await toMainUi(); } await main(); @@ -57,10 +146,29 @@ async function toMainUi() { */ async function main() { let ms = 600 - await campaignAreaUtil.dailyCommissionMain() - await sleep(ms*2); - await campaignAreaUtil.campaignAreaMain(false) - await sleep(ms*2); + const mapList = await getValueByMultiCheckboxName('mapMissionKeys')|| [] + // log.info(`mapList=>{0}`,JSON.stringify(mapList)) + if (mapList.length > 0) { + try { + log.info(`开始识别地图任务`) + await mapUtil.mapMission(mapList) + } finally { + await toMainUi() + } + } + + let openKey = true + + try { + await campaignAreaUtil.dailyCommissionMain(openKey) + await sleep(ms * 2); + openKey = false + } catch (e) { + await toMainUi() + throw e + } + await campaignAreaUtil.campaignAreaMain(openKey) + await sleep(ms * 2); 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 07a2110b6..778adb2f4 100644 --- a/repo/js/ActivitySwitchNotice/manifest.json +++ b/repo/js/ActivitySwitchNotice/manifest.json @@ -1,9 +1,10 @@ { "name": "活动期限/周本通知器", - "version": "0.0.7", + "version": "0.0.8", "description": "", "settings_ui": "settings.json", "main": "main.js", + "bgi_version": "0.55.0", "authors": [ { "name": "云端客", diff --git a/repo/js/ActivitySwitchNotice/md/0.0.8-map-00.jpg b/repo/js/ActivitySwitchNotice/md/0.0.8-map-00.jpg new file mode 100644 index 000000000..a4cb0d82f Binary files /dev/null and b/repo/js/ActivitySwitchNotice/md/0.0.8-map-00.jpg differ diff --git a/repo/js/ActivitySwitchNotice/md/0.0.8-map.jpg b/repo/js/ActivitySwitchNotice/md/0.0.8-map.jpg new file mode 100644 index 000000000..5f183e2a9 Binary files /dev/null and b/repo/js/ActivitySwitchNotice/md/0.0.8-map.jpg differ diff --git a/repo/js/ActivitySwitchNotice/settings.json b/repo/js/ActivitySwitchNotice/settings.json index 725fa652a..6c3b5de11 100644 --- a/repo/js/ActivitySwitchNotice/settings.json +++ b/repo/js/ActivitySwitchNotice/settings.json @@ -1,102 +1,135 @@ [ - { - "name": "toMainUi", - "type": "checkbox", - "label": "启用先返回主界面后执行切换", - "default": true - }, - { - "name": "noticeType", - "type": "select", - "label": "通知模式(默认BGI通知-使用独立通知需要开启JS HTTP权限)", - "options": [ - "BGI通知", - "独立通知", - "独立通知和BGI通知", - ], - "default": "BGI通知" - }, - { - "name": "relationship", - "type": "checkbox", - "label": "剩余时间,白名单 启用`和`关系(默认`或`关系)", - "default": false - }, - { - "name": "whiteActivityNameList", - "type": "input-text", - "label": "白名单活动名称(使用|分割)<可不填 默认推送所有有剩余时间的活动>" - }, - { - "name": "blackActivity", - "type": "input-text", - "label": "黑名单活动名称(使用|分割)<可不填,默认没有不推送的活动>(新增语法指定条件的黑名单:活动1-条件1,条件2|活动2-条件1)" - }, - { - "name": "notifyHoursThreshold", - "type": "input-text", - "label": "通知剩余时间阈值<单位:小时>(默认 8760小时=365天)", - "default": "8760" - }, - { - "name": "activityKey", - "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" - }, - { - "name": "ws_proxy_url", - "type": "input-text", - "label": "独立通知配置:\n==============================\nWebSocketProxyUrl(列:http://127.0.0.1:8081/ws-proxy/message/send)", - "default": "http://127.0.0.1:8081/ws-proxy/message/send" - }, - { - "name": "ws_url", - "type": "input-text", - "label": "WebSocket客户端 Url(列:ws://127.0.0.1:8080)", - "default": "ws://127.0.0.1:8080/" - }, - { - "name": "ws_token", - "type": "input-text", - "label": "WebSocket客户端 token(没有可不填)", - "default": "" - }, - { - "name": "action", - "type": "select", - "label": "发送类型", - "options": [ - "私聊", - "群聊" - ], - "default": "私聊", - }, - { - "name": "send_id", - "type": "input-text", - "label": "(发送id 群号|QQ号 对应发送类型)", - "default": "" - }, - { - "name": "at_list", - "type": "input-text", - "label": "@某人列表使用,隔开(QQ号)", - "default": "" - }, -] + { + "name": "toMainUi", + "type": "checkbox", + "label": "启用先返回主界面后执行切换", + "default": true + }, + { + "name": "noticeType", + "type": "select", + "label": "通知模式(默认BGI通知-使用独立通知需要开启JS HTTP权限)", + "options": [ + "BGI通知", + "独立通知", + "独立通知和BGI通知" + ], + "default": "BGI通知" + }, + { + "type": "separator" + }, + { + "label": "地图识别任务提醒", + "type": "multi-checkbox", + "name": "mapMissionKeys", + "options": [ + "伴月纪闻任务", + "探索派遣奖励", + "豪斗旅纪奖励", + "每日委托奖励" + ], + "default": [ + "伴月纪闻任务", + "探索派遣奖励", + "每日委托奖励"] + }, + { + "type": "separator" + }, + { + "name": "relationship", + "type": "checkbox", + "label": "剩余时间,白名单 启用`和`关系(默认`或`关系)", + "default": false + }, + { + "name": "whiteActivityNameList", + "type": "input-text", + "label": "白名单活动名称(使用|分割)<可不填 默认推送所有有剩余时间的活动>" + }, + { + "name": "blackActivity", + "type": "input-text", + "label": "黑名单活动名称(使用|分割)<可不填,默认没有不推送的活动>(新增语法指定条件的黑名单:活动1-条件1,条件2|活动2-条件1)" + }, + { + "name": "notifyHoursThreshold", + "type": "input-text", + "label": "通知剩余时间阈值<单位:小时>(默认 8760小时=365天)", + "default": "8760" + }, + { + "name": "activityKey", + "type": "input-text", + "label": "打开活动页面按键(不填,默认:F5)", + "default": "F5" + }, + { + "type": "separator" + }, + { + "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" + }, + { + "type": "separator" + }, + { + "name": "ws_proxy_url", + "type": "input-text", + "label": "独立通知配置:\n==============================\nWebSocketProxyUrl(列:http://127.0.0.1:8081/ws-proxy/message/send)", + "default": "http://127.0.0.1:8081/ws-proxy/message/send" + }, + { + "name": "ws_url", + "type": "input-text", + "label": "WebSocket客户端 Url(列:ws://127.0.0.1:8080)", + "default": "ws://127.0.0.1:8080/" + }, + { + "name": "ws_token", + "type": "input-text", + "label": "WebSocket客户端 token(没有可不填)", + "default": "" + }, + { + "name": "action", + "type": "select", + "label": "发送类型", + "options": [ + "私聊", + "群聊" + ], + "default": "私聊" + }, + { + "name": "send_id", + "type": "input-text", + "label": "(发送id 群号|QQ号 对应发送类型)", + "default": "" + }, + { + "name": "at_list", + "type": "input-text", + "label": "@某人列表使用,隔开(QQ号)", + "default": "" + } +] \ No newline at end of file diff --git a/repo/js/ActivitySwitchNotice/utils/mapMission.js b/repo/js/ActivitySwitchNotice/utils/mapMission.js new file mode 100644 index 000000000..32385807f --- /dev/null +++ b/repo/js/ActivitySwitchNotice/utils/mapMission.js @@ -0,0 +1,100 @@ +const ocrRegionConfig = { + mapMission: {x: 6, y: 8, width: 395, height: 977},//地图任务识别区域坐标和尺寸 +} + + +/** + * OCR地图任务识别函数 + * 通过OCR技术识别游戏界面中的任务名称,并与预设的任务名称列表进行匹配 + * @param {Array} [missionNameList=[]] - 需要识别的任务名称列表 + * @param {Object} [regionConfig=ocrRegionConfig.mapMission] - OCR识别区域配置对象,包含x、y、width、height属性 + * @returns {Promise>} 返回识别结果数组,每个元素包含ok(boolean)和text(string)属性 + */ +async function ocrMapMission(missionNameList = [], regionConfig = ocrRegionConfig.mapMission) { + let jsonList = []; + let region = null; + + try { + // 捕获游戏区域并创建OCR识别对象 + region = captureGameRegion(); + let recognitionObject = RecognitionObject.Ocr(regionConfig.x, regionConfig.y, regionConfig.width, regionConfig.height); + // 执行多目标OCR识别 + let resList = region.findMulti(recognitionObject); + // if (!resList || !resList.length) { + // return jsonList; + // } + + // 遍历识别结果并匹配任务名称 + for (let i = 0; i < resList.count; i++) { + let res = resList[i]; + log.debug(`[-]识别结果: ${res.text}, 原始坐标: x=${res.x}, y=${res.y},width:${res.width},height:${res.height}`); + + let json = { + ok: false, + text: undefined + }; + + // 检查当前识别文本是否包含任一任务名称 + let matchedMission = null; + for (const missionName of missionNameList) { + if (res.text.trim().includes(missionName)) { + matchedMission = missionName; + break; + } + } + + if (matchedMission) { + log.debug(`识别成功=>${matchedMission}->${res.text}`); + json.ok = true; + json.text = res.text.trim(); + } + + jsonList.push(json); + } + } catch (e) { + log.error('OCR识别过程出错:', e.message); + throw e; + } finally { + // 确保资源始终被释放 + if (region) { + region.Dispose(); + } + } + + return jsonList; +} + + +//伴月纪闻任务待完成 +// 通过地图识别任务 +async function openMap() { + const key = settings.mapKey || 'M' + await sleep(200) + await keyPress(key) +} + +async function mapMission(list = [], toOpenMap = true) { + let ms = 600 + if (toOpenMap) { + await openMap(); + await sleep(ms); + } + await sleep(ms * 2); + let keyJsonList = await ocrMapMission(list); + keyJsonList = keyJsonList.filter(item => item.ok) + log.info(`识别到地图任务数量:${keyJsonList.length}`) + if (keyJsonList.length <= 0) { + log.warn(`未识别到地图任务`) + return + } + const uid = await uidUtil.ocrUID() + let text = "" + keyJsonList.forEach(item => text += "|< " + item.text + " >\n") + await noticeUtil.sendText(text, `UID:${uid}\n地图任务`) +} + +this.mapUtil = { + mapMission, + ocrMapMission, + openMap, +} \ No newline at end of file diff --git a/repo/js/ActivitySwitchNotice/utils/notice.js b/repo/js/ActivitySwitchNotice/utils/notice.js index 807821a2a..b985e3e54 100644 --- a/repo/js/ActivitySwitchNotice/utils/notice.js +++ b/repo/js/ActivitySwitchNotice/utils/notice.js @@ -59,9 +59,8 @@ async function sendNotice(map = new Map(), title, noNotice = false) { * 异步发送通知的函数 * @param {string} noticeText - 通知内容文本 * @param {string} title - 通知标题 - * @param {boolean} noNotice - 是否不发送通知的标志 */ -async function sendText(noticeText, title, noNotice) { +async function sendText(noticeText, title, noNotice = false) { // 检查是否有通知内容且设置了不发送通知的标志 if ((!noticeText) || noNotice) { log.info(`sendText 无通知内容`) // 记录日志信息