元宝截图B站验证码示例测试
元宝截图B站验证码示例测试// AIWROK 自动化流程:截图B站验证码 → 启动元宝 → 发送截图询问拖动距离 → 采集回答 → 切回B站拖动滑块
// 包名:com.tencent.hunyuan.app.chat(元宝)
//
// 使用说明:
// 1. 在B站验证码页面运行脚本
// 2. 脚本自动截图并发送给元宝识别
// 3. 解析元宝返回的拖动距离,切回B站拖动滑块
print.log("===== 元宝识别验证码拖动B站流程启动 =====");
print.time();
// ========== 配置区 ==========
var 元宝包名 = "com.tencent.hunyuan.app.chat";
var B站包名 = "tv.danmaku.bili";
var 截图保存路径 = "/sdcard/验证码截图.png";
var 等待回答超时秒 = 20;
var 回答输出等待秒 = 5;
var 滑块校准系数 = 1.0;
var 提问话术 = "请帮我分析这张图片,告诉我左边的方块需要向右移动多少像素才能和右边的缺口对齐,只输出数字";
// ========== 获取设备信息 ==========
var 屏幕宽 = screen.getScreenWidth();
var 屏幕高 = screen.getScreenHeight();
var 验证码滑块距离 = null;
print.log("屏幕尺寸:" + 屏幕宽 + " x " + 屏幕高);
print.log("代理状态:" + (agent.isOpen() ? "已开启" : "未开启"));
// ========== 工具函数 ==========
function 获取UI() {
var xml = agent.getXml();
return "" + xml;
}
function 字符串长度(s) {
var str = "" + s;
return str.length;
}
function 解码HTML实体(s) {
var str = "" + s;
str = str.replace(/
/g, "\n");
str = str.replace(/
/g, "\r");
str = str.replace(/ /g, "\t");
str = str.replace(/'/g, "'");
str = str.replace(/"/g, '"');
str = str.replace(/"/g, '"');
str = str.replace(/'/g, "'");
str = str.replace(/&/g, "&");
str = str.replace(/</g, "<");
str = str.replace(/>/g, ">");
return str;
}
function 采集AI回答列表(xml) {
var xmlStr = "" + xml;
var 回答列表 = [];
var messageLinearlayoutPattern = /<node[^>]*?resource-id="[^"]*?message_linearlayout"[^>]*?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"/g;
var lineMatch;
while ((lineMatch = messageLinearlayoutPattern.exec(xmlStr)) !== null) {
var lineStart = lineMatch.index;
var lineEnd = xmlStr.indexOf('/node>', lineStart);
if (lineEnd === -1) lineEnd = xmlStr.length;
var lineContent = xmlStr.substring(lineStart, lineEnd);
if (lineContent.indexOf('chats_message_dtmp_answer_container') !== -1) {
var textViewPattern = /<node[^>]*?text="([^"]+)"[^>]*?class="android\.widget\.TextView"[^>]*?\/?>/g;
var tvMatch;
while ((tvMatch = textViewPattern.exec(lineContent)) !== null) {
if (tvMatch && 字符串长度(tvMatch) > 1) {
var 原始文本 = tvMatch;
var 解码文本 = 解码HTML实体(原始文本);
if (!回答列表.includes(解码文本)) {
回答列表.push(解码文本);
print.log("✓ 在回答容器中找到文本: '" + 解码文本 + "'");
}
}
}
}
}
if (回答列表.length === 0) {
print.log("⚠ 未找到AI回答节点");
} else {
print.log("✓ 采集到" + 回答列表.length + "条AI回答");
}
return 回答列表;
}
function 获取当前最后AI回答(xml) {
var 回答列表 = 采集AI回答列表(xml);
if (回答列表.length === 0) return "";
return 回答列表[回答列表.length - 1];
}
function 全屏截图(保存路径) {
try {
var result = agent.shell("screencap -p " + 保存路径);
print.log("✓ 截图命令执行:" + result);
var lsResult = agent.shell("ls -la " + 保存路径);
print.log("✓ 截图文件检查:" + lsResult);
return true;
} catch (e) {
print.log("⚠ 截图失败:" + e.message);
return false;
}
}
function 查找输入框中心(xml) {
var xmlStr = "" + xml;
var inputIdx = xmlStr.indexOf('edConversationInput');
if (inputIdx === -1) {
inputIdx = xmlStr.indexOf('text="发消息或按住说话..."');
}
if (inputIdx === -1) {
inputIdx = xmlStr.indexOf('class="android.widget.EditText"');
}
if (inputIdx !== -1) {
var 段 = xmlStr.substring(inputIdx);
var m = 段.match(/bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"/);
if (m) {
var x1 = parseInt(m);
var y1 = parseInt(m);
var x2 = parseInt(m);
var y2 = parseInt(m);
print.log("✓ 输入框定位:bounds=[" + x1 + "," + y1 + "][" + x2 + "," + y2 + "]");
return {
x: (x1 + x2) / 2,
y: (y1 + y2) / 2
};
}
}
print.log("⚠ 未找到输入框,使用兜底坐标");
return {
x: 屏幕宽 * 0.4,
y: 屏幕高 * 0.92
};
}
function 查找加号按钮(xml) {
var xmlStr = "" + xml;
var plusIdx = xmlStr.indexOf('flConversationInputPlus');
if (plusIdx !== -1) {
var 段 = xmlStr.substring(plusIdx);
var m = 段.match(/bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"/);
if (m) {
var x = (parseInt(m) + parseInt(m)) / 2;
var y = (parseInt(m) + parseInt(m)) / 2;
print.log("✓ 加号按钮定位:flConversationInputPlus,坐标(" + Math.round(x) + "," + Math.round(y) + ")");
return { x: x, y: y };
}
}
var cvPlusIdx = xmlStr.indexOf('cv_chat_input_plus');
if (cvPlusIdx !== -1) {
var 段2 = xmlStr.substring(cvPlusIdx);
var m2 = 段2.match(/bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"/);
if (m2) {
var x = (parseInt(m2) + parseInt(m2)) / 2;
var y = (parseInt(m2) + parseInt(m2)) / 2;
print.log("✓ 加号按钮定位:cv_chat_input_plus,坐标(" + Math.round(x) + "," + Math.round(y) + ")");
return { x: x, y: y };
}
}
var 输入框 = 查找输入框中心(xml);
if (输入框) {
var 预估X = 屏幕宽 * 0.92;
var 预估Y = 输入框.y;
print.log("⚠ 未找到加号按钮,使用输入框右侧兜底坐标(" + Math.round(预估X) + "," + Math.round(预估Y) + ")");
return { x: 预估X, y: 预估Y };
}
print.log("⚠ 加号按钮使用屏幕右侧底部兜底坐标");
return {
x: 屏幕宽 * 0.92,
y: 屏幕高 * 0.92
};
}
function 粘贴文本(文本) {
try {
Clipboard.copy("" + 文本);
} catch (e) {
print.log("⚠ Clipboard.copy 失败:" + e.message);
}
sleep.millisecond(毫秒 = 300);
try {
agent.shell("service call clipboard 3 i32 1 s16 '" + 文本 + "' i32 0");
} catch (e2) {
// 忽略错误
}
sleep.millisecond(毫秒 = 300);
agent.shell("input keyevent 279");
sleep.second(秒 = 1);
print.log("✓ 已粘贴文本:" + 文本);
}
function 查找相册选项(xml) {
var xmlStr = "" + xml;
var text关键词 = ["相册", "图片", "照片", "所有照片", "从相册选择", "选择图片"];
for (var i = 0; i < text关键词.length; i++) {
var kw = text关键词;
var idx = xmlStr.indexOf('text="' + kw + '"');
if (idx !== -1) {
var 段 = xmlStr.substring(idx);
var m = 段.match(/bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"/);
if (m) {
print.log("✓ 相册选项定位:text=" + kw);
return {
x: (parseInt(m) + parseInt(m)) / 2,
y: (parseInt(m) + parseInt(m)) / 2
};
}
}
}
var desc关键词 = ["相册", "图片", "照片"];
for (var j = 0; j < desc关键词.length; j++) {
var kw2 = desc关键词;
var idx2 = xmlStr.indexOf('content-desc="' + kw2 + '"');
if (idx2 !== -1) {
var 段2 = xmlStr.substring(idx2);
var m2 = 段2.match(/bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"/);
if (m2) {
print.log("✓ 相册选项定位:content-desc=" + kw2);
return {
x: (parseInt(m2) + parseInt(m2)) / 2,
y: (parseInt(m2) + parseInt(m2)) / 2
};
}
}
}
return null;
}
function 是否在系统相册页面(xml) {
var xmlStr = "" + xml;
var packageMatch = xmlStr.match(/package="([^"]+)"/);
var 当前包名 = packageMatch ? packageMatch : "";
if (当前包名 && 当前包名 !== 元宝包名) {
print.log("✓ 检测到系统相册页面,包名:" + 当前包名);
return true;
}
var gallery关键词 = ["最近", "图库", "Camera", "所有图片", "照片", "相册", "All photos", "Photos"];
for (var i = 0; i < gallery关键词.length; i++) {
if (xmlStr.indexOf('text="' + gallery关键词 + '"') !== -1 ||
xmlStr.indexOf('content-desc="' + gallery关键词 + '"') !== -1) {
var 有聊天输入 = xmlStr.indexOf("edConversationInput") !== -1;
if (!有聊天输入) {
print.log("✓ 检测到相册页面(关键词:" + gallery关键词 + ")");
return true;
}
}
}
var 有图片文件 = xmlStr.indexOf('.png"') !== -1 || xmlStr.indexOf('.jpg"') !== -1 ||
xmlStr.indexOf('.jpeg"') !== -1 || xmlStr.indexOf('.gif"') !== -1;
var 有选择按钮 = xmlStr.indexOf('text="选择"') !== -1;
var 有聊天输入2 = xmlStr.indexOf("edConversationInput") !== -1;
var 有聊天列表 = xmlStr.indexOf("chat_recycler_view") !== -1;
if (有图片文件 && 有选择按钮 && !有聊天输入2 && !有聊天列表) {
print.log("✓ 检测到相册页面(元宝ComposeView,有图片文件和选择按钮)");
return true;
}
return false;
}
function 是否在图片预览页面(xml) {
var xmlStr = "" + xml;
if (xmlStr.indexOf('text="完成"') !== -1 || xmlStr.indexOf('text="发送"') !== -1) {
return true;
}
if (xmlStr.indexOf('content-desc="完成"') !== -1 || xmlStr.indexOf('content-desc="发送"') !== -1) {
return true;
}
return false;
}
function 查找图片预览确认按钮(xml) {
var xmlStr = "" + xml;
var 关键词 = ["完成", "发送", "确认"];
for (var i = 0; i < 关键词.length; i++) {
var kw = 关键词;
var textIdx = xmlStr.indexOf('text="' + kw + '"');
if (textIdx !== -1) {
var 段 = xmlStr.substring(textIdx);
var m = 段.match(/bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"/);
if (m) {
var x = (parseInt(m) + parseInt(m)) / 2;
var y = (parseInt(m) + parseInt(m)) / 2;
print.log("✓ 图片预览确认按钮定位:text=" + kw + ",坐标(" + Math.round(x) + "," + Math.round(y) + ")");
return { x: x, y: y };
}
}
}
for (var j = 0; j < 关键词.length; j++) {
var kw2 = 关键词;
var descIdx = xmlStr.indexOf('content-desc="' + kw2 + '"');
if (descIdx !== -1) {
var 段2 = xmlStr.substring(descIdx);
var m2 = 段2.match(/bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"/);
if (m2) {
var x = (parseInt(m2) + parseInt(m2)) / 2;
var y = (parseInt(m2) + parseInt(m2)) / 2;
print.log("✓ 图片预览确认按钮定位:content-desc=" + kw2 + ",坐标(" + Math.round(x) + "," + Math.round(y) + ")");
return { x: x, y: y };
}
}
}
print.log("⚠ 图片预览确认按钮使用兜底坐标(底部中间)");
return { x: 屏幕宽 * 0.5, y: 屏幕高 * 0.92 };
}
function 查找相册选择按钮(xml) {
var xmlStr = "" + xml;
var 关键词列表 = ["选择", "完成", "确认", "发送", "确定"];
for (var i = 0; i < 关键词列表.length; i++) {
var kw = 关键词列表;
var textPattern = new RegExp('text="' + kw + '[^"]*"[^>]*?bounds="\\[(\\d+),(\\d+)\\]\\[(\\d+),(\\d+)\\]"', 'gi');
var textMatch = textPattern.exec(xmlStr);
if (textMatch) {
var x = (parseInt(textMatch) + parseInt(textMatch)) / 2;
var y = (parseInt(textMatch) + parseInt(textMatch)) / 2;
print.log("✓ 相册选择按钮定位:text=" + kw + ",坐标(" + Math.round(x) + "," + Math.round(y) + ")");
return { x: x, y: y };
}
var descIdx = xmlStr.indexOf('content-desc="' + kw + '"');
if (descIdx !== -1) {
var 段2 = xmlStr.substring(descIdx);
var m2 = 段2.match(/bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"/);
if (m2) {
var x = (parseInt(m2) + parseInt(m2)) / 2;
var y = (parseInt(m2) + parseInt(m2)) / 2;
print.log("✓ 相册选择按钮定位:content-desc=" + kw + ",坐标(" + Math.round(x) + "," + Math.round(y) + ")");
return { x: x, y: y };
}
}
}
var bottomButtonPattern = /<node[^>]*?clickable="true"[^>]*?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"/g;
var bottomMatch;
var 底部按钮列表 = [];
while ((bottomMatch = bottomButtonPattern.exec(xmlStr)) !== null) {
var bx1 = parseInt(bottomMatch);
var by1 = parseInt(bottomMatch);
var bx2 = parseInt(bottomMatch);
var by2 = parseInt(bottomMatch);
if (by1 >= 屏幕高 * 0.85) {
底部按钮列表.push({ x: (bx1 + bx2) / 2, y: (by1 + by2) / 2 });
}
}
if (底部按钮列表.length > 0) {
底部按钮列表.sort(function(a, b) { return b.x - a.x; });
var 最右边按钮 = 底部按钮列表;
print.log("✓ 相册选择按钮定位:底部最右边按钮,坐标(" + Math.round(最右边按钮.x) + "," + Math.round(最右边按钮.y) + ")");
return { x: 最右边按钮.x, y: 最右边按钮.y };
}
print.log("⚠ 相册选择按钮使用兜底坐标(底部)");
return { x: 屏幕宽 * 0.78, y: 屏幕高 * 0.92 };
}
function 查找第一张图片(xml) {
var xmlStr = "" + xml;
if (是否在系统相册页面(xmlStr)) {
agent.shell("echo '" + xmlStr.substring(0, 5000) + "' > /sdcard/相册UI_dump.xml");
print.log("✓ 已保存相册UI XML到/sdcard/相册UI_dump.xml");
var nafPattern = /<node[^>]*?NAF="true"[^>]*?clickable="true"[^>]*?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"/g;
var nafMatch;
var NAF小圆点列表 = [];
while ((nafMatch = nafPattern.exec(xmlStr)) !== null) {
var nx1 = parseInt(nafMatch);
var ny1 = parseInt(nafMatch);
var nx2 = parseInt(nafMatch);
var ny2 = parseInt(nafMatch);
var nw = nx2 - nx1;
var nh = ny2 - ny1;
if (nw >= 50 && nw <= 300 && nh >= 50 && nh <= 300) {
if (ny1 >= 400 && ny2 <= 屏幕高 - 400) {
NAF小圆点列表.push({ x1: nx1, y1: ny1, x2: nx2, y2: ny2, x: (nx1 + nx2) / 2, y: (ny1 + ny2) / 2 });
}
}
}
if (NAF小圆点列表.length > 0) {
NAF小圆点列表.sort(function(a, b) {
if (a.y1 !== b.y1) return a.y1 - b.y1;
return a.x1 - b.x1;
});
var 选中小圆点 = NAF小圆点列表;
print.log("✓ ComposeView找到NAF小圆点选择框:坐标(" + Math.round(选中小圆点.x) + "," + Math.round(选中小圆点.y) + "),bounds=[" + 选中小圆点.x1 + "," + 选中小圆点.y1 + "][" + 选中小圆点.x2 + "," + 选中小圆点.y2 + "]");
return { x: 选中小圆点.x, y: 选中小圆点.y };
}
var imageDescPattern = /<node[^>]*?content-desc="([^"]+\.(png|jpg|jpeg|gif))"[^>]*?class="android\.view\.View"[^>]*?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"/gi;
var imgMatch;
var 图片节点列表 = [];
while ((imgMatch = imageDescPattern.exec(xmlStr)) !== null) {
var 文件名 = imgMatch;
var ix1 = parseInt(imgMatch);
var iy1 = parseInt(imgMatch);
var ix2 = parseInt(imgMatch);
var iy2 = parseInt(imgMatch);
var iw = ix2 - ix1;
var ih = iy2 - iy1;
if (iw >= 100 && ih >= 100) {
图片节点列表.push({ 文件名: 文件名, x1: ix1, y1: iy1, x2: ix2, y2: iy2, w: iw, h: ih });
}
}
if (图片节点列表.length > 0) {
图片节点列表.sort(function(a, b) {
if (a.y1 !== b.y1) return a.y1 - b.y1;
return a.x1 - b.x1;
});
var 第一张图片 = 图片节点列表;
print.log("✓ 找到图片:" + 第一张图片.文件名 + ",bounds=[" + 第一张图片.x1 + "," + 第一张图片.y1 + "][" + 第一张图片.x2 + "," + 第一张图片.y2 + "]");
var 选择框X = 第一张图片.x1 + 第一张图片.w * 0.77;
var 选择框Y = 第一张图片.y1 + 第一张图片.h * 0.24;
print.log("✓ 计算小圆点选择框坐标:(" + Math.round(选择框X) + "," + Math.round(选择框Y) + ")");
return { x: 选择框X, y: 选择框Y };
}
var clickableViewPattern = /<node[^>]*?clickable="true"[^>]*?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"/g;
var clickMatch;
var 可点击区域 = [];
while ((clickMatch = clickableViewPattern.exec(xmlStr)) !== null) {
var cx1 = parseInt(clickMatch);
var cy1 = parseInt(clickMatch);
var cx2 = parseInt(clickMatch);
var cy2 = parseInt(clickMatch);
var cw = cx2 - cx1;
var ch = cy2 - cy1;
可点击区域.push({ x1: cx1, y1: cy1, x2: cx2, y2: cy2, w: cw, h: ch, x: (cx1 + cx2) / 2, y: (cy1 + cy2) / 2 });
}
var 图片区域内可点击 = [];
for (var k = 0; k < 可点击区域.length; k++) {
var item = 可点击区域;
if (item.y >= 400 && item.y <= 屏幕高 - 400) {
图片区域内可点击.push(item);
}
}
if (图片区域内可点击.length > 0) {
图片区域内可点击.sort(function(a, b) {
if (a.y !== b.y) return a.y - b.y;
return a.x - b.x;
});
var 第一个可点击 = 图片区域内可点击;
print.log("✓ ComposeView图片区域内第一个可点击元素:坐标(" + Math.round(第一个可点击.x) + "," + Math.round(第一个可点击.y) + ")");
return { x: 第一个可点击.x, y: 第一个可点击.y };
}
print.log("⚠ 系统相册未找到图片,输出调试信息:");
print.log("当前包名:" + xmlStr.match(/package="([^"]+)"/));
print.log("XML前1000字符:" + xmlStr.substring(0, 1000));
print.log("NAF小圆点数量:" + NAF小圆点列表.length);
print.log("图片节点数量:" + 图片节点列表.length);
var allImgViews = xmlStr.match(/class="android\.widget\.ImageView"/g);
print.log("全部ImageView数量:" + (allImgViews ? allImgViews.length : 0));
return { x: 屏幕宽 * 0.25, y: 屏幕高 * 0.3 };
}
var rvPattern = /<node[^>]*?class="androidx\.recyclerview\.widget\.RecyclerView"[^>]*?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"/g;
var rvMatch;
var 图片列表区域 = null;
while ((rvMatch = rvPattern.exec(xmlStr)) !== null) {
var rx1 = parseInt(rvMatch);
var ry1 = parseInt(rvMatch);
var rx2 = parseInt(rvMatch);
var ry2 = parseInt(rvMatch);
var 高度 = ry2 - ry1;
if (ry1 > 屏幕高 * 0.7 && 高度 > 300) {
图片列表区域 = {x1: rx1, y1: ry1, x2: rx2, y2: ry2};
print.log("✓ 找到底部图片列表RecyclerView:[" + rx1 + "," + ry1 + "][" + rx2 + "," + ry2 + "]");
break;
}
}
if (图片列表区域) {
var imgPattern = /<node[^>]*?class="android\.widget\.ImageView"[^>]*?\/?>/g;
var imgMatch;
var 候选 = [];
while ((imgMatch = imgPattern.exec(xmlStr)) !== null) {
var imgNode = imgMatch;
var boundsMatch = imgNode.match(/bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"/);
if (!boundsMatch) continue;
var ix1 = parseInt(boundsMatch);
var iy1 = parseInt(boundsMatch);
var ix2 = parseInt(boundsMatch);
var iy2 = parseInt(boundsMatch);
var iw = ix2 - ix1;
var ih = iy2 - iy1;
var 在区域内 = (ix1 >= 图片列表区域.x1 && ix2 <= 图片列表区域.x2 &&
iy1 >= 图片列表区域.y1 && iy2 <= 图片列表区域.y2);
var 是大图 = (iw > 100 && ih > 100);
if (在区域内 && 是大图) {
候选.push({
x: (ix1 + ix2) / 2,
y: (iy1 + iy2) / 2,
y1: iy1,
x1: ix1
});
}
}
if (候选.length > 0) {
候选.sort(function(a, b) {
if (a.y1 !== b.y1) return a.y1 - b.y1;
return a.x1 - b.x1;
});
var 选中 = 候选;
print.log("✓ 第一张图片定位:底部图片列表内的ImageView,坐标(" + Math.round(选中.x) + "," + Math.round(选中.y) + ")");
return { x: 选中.x, y: 选中.y };
}
}
var imgPattern2 = /<node[^>]*?class="android\.widget\.ImageView"[^>]*?\/?>/g;
var imgMatch2;
var 候选2 = [];
while ((imgMatch2 = imgPattern2.exec(xmlStr)) !== null) {
var imgNode = imgMatch2;
var boundsMatch = imgNode.match(/bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"/);
if (!boundsMatch) continue;
var ix1 = parseInt(boundsMatch);
var iy1 = parseInt(boundsMatch);
var ix2 = parseInt(boundsMatch);
var iy2 = parseInt(boundsMatch);
var iw = ix2 - ix1;
var ih = iy2 - iy1;
if (iy1 > 屏幕高 * 0.7 && iw > 100 && ih > 100) {
候选2.push({
x: (ix1 + ix2) / 2,
y: (iy1 + iy2) / 2,
y1: iy1,
x1: ix1
});
}
}
if (候选2.length > 0) {
候选2.sort(function(a, b) {
if (a.y1 !== b.y1) return a.y1 - b.y1;
return a.x1 - b.x1;
});
var 选中2 = 候选2;
print.log("✓ 第一张图片定位:底部区域ImageView,坐标(" + Math.round(选中2.x) + "," + Math.round(选中2.y) + ")");
return { x: 选中2.x, y: 选中2.y };
}
print.log("⚠ 未找到图片,使用兜底坐标(底部第一行第一列)");
return { x: 屏幕宽 * 0.15, y: 屏幕高 * 0.85 };
}
function 查找发送按钮中心(xml) {
var xmlStr = "" + xml;
var sendTextIdx = xmlStr.indexOf('text="发送"');
if (sendTextIdx !== -1) {
var 段 = xmlStr.substring(sendTextIdx);
var m = 段.match(/bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"/);
if (m) {
print.log("✓ 发送按钮定位:文本匹配");
return {
x: (parseInt(m) + parseInt(m)) / 2,
y: (parseInt(m) + parseInt(m)) / 2
};
}
}
var inputIdx = xmlStr.indexOf('edConversationInput');
if (inputIdx !== -1) {
var 输入框段 = xmlStr.substring(inputIdx);
var 输入框边界 = 输入框段.match(/bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"/);
if (输入框边界) {
var 输入框信息 = {
x1: parseInt(输入框边界),
y1: parseInt(输入框边界),
x2: parseInt(输入框边界),
y2: parseInt(输入框边界),
centerY: (parseInt(输入框边界) + parseInt(输入框边界)) / 2
};
var clickablePattern = /<node[^>]*?clickable="true"[^>]*?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"[^>]*?\/?>/g;
var clickMatch;
var candidates = [];
while ((clickMatch = clickablePattern.exec(xmlStr)) !== null) {
var cx1 = parseInt(clickMatch);
var cy1 = parseInt(clickMatch);
var cx2 = parseInt(clickMatch);
var cy2 = parseInt(clickMatch);
var cw = cx2 - cx1;
var ch = cy2 - cy1;
var 是小按钮 = (cw < 300 && ch < 300);
var 在输入框右侧 = (cx1 > 输入框信息.x2 - 100);
var 在输入框水平行 = (cy1 >= 输入框信息.y1 - 100 && cy1 <= 输入框信息.y2 + 100);
if (是小按钮 && 在输入框右侧 && 在输入框水平行) {
candidates.push({
x1: cx1, y1: cy1, x2: cx2, y2: cy2,
x: (cx1 + cx2) / 2,
y: (cy1 + cy2) / 2
});
}
}
if (candidates.length > 0) {
var 发送按钮候选 = [];
for (var ci = 0; ci < candidates.length; ci++) {
var cand = candidates;
if (cand.y1 >= 输入框信息.y1 + 300) {
发送按钮候选.push(cand);
}
}
if (发送按钮候选.length > 0) {
发送按钮候选.sort(function(a, b) {
return b.x1 - a.x1;
});
var 选中 = 发送按钮候选;
print.log("✓ 发送按钮定位:输入框右侧下方clickable元素,坐标(" + Math.round(选中.x) + "," + Math.round(选中.y) + ")");
return { x: 选中.x, y: 选中.y };
}
candidates.sort(function(a, b) {
return b.x1 - a.x1;
});
var 选中 = candidates;
print.log("✓ 发送按钮定位:输入框右侧clickable元素,坐标(" + Math.round(选中.x) + "," + Math.round(选中.y) + ")");
return { x: 选中.x, y: 选中.y };
}
var 预估X = 输入框信息.x2 + 100;
var 预估Y = 输入框信息.centerY;
print.log("⚠ 发送按钮使用输入框右侧兜底坐标(" + Math.round(预估X) + "," + Math.round(预估Y) + ")");
return { x: 预估X, y: 预估Y };
}
}
print.log("⚠ 发送按钮使用屏幕右侧兜底策略");
return {
x: 屏幕宽 * 0.92,
y: 屏幕高 * 0.92
};
}
function 等待AI回答完成(发送前的最后文本, 发送前回答数量) {
var 已耗时 = 0;
var 检查间隔秒 = 1;
print.log("发送前最后AI回答:'" + 发送前的最后文本 + "',发送前回答数量:" + 发送前回答数量);
while (已耗时 < 等待回答超时秒) {
sleep.second(秒 = 检查间隔秒);
已耗时 += 检查间隔秒;
xml = 获取UI();
var 回答列表 = 采集AI回答列表(xml);
if (回答列表.length > 0) {
var 当前最后回答 = 回答列表[回答列表.length - 1];
var 有新回答 = (当前最后回答 !== 发送前的最后文本);
var 回答数量增加 = (回答列表.length > 发送前回答数量);
if (有新回答 || 回答数量增加) {
print.log("✓ 检测到新回答(" + 已耗时 + "秒):'" + 当前最后回答 + "',数量:" + 回答列表.length);
var 等待完成 = 0;
var 上次长度 = 字符串长度(当前最后回答);
while (等待完成 < 回答输出等待秒) {
sleep.second(秒 = 1);
等待完成++;
xml = 获取UI();
var 当前列表 = 采集AI回答列表(xml);
if (当前列表.length > 0) {
var 当前文本 = 当前列表[当前列表.length - 1];
if (字符串长度(当前文本) === 上次长度) {
print.log("✓ 文本停止变化,返回采集结果:" + 当前文本);
return 当前文本;
}
上次长度 = 字符串长度(当前文本);
}
}
var 最终列表 = 采集AI回答列表(xml);
if (最终列表.length > 0) {
var 最终文本 = 最终列表[最终列表.length - 1];
print.log("✓ 采集成功,返回结果:" + 最终文本);
return 最终文本;
}
}
}
}
print.log("⚠ 等待AI回答超时(" + 等待回答超时秒 + "秒),尝试返回当前内容");
xml = 获取UI();
var 兜底列表 = 采集AI回答列表(xml);
if (兜底列表.length > 0) {
var 兜底文本 = 兜底列表[兜底列表.length - 1];
print.log("✓ 超时兜底:返回当前采集到的回答:" + 兜底文本);
return 兜底文本;
}
return null;
}
// ========== 主流程开始 ==========
function 主流程() {
// 步骤1:全屏截图(B站验证码页面)
print.log("\n========== 步骤1:全屏截图B站验证码 ==========");
var 截图成功 = 全屏截图(截图保存路径);
if (!截图成功) {
print.log("✗ 截图失败,退出流程");
print.log("===== 元宝识别验证码拖动B站流程结束 =====");
return;
}
// 步骤2:启动元宝APP
print.log("\n========== 步骤2:启动元宝APP ==========");
try {
app.openApp(元宝包名);
print.log("✓ 已启动元宝");
} catch (e) {
print.log("⚠ 启动失败:" + e.message);
}
sleep.second(秒 = 5);
// 步骤3:确认进入对话页面
print.log("\n========== 步骤3:确认进入对话页面 ==========");
var xml = 获取UI();
var hasRecyclerView = (xml.indexOf("chat_recycler_view") !== -1);
if (!hasRecyclerView) {
print.log("⚠ 当前页面可能不是元宝对话页,再次等待");
sleep.second(秒 = 3);
xml = 获取UI();
hasRecyclerView = (xml.indexOf("chat_recycler_view") !== -1);
}
if (hasRecyclerView) {
print.log("✓ 已进入元宝对话页面");
} else {
print.log("⚠ 未检测到消息列表,继续尝试");
}
// 步骤4:记录发送前的最后一条AI回答和数量
var 发送前回答列表 = 采集AI回答列表(xml);
var 发送前回答数量 = 发送前回答列表.length;
var 发送前最后文本 = "";
if (发送前回答列表.length > 0) {
发送前最后文本 = 发送前回答列表[发送前回答列表.length - 1];
}
print.log("✓ 发送前回答数量:" + 发送前回答数量 + ",最后文本:'" + 发送前最后文本 + "'");
// 步骤5:点击加号按钮打开附件菜单
print.log("\n========== 步骤5:点击加号按钮打开附件菜单 ==========");
var 加号按钮 = 查找加号按钮(xml);
agent.click(加号按钮.x, 加号按钮.y);
print.log("✓ 已点击加号按钮,坐标(" + Math.round(加号按钮.x) + "," + Math.round(加号按钮.y) + ")");
sleep.second(秒 = 2);
// 步骤6:选择相册或图片
print.log("\n========== 步骤6:选择相册或图片 ==========");
var 相册选择成功 = false;
var 尝试次数 = 0;
while (!相册选择成功 && 尝试次数 < 5) {
尝试次数++;
xml = 获取UI();
if (是否在系统相册页面(xml)) {
print.log("✓ 已进入系统相册页面");
相册选择成功 = true;
break;
}
var 相册选项 = 查找相册选项(xml);
if (相册选项) {
agent.click(相册选项.x, 相册选项.y);
print.log("✓ 已点击相册选项,坐标(" + Math.round(相册选项.x) + "," + Math.round(相册选项.y) + ")");
sleep.second(秒 = 2);
相册选择成功 = true;
break;
}
var imgCount = (xml.match(/class="android\.widget\.ImageView"/g) || []).length;
if (imgCount >= 4) {
print.log("✓ 加号菜单底部已显示图片列表(检测到" + imgCount + "个图片)");
相册选择成功 = true;
break;
}
print.log("⚠ 第" + 尝试次数 + "次尝试:未找到相册选项,等待重试");
sleep.second(秒 = 1);
}
// 步骤7:选择第一张图片(处理系统相册和图片预览页面)
print.log("\n========== 步骤7:选择第一张图片 ==========");
var 图片选择成功 = false;
var 图片尝试次数 = 0;
while (!图片选择成功 && 图片尝试次数 < 10) {
图片尝试次数++;
xml = 获取UI();
if (图片尝试次数 === 1) {
var pkgMatch = xml.match(/package="([^"]+)"/);
print.log("调试:当前包名=" + (pkgMatch ? pkgMatch : "未知"));
print.log("调试:XML前300字符=" + xml.substring(0, 300));
print.log("调试:是否有edConversationInput=" + (xml.indexOf("edConversationInput") !== -1));
print.log("调试:是否有chat_recycler_view=" + (xml.indexOf("chat_recycler_view") !== -1));
var dbgImgCount = (xml.match(/class="android\.widget\.ImageView"/g) || []).length;
print.log("调试:ImageView数量=" + dbgImgCount);
}
if (是否在图片预览页面(xml)) {
var 确认按钮 = 查找图片预览确认按钮(xml);
agent.click(确认按钮.x, 确认按钮.y);
print.log("✓ 已点击图片预览确认按钮,坐标(" + Math.round(确认按钮.x) + "," + Math.round(确认按钮.y) + ")");
图片选择成功 = true;
sleep.second(秒 = 3);
break;
}
if (是否在系统相册页面(xml)) {
var 第一张图片 = 查找第一张图片(xml);
if (第一张图片) {
agent.click(第一张图片.x, 第一张图片.y);
print.log("✓ 已点击第一张图片右上角小圆点,坐标(" + Math.round(第一张图片.x) + "," + Math.round(第一张图片.y) + ")");
sleep.second(秒 = 2);
xml = 获取UI();
var 选择按钮 = 查找相册选择按钮(xml);
if (选择按钮) {
agent.click(选择按钮.x, 选择按钮.y);
print.log("✓ 已点击选择按钮,坐标(" + Math.round(选择按钮.x) + "," + Math.round(选择按钮.y) + ")");
图片选择成功 = true;
sleep.second(秒 = 3);
break;
} else {
print.log("⚠ 第" + 图片尝试次数 + "次:未找到选择按钮,重新获取XML重试");
sleep.second(秒 = 1);
continue;
}
} else {
print.log("⚠ 系统相册未找到图片");
sleep.second(秒 = 1);
continue;
}
}
print.log("⚠ 第" + 图片尝试次数 + "次尝试:未检测到相册页面,等待重试");
sleep.second(秒 = 1);
}
// 步骤8:等待图片添加到输入区域(等待回到元宝对话页面)
print.log("\n========== 步骤8:等待图片添加到输入区域 ==========");
var 图片添加成功 = false;
var 等待尝试次数 = 0;
while (!图片添加成功 && 等待尝试次数 < 10) {
等待尝试次数++;
xml = 获取UI();
var packageMatch = xml.match(/package="([^"]+)"/);
if (packageMatch && packageMatch === 元宝包名) {
print.log("✓ 已回到元宝对话页面");
if (xml.indexOf("edConversationInput") !== -1) {
print.log("✓ 图片已添加到输入区域");
图片添加成功 = true;
break;
}
}
print.log("⚠ 第" + 等待尝试次数 + "次尝试:图片尚未添加,等待重试");
sleep.second(秒 = 1);
}
if (!图片添加成功) {
print.log("⚠ 多次等待图片未添加,继续后续步骤");
}
// 步骤9:粘贴文字询问拖动距离
print.log("\n========== 步骤9:粘贴文字询问拖动距离 ==========");
xml = 获取UI();
var 输入框 = 查找输入框中心(xml);
var AI回答 = null;
if (输入框) {
agent.click(输入框.x, 输入框.y);
sleep.second(秒 = 1);
粘贴文本(提问话术);
print.log("✓ 已粘贴文本:" + 提问话术);
sleep.second(秒 = 2);
// 步骤10:发送图片和文字
print.log("\n========== 步骤10:发送图片和文字 ==========");
xml = 获取UI();
var 发送按钮 = 查找发送按钮中心(xml);
if (发送按钮) {
agent.click(发送按钮.x, 发送按钮.y);
print.log("✓ 已点击发送按钮,坐标(" + Math.round(发送按钮.x) + "," + Math.round(发送按钮.y) + ")");
} else {
agent.shell("input keyevent 66");
print.log("⚠ 未找到发送按钮,已尝试按回车发送");
}
sleep.second(秒 = 3);
// 步骤11:等待元宝AI回答
print.log("\n========== 步骤11:等待元宝AI回答 ==========");
AI回答 = 等待AI回答完成(发送前最后文本, 发送前回答数量);
} else {
print.log("⚠ 未找到输入框");
}
if (!AI回答) {
print.log("✗ 未获取到AI回答");
print.log("===== 元宝识别验证码拖动B站流程结束 =====");
return;
}
print.log("\n✓ 采集到AI回答");
print.log("--- 采集内容开始 ---");
print.log(AI回答);
print.log("--- 采集内容结束 ---");
// 步骤12:解析拖动距离坐标
print.log("\n========== 步骤12:解析拖动距离坐标 ==========");
print.log("✓ AI原始回答:'" + AI回答 + "'");
var 所有数字 = AI回答.match(/\d+/g);
var 拖动距离像素 = null;
var AI图像宽度 = null;
if (所有数字 && 所有数字.length >= 2) {
var 数字列表 = [];
for (var i = 0; i < 所有数字.length; i++) {
数字列表.push(parseInt(所有数字));
}
print.log("✓ 提取到所有数字:" + 数字列表.join(", "));
if (数字列表.length >= 5) {
var x1 = 数字列表;
var y1 = 数字列表;
var x2 = 数字列表;
var y2 = 数字列表;
AI图像宽度 = 数字列表;
拖动距离像素 = Math.abs(x2 - x1);
print.log("✓ 识别到两组坐标+图像宽度:(" + x1 + "," + y1 + ") 和 (" + x2 + "," + y2 + "),图像宽度=" + AI图像宽度);
print.log("✓ 计算拖动距离:|x2 - x1| = |" + x2 + " - " + x1 + "| = " + 拖动距离像素);
} else if (数字列表.length >= 4) {
var x1 = 数字列表;
var y1 = 数字列表;
var x2 = 数字列表;
var y2 = 数字列表;
拖动距离像素 = Math.abs(x2 - x1);
AI图像宽度 = Math.max(x1, x2) + 100;
print.log("✓ 识别到两组坐标格式:(" + x1 + "," + y1 + ") 和 (" + x2 + "," + y2 + ")");
print.log("✓ 计算拖动距离:|x2 - x1| = |" + x2 + " - " + x1 + "| = " + 拖动距离像素);
print.log("✓ 估算图像宽度:max(x1,x2)+100 = " + AI图像宽度);
} else if (数字列表.length >= 2) {
var 坐标匹配 = AI回答.match(/(\d+)\s*[,,]\s*(\d+)/);
if (坐标匹配) {
var cx1 = parseInt(坐标匹配);
var cx2 = parseInt(坐标匹配);
拖动距离像素 = Math.abs(cx2 - cx1);
AI图像宽度 = Math.max(cx1, cx2) + 100;
print.log("✓ 识别到坐标格式:(" + cx1 + "," + cx2 + "),计算距离:" + 拖动距离像素);
} else {
拖动距离像素 = 数字列表;
print.log("✓ 提取到单个数字(拖动距离):" + 拖动距离像素);
}
} else {
拖动距离像素 = 数字列表;
print.log("✓ 提取到单个数字(拖动距离):" + 拖动距离像素);
}
} else if (所有数字 && 所有数字.length === 1) {
拖动距离像素 = parseInt(所有数字);
print.log("✓ 提取到单个数字(拖动距离):" + 拖动距离像素);
} else {
print.log("✗ 未找到有效的拖动距离");
print.log("===== 元宝识别验证码拖动B站流程结束 =====");
return;
}
var 图像缩放系数 = 1.0;
if (AI图像宽度 && AI图像宽度 > 0 && AI图像宽度 < 屏幕宽) {
图像缩放系数 = 屏幕宽 / AI图像宽度;
if (图像缩放系数 > 5) {
图像缩放系数 = 1.0;
} else {
拖动距离像素 = Math.round(拖动距离像素 * 图像缩放系数);
print.log("✓ 图像缩放补偿:AI图像宽度=" + AI图像宽度 + ",屏幕宽=" + 屏幕宽);
print.log("✓ 缩放系数=" + 图像缩放系数.toFixed(2) + ",补偿后拖动距离=" + 拖动距离像素);
}
} else if (拖动距离像素 < 200) {
图像缩放系数 = 屏幕宽 / 750;
拖动距离像素 = Math.round(拖动距离像素 * 图像缩放系数);
print.log("✓ 单数字模式补偿:假设AI图像宽度≈750,缩放系数=" + 图像缩放系数.toFixed(2));
print.log("✓ 补偿后拖动距离=" + 拖动距离像素);
}
验证码滑块距离 = {
x: 拖动距离像素,
y: 0
};
// 步骤13:切换回B站并拖动滑块
print.log("\n========== 步骤13:切换回B站并拖动滑块 ==========");
agent.shell("input keyevent 187");
print.log("✓ 已按后台键打开任务列表");
sleep.second(秒 = 2);
xml = 获取UI();
var biliIdx = xml.indexOf('哔哩哔哩');
if (biliIdx === -1) {
biliIdx = xml.indexOf('bilibili');
}
if (biliIdx === -1) {
biliIdx = xml.indexOf('B站');
}
if (biliIdx !== -1) {
var 段 = xml.substring(biliIdx);
var m = 段.match(/bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"/);
if (m) {
var clickX = (parseInt(m) + parseInt(m)) / 2;
var clickY = (parseInt(m) + parseInt(m)) / 2;
agent.click(clickX, clickY);
print.log("✓ 已点击B站任务卡片,坐标(" + Math.round(clickX) + "," + Math.round(clickY) + ")");
}
} else {
print.log("⚠ 未找到B站任务卡片,尝试直接打开");
try {
app.openApp(B站包名);
print.log("✓ 已打开B站");
} catch (e) {
print.log("⚠ 打开B站失败:" + e.message);
}
}
sleep.second(秒 = 3);
xml = 获取UI();
var 验证码区域左边界 = 屏幕宽 * 0.15;
var 滑块轨道匹配 = xml.match(/<node[^>]*?text="拖动滑块完成拼图"[^>]*?bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"/);
if (滑块轨道匹配) {
验证码区域左边界 = parseInt(滑块轨道匹配);
}
var 拖动距离 = 验证码滑块距离.x * 滑块校准系数;
var startX = 验证码区域左边界 + 50;
var startY = 屏幕高 * 0.42;
var endX = startX + 拖动距离;
var endY = startY;
print.log("✓ 验证码区域左边界: " + 验证码区域左边界 + ", 拖动距离: " + 拖动距离);
agent.swip(startX, startY, endX, endY, 2000);
print.log("✓ 已拖动滑块,起点(" + Math.round(startX) + "," + Math.round(startY) + "),终点(" + Math.round(endX) + "," + Math.round(endY) + ")");
}
主流程();
print.log("\n===== 元宝识别验证码拖动B站流程结束 =====");
页:
[1]