AIWROK软件番茄小说自动阅读刷金币示例

384

主题

193

回帖

2417

积分

管理员

积分
2417


AIWROK软件番茄小说自动阅读刷金币示例



1.png


2.png


3.png


4.png


  1. /*
  2. * ============================================================
  3. * AIWROK软件番茄小说自动阅读刷金币示例
  4. * ============================================================
  5. * 功能: 使用无障碍模式UI自动化实现番茄小说自动阅读和领取金币
  6. * 流程: 启动APP → 关闭弹窗 → 检测登录 → 进入阅读 → 自动翻页 → 领取金币
  7. * ============================================================
  8. */

  9. print.log("===== 番茄小说自动阅读刷金币(无障碍模式)=====");
  10. print.time();

  11. // ========== 初始化 ==========
  12. print.log("\n【步骤1】初始化无障碍模式...");
  13. auto.setDetailedModel();

  14. // ========== 配置参数 ==========
  15. print.log("【步骤2】加载配置参数...");
  16. var config = {
  17.     APP包名: "com.dragon.read",          // 番茄小说包名
  18.     每轮阅读时长分钟: 30,                  // 每轮自动阅读时长
  19.     每页阅读时长秒: 8,                     // 每页停留阅读时间
  20.     滑动间隔秒: 12,                       // 滑动翻页间隔
  21.     金币领取间隔章节: 5,                   // 每阅读N章领取一次金币
  22.     最大重试次数: 3,                       // 操作失败最大重试次数
  23.     连续错误阈值: 3                       // 连续错误次数上限,超过则重启APP
  24. };

  25. // ========== 获取屏幕尺寸 ==========
  26. print.log("【步骤3】获取屏幕尺寸...");
  27. var 屏幕宽 = screen.getScreenWidth();
  28. var 屏幕高 = screen.getScreenHeight();
  29. print.log("屏幕尺寸:" + 屏幕宽 + " x " + 屏幕高);

  30. // ========== 工具函数:查找文字节点 ==========
  31. // 功能: 根据关键词列表查找UI元素节点
  32. // 参数: 关键词数组,依次尝试精确匹配和包含匹配
  33. // 返回: 找到的节点对象或null
  34. print.log("\n【步骤4】定义工具函数...");

  35. function 查找文字节点(关键词列表) {
  36.     try {
  37.         auto.refresh();
  38.         var 节点集合 = auto.nodeSearch(2000);
  39.         if (节点集合 == '' || 节点集合 == null) {
  40.             return null;
  41.         }
  42.         
  43.         for (var i = 0; i < 关键词列表.length; i++) {
  44.             var 关键词 = 关键词列表[i];
  45.             
  46.             var 文本过滤 = 节点集合.text(关键词);
  47.             if (文本过滤 != '' && 文本过滤 != null && 文本过滤.count() > 0) {
  48.                 var 节点 = 文本过滤.getNode(0);
  49.                 if (节点 != '' && 节点 != null) {
  50.                     return 节点;
  51.                 }
  52.             }
  53.             
  54.             var 包含过滤 = 节点集合.textIncludes(关键词);
  55.             if (包含过滤 != '' && 包含过滤 != null && 包含过滤.count() > 0) {
  56.                 var 节点 = 包含过滤.getNode(0);
  57.                 if (节点 != '' && 节点 != null) {
  58.                     return 节点;
  59.                 }
  60.             }
  61.         }
  62.     } catch (e) {
  63.         print.log("  ⚠ 查找文字节点出错:" + e.message);
  64.     }
  65.     return null;
  66. }

  67. // ========== 工具函数:检查文字是否存在 ==========
  68. // 功能: 检测页面是否包含指定关键词(使用||表示或关系)
  69. // 参数: 关键词数组
  70. // 返回: 存在返回true,否则返回false
  71. function 检查文字存在(关键词列表) {
  72.     var 规则 = 关键词列表.join("||");
  73.     return auto.findText(规则);
  74. }

  75. // ========== 工具函数:安全点击节点 ==========
  76. // 功能: 三级降级策略点击节点,解决clickable=false问题
  77. // 策略: 1)节点可点击→直接点击 2)父/祖父节点可点击→点击父级 3)兜底→坐标点击
  78. // 参数: 要点击的节点对象
  79. // 返回: 点击成功返回true,否则返回false
  80. function 安全点击节点(节点) {
  81.     if (!节点 || 节点 == '' || 节点 == null) {
  82.         return false;
  83.     }

  84.     // 第一级:节点本身可点击,直接点击
  85.     try {
  86.         if (节点.isClickable && 节点.isClickable()) {
  87.             节点.click();
  88.             return true;
  89.         }
  90.     } catch (e) {
  91.     }

  92.     // 第二级:向上查找可点击的父节点或祖父节点
  93.     try {
  94.         var 父节点 = 节点.getParent();
  95.         if (父节点 != '' && 父节点 != null) {
  96.             if (父节点.isClickable && 父节点.isClickable()) {
  97.                 父节点.click();
  98.                 return true;
  99.             }

  100.             var 祖父节点 = 父节点.getParent();
  101.             if (祖父节点 != '' && 祖父节点 != null) {
  102.                 if (祖父节点.isClickable && 祖父节点.isClickable()) {
  103.                     祖父节点.click();
  104.                     return true;
  105.                 }
  106.             }
  107.         }
  108.     } catch (e2) {
  109.     }

  110.     // 第三级:兜底方案,使用坐标点击
  111.     try {
  112.         var 中心 = 节点.getCenter();
  113.         var x = 中心.x();
  114.         var y = 中心.y();
  115.         auto.clickPoint(x, y);
  116.         return true;
  117.     } catch (e3) {
  118.         print.log("  ⚠ 安全点击全部失败:" + e3.message);
  119.         return false;
  120.     }
  121. }

  122. // ========== 工具函数:关闭弹窗 ==========
  123. // 功能: 自动识别并关闭各种弹窗和广告
  124. // 处理: 1)点击赚钱浮窗(特殊处理) 2)通用弹窗(知道了、关闭等)
  125. // 返回: 成功关闭返回true,否则返回false
  126. function 关闭弹窗() {
  127.     var 弹窗关键词 = [
  128.         "我知道了", "知道了", "关闭", "暂不开启", "以后再说",
  129.         "取消", "暂不更新", "下次再说", "拒绝", "不允许", "跳过", "领取", "收下"
  130.     ];

  131.     // 优先处理"点击赚钱"浮窗(需要查找带Close描述的可点击节点)
  132.     if (auto.findText("点击赚钱")) {
  133.         try {
  134.             var 节点集 = auto.nodeSearch(1000);
  135.             if (节点集 != '' && 节点集 != null) {
  136.                 var 可点击节点 = 节点集.clickable(true);
  137.                 if (可点击节点 != '' && 可点击节点 != null) {
  138.                     for (var i = 0; i < 可点击节点.count(); i++) {
  139.                         var n = 可点击节点.getNode(i);
  140.                         if (n && n.getDesc) {
  141.                             var d = "" + n.getDesc();
  142.                             if (d.indexOf("Close") >= 0 || d.indexOf("关闭") >= 0) {
  143.                                 n.click();
  144.                                 print.log("  ✓ 关闭点击赚钱浮窗");
  145.                                 sleep.second(秒 = 1);
  146.                                 return true;
  147.                             }
  148.                         }
  149.                     }
  150.                 }
  151.             }
  152.         } catch (e) {
  153.         }
  154.     }

  155.     // 处理通用弹窗
  156.     var 检测结果 = auto.findText(弹窗关键词.join("||"));
  157.     if (!检测结果) {
  158.         return false;
  159.     }

  160.     for (var i = 0; i < 弹窗关键词.length; i++) {
  161.         var 关键词 = 弹窗关键词[i];
  162.         var 节点 = 查找文字节点([关键词]);
  163.         if (节点) {
  164.             try {
  165.                 if (安全点击节点(节点)) {
  166.                     print.log("  ✓ 关闭弹窗:点击【" + 关键词 + "】");
  167.                     sleep.second(秒 = 1);
  168.                     return true;
  169.                 }
  170.             } catch (e) {
  171.                 print.log("  ⚠ 点击弹窗按钮失败:" + e.message);
  172.             }
  173.         }
  174.     }
  175.     return false;
  176. }

  177. // ========== 工具函数:检测是否在阅读页 ==========
  178. // 功能: 判断当前页面是否为小说阅读界面
  179. // 依据: 检查章节进度(如"1/23351")、章节标题(如"第1章")、金币领取提示
  180. // 返回: 在阅读页返回true,否则返回false
  181. function 检测是否在阅读页() {
  182.     try {
  183.         // 检查章节进度文字(格式如 "1/23351")
  184.         var 进度结果 = auto.findText("/");
  185.         if (进度结果) {
  186.             // 进一步验证:检查是否有章节号
  187.             if (auto.findText("第")) {
  188.                 return true;
  189.             }
  190.         }

  191.         // 检查金币领取提示
  192.         if (auto.findText("金币")) {
  193.             return true;
  194.         }

  195.         // 检查阅读页底部状态栏(章节进度)
  196.         try {
  197.             var 节点集合 = auto.nodeSearch(500);
  198.             if (节点集合 != '' && 节点集合 != null) {
  199.                 // 检查j4g节点(章节进度)
  200.                 var 进度节点 = 节点集合.id("com.dragon.read:id/j4g");
  201.                 if (进度节点 != '' && 进度节点 != null && 进度节点.count() > 0) {
  202.                     return true;
  203.                 }

  204.                 // 检查阅读页文本特征
  205.                 var 文本进度 = 节点集合.textIncludes("/");
  206.                 if (文本进度 != '' && 文本进度 != null && 文本进度.count() > 0) {
  207.                     // 有斜杠可能是进度如 "1/23351"
  208.                     var 节点 = 文本进度.getNode(0);
  209.                     if (节点) {
  210.                         var 文本 = "" + 节点.getText();
  211.                         if (文本.indexOf("/") > 0 && 文本.length < 20) {
  212.                             return true;
  213.                         }
  214.                     }
  215.                 }
  216.             }
  217.         } catch (e) {
  218.         }

  219.         // 检查阅读页标题特征
  220.         if (auto.findText("章")) {
  221.             return true;
  222.         }
  223.     } catch (e) {
  224.         print.log("  ⚠ 检测阅读页出错:" + e.message);
  225.     }
  226.     return false;
  227. }

  228. // ========== 工具函数:检测是否在金币页面 ==========
  229. // 功能: 判断当前页面是否为金币领取界面
  230. // 返回: 在金币页返回true,否则返回false
  231. function 检测是否在金币页面() {
  232.     try {
  233.         return 检查文字存在(["金币", "奖励", "领取金币", "去领取"]);
  234.     } catch (e) {
  235.         print.log("  ⚠ 检测金币页面出错:" + e.message);
  236.         return false;
  237.     }
  238. }

  239. // ========== 核心函数:领取金币 ==========
  240. // 功能: 领取阅读页顶部的金币奖励
  241. // 流程: 1)只点击"点击领"开头的节点 2)领取后自动返回
  242. // 说明: 不点击"去赚更多",因为会进入赚钱页面
  243. // 返回: 领取成功返回true,否则返回false
  244. function 领取金币() {
  245.     print.log("\n📦 尝试领取金币");
  246.    
  247.     // 只查找 "点击领" 开头的节点(这是直接领取金币的入口)
  248.     // "去赚更多" 会进入赚钱页面,不点击
  249.     var 金币入口节点 = null;
  250.     var 提示文字 = "";
  251.    
  252.     // 方法1: 精准查找 "点击领" 开头的TextView
  253.     try {
  254.         var 节点集合 = auto.nodeSearch(3000);
  255.         if (节点集合 != '' && 节点集合 != null) {
  256.             // 精准匹配:textIncludes("点击领") + className("android.widget.TextView")
  257.             var 结果1 = 节点集合.textIncludes("点击领").className("android.widget.TextView");
  258.             if (结果1 != '' && 结果1 != null && 结果1.count() > 0) {
  259.                 金币入口节点 = 结果1.getNode(0);
  260.                 提示文字 = "" + 金币入口节点.getText();
  261.                 print.log("  找到金币入口:" + 提示文字);
  262.             }
  263.         }
  264.     } catch (e) {
  265.         print.log("  ⚠ 精准查找出错:" + e.message);
  266.     }
  267.    
  268.     // 方法2: 如果没找到"点击领",检查是否在赚钱页面
  269.     if (!金币入口节点) {
  270.         // 检测是否在赚钱页面(特征:有"新人福利""提现""领现金"等文字)
  271.         var 在赚钱页面 = false;
  272.         try {
  273.             var 检查集合 = auto.nodeSearch(1000);
  274.             if (检查集合 != '' && 检查集合 != null) {
  275.                 // 检测赚钱页面特征
  276.                 var 新人福利节点 = 检查集合.textIncludes("新人福利");
  277.                 var 提现节点 = 检查集合.textIncludes("提现");
  278.                 var 领现金节点 = 检查集合.textIncludes("领现金");
  279.                
  280.                 if ((新人福利节点 != '' && 新人福利节点 != null && 新人福利节点.count() > 0) ||
  281.                     (提现节点 != '' && 提现节点 != null && 提现节点.count() > 0) ||
  282.                     (领现金节点 != '' && 领现金节点 != null && 领现金节点.count() > 0)) {
  283.                     在赚钱页面 = true;
  284.                     print.log("  ⚠ 当前在赚钱页面,返回阅读页");
  285.                 }
  286.             }
  287.         } catch (e) {
  288.         }
  289.         
  290.         // 如果在赚钱页面,返回后跳过本次领取
  291.         if (在赚钱页面) {
  292.             auto.back();
  293.             sleep.second(秒 = 1);
  294.             return false;
  295.         }
  296.         
  297.         // 没找到金币入口,可能是已经领完了
  298.         print.log("  ℹ 暂无金币可领(已领完或未达到领取条件)");
  299.         return false;
  300.     }
  301.    
  302.     // 找到了"点击领"节点,点击领取
  303.     try {
  304.         // 点击金币入口
  305.         安全点击节点(金币入口节点);
  306.         print.log("✓ 点击领取:" + 提示文字);
  307.         sleep.second(秒 = 2);

  308.         // 检查是否领取成功(查看是否还有"点击领"节点)
  309.         try {
  310.             var 验证集合 = auto.nodeSearch(1000);
  311.             if (验证集合 != '' && 验证集合 != null) {
  312.                 var 验证结果 = 验证集合.textIncludes("点击领");
  313.                 if (验证结果 == '' || 验证结果 == null || 验证结果.count() == 0) {
  314.                     print.log("✓ 金币领取成功");
  315.                 }
  316.             }
  317.         } catch (e) {
  318.         }

  319.         // 关闭领取后的弹窗(如果有)
  320.         关闭弹窗();

  321.         // 返回阅读页面
  322.         auto.back();
  323.         sleep.second(秒 = 2);
  324.         
  325.         // 如果返回后还在赚钱页面,再按一次返回
  326.         try {
  327.             var 检查集合2 = auto.nodeSearch(1000);
  328.             if (检查集合2 != '' && 检查集合2 != null) {
  329.                 var 新人福利节点2 = 检查集合2.textIncludes("新人福利");
  330.                 if (新人福利节点2 != '' && 新人福利节点2 != null && 新人福利节点2.count() > 0) {
  331.                     auto.back();
  332.                     sleep.second(秒 = 1);
  333.                 }
  334.             }
  335.         } catch (e) {
  336.         }
  337.         
  338.         return true;
  339.     } catch (e) {
  340.         print.log("✗ 领取金币失败:" + e.message);
  341.         // 出错则返回
  342.         auto.back();
  343.         sleep.second(秒 = 2);
  344.         return false;
  345.     }
  346. }

  347. // ========== 核心函数:翻页阅读 ==========
  348. // 功能: 向左滑动翻页到下一章
  349. // 说明: 番茄小说阅读页采用左滑翻页方式
  350. function 翻页阅读() {
  351.     print.log("▶ 翻页到下一章");
  352.     auto.drag(
  353.         Math.floor(屏幕宽 * 0.9),   // 起点X:屏幕右侧
  354.         Math.floor(屏幕高 * 0.5),   // 起点Y:屏幕垂直居中
  355.         Math.floor(屏幕宽 * 0.1),   // 终点X:屏幕左侧
  356.         Math.floor(屏幕高 * 0.5),   // 终点Y:保持垂直居中
  357.         300                         // 滑动时长300毫秒
  358.     );
  359.     print.log("✓ 已翻页");
  360.     sleep.second(秒 = 1);
  361. }

  362. // ========== 核心函数:重启APP ==========
  363. // 功能: 使用app.resetApp重启APP,用于异常恢复
  364. // 说明: resetApp会强制停止并重新启动APP,比closeApp+openApp更彻底
  365. // 返回: 重启成功返回true,否则返回false
  366. function 重启APP() {
  367.     print.log("✗ 需要重启APP");
  368.     try {
  369.         // 使用resetApp强制重启(参数为包名)
  370.         app.resetApp(config.APP包名);
  371.         sleep.second(秒 = 8);
  372.         print.log("✓ APP重启成功");
  373.         return true;
  374.     } catch (e) {
  375.         print.log("✗ 重启失败:" + e.message);
  376.         return false;
  377.     }
  378. }

  379. // ========== 主流程:无限循环阅读 ==========
  380. // 流程: 启动APP → 关闭弹窗 → 检测登录 → 进入阅读 → 循环翻页 → 领取金币 → 统计输出
  381. print.log("\n【步骤5】开始主循环流程...\n");

  382. // 重启标志:当错误恢复时,重启APP已完成打开,跳过步骤5.1的重复打开
  383. var 刚重启过 = false;

  384. for (var 当前循环 = 1; ; 当前循环++) {
  385.     print.log("\n===== 第 " + 当前循环 + " 次循环 =====");

  386.     // ===== 步骤5.1:启动APP =====
  387.     print.log("\n【步骤5.1】启动番茄小说...");
  388.    
  389.     // 如果是刚重启过,直接跳过打开APP(重启APP已经打开了)
  390.     if (刚重启过) {
  391.         print.log("  (刚重启过,跳过打开APP)");
  392.         刚重启过 = false;
  393.         sleep.second(秒 = 3);
  394.     } else {
  395.         var 启动成功 = false;
  396.         for (var i = 0; i < config.最大重试次数; i++) {
  397.             try {
  398.                 app.openApp(config.APP包名);
  399.                 sleep.second(秒 = 6);
  400.                
  401.                 // 启动后立即关闭弹窗
  402.                 关闭弹窗();
  403.                
  404.                 // 验证APP是否正常启动
  405.                 if (检查文字存在(["番茄小说", "书架", "推荐", "阅读"])) {
  406.                     启动成功 = true;
  407.                     print.log("✓ APP启动成功");
  408.                     break;
  409.                 }
  410.             } catch (e) {
  411.                 print.log("⚠ 启动失败:" + e.message);
  412.                 sleep.second(秒 = 3);
  413.             }
  414.         }
  415.     }
  416.    
  417.     // 启动失败则等待后重试
  418.     if (!启动成功) {
  419.         print.log("✗ 多次启动失败,等待5秒后重试");
  420.         sleep.second(秒 = 5);
  421.         continue;
  422.     }

  423.     sleep.second(秒 = 3);

  424.     // ===== 步骤5.2:检查登录状态 =====
  425.     print.log("\n【步骤5.2】检查登录状态...");
  426.     var 需要登录 = false;
  427.     try {
  428.         关闭弹窗();
  429.         sleep.millisecond(毫秒 = 500);

  430.         // 通过底部导航栏判断登录状态
  431.         if (检查文字存在(["书架", "推荐", "我的"])) {
  432.             print.log("✓ 已登录");
  433.         } else if (检查文字存在(["手机号", "验证码", "密码", "登录"])) {
  434.             print.log("⚠ 检测到登录页面,需要手动登录");
  435.             需要登录 = true;
  436.         }
  437.     } catch (e) {
  438.         print.log("⚠ 检查登录状态失败:" + e.message);
  439.     }

  440.     // 需要手动登录时暂停
  441.     if (需要登录) {
  442.         print.log("⚠ 请手动登录后重新运行");
  443.         sleep.second(秒 = 10);
  444.         continue;
  445.     }

  446.     // ===== 步骤5.3:进入阅读页面 =====
  447.     // 流程: 点击书城 → 点击书籍 → 左滑进入阅读
  448.     print.log("\n【步骤5.3】进入阅读页面...");
  449.     var 进入阅读成功 = false;
  450.     for (var i = 0; i < config.最大重试次数; i++) {
  451.         try {
  452.             // 步骤1:点击底部"书城"标签
  453.             print.log("  点击书城...");
  454.             auto.back();
  455.             sleep.second(秒 = 1);
  456.             
  457.             var 书城节点 = 查找文字节点(["书城"]);
  458.             if (!书城节点) {
  459.                 print.log("  ⚠ 找不到书城,重试");
  460.                 continue;
  461.             }
  462.             安全点击节点(书城节点);
  463.             print.log("  ✓ 已点击书城");
  464.             sleep.second(秒 = 3);

  465.             // 步骤2:点击一本书
  466.             print.log("  查找书籍...");
  467.             var 书籍点击成功 = false;
  468.             try {
  469.                 // 通过文本特征找书名:先排除榜单,再找实际书名
  470.                 // 已知的书名节点特征:id="com.dragon.read:id/fuw"
  471.                 // 榜单文字:推荐榜、完本榜、巅峰榜、新书榜、完整榜单
  472.                
  473.                 var 候选书名 = [];
  474.                
  475.                 // 方法1:用textIncludes查找所有fuw节点(书名节点的id都是fuw)
  476.                 var 节点集合 = auto.nodeSearch(1000);
  477.                 if (节点集合 != '' && 节点集合 != null) {
  478.                     // 用id精确匹配fuw(书名节点)
  479.                     var 书名节点列表 = 节点集合.id("com.dragon.read:id/fuw");
  480.                     if (书名节点列表 != '' && 书名节点列表 != null && 书名节点列表.count() > 0) {
  481.                         print.log("  找到 " + 书名节点列表.count() + " 个fuw节点");
  482.                         
  483.                         for (var j = 0; j < 书名节点列表.count(); j++) {
  484.                             var 节点 = 书名节点列表.getNode(j);
  485.                             if (节点) {
  486.                                 var 文字 = "" + 节点.getText();
  487.                                 // 跳过榜单文字,保留真正的书名
  488.                                 if (文字 && 文字.length > 1 &&
  489.                                     文字.indexOf("榜") < 0 &&
  490.                                     文字.indexOf("点评") < 0 &&
  491.                                     文字.indexOf("热度") < 0 &&
  492.                                     文字.indexOf("在看") < 0 &&
  493.                                     文字.indexOf("榜单") < 0) {
  494.                                     候选书名.push({节点: 节点, 文字: 文字});
  495.                                 }
  496.                             }
  497.                         }
  498.                     }
  499.                 }

  500.                 // 方法2:直接用findText找一本书名(兜底方案)
  501.                 if (候选书名.length == 0) {
  502.                     print.log("  用findText兜底查找...");
  503.                     var 已知书名 = ["武极天尊", "九龙神帝", "绝鼎仙缘", "剑断仙缘"];
  504.                     for (var k = 0; k < 已知书名.length; k++) {
  505.                         if (auto.findText(已知书名[k])) {
  506.                             var 节点 = auto.findText(已知书名[k]);
  507.                             if (节点) {
  508.                                 候选书名.push({节点: 节点, 文字: 已知书名[k]});
  509.                                 print.log("  找到书名:《" + 已知书名[k] + "》");
  510.                                 break;
  511.                             }
  512.                         }
  513.                     }
  514.                 }

  515.                 // 点击找到的第一本书
  516.                 if (候选书名.length > 0) {
  517.                     var 书籍 = 候选书名[0];
  518.                     print.log("  点击书籍:《" + 书籍.文字 + "》");
  519.                     
  520.                     // 点击父容器进入详情页
  521.                     var 父容器 = 书籍.节点.getParent();
  522.                     if (父容器) {
  523.                         安全点击节点(父容器);
  524.                         sleep.second(秒 = 2);
  525.                         书籍点击成功 = true;
  526.                     } else {
  527.                         // 直接点击节点
  528.                         安全点击节点(书籍.节点);
  529.                         sleep.second(秒 = 2);
  530.                         书籍点击成功 = true;
  531.                     }
  532.                 }
  533.             } catch (e) {
  534.                 print.log("  ⚠ 查找书籍出错:" + e.message);
  535.             }

  536.             // 如果节点查找失败,用坐标点击兜底
  537.             if (!书籍点击成功) {
  538.                 print.log("  坐标点击兜底...");
  539.                 auto.clickPoint(Math.floor(屏幕宽 * 0.3), Math.floor(屏幕高 * 0.35));
  540.                 sleep.second(秒 = 2);
  541.                 书籍点击成功 = true;
  542.             }

  543.             if (!书籍点击成功) {
  544.                 print.log("  ⚠ 无法点击书籍,重试");
  545.                 continue;
  546.             }

  547.             // 步骤3:在详情页左滑进入阅读
  548.             // 关键:番茄小说详情页需要"左滑开始阅读"
  549.             print.log("  左滑进入阅读...");
  550.             auto.drag(
  551.                 Math.floor(屏幕宽 * 0.8),   // 起点X:右侧
  552.                 Math.floor(屏幕高 * 0.5),   // 起点Y:中间
  553.                 Math.floor(屏幕宽 * 0.2),   // 终点X:左侧
  554.                 Math.floor(屏幕高 * 0.5),   // 终点Y:中间
  555.                 500                         // 滑动时长
  556.             );
  557.             sleep.second(秒 = 3);

  558.             // 验证是否进入阅读页
  559.             if (检测是否在阅读页()) {
  560.                 print.log("✓ 成功进入阅读页面");
  561.                 进入阅读成功 = true;
  562.                 break;
  563.             }

  564.             // 尝试点击"左滑开始阅读"按钮
  565.             var 左滑节点 = 查找文字节点(["左滑开始阅读", "左滑"]);
  566.             if (左滑节点) {
  567.                 安全点击节点(左滑节点);
  568.                 sleep.second(秒 = 2);
  569.                
  570.                 // 再次左滑
  571.                 auto.drag(
  572.                     Math.floor(屏幕宽 * 0.8),
  573.                     Math.floor(屏幕高 * 0.5),
  574.                     Math.floor(屏幕宽 * 0.2),
  575.                     Math.floor(屏幕高 * 0.5),
  576.                     500
  577.                 );
  578.                 sleep.second(秒 = 2);

  579.                 if (检测是否在阅读页()) {
  580.                     print.log("✓ 成功进入阅读页面");
  581.                     进入阅读成功 = true;
  582.                     break;
  583.                 }
  584.             }

  585.             print.log("  ⚠ 未能进入阅读页,返回重试");
  586.             auto.back();
  587.             sleep.second(秒 = 1);

  588.         } catch (e) {
  589.             print.log("⚠ 进入阅读页面失败:" + e.message);
  590.             sleep.second(秒 = 3);
  591.         }
  592.     }

  593.     // 进入阅读失败则重启APP
  594.     if (!进入阅读成功) {
  595.         print.log("✗ 无法进入阅读页面,重启APP");
  596.         重启APP();
  597.         continue;
  598.     }

  599.     // ===== 步骤5.4:自动阅读循环 =====
  600.     print.log("\n【步骤5.4】开始自动阅读...");
  601.     var 已阅读章节数 = 0;
  602.     var 连续错误次数 = 0;
  603.     var 阅读开始时间 = Date.now();
  604.     var 目标阅读时长毫秒 = config.每轮阅读时长分钟 * 60 * 1000;

  605.     // 在目标时长内持续阅读
  606.     while (Date.now() - 阅读开始时间 < 目标阅读时长毫秒) {
  607.         已阅读章节数++;
  608.         print.log("\n--- 第 " + 已阅读章节数 + " 章 ---");

  609.         try {
  610.             // 步骤A:阅读当前页
  611.             print.log("⏳ 阅读中...");
  612.             sleep.second(秒 = config.每页阅读时长秒);

  613.             // 步骤B:关闭弹窗
  614.             关闭弹窗();
  615.             sleep.millisecond(毫秒 = 500);

  616.             // 步骤C:定时领取金币
  617.             if (已阅读章节数 % config.金币领取间隔章节 === 0) {
  618.                 领取金币();
  619.             }

  620.             // 步骤D:翻页到下一章
  621.             翻页阅读();

  622.             // 步骤E:验证页面状态
  623.             if (!检测是否在阅读页()) {
  624.                 print.log("⚠ 不在阅读页面,尝试返回");
  625.                 auto.back();
  626.                 sleep.second(秒 = 2);
  627.                
  628.                 // 返回后仍不在阅读页则抛异常
  629.                 if (!检测是否在阅读页()) {
  630.                     throw new Error("无法回到阅读页面");
  631.                 }
  632.             }

  633.             // 阅读成功,重置错误计数
  634.             连续错误次数 = 0;

  635.         } catch (e) {
  636.             // 错误处理与恢复
  637.             连续错误次数++;
  638.             print.log("⚠ 出错(第" + 连续错误次数 + "次):" + e.message);
  639.             sleep.second(秒 = 3);

  640.             // 超过错误阈值则重启APP,从头开始
  641.             if (连续错误次数 >= config.连续错误阈值) {
  642.                 print.log("✗ 连续错误" + config.连续错误阈值 + "次,重启APP重新开始");
  643.                 重启APP();
  644.                 刚重启过 = true;  // 设置标志,下次循环跳过打开APP
  645.                 连续错误次数 = 0;
  646.                 print.log("✓ APP已重启,返回主循环重新开始");
  647.                 break;  // 跳出阅读循环,回到主循环重新开始
  648.             }
  649.         }
  650.     }

  651.     // ===== 步骤5.5:统计本轮结果 =====
  652.     var 实际阅读时长分钟 = (Date.now() - 阅读开始时间) / 1000 / 60;
  653.     print.log("\n【步骤5.5】本轮阅读结束统计");
  654.     print.log("📊 阅读统计:");
  655.     print.log("  已阅读章节:" + 已阅读章节数 + " 章");
  656.     print.log("  实际阅读时长:" + 实际阅读时长分钟.toFixed(1) + " 分钟");
  657.    
  658.     print.log("\n等待5秒后开始下一轮...");
  659.     sleep.second(秒 = 5);
  660. }
复制代码


举报 回复