Autox.js实现每日定时小程序抢课
Autox.js实现每日定时小程序抢课
1、Autox.js下载与配置
1.1 简介和文档地址
autox.js(项目地址)是大佬在auto.js 4.1(项目地址)版本的基础上继续维护项目,Autox.js(文档地址)是一款不需要root便能实现自动化的脚本框架,很适合新手小白安卓自动化的学习和编写。
1.2 连接电脑调试/定时启动
一些配置方法以及定时启用可参考之前写的另一篇博文,这里不多赘述了
Autox.js实现全自动每日抖音续火花
2、大致流程叙述
工具:autoxjs本质还是模拟点击,没有绕过前端直接向服务器发送消息,所以只要不频繁多次点击,不会对服务器造成多大影响,也无法被发现。
- 由于发现所用的小程序是获取手机系统时间而不是服务器时间的,所以如果将手机的系统时间提前,可以比别人提早进入抢课页面,在点击抢课的时候比其他人少按一步,保证最快。所以提前将手机时间调快了两分钟。
- 在抢课前几分钟解锁屏幕。autoxjs因为没有root,没办法绕过开机密码,所以需要模拟开机密码的位置进行点击,进入桌面。
- 正确打开约课小程序
- 提前筛选好要抢的课程并等待约课时间到达
- 到达时间依次约课
auto();
//---------------------一些全局参数------------
//抢课时间(小时,分钟,延时(单位毫秒,可以写随机数))
//抢课时间为北京时间21:50,我将系统时间调快了2分钟,所以设置的抢课时间调为了21:52,并延时500毫秒运行
var CLASSHOUR = 21;
var CLASSMINUTE = 52;
var LAYBACK = 500;
//-----------------以下是主程序--------------
//1、解锁屏幕、打开报告窗口
unLockPhone();
console.show();
//打开小程序,选择门店(比如nancun为南村万博,huangpu为黄埔,自己设定)
openfoxclass("nancun");
//获取今天的时间和星期
var date = new Date();
var dayofweek_tomo = (date.getDay() + 1) % 7;
//筛选要抢的课程
ChooseForDay(dayofweek_tomo);
//点击明天的课(坐标点击简单但是兼容性差)
//click(218,1020);
sleep(3000);
//抢课
reservation(CLASSHOUR, CLASSMINUTE, LAYBACK);
//结束后锁屏
sleep(5000)
lockScreen();
3、具体细节
3.1 屏幕唤醒解锁
由于没有root权限,autojs无法从有密码的锁屏下直接进入安卓系统,只能模拟坐标点击,依次点击密码进入。PS:autojs点击过快会无法点到,所以需要在两次点击之间增加sleep间隙。
//输入密码解锁手机
function unLockPhone() {
//为了方便如果本身是亮屏的,从先锁屏开始
lockScreen();
sleep(1000);
//亮屏+输入密码
device.wakeUp();
sleep(1000);
//下滑,开始输入密码
swipe(500, 2322, 500, 400, 900);
sleep(3000);
//密码点击部分-----------
//由于我的密码为3333,所以循环点击了4次,需要重新修改为自己的密码坐标
for (var i = 0; i < 4; i++) {
sleep(500);
//点击位置(553, 779)
press(553, 779, 400);
}
//---------------------------
//保持屏幕常亮
device.keepScreenOn();
sleep(2000);
}
3.2 打开微信小程序并选择分店
思考过一些方法去打开小程序,但是实际操作起来有点麻烦,最终使用如下方法。
- 先将小程序提前登录好,保存在首页的桌面(以圈中小程序为例进行演示)
- 切换需要预约的门店
//打开小程序并打开课程页面
//南村万博,黄埔店选店
function openfoxclass(house){
//回到首页
home();
sleep(3000);
home();
sleep(1000);
//小程序控件无法被点击,只能找到控件的位置然后点击中心区域
className("android.widget.ImageView").desc("Fox舞蹈").waitFor();
var fox_index = className("android.widget.ImageView").desc("Fox舞蹈").findOne().bounds();
click(fox_index.centerX(), fox_index.centerY());
sleep(10000);
//点击主页
text("主页").waitFor();
text("主页").findOne().click();
sleep(4000);
//点击切换门店
if (house == "huangpu") var housename = "黄埔店";
if (house == "nancun") var housename = "南村万博店";
log("切换门店"+ housename);
className("android.widget.Image").depth(9).indexInParent(0).findOne().click();
sleep(5000);
log("切换成功");
className("android.widget.TextView").text(housename).findOne().click();
sleep(3000);
//点击课程
text("课程").waitFor();
text("课程").findOne().click();
sleep(2000);
}
3.3 抢课前的准备
3.3.1 提前设置好每周的抢课表
根据自己需要进行修改,仅作为方法演示
//提前设置一周内每天要上的课程
function geteverydayclass(num) {
var reserclass = new Array();
switch (num) {
//星期天~星期一
case 0:
reserclass.push("JAZZ FUNK 入门课程---爆米花");
break;
case 1:
reserclass.push("HIPHOP/CHOREO入门课程---大灰");
break;
case 2:
reserclass.push("JAZZ入门课程---大灰");
break;
case 3:
reserclass.push("芬仔");
//reserclass.push("晚上");
break;
case 4:
reserclass.push("HIPHOP/CHOREO入门课堂---芬仔");
break;
case 5:
reserclass.push("CHOREO/HIPHOP入门课程---糖果");
break;
case 6:
reserclass.push("JAZZ入门课程---糖果");
break;
}
return reserclass;
}
3.3.2 根据抢课表筛选出要抢的课程
根据前面设定的抢课表,筛选出今天要抢的课
手动操作流程
- 点击明天(或者明天对应的星期)这个小程序有时候按钮会显示星期而不是“明天“
- 点击筛选按钮
- 点击重置重新筛选
- 根据设定的抢课表,点击选择课程
- 点击确定
- 界面就筛选出符合要求的课程了,比如选择明天强子老师的课程,筛选结果有以下两个
代码部分
//将数字转化为星期时间
function get_dayofweek_tomo(dayofweek_tomo){
var list1 = new Array("周日","周一","周二","周三","周四","周五","周六");
return list1[dayofweek_tomo];
}
//根据要上的课进行筛选(输入项:明天的星期)
function ChooseForDay(dayofweek_tomo) {
sleep(3000);
//点击明天的课表
var tip = get_dayofweek_tomo(dayofweek_tomo);
if (className("android.widget.TextView").text("明天").exists()){
className("android.widget.TextView").text("明天").findOne().click();
}
else if (className("android.widget.TextView").text(tip).exists()){
className("android.widget.TextView").text(tip).findOne().click();
}
sleep(2000);
//点击筛选
className("android.widget.TextView").text("筛选").findOne().click();
log("筛选中");
sleep(2000);
//先重置一下
className("android.widget.Button").text("重置").waitFor();
className("android.widget.Button").text("重置").findOne().click();
log("重置");
sleep(2000);
//选出明天要上的课
var chooseclass = geteverydayclass(dayofweek_tomo);
//测试
//var chooseclass = geteverydayclass(0);
for (var i = 0; i < chooseclass.length; i++) {
//如果打错了不存在的课
//if (!className("android.widget.Button").text(chooseclass[i]).exists()) {
if (!className("android.widget.Button").textContains(chooseclass[i]).exists()) {
log(chooseclass[i] + "不存在");
continue;
}
//点击符合的,提前进入到抢课页面
className("android.widget.Button").textContains(chooseclass[i]).untilFind().forEach(function(tv){
tv.click();
});
}
sleep(1000);
//点击确定
className("android.widget.Button").text("确定").waitFor();
className("android.widget.Button").text("确定").findOne().click();
log("确定");
sleep(3000);
}
3.4 等待抢课时间的到来
- 到达真正的抢课时间便开始抢课
//查看抢课时间
function checkTime(hour, minute, layback) {
var currentTime = new Date();
var targetTime = new Date();
targetTime.setHours(hour);
targetTime.setMinutes(minute);
targetTime.setSeconds(0);
targetTime.setMilliseconds(0);
// 如果当前时间超过了目标时间,则将目标时间设置到第二天
//(此条一般不会被执行,除非抢课晚了
if (currentTime > targetTime) {
//到时间也要先sleep
sleep(2000);
log("到时间了");
return true;
}
// 一般会执行这一条:如果没到时间,延时到达目标时间
var waittime = targetTime - currentTime;
log("等待" + String(waittime/1000) + "秒自动抢课");toast("等待" + String(waittime/1000) + "秒自动抢课");
sleep(waittime + layback);
return true;
}
3.5 依次点击预约列表进行预约
- 经过筛选后,界面显示所有待预约的课程
- 先进入到一个预约页面
- 到达时间了再点击“马上预约”
- 预约成功后会出现上述界面,点击确定
- 点击返回回到上面的界面,继续预约下一个课程,直到所有待预约的课程全部预约完成
- 预约不到的课也进行排队抢位操作(会记录最终的预约情况)
//预约课程
function reservation (CLASSHOUR, CLASSMINUTE, LAYBACK) {
sleep(3000);
//找到所有的预约按钮
className("android.widget.Button").waitFor();
while (true) {
sleep(1500);
var allclass1 = className("android.w idget.Button").find();
sleep(1500);
//找两次,看看数量一不一样
var allclass2 = className("android.widget.Button").find();
if (allclass2.length == allclass1.length) {
var allclass = allclass2;
break;
}
}
//第0位是空按钮,所以用shift删掉一个
allclass.shift();
log(allclass.length);
//是否约上课,0是未预约,1是成功预约,2是已经在排位
var ifsuccess = new Array(allclass.length).fill(0);
//记录所有的课程名
try {
var allclass_time = className("android.widget.TextView").textContains("~").untilFind();
//var allclass_name = className("android.widget.TextView").textContains("课程——").untilFind();
log("今日待约课程——"+String(allclass_time.length));
toast("今日待约课程数量:"+String(allclass_time.length));
} catch(error) {
log("课程识别有问题");
}
var retry = 0;
//预约开始
for(var i = 0; i < allclass.length; i++) {
var one = allclass[i];
//预约,余x席,加入等位都是可以预约的,其他的没到时间不能预约
if(one.text().length < 6 && one.text() != "再约一节") {
log("准备预约"+ String(allclass_time[i].text()));
toast("准备预约"+ String(allclass_time[i].text()));
//点击预约按钮
toast(one.text());log(one.text());
one.click();
//查看是否到时间,没到时间就先等等(?)
while(true) {
if(checkTime(CLASSHOUR, CLASSMINUTE, LAYBACK)) {
break;
}
}
//进入到会员卡选择的界面
//className("android.widget.TextView").text("会员卡选择").waitFor();
//进入到会员卡选择的界面
//id("go").waitFor();
//sleep(2000);
//className("android.widget.Button").clickable(true).depth(7).waitFor();
if (className("android.widget.Button").clickable(true).depth(7).exists()){
var yuyue = className("android.widget.Button").clickable(true).depth(7).findOne();
yuyue.click();
//sleep(2000);
} else {
//直接点了管不了了
log("找不到等位的控件,直接点击了");
click(737,2324);
sleep(2000);
}
//3是提示,4是里面的文字
var warning = className("android.widget.TextView").depth(4).findOne().text();
log(warning);
toast(warning);
//如果是请稍后重试
//找不到提示语点击确定
id("mm_alert_ok_btn").findOne(5000);
if (id("mm_alert_ok_btn").exists()){
sleep(500);
id("mm_alert_ok_btn").findOne().click();
sleep(500);
} else {
sleep(500);
//直接点了管不了了
log("找不到确认的控件,直接点击了");
click(530, 1413);
sleep(2000);
}
//如果是请稍后重试(只试3遍,超了就算了)
if (warning == "请稍后重试") {
sleep(4000);
if (retry < 3) {
//三秒后再试
i = i - 1;
retry = retry + 1;
continue;
}
else {
continue;
}
}
//sleep(2000);
//等待跳转到课程详情
className("android.widget.TextView").text("课程信息").findOne(5000);
//看看怎么回事
//1、显示在等位
if(className("android.widget.TextView").text("等位中").exists()) {
ifsuccess[i] = 2;
toast(String(allclass_time[i].text())+"成功等位");
}
if (className("android.widget.TextView").text("已预约").exists()){
toast(String(allclass_time[i].text())+"成功预约");
ifsuccess[i] = 1;
}
//返回键(?)
sleep(2000);
if (className("android.widget.TextView").text("").exists()) {
className("android.widget.TextView").text("").findOne().click();
} if (!className("android.widget.TextView").text("").exists()) {
log("没找到返回键,直接点了")
click(49,179);
}
}
sleep(2000);
}
log(ifsuccess);
toast(ifsuccess);
}
4、无法解决的问题
-
😰有时候无法正确识别出界面的控件,使用了waitFor()以后有时阻塞完全卡住,也找不到其他的办法,只能将识别不出的控件的位置记录下来,改成点击控件所在的位置。这样写会导致兼容性极低,只能自己用,换个设备就会出问题。只能增加加载时间(凭运气),尽量加载出控件再点击,但是有些控件无论等多久都加载不出来,还是有点难受的。。。
-
所以该脚本仅供参考,换到其他手机上需要多次调试再使用