YYPOST群发软件 发表于 2026-8-3 06:53:36

无碍模式auto淘宝千牛自动发货系统


无碍模式auto淘宝千牛自动发货系统


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

importClass(java.io.File);
importClass(java.io.FileWriter);
importClass(java.io.BufferedReader);
importClass(java.io.InputStreamReader);
importClass(java.io.FileInputStream);
importClass(java.io.FileOutputStream);
importClass(java.io.OutputStreamWriter);

// =============================================================================
//第一部分:全局配置
// =============================================================================

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

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

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

    启用保活: true,
    保活间隔: 300,

    启用悬浮窗: true,

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

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

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

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

var ActionAdapter = {

    // ================================================================
    //基础操作(直接调用 auto 对象)
    // ================================================================

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

    click: function(x, y) {
      try {
            auto.clickPoint(x, y);
            return true;
      } catch (e) {
            logger.err("点击失败: " + e);
            return false;
      }
    },

    clickPercent: function(px, py) {
      try {
            auto.clickPercent(px, py);
            return true;
      } catch (e) {
            logger.err("百分比点击失败: " + e);
            return false;
      }
    },

    swipe: function(x1, y1, x2, y2, duration) {
      duration = duration || 500;
      try {
            auto.swip(x1, y1, x2, y2, duration, 0);
            return true;
      } catch (e) {
            logger.err("滑动失败: " + e);
            return false;
      }
    },

    swipePercent: function(px1, py1, px2, py2, duration) {
      duration = duration || 500;
      var w = screen.getScreenWidth();
      var h = screen.getScreenHeight();
      return this.swipe(
            Math.floor(px1 * w), Math.floor(py1 * h),
            Math.floor(px2 * w), Math.floor(py2 * h), duration
      );
    },

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

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

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

    getXml: function() {
      try {
            if (typeof auto !== 'undefined' && typeof auto.getXml === 'function') {
                return auto.getXml();
            }
      } catch (e) {
            logger.err("getXml失败: " + e);
      }
      return "";
    },

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

      var pattern = /<node\s+([^>]+?)(?:\/>|>)/g;
      var match;
      while ((match = pattern.exec(xml)) !== null) {
            var attrs = match;
            var textVal = "";
            var descVal = "";
            var tm = attrs.match(/text="([^"]*)"/);
            if (tm) textVal = tm;
            var dm = attrs.match(/desc="([^"]*)"/);
            if (dm) descVal = dm;

            if (textVal.indexOf(keyword) !== -1 || descVal.indexOf(keyword) !== -1) {
                results.push(this._parseNode(attrs, textVal, descVal));
            }
      }
      return results;
    },

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

      var pattern = /<node\s+([^>]+?)(?:\/>|>)/g;
      var match;
      while ((match = pattern.exec(xml)) !== null) {
            var attrs = match;
            var tm = attrs.match(/text="([^"]*)"/);
            var dm = attrs.match(/desc="([^"]*)"/);
            results.push(this._parseNode(attrs, tm ? tm : "", dm ? dm : ""));
      }
      return results;
    },

    /**
   * 解析单个节点属性(内部方法)
   */
    _parseNode: function(attrs, textVal, descVal) {
      var cpm = attrs.match(/centerPercent="\[(+),(+)\]"/);
      var cm = attrs.match(/center="\[(\d+),(\d+)\]"/);
      var clm = attrs.match(/clickable="([^"]*)"/);
      var idm = attrs.match(/id="([^"]*)"/);
      var bm = attrs.match(/bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"/);
      var clsm = attrs.match(/class="([^"]*)"/);

      return {
            text: textVal,
            desc: descVal,
            x: cm ? parseInt(cm) : 0,
            y: cm ? parseInt(cm) : 0,
            px: cpm ? parseFloat(cpm) : 0,
            py: cpm ? parseFloat(cpm) : 0,
            clickable: clm ? clm : "false",
            id: idm ? idm : "",
            bounds: bm ? ), parseInt(bm), parseInt(bm), parseInt(bm)] : null,
            class: clsm ? clsm : ""
      };
    },

    /**
   * 查找不可点击节点的最近可点击父节点
   */
    findClickableParent: function(node, xml) {
      if (!node || !node.bounds) return null;
      var allNodes = this.findAllNodes(xml);
      var nodeArea = (node.bounds - node.bounds) * (node.bounds - node.bounds);
      var bestParent = null;
      var bestArea = 999999999;

      for (var i = 0; i < allNodes.length; i++) {
            var p = allNodes;
            if (p.clickable !== "true" || !p.bounds) continue;
            // 父节点必须包含目标节点
            if (p.bounds <= node.bounds && p.bounds <= node.bounds &&
                p.bounds >= node.bounds && p.bounds >= node.bounds) {
                var pArea = (p.bounds - p.bounds) * (p.bounds - p.bounds);
                // 选面积最小的可点击父节点(最近的)
                if (pArea < bestArea && pArea >= nodeArea) {
                  bestArea = pArea;
                  bestParent = p;
                }
            }
      }
      return bestParent;
    },

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

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

            var result = this._findBestNode(xml, text);
            if (result) {
                if (result.clickable === "true") {
                  this.clickPercent(result.px, result.py);
                } else {
                  // 不可点击 → 找最近可点击父节点
                  var parent = this.findClickableParent(result, xml);
                  if (parent) {
                        logger.log("节点不可点击,使用父节点: [" + result.text + "|" + result.desc + "] -> px=" + parent.px + " py=" + parent.py);
                        this.clickPercent(parent.px, parent.py);
                  } else {
                        this.clickPercent(result.px, result.py);
                  }
                }
                sleep.millisecond(毫秒 = 500);
                logger.log("找到并点击: " + text + " (text=" + result.text + " desc=" + result.desc + ")", true);
                return true;
            }
            sleep.millisecond(毫秒 = 300);
      }

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

    /**
   * 查找最佳节点(优先可点击的)
   */
    _findBestNode: function(xml, text) {
      var nodes = this.findNodes(xml, text);
      if (nodes.length === 0) return null;
      // 优先返回 clickable=true
      for (var i = 0; i < nodes.length; i++) {
            if (nodes.clickable === "true") return nodes;
      }
      return nodes;
    },

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

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

    hasAnyText: function(texts) {
      for (var i = 0; i < texts.length; i++) {
            if (this.findText(texts)) return true;
      }
      return false;
    },

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

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

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

            // 尝试粘贴
            try {
                var node = auto.nodeSearch(3000).focused(true);
                if (node && typeof node.getNode === 'function') {
                  var n = node.getNode(0);
                  if (n && typeof n.paste === 'function') {
                        n.paste();
                  }
                }
            } catch (pe) {}

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

    /**
   * 等待指定文字出现
   */
    backToText: function(text, timeout) {
      timeout = timeout || 10000;
      var startTime = new Date().getTime();
      while (new Date().getTime() - startTime < timeout) {
            if (this.hasText(text)) return true;
            sleep.millisecond(毫秒 = 500);
      }
      return false;
    }
};

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

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

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

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

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

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

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

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

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

      // 按级别调用 AIWROK 平台原生方法
      try {
            if (level === "错误" && typeof print !== 'undefined' && print.err) {
                print.err(line);
            } else if (level === "警告" && typeof print !== 'undefined' && print.warn) {
                print.warn(line);
            } else if (typeof print !== 'undefined' && print.log) {
                print.log(line);
            } else if (typeof printl === 'function') {
                printl(line);
            }
      } catch (e) {}

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

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

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

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

// =============================================================================
//第五部分:卡密管理
// =============================================================================

var 卡密管理 = {

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

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

            var remaining = lines.slice(1);
            var writer = new OutputStreamWriter(new FileOutputStream(file), "UTF-8");
            for (var i = 0; i < remaining.length; i++) writer.write(remaining + "\n");
            writer.close();
            return 卡密;
      } catch (e) {
            logger.err("读取卡密失败: " + e);
            return null;
      }
    },

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

            try {
                var json = JSON.parse(response);
                if (json.code === 0 && json.data) {
                  logger.log("服务器返回卡密: " + json.data);
                  return json.data;
                }
                logger.err("服务器返回错误: " + (json.msg || json.message || response));
            } catch (e) {
                if (response && response.length > 0) {
                  logger.log("服务器返回卡密: " + response);
                  return response;
                }
            }
      } catch (e) {
            logger.err("服务器获取卡密失败: " + e);
      }
      return null;
    },

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

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

    检查库存: function(商品) {
      if (CONFIG.使用服务器) return -1;
      try {
            var file = new File(商品.卡密文件);
            if (!file.exists()) return 0;
            var reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF-8"));
            var count = 0, line;
            while ((line = reader.readLine()) !== null) {
                line = line.trim();
                if (line !== "" && line.indexOf("#") !== 0) count++;
            }
            reader.close();
            return count;
      } catch (e) { return -1; }
    }
};

// =============================================================================
//第六部分:发货记录
// =============================================================================

var 发货记录 = {

    已发货: function(订单号) {
      try {
            var file = new File(CONFIG.发货记录文件);
            if (!file.exists()) return false;
            var reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF-8"));
            var line;
            while ((line = reader.readLine()) !== null) {
                if (line.indexOf(订单号) !== -1) { reader.close(); return true; }
            }
            reader.close();
      } catch (e) {}
      return false;
    },

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

    今日数量: function() {
      try {
            var file = new File(CONFIG.发货记录文件);
            if (!file.exists()) return 0;
            var reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF-8"));
            var now = new Date();
            var today = now.getFullYear() + "-" + (now.getMonth() + 1) + "-" + now.getDate();
            var count = 0, line;
            while ((line = reader.readLine()) !== null) {
                if (line.indexOf(today) !== -1) count++;
            }
            reader.close();
            return count;
      } catch (e) { return 0; }
    }
};

// =============================================================================
//第七部分:千牛自动化操作
// =============================================================================

var 千牛操作 = {

    启动千牛: function() {
      logger.log("启动千牛...", true);
      try {
            app.openApp(CONFIG.千牛包名);
            for (var i = 8; i > 0; i--) {
                sleep.second(秒 = 1);
            }
            logger.success("千牛已启动");
            return true;
      } catch (e) {
            logger.err("启动千牛失败: " + e);
            return false;
      }
    },

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

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

    /**
   * 检查待发货标签是否已选中
   */
    待发货已选中: function() {
      var xml = ActionAdapter.getXml();
      if (!xml) return false;
      var nodes = ActionAdapter.findNodes(xml, "待发货");
      for (var i = 0; i < nodes.length; i++) {
            // selected=true 表示已选中
            var pattern = /<node\s+([^>]+?)(?:\/>|>)/g;
            var match;
            while ((match = pattern.exec(xml)) !== null) {
                var attrs = match;
                if ((attrs.indexOf("待发货") !== -1) && attrs.indexOf('selected="true"') !== -1) {
                  return true;
                }
            }
      }
      return false;
    },

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

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

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

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

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

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

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

      // 检查是否有订单号格式的数字
      var xml = ActionAdapter.getXml();
      if (xml) {
            var allNodes = ActionAdapter.findAllNodes(xml);
            for (var j = 0; j < allNodes.length; j++) {
                if (allNodes.text && /^\d{15,20}$/.test(allNodes.text)) {
                  logger.log("检测到订单号: " + allNodes.text, true);
                  return true;
                }
            }
      }
      return false;
    },

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

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

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

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

    /**
   * 获取当前订单号
   */
    获取订单号: function() {
      var xml = ActionAdapter.getXml();
      if (xml) {
            var patterns = [
                /订单号[::]\s*(\d{10,20})/,
                /订单编号[::]\s*(\d{10,20})/,
                /"(\d{15,20})"/
            ];
            for (var i = 0; i < patterns.length; i++) {
                var m = xml.match(patterns);
                if (m && m) {
                  logger.log("提取到订单号: " + m);
                  return m;
                }
            }
      }
      var 临时号 = "TEMP" + new Date().getTime();
      logger.warn("未能提取订单号,使用临时号: " + 临时号);
      return 临时号;
    },

    /**
   * 点击订单进入详情页
   */
    点击进入订单: function(商品节点) {
      if (!商品节点) {
            ActionAdapter.clickPercent(0.5, 0.4);
            sleep.second(秒 = CONFIG.操作延时);
      } else {
            var node = 商品节点.节点 || 商品节点;
            if (node.px > 0 && node.py > 0) {
                ActionAdapter.clickPercent(node.px, node.py);
            } else {
                ActionAdapter.clickPercent(0.5, 0.4);
            }
            sleep.second(秒 = CONFIG.操作延时);
      }

      // 验证是否进入订单详情
      if (ActionAdapter.hasText("订单详情") || ActionAdapter.hasText("订单号") ||
            ActionAdapter.hasText("联系买家") || ActionAdapter.hasText("发货") ||
            ActionAdapter.hasText("阿里旺旺") || ActionAdapter.hasText("旺旺")) {
            logger.success("已进入订单详情");
            return true;
      }
      if (ActionAdapter.hasText("确认发货") || ActionAdapter.hasText("物流")) {
            logger.success("已进入发货页面");
            return true;
      }

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

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

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

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

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

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

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

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

    /**
   * 返回待发货订单列表
   */
    返回列表: function() {
      logger.log("返回订单列表...");
      ActionAdapter.back();
      sleep.second(秒 = CONFIG.操作延时);
      ActionAdapter.back();
      sleep.second(秒 = CONFIG.操作延时);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

      // 处理可能的二次确认弹窗
      if (ActionAdapter.hasText("确定") || ActionAdapter.hasText("确认")) {
            var 弹窗按钮 = ["确定", "确认", "是"];
            for (var n = 0; n < 弹窗按钮.length; n++) {
                if (ActionAdapter.findTextAndClick(弹窗按钮, 1500)) {
                  sleep.second(秒 = 1);
                  logger.log("已确认弹窗: " + 弹窗按钮);
                  break;
                }
            }
            sleep.second(秒 = 2);
            // 再次检查
            if (!发货成功) {
                发货成功 = ActionAdapter.hasText("发货成功") ||
                           ActionAdapter.hasText("已发货") ||
                           ActionAdapter.hasText("发货完成");
            }
      }

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

      return 发货成功;
    },

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

// =============================================================================
//第八部分:发货核心流程
// =============================================================================

var 发货流程 = {

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

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

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

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

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

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

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

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

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

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

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

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

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

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

      if (配置.发货类型 === "卡密") {
            卡密 = 卡密管理.取卡密(配置);
            if (!卡密) { logger.err("无可用卡密!商品: " + 商品.名称); return null; }
            话术 = 话术.replace("{卡密}", 卡密).replace("{网盘}", 网盘);
            摘要 = "卡密:" + 卡密;
      } else if (配置.发货类型 === "网盘") {
            话术 = 话术.replace("{网盘}", 网盘).replace("{卡密}", "");
            摘要 = "网盘:" + 网盘.substring(0, 20);
      } else if (配置.发货类型 === "卡密+网盘") {
            卡密 = 卡密管理.取卡密(配置);
            if (!卡密) { logger.err("无可用卡密!商品: " + 商品.名称); return null; }
            话术 = 话术.replace("{卡密}", 卡密).replace("{网盘}", 网盘);
            摘要 = "卡密:" + 卡密 + " 网盘:" + 网盘.substring(0, 15);
      }
      return { 消息: 话术, 摘要: 摘要, 卡密: 卡密 };
    },

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

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

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

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

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

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

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

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

// =============================================================================
//第十部分:库存预警
// =============================================================================

var 库存预警 = {
    检查全部: function() {
      var 预警商品 = [];
      for (var 商品名 in 商品配置) {
            var 配置 = 商品配置[商品名];
            if (配置.发货类型 === "卡密" || 配置.发货类型 === "卡密+网盘") {
                var 库存 = 卡密管理.检查库存(配置);
                if (库存 >= 0 && 库存 < 5) {
                  预警商品.push(商品名 + " (剩余" + 库存 + "个)");
                  logger.warn("库存预警: " + 商品名 + " 仅剩 " + 库存 + " 个卡密!");
                }
            }
      }
      return 预警商品;
    }
};

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

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

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

      logger.init();

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// =============================================================================
//第十二部分:启动入口
// =============================================================================

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

function 创建卡密示例() {
    try {
      for (var 商品名 in 商品配置) {
            var 配置 = 商品配置[商品名];
            if (配置.卡密文件 && 配置.卡密文件 !== "") {
                var file = new File(配置.卡密文件);
                if (!file.exists()) {
                  var writer = new OutputStreamWriter(new FileOutputStream(file), "UTF-8");
                  writer.write("# " + 商品名 + " 卡密文件\n");
                  writer.write("# 每行一个卡密,用过的自动删除\n\n");
                  writer.write("Q5J3HHT8-NJMF\n");
                  writer.write("KA8CPLJP-DNCM\n");
                  writer.write("FYYEGC6D-KPCA\n");
                  writer.close();
                  logger.log("已创建卡密示例文件: " + 配置.卡密文件);
                }
            }
      }
    } catch (e) {
      logger.err("创建卡密示例失败: " + e);
    }
}

// =============================================================================
//启动
// =============================================================================

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

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

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

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

      logger.log("===== 测试查找'待发货'节点 =====");
      var xml = ActionAdapter.getXml();
      var nodes = ActionAdapter.findNodes(xml, "待发货");
      logger.log("查找'待发货'结果: " + nodes.length + " 个节点");
      for (var i = 0; i < nodes.length; i++) {
            logger.log("节点" + i + ": text=[" + nodes.text + "] desc=[" + nodes.desc + "] px=" + nodes.px + " py=" + nodes.py + " clickable=" + nodes.clickable);
            if (nodes.clickable === "false") {
                var parent = ActionAdapter.findClickableParent(nodes, xml);
                if (parent) {
                  logger.log("    -> 父节点: px=" + parent.px + " py=" + parent.py + " clickable=" + parent.clickable);
                }
            }
      }

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

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

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

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

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

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

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

                  // 处理这个订单
                  logger.log("===== 开始处理订单 =====");
                  var 结果 = 发货流程.处理订单();
                  if (结果) {
                        logger.success("测试发货成功!", true);
                  } else {
                        logger.err("测试发货失败,请检查日志", true);
                  }
                } else {
                  logger.warn("未匹配到商品");
                  logger.log("请检查商品配置中的标题是否与淘宝订单中的商品标题一致");
                  logger.log("===== 当前页面所有节点 =====");
                  ActionAdapter.dumpPageSummary();
                }
            } else {
                logger.log("当前没有待发货订单,请先在淘宝下一单测试");
                logger.log("===== 当前页面节点 =====");
                ActionAdapter.dumpPageSummary();
            }
      }

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

} else {
    // ===== 正常模式:24小时持续监控 =====
    主控.start();
}


页: [1]
查看完整版本: 无碍模式auto淘宝千牛自动发货系统