向指定QQ好友发消息
主程序
var QQFriends = [{
QQName: '',
QQNum: '1553515922'
}]
//给QQ指定好友发消息的步骤大致如下
// 1 打开QQ
// 2 点击搜索
// 3 输入QQ名字或者QQ号码
// 4 等待联系人出现
// 5 点击联系人
// 6 输入消息
// 7 发消息
// 8 回到QQ消息界面
// 以上是8个work
// 先导入我们需要的模块
var init = require('./init')
var common = require('./common')
var Work = init.Work
// 创建8个work
var mainFlow = [
"打开QQ",
"点击搜索",
"输入QQ名字或者QQ号码",
"等待联系人出现",
"点击联系人",
"输入消息",
"发消息",
"回到QQ消息界面"
]
var mainWork = new Work('mainWork')
for (let i = 0; i < mainFlow.length; i++) {
eval(mainFlow[i] + "=" + "new Work(" + "'" + mainFlow[i] + "')")
mainWork.flow.push(eval(mainFlow[i]))
}
//看看设计的流程
mainWork.flow.map((ele) => {
log(ele.name)
})
//上面的流程设计完了
//下面设计具体每个流程要执行的内容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 = QQFriends[0].QQNum
searchView.setText(qqInfo)
}
}
输入QQ名字或者QQ号码.statusMustBeAfterDo = {
viewArr: [{
text: QQFriends[0].QQNum
},
{
text: '取消'
}
]
}
等待联系人出现.action = () => {
sleep(1000)
}
等待联系人出现.statusMustBeAfterDo = {
viewArr: [{
textMatches: '\\(' + QQFriends[0].QQNum + '\\)'
}]
}
点击联系人.action = () => {
var reg = new RegExp("\\(" + QQFriends[0].QQNum + "\\)");
var friend = textMatches(reg).findOnce()
if (friend) {
common.clickView(friend)
}
}
点击联系人.statusMustBeAfterDo = {
viewArr: [{
text: '发送'
}]
}
输入消息.action = () => {
// fullId = com.tencent.mobileqq:id/input
var inputView = id('com.tencent.mobileqq:id/input').findOnce()
if (inputView) {
inputView.setText('hello 2019')
}
}
输入消息.statusMustBeAfterDo = {
viewArr: [{
text: 'hello 2019'
}]
}
发消息.action = () => {
var sendView = text('发送').findOnce()
if (sendView) {
common.clickView(sendView)
}
}
发消息.statusMustBeAfterDo = {
viewArr: [{
text: 'hello 2019'
}]
}
回到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
mainWork.do()
需要用到的两个模块
init.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.messagesToOthers = 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')
}
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