向指定QQ好友发消息第二版

主要问题是发送消息之后,要检测是否发送了消息,
脚本流程没什么变化,
增加了一个inspectDynamicViewAfterDo方法
用来更新statusMustBeAfterDo的内容

如果不加inspectDynamicViewAfterDo方法
statusMustBeAfterDo一直检测的都是发送的第一条QQ消息

最后由 jiajia123 编辑

2019-1-7 手机P9测试QQ正常发消息

var QQFriends = [{
    QQName: '',
    QQNum: '1553515922',
    QQMsg: '你好2019你的序号是1'
  },
  {
    QQName: '',
    QQNum: '1553515922',
    QQMsg: '你好2019你的序号是2'
  },
  {
    QQName: '',
    QQNum: '1553515922',
    QQMsg: '你好2019你的序号是3'
  },
  {
    QQName: '',
    QQNum: '1553515922',
    QQMsg: '你好2019你的序号是4'
  },
  {
    QQName: '',
    QQNum: '1553515922',
    QQMsg: '你好2019你的序号是5'
  },
  {
    QQName: '',
    QQNum: '1553515922',
    QQMsg: '你好2019你的序号是6'
  },
  {
    QQName: '',
    QQNum: '1553515922',
    QQMsg: '你好2019你的序号是7'
  },
  {
    QQName: '',
    QQNum: '1553515922',
    QQMsg: '你好2019你的序号是8'
  },
  {
    QQName: '',
    QQNum: '1553515922',
    QQMsg: '你好2019你的序号是9'
  },
  {
    QQName: '',
    QQNum: '1553515922',
    QQMsg: '你好2019你的序号是10'
  },
]
//给QQ指定好友发消息的步骤大致如下
// 1 打开QQ
// 2 点击搜索
// 3 输入QQ名字或者QQ号码
// 4 等待联系人出现
// 5 点击联系人
// 6 输入消息
// 7 发消息
// 8 回到QQ消息界面
// 以上是8个work
// 先导入我们需要的模块
var init = require('./init2')
var common = require('./common')
var Work = init.Work
// 创建8个work
var mainFlow = [
  "打开QQ",
  "点击搜索",
  "输入QQ名字或者QQ号码",
  "等待联系人出现",
  "点击联系人",
  "输入消息",
  "发消息",
  "回到QQ消息界面"
]
var mainWork = new Work('mainWork')
mainWork.carriedInfo = QQFriends
mainWork.friendSerialNumber = 0
mainWork.getFriendSerialNumber = function () {
  return mainWork.friendSerialNumber
}
for (let i = 0; i < mainFlow.length; i++) {
  eval(mainFlow[i] + "=" + "new Work(" + "'" + mainFlow[i] + "')")
  mainWork.flow.push(eval(mainFlow[i]))
}
//看看设计的流程
log('打印工作流开始')
mainWork.flow.map((ele) => {
  log(ele.name)
})
log('打印工作流结束')
//上面的流程设计完了
//下面设计具体每个流程要执行的内容action
打开QQ.action = () => {
  launchApp("QQ");
}
//怎样就算打开QQ了呢?
//QQ底部有消息和联系人两个控件,我们就认为打开了QQ
打开QQ.statusMustBeAfterDo = {
  viewArr: [{
    text: '消息'
  }, {
    text: '联系人'
  }]
}
// 上面的属性翻译是:action之后必须具备的状态
// 我们也可以加action之前必须具备的状态,
// 不过
// 奥卡姆剃刀:如无必要,勿增实体
// 所以等我们觉得有必要了再加
点击搜索.action = () => {
  //这里用findOnce()没有问题
  //因为Word类会默认执行15次,间隔1秒
  var searchView = text('搜索').findOnce()
  if (searchView) {
    common.clickView(searchView)
  }
}
//加上点击搜索之后必须具备的状态
点击搜索.statusMustBeAfterDo = {
  viewArr: [{
    text: '搜索'
  }, {
    text: '取消'
  }]
}
输入QQ名字或者QQ号码.action = () => {
  var searchView = text('搜索').findOnce()
  if (searchView) {
    var qqInfo = mainWork.carriedInfo[mainWork.getFriendSerialNumber()].QQNum
    searchView.setText(qqInfo)
  }
}
// 这是静态的,但是QQ号码是动态的所以用inspectDynamicViewAfterDo
// 输入QQ名字或者QQ号码.statusMustBeAfterDo = {
//   viewArr: [{
//       text: mainWork.carriedInfo[mainWork.getFriendSerialNumber()].QQNum
//     },
//     {
//       text: '取消'
//     }
//   ]
// }
输入QQ名字或者QQ号码.inspectDynamicViewAfterDo = function () {
  log('mainWork.carriedInfo')
  log(mainWork.carriedInfo)
  log('mainWork.getFriendSerialNumber()')
  log(mainWork.getFriendSerialNumber())
  var textContent = mainWork.carriedInfo[mainWork.getFriendSerialNumber()].QQNum
  this.statusMustBeAfterDo = {
    viewArr: [{
        text: textContent
      },
      {
        text: '取消'
      }
    ]
  }
}
等待联系人出现.action = () => {
  sleep(1000)
}
// 等待联系人出现.statusMustBeAfterDo = {
//   viewArr: [{
//     textMatches: '\\(' + mainWork.carriedInfo[mainWork.getFriendSerialNumber()].QQNum + '\\)'
//   }]
// }
等待联系人出现.inspectDynamicViewAfterDo = function () {
  var textMatchesContent = '\\(' + mainWork.carriedInfo[mainWork.getFriendSerialNumber()].QQNum + '\\)'
  this.statusMustBeAfterDo = {
    viewArr: [{
      textMatches: textMatchesContent
    }]
  }
}
点击联系人.action = () => {
  var reg = new RegExp("\\(" + mainWork.carriedInfo[mainWork.getFriendSerialNumber()].QQNum + "\\)");
  var friend = textMatches(reg).findOnce()
  if (friend) {
    common.clickView(friend)
  }
}
点击联系人.statusMustBeAfterDo = {
  viewArr: [{
    text: '发送'
  }]
}
点击联系人.timesMaxWait=3
输入消息.action = () => {
  // fullId = com.tencent.mobileqq:id/input
  var inputView = id('com.tencent.mobileqq:id/input').findOnce()
  if (inputView) {
    inputView.setText(mainWork.carriedInfo[mainWork.getFriendSerialNumber()].QQMsg)
  }
}
// 输入消息.statusMustBeAfterDo = {
//   viewArr: [{
//     text: mainWork.carriedInfo[mainWork.getFriendSerialNumber()].QQMsg
//   }]
// }
输入消息.inspectDynamicViewAfterDo = function(){
  var textContent = mainWork.carriedInfo[mainWork.getFriendSerialNumber()].QQMsg
  this.statusMustBeAfterDo = {
    viewArr: [{
      text: textContent
    }]
  }
}
发消息.action = () => {
  var sendView = text('发送').findOnce()
  if (sendView) {
    common.clickView(sendView)
  }
}
// 发消息.statusMustBeAfterDo = {
//   viewArr: [{
//     text: mainWork.carriedInfo[mainWork.getFriendSerialNumber()].QQMsg
//   }]
// }
发消息.inspectDynamicViewAfterDo = function(){
  var textContent = mainWork.carriedInfo[mainWork.getFriendSerialNumber()].QQMsg
  this.statusMustBeAfterDo = {
    viewArr: [{
      text: textContent
    }]
  }
}
回到QQ消息界面.action = () => {
  var leftTopCornerBackButton = id('com.tencent.mobileqq:id/rlCommenTitle').findOnce()
  // fullId = com.tencent.mobileqq:id/rlCommenTitle
  if (leftTopCornerBackButton) {
    common.clickView(leftTopCornerBackButton)
    var x = leftTopCornerBackButton.bounds().width() / 6
    var y = leftTopCornerBackButton.bounds().centerY()
    log('leftTopCornerBackButton')
    log(x, y)
    press(x, y, 1)
  }
}
回到QQ消息界面.statusMustBeAfterDo = {
  viewArr: [{
    text: '消息'
  }, {
    text: '联系人'
  }]
}
回到QQ消息界面.timesMaxWait = 3
//属性方法随便加

for (let i = 0; i < QQFriends.length; i++) {
  mainWork.do()
  mainWork.friendSerialNumber++;
}

测试用到的两个模块
init2.js

// log('加载了init模块')

var common = require('./common')
var currentDate = common.getCurrrentDate()
var date = currentDate.substring(0, 5)
var recordClickedVideoNamePath = files.path('./'+date+'recordClickedVideoName.json')
createFileRecordClickedVideoName()

function createFileRecordClickedVideoName() {
  if (files.exists(recordClickedVideoNamePath)) {} else {
    files.createWithDirs(recordClickedVideoNamePath);
    files.write(recordClickedVideoNamePath, JSON.stringify([]));

  }
}


function Work(name, flow) {
  this.name = name || ''
  this.flow = flow || []
  this.status = 'ready'
  this.timesMaxDo = 15
  this.timesDo = 0
  this.timesMaxWait = 15
  this.statusMustBeBeforeDo = {}
  this.inspectDynamicViewBeforeDo = function (){}
  this.inspectDynamicViewAfterDo = function (){}
  this.messagesToOthers = null
  this.carriedInfo = null
  // {
  //   viewArr:[{text:'QQ'}]
  // }
  this.statusMustBeAfterDo = {}
  this.action = function () {
    for (let i = 0; i < this.flow.length; i++) {
      // log(this.name)
      // log(this.flow)
      // log(this.flow[i])
      this.flow[i].do()
      if(!this.messagesToOthers){
        this.messagesToOthers={}
      }
      this.messagesToOthers[this.flow[i].name]=this.flow[i].messagesToOthers
    }
  }
  this.do = function () {
    log('现在的工作是: %s',this.name)
    if (this.timesDo > this.timesMaxDo) {
      this.handleError('timesDoOver')
    }
    this.inspectDynamicViewBeforeDo()
    this.inspectDynamicViewAfterDo()

    var inspectStatusBeforeDo = new Inspect(this.statusMustBeBeforeDo)

    var inspectStatusAfterDo = new Inspect(this.statusMustBeAfterDo)
    if (this.timesDo > 0) {
      if (inspectStatusAfterDo.inspect()) {
        log(util.format('%s %s is OK', this.name, 'inspectStatusAfterDo'))
        this.timesDo = 0
        return true;
      }
    }

    if (!inspectStatusBeforeDo.inspect()) {
      this.handleError('statusStartError')
    } else {
      log(util.format('%s %s is OK', this.name, 'inspectStatusBeforeDo'))
    }
    this.action()
    this.timesDo++;
    for (let i = 0; i < this.timesMaxWait; i++) {
      if (inspectStatusAfterDo.inspect()) {
        break;
      } else {
        sleep(1000)
      }
    }
    if (!inspectStatusAfterDo.inspect()) {
      this.handleError('statusEndError')
    } else {
      log(util.format('%s %s is OK', this.name, 'inspectStatusAfterDo'))
      // alert(util.format('%s %s is OK', this.name, 'inspectStatusAfterDo'))
      this.timesDo = 0
    }
  }
  this.handleError = function (err) {
    switch (err) {
      case 'statusStartError':
        log(util.format('%s %s is error', this.name, 'inspectStatusBeforeDo'))
        sleep(1000)
        this.timesDo++;
        this.do()
        break;
      case 'statusEndError':
        log(util.format('%s %s is error', this.name, 'inspectStatusAfterDo'))
        this.timesDo++;
        this.do()
        break;
      case 'timesDoOver':
        log(util.format('%s is error: %s', this.name, 'timesDoOver'))
        throw (util.format('%s is error: %s', this.name, 'timesDoOver'))
        break;
      default:
        throw util.format('%s occur error: ', this.name, err)
    }
  }
}
//做个检查类,来检查界面的状态,当然也可以拓展检查其他东西,时间,手机电量,wifi状态啦之类的,只要返回布尔值就可以了
function Inspect(status) {
  this.status = status || {}
  //status是指statusMustBeBeforeDo或者statusMustBeAfterDo,
  //一般就是检查控件是否存在
  //当然也可以是其他状态
  //比如时间,文件
  //毕竟状态是一种抽象的概念,
  //万事万物都有状态
  //至于检查的方法就各有章法了
  //至于status用数组还是对象来保存,
  //我都思考了好长时间
  //我觉得病人的状态按照心跳,血压,气色,来观察的
  //所以我就决定选择对象来保存,
  //其实数组也可以保存,都一样的,
  //感觉用数组或者对象保存,并没有什么区别
  this.inspect = function () {
    var status = this.status
    for (var k in status) {
      switch (k) {
        case 'viewArr':
          log(util.format('当前检查类型是%s', 'viewArr'))
          return this.inspectViewArr(status[k])
        case 'wordArr':
          log(util.format('当前检查类型是%s', 'viewArr'))
          return this.inspectWordArr(status[k])
        default:
          throw util.format('没有这种检查类型%s,你自己加吧', k)
      }
    }
    return true
  }
}
//检查控件就和检查鸭子一样,
//如果它看起来像鸭子,走起来像鸭子,叫起来像鸭子,它就是鸭子。--麦卡锡
Inspect.prototype.inspectViewArr = function (viewArr) {
  var common = require('./common')
  if (!(common.isArrayFn(viewArr))) {
    throw 'viewArr必须是数组'
  }
  if (viewArr.length == 0) {
    return true
  }

  function getSelectorRule(attr, val) {
    switch (attr) {
      case 'id':
        return 'id("' + val + '").'
      case 'text':
        return 'text("' + val + '").'
      case 'textMatches':
        return 'textMatches(/' + val + '/).'
      case 'desc':
        return 'desc("' + val + '").'
      case 'descMatches':
        return 'descMatches(/' + val + '/).'
      default:
        throw '没有这个属性'
    }
  }

  function getSelector(view) {
    var arr = Object.keys(view);
    if (arr.length == 0) {
      throw '不允许传入空对象'
    }
    var findRule = 'findOnce()'
    var rule = ''
    for (var k in view) {
      rule += getSelectorRule(k, view[k])
    }
    return rule + findRule
  }
  for (let i = 0; i < viewArr.length; i++) {
    var findViewRule = getSelector(viewArr[i])
    log(findViewRule)
    if (eval(findViewRule)) {
      log(util.format('这个规则%s找到控件了'), findViewRule)
    } else {
      log(util.format('这个规则%s没有找到控件'), findViewRule)
      return false
    }
  }
  return true
}
Inspect.prototype.inspectWordArr = function (wordArr) {
  //检查页面是否包含指定文字
  //common中存放一些公共的函数
  var common = require('./common')
  if (!(common.isArrayFn(wordArr))) {
    throw 'wordArr必须是数组'
  }
  var allWords = common.getAllWords()
  var result = common.bigArrContainsSmallArr(allWords, wordArr)
  return result
}
module.exports = {
  Work: Work,
  recordClickedVideoNamePath:recordClickedVideoNamePath
}

common.js

//点击控件
function clickView(view) {
  log(arguments.callee.name + '开始')
  log(view)
  if (view) {
    var x = view.bounds().centerX()
    var y = view.bounds().centerY()
    log('将要点击的坐标 %s,%s', x, y)
    press(x, y, 1)
  } else {
    throw '传入clickView中的view异常'
  }
  log(arguments.callee.name + '结束')
}
//去除头尾空格
String.prototype.strip = function () {
  log(arguments.callee.name + '开始')
  var str = this,
     whitespace = ' \n\r\t\f\x0b\xa0\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\u3000';
  for (var i = 0, len = str.length; i < len; i++) {
    if (whitespace.indexOf(str.charAt(i)) === -1) {
      str = str.substring(i);
      break;
    }
  }
  for (i = str.length - 1; i >= 0; i--) {
    if (whitespace.indexOf(str.charAt(i)) === -1) {
      str = str.substring(0, i + 1);
      break;
    }
  }
  log(arguments.callee.name + '结束')
  return whitespace.indexOf(str.charAt(0)) === -1 ? str : '';
}
// 随机滑动
function swipeRnd(x1, y1, x2, y2, duration) {
  log(arguments.callee.name + '开始')
  var k = 20
  var x1 = x1 + random(-(k), k)
  var y1 = y1 + random(-(k), k)
  var x2 = x2 + random(-(k), k)
  var y2 = y2 + random(-(k), k)
  var duration = duration + random(-(k), k)
  swipeRnd2(x1, y1, x2, y2, duration)
  log(arguments.callee.name + '结束')
}

function swipeRnd2(x1, y1, x2, y2, duration){
  log(arguments.callee.name + '开始')
  gesture(duration, [x1, y1], [x1+60, y1-80], [x2, y2])
  log(arguments.callee.name + '结束')
}
















// 随机滑动  两点滑动 变为三点滑动
function swipeRnd3(x1, y1, x2, y2, duration) {
  log(arguments.callee.name + '开始')
  gesture(duration, [x1, y1], [x1 + random(10, 60), y1 - random(10, 80)], [x2, y2])
  log(arguments.callee.name + '结束')
}

function swipeFromDownToUp() {
  log(arguments.callee.name + '开始')
  var w = device.width
  var h = device.height
  var x1 = Math.floor(w / 5 * 1)
  var y1 = Math.floor(h / 5 * 4)
  var x2 = Math.floor(w / 5 * 3)
  var y2 = Math.floor(h / 5 * 1)
  var duration = 300
  log('滑动参数=', x1, y1, x2, y2, duration)
  swipeRnd(x1, y1, x2, y2, duration)
  log(arguments.callee.name + '结束')
  sleep(3000)
}
Array.prototype.minus = function (arr) {
  return this.filter(function (element) {
    for (let i = 0; i < arr.length; i++) {
      if (JSON.stringify(element) == JSON.stringify(arr[i])) {
        return false
      }
    }
    return true
  });
};
Array.prototype.intersect = function (arr) {
  return this.filter(function (element) {
    for (let i = 0; i < arr.length; i++) {
      if (JSON.stringify(element) == JSON.stringify(arr[i])) {
        return true
      }
    }
    return false
  });
};

function bigArrContainsSmallArr(bigArr, smallArr) {
  //对于重复的元素采用计数的方式对比
  var bigArrObj = {}
  var smallArrObj = {}
  for (let i = 0; i < bigArr.length; i++) {
    var has = bigArrObj.hasOwnProperty(bigArr[i])
    if (has) {
      bigArrObj[bigArr[i]]++;
    } else {
      bigArrObj[bigArr[i]] = 1
    }
  }
  for (let i = 0; i < smallArr.length; i++) {
    var has = smallArrObj.hasOwnProperty(smallArr[i])
    if (has) {
      smallArrObj[smallArr[i]]++;
    } else {
      smallArrObj[smallArr[i]] = 1
    }
  }
  for (var k in smallArrObj) {
    if (bigArrObj.hasOwnProperty(k) && bigArrObj[k] >= smallArrObj[k]) {} else {
      return false
    }
  }
  return true
}
/**
  * @method getAllWords
  * @param  setting 是个对象, 决定是否获取text,desc,id,以及是否去重; 默认获取text和desc,不获取id,默认去重
  * @desc 默认设置为{
    getText: true,
    getDesc: true,
    getId: false,
    removeRepetitiveElements: true
  }
  * @desc 获取页面所有文字,可以指定text,desc,id三个中的任意几个
  * @return 所有文字组成的数组
  */
function getAllWords(setting) {
  var setting = setting || {}
  var defaultSetting = {
    getText: true,
    getDesc: true,
    getId: false,
    removeRepetitiveElements: false
  }
  Object.assign(defaultSetting, setting);
  log(defaultSetting)
  var allStr = []
  var getDescAndTextAndIdOfNode = function (node) {
    if (node) {
      if (defaultSetting.getText) {
        var text = node.text()
        if (!!text) {
          allStr.push(text)
        }
      }
      if (defaultSetting.getDesc) {
        var desc = node.desc()
        if (!!desc) {
          allStr.push(desc)
        }
      }
      if (defaultSetting.getId) {
        var id = node.id()
        if (!!id) {
          allStr.push(id)
        }
      }
    }
    for (let i = 0; i < node.childCount(); i++) {
      getDescAndTextAndIdOfNode(node.child(i));
    }
  }
  var getFrameLayoutNode = function () {
    return className('FrameLayout').findOne(2000)
  }
  getDescAndTextAndIdOfNode(getFrameLayoutNode())

  function removeRepetitiveElements(arr) {
    var obj = {}
    for (let i = 0; i < arr.length; i++) {
      if (obj.hasOwnProperty(arr[i])) {} else {
        obj[arr[i]] = true
      }
    }
    return Object.keys(obj)
  }
  if (defaultSetting.removeRepetitiveElements) {
    allStr = removeRepetitiveElements(allStr)
  }
  return allStr
}
// JavaScript 标准文档中定义: [[Class]] 的值只可能是下面字符串中的一个: Arguments, Array, Boolean, Date, Error, Function, JSON, Math, Number, Object, RegExp, String.
function isArrayFn(value) {
  if (typeof Array.isArray === "function") {
    return Array.isArray(value);
  } else {
    return Object.prototype.toString.call(value) === "[object Array]";
  }
}

function isObjectFn(value) {
  return Object.prototype.toString.call(value) === "[object Object]";
}
Date.prototype.Format = function(fmt)
{ //author: meizz
  var o = {
    "y+" : this.getFullYear(),                 //月份
    "M+" : this.getMonth()+1,                 //月份
    "d+" : this.getDate(),                    //日
    "h+" : this.getHours(),                   //小时
    "m+" : this.getMinutes(),                 //分
    "s+" : this.getSeconds(),                 //秒
    "q+" : Math.floor((this.getMonth()+3)/3), //季度
    "S"  : this.getMilliseconds()             //毫秒
  };
  if(/(y+)/.test(fmt))
    fmt=fmt.replace(RegExp.$1, (this.getFullYear()+"").substr(4 - RegExp.$1.length));
  for(var k in o)
    if(new RegExp("("+ k +")").test(fmt))
  fmt = fmt.replace(RegExp.$1, (RegExp.$1.length==1) ? (o[k]) : (("00"+ o[k]).substr((""+ o[k]).length)));
  return fmt;
}
function getCurrrentDate(){
  var result=(new Date()).Format("MM-dd-yy-hh-mm")
  return result
}

var common = {
  clickView: clickView,
  bigArrContainsSmallArr: bigArrContainsSmallArr,
  swipeRnd: swipeRnd,
  swipeRnd3: swipeRnd3,
  swipeFromDownToUp: swipeFromDownToUp,
  getAllWords: getAllWords,
  isArrayFn: isArrayFn,
  getCurrrentDate: getCurrrentDate,
  isObjectFn: isObjectFn
}
module.exports = common
  • 4
    帖子
  • 1032
    浏览

与 Auto.js 的连接断开,我们正在尝试重连,请耐心等待