Files
Live-streaming/integrations/扫码上号/main.js
T

285 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
(async function () {
// ========================================
// 扫码上号 v3
// 流程: 状态判定 → (已登录则先退出) → A0检测tap → A1选号 → A2等登录+点中心 → A3完成
// 状态文件: status.txt (登录中 / 已登录)
// ========================================
setGameMetrics(1920, 1080, 1);
// ---------- 状态文件 ----------
const STATUS_FILE = "status.txt";
function writeStatus(text) {
try {
file.writeTextSync(STATUS_FILE, text);
} catch (e) {
log.warn("写入状态文件失败: " + e);
}
}
// 脚本启动 → 写入"登录中"
writeStatus("登录中");
// ---------- 加载图像资源 ----------
const tapMat = file.readImageMatSync("assets/tap.png");
const a0PhoneMat = file.readImageMatSync("assets/a0_phone.png");
const loggedInMat = file.readImageMatSync("assets/btn_logged_in_real.png");
const paimonMat = file.readImageMatSync("assets/paimon_menu.png");
const exitDoorMat = file.readImageMatSync("assets/btn_exit_door.png");
const preLoginNoticeMat = file.readImageMatSync("assets/pre_login_notice.png");
// ---------- 工具函数 ----------
/**
* 在截屏中匹配指定图片, 返回识别结果(含 x/y/中心坐标)
*/
function findImageMatch(mat, x, y, w, h) {
const cap = captureGameRegion();
try {
const ro = RecognitionObject.TemplateMatch(mat, x || 0, y || 0, w || 1920, h || 1080);
const r = cap.find(ro);
if (r.isExist()) {
return { x: r.x, y: r.y, w: r.width, h: r.height };
}
return null;
} catch (e) {
log.error("图像识别失败: " + e);
return null;
} finally {
cap.dispose();
}
}
/**
* 严格匹配:用于 A0 入口判定,避免地图页面/传送点/圆形 UI 被低阈值误识别。
*/
function findImageMatchStrict(mat, x, y, w, h) {
const thresholds = [0.88, 0.84];
for (const t of thresholds) {
const cap = captureGameRegion();
try {
const ro = RecognitionObject.TemplateMatch(mat, x || 0, y || 0, w || 1920, h || 1080);
ro.threshold = t;
ro.Use3Channels = true;
const r = cap.find(ro);
if (r.isExist()) {
log.info(`严格找到图标 (阈值=${t}, x=${r.x}, y=${r.y})`);
return { x: r.x, y: r.y, w: r.width, h: r.height };
}
} catch (e) {
// 忽略, 继续下一个阈值
} finally {
cap.dispose();
}
}
return null;
}
/**
* 多阈值尝试匹配, 提高识别率
*/
function findImageMatchRobust(mat, x, y, w, h) {
const thresholds = [0.7, 0.6, 0.5];
for (const t of thresholds) {
const cap = captureGameRegion();
try {
const ro = RecognitionObject.TemplateMatch(mat, x || 0, y || 0, w || 1920, h || 1080);
ro.threshold = t;
ro.Use3Channels = true;
const r = cap.find(ro);
if (r.isExist()) {
log.info(`找到图标 (阈值=${t}, x=${r.x}, y=${r.y})`);
return { x: r.x, y: r.y, w: r.width, h: r.height };
}
} catch (e) {
// 忽略, 继续下一个阈值
} finally {
cap.dispose();
}
}
return null;
}
function clickImage(found) {
click(Math.round(found.x + found.w / 2), Math.round(found.y + found.h / 2));
}
async function waitForImage(mat, timeout, interval) {
interval = interval || 500;
const start = Date.now();
while (Date.now() - start < timeout) {
const found = findImageMatch(mat);
if (found) return found;
await sleep(interval);
}
return null;
}
// ========================================
// 前置处理:检测登录前的提示图标
// 检测到则点击 (1830, 985)1秒后点击 (1100, 675),再1秒后进入初始判定;
// 未检测到则直接进入初始判定,不影响原流程。
// ========================================
log.info("===== 前置检测: pre_login_notice 图标 =====");
const preNoticeFound = findImageMatchRobust(preLoginNoticeMat);
if (preNoticeFound) {
log.info(`检测到 pre_login_notice 图标 (x=${preNoticeFound.x}, y=${preNoticeFound.y})`);
log.info("点击 (1830, 985)...");
click(1830, 985);
await sleep(1000);
log.info("点击 (1100, 675)...");
click(1100, 675);
await sleep(1000);
} else {
log.info("未检测到 pre_login_notice 图标,跳过前置处理");
}
// ========================================
// 初始状态判定:先检测 tap + 手机图标
// 1. 同时存在:已经在开门页,直接进入 A0 选号
// 2. 不同时存在:检测派蒙头像;若无派蒙,则按 ESC 后重检,最多 8 次
// 3. 8 次仍无派蒙:再严格检测一次开门页;开门页也不存在才判定登录失败
// 4. 找到派蒙:说明已在游戏内,先退出到开门页,再进入 A0
// ========================================
log.info("===== 初始状态判定: 严格检测 tap 图标 + 手机图标 =====");
// A0 入口图标不能全屏低阈值搜索,否则地图/传送点等 UI 容易误判。
// 这里限定在开门页中下区域,并使用高阈值严格匹配。
const A0_X = 420;
const A0_Y = 120;
const A0_W = 1080;
const A0_H = 820;
let tapFound = findImageMatchStrict(tapMat, A0_X, A0_Y, A0_W, A0_H);
let phoneFound = findImageMatchStrict(a0PhoneMat, A0_X, A0_Y, A0_W, A0_H);
if (!tapFound || !phoneFound) {
log.info("未同时检测到 tap 图标和手机图标 → 开始检测派蒙头像");
let paimonFound = findImageMatchRobust(paimonMat);
for (let i = 0; !paimonFound && i < 8; i++) {
log.info(`第 ${i + 1}/8 次未检测到派蒙头像,按 ESC 后重试...`);
keyPress("Escape");
await sleep(1000);
paimonFound = findImageMatchRobust(paimonMat);
}
if (!paimonFound) {
log.info("按 ESC 检测 8 次后仍未检测到派蒙头像 → 再严格检测一次开门页");
tapFound = findImageMatchStrict(tapMat, A0_X, A0_Y, A0_W, A0_H);
phoneFound = findImageMatchStrict(a0PhoneMat, A0_X, A0_Y, A0_W, A0_H);
if (!tapFound || !phoneFound) {
log.error("未检测到派蒙头像,最终也未同时检测到 tap 图标和手机图标,判定登录失败");
writeStatus("登录失败");
return;
}
log.info("最终严格检测到 tap 图标和手机图标 → 当前已在开门页,继续扫码流程");
}
if (paimonFound) {
// ========================================
// 已登录流程: 退出到开门页
// 找到派蒙→ESC关菜单→点55,1010→找exit_door→点击→等10s
// ========================================
log.info("检测到派蒙头像 → 已登录状态, 执行退出流程");
log.info(`找到派蒙头像 (x=${paimonFound.x}, y=${paimonFound.y}), 按 ESC 打开菜单...`);
keyPress("Escape");
await sleep(1000);
log.info("点击 (55, 1010) 打开派蒙菜单...");
click(55, 1010);
await sleep(1500);
log.info("查找退出图标 btn_exit_door...");
const doorFound = await waitForImage(exitDoorMat, 10 * 1000, 500);
if (doorFound) {
log.info("找到退出图标, 点击退出...");
clickImage(doorFound);
} else {
log.warn("未找到退出图标, 继续后续流程");
}
log.info("等待 8 秒, 等待退回到开门页...");
await sleep(8 * 1000);
log.info("退出流程完成, 重新严格检测 tap 图标 + 手机图标");
tapFound = findImageMatchStrict(tapMat, A0_X, A0_Y, A0_W, A0_H);
phoneFound = findImageMatchStrict(a0PhoneMat, A0_X, A0_Y, A0_W, A0_H);
if (!tapFound || !phoneFound) {
log.error("退出后仍未同时检测到 tap 图标和手机图标,判定登录失败");
writeStatus("登录失败");
return;
}
}
}
log.info("同时检测到 tap 图标和手机图标 → 准备进入扫码流程");
// ========================================
// A1: 进入扫码页
// 点击选号坐标 → 确认进入
// ========================================
log.info("===== A1: 进入扫码页 =====");
log.info("点击 (660, 250) 选择米游社账号...");
click(660, 250);
await sleep(750);
log.info("点击 (828, 691) 确认进入...");
click(828, 691);
log.info("A1 完成, 进入等待登录...");
// ========================================
// A2: 等待登录完成 → 点击画面进入游戏
// 等待 btn_logged_in_real 出现 (80秒超时)
// 找到后每秒点击 (960, 800), 每3秒检测派蒙头像
// ========================================
log.info("===== A2: 等待登录完成 =====");
log.info("等待扫码登录... 超时 80 秒");
const loginResult = await waitForImage(loggedInMat, 80 * 1000, 1000);
if (!loginResult) {
log.error("登录超时! 80 秒内未检测到登录完成图标");
writeStatus("登录失败");
return;
}
log.info(`检测到登录完成图标! (x=${loginResult.x}, y=${loginResult.y})`);
log.info("点击画面进入游戏...");
const CENTER_X = 960;
const CENTER_Y = 800;
let enteredGame = false;
for (let i = 0; i < 60; i++) {
click(CENTER_X, CENTER_Y);
await sleep(1000);
// 每 3 秒检测一次是否进入游戏 (匹配派蒙头像)
if (i % 3 === 2) {
const found = findImageMatch(paimonMat);
if (found) {
log.info(`检测到派蒙头像! 已进入游戏 (x=${found.x}, y=${found.y})`);
enteredGame = true;
break;
}
}
}
if (!enteredGame) {
log.error("60秒内未检测到最后的派蒙头像, 判定登录失败");
writeStatus("登录失败");
return;
}
// ========================================
// A3: 登录流程完成, 退出脚本
// ========================================
log.info("===== A3: 登录流程完成! =====");
log.info("扫码上号成功, 脚本退出");
// 写入"已登录"后按一次 ESC,收起菜单/退出可能残留的界面
writeStatus("已登录");
await sleep(500);
keyPress("Escape");
})();