B2B网络软件

标题: 贝塞尔曲线滑动的实际演示 [打印本页]

作者: YYPOST群发软件    时间: 1 小时前
标题: 贝塞尔曲线滑动的实际演示
贝塞尔曲线滑动的实际演示


贝塞尔曲线滑动的实际演示 B2B网络软件

贝塞尔曲线滑动的实际演示 B2B网络软件

贝塞尔曲线滑动的实际演示 B2B网络软件

  1. // =====================================================================
  2. //  例子1:贝塞尔曲线滑动的实际演示(真机可直接运行)
  3. //  作用:把"机器味"很重的直上直下滑动,改成真人手感的曲线滑动,
  4. //        并加入随机延迟、随机偏移、随机回滑、随机停留等防检测细节。
  5. //
  6. //  ★ 曲线 vs 单次直滑,用错就翻不动页:
  7. //    · 列表页(设置/图文/小说/商品列表)→ 用曲线,效果好,像人慢慢滚
  8. //    · 短视频信息流(快手/抖音)        → 必须用【一次完整的快速长滑】,
  9. //      曲线会被拆成十几个小手势,画面只抖不翻页(已实测踩坑)
  10. //    本文件两套都实现了,改 CFG.feedType 即可切换:list / video
  11. //  引擎:ES5 / Rhino(不要用 let const 箭头函数 模板字符串)
  12. // =====================================================================

  13. // ---------------- 运行统计(结束时汇总打印)----------------
  14. var STAT = { ok: 0, retry: 0, back: 0, stay: 0, popup: 0, startMs: 0 };

  15. // ---------------- 一、配置区(改这里就行)----------------
  16. var CFG = {
  17.     // 就用快手,不折腾
  18.     targetPkg: "com.smile.gifmaker",
  19.     loopCount: 8,          // 演示滑动多少轮
  20.     swipeMinMs: 420,       // 列表页单次滑动最快耗时
  21.     swipeMaxMs: 900,       // 列表页单次滑动最慢耗时
  22.     stayMinMs: 1200,       // 每屏停留最短
  23.     stayMaxMs: 3500,       // 每屏停留最长
  24.     // feedType:list =列表页(设置/图文),用贝塞尔曲线滑动;
  25.     //           video=短视频信息流(快手/抖音),用一次完整的快速长滑翻页。
  26.     feedType: "video",
  27.     videoSwipeMinMs: 220,  // 短视频单次翻页最快(ms):太快不翻,太慢像拖拽
  28.     videoSwipeMaxMs: 380,  // 短视频单次翻页最慢(ms)
  29.     verifySwipe: true,     // 翻页后校验画面是否变化,没变就补一次强滑
  30.     curveEnable: true,     // 列表模式下的曲线滑动开关
  31.     curveSteps: 14,        // 曲线拆成多少小段(越多越圆滑,越慢)
  32.     offsetPx: 18,          // 点击时的随机抖动半径(像素)
  33.     backSwipeRate: 25,     // 百分之多少的概率"往回滑一下"(真人常有的动作)
  34.     microPauseRate: 35,    // 百分之多少的概率中途"手指停一下"
  35.     clearPopupEnable: true, // 刷的过程中顺带关弹窗(青少年模式/升级/权限)
  36.     tapBackEnable: false,   // 视频流不按返回(会退出快手)
  37.     randomTapEnable: false, // 视频流不乱点(会误触点赞/评论/进主页)
  38.     finishGoHome: false,    // 跑完停在快手,不回桌面
  39.     logToFile: true,       // 是否把动作写日志文件
  40.     logPath: "/sdcard/AIWROK/技巧1_曲线滑动日志.txt"
  41. };

  42. // ---------------- 二、基础工具 ----------------
  43. var LOG = [];

  44. function log(s) {
  45.     var line = "[" + nowStr() + "] " + s;
  46.     try { printl(line); } catch (e) {}
  47.     LOG.push(line);
  48.     if (CFG.logToFile && LOG.length >= 10) flushLog();
  49. }

  50. function flushLog() {
  51.     try {
  52.         var txtAll = LOG.join("\n") + "\n";
  53.         LOG = [];
  54.         file.createFloder(parentOf(CFG.logPath));
  55.         txt.writeTxt(CFG.logPath, txtAll, true); // 第三个参数 true = 追加
  56.     } catch (e) {}
  57. }

  58. function parentOf(p) {
  59.     var i = p.lastIndexOf("/");
  60.     return i > 0 ? p.substring(0, i) : p;
  61. }

  62. function nowStr() {
  63.     try { return time.now(); } catch (e) {}
  64.     var d = new Date();
  65.     return d.getHours() + ":" + d.getMinutes() + ":" + d.getSeconds();
  66. }

  67. function rand(min, max) {
  68.     return Math.floor(min + Math.random() * (max - min + 1));
  69. }

  70. function rSleep(min, max) {
  71.     sleep.millisecond(rand(min, max));
  72. }

  73. // 屏幕宽高(带兜底,某些机型取不到就当 1080x2400)
  74. function sw() {
  75.     try { return screen.getScreenWidth(); } catch (e) {}
  76.     try { return device.getScreenWidth(); } catch (e) {}
  77.     return 1080;
  78. }
  79. function sh() {
  80.     try { return screen.getScreenHeight(); } catch (e) {}
  81.     try { return device.getScreenHeight(); } catch (e) {}
  82.     return 2400;
  83. }

  84. // 安全执行:任何一步炸了都不影响主流程(真机必备)
  85. function safe(fn, tag) {
  86.     try { return fn(); } catch (e) {
  87.         log("!! " + (tag || "未知步骤") + " 异常: " + (e.message || e));
  88.         return null;
  89.     }
  90. }

  91. // ---------------- 三、技巧1:贝塞尔曲线滑动 ----------------
  92. // 直上直下的 swipe 是最容易被识别的特征,用二次贝塞尔让轨迹带弧度
  93. function bezierSwipe(x1, y1, x2, y2, durMs, steps) {
  94.     steps = steps || CFG.curveSteps;
  95.     // 控制点:取中点 + 随机横向偏移,让每一刀的弧度都不一样
  96.     var cx = (x1 + x2) / 2 + rand(-60, 60);
  97.     var cy = (y1 + y2) / 2;
  98.     var prevX = x1, prevY = y1;
  99.     var segMs = Math.max(16, Math.round(durMs / steps));

  100.     for (var i = 1; i <= steps; i++) {
  101.         var t = i / steps;
  102.         var mt = 1 - t;
  103.         // 二次贝塞尔公式
  104.         var bx = mt * mt * x1 + 2 * mt * t * cx + t * t * x2;
  105.         var by = mt * mt * y1 + 2 * mt * t * cy + t * t * y2;
  106.         // 每一小段都加一点手抖
  107.         bx += rand(-2, 2);
  108.         by += rand(-2, 2);
  109.         try {
  110.             auto.swip(prevX, prevY, bx, by, segMs, 0);
  111.         } catch (e) {
  112.             try { auto.swipe(prevX, prevY, bx, by, segMs); } catch (e2) {}
  113.         }
  114.         prevX = bx; prevY = by;

  115.         // 技巧:中途随机"手指顿一下",更像真人在看内容
  116.         if (CFG.microPauseRate > 0 && rand(1, 100) <= CFG.microPauseRate && i < steps - 1) {
  117.             sleep.millisecond(rand(40, 160));
  118.         }
  119.     }
  120. }

  121. function lineSwipe(x1, y1, x2, y2, durMs) {
  122.     try { auto.swip(x1, y1, x2, y2, durMs, 0); }
  123.     catch (e) { try { auto.swipe(x1, y1, x2, y2, durMs); } catch (e2) {} }
  124. }

  125. // !!!关键:短视频翻页必须用【一次完整的快速长滑】!!!
  126. // 拆成十几段小滑动(曲线),快手会把每段当成独立的微小手势 → 画面抖动但不翻页
  127. function onceSwipe(x1, y1, x2, y2, durMs) {
  128.     try { auto.swip(x1, y1, x2, y2, durMs, 0); }
  129.     catch (e) { try { auto.swipe(x1, y1, x2, y2, durMs); } catch (e2) {} }
  130. }

  131. // 向上翻一屏(信息流最常用)
  132. function swipeUpFeed() {
  133.     var w = sw(), h = sh();
  134.     var x1 = rand(Math.round(w * 0.35), Math.round(w * 0.65));

  135.     if (CFG.feedType === "video") {
  136.         // 短视频模式:起点靠下、终点靠上、距离长、速度快,一次搞定
  137.         var vy1 = Math.round(h * (0.78 + Math.random() * 0.07));  // 0.78~0.85
  138.         var vy2 = Math.round(h * (0.12 + Math.random() * 0.08));  // 0.12~0.20
  139.         var vdur = rand(CFG.videoSwipeMinMs, CFG.videoSwipeMaxMs); // 220~380ms
  140.         onceSwipe(x1, vy1, x1 + rand(-12, 12), vy2, vdur);
  141.         log("翻页上滑(单次) 起点y=" + vy1 + " 终点y=" + vy2 + " 距离" + (vy1 - vy2) + "px x=" + x1 + " 耗时" + vdur + "ms");
  142.         return true;
  143.     }

  144.     // 列表模式:曲线滑动(设置页/图文列表这类才用得上)
  145.     var y1 = rand(Math.round(h * 0.72), Math.round(h * 0.85));
  146.     var x2 = x1 + rand(-70, 70);
  147.     var y2 = rand(Math.round(h * 0.18), Math.round(h * 0.35));
  148.     var dur = rand(CFG.swipeMinMs, CFG.swipeMaxMs);
  149.     if (CFG.curveEnable) bezierSwipe(x1, y1, x2, y2, dur, CFG.curveSteps);
  150.     else lineSwipe(x1, y1, x2, y2, dur);
  151.     log("上滑(列表) (" + Math.round(x1) + "," + y1 + ")->(" + Math.round(x2) + "," + y2 + ") " + dur + "ms");
  152.     return true;
  153. }

  154. // 向下回滑(真人看完会往回翻,视频流里 = 翻回上一个视频)
  155. function swipeDownFeed() {
  156.     var w = sw(), h = sh();
  157.     var x1 = rand(Math.round(w * 0.35), Math.round(w * 0.65));

  158.     if (CFG.feedType === "video") {
  159.         var vy1 = Math.round(h * (0.15 + Math.random() * 0.08));
  160.         var vy2 = Math.round(h * (0.80 + Math.random() * 0.06));
  161.         var vdur = rand(CFG.videoSwipeMinMs, CFG.videoSwipeMaxMs);
  162.         onceSwipe(x1, vy1, x1 + rand(-12, 12), vy2, vdur);
  163.         STAT.back++;
  164.         log("回滑(单次) 上一个视频 (" + x1 + "," + vy1 + ")->(" + vy2 + ") " + vdur + "ms  [累计回滑" + STAT.back + "次]");
  165.         return true;
  166.     }

  167.     var y1 = rand(Math.round(h * 0.22), Math.round(h * 0.35));
  168.     var x2 = x1 + rand(-50, 50);
  169.     var y2 = rand(Math.round(h * 0.70), Math.round(h * 0.82));
  170.     var dur = rand(CFG.swipeMinMs, CFG.swipeMaxMs);
  171.     if (CFG.curveEnable) bezierSwipe(x1, y1, x2, y2, dur, CFG.curveSteps);
  172.     else lineSwipe(x1, y1, x2, y2, dur);
  173.     log("回滑一下(拟人)");
  174.     return true;
  175. }

  176. // 慢慢"看内容"的小幅滑动(阅读类任务用)
  177. function slowReadSwipe() {
  178.     if (CFG.feedType === "video") {
  179.         // 视频流没有"慢速阅读",改成老老实实看一会儿(停留就是最好的拟人)
  180.         var s = rand(1500, 3500);
  181.         STAT.stay++;
  182.         log("停留观看 " + s + "ms  [累计停留" + STAT.stay + "次]");
  183.         sleep.millisecond(s);
  184.         return;
  185.     }
  186.     var w = sw(), h = sh();
  187.     var x = rand(Math.round(w * 0.4), Math.round(w * 0.6));
  188.     var y1 = Math.round(h * 0.78);
  189.     var y2 = Math.round(h * 0.48);
  190.     if (CFG.curveEnable) bezierSwipe(x, y1, x, y2, rand(1200, 2000), 20);
  191.     else lineSwipe(x, y1, x, y2, 1500);
  192.     log("慢速阅读滑动");
  193. }

  194. // ---------------- 翻页校验:滑了没换视频,就补一次更强的滑 ----------------
  195. function shotFingerprint() {
  196.     try {
  197.         var b = screen.screenShotFull().toBase64();
  198.         if (!b) return "";
  199.         // 取头尾各 200 字符做指纹,够用且快
  200.         return b.substring(0, 200) + "|" + b.substring(b.length - 200) + "|" + b.length;
  201.     } catch (e) { return ""; }
  202. }

  203. // 返回 true = 确认翻页成功
  204. function swipeUpWithVerify() {
  205.     var before = shotFingerprint();
  206.     swipeUpFeed();
  207.     sleep.millisecond(700); // 等视频切换动画
  208.     var after = shotFingerprint();
  209.     if (before && after && before === after) {
  210.         STAT.retry++;
  211.         log("× 画面无变化(没翻页),补一次强滑 0.88h→0.08h 250ms");
  212.         var w = sw(), h = sh();
  213.         onceSwipe(Math.round(w / 2), Math.round(h * 0.88), Math.round(w / 2), Math.round(h * 0.08), 250);
  214.         sleep.millisecond(900);
  215.         var after2 = shotFingerprint();
  216.         if (after2 && after2 !== before) {
  217.             STAT.ok++;
  218.             log("√ 补滑成功,已切到新视频");
  219.             return true;
  220.         }
  221.         log("× 补滑后仍未变化,可能被弹窗/直播页挡住");
  222.         return false;
  223.     }
  224.     STAT.ok++;
  225.     log("√ 翻页成功,画面已变化(本轮第 " + STAT.ok + " 次有效翻页)");
  226.     return true;
  227. }

  228. // ---------------- 四、技巧2:随机偏移点击 ----------------
  229. // 永远点同一个像素 = 一眼假的机器人,点的时候给个抖动
  230. function tapRandom(cx, cy, offset) {
  231.     offset = (offset === undefined) ? CFG.offsetPx : offset;
  232.     var x = cx + rand(-offset, offset);
  233.     var y = cy + rand(-offset, offset);
  234.     if (x < 0) x = 0; if (y < 0) y = 0;
  235.     if (x > sw()) x = sw(); if (y > sh()) y = sh();
  236.     try { auto.clickPoint(x, y); }
  237.     catch (e) { try { device.clickPoint(x, y); } catch (e2) {} }
  238.     log("点击 (" + x + "," + y + ") 抖动±" + offset);
  239.     return { x: x, y: y };
  240. }

  241. // 在矩形区域内随机点(百分比写法,不挑分辨率)
  242. // areaPct = [x1%, y1%, x2%, y2%],例如 [0.1,0.2,0.9,0.6]
  243. function tapAreaPct(areaPct) {
  244.     var w = sw(), h = sh();
  245.     var x1 = Math.round(areaPct[0] * w), y1 = Math.round(areaPct[1] * h);
  246.     var x2 = Math.round(areaPct[2] * w), y2 = Math.round(areaPct[3] * h);
  247.     // 抖动上限 25px:区域越大不代表要抖越狠,抖太大会误触到旁边的按钮(点赞/评论/头像)
  248.     var off = Math.min(25, Math.max(8, (x2 - x1) / 4));
  249.     return tapRandom(Math.round((x1 + x2) / 2), Math.round((y1 + y2) / 2), off);
  250. }

  251. // ---------------- 五、技巧3:长按 + 轻点 ----------------
  252. function longTap(x, y, ms) {
  253.     ms = ms || 800;
  254.     try { auto.longClickPoint(x, y, ms); }
  255.     catch (e) {
  256.         // 没有 longClick 接口时,用"按下-停留-抬起"手势兜底
  257.         try {
  258.             auto.swip(x, y, x + rand(-2, 2), y + rand(-2, 2), ms, 0);
  259.         } catch (e2) {}
  260.     }
  261.     log("长按 (" + x + "," + y + ") " + ms + "ms");
  262. }

  263. // ---------------- 六、前台判断(多通道,实测有的设备 getTopPackName 返回空)----------------
  264. function topPkg() {
  265.     try { var t1 = app.getTopPackName(); if (t1) return t1; } catch (e) {}
  266.     try { var t2 = app.getTopPackNameV2(); if (t2) return t2; } catch (e) {}
  267.     try { var t3 = auto.getTopPackName(); if (t3) return t3; } catch (e) {}
  268.     // 兜底:从当前 Activity 里截包名(形如 com.smile.gifmaker/xxx.Activity)
  269.     var act = "";
  270.     try { act = "" + app.getCurrentActivity(); } catch (e) {
  271.         try { act = "" + app.getAgentCurrenAcitvity(); } catch (e2) { act = ""; }
  272.     }
  273.     if (act && act.indexOf("/") > 0) return act.substring(0, act.indexOf("/"));
  274.     return act || "";
  275. }

  276. function isInstalled(pkg) {
  277.     // 方式1(最可靠):能查到版本号/应用名 = 装了
  278.     try { var v = app.getVerName(pkg); if (v) return true; } catch (e) {}
  279.     try { var n = app.getAppName(pkg); if (n) return true; } catch (e) {}
  280.     // 方式2:getAllApp 返回格式不定(字符串或对象),统一序列化后匹配
  281.     try {
  282.         var list = app.getAllApp();
  283.         for (var i = 0; i < list.length; i++) {
  284.             var s = "";
  285.             try {
  286.                 s = (typeof list[i] === "object") ? JSON.stringify(list[i]) : String(list[i]);
  287.             } catch (e1) { s = ""; }
  288.             if (s.indexOf(pkg) >= 0) return true;
  289.         }
  290.     } catch (e) {}
  291.     return false; // 注意:检测API不全时可能误报,openTarget 里不做硬回退
  292. }

  293. // 启动后/刷的过程中的弹窗兜底(快手常见:青少年模式、升级、权限)
  294. // 只放"点了绝对安全"的词,不放"关闭/确定"防止误点业务按钮
  295. var POPUP_WORDS = ["我知道了", "以后再说", "下次再说", "暂不更新", "暂不升级",
  296.                    "跳过", "稍后再说", "拒绝", "不允许"];

  297. // 取整页文本(一次调用拿到全部文字,避免对词库逐个 nodeSearch 盲扫刷警告)
  298. function pageText() {
  299.     var t = "";
  300.     try { t = String(auto.getXml() || ""); } catch (e) { t = ""; }
  301.     if (!t || t.length < 20) {
  302.         try { t = String(ocr.getAllString() || ""); } catch (e) { t = ""; }
  303.     }
  304.     return t;
  305. }

  306. function killPopupOnce() {
  307.     if (!CFG.clearPopupEnable) return false;
  308.     // 关键:先确认页面上真的有弹窗词,再去查找节点。
  309.     // 否则找不到元素时引擎会打印"getNode错误元素0未找到"警告,刷满日志。
  310.     var page = pageText();
  311.     if (!page) return false; // 取不到页面信息就放弃本轮杀弹窗,不硬扫

  312.     for (var i = 0; i < POPUP_WORDS.length; i++) {
  313.         if (page.indexOf(POPUP_WORDS[i]) < 0) continue; // 页面里没这个词,跳过
  314.         // 通道1:节点(先验明节点真身再点)
  315.         var n = null;
  316.         try { n = auto.nodeSearch(300).text(POPUP_WORDS[i]).getNode(0); } catch (e) { n = null; }
  317.         if (n) {
  318.             var txtN = "";
  319.             try { txtN = String(n.getText() || ""); } catch (e1) { txtN = ""; }
  320.             if (txtN.indexOf(POPUP_WORDS[i]) >= 0) {
  321.                 var clicked = false;
  322.                 try { n.clickPoint(); clicked = true; } catch (e2) {
  323.                     try { n.click(); clicked = true; } catch (e3) {}
  324.                 }
  325.                 if (clicked) {
  326.                     log("关弹窗(节点): " + POPUP_WORDS[i]);
  327.                     sleep.millisecond(1000);
  328.                     return true;
  329.                 }
  330.             }
  331.         }
  332.         // 通道2:OCR
  333.         try {
  334.             var p = ocr.findTapPoint(POPUP_WORDS[i]);
  335.             if (p && typeof p.x !== "undefined" && p.x > 0) {
  336.                 auto.clickPoint(p.x + rand(-2, 2), p.y + rand(-2, 2));
  337.                 log("关弹窗(OCR): " + POPUP_WORDS[i]);
  338.                 sleep.millisecond(1000);
  339.                 return true;
  340.             }
  341.         } catch (e) {}
  342.     }
  343.     return false;
  344. }

  345. // ---------------- 七、启动目标APP ----------------
  346. function openTarget() {
  347.     // 打印检测结果,方便核对(应用名/版本能取到就是装了)
  348.     var vName = "", aName = "";
  349.     try { vName = app.getVerName(CFG.targetPkg); } catch (e) {}
  350.     try { aName = app.getAppName(CFG.targetPkg); } catch (e) {}
  351.     log("安装检测: " + CFG.targetPkg + " 应用名=" + (aName || "取不到") + " 版本=" + (vName || "取不到"));
  352.     if (!isInstalled(CFG.targetPkg)) {
  353.         // 只提示,不回退——安装检测API在部分设备上不全,误报率高于漏报率
  354.         log("注意:安装检测未确认(可能误报),仍直接尝试启动");
  355.     }
  356.     log("启动 " + CFG.targetPkg);
  357.     try { app.openApp(CFG.targetPkg); } catch (e) { try { app.startApp(CFG.targetPkg); } catch (e2) {} }
  358.     rSleep(4500, 6000);
  359.     var top = "";
  360.     for (var i = 0; i < 12; i++) {
  361.         killPopupOnce(); // 首启的青少年模式/权限/升级弹窗
  362.         top = topPkg();
  363.         if (top === CFG.targetPkg) { log("已在前台: " + top); return true; }
  364.         sleep.millisecond(800);
  365.     }
  366.     // 部分设备的前台API一律返回空(非脚本问题),此时再补一次启动兜底,然后照常往下走
  367.     if (!top) {
  368.         log("提示:本机前台API取不到包名(属设备兼容问题,不影响滑动)");
  369.         try { app.openApp(CFG.targetPkg); } catch (e) { try { app.startApp(CFG.targetPkg); } catch (e2) {} }
  370.         sleep.millisecond(3000);
  371.         log("已补启动一次,继续滑动");
  372.     } else {
  373.         log("当前前台=" + top + ",目标=" + CFG.targetPkg + "(继续滑动,不影响)");
  374.     }
  375.     return false;
  376. }

  377. // ---------------- 七、主流程 ----------------
  378. function main() {
  379.     log("===== 例子1 贝塞尔曲线滑动演示 开始(模式:" + CFG.feedType + ")=====");

  380.     STAT.startMs = new Date().getTime();

  381.     var w = sw(), h = sh();
  382.     log("分辨率: " + w + " x " + h);

  383.     // 开场信息:设备、电量、模式、本轮参数
  384.     try { log("设备: " + device.getModel() + " / 系统 " + device.getVersion()); } catch (e) {}
  385.     try { log("电量: " + device.getBatteryLevel() + "%  充电中: " + device.isCharging()); } catch (e) {}
  386.     log("运行参数: 轮数=" + CFG.loopCount + " 模式=" + CFG.feedType +
  387.         " 翻页耗时=" + CFG.videoSwipeMinMs + "~" + CFG.videoSwipeMaxMs + "ms" +
  388.         " 停留=" + CFG.stayMinMs + "~" + CFG.stayMaxMs + "ms" +
  389.         " 回滑概率=" + CFG.backSwipeRate + "%");

  390.     openTarget();

  391.     for (var i = 1; i <= CFG.loopCount; i++) {
  392.         var roundStart = new Date().getTime();
  393.         log("---- 第 " + i + " / " + CFG.loopCount + " 轮(进度 " +
  394.             Math.round(i * 100 / CFG.loopCount) + "%)----");

  395.         // 0) 每轮先顺手清一下弹窗(青少年模式/升级随时会蹦)
  396.         if (killPopupOnce()) {
  397.             STAT.popup++;
  398.             log("本轮清理了弹窗 [累计弹窗" + STAT.popup + "次]");
  399.         }

  400.         // 1) 主行动:上滑翻页(带校验,没翻成功就补一次)
  401.         if (CFG.verifySwipe) swipeUpWithVerify();
  402.         else swipeUpFeed();

  403.         // 2) 拟人停留:随机看一会儿
  404.         var st = rand(CFG.stayMinMs, CFG.stayMaxMs);
  405.         log("停留 " + st + "ms 观看");
  406.         sleep.millisecond(st);

  407.         // 3) 概率性回滑:真人不是单向下拉的
  408.         if (rand(1, 100) <= CFG.backSwipeRate) {
  409.             rSleep(400, 1200);
  410.             swipeDownFeed();
  411.             rSleep(600, 1500);
  412.         }

  413.         // 4) 概率性慢速阅读:偶尔停下来认真看一条
  414.         if (rand(1, 100) <= 30) {
  415.             slowReadSwipe();
  416.             rSleep(1000, 2500);
  417.         }

  418.         // 5) 概率性随机轻点空白区:模拟误触/看图片(视频流默认关闭)
  419.         if (CFG.randomTapEnable && rand(1, 100) <= 20) {
  420.             tapAreaPct([0.15, 0.25, 0.85, 0.55]);
  421.             rSleep(800, 1800);
  422.             if (CFG.tapBackEnable) {
  423.                 try { auto.pressBack(); } catch (e) { try { device.pressBack(); } catch (e2) {} }
  424.             }
  425.             rSleep(600, 1200);
  426.         }

  427.         log("本轮耗时 " + (new Date().getTime() - roundStart) + "ms");

  428.         // 每轮汇报一次电量,长时间跑能看出掉电速度
  429.         if (i % 3 === 0) {
  430.             try { log("当前电量: " + device.getBatteryLevel() + "%"); } catch (e) {}
  431.         }
  432.     }

  433.     // 收尾:默认停在原地(刷视频就是让它一直待在APP里)
  434.     if (CFG.finishGoHome) {
  435.         safe(function () { auto.home(); }, "回桌面");
  436.         log("已回桌面");
  437.     } else {
  438.         log("收尾:停在 " + CFG.targetPkg + " 不退出");
  439.     }

  440.     // 结束汇总报告
  441.     var totalMs = new Date().getTime() - STAT.startMs;
  442.     log("================ 运行汇总 ================");
  443.     log("总耗时: " + Math.round(totalMs / 1000) + " 秒(平均单轮 " +
  444.         Math.round(totalMs / CFG.loopCount) + "ms)");
  445.     log("有效翻页: " + STAT.ok + " 次   补滑: " + STAT.retry + " 次");
  446.     log("回滑: " + STAT.back + " 次   停留观看: " + STAT.stay + " 次   清理弹窗: " + STAT.popup + " 次");
  447.     if (CFG.loopCount > 0) {
  448.         log("翻页成功率: " + Math.round(STAT.ok * 100 / CFG.loopCount) + "%");
  449.     }
  450.     log("==========================================");

  451.     flushLog();
  452.     log("===== 例子1 结束,日志: " + CFG.logPath + " =====");
  453.     try { toast.show("演示完成 翻页" + STAT.ok + "次"); } catch (e) {}
  454. }

  455. main();
复制代码







欢迎光临 B2B网络软件 (http://bbs.niubt.cn/) Powered by Discuz! X3.2