Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
116 lines (88 sloc) 6.55 KB
title tags categories date
抢红包机器人开发过程
Python
QQ
作品
黑客精神
网络
2016-02-08 04:35:05 -0800

又到了红包雨的季节,在经过了除夕上午“错过一瞬间,损失几个亿”的紧张之后,我终于意识到曾经一度被红包所支配的恐惧,以及被囚禁于手机旁的那份屈辱……为了将双手解放出来,除夕下午紧急想办法开发抢红包机器人,现在一切都过去了,回顾一下整个经历。

上次做[QQ机器人](/QQBot)是采用了Python脚本通过扫二维码登录[SmartQQ](http://w.qq.com)。这次因为目的是抢红包,这套思路就没用了——QQ红包必须通过手机应用才能抢,其他平台都不行。于是就剩下了两种思路:运行手机QQ并且向它发送点击事件,或者搞明白手机QQ的网络通信协议,写一个程序伪装成手机QQ。后者由于SSL的存在,是很难抓包实现的,而逆向工程的工作量也太大了,不符合这个项目能产生的收入(笑)。因此决定使用电脑上的安卓模拟器+按键精灵来实现。

以前曾经体验过按键精灵,觉得自己真正需要的功能其实只是三个函数:移动鼠标到某处,点击鼠标,检测屏幕某处的颜色。因此虽然不精通按键精灵编程,但总是凑合能写出程序的。安装安卓模拟器Windroye和按键精灵后,我发现SandboxIE沙盒环境阻止了它控制其他窗口,而我又不愿意信任它,把它拿出沙盒。遂换用Python代替按键精灵。

首先,搜索“Python 控制鼠标”,找到线索:需要安装pywin32库。然后为了符合自己使用习惯,我用网上的代码包装出了这个库:

import win32api, win32con, win32gui
from ctypes import *

moveTo = windll.user32.SetCursorPos

getPos = win32gui.GetCursorPos

def click(x, y):
    moveTo(x, y)
    win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN, x, y)
    win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP, x, y)

hdc = windll.user32.GetDC(None)
def getPixel(x, y):
    return windll.gdi32.GetPixel(hdc, x, y)

然后思考抢红包逻辑:因为同一时间手机QQ只能在一个聊天窗口里,所以为了能抢到每个窗口的红包,程序应该一般把手机QQ放在消息列表的界面上,检测表示有新消息的那个红点。点进每个有新消息的聊天检查是否有红包,如果有就执行抢红包的过程后返回消息列表,如果没有直接返回。红包的特点是无论哪一种皮肤,背景色都是红的,而且颜色的数值是精确地相等的,因此可以检测颜色来判断。点了红包后有两种可能性,一种是弹出告诉你成功或者失败的窗口,要点叉关闭;另一种是聊天框上出现了口令,需要点击来输入,然后发送,才会弹出那个窗口。这两种可能性可以通过检测左下角有没有口令来区分。

因此,我首先运行了以下程序,它每隔一秒打印出当前鼠标的坐标和所指向的点的颜色,来获取我需要的各种坐标位置和颜色数值:

import time
import pymouse

while True:
    time.sleep(1)
    pos = pymouse.getPos()
    print(str(pos) + ': ' + hex(pymouse.getPixel(*pos)))

对于消息列表,程序需要关注的是所有的“红点”的位置(其实也有蓝点)为了避免红点上的白色数字的干扰,我将检测点选在了最右端。如果颜色是红点或蓝点的颜色,就要点击左侧的位置来进入聊天窗口。

消息列表

在聊天窗口中,重要的位置是能检测到红包底色的位置。为了避免图片(没错!就是你们发的那些“发作业啦”、“发坦克啦”、“发续命包啦”)的干扰,检测点也尽量靠边。安排了一列检测点是因为有时口令红包发出后迅速被大家的口令“刷上去”了。另外,返回按钮也定位了。

消息窗口

一旦打开了红包,就要点叉关闭这个窗口:

普通红包

唯有一种特殊情况是口令红包。在点击红包后要判断左下角那个点的颜色是不是口令提示的底色,如果是就点击输入口令,再发送:

口令红包

最终写出的代码并不复杂:

import time
import pymouse

SEND = (363, 634)
TEST = (186, 484)
Ys = [484, 440, 400, 360, 320, 280, 240]
INPUT = (45, 594)
CLOSE = (309, 219)
BACK = (42, 75)
DOTSX = 378
ENTERX = 270
DOTSY = [166, 220, 274, 328, 382, 436, 490, 544, 598]
BLUEDOT = 0xf0d796
REDDOT = 0x314cf7
RED = 0x9e95de
MCOLOR = 0xa5ddff
MTEST = (52, 610)

count = 0
while True:
    time.sleep(0.5)
    found = False
    for y in DOTSY:
        if pymouse.getPixel(DOTSX, y) in (BLUEDOT, REDDOT):
            pymouse.click(ENTERX, y)
            found = True
            break
    if found:
        time.sleep(0.7)
        for y in Ys:
            if pymouse.getPixel(TEST[0], y) == RED:
                count += 1
                print('wow!  ' + str(count))
                pymouse.click(TEST[0], y)
                time.sleep(2)
                if pymouse.getPixel(*MTEST) == MCOLOR:
                    print('entering')
                    pymouse.click(*MTEST)
                    time.sleep(0.5)
                    pymouse.click(*SEND)
                    time.sleep(2)
                pymouse.click(*CLOSE)
                time.sleep(0.5)
                break
        pymouse.click(*BACK)

目前,这个程序仍然存在的主要问题有两个:一是它运行时我无法使用电脑,因为鼠标被占用,也无法用手机登录QQ,因为同时只能有一个手机在线。

二是如果在某群抢到了一个红包,过一会那个群有新消息,机器人进入后会看到刚刚自己已经抢过的红包。按照QQ的逻辑,点击抢过的红包,会跳转到“红包记录”的界面(而不是预期的弹出小窗口)。接下来,原本点击叉的动作不起任何作用,点击返回按钮的动作会退出这个界面,回到聊天界面。程序会永远卡在这里,检测不到“红点”而什么都不做。必须人工再次点击返回按钮,才能回到聊天列表界面,让程序恢复工作。