|
|
示例小红书发图文代理模式不用开发者
- // AIWROK 完整自动化流程:启动小红书 → 点击加号 → 选择相册 → 选图 → 自动发图片笔记
- // 全程使用 agent 方式操作,不依赖无障碍权限
- // 包名:com.xingin.xhs(小红书)
- print.log("===== 小红书自动发图片流程启动 =====");
- print.time();
- // ========== 配置区 ==========
- var APP包名 = "com.xingin.xhs";
- // 循环配置
- var 循环次数 = 3; // 设置循环次数,0表示无限循环
- var 当前循环 = 0;
- // 发布文案数组(可以设置多条不同文案)
- var 发布文案列表 = [
- "分享今日份的美好~",
- "生活需要仪式感 📷",
- "记录每一个值得纪念的瞬间 ✨"
- ];
- var 文案索引 = 0;
- // 标题数组(可以设置多条不同标题)
- var 标题列表 = [
- "今日分享",
- "生活日常",
- "美好瞬间"
- ];
- var 标题索引 = 0;
- // 要选择的图片数量
- var 选择图片数量 = 3;
- // ========== 获取设备信息 ==========
- var 屏幕宽 = screen.getScreenWidth();
- var 屏幕高 = screen.getScreenHeight();
- print.log("屏幕尺寸:" + 屏幕宽 + " x " + 屏幕高);
- print.log("代理状态:" + (agent.isOpen() ? "已开启" : "未开启"));
- // ========== 工具函数区 ==========
- /**
- * 获取当前界面的UI XML
- * @returns {string} UI XML内容
- */
- function 获取UI() {
- return agent.shell("uiautomator dump --compressed /proc/self/fd/1 2>/dev/null");
- }
- /**
- * 通过属性查找节点信息
- * @param {string} xml UI XML内容
- * @param {object} 条件 查找条件(text, resourceId, className, contentDesc)
- * @returns {object|null} 节点信息(left, top, right, bottom, centerX, centerY)
- */
- function 查找节点(xml, 条件) {
- var 模式 = "";
- var 查询值 = "";
-
- if (条件.text) {
- 模式 = "text";
- 查询值 = 条件.text;
- } else if (条件.resourceId) {
- 模式 = "resourceId";
- 查询值 = 条件.resourceId;
- } else if (条件.className) {
- 模式 = "className";
- 查询值 = 条件.className;
- } else if (条件.contentDesc) {
- 模式 = "contentDesc";
- 查询值 = 条件.contentDesc;
- } else {
- print.log("⚠ 查找条件不能为空");
- return null;
- }
-
- var 属性名 = {
- "text": "text="",
- "resourceId": "resource-id="",
- "className": "class="",
- "contentDesc": "content-desc=""
- }[模式];
-
- var 索引 = xml.indexOf(属性名 + 查询值);
- if (索引 === -1) {
- return null;
- }
-
- var 后面 = xml.substring(索引);
- var m = 后面.match(/bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"/);
- if (!m) {
- return null;
- }
-
- var left = parseInt(m[1]);
- var top = parseInt(m[2]);
- var right = parseInt(m[3]);
- var bottom = parseInt(m[4]);
-
- return {
- left: left,
- top: top,
- right: right,
- bottom: bottom,
- centerX: (left + right) / 2,
- centerY: (top + bottom) / 2,
- width: right - left,
- height: bottom - top
- };
- }
- /**
- * 通过节点信息点击
- * @param {object} 节点 节点信息对象
- * @param {string} 描述 节点描述(用于日志)
- */
- function 点击节点信息(节点, 描述) {
- agent.click(节点.centerX, 节点.centerY);
- print.log("✓ 点击【" + 描述 + "】成功 (" + 节点.centerX + ", " + 节点.centerY + ")");
- }
- /**
- * 通过文本查找并点击节点
- * @param {string} 文本 要查找的节点文本
- * @param {number} 超时秒 超时时间(秒)
- * @returns {boolean} 是否找到并点击成功
- */
- function 点击节点(文本, 超时秒) {
- var 开始时间 = Date.now();
- var 超时毫秒 = 超时秒 * 1000;
-
- while (Date.now() - 开始时间 < 超时毫秒) {
- try {
- var xml = 获取UI();
- var 节点 = 查找节点(xml, { text: 文本 });
- if (节点) {
- 点击节点信息(节点, 文本);
- return true;
- }
- } catch (e) {
- print.log("⚠ 查找节点出错:" + e.message);
- }
- sleep.second(秒 = 1);
- }
- print.log("✗ 未找到节点【" + 文本 + "】,超时 " + 超时秒 + " 秒");
- return false;
- }
- /**
- * 通过resourceId查找并点击节点
- * @param {string} resourceId 资源ID
- * @param {number} 超时秒 超时时间(秒)
- * @returns {boolean} 是否找到并点击成功
- */
- function 点击节点ById(resourceId, 超时秒) {
- var 开始时间 = Date.now();
- var 超时毫秒 = 超时秒 * 1000;
-
- while (Date.now() - 开始时间 < 超时毫秒) {
- try {
- var xml = 获取UI();
- var 节点 = 查找节点(xml, { resourceId: resourceId });
- if (节点) {
- 点击节点信息(节点, "resourceId:" + resourceId);
- return true;
- }
- } catch (e) {
- print.log("⚠ 通过ID查找节点出错:" + e.message);
- }
- sleep.second(秒 = 1);
- }
- print.log("✗ 未找到节点【resourceId:" + resourceId + "】,超时 " + 超时秒 + " 秒");
- return false;
- }
- /**
- * 通过className查找并点击节点
- * @param {string} className 类名
- * @param {number} 超时秒 超时时间(秒)
- * @param {number} 索引 第几个匹配节点(默认0,第一个)
- * @returns {boolean} 是否找到并点击成功
- */
- function 点击节点ByClass(className, 超时秒, 索引) {
- 索引 = 索引 || 0;
- var 开始时间 = Date.now();
- var 超时毫秒 = 超时秒 * 1000;
-
- while (Date.now() - 开始时间 < 超时毫秒) {
- try {
- var xml = 获取UI();
- var 节点列表 = 查找所有节点ByClass(xml, className);
- if (节点列表.length > 索引) {
- var 节点 = 节点列表[索引];
- 点击节点信息(节点, "className:" + className + "[" + 索引 + "]");
- return true;
- }
- } catch (e) {
- print.log("⚠ 通过Class查找节点出错:" + e.message);
- }
- sleep.second(秒 = 1);
- }
- print.log("✗ 未找到节点【className:" + className + "[" + 索引 + "]】,超时 " + 超时秒 + " 秒");
- return false;
- }
- /**
- * 通过className查找所有匹配节点
- * @param {string} xml UI XML内容
- * @param {string} className 类名
- * @returns {array} 节点信息数组
- */
- function 查找所有节点ByClass(xml, className) {
- var 结果 = [];
- var 属性名 = "class="";
- var 查找值 = 属性名 + className;
- var 位置 = 0;
-
- while (true) {
- var 索引 = xml.indexOf(查找值, 位置);
- if (索引 === -1) break;
-
- var 后面 = xml.substring(索引);
- var m = 后面.match(/bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"/);
- if (m) {
- var left = parseInt(m[1]);
- var top = parseInt(m[2]);
- var right = parseInt(m[3]);
- var bottom = parseInt(m[4]);
-
- 结果.push({
- left: left,
- top: top,
- right: right,
- bottom: bottom,
- centerX: (left + right) / 2,
- centerY: (top + bottom) / 2
- });
- }
-
- 位置 = 索引 + 查找值.length;
- }
-
- return 结果;
- }
- /**
- * 通过hint文本查找输入框并点击
- * @param {string} hint 提示文本
- * @param {number} 超时秒 超时时间(秒)
- * @returns {boolean} 是否找到并点击成功
- */
- function 点击输入框(hint, 超时秒) {
- var 开始时间 = Date.now();
- var 超时毫秒 = 超时秒 * 1000;
-
- var 备选hint列表 = [hint];
- if (hint === "标题") {
- 备选hint列表 = ["标题", "输入标题", "请输入标题", "写标题"];
- } else if (hint === "正文") {
- 备选hint列表 = ["正文", "输入正文", "请输入正文", "写正文", "分享文字"];
- }
-
- while (Date.now() - 开始时间 < 超时毫秒) {
- try {
- var xml = 获取UI();
-
- for (var i = 0; i < 备选hint列表.length; i++) {
- var 当前hint = 备选hint列表[i];
- var 索引 = xml.indexOf('hint="' + 当前hint);
- if (索引 !== -1) {
- var 后面 = xml.substring(索引);
- var m = 后面.match(/bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"/);
- if (m) {
- var cx = (parseInt(m[1]) + parseInt(m[3])) / 2;
- var cy = (parseInt(m[2]) + parseInt(m[4])) / 2;
- agent.click(cx, cy);
- print.log("✓ 点击输入框【" + 当前hint + "】成功 (" + cx + ", " + cy + ")");
- return true;
- }
- }
- }
-
- var editTextIndex = xml.indexOf('class="android.widget.EditText"');
- if (editTextIndex !== -1) {
- var 后面 = xml.substring(editTextIndex);
- var m = 后面.match(/bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"/);
- if (m) {
- var cx = (parseInt(m[1]) + parseInt(m[3])) / 2;
- var cy = (parseInt(m[2]) + parseInt(m[4])) / 2;
- agent.click(cx, cy);
- print.log("✓ 通过EditText类点击输入框成功 (" + cx + ", " + cy + ")");
- return true;
- }
- }
- } catch (e) {
- print.log("⚠ 查找输入框出错:" + e.message);
- }
- sleep.second(秒 = 1);
- }
-
- print.log("✗ 未找到输入框【" + hint + "】,超时 " + 超时秒 + " 秒");
- print.log("当前UI片段(供调试):" + 获取UI().substring(0, 3000));
- return false;
- }
- /**
- * 通过剪贴板 + 粘贴向当前输入框输入文字
- * @param {string} 文本 待输入内容
- */
- function 输入文字(文本) {
- // 设置剪贴板(双保险)
- agent.shell("service call clipboard 4 i32 1 s16 '" + 文本 + "' i32 0");
- sleep.millisecond(毫秒 = 300);
- agent.shell("service call clipboard 3 i32 1 s16 '" + 文本 + "' i32 0");
- sleep.millisecond(毫秒 = 300);
- Clipboard.copy(文本);
- sleep.millisecond(毫秒 = 300);
- var 粘贴成功 = false;
- // 方式A:KEYCODE_PASTE (279)
- agent.shell("input keyevent 279");
- sleep.second(秒 = 1);
- var xmlA = 获取UI();
- if (xmlA.indexOf(文本.substring(0, 2)) !== -1) {
- 粘贴成功 = true;
- print.log("✓ KEYCODE_PASTE 粘贴成功");
- }
- // 方式B:Ctrl+V
- if (!粘贴成功) {
- agent.shell("input keyevent 50 --meta 0x1000");
- sleep.second(秒 = 1);
- var xmlB = 获取UI();
- if (xmlB.indexOf(文本.substring(0, 2)) !== -1) {
- 粘贴成功 = true;
- print.log("✓ Ctrl+V 粘贴成功");
- }
- }
- // 方式C:长按 + 查找粘贴按钮
- if (!粘贴成功) {
- var 输入框中心 = 获取输入框中心();
- if (输入框中心) {
- var 长按命令 = "input swipe " + 输入框中心.x + " " + 输入框中心.y + " " + (输入框中心.x + 1) + " " + (输入框中心.y + 1) + " 2000";
- agent.shell(长按命令);
- sleep.second(秒 = 2);
- var xmlC = 获取UI();
- var 关键词 = ["粘贴", "Paste", "paste"];
- for (var i = 0; i < 关键词.length; i++) {
- if (xmlC.indexOf(关键词[i]) !== -1) {
- var 后面 = xmlC.substring(xmlC.indexOf(关键词[i]));
- var m = 后面.match(/bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"/);
- if (m) {
- var cx = (parseInt(m[1]) + parseInt(m[3])) / 2;
- var cy = (parseInt(m[2]) + parseInt(m[4])) / 2;
- agent.click(cx, cy);
- 粘贴成功 = true;
- print.log("✓ 长按粘贴成功");
- break;
- }
- }
- }
- }
- }
- if (!粘贴成功) {
- print.log("✗ 所有粘贴方式均未成功");
- }
- return 粘贴成功;
- }
- /**
- * 获取当前输入框的中心坐标
- * @returns {object|null} {x, y} 坐标对象
- */
- function 获取输入框中心() {
- try {
- var xml = 获取UI();
-
- var editTextIndex = xml.indexOf('class="android.widget.EditText"');
- if (editTextIndex !== -1) {
- var 后面 = xml.substring(editTextIndex);
- var m = 后面.match(/bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"/);
- if (m) {
- return {
- x: (parseInt(m[1]) + parseInt(m[3])) / 2,
- y: (parseInt(m[2]) + parseInt(m[4])) / 2
- };
- }
- }
-
- var inputIndex = xml.indexOf('class="android.widget.TextView"');
- while (inputIndex !== -1) {
- var 后面 = xml.substring(inputIndex);
- var m = 后面.match(/bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"/);
- if (m) {
- var left = parseInt(m[1]);
- var top = parseInt(m[2]);
- var right = parseInt(m[3]);
- var bottom = parseInt(m[4]);
-
- if (right - left > 100 && bottom - top > 30) {
- return {
- x: (left + right) / 2,
- y: (top + bottom) / 2
- };
- }
- }
- inputIndex = xml.indexOf('class="android.widget.TextView"', inputIndex + 30);
- }
-
- return { x: 屏幕宽 / 2, y: 屏幕高 / 2 };
- } catch (e) {
- return { x: 屏幕宽 / 2, y: 屏幕高 / 2 };
- }
- }
- /**
- * 在相册网格中按顺序选择指定数量的图片(节点查找方式)
- * @param {number} 数量 要选择的图片数
- */
- function 选择相册图片(数量) {
- print.log("准备从相册选择 " + 数量 + " 张图片");
- var 已选 = 0;
- var 开始时间 = Date.now();
-
- while (已选 < 数量 && Date.now() - 开始时间 < 30000) {
- try {
- var xml = 获取UI();
-
- var 选择框列表 = [];
- var classIndex = xml.indexOf('class="android.widget.FrameLayout"');
- while (classIndex !== -1) {
- var 后面 = xml.substring(classIndex);
- var boundsM = 后面.match(/bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"/);
- if (boundsM) {
- var left = parseInt(boundsM[1]);
- var top = parseInt(boundsM[2]);
- var right = parseInt(boundsM[3]);
- var bottom = parseInt(boundsM[4]);
- var width = right - left;
- var height = bottom - top;
-
- if (width > 50 && height > 50 && top > 600 && width < 400 && height < 400) {
- 选择框列表.push({
- centerX: (left + right) / 2,
- centerY: (top + bottom) / 2,
- left: left,
- top: top
- });
- }
- }
- classIndex = xml.indexOf('class="android.widget.FrameLayout"', classIndex + 35);
- }
-
- 选择框列表.sort(function(a, b) {
- if (Math.abs(a.top - b.top) < 50) {
- return a.left - b.left;
- }
- return a.top - b.top;
- });
-
- print.log("找到 " + 选择框列表.length + " 个图片选择框");
-
- if (选择框列表.length > 已选) {
- var 目标节点 = 选择框列表[已选];
- agent.click(目标节点.centerX, 目标节点.centerY);
- print.log("✓ 已选中第 " + (已选 + 1) + " 张图片 (" + 目标节点.centerX + ", " + 目标节点.centerY + ")");
- 已选++;
- sleep.millisecond(毫秒 = 800);
- } else {
- print.log("⚠ 选择框不足,等待加载...");
- sleep.second(秒 = 2);
- }
- } catch (e) {
- print.log("⚠ 选择第 " + (已选 + 1) + " 张图片失败:" + e.message);
- sleep.second(秒 = 1);
- }
- }
-
- print.log("共选中 " + 已选 + " 张图片");
- return 已选;
- }
- // ========================================
- // 主循环开始
- // ========================================
- for (当前循环 = 1; 循环次数 == 0 || 当前循环 <= 循环次数; 当前循环++) {
- if (循环次数 > 0) {
- print.log("\n======================================");
- print.log("===== 第 " + 当前循环 + " / " + 循环次数 + " 次循环 =====");
- print.log("======================================");
- } else {
- print.log("\n======================================");
- print.log("===== 第 " + 当前循环 + " 次循环(无限模式)=====");
- print.log("======================================");
- }
- // 获取当前要发布的文案与标题
- var 发布文案 = 发布文案列表[文案索引 % 发布文案列表.length];
- var 发布标题 = 标题列表[标题索引 % 标题列表.length];
- 文案索引++;
- 标题索引++;
- print.log("本次标题:" + 发布标题);
- print.log("本文案:" + 发布文案);
- // ========================================
- // 步骤1:启动小红书
- // ========================================
- print.log("\n========== 步骤1:启动小红书 ==========");
- try {
- app.openApp(APP包名);
- print.log("✓ 已启动小红书");
- } catch (e) {
- print.log("启动失败:" + e.message);
- }
- sleep.second(秒 = 5);
- // ========================================
- // 步骤2:点击底部加号按钮(节点查找)
- // ========================================
- print.log("\n========== 步骤2:点击加号按钮 ==========");
- var 加号点击成功 = false;
-
- var 开始时间 = Date.now();
- while (Date.now() - 开始时间 < 15000 && !加号点击成功) {
- try {
- var xml = 获取UI();
-
- var 加号节点 = null;
-
- // 方式1:通过content-desc查找加号按钮
- var contentDesc索引 = xml.indexOf('content-desc="');
- while (contentDesc索引 !== -1) {
- var 后面 = xml.substring(contentDesc索引);
- var m = 后面.match(/content-desc="([^"]*)"/);
- if (m && (m[1] === "发布" || m[1] === "加号" || m[1] === "+")) {
- var boundsM = 后面.match(/bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"/);
- if (boundsM) {
- 加号节点 = {
- centerX: (parseInt(boundsM[1]) + parseInt(boundsM[3])) / 2,
- centerY: (parseInt(boundsM[2]) + parseInt(boundsM[4])) / 2
- };
- break;
- }
- }
- contentDesc索引 = xml.indexOf('content-desc="', contentDesc索引 + 14);
- }
-
- // 方式2:查找底部导航栏中最中间的按钮
- if (!加号节点) {
- var 导航按钮列表 = [];
- var classIndex = xml.indexOf('class="android.widget.ImageView"');
- while (classIndex !== -1) {
- var 后面 = xml.substring(classIndex);
- var boundsM = 后面.match(/bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"/);
- if (boundsM) {
- var top = parseInt(boundsM[2]);
- if (top > 屏幕高 * 0.85) {
- 导航按钮列表.push({
- centerX: (parseInt(boundsM[1]) + parseInt(boundsM[3])) / 2,
- centerY: (parseInt(boundsM[2]) + parseInt(boundsM[4])) / 2
- });
- }
- }
- classIndex = xml.indexOf('class="android.widget.ImageView"', classIndex + 32);
- }
-
- if (导航按钮列表.length >= 3) {
- 加号节点 = 导航按钮列表[Math.floor(导航按钮列表.length / 2)];
- }
- }
-
- if (加号节点) {
- agent.click(加号节点.centerX, 加号节点.centerY);
- print.log("✓ 通过节点查找点击加号按钮成功 (" + 加号节点.centerX + ", " + 加号节点.centerY + ")");
- sleep.second(秒 = 2);
-
- var xml2 = 获取UI();
- if (xml2.indexOf("从相册选择") !== -1 || xml2.indexOf("相册") !== -1) {
- 加号点击成功 = true;
- print.log("✓ 检测到发布弹窗,加号点击成功");
- } else {
- print.log("⚠ 未检测到弹窗,重试...");
- }
- } else {
- print.log("⚠ 未找到加号按钮节点,等待重试...");
- sleep.second(秒 = 1);
- }
- } catch (e) {
- print.log("⚠ 点击加号失败:" + e.message);
- sleep.second(秒 = 1);
- }
- }
-
- if (!加号点击成功) {
- print.log("✗ 加号按钮点击失败,dump当前UI供调试:");
- var debugXml = 获取UI();
- print.log("UI内容片段:" + debugXml.substring(0, 2000));
- }
- // ========================================
- // 步骤3:选择"从相册选择"选项(节点查找)
- // ========================================
- print.log("\n========== 步骤3:选择相册选项 ==========");
- var 相册成功 = 点击节点("从相册选择", 10);
- if (!相册成功) {
- 点击节点("相册", 10);
- }
- sleep.second(秒 = 3);
- // ========================================
- // 步骤4:从相册选择图片(节点查找)
- // ========================================
- print.log("\n========== 步骤4:从相册选择图片 ==========");
- try {
- 选择相册图片(选择图片数量);
- } catch (e) {
- print.log("⚠ 选择图片失败:" + e.message);
- }
- sleep.second(秒 = 1);
- // ========================================
- // 步骤5:点击"下一步"(节点查找)
- // ========================================
- print.log("\n========== 步骤5:点击下一步 ==========");
- 点击节点("下一步", 10);
- sleep.second(秒 = 5);
- // ========================================
- // 步骤6:点击图片编辑页的"下一步"(节点查找)
- // ========================================
- print.log("\n========== 步骤6:点击编辑页下一步 ==========");
- 点击节点("下一步", 10);
- sleep.second(秒 = 3);
- // ========================================
- // 步骤7:输入标题(节点查找输入框)
- // ========================================
- print.log("\n========== 步骤7:输入标题 ==========");
- print.log("准备输入标题:" + 发布标题);
-
- 点击输入框("标题", 15);
- sleep.second(秒 = 1);
- 输入文字(发布标题);
- sleep.second(秒 = 2);
- // ========================================
- // 步骤8:输入正文文案(节点查找输入框)
- // ========================================
- print.log("\n========== 步骤8:输入正文文案 ==========");
- print.log("准备输入正文:" + 发布文案);
-
- var 正文输入成功 = false;
-
- var xml = 获取UI();
- var editTextIndex = xml.indexOf('class="android.widget.EditText"');
- var editTextCount = 0;
-
- while (editTextIndex !== -1) {
- var 后面 = xml.substring(editTextIndex);
- var m = 后面.match(/bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"/);
- if (m) {
- var left = parseInt(m[1]);
- var top = parseInt(m[2]);
- var right = parseInt(m[3]);
- var bottom = parseInt(m[4]);
- var width = right - left;
- var height = bottom - top;
-
- if (editTextCount === 1) {
- var cx = (left + right) / 2;
- var cy = (parseInt(m[2]) + parseInt(m[4])) / 2;
- agent.click(cx, cy);
- print.log("✓ 点击第2个EditText作为正文输入框成功 (" + cx + ", " + cy + ")");
- 正文输入成功 = true;
- break;
- }
- editTextCount++;
- }
- editTextIndex = xml.indexOf('class="android.widget.EditText"', editTextIndex + 32);
- }
-
- if (!正文输入成功) {
- print.log("⚠ 尝试通过hint查找正文输入框...");
- 正文输入成功 = 点击输入框("正文", 15);
- }
-
- sleep.second(秒 = 1);
- 输入文字(发布文案);
- sleep.second(秒 = 2);
- // ========================================
- // 步骤9:点击"发布笔记"(节点查找)
- // ========================================
- print.log("\n========== 步骤9:点击发布笔记 ==========");
- 点击节点("发布", 10);
- sleep.second(秒 = 3);
- // ========================================
- // 循环间隔(最后一次不等待)
- // ========================================
- if (循环次数 == 0 || 当前循环 < 循环次数) {
- print.log("\n========== 等待下一次循环 ==========");
- sleep.second(秒 = 5);
- }
- }
- // ========================================
- // 结果汇总
- // ========================================
- print.timeEnd();
- print.log("\n======================================");
- print.log("===== 所有循环执行完成 =====");
- if (循环次数 > 0) {
- print.log("总计执行:" + 循环次数 + " 次");
- } else {
- print.log("无限循环模式,已执行:" + (当前循环 - 1) + " 次");
- }
- print.log("======================================");
复制代码
|
|