【小玩具】 双 11 喵糖收集者

1,821 阅读3分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

image.png

想要集喵糖,又不想看广告。想着写个脚本机器人协助一下,想起当年微信跳一跳的闯关脚本,这个简单版的机器人应该是可以实现的。

刷店铺集喵糖的操作很机械,分为几步:

  1. 点击“赚糖领红包”按钮,展开任务面板
  2. 点击面板中的“去浏览”按钮,开始看广告
  3. 广告时间 15 ~ 20 秒
  4. 喵糖到手,返回上一级,重新进入第 2 步

也就是说只要能确定按钮在哪,能触发点击,能返回上一级,这件事就能成。

实现思路:

  1. adb 只想手机截屏上传图片到 PC 端
  2. PC 端进行图像识别,确定按钮位置
  3. adb 执行触屏操作

连接手机和 PC

PC 端安装 adb

brew install --cask android-platform-tools

检测安装成功与否

adb version

手机端开启调试,一般是关于手机 -> 版本号 -> 连击 N 次,开启开发者选项后,在其他设置找到入口打开 USB 调试。

Android 11 以上还可以开启无线调试模式:

  • 开发者选项 -> 无线调试
  • 使用配对码配对设备
  • PC 端 adb pair ipaddr:port,输入配对码配对
  • 配对成功后,PC 端 adb connect ipaddr:port 连接成功

更具体的操作见 官方文档

adb 截屏并上传到 PC 端

import { promisify } from "util";
import { exec as nodeExec } from "child_process";
const exec = promisify(nodeExec);

async function pullCurrentScreenShot() {
  // 截屏
  await exec("adb shell screencap /sdcard/meow_candy.png");
  // 上传
  await exec("adb pull /sdcard/meow_candy.png ./images");
}

图片识别确定按钮位置

从图片中识别元素有几种方案:

  1. 如果元素有固定的像素分布特征,通过像素匹配得到位置
  2. 如果元素中包含特征文本,通过文本识别得到位置
  3. opencv 准确识别元素整体

简单尝试 tesseract 之后,排除文本识别。由于活动页面元素过多,并且字体并不统一,OCR 识别准确率很低。opencv 涉及图像处理算法,上手折腾需要一定时间。实现基本需求为先,尝试使用像素匹配简单实现。

像素匹配思路:

  1. 确定点击目标的图像,截图保存下来。
  2. 获取源图片和目标的像素矩阵
  3. 基于像素矩阵匹配查找

匹配时做全图矩阵对比消耗太大,做一丢丢优化。

由于精度要求不高,这里先对全图做灰度化,再取目标图片的四个端点构成矩形框扫描匹配,最后计算原图匹配到的区域和目标图片的像素差。

图像处理使用 Jimp 来实现。

import Jimp from "jimp";

type TapTargetType = [number, number];
async function recognizeTapTarget(
  srcImgPath: string,
  targetImgPath: string
): Promise<TapTargetType | null> {
  // 准备目标图像端点数据
  const targetImage = await Jimp.read(targetImgPath);
  targetImage.greyscale();
  const tWidth = targetImage.bitmap.width;
  const tHeight = targetImage.bitmap.height;
  const rect = [
    targetImage.getPixelColor(0, 0),
    targetImage.getPixelColor(tWidth, 0),
    targetImage.getPixelColor(tWidth, tHeight),
    targetImage.getPixelColor(0, tHeight),
  ];

  // 读取当前的截屏图片
  const image = await Jimp.read(srcImgPath);
  image.greyscale();
  const sWidth = image.bitmap.width;
  const sHeight = image.bitmap.height;
  
  // 开始从左上角开始扫描
  let matchedPoint: TapTargetType | null = null;
  for (const { x, y } of image.scanIterator(
    0,
    0,
    sWidth - tWidth,
    sHeight - tHeight
  )) {
    // 前四项是端点对比,通过层层验证后再进行像素差计算
    if (
      image.getPixelColor(x, y) === rect[0] &&
      image.getPixelColor(x + tWidth, y + tHeight) === rect[3] &&
      image.getPixelColor(x + tWidth, y) === rect[1] &&
      image.getPixelColor(x, y + tHeight) === rect[2] &&
      Jimp.diff(image.clone().crop(x, y, tWidth, tHeight), targetImage, 0.4).percent <
        0.15
    ) {
      matchedPoint = [x, y];
      break;
    }
  }

  return matchedPoint;
}

adb 触屏操作

查看 adb 输入命令的说明

adb shell input --help

点击某点

adb shell input tap <x> <y>

滑动屏幕

adb shell input swipe <x1> <y1> <x2> <y2> [duration]

回到上级

adb shell input keyevent 4

总结

简单实现,满足基本需求,点到为止。

完整代码:curly210102/meow-candy-collector: Cute meow candy automatic collector for 11.11 (github.com)