// ====================================================== // 切换账号OCR模式 const author = "彩虹QQ人"; // 图像识别资源 const pm_out = { template: RecognitionObject.TemplateMatch(file.ReadImageMatSync("Assets/RecognitionObject/pm_out.png")), name: "pm_out.png" }; const out_to_login = { template: RecognitionObject.TemplateMatch(file.ReadImageMatSync("Assets/RecognitionObject/out_to_login.png")), name: "out_to_login.png" }; const login_out_account = { template: RecognitionObject.TemplateMatch(file.ReadImageMatSync("Assets/RecognitionObject/login_out_account.png")), name: "login_out_account.png" }; const out_account = { template: RecognitionObject.TemplateMatch(file.ReadImageMatSync("Assets/RecognitionObject/out_account.png")), name: "out_account.png" }; const input_phone_or_email = { template: RecognitionObject.TemplateMatch(file.ReadImageMatSync("Assets/RecognitionObject/input_phone_or_email.png")), name: "input_phone_or_email.png" }; const input_password = { template: RecognitionObject.TemplateMatch(file.ReadImageMatSync("Assets/RecognitionObject/input_password.png")), name: "input_password.png" }; const agree = { template: RecognitionObject.TemplateMatch(file.ReadImageMatSync("Assets/RecognitionObject/agree.png")), name: "agree.png" }; const confirm_switch_account = { template: RecognitionObject.TemplateMatch(file.ReadImageMatSync("Assets/RecognitionObject/confirm.png")), name: "confirm.png" }; const input_email_username = { template: RecognitionObject.TemplateMatch(file.ReadImageMatSync("Assets/RecognitionObject/input_email_username.png")), name: "input_email_username.png" }; const switch_server = { template: RecognitionObject.TemplateMatch(file.ReadImageMatSync("Assets/RecognitionObject/switch_server.png")), name: "switch_server.png" }; const asia_server = { template: RecognitionObject.TemplateMatch(file.ReadImageMatSync("Assets/RecognitionObject/asia.png")), name: "asia.png" }; const europe_server = { template: RecognitionObject.TemplateMatch(file.ReadImageMatSync("Assets/RecognitionObject/europe.png")), name: "europe.png" }; const america_server = { template: RecognitionObject.TemplateMatch(file.ReadImageMatSync("Assets/RecognitionObject/america.png")), name: "america.png" }; const twhkmo_server = { template: RecognitionObject.TemplateMatch(file.ReadImageMatSync("Assets/RecognitionObject/twhkmo.png")), name: "twhkmo.png" }; const confirm_button = { template: RecognitionObject.TemplateMatch(file.ReadImageMatSync("Assets/RecognitionObject/confirm_button.png")), name: "confirm_button.png" }; const clear_login_info = { template: RecognitionObject.TemplateMatch(file.ReadImageMatSync("Assets/RecognitionObject/clear_login_info.png")), name: "clear_login_info.png" }; const save_login_info = { template: RecognitionObject.TemplateMatch(file.ReadImageMatSync("Assets/RecognitionObject/save_login_info.png")), name: "save_login_info.png" }; // 人机验证识别图片 const login_verification = { template: RecognitionObject.TemplateMatch(file.ReadImageMatSync("Assets/RecognitionObject/verification.png")), name: "verification.png" }; // 判断temporaryAccount是否为空,如果为空则赋值‘否’ const Account = settings.temporaryAccount || "否"; eval(file.readTextSync('utils/uid.js')) // 点击区域中心 async function clickCenter(x, y, width, height) { let centerX = Math.round(x + width / 2); let centerY = Math.round(y + height / 2); await click(centerX, centerY); await sleep(500); // 确保点击后有足够的时间等待 return { success: true, x: centerX, y: centerY }; } // 匹配图像并点击 async function matchImgAndClick(obj, desc, timeout = 8000) { const script_mode = "matchImgAndClick"; const start = Date.now(); let retryCount = 0; // 识别次数计数 let status = false; // 用于记录是否匹配成功 try { while (Date.now() - start < timeout && !status) { await sleep(300); const ro = captureGameRegion(); let result = ro.Find(obj.template); ro.dispose(); await sleep(500); // 短暂延迟,避免过快循环 if (result.isExist()) { let clickResult = await clickCenter(result.x, result.y, result.width, result.height); log.info(`【IMG】成功识别并点击 ${desc}| 耗时: ${Date.now() - start}ms`); status = true; // 设置匹配成功状态 return { success: true, x: clickResult.x, y: clickResult.y }; } // await sleep(200); // 短暂延迟,避免过快循环 log.info(`【IMG】第${retryCount++}次识别并点击 ${desc} 失败 | 耗时: ${Date.now() - start}ms`); } } catch (error) { log.error(`【IMG】${script_mode}等待超时,请人工介入。===待切换账号:${settings.username}===超时原因:未找到目标 [${desc}] | 文件:${obj.name}`); //如果有配置通知…… notification.error(`【IMG】${script_mode}等待超时,请人工介入。===待切换账号:${settings.username}===超时原因:未找到目标 [${desc}] | 文件:${obj.name}`); throw new Error(`【IMG】识别图像时发生异常: ${error.message}`); } return { success: false }; } // 文字识别并点击 async function recognizeTextAndClick(targetText, ocrRegion, timeout = 8000) { let start = Date.now(); let retryCount = 0; // 重试计数 let status = false; // 用于记录是否匹配成功 try { while (Date.now() - start < timeout && !status) { const ro = captureGameRegion(); let resultList = ro.findMulti(ocrRegion); ro.dispose(); await sleep(500); // 短暂延迟,避免过快循环 for (let result of resultList) { if (result.text.includes(targetText)) { let clickResult = await clickCenter(result.x, result.y, result.width, result.height); log.info(`【OCR】成功识别并点击 ${targetText}| 耗时: ${Date.now() - start}ms`); status = true; // 设置匹配成功状态 return { success: true, x: clickResult.x, y: clickResult.y }; } } // await sleep(200); // 短暂延迟,避免过快循环 log.info(`【OCR】${targetText}失败,正在进行第 ${retryCount++} 次重试...`); } } catch (error) { log.warn(`【OCR】经过多次尝试,仍然无法识别文字: ”${targetText}“,尝试点击默认中心位置`); await clickCenter(result.x, result.y, result.width, result.height); //如果有配置通知…… notification.error(`【OCR】识别文字: “${targetText}”,发生异常`); throw new Error(`【OCR】识别文字时发生异常: ${error.message}`); } return { success: false }; } // 切换账号OCR模式 // ====================================================== (async function () { // ====================================================== // Library functions var u = {}; // utilities 工具函数集合 u.logi = function (message, args) { log.info("[下拉列表切换账号]" + message, args) }; u.logw = function (message, args) { log.warn("[下拉列表切换账号]" + message, args) }; u.loadTemplate = function (filePath, x /* 0 if omit */, y /* 0 if omit */, w /* maxWidth if omit */, h /* maxHeight if omit */) { return RecognitionObject.TemplateMatch(file.ReadImageMatSync(filePath), x, y, w, h); }; u.findText = function (resList, text) { for (let i = 0; i < resList.count; i++) { let res = resList[i]; if (res.text == text) { return res; } } return null; }; u.matchUser = function (text, username) { if (typeof text !== "string" || typeof username !== "string") return false; if (text.length !== username.length) return false; for (let i = 0; i < text.length; i++) { const a = text[i]; const b = username[i]; if (a === '*' || b === '*') continue; if (a !== b) return false; } return true; }; u.matchUserRelaxed = function (text, username) { if (typeof text !== "string" || typeof username !== "string") return false; // 1. 规范化:将连续的 '*' 替换为单个 '*' const pattern = text.replace(/\*+/g, '*'); const target = username.replace(/\*+/g, '*'); // 2. 如果规范化后没有通配符,则进行全等匹配 if (!pattern.includes('*')) { return pattern === target; } // 3. 获取前缀和后缀 // split('*') 会将 "abc*def" 拆分为 ["abc", "def"] // 如果是 "*abc",拆分为 ["", "abc"];如果是 "abc*",拆分为 ["abc", ""] const parts = pattern.split('*'); const prefix = parts[0]; const suffix = parts[parts.length - 1]; // 4. 匹配逻辑: // - 目标字符串必须以 prefix 开头 // - 目标字符串必须以 suffix 结尾 // - 目标字符串的长度必须足以容纳 prefix 和 suffix (避免重叠部分的逻辑错误) return ( target.startsWith(prefix) && target.endsWith(suffix) && target.length >= (prefix.length + suffix.length) ); }; u.waitAndFindImage = async function (asset, internal = 500, timeout = 60000) { const start = Date.now(); let lastLog = start; let endTime = start + timeout; while (Date.now() < endTime) { let captureRegion = captureGameRegion(); let res = captureRegion.Find(asset); captureRegion.dispose(); if (!res.isEmpty()) { return res; } if (Date.now() - lastLog >= 10000) { let elapsed = ((Date.now() - start) / 1000).toFixed(1); u.logw("等待匹配图像已持续 {0} 秒,仍在尝试寻找图像:{1}", elapsed, asset.Name); lastLog = Date.now(); } await sleep(internal); } return null; } u.waitAndFindText = async function (text, x, y, w, h, internal = 500) { const start = Date.now(); let lastLog = start; while (true) { let captureRegion = captureGameRegion(); let resList = captureRegion.findMulti(RecognitionObject.ocr(x, y, w, h)); captureRegion.dispose(); if (typeof text === "string") { let textFound = u.findText(resList, text); if (textFound) { return textFound; } } else if (text instanceof Array) { var textFound = null; for (let i = 0; i < text.length; i++) { let textItem = text[i]; textFound = u.findText(resList, textItem); if (!textFound) { break; } } if (textFound) { return textFound; } } if (Date.now() - lastLog >= 10000) { let elapsed = ((Date.now() - start) / 1000).toFixed(1); u.logw("等待匹配图像已持续 {0} 秒,仍在尝试寻找文字:{1}", elapsed, text.toString()); lastLog = Date.now(); } await sleep(internal); } } // ====================================================== // Setup const targetUser = settings.username; const assetLogoutIcon = u.loadTemplate("Assets/RecognitionObject/login_out_account.png", 1750, 900, 170, 180); const assetPaimonMenuIcon = u.loadTemplate("Assets/RecognitionObject/paimon_menu.png", 0, 0, 150, 150); // Check current state /** * 领取空月祝福 */ async function useBlessingOfTheWelkinMoon() { u.logi("开始尝试领取空月祝福"); let captureRegion = captureGameRegion(); let resList = captureRegion.findMulti(RecognitionObject.ocrThis); captureRegion.dispose(); for (let i = 0; i < resList.count; i++) { let res = resList[i]; if (res.text.includes("点击领取") || res.text.includes("空月祝福")) { res.click(); await sleep(500); res.click(); res.click(); await sleep(500); } } let captureRegionGetReward = captureGameRegion(); let resGetReward = captureRegionGetReward.findMulti(RecognitionObject.ocrThis); captureRegionGetReward.dispose(); for (let i = 0; i < resGetReward.count; i++) { let res = resGetReward[i]; if (res.text.includes("点击") || res.text.includes("空白") || res.text.includes("获得")) { res.click(); await sleep(500); } } u.logi("空月祝福领取成功"); } async function waitAndDetermineCurrentView() { u.logi("开始判断当前画面状态"); while (true) { let captureRegion = captureGameRegion(); let res = captureRegion.Find(assetLogoutIcon); let logoutIconFound = !res.isEmpty(); if (logoutIconFound) { let resList = captureRegion.findMulti(RecognitionObject.ocr(850, 970, 220, 100)); captureRegion.dispose(); if (u.findText(resList, "点击进入")) { u.logi("检测到目前处于登录界面"); return false; } } // 尝试领取空月祝福 await useBlessingOfTheWelkinMoon(); // Not in the login screen, check if is in the game main menu. let paimonIcon = captureRegion.Find(assetPaimonMenuIcon); captureRegion.dispose(); if (!paimonIcon.isEmpty()) { u.logi("检测到目前处于游戏主界面"); return true; } // Not in the main game screen either, wait and try again. u.logi("未检测到登出按钮或派蒙菜单图标,可能处于游戏中,等待状态变化"); genshin.blessingOfTheWelkinMoon(); moveMouseTo(960, 0); await sleep(100); middleButtonClick(); genshin.returnMainUi(); await sleep(4900); } } async function stateReturnToGenshinGate() { const assetPaimonCancelIcon = u.loadTemplate("Assets/RecognitionObject/paimon_cancel.png", 0, 0, 100, 100); while (true) { let paimonIcon = await u.waitAndFindImage(assetPaimonMenuIcon, 500, 1000); if (!paimonIcon) { u.logi("检测到派蒙菜单图标不存在,可能已开启菜单,进入登出按钮识别流程"); break; } moveMouseTo(960, 0); await sleep(100); middleButtonClick(); keyPress("VK_ESCAPE"); u.logi("已按下ESC键,打开派蒙菜单"); await sleep(1000); } // 识别派蒙菜单,点击登出按钮 await u.waitAndFindImage(assetPaimonCancelIcon); // u.logi("点击登出按钮"); click(50, 1024); // 退出至登录界面 let btnExitToLogin = await u.waitAndFindText("退出至登录界面", 680, 380, 540, 340); // u.logi("检测到\"退出至登录界面\"按钮,点击"); // btnExitToLogin.DrawSelf("ExitToLoginBtn"); btnExitToLogin.Click(); } async function stateLogout() { u.logi("开始登出"); let btnLogout = await u.waitAndFindImage(assetLogoutIcon); // u.logi("识别到登出按钮,点击"); btnLogout.DrawSelf("LogoutBtn"); btnLogout.Click(); const assetQuitTextButton = u.loadTemplate("Assets/RecognitionObject/out_account.png", 680, 380, 1220, 700); let btnQuit = await u.waitAndFindImage(assetQuitTextButton, 200); // u.logi("识别到退出按钮,点击"); // btnQuit.DrawSelf("QuitBtn"); btnQuit.Click(); }; async function stateChangeUser() { u.logi("开始切换账号"); await u.waitAndFindText(["进入游戏", "登录其他账号"], 680, 380, 540, 340, 200); const assetSelectUserDropDownIcon = u.loadTemplate("Assets/RecognitionObject/caret.png", 680, 380, 1220, 700); let captureRegion = captureGameRegion(); let res = captureRegion.Find(assetSelectUserDropDownIcon); captureRegion.dispose(); if (res.isEmpty()) { u.logi("未找到下拉菜单图标,点击硬编码的坐标(960, 500)展开菜单"); click(960, 500); } else { // u.logi("识别到下拉菜单,点击"); // res.DrawSelf("UserDropdown"); res.Click(); } u.logi("开始从下拉列表选择账号"); var selectedUser = null; { const start = Date.now(); let lastLog = start; // 超时8秒直接跳出让下一步报错 while (selectedUser == null && Date.now() - start <= 8000) { await sleep(500); let captureRegion = captureGameRegion(); let resList = captureRegion.findMulti(RecognitionObject.ocr(680, 540, 540, 500)); captureRegion.dispose(); for (let i = 0; i < resList.count; i++) { let res = resList[i]; let user = u.matchUserRelaxed(res.text, targetUser) || u.matchUser(res.text, targetUser); if (user) { selectedUser = res; break; } else { u.logw("当前匹配文本:{0}", res.text); lastLog = Date.now(); } } } } u.logi("识别到目标账号:{0}", selectedUser.text); selectedUser.DrawSelf("SelectUser"); selectedUser.Click(); await sleep(500); { let captureRegion = captureGameRegion(); let btnEnterGame = captureRegion.DeriveCrop(684, 598, 552, 66); btnEnterGame.Click(); captureRegion.dispose(); btnEnterGame.dispose(); // btnEnterGame.DrawSelf("EnterGameBtn"); // u.logi("已点击\"进入游戏\"按钮,完成账号选择。"); } } async function stateEnterGame() { // u.logi("开始进入游戏,等待游戏加载。"); let textClickToStart = await u.waitAndFindText("点击进入", 850, 970, 220, 100); // u.logi("已识别到\"点击进入\"文本,点击鼠标进入游戏。"); textClickToStart.DrawSelf("ClickToStart"); textClickToStart.Click(); } // ====================================================== // Main flow if (await uidUtil.check()) { log.info("当前UID与设置UID相同,无需切换账号。"); return } if (settings.Modes == "下拉列表" && !settings.GlobalAccount) { await DropDownMode(); } else if (settings.Modes == "账号+密码+OCR" && !settings.GlobalAccount) { await OcrMode(); } else if (settings.Modes == "B服切换另一个账号匹配+键鼠") { await BilibiliMatchAndKeyboardMouseMode(); } else if (settings.Modes == "账号+密码+OCR" && settings.GlobalAccount) { await GlobalOcrMode(); } else if (settings.Modes == "下拉列表" && settings.GlobalAccount) { await GlobalDropDownMode(); } else { log.info("尖尖哇嘎乃") } // 下拉列表模式 async function DropDownMode() { const isInGame = await waitAndDetermineCurrentView(); if (isInGame) { await stateReturnToGenshinGate(); await sleep(1000); } await stateLogout(); await sleep(500); await stateChangeUser(); await sleep(500); await stateEnterGame(); await sleep(20000); await waitAndDetermineCurrentView(); } // 国际服下拉列表模式 async function GlobalDropDownMode() { const script_mode = "国际服下拉列表模式"; const page = new BvPage(); setGameMetrics(1920, 1080, 1); const isInGame = await waitAndDetermineCurrentView(); if (isInGame) { await stateReturnToGenshinGate(); await sleep(1000); } await page.WaitForOcrMatch("开始游戏"); await matchImgAndClick(login_out_account, "登录页的右下角退出按钮"); await page.WaitForOcrMatch("切换账号"); await matchImgAndClick(confirm_switch_account, "确认切换账号"); // 检测是否弹出保存登陆记录弹框 if (await page.WaitForOcrMatch("退出并", new OpenCvSharp.OpenCvSharp.Rect(0, 0, 1920, 1080), 0.8, 1000)) { await matchImgAndClick(save_login_info, "保留登陆记录"); await sleep(500); await matchImgAndClick(confirm_switch_account, "确认切换账号"); await sleep(500); } await stateChangeUser(); await sleep(500); // 换服务器操作 if (settings.Servers && (settings.Servers !== "不切换服务器" || settings.Servers == "")) { await page.WaitForOcrMatch("开始游戏"); log.info("正在更换服务器") await matchImgAndClick(switch_server, "更换服务器"); let serversMatched = true; if (settings.Servers == "Asia") { await matchImgAndClick(asia_server, "亚服"); } else if (settings.Servers == "Europe") { await matchImgAndClick(europe_server, "欧服"); } else if (settings.Servers == "America") { await matchImgAndClick(america_server, "美服"); } else if (settings.Servers == "TW,HK,MO") { await matchImgAndClick(twhkmo_server, "台港澳"); } else { log.info("尖尖哇嘎乃") serversMatched = false; } if (serversMatched) { await matchImgAndClick(confirm_button, "确认换服"); } } await keyPress("VK_ESCAPE"); await page.WaitForOcrMatch("开始游戏"); await click(960, 640); await page.Wait(5000); log.info('等待提瓦特大门加载'); await page.WaitForOcrMatch("点击进入"); await click(960, 640); // 可能登录账号的时候出现月卡提醒,则先点击一次月卡。 await genshin.blessingOfTheWelkinMoon(); await page.Wait(1000); await genshin.returnMainUi(); // 如果配置了通知 notification.send("账号【" + settings.username + "】切换成功"); } // OCR模式 对应:账号+密码+OCR async function OcrMode() { const script_mode = "OCR模式"; setGameMetrics(1920, 1080, 1); // 如果切换账号是第一个脚本,则有可能出现月卡选项 try { keyDown("VK_MENU"); await sleep(500); for (let i = 0; i <= 4; i++) { await click(960, 864); await sleep(1000); } } finally { keyUp("VK_MENU"); } await genshin.returnMainUi(); await keyPress("VK_ESCAPE"); await sleep(500); try { await matchImgAndClick(pm_out, "左下角退出门"); await matchImgAndClick(out_to_login, "退出至登陆页面"); //这一步根据 电脑配置和当前网络情况不同休眠时间不同,建议实际运行之后,如果有日志 : 第x次 识别失败,就适当增加休眠时间 await sleep(9000); await matchImgAndClick(login_out_account, "登录页的右下角退出按钮"); await matchImgAndClick(out_account, "退出当前账号"); await recognizeTextAndClick("登录其他账号", RecognitionObject.Ocr(300, 200, 1200, 800), 5000); await sleep(1000); await matchImgAndClick(input_phone_or_email, "填写邮箱/手机号"); await inputText(settings.username); await sleep(1000); await matchImgAndClick(input_password, "填写密码"); await inputText(settings.password); await sleep(1000); //按下回车登录账号,弹出用户协议对话框 await keyPress("VK_RETURN"); //点击回车后,等待特瓦特大门加载 await matchImgAndClick(agree, "同意用户协议"); //如果当天上下线次数过于频繁 for (let i = 1; i <= 2; i++) { const ro = captureGameRegion(); let verify = ro.Find(login_verification.template); ro.dispose(); //等待1s避免循环速度过快 await sleep(1000); if (verify.isExist()) { //这里可配置通知方法 notification.error(`${script_mode}触发人机验证,请手动登录。===待切换账号:${settings.username}`); log.error(`${script_mode}触发人机验证,请手动登录。===待切换账号:${settings.username}`); } } /** * 根据不同网络环境和电脑配置,此操作可能会将领取月卡操作取代,但是不影响使用 * 如果发现卡在这一步,请适当延长sleep时间 */ await sleep(8000); await recognizeTextAndClick("点击进入", RecognitionObject.Ocr(862, 966, 206, 104), 5000); await sleep(15000); //可能登录账号的时候出现月卡提醒,则先点击一次月卡。 await sleep(2000); keyDown("VK_MENU"); await sleep(500); for (let i = 0; i <= 4; i++) { await click(960, 864); await sleep(1000); } //keyUp("VK_MENU"); await genshin.returnMainUi(); await sleep(1000); // 如果配置了通知 notification.send("账号【" + settings.username + "】切换成功"); } catch (error) { log.error(`${script_mode}脚本执行过程中发生错误:${error.message}`); //如果发生错误,则发送通知 notification.error(`${script_mode}脚本执行过程中发生错误:${error.message}`); throw new Error(`${script_mode}脚本执行过程中发生错误:${error.message}`); } finally { keyUp("VK_MENU"); } } // B服切换匹配+键鼠模式 对应:B服切换另一个账号匹配+键鼠 async function BilibiliMatchAndKeyboardMouseMode() { const script_mode = "B服切换匹配+键鼠模式"; setGameMetrics(1920, 1080, 1); // 如果切换账号是第一个脚本,则有可能出现月卡选项 try { keyDown("VK_MENU"); await sleep(500); for (let i = 0; i <= 4; i++) { await click(960, 864); await sleep(1000); } } finally { keyUp("VK_MENU"); } await genshin.returnMainUi(); await keyPress("VK_ESCAPE"); await sleep(500); try { await matchImgAndClick(pm_out, "左下角退出门"); await matchImgAndClick(out_to_login, "退出至登陆页面"); //这一步根据 电脑配置和当前网络情况不同休眠时间不同,建议实际运行之后,如果有日志 : 第x次 识别失败,就适当增加休眠时间 await sleep(7000); //登录其他账号 click(1135, 478); await sleep(1000); //点击用户名 click(963, 635); await sleep(500); //点击登录 click(963, 635); await sleep(500); //登录其他账号 click(1135, 478); await sleep(1000); //取消点击用户名(确保能登录) click(963, 400); await sleep(500); //点击登录 click(963, 635); await sleep(500); //如果当天上下线次数过于频繁 for (let i = 1; i <= 2; i++) { const ro = captureGameRegion(); let verify = ro.Find(login_verification.template); ro.dispose(); //等待1s避免循环速度过快 await sleep(1000); if (verify.isExist()) { //这里可配置通知方法 notification.error(`${script_mode}触发人机验证,请手动登录。===待切换账号:${settings.username}`); log.error(`${script_mode}触发人机验证,请手动登录。===待切换账号:${settings.username}`); } } /** * 根据不同网络环境和电脑配置,此操作可能会将领取月卡操作取代,但是不影响使用 * 如果发现卡在这一步,请适当延长sleep时间 */ await sleep(6500); await recognizeTextAndClick("点击进入", RecognitionObject.Ocr(862, 966, 206, 104), 5000); await sleep(12000); //可能登录账号的时候出现月卡提醒,则先点击一次月卡。 await sleep(2000); keyDown("VK_MENU"); await sleep(500); for (let i = 0; i <= 4; i++) { await click(960, 864); await sleep(1000); } //keyUp("VK_MENU"); await genshin.returnMainUi(); await sleep(1000); // 如果配置了通知 notification.send("账号【" + settings.username + "】切换成功"); } catch (error) { log.error(`${script_mode}脚本执行过程中发生错误:${error.message}`); //如果发生错误,则发送通知 notification.error(`${script_mode}脚本执行过程中发生错误:${error.message}`); throw new Error(`${script_mode}脚本执行过程中发生错误:${error.message}`); } finally { keyUp("VK_MENU"); } } // 国际服OCR模式 对应:国际服账号+密码+OCR async function GlobalOcrMode() { const script_mode = "国际服OCR模式"; const page = new BvPage(); setGameMetrics(1920, 1080, 1); await genshin.blessingOfTheWelkinMoon(); await genshin.returnMainUi(); await keyPress("VK_ESCAPE"); await sleep(500); try { await matchImgAndClick(pm_out, "左下角退出门"); await matchImgAndClick(out_to_login, "退出至登陆页面"); await page.WaitForOcrMatch("开始游戏"); await matchImgAndClick(login_out_account, "登录页的右下角退出按钮"); await page.WaitForOcrMatch("切换账号"); await matchImgAndClick(confirm_switch_account, "确认切换账号"); // 检测是否弹出保存登陆记录弹框 if (await page.WaitForOcrMatch("退出并", new OpenCvSharp.OpenCvSharp.Rect(0, 0, 1920, 1080), 0.8, 1000)) { await matchImgAndClick(save_login_info, "保留登陆记录"); await sleep(500); await matchImgAndClick(confirm_switch_account, "确认切换账号"); await sleep(500); } await recognizeTextAndClick("登录其他账号", RecognitionObject.Ocr(300, 200, 1200, 800), 8000); await sleep(1000); await matchImgAndClick(input_email_username, "输入邮箱/用户名"); await inputText(settings.username); await sleep(1000); await matchImgAndClick(input_password, "输入密码"); await inputText(settings.password); await sleep(1000); // 按下回车登录账号,弹出用户协议对话框 await keyPress("VK_RETURN"); // await matchImgAndClick(agree, "同意用户协议"); // 当天上下线次数过于频繁弹验证码 for (let i = 1; i <= 5; i++) { const ro = captureGameRegion(); let verify = ro.Find(login_verification.template); ro.dispose(); await sleep(1000); if (verify.isExist()) { notification.error(`${script_mode}触发人机验证,请手动登录。===待切换账号:${settings.username}`); log.error(`${script_mode}触发人机验证,请手动登录。===待切换账号:${settings.username}`); } } // 换服务器操作 if (settings.Servers && (settings.Servers !== "不切换服务器" || settings.Servers == "")) { await page.WaitForOcrMatch("开始游戏"); log.info("正在更换服务器") await matchImgAndClick(switch_server, "更换服务器"); let serversMatched = true; if (settings.Servers == "Asia") { await matchImgAndClick(asia_server, "亚服"); } else if (settings.Servers == "Europe") { await matchImgAndClick(europe_server, "欧服"); } else if (settings.Servers == "America") { await matchImgAndClick(america_server, "美服"); } else if (settings.Servers == "TW,HK,MO") { await matchImgAndClick(twhkmo_server, "台港澳"); } else { log.info("尖尖哇嘎乃") serversMatched = false; } if (serversMatched) { await matchImgAndClick(confirm_button, "确认换服"); } } await keyPress("VK_ESCAPE"); await page.WaitForOcrMatch("开始游戏"); await click(960, 640); await page.Wait(5000); log.info('等待提瓦特大门加载'); await page.WaitForOcrMatch("点击进入"); await click(960, 640); // 可能登录账号的时候出现月卡提醒,则先点击一次月卡。 await genshin.blessingOfTheWelkinMoon(); await page.Wait(1000); await genshin.returnMainUi(); // 如果配置了通知 notification.send("账号【" + settings.username + "】切换成功"); } catch (error) { log.error(`${script_mode}脚本执行过程中发生错误:${error.message}`); //如果发生错误,则发送通知 notification.error(`${script_mode}脚本执行过程中发生错误:${error.message}`); throw new Error(`${script_mode}脚本执行过程中发生错误:${error.message}`); } finally { keyUp("VK_MENU"); } } })();