信息发布软件,b2b软件,广告发布软件

 找回密码
 立即注册
搜索
查看: 1|回复: 0

[宣传软件网站动态] 无碍模式auto淘宝千牛自动发货系统

[复制链接]

1950

主题

1960

帖子

1万

积分

积分
11428
发表于 1 小时前 | 显示全部楼层 |阅读模式

资讯缩略图:

资讯发布日期:2026-08-03

资讯发布简介:无碍模式auto淘宝千牛自动发货系统

资讯关键词:无碍模式auto淘宝千牛自动发货系统

资讯所属分类:IT资讯 

联系:

① 本信息收集于网络,如有不对的地方欢迎联系我纠正!
② 本信息免费收录,不存在价格的问题!
③ 如果您的网站也想这样出现在这里,请您加好友情链接,我当天会审核通过!

④友情链接关键字:软件网站分类目录 网址:http://www.postbbs.com/

资讯详细描述
无碍模式auto淘宝千牛自动发货系统 3.png
  1. // =============================================================================
  2. //  淘宝千牛自动发货系统  v3.0(精简版)
  3. //  仅支持无碍模式(auto),使用完整商品标题精准匹配
  4. //  功能:24小时监控千牛待发货列表 → 匹配商品标题 → 发送卡密/网盘链接
  5. //  适用:AIWROK 安卓自动化平台,ES5/Rhino 引擎
  6. // =============================================================================

  7. importClass(java.io.File);
  8. importClass(java.io.FileWriter);
  9. importClass(java.io.BufferedReader);
  10. importClass(java.io.InputStreamReader);
  11. importClass(java.io.FileInputStream);
  12. importClass(java.io.FileOutputStream);
  13. importClass(java.io.OutputStreamWriter);

  14. // =============================================================================
  15. //  第一部分:全局配置
  16. // =============================================================================

  17. var CONFIG = {
  18.     千牛包名: "com.taobao.qianniu",
  19.     轮询间隔: 30,          // 检查新订单间隔(秒)
  20.     最大重试: 3,
  21.     操作延时: 2,            // 每步操作后等待(秒)

  22.     // 卡密服务器API(留空则用本地文件)
  23.     服务器地址: "",
  24.     服务器密钥: "",
  25.     使用服务器: false,

  26.     卡密目录: "/sdcard/卡密仓库/",
  27.     发货记录文件: "/sdcard/发货记录.txt",

  28.     启用保活: true,
  29.     保活间隔: 300,

  30.     启用悬浮窗: true,

  31.     // debug=只输出节点摘要不发货; test=处理一个真实订单测试; false=正常监控
  32.     调试模式: "debug",
  33. };

  34. // =============================================================================
  35. //  第二部分:商品配置(用完整商品标题,精准匹配)
  36. // =============================================================================

  37. /**
  38. * 配置说明:
  39. *   商品标题:  淘宝商品的完整标题,用于在订单列表中精准匹配
  40. *   发货类型:  "卡密" / "网盘" / "卡密+网盘"
  41. *   卡密文件:  本地卡密文件路径(每行一个卡密,用过的自动删除)
  42. *   网盘链接:  该商品对应的网盘链接
  43. *   发货话术:  发送给买家的话术,{卡密}和{网盘}自动替换
  44. */
  45. var 商品配置 = {
  46.     "二年级数学必刷题上册人教版口算题卡天天练": {
  47.         发货类型: "卡密",
  48.         卡密文件: "/sdcard/卡密仓库/二年级数学必刷题.txt",
  49.         网盘链接: "",
  50.         发货话术: "您好,您购买的【二年级数学必刷题】已发货!\n卡密:{卡密}\n请打开APP输入卡密激活使用,有问题随时联系~"
  51.     },
  52.     "小学单位换算练习元角分长度单位专项训练": {
  53.         发货类型: "网盘",
  54.         卡密文件: "",
  55.         网盘链接: "https://pan.baidu.com/s/1aBcDeFgH 提取码: x1y2",
  56.         发货话术: "您好,您购买的【小学单位换算练习】已发货!\n网盘下载链接:{网盘}\n请复制链接到浏览器下载,有问题随时联系~"
  57.     },
  58.     "一年级加减口算练习册10以内20以内凑十法": {
  59.         发货类型: "卡密+网盘",
  60.         卡密文件: "/sdcard/卡密仓库/一年级加减口算.txt",
  61.         网盘链接: "https://pan.baidu.com/s/2bCdEfGhI 提取码: a3b4",
  62.         发货话术: "您好,您购买的【一年级加减口算】已发货!\n激活卡密:{卡密}\n网盘链接:{网盘}\n请先下载APP,输入卡密激活使用~"
  63.     }
  64.     // 继续添加商品...把完整的淘宝商品标题填进来即可
  65. };

  66. // =============================================================================
  67. //  第三部分:无碍模式操作封装(仅auto,无模式分支)
  68. // =============================================================================

  69. var ActionAdapter = {

  70.     // ================================================================
  71.     //  基础操作(直接调用 auto 对象)
  72.     // ================================================================

  73.     init: function() {
  74.         if (typeof auto !== 'undefined') {
  75.             logger.log("无碍模式已就绪");
  76.             return true;
  77.         }
  78.         logger.err("auto对象不存在,请开启无障碍服务");
  79.         return false;
  80.     },

  81.     click: function(x, y) {
  82.         try {
  83.             auto.clickPoint(x, y);
  84.             return true;
  85.         } catch (e) {
  86.             logger.err("点击失败: " + e);
  87.             return false;
  88.         }
  89.     },

  90.     clickPercent: function(px, py) {
  91.         try {
  92.             auto.clickPercent(px, py);
  93.             return true;
  94.         } catch (e) {
  95.             logger.err("百分比点击失败: " + e);
  96.             return false;
  97.         }
  98.     },

  99.     swipe: function(x1, y1, x2, y2, duration) {
  100.         duration = duration || 500;
  101.         try {
  102.             auto.swip(x1, y1, x2, y2, duration, 0);
  103.             return true;
  104.         } catch (e) {
  105.             logger.err("滑动失败: " + e);
  106.             return false;
  107.         }
  108.     },

  109.     swipePercent: function(px1, py1, px2, py2, duration) {
  110.         duration = duration || 500;
  111.         var w = screen.getScreenWidth();
  112.         var h = screen.getScreenHeight();
  113.         return this.swipe(
  114.             Math.floor(px1 * w), Math.floor(py1 * h),
  115.             Math.floor(px2 * w), Math.floor(py2 * h), duration
  116.         );
  117.     },

  118.     back: function() {
  119.         try { auto.back(); return true; } catch (e) { return false; }
  120.     },

  121.     home: function() {
  122.         try { auto.home(); return true; } catch (e) { return false; }
  123.     },

  124.     // ================================================================
  125.     //  核心:auto.getXml() 节点解析
  126.     //  千牛很多按钮文字在 desc 属性里,必须同时匹配 text 和 desc
  127.     // ================================================================

  128.     getXml: function() {
  129.         try {
  130.             if (typeof auto !== 'undefined' && typeof auto.getXml === 'function') {
  131.                 return auto.getXml();
  132.             }
  133.         } catch (e) {
  134.             logger.err("getXml失败: " + e);
  135.         }
  136.         return "";
  137.     },

  138.     /**
  139.      * 从XML中查找包含指定文字的节点(同时匹配 text 和 desc)
  140.      */
  141.     findNodes: function(xml, keyword) {
  142.         var results = [];
  143.         if (!xml || !keyword) return results;

  144.         var pattern = /<node\s+([^>]+?)(?:\/>|>)/g;
  145.         var match;
  146.         while ((match = pattern.exec(xml)) !== null) {
  147.             var attrs = match[1];
  148.             var textVal = "";
  149.             var descVal = "";
  150.             var tm = attrs.match(/text="([^"]*)"/);
  151.             if (tm) textVal = tm[1];
  152.             var dm = attrs.match(/desc="([^"]*)"/);
  153.             if (dm) descVal = dm[1];

  154.             if (textVal.indexOf(keyword) !== -1 || descVal.indexOf(keyword) !== -1) {
  155.                 results.push(this._parseNode(attrs, textVal, descVal));
  156.             }
  157.         }
  158.         return results;
  159.     },

  160.     /**
  161.      * 解析XML中所有节点(不过滤关键词,用于调试和父节点查找)
  162.      */
  163.     findAllNodes: function(xml) {
  164.         var results = [];
  165.         if (!xml) return results;

  166.         var pattern = /<node\s+([^>]+?)(?:\/>|>)/g;
  167.         var match;
  168.         while ((match = pattern.exec(xml)) !== null) {
  169.             var attrs = match[1];
  170.             var tm = attrs.match(/text="([^"]*)"/);
  171.             var dm = attrs.match(/desc="([^"]*)"/);
  172.             results.push(this._parseNode(attrs, tm ? tm[1] : "", dm ? dm[1] : ""));
  173.         }
  174.         return results;
  175.     },

  176.     /**
  177.      * 解析单个节点属性(内部方法)
  178.      */
  179.     _parseNode: function(attrs, textVal, descVal) {
  180.         var cpm = attrs.match(/centerPercent="\[([0-9.]+),([0-9.]+)\]"/);
  181.         var cm = attrs.match(/center="\[(\d+),(\d+)\]"/);
  182.         var clm = attrs.match(/clickable="([^"]*)"/);
  183.         var idm = attrs.match(/id="([^"]*)"/);
  184.         var bm = attrs.match(/bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"/);
  185.         var clsm = attrs.match(/class="([^"]*)"/);

  186.         return {
  187.             text: textVal,
  188.             desc: descVal,
  189.             x: cm ? parseInt(cm[1]) : 0,
  190.             y: cm ? parseInt(cm[2]) : 0,
  191.             px: cpm ? parseFloat(cpm[1]) : 0,
  192.             py: cpm ? parseFloat(cpm[2]) : 0,
  193.             clickable: clm ? clm[1] : "false",
  194.             id: idm ? idm[1] : "",
  195.             bounds: bm ? [parseInt(bm[1]), parseInt(bm[2]), parseInt(bm[3]), parseInt(bm[4])] : null,
  196.             class: clsm ? clsm[1] : ""
  197.         };
  198.     },

  199.     /**
  200.      * 查找不可点击节点的最近可点击父节点
  201.      */
  202.     findClickableParent: function(node, xml) {
  203.         if (!node || !node.bounds) return null;
  204.         var allNodes = this.findAllNodes(xml);
  205.         var nodeArea = (node.bounds[2] - node.bounds[0]) * (node.bounds[3] - node.bounds[1]);
  206.         var bestParent = null;
  207.         var bestArea = 999999999;

  208.         for (var i = 0; i < allNodes.length; i++) {
  209.             var p = allNodes[i];
  210.             if (p.clickable !== "true" || !p.bounds) continue;
  211.             // 父节点必须包含目标节点
  212.             if (p.bounds[0] <= node.bounds[0] && p.bounds[1] <= node.bounds[1] &&
  213.                 p.bounds[2] >= node.bounds[2] && p.bounds[3] >= node.bounds[3]) {
  214.                 var pArea = (p.bounds[2] - p.bounds[0]) * (p.bounds[3] - p.bounds[1]);
  215.                 // 选面积最小的可点击父节点(最近的)
  216.                 if (pArea < bestArea && pArea >= nodeArea) {
  217.                     bestArea = pArea;
  218.                     bestParent = p;
  219.                 }
  220.             }
  221.         }
  222.         return bestParent;
  223.     },

  224.     /**
  225.      * 找文字并点击(核心方法)
  226.      * 1. 查找包含文字的节点
  227.      * 2. 优先点击 clickable=true 的节点
  228.      * 3. 不可点击的找父节点点击
  229.      */
  230.     findTextAndClick: function(text, timeout) {
  231.         timeout = timeout || 3000;
  232.         var startTime = new Date().getTime();

  233.         while (new Date().getTime() - startTime < timeout) {
  234.             var xml = this.getXml();
  235.             if (!xml) { sleep.millisecond(毫秒 = 300); continue; }

  236.             var result = this._findBestNode(xml, text);
  237.             if (result) {
  238.                 if (result.clickable === "true") {
  239.                     this.clickPercent(result.px, result.py);
  240.                 } else {
  241.                     // 不可点击 → 找最近可点击父节点
  242.                     var parent = this.findClickableParent(result, xml);
  243.                     if (parent) {
  244.                         logger.log("节点不可点击,使用父节点: [" + result.text + "|" + result.desc + "] -> px=" + parent.px + " py=" + parent.py);
  245.                         this.clickPercent(parent.px, parent.py);
  246.                     } else {
  247.                         this.clickPercent(result.px, result.py);
  248.                     }
  249.                 }
  250.                 sleep.millisecond(毫秒 = 500);
  251.                 logger.log("找到并点击: " + text + " (text=" + result.text + " desc=" + result.desc + ")", true);
  252.                 return true;
  253.             }
  254.             sleep.millisecond(毫秒 = 300);
  255.         }

  256.         logger.warn("未找到文字: " + text);
  257.         return false;
  258.     },

  259.     /**
  260.      * 查找最佳节点(优先可点击的)
  261.      */
  262.     _findBestNode: function(xml, text) {
  263.         var nodes = this.findNodes(xml, text);
  264.         if (nodes.length === 0) return null;
  265.         // 优先返回 clickable=true
  266.         for (var i = 0; i < nodes.length; i++) {
  267.             if (nodes[i].clickable === "true") return nodes[i];
  268.         }
  269.         return nodes[0];
  270.     },

  271.     /**
  272.      * 找文字(返回节点信息,不点击)
  273.      */
  274.     findText: function(text) {
  275.         var xml = this.getXml();
  276.         if (!xml) return null;
  277.         return this._findBestNode(xml, text);
  278.     },

  279.     hasText: function(text) {
  280.         return this.findText(text) !== null;
  281.     },

  282.     hasAnyText: function(texts) {
  283.         for (var i = 0; i < texts.length; i++) {
  284.             if (this.findText(texts[i])) return true;
  285.         }
  286.         return false;
  287.     },

  288.     /**
  289.      * 输出当前页面所有有意义节点的摘要(调试用)
  290.      */
  291.     dumpPageSummary: function() {
  292.         var xml = this.getXml();
  293.         if (!xml) { logger.warn("无法获取页面XML"); return; }

  294.         var nodes = this.findAllNodes(xml);
  295.         var count = 0;
  296.         logger.log("====== 页面节点摘要 ======");
  297.         for (var i = 0; i < nodes.length; i++) {
  298.             var n = nodes[i];
  299.             // 过滤掉空白节点
  300.             if (n.text === "" && n.desc === "") continue;
  301.             logger.log("  [" + (n.clickable === "true" ? "可点击" : "  不可") + "] text=\"" + n.text + "\" desc=\"" + n.desc + "\" px=" + n.px + " py=" + n.py);
  302.             count++;
  303.         }
  304.         logger.log("====== 共 " + count + " 个有意义节点 ======");
  305.     },

  306.     /**
  307.      * 输入文字(通过剪贴板粘贴,兼容中文)
  308.      */
  309.     inputText: function(text) {
  310.         try {
  311.             if (typeof Clipboard !== 'undefined' && Clipboard.copy) {
  312.                 Clipboard.copy(text);
  313.                 sleep.millisecond(毫秒 = 300);
  314.             }

  315.             // 尝试粘贴
  316.             try {
  317.                 var node = auto.nodeSearch(3000).focused(true);
  318.                 if (node && typeof node.getNode === 'function') {
  319.                     var n = node.getNode(0);
  320.                     if (n && typeof n.paste === 'function') {
  321.                         n.paste();
  322.                     }
  323.                 }
  324.             } catch (pe) {}

  325.             sleep.second(秒 = 1);
  326.             logger.log("已输入文字: " + text.substring(0, 20) + (text.length > 20 ? "..." : ""));
  327.             return true;
  328.         } catch (e) {
  329.             logger.err("输入文字失败: " + e);
  330.             return false;
  331.         }
  332.     },

  333.     /**
  334.      * 等待指定文字出现
  335.      */
  336.     backToText: function(text, timeout) {
  337.         timeout = timeout || 10000;
  338.         var startTime = new Date().getTime();
  339.         while (new Date().getTime() - startTime < timeout) {
  340.             if (this.hasText(text)) return true;
  341.             sleep.millisecond(毫秒 = 500);
  342.         }
  343.         return false;
  344.     }
  345. };

  346. // =============================================================================
  347. //  第四部分:日志系统(悬浮窗 + Toast + print.log/err/warn)
  348. // =============================================================================

  349. var logger = {
  350.     悬浮窗实例: null,
  351.     标题控件: null,
  352.     内容控件: null,
  353.     状态控件: null,
  354.     日志缓冲: [],
  355.     最大行数: 12,
  356.     统计: { 轮次: 0, 发货: 0, 错误: 0, 启动时间: null },

  357.     init: function() {
  358.         this.统计.启动时间 = new Date();
  359.         if (!CONFIG.启用悬浮窗) return;
  360.         try {
  361.             this.悬浮窗实例 = new floatUI();
  362.             this.悬浮窗实例.loadXML(
  363.                 '<LinearLayout orientation="vertical" w="400" h="280" gravity="top" id="log_main" background="#CC000000">' +
  364.                 '  <LinearLayout orientation="horizontal" w="400" h="30" gravity="center_vertical" background="#FF1a1a2e">' +
  365.                 '    <TextView id="log_title" textColor="#FFD700" textSize="13sp" padding="8dp" layout_weight="1" text="&#128230; 淘宝自动发货v3" />' +
  366.                 '  </LinearLayout>' +
  367.                 '  <LinearLayout orientation="horizontal" w="400" h="24" gravity="center_vertical" background="#FF16213e">' +
  368.                 '    <TextView id="log_status" textColor="#00FFFF" textSize="10sp" padding="6dp" layout_weight="1" text="状态: 初始化中..." />' +
  369.                 '  </LinearLayout>' +
  370.                 '  <TextView id="log_content" textColor="#00FF00" textSize="10sp" padding="6dp" layout_weight="1" text="等待启动...\n" />' +
  371.                 '</LinearLayout>'
  372.             );
  373.             this.标题控件 = this.悬浮窗实例.findViewById('log_title');
  374.             this.状态控件 = this.悬浮窗实例.findViewById('log_status');
  375.             this.内容控件 = this.悬浮窗实例.findViewById('log_content');

  376.             var self = this;
  377.             setTimeout(function() {
  378.                 if (self.悬浮窗实例) self.悬浮窗实例.setPosition(5, 5);
  379.             }, 200);
  380.         } catch (e) {}
  381.     },

  382.     _time: function() {
  383.         var now = new Date();
  384.         var h = now.getHours(), m = now.getMinutes(), s = now.getSeconds();
  385.         return (h < 10 ? "0" : "") + h + ":" + (m < 10 ? "0" : "") + m + ":" + (s < 10 ? "0" : "") + s;
  386.     },

  387.     _更新状态: function() {
  388.         if (!this.状态控件) return;
  389.         try {
  390.             var runtime = "";
  391.             if (this.统计.启动时间) {
  392.                 var diff = Math.floor((new Date() - this.统计.启动时间) / 1000);
  393.                 var h = Math.floor(diff / 3600);
  394.                 var m = Math.floor((diff % 3600) / 60);
  395.                 var s = diff % 60;
  396.                 runtime = " 运行" + (h > 0 ? h + "h" : "") + m + "m" + s + "s";
  397.             }
  398.             this.状态控件.setText("轮次:" + this.统计.轮次 + " | 发货:" + this.统计.发货 + " | 错误:" + this.统计.错误 + runtime);
  399.         } catch (e) {}
  400.     },

  401.     _toast: function(msg) {
  402.         try {
  403.             if (typeof toast !== 'undefined' && toast.show) toast.show("" + msg);
  404.         } catch (e) {}
  405.     },

  406.     _output: function(level, msg, showToast) {
  407.         var timeStr = this._time();
  408.         var levelTag = "";
  409.         if (level === "信息") { levelTag = "ℹ"; }
  410.         else if (level === "成功") { levelTag = "✅"; }
  411.         else if (level === "警告") { levelTag = "⚠"; }
  412.         else if (level === "错误") { levelTag = "❌"; this.统计.错误++; }

  413.         var line = timeStr + " " + levelTag + " " + msg;

  414.         // 按级别调用 AIWROK 平台原生方法
  415.         try {
  416.             if (level === "错误" && typeof print !== 'undefined' && print.err) {
  417.                 print.err(line);
  418.             } else if (level === "警告" && typeof print !== 'undefined' && print.warn) {
  419.                 print.warn(line);
  420.             } else if (typeof print !== 'undefined' && print.log) {
  421.                 print.log(line);
  422.             } else if (typeof printl === 'function') {
  423.                 printl(line);
  424.             }
  425.         } catch (e) {}

  426.         // 悬浮窗
  427.         this.日志缓冲.push(line);
  428.         if (this.日志缓冲.length > this.最大行数) this.日志缓冲.shift();
  429.         if (this.内容控件) {
  430.             try { this.内容控件.setText(this.日志缓冲.join("\n")); } catch (e) {}
  431.         }
  432.         this._更新状态();

  433.         if (showToast) this._toast(levelTag + " " + msg);
  434.     },

  435.     log: function(msg, toast) { this._output("信息", msg, toast); },
  436.     warn: function(msg, toast) { this._output("警告", msg, toast); },
  437.     err: function(msg, toast) { this._output("错误", msg, toast); },
  438.     success: function(msg, toast) { this._output("成功", msg, toast); },

  439.     addShip: function() { this.统计.发货++; this._更新状态(); },
  440.     addRound: function() { this.统计.轮次++; this._更新状态(); }
  441. };

  442. // =============================================================================
  443. //  第五部分:卡密管理
  444. // =============================================================================

  445. var 卡密管理 = {

  446.     取本地卡密: function(文件路径) {
  447.         try {
  448.             var file = new File(文件路径);
  449.             if (!file.exists()) {
  450.                 logger.err("卡密文件不存在: " + 文件路径);
  451.                 return null;
  452.             }
  453.             var reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF-8"));
  454.             var lines = [], line;
  455.             while ((line = reader.readLine()) !== null) {
  456.                 line = line.trim();
  457.                 if (line !== "" && line.indexOf("#") !== 0) lines.push(line);
  458.             }
  459.             reader.close();

  460.             if (lines.length === 0) {
  461.                 logger.warn("卡密文件已空: " + 文件路径);
  462.                 return null;
  463.             }
  464.             var 卡密 = lines[0];
  465.             logger.log("取出卡密: " + 卡密 + " (剩余 " + (lines.length - 1) + " 个)");

  466.             var remaining = lines.slice(1);
  467.             var writer = new OutputStreamWriter(new FileOutputStream(file), "UTF-8");
  468.             for (var i = 0; i < remaining.length; i++) writer.write(remaining[i] + "\n");
  469.             writer.close();
  470.             return 卡密;
  471.         } catch (e) {
  472.             logger.err("读取卡密失败: " + e);
  473.             return null;
  474.         }
  475.     },

  476.     取服务器卡密: function(商品ID) {
  477.         try {
  478.             var url = CONFIG.服务器地址 + "?action=get&product=" + 商品ID + "&key=" + CONFIG.服务器密钥;
  479.             logger.log("从服务器获取卡密: " + url);
  480.             var response = http.get(url);
  481.             if (!response) { logger.err("服务器无响应"); return null; }

  482.             try {
  483.                 var json = JSON.parse(response);
  484.                 if (json.code === 0 && json.data) {
  485.                     logger.log("服务器返回卡密: " + json.data);
  486.                     return json.data;
  487.                 }
  488.                 logger.err("服务器返回错误: " + (json.msg || json.message || response));
  489.             } catch (e) {
  490.                 if (response && response.length > 0) {
  491.                     logger.log("服务器返回卡密: " + response);
  492.                     return response;
  493.                 }
  494.             }
  495.         } catch (e) {
  496.             logger.err("服务器获取卡密失败: " + e);
  497.         }
  498.         return null;
  499.     },

  500.     上报已使用: function(商品ID, 卡密, 订单号) {
  501.         try {
  502.             var url = CONFIG.服务器地址 + "?action=report&product=" + 商品ID + "&key=" + 卡密 + "&order=" + 订单号;
  503.             var response = http.get(url);
  504.             logger.log("服务器上报结果: " + response);
  505.         } catch (e) {
  506.             logger.err("服务器上报失败: " + e);
  507.         }
  508.     },

  509.     取卡密: function(商品) {
  510.         if (CONFIG.使用服务器) return this.取服务器卡密(商品.名称 || "");
  511.         return this.取本地卡密(商品.卡密文件);
  512.     },

  513.     检查库存: function(商品) {
  514.         if (CONFIG.使用服务器) return -1;
  515.         try {
  516.             var file = new File(商品.卡密文件);
  517.             if (!file.exists()) return 0;
  518.             var reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF-8"));
  519.             var count = 0, line;
  520.             while ((line = reader.readLine()) !== null) {
  521.                 line = line.trim();
  522.                 if (line !== "" && line.indexOf("#") !== 0) count++;
  523.             }
  524.             reader.close();
  525.             return count;
  526.         } catch (e) { return -1; }
  527.     }
  528. };

  529. // =============================================================================
  530. //  第六部分:发货记录
  531. // =============================================================================

  532. var 发货记录 = {

  533.     已发货: function(订单号) {
  534.         try {
  535.             var file = new File(CONFIG.发货记录文件);
  536.             if (!file.exists()) return false;
  537.             var reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF-8"));
  538.             var line;
  539.             while ((line = reader.readLine()) !== null) {
  540.                 if (line.indexOf(订单号) !== -1) { reader.close(); return true; }
  541.             }
  542.             reader.close();
  543.         } catch (e) {}
  544.         return false;
  545.     },

  546.     记录: function(订单号, 商品名, 发货内容) {
  547.         try {
  548.             var writer = new OutputStreamWriter(new FileOutputStream(CONFIG.发货记录文件, true), "UTF-8");
  549.             var now = new Date();
  550.             var dateStr = now.getFullYear() + "-" + (now.getMonth() + 1) + "-" + now.getDate() + " " + logger._time();
  551.             writer.write(dateStr + " | " + 订单号 + " | " + 商品名 + " | " + 发货内容 + "\n");
  552.             writer.close();
  553.             logger.log("已记录发货: " + 订单号 + " -> " + 商品名);
  554.         } catch (e) {
  555.             logger.err("记录发货失败: " + e);
  556.         }
  557.     },

  558.     今日数量: function() {
  559.         try {
  560.             var file = new File(CONFIG.发货记录文件);
  561.             if (!file.exists()) return 0;
  562.             var reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF-8"));
  563.             var now = new Date();
  564.             var today = now.getFullYear() + "-" + (now.getMonth() + 1) + "-" + now.getDate();
  565.             var count = 0, line;
  566.             while ((line = reader.readLine()) !== null) {
  567.                 if (line.indexOf(today) !== -1) count++;
  568.             }
  569.             reader.close();
  570.             return count;
  571.         } catch (e) { return 0; }
  572.     }
  573. };

  574. // =============================================================================
  575. //  第七部分:千牛自动化操作
  576. // =============================================================================

  577. var 千牛操作 = {

  578.     启动千牛: function() {
  579.         logger.log("启动千牛...", true);
  580.         try {
  581.             app.openApp(CONFIG.千牛包名);
  582.             for (var i = 8; i > 0; i--) {
  583.                 sleep.second(秒 = 1);
  584.             }
  585.             logger.success("千牛已启动");
  586.             return true;
  587.         } catch (e) {
  588.             logger.err("启动千牛失败: " + e);
  589.             return false;
  590.         }
  591.     },

  592.     确保运行: function() {
  593.         // 无碍模式没有shell,直接用app.openApp确保运行
  594.         app.openApp(CONFIG.千牛包名);
  595.         sleep.second(秒 = 3);
  596.     },

  597.     /**
  598.      * 检查是否在订单管理页面
  599.      */
  600.     在订单管理页: function() {
  601.         return ActionAdapter.hasText("待发货") || ActionAdapter.hasText("待付款") ||
  602.                ActionAdapter.hasText("已发货") || ActionAdapter.hasText("催发货");
  603.     },

  604.     /**
  605.      * 检查待发货标签是否已选中
  606.      */
  607.     待发货已选中: function() {
  608.         var xml = ActionAdapter.getXml();
  609.         if (!xml) return false;
  610.         var nodes = ActionAdapter.findNodes(xml, "待发货");
  611.         for (var i = 0; i < nodes.length; i++) {
  612.             // selected=true 表示已选中
  613.             var pattern = /<node\s+([^>]+?)(?:\/>|>)/g;
  614.             var match;
  615.             while ((match = pattern.exec(xml)) !== null) {
  616.                 var attrs = match[1];
  617.                 if ((attrs.indexOf("待发货") !== -1) && attrs.indexOf('selected="true"') !== -1) {
  618.                     return true;
  619.                 }
  620.             }
  621.         }
  622.         return false;
  623.     },

  624.     /**
  625.      * 进入待发货订单列表
  626.      * 多条路径:直接找"待发货"标签 → 找"订单管理"入口 → 首页重试
  627.      */
  628.     进入待发货列表: function() {
  629.         logger.log("进入待发货订单列表...", true);

  630.         // 路径1:直接点击"待发货"标签
  631.         if (ActionAdapter.findTextAndClick("待发货", 3000)) {
  632.             sleep.second(秒 = CONFIG.操作延时);
  633.             if (this.在订单管理页()) {
  634.                 logger.log("已在待发货列表页面");
  635.                 return true;
  636.             }
  637.         }

  638.         // 路径2:找"订单管理"入口
  639.         if (ActionAdapter.findTextAndClick("订单管理", 2000)) {
  640.             sleep.second(秒 = CONFIG.操作延时);
  641.             if (ActionAdapter.findTextAndClick("待发货", 3000)) {
  642.                 sleep.second(秒 = CONFIG.操作延时);
  643.                 if (this.在订单管理页()) return true;
  644.             }
  645.         }

  646.         // 路径3:从首页找订单入口
  647.         ActionAdapter.home();
  648.         sleep.second(秒 = 2);
  649.         if (ActionAdapter.findTextAndClick("订单", 3000)) {
  650.             sleep.second(秒 = CONFIG.操作延时);
  651.             ActionAdapter.findTextAndClick("待发货", 3000);
  652.             sleep.second(秒 = CONFIG.操作延时);
  653.             if (this.在订单管理页()) return true;
  654.         }

  655.         logger.warn("未能进入待发货列表");
  656.         return false;
  657.     },

  658.     /**
  659.      * 检查是否有新订单(空状态消失 = 有订单)
  660.      */
  661.     有新订单: function() {
  662.         // 空状态提示
  663.         if (ActionAdapter.hasText("还没有相关订单") || ActionAdapter.hasText("暂无")) {
  664.             return false;
  665.         }

  666.         // 有订单时的特征文字
  667.         var 订单特征 = ["立即发货", "去发货", "买家已付款", "联系买家"];
  668.         for (var i = 0; i < 订单特征.length; i++) {
  669.             if (ActionAdapter.hasText(订单特征[i])) {
  670.                 logger.log("检测到待发货订单: " + 订单特征[i], true);
  671.                 return true;
  672.             }
  673.         }

  674.         // 检查是否有订单号格式的数字
  675.         var xml = ActionAdapter.getXml();
  676.         if (xml) {
  677.             var allNodes = ActionAdapter.findAllNodes(xml);
  678.             for (var j = 0; j < allNodes.length; j++) {
  679.                 if (allNodes[j].text && /^\d{15,20}$/.test(allNodes[j].text)) {
  680.                     logger.log("检测到订单号: " + allNodes[j].text, true);
  681.                     return true;
  682.                 }
  683.             }
  684.         }
  685.         return false;
  686.     },

  687.     /**
  688.      * 匹配商品(用完整商品标题精准匹配)
  689.      * 遍历商品配置,在页面XML中查找哪个商品标题出现了
  690.      * @return {object|null} {名称, 配置, 节点}
  691.      */
  692.     匹配商品: function() {
  693.         var xml = ActionAdapter.getXml();
  694.         if (!xml) xml = "";

  695.         for (var 商品标题 in 商品配置) {
  696.             // 直接用完整标题去XML里找
  697.             var nodes = ActionAdapter.findNodes(xml, 商品标题);
  698.             if (nodes.length > 0) {
  699.                 logger.success("精准匹配到商品: " + 商品标题, true);
  700.                 logger.log("  节点: text=[" + nodes[0].text + "] desc=[" + nodes[0].desc + "] px=" + nodes[0].px + " py=" + nodes[0].py);
  701.                 return {
  702.                     名称: 商品标题,
  703.                     配置: 商品配置[商品标题],
  704.                     节点: nodes[0]
  705.                 };
  706.             }
  707.         }

  708.         // 如果完整标题没匹配到,尝试模糊匹配(标题前6个字)
  709.         for (var 标题 in 商品配置) {
  710.             var 短标题 = 标题.substring(0, 6);
  711.             var nodes2 = ActionAdapter.findNodes(xml, 短标题);
  712.             if (nodes2.length > 0) {
  713.                 logger.log("模糊匹配到商品: " + 标题 + " (关键词: " + 短标题 + ")", true);
  714.                 return {
  715.                     名称: 标题,
  716.                     配置: 商品配置[标题],
  717.                     节点: nodes2[0]
  718.                 };
  719.             }
  720.         }

  721.         logger.warn("未匹配到任何商品");
  722.         return null;
  723.     },

  724.     /**
  725.      * 获取当前订单号
  726.      */
  727.     获取订单号: function() {
  728.         var xml = ActionAdapter.getXml();
  729.         if (xml) {
  730.             var patterns = [
  731.                 /订单号[::]\s*(\d{10,20})/,
  732.                 /订单编号[::]\s*(\d{10,20})/,
  733.                 /"(\d{15,20})"/
  734.             ];
  735.             for (var i = 0; i < patterns.length; i++) {
  736.                 var m = xml.match(patterns[i]);
  737.                 if (m && m[1]) {
  738.                     logger.log("提取到订单号: " + m[1]);
  739.                     return m[1];
  740.                 }
  741.             }
  742.         }
  743.         var 临时号 = "TEMP" + new Date().getTime();
  744.         logger.warn("未能提取订单号,使用临时号: " + 临时号);
  745.         return 临时号;
  746.     },

  747.     /**
  748.      * 点击订单进入详情页
  749.      */
  750.     点击进入订单: function(商品节点) {
  751.         if (!商品节点) {
  752.             ActionAdapter.clickPercent(0.5, 0.4);
  753.             sleep.second(秒 = CONFIG.操作延时);
  754.         } else {
  755.             var node = 商品节点.节点 || 商品节点;
  756.             if (node.px > 0 && node.py > 0) {
  757.                 ActionAdapter.clickPercent(node.px, node.py);
  758.             } else {
  759.                 ActionAdapter.clickPercent(0.5, 0.4);
  760.             }
  761.             sleep.second(秒 = CONFIG.操作延时);
  762.         }

  763.         // 验证是否进入订单详情
  764.         if (ActionAdapter.hasText("订单详情") || ActionAdapter.hasText("订单号") ||
  765.             ActionAdapter.hasText("联系买家") || ActionAdapter.hasText("发货") ||
  766.             ActionAdapter.hasText("阿里旺旺") || ActionAdapter.hasText("旺旺")) {
  767.             logger.success("已进入订单详情");
  768.             return true;
  769.         }
  770.         if (ActionAdapter.hasText("确认发货") || ActionAdapter.hasText("物流")) {
  771.             logger.success("已进入发货页面");
  772.             return true;
  773.         }

  774.         logger.warn("未能进入订单详情");
  775.         return false;
  776.     },

  777.     /**
  778.      * 进入买家聊天界面(点阿里旺旺)
  779.      */
  780.     进入聊天: function() {
  781.         logger.log("进入买家聊天界面...");
  782.         var 按钮文字 = ["阿里旺旺", "旺旺", "联系买家", "发消息", "联系", "留言"];
  783.         for (var i = 0; i < 按钮文字.length; i++) {
  784.             if (ActionAdapter.findTextAndClick(按钮文字[i], 2000)) {
  785.                 sleep.second(秒 = CONFIG.操作延时);
  786.                 logger.success("已进入聊天界面: " + 按钮文字[i]);
  787.                 return true;
  788.             }
  789.         }
  790.         logger.warn("未能进入聊天界面");
  791.         return false;
  792.     },

  793.     /**
  794.      * 在聊天界面发送消息
  795.      */
  796.     发送消息: function(消息内容) {
  797.         logger.log("准备发送消息...", true);

  798.         // 步骤1:点击聊天输入框
  799.         ActionAdapter.clickPercent(0.5, 0.92);
  800.         sleep.second(秒 = 1);

  801.         // 步骤2:通过剪贴板粘贴
  802.         ActionAdapter.inputText(消息内容);
  803.         sleep.second(秒 = 1);

  804.         // 步骤3:点击发送按钮
  805.         var 发送按钮文字 = ["发送", "Send", "发送消息"];
  806.         var 发送成功 = false;
  807.         for (var i = 0; i < 发送按钮文字.length; i++) {
  808.             if (ActionAdapter.findTextAndClick(发送按钮文字[i], 2000)) {
  809.                 sleep.second(秒 = 1);
  810.                 发送成功 = true;
  811.                 break;
  812.             }
  813.         }

  814.         // 百分比点击发送按钮(通常在右下角)
  815.         if (!发送成功) {
  816.             ActionAdapter.clickPercent(0.95, 0.92);
  817.             sleep.second(秒 = 1);
  818.             发送成功 = true;
  819.         }

  820.         if (发送成功) logger.success("消息已发送", true);
  821.         return 发送成功;
  822.     },

  823.     /**
  824.      * 返回待发货订单列表
  825.      */
  826.     返回列表: function() {
  827.         logger.log("返回订单列表...");
  828.         ActionAdapter.back();
  829.         sleep.second(秒 = CONFIG.操作延时);
  830.         ActionAdapter.back();
  831.         sleep.second(秒 = CONFIG.操作延时);

  832.         if (!this.在订单管理页()) {
  833.             sleep.second(秒 = 1);
  834.             if (!this.在订单管理页()) {
  835.                 logger.warn("返回后不在订单管理页,重新进入");
  836.                 this.进入待发货列表();
  837.             }
  838.         }
  839.     },

  840.     /**
  841.      * 执行发货(完整流程)
  842.      * 在订单详情页点击"发货" → 进入发货页面 → 选"无需物流" → 填写发货留言 → 确认发货
  843.      * @param {object} 发货内容 - {消息, 摘要, 卡密}
  844.      * @return {boolean} 是否发货成功
  845.      */
  846.     执行发货: function(发货内容) {
  847.         logger.log("===== 开始执行发货操作 =====", true);

  848.         // 步骤1:在订单详情页点击"发货"按钮
  849.         var 发货按钮 = ["立即发货", "去发货", "发货"];
  850.         var 点击成功 = false;
  851.         for (var i = 0; i < 发货按钮.length; i++) {
  852.             if (ActionAdapter.findTextAndClick(发货按钮[i], 3000)) {
  853.                 sleep.second(秒 = CONFIG.操作延时);
  854.                 logger.log("已点击发货按钮: " + 发货按钮[i]);
  855.                 点击成功 = true;
  856.                 break;
  857.             }
  858.         }

  859.         if (!点击成功) {
  860.             // 可能已经在发货页面了
  861.             if (ActionAdapter.hasText("确认发货") || ActionAdapter.hasText("无需物流")) {
  862.                 logger.log("已在发货页面");
  863.                 点击成功 = true;
  864.             } else {
  865.                 logger.err("未找到发货按钮");
  866.                 return false;
  867.             }
  868.         }

  869.         // 步骤2:等待发货页面加载,检查页面特征
  870.         sleep.second(秒 = 2);
  871.         var 在发货页 = ActionAdapter.hasText("确认发货") ||
  872.                        ActionAdapter.hasText("无需物流") ||
  873.                        ActionAdapter.hasText("物流公司") ||
  874.                        ActionAdapter.hasText("发货方式");
  875.         if (!在发货页) {
  876.             logger.log("发货页面未加载,再等一下...");
  877.             sleep.second(秒 = 2);
  878.             在发货页 = ActionAdapter.hasText("确认发货") ||
  879.                        ActionAdapter.hasText("无需物流") ||
  880.                        ActionAdapter.hasText("物流公司");
  881.         }

  882.         if (在发货页) {
  883.             logger.log("已进入发货页面");
  884.             ActionAdapter.dumpPageSummary();
  885.         }

  886.         // 步骤3:选择"无需物流"(虚拟商品不需要快递)
  887.         var 物流选项 = ["无需物流", "自己联系物流", "虚拟商品", "电子凭证", "不需要物流"];
  888.         var 选了物流 = false;
  889.         for (var j = 0; j < 物流选项.length; j++) {
  890.             if (ActionAdapter.findTextAndClick(物流选项[j], 2000)) {
  891.                 sleep.second(秒 = 1);
  892.                 logger.log("已选择: " + 物流选项[j]);
  893.                 选了物流 = true;
  894.                 break;
  895.             }
  896.         }

  897.         if (!选了物流) {
  898.             logger.log("未找到'无需物流'选项,可能默认已选或页面不同");
  899.             // 输出页面节点帮助排查
  900.             ActionAdapter.dumpPageSummary();
  901.         }

  902.         // 步骤4:填写发货留言/备注(把卡密/网盘信息填进去)
  903.         var 留言字段 = ["发货留言", "卖家发货备注", "发货备注", "留言", "备注", "给买家留言"];
  904.         var 填了留言 = false;
  905.         for (var k = 0; k < 留言字段.length; k++) {
  906.             if (ActionAdapter.findTextAndClick(留言字段[k], 1500)) {
  907.                 sleep.second(秒 = 1);
  908.                 // 点击后可能出现输入框,填入发货内容
  909.                 ActionAdapter.inputText(发货内容.消息);
  910.                 sleep.second(秒 = 1);
  911.                 logger.log("已填写发货留言: " + 留言字段[k]);
  912.                 填了留言 = true;
  913.                 break;
  914.             }
  915.         }

  916.         if (!填了留言) {
  917.             // 尝试找输入框直接填写
  918.             logger.log("未找到留言字段,尝试直接找输入框...");
  919.             // 发货页面的输入框通常在中部
  920.             ActionAdapter.clickPercent(0.5, 0.5);
  921.             sleep.second(秒 = 1);
  922.             ActionAdapter.inputText(发货内容.消息);
  923.             sleep.second(秒 = 1);
  924.         }

  925.         // 步骤5:点击"确认发货"按钮
  926.         logger.log("寻找确认发货按钮...");
  927.         var 确认按钮 = ["确认发货", "确定发货", "提交", "确定"];
  928.         var 确认成功 = false;
  929.         for (var m = 0; m < 确认按钮.length; m++) {
  930.             if (ActionAdapter.findTextAndClick(确认按钮[m], 3000)) {
  931.                 sleep.second(秒 = CONFIG.操作延时);
  932.                 logger.log("已点击: " + 确认按钮[m]);
  933.                 确认成功 = true;
  934.                 break;
  935.             }
  936.         }

  937.         if (!确认成功) {
  938.             // 尝试点击右下角的确认按钮(常见位置)
  939.             logger.log("未找到确认按钮文字,尝试点击右下角...");
  940.             ActionAdapter.clickPercent(0.85, 0.9);
  941.             sleep.second(秒 = CONFIG.操作延时);
  942.         }

  943.         // 步骤6:验证发货是否成功
  944.         sleep.second(秒 = 2);
  945.         var 发货成功 = ActionAdapter.hasText("发货成功") ||
  946.                        ActionAdapter.hasText("已发货") ||
  947.                        ActionAdapter.hasText("发货完成") ||
  948.                        ActionAdapter.hasText("提交成功");

  949.         // 如果没有明确的成功提示,检查是否回到了订单详情或列表
  950.         if (!发货成功) {
  951.             if (ActionAdapter.hasText("订单详情") || ActionAdapter.hasText("待发货") ||
  952.                 ActionAdapter.hasText("已发货") || ActionAdapter.hasText("催发货")) {
  953.                 logger.log("已返回订单页面,推断发货成功");
  954.                 发货成功 = true;
  955.             }
  956.         }

  957.         // 处理可能的二次确认弹窗
  958.         if (ActionAdapter.hasText("确定") || ActionAdapter.hasText("确认")) {
  959.             var 弹窗按钮 = ["确定", "确认", "是"];
  960.             for (var n = 0; n < 弹窗按钮.length; n++) {
  961.                 if (ActionAdapter.findTextAndClick(弹窗按钮[n], 1500)) {
  962.                     sleep.second(秒 = 1);
  963.                     logger.log("已确认弹窗: " + 弹窗按钮[n]);
  964.                     break;
  965.                 }
  966.             }
  967.             sleep.second(秒 = 2);
  968.             // 再次检查
  969.             if (!发货成功) {
  970.                 发货成功 = ActionAdapter.hasText("发货成功") ||
  971.                            ActionAdapter.hasText("已发货") ||
  972.                            ActionAdapter.hasText("发货完成");
  973.             }
  974.         }

  975.         if (发货成功) {
  976.             logger.success("发货操作完成!", true);
  977.         } else {
  978.             logger.warn("未能确认发货是否成功,请手动检查");
  979.             ActionAdapter.dumpPageSummary();
  980.         }

  981.         return 发货成功;
  982.     },

  983.     /**
  984.      * 标记发货(旧方法保留,仅作为后备)
  985.      */
  986.     标记发货: function() {
  987.         logger.log("尝试标记订单发货...");
  988.         var 发货按钮 = ["发货", "确认发货", "一键发货"];
  989.         for (var i = 0; i < 发货按钮.length; i++) {
  990.             if (ActionAdapter.findTextAndClick(发货按钮[i], 2000)) {
  991.                 sleep.second(秒 = CONFIG.操作延时);
  992.                 logger.success("已标记发货");
  993.                 return true;
  994.             }
  995.         }
  996.         logger.warn("未找到发货按钮");
  997.         return false;
  998.     }
  999. };

  1000. // =============================================================================
  1001. //  第八部分:发货核心流程
  1002. // =============================================================================

  1003. var 发货流程 = {

  1004.     /**
  1005.      * 处理一个订单的完整流程
  1006.      * 匹配商品 → 进详情 → 执行发货(选无需物流+确认) → 点旺旺 → 发消息 → 返回列表
  1007.      */
  1008.     处理订单: function() {
  1009.         logger.log("========== 开始处理订单 ==========");

  1010.         // 步骤1:匹配商品
  1011.         var 商品 = 千牛操作.匹配商品();
  1012.         if (!商品) {
  1013.             logger.warn("未匹配到商品,跳过");
  1014.             return false;
  1015.         }

  1016.         // 步骤2:获取订单号
  1017.         var 订单号 = 千牛操作.获取订单号();

  1018.         // 步骤3:检查是否已发货
  1019.         if (发货记录.已发货(订单号)) {
  1020.             logger.log("订单 " + 订单号 + " 已发过货,跳过");
  1021.             ActionAdapter.swipePercent(0.5, 0.6, 0.5, 0.3, 500);
  1022.             sleep.second(秒 = 1);
  1023.             return false;
  1024.         }

  1025.         // 步骤4:准备发货内容
  1026.         var 发货内容 = this.准备发货内容(商品);
  1027.         if (!发货内容) {
  1028.             logger.err("准备发货内容失败");
  1029.             return false;
  1030.         }

  1031.         // 步骤5:点击订单进入详情
  1032.         if (!千牛操作.点击进入订单(商品.节点)) {
  1033.             logger.err("进入订单详情失败");
  1034.             return false;
  1035.         }

  1036.         // 步骤6:执行发货(点发货 → 选无需物流 → 填留言 → 确认发货)
  1037.         var 发货结果 = 千牛操作.执行发货(发货内容);
  1038.         if (发货结果) {
  1039.             logger.success("订单已发货: " + 订单号, true);
  1040.         } else {
  1041.             logger.warn("发货操作未能确认成功,继续发旺旺消息");
  1042.         }

  1043.         // 步骤7:进入聊天界面(点阿里旺旺,给买家发卡密/网盘信息)
  1044.         // 此时可能需要返回到订单详情页
  1045.         if (!ActionAdapter.hasText("订单详情") && !ActionAdapter.hasText("阿里旺旺")) {
  1046.             // 如果发货后不在订单详情,尝试返回
  1047.             ActionAdapter.back();
  1048.             sleep.second(秒 = CONFIG.操作延时);
  1049.         }

  1050.         if (千牛操作.进入聊天()) {
  1051.             // 步骤8:发送消息
  1052.             if (千牛操作.发送消息(发货内容.消息)) {
  1053.                 logger.success("旺旺消息已发送", true);
  1054.                 // 步骤9:返回订单详情
  1055.                 ActionAdapter.back();
  1056.                 sleep.second(秒 = CONFIG.操作延时);
  1057.             } else {
  1058.                 logger.err("发送旺旺消息失败");
  1059.             }
  1060.         } else {
  1061.             logger.warn("未能进入旺旺聊天(发货已完成,消息发送失败)");
  1062.         }

  1063.         // 步骤10:记录发货
  1064.         发货记录.记录(订单号, 商品.名称, 发货内容.摘要);

  1065.         // 步骤11:上报服务器
  1066.         if (CONFIG.使用服务器 && 发货内容.卡密) {
  1067.             卡密管理.上报已使用(商品.名称, 发货内容.卡密, 订单号);
  1068.         }

  1069.         logger.success("订单 " + 订单号 + " 处理完成: " + 商品.名称, true);
  1070.         logger.success("今日已发货: " + 发货记录.今日数量() + " 单");

  1071.         // 步骤12:返回列表
  1072.         千牛操作.返回列表();
  1073.         return true;
  1074.     },

  1075.     准备发货内容: function(商品) {
  1076.         var 配置 = 商品.配置;
  1077.         var 话术 = 配置.发货话术;
  1078.         var 卡密 = "";
  1079.         var 网盘 = 配置.网盘链接 || "";
  1080.         var 摘要 = "";

  1081.         if (配置.发货类型 === "卡密") {
  1082.             卡密 = 卡密管理.取卡密(配置);
  1083.             if (!卡密) { logger.err("无可用卡密!商品: " + 商品.名称); return null; }
  1084.             话术 = 话术.replace("{卡密}", 卡密).replace("{网盘}", 网盘);
  1085.             摘要 = "卡密:" + 卡密;
  1086.         } else if (配置.发货类型 === "网盘") {
  1087.             话术 = 话术.replace("{网盘}", 网盘).replace("{卡密}", "");
  1088.             摘要 = "网盘:" + 网盘.substring(0, 20);
  1089.         } else if (配置.发货类型 === "卡密+网盘") {
  1090.             卡密 = 卡密管理.取卡密(配置);
  1091.             if (!卡密) { logger.err("无可用卡密!商品: " + 商品.名称); return null; }
  1092.             话术 = 话术.replace("{卡密}", 卡密).replace("{网盘}", 网盘);
  1093.             摘要 = "卡密:" + 卡密 + " 网盘:" + 网盘.substring(0, 15);
  1094.         }
  1095.         return { 消息: 话术, 摘要: 摘要, 卡密: 卡密 };
  1096.     },

  1097.     /**
  1098.      * 处理当前所有可见的待发货订单
  1099.      */
  1100.     处理当前订单: function() {
  1101.         var 处理数量 = 0;
  1102.         var 连续无订单 = 0;

  1103.         for (var i = 0; i < 50; i++) {
  1104.             if (!千牛操作.有新订单()) {
  1105.                 连续无订单++;
  1106.                 if (连续无订单 >= 2) {
  1107.                     logger.log("没有更多待发货订单");
  1108.                     break;
  1109.                 }
  1110.                 ActionAdapter.swipePercent(0.5, 0.6, 0.5, 0.3, 500);
  1111.                 sleep.second(秒 = 1);
  1112.                 continue;
  1113.             }
  1114.             连续无订单 = 0;
  1115.             logger.log("--- 处理第 " + (i + 1) + " 个订单 ---");

  1116.             if (this.处理订单()) 处理数量++;
  1117.             sleep.second(秒 = 1);
  1118.         }

  1119.         logger.success("本轮处理完成,共发货 " + 处理数量 + " 单", true);
  1120.         return 处理数量;
  1121.     }
  1122. };

  1123. // =============================================================================
  1124. //  第九部分:保活心跳(无碍版,不用shell)
  1125. // =============================================================================

  1126. var 保活系统 = {
  1127.     线程: null,
  1128.     运行中: false,

  1129.     start: function() {
  1130.         if (!CONFIG.启用保活) return;
  1131.         this.运行中 = true;
  1132.         this.线程 = new thread().runJsCode(function() {
  1133.             while (保活系统.运行中) {
  1134.                 try {
  1135.                     // 无碍模式没有shell,用getXml检查千牛是否在前台
  1136.                     var xml = ActionAdapter.getXml();
  1137.                     if (!xml || xml === "") {
  1138.                         logger.warn("千牛可能未运行,尝试重启...");
  1139.                         app.openApp(CONFIG.千牛包名);
  1140.                         sleep.second(秒 = 8);
  1141.                     }
  1142.                     logger.log("保活心跳: 今日发货 " + 发货记录.今日数量() + " 单");
  1143.                 } catch (e) {}
  1144.                 sleep.second(秒 = CONFIG.保活间隔);
  1145.             }
  1146.         });
  1147.         logger.log("保活线程已启动");
  1148.     },

  1149.     stop: function() {
  1150.         this.运行中 = false;
  1151.         logger.log("保活线程已停止");
  1152.     }
  1153. };

  1154. // =============================================================================
  1155. //  第十部分:库存预警
  1156. // =============================================================================

  1157. var 库存预警 = {
  1158.     检查全部: function() {
  1159.         var 预警商品 = [];
  1160.         for (var 商品名 in 商品配置) {
  1161.             var 配置 = 商品配置[商品名];
  1162.             if (配置.发货类型 === "卡密" || 配置.发货类型 === "卡密+网盘") {
  1163.                 var 库存 = 卡密管理.检查库存(配置);
  1164.                 if (库存 >= 0 && 库存 < 5) {
  1165.                     预警商品.push(商品名 + " (剩余" + 库存 + "个)");
  1166.                     logger.warn("库存预警: " + 商品名 + " 仅剩 " + 库存 + " 个卡密!");
  1167.                 }
  1168.             }
  1169.         }
  1170.         return 预警商品;
  1171.     }
  1172. };

  1173. // =============================================================================
  1174. //  第十一部分:主控制流程(持续监控模式)
  1175. // =============================================================================

  1176. var 主控 = {
  1177.     运行中: false,
  1178.     统计: { 总轮次: 0, 总发货: 0, 开始时间: null, 最后检查: null },

  1179.     初始化: function() {
  1180.         logger.log("========================================");
  1181.         logger.log("  淘宝千牛自动发货系统 v3.0");
  1182.         logger.log("  精简版 · 仅无碍模式 · 完整标题匹配");
  1183.         logger.log("========================================");

  1184.         logger.init();

  1185.         if (!ActionAdapter.init()) {
  1186.             logger.err("无碍模式初始化失败,系统退出");
  1187.             return false;
  1188.         }

  1189.         try {
  1190.             var dir = new File(CONFIG.卡密目录);
  1191.             if (!dir.exists()) dir.mkdirs();
  1192.         } catch (e) {}

  1193.         var 预警 = 库存预警.检查全部();
  1194.         if (预警.length > 0) logger.warn("库存预警: " + 预警.join(", "));

  1195.         logger.log("商品数量: " + Object.keys(商品配置).length);
  1196.         logger.log("监控间隔: " + CONFIG.轮询间隔 + "秒");

  1197.         this.统计.开始时间 = new Date();
  1198.         logger.success("系统初始化完成", true);
  1199.         return true;
  1200.     },

  1201.     进入监控状态: function() {
  1202.         千牛操作.确保运行();
  1203.         sleep.second(秒 = 2);

  1204.         if (!千牛操作.进入待发货列表()) {
  1205.             logger.err("进入待发货列表失败!", true);
  1206.             return false;
  1207.         }

  1208.         if (!千牛操作.待发货已选中()) {
  1209.             ActionAdapter.findTextAndClick("待发货", 3000);
  1210.             sleep.second(秒 = CONFIG.操作延时);
  1211.         }

  1212.         logger.success("已进入待发货列表,开始持续监控", true);
  1213.         return true;
  1214.     },

  1215.     监控一轮: function() {
  1216.         this.统计.总轮次++;
  1217.         logger.addRound();

  1218.         try {
  1219.             if (!千牛操作.在订单管理页()) {
  1220.                 logger.warn("不在订单管理页面,重新进入");
  1221.                 this.进入监控状态();
  1222.                 return;
  1223.             }

  1224.             if (千牛操作.有新订单()) {
  1225.                 logger.success("检测到新订单!开始处理...", true);
  1226.                 var 发货数 = 发货流程.处理当前订单();
  1227.                 this.统计.总发货 += 发货数;
  1228.                 for (var i = 0; i < 发货数; i++) logger.addShip();

  1229.                 if (发货数 > 0) {
  1230.                     logger.success("本轮发货 " + 发货数 + " 单,累计 " + this.统计.总发货 + " 单", true);
  1231.                 }

  1232.                 if (!千牛操作.在订单管理页()) this.进入监控状态();
  1233.             } else {
  1234.                 if (this.统计.总轮次 % 10 === 0) {
  1235.                     logger.log("监控中... 暂无新订单 (轮次:" + this.统计.总轮次 + ")");
  1236.                 }
  1237.                 if (this.统计.总轮次 % 20 === 0) 库存预警.检查全部();
  1238.             }
  1239.         } catch (e) {
  1240.             logger.err("监控异常: " + e, true);
  1241.             logger.log("异常恢复:重新进入待发货列表...", true);
  1242.             this.进入监控状态();
  1243.         }
  1244.         this.统计.最后检查 = new Date();
  1245.     },

  1246.     主循环: function() {
  1247.         this.运行中 = true;
  1248.         logger.success("系统进入持续监控模式", true);
  1249.         logger.log("停在待发货列表页面,有人下单自动发货", true);

  1250.         while (this.运行中) {
  1251.             try { this.监控一轮(); } catch (e) { logger.err("主循环异常: " + e, true); }

  1252.             var 等待秒数 = CONFIG.轮询间隔;
  1253.             for (var i = 等待秒数; i > 0; i--) {
  1254.                 if (!this.运行中) break;
  1255.                 if (i % 10 === 0 || i <= 3) { try { logger._更新状态(); } catch (e2) {} }
  1256.                 sleep.second(秒 = 1);
  1257.             }
  1258.         }
  1259.     },

  1260.     start: function() {
  1261.         if (!this.初始化()) return;
  1262.         保活系统.start();

  1263.         if (!this.进入监控状态()) {
  1264.             logger.err("无法进入监控状态,5秒后重试...", true);
  1265.             sleep.second(秒 = 5);
  1266.             if (!this.进入监控状态()) {
  1267.                 logger.err("重试失败,系统退出", true);
  1268.                 return;
  1269.             }
  1270.         }
  1271.         this.主循环();
  1272.     },

  1273.     stop: function() {
  1274.         this.运行中 = false;
  1275.         保活系统.stop();
  1276.         logger.log("系统已停止");
  1277.     }
  1278. };

  1279. // =============================================================================
  1280. //  第十二部分:启动入口
  1281. // =============================================================================

  1282. function 确保目录存在(路径) {
  1283.     try {
  1284.         var dir = new File(路径);
  1285.         if (!dir.exists()) dir.mkdirs();
  1286.     } catch (e) {}
  1287. }

  1288. function 创建卡密示例() {
  1289.     try {
  1290.         for (var 商品名 in 商品配置) {
  1291.             var 配置 = 商品配置[商品名];
  1292.             if (配置.卡密文件 && 配置.卡密文件 !== "") {
  1293.                 var file = new File(配置.卡密文件);
  1294.                 if (!file.exists()) {
  1295.                     var writer = new OutputStreamWriter(new FileOutputStream(file), "UTF-8");
  1296.                     writer.write("# " + 商品名 + " 卡密文件\n");
  1297.                     writer.write("# 每行一个卡密,用过的自动删除\n\n");
  1298.                     writer.write("Q5J3HHT8-NJMF\n");
  1299.                     writer.write("KA8CPLJP-DNCM\n");
  1300.                     writer.write("FYYEGC6D-KPCA\n");
  1301.                     writer.close();
  1302.                     logger.log("已创建卡密示例文件: " + 配置.卡密文件);
  1303.                 }
  1304.             }
  1305.         }
  1306.     } catch (e) {
  1307.         logger.err("创建卡密示例失败: " + e);
  1308.     }
  1309. }

  1310. // =============================================================================
  1311. //  启动
  1312. // =============================================================================

  1313. 确保目录存在(CONFIG.卡密目录);
  1314. 确保目录存在("/sdcard/");
  1315. 创建卡密示例();

  1316. if (CONFIG.调试模式 === "debug") {
  1317.     // ===== 调试模式:输出页面节点,验证XML解析 =====
  1318.     logger.init();
  1319.     logger.log("========================================");
  1320.     logger.log("  调试模式:验证XML解析");
  1321.     logger.log("========================================");

  1322.     if (!ActionAdapter.init()) {
  1323.         logger.err("无碍模式初始化失败");
  1324.     } else {
  1325.         千牛操作.启动千牛();
  1326.         sleep.second(秒 = 3);

  1327.         logger.log("===== 当前页面节点摘要 =====");
  1328.         ActionAdapter.dumpPageSummary();

  1329.         logger.log("===== 测试查找'待发货'节点 =====");
  1330.         var xml = ActionAdapter.getXml();
  1331.         var nodes = ActionAdapter.findNodes(xml, "待发货");
  1332.         logger.log("查找'待发货'结果: " + nodes.length + " 个节点");
  1333.         for (var i = 0; i < nodes.length; i++) {
  1334.             logger.log("  节点" + i + ": text=[" + nodes[i].text + "] desc=[" + nodes[i].desc + "] px=" + nodes[i].px + " py=" + nodes[i].py + " clickable=" + nodes[i].clickable);
  1335.             if (nodes[i].clickable === "false") {
  1336.                 var parent = ActionAdapter.findClickableParent(nodes[i], xml);
  1337.                 if (parent) {
  1338.                     logger.log("    -> 父节点: px=" + parent.px + " py=" + parent.py + " clickable=" + parent.clickable);
  1339.                 }
  1340.             }
  1341.         }

  1342.         logger.log("===== 测试商品标题匹配 =====");
  1343.         var 商品结果 = 千牛操作.匹配商品();
  1344.         if (商品结果) {
  1345.             logger.success("匹配到商品: " + 商品结果.名称);
  1346.         } else {
  1347.             logger.log("当前页面未匹配到商品(正常,列表为空时无商品)");
  1348.         }

  1349.         logger.success("调试完成!", true);
  1350.         logger.log("如需测试真实发货,请将 CONFIG.调试模式 设为 'test'");
  1351.         logger.log("如需正常监控,请将 CONFIG.调试模式 设为 false");
  1352.     }

  1353. } else if (CONFIG.调试模式 === "test") {
  1354.     // ===== 测试模式:处理一个真实订单,验证完整发货流程 =====
  1355.     logger.init();
  1356.     logger.log("========================================");
  1357.     logger.log("  测试模式:处理一个真实订单");
  1358.     logger.log("========================================");

  1359.     if (!ActionAdapter.init()) {
  1360.         logger.err("无碍模式初始化失败");
  1361.     } else {
  1362.         // 启动千牛
  1363.         千牛操作.启动千牛();
  1364.         sleep.second(秒 = 3);

  1365.         // 进入待发货列表
  1366.         if (!千牛操作.进入待发货列表()) {
  1367.             logger.err("进入待发货列表失败!", true);
  1368.         } else {
  1369.             logger.log("===== 检查是否有待发货订单 =====");
  1370.             if (千牛操作.有新订单()) {
  1371.                 logger.success("检测到订单!开始处理...", true);

  1372.                 // 输出页面节点,看看订单列表长什么样
  1373.                 logger.log("===== 订单列表节点摘要 =====");
  1374.                 ActionAdapter.dumpPageSummary();

  1375.                 // 尝试匹配商品
  1376.                 logger.log("===== 尝试匹配商品 =====");
  1377.                 var 商品 = 千牛操作.匹配商品();
  1378.                 if (商品) {
  1379.                     logger.success("匹配到商品: " + 商品.名称, true);

  1380.                     // 处理这个订单
  1381.                     logger.log("===== 开始处理订单 =====");
  1382.                     var 结果 = 发货流程.处理订单();
  1383.                     if (结果) {
  1384.                         logger.success("测试发货成功!", true);
  1385.                     } else {
  1386.                         logger.err("测试发货失败,请检查日志", true);
  1387.                     }
  1388.                 } else {
  1389.                     logger.warn("未匹配到商品");
  1390.                     logger.log("请检查商品配置中的标题是否与淘宝订单中的商品标题一致");
  1391.                     logger.log("===== 当前页面所有节点 =====");
  1392.                     ActionAdapter.dumpPageSummary();
  1393.                 }
  1394.             } else {
  1395.                 logger.log("当前没有待发货订单,请先在淘宝下一单测试");
  1396.                 logger.log("===== 当前页面节点 =====");
  1397.                 ActionAdapter.dumpPageSummary();
  1398.             }
  1399.         }

  1400.         logger.log("========================================");
  1401.         logger.log("测试完成!");
  1402.         logger.log("如需正常监控,请将 CONFIG.调试模式 设为 false");
  1403.         logger.log("========================================");
  1404.     }

  1405. } else {
  1406.     // ===== 正常模式:24小时持续监控 =====
  1407.     主控.start();
  1408. }
复制代码






回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

相关导读
群发软件无碍模式auto淘宝千牛自动发货系统
无碍模式auto淘宝千牛自动发货系统
群发软件ghfd
safsdafasfd
群发软件安卓AIWROK获取包名所有方法汇总
安卓AIWROK获取包名所有方法汇总
群发软件AIWROK软件番茄小说自动阅读刷金币示例
AIWROK软件番茄小说自动阅读刷金币示例
信息发布软件AIWROK软件无碍模式一键删除所有APP版示例
AIWROK软件无碍模式一键删除所有APP版示例
信息发布软件AIWROK软件无碍模式auto模块抖音极速版滑动刷视频
AIWROK软件无碍模式auto模块抖音极速版滑动刷视频
信息发布软件快手极速版自动刷视频无障碍模式
快手极速版自动刷视频无障碍模式
信息发布软件AIWROK软件代理模式一键删除桌面所有APP版示例
AIWROK软件代理模式一键删除桌面所有APP版示例
信息发布软件AIWROK软件自动找图利用agent代理模式删除应用版示例
AIWROK软件自动找图利用agent代理模式删除应用版示例
信息发布软件解析Kimi返回的拖动距离示例
解析Kimi返回的拖动距离示例
信息发布软件自动截图并发送给DEEPSEEK识别示例过程
自动截图并发送给DEEPSEEK识别示例过程
信息发布软件AIWROK软件测试文心识别识别验证码示例
AIWROK软件测试文心识别识别验证码示例
信息发布软件元宝截图B站验证码示例测试
元宝截图B站验证码示例测试
信息发布软件示例千问自动采集文章保存记事本
示例千问自动采集文章保存记事本
信息发布软件豆包识别验证码加拖动验证码示例
豆包识别验证码加拖动验证码示例
信息发布软件豆包对话采集示例
豆包对话采集示例
信息发布软件启动自动百度贴吧发图片
启动自动百度贴吧发图片
信息发布软件自动百度贴吧示例
自动百度贴吧示例
信息发布软件代理模式简书自动流程
代理模式简书自动流程
信息发布软件启动极速版任务页看小说赚钱自动阅读
启动极速版任务页看小说赚钱自动阅读
信息发布软件agent代理模式功能小结
agent代理模式功能小结
信息发布软件完整苹果IOS小脚本文本输入与粘贴流程
完整苹果IOS小脚本文本输入与粘贴流程
信息发布软件AIWROK使用activity列表开关与进度条的协同交互
AIWROK使用activity列表开关与进度条的协同交互
群发软件苹果脚本找图后Detect对象各种方法演示
苹果脚本找图后Detect对象各种方法演示
群发软件苹果脚本网络API监控与数据管理工具
苹果脚本网络API监控与数据管理工具
群发软件IOS苹果脚本TXT文件读写H5日志打印完整示例
IOS苹果脚本TXT文件读写H5日志打印完整示例
群发软件AIWROK软件演示多线程创建暂停恢复
AIWROK软件演示多线程创建暂停恢复
群发软件AIWROK软件定时检查页面状态防止卡死技巧
AIWROK软件定时检查页面状态防止卡死技巧
群发软件安卓脚本runTime相关功能示例
安卓脚本runTime相关功能示例
群发软件floatUI实例秒显示秒关闭吐司功能
floatUI实例秒显示秒关闭吐司功能
群发软件苹果脚本HID多行实例粘帖板示例
苹果脚本HID多行实例粘帖板示例
群发软件安卓脚本HID节点查找完整方法示例
HID节点查找完整方法示例
群发软件AIWROK软件框架的抖音自动化营销系统
AIWROK软件框架的抖音自动化营销系统
群发软件赛博朋克任务控制中心H5示例
赛博朋克任务控制中心H5示例
群发软件苹果脚本现代深色UI设计风格
现代深色UI设计风格
群发软件AIWROK软件智能任务监控悬浮窗
AIWROK软件智能任务监控悬浮窗
群发软件AIWROK软件事件监听示例
AIWROK软件事件监听示例
群发软件QZ掘金原生UI界面
QZ掘金原生UI界面
群发软件安卓脚本auto方法完整示例
安卓脚本auto方法完整示例
群发软件安卓脚本暂停弹出确认对话框
安卓脚本暂停弹出确认对话框
群发软件苹果iOS脚本天气信息查询工具
苹果iOS脚本天气信息查询工具
群发软件安卓脚本简化版H5日志输出窗口
安卓脚本简化版H5日志输出窗口
群发软件安卓脚本简化版H5日志输出窗口
安卓脚本简化版H5日志输出窗口
群发软件安卓脚本应用重置、代理模式监控及补丁升级
安卓脚本应用重置、代理模式监控及补丁升级
群发软件安卓脚本里的打开关闭安装以及卡密授权信息
安卓脚本里的打开关闭安装以及卡密授权信息
群发软件AIWROK软件脚本GET下载文件并检测0KB工具
AIWROK软件脚本GET下载文件并检测0KB工具
群发软件安卓脚本HID按键方法完整示例
安卓脚本HID按键方法完整示例
群发软件AIWROK软件键鼠HID滑动所有方法功能演示
AIWROK软件键鼠HID滑动所有方法功能演示
群发软件苹果脚本里HID的5种滑动方法的使用
苹果脚本里HID的5种滑动方法的使用
群发软件苹果脚本示例7种不同的OCR识别和点击方法
苹果脚本示例7种不同的OCR识别和点击方法
群发软件AIWROK软件安卓脚本FTP上传下载例子
AIWROK软件安卓脚本FTP上传下载例子
群发软件AIWROK软件脚本JSON转换示例
AIWROK软件脚本JSON转换示例
群发软件AIWROK软件运算符高级应用实例
AIWROK软件运算符高级应用实例
群发软件JavaScript语法小示例
JavaScript语法小示例
群发软件展示JavaScript各种语句标识符的实际应用
展示JavaScript各种语句标识符的实际应用
群发软件AIWROK条件语句实用示例智能活动推荐系统
AIWROK条件语句实用示例智能活动推荐系统
群发软件AIWROK软件示例数组方法实战应用
AIWROK软件示例数组方法实战应用
群发软件示例JavaScript的 try-catch-finally-throw用法
示例JavaScript的 try-catch-finally-throw用法
群发软件JavaScript 高级错误处理实战示例
JavaScript 高级错误处理实战示例
群发软件安卓脚本AIWROK软件示例JS函数高级用法
安卓脚本AIWROK软件示例JS函数高级用法
群发软件AIWROK环境JavaScript编码规范完整示例
AIWROK环境JavaScript编码规范完整示例
群发软件安卓脚本示例实时截图判断页面变化
安卓脚本示例实时截图判断页面变化
群发软件苹果脚本7种不同的OCR识别
苹果脚本7种不同的OCR识别
群发软件安卓脚本多变的function用法
安卓脚本多变的function用法
群发软件苹果脚本示例Config配置H5集成
苹果脚本示例Config配置H5集成

QQ|( 京ICP备09078825号 )

本网站信息发布软件,是可以发布论坛,发送信息到各大博客,各大b2b软件自动发布,好不夸张的说:只要手工能发在电脑打开IE能发的网站,用这个宣传软件就可以仿制动作,进行推送发到您想发送的B2B网站或是信息发布平台上,不管是后台,还是前台,都可以进行最方便的广告发布,这个广告发布软件,可以按月购买,还可以试用软件,对网站的验证码也可以完全自动对信息发布,让客户自动找上门,使企业轻松实现b2b发布,这个信息发布软件,均是本站原创正版开发,拥有正版的血统,想要新功能,欢迎提意见给我,一好的分类信息群发软件在手,舍我其谁。QQ896757558

GMT+8, 2026-8-3 08:01 , Processed in 15.206274 second(s), 51 queries .

宣传软件--信息发布软件--b2b软件广告发布软件

快速回复 返回顶部 返回列表