[原创] QQ群自动签到脚本

0x00 前言

许久未更博。

忽然想刷一下QQ的群聊等级,然后看了看升级的规则,有一项是群签到。

通过群签到每天最多获得15积分。

而群签到的“本群首签”会获得3积分,这样的话,自己准备5个群就能获得每天的积分了。

当然如果想更快提高群聊等级,还可以每天向某个群上传十个文件。

不过每天都运行这个脚本的话,还是比较麻烦的,所以直接弄成自动化的就好了。即把脚本挂到服务器上,设置成一个定时任务。 Nice~

脚本是我自己写的,许多地方其实还是可以优化的,比如加一些异常捕获机制,但是懒…就没去弄。

0x01 原理说明

基于python的requests库和selenium库来实现各功能。

通过抓包可以得知: Q群签到实际上是通过向URL ‘https://qun.qq.com/cgi-bin/qiandao/sign/publish' 进行post传参。

首先我们需要cookie中的两个主要部分:uin和skey

uin是根据QQ号来的,是不会变的,它有一个规律:

如果你QQ号小于等于9位,则自动在前面补0凑成10位,然后在最前面补一个小写字母’o’;如果你QQ号是10位的,则只进行在最前面补’o’的操作。

比如QQ号 123456789的uin 就是 o0123456789

QQ号1122334455的uin就是o1122334455

emmmm11位QQ的未进行测试,可自行测试。(登陆QQ空间看cookie即可)

而skey是随机生成的,一般以字符‘@’开头,总长度好像是10位?还是11位来着…..

这个也可以通过登陆QQ空间看cookie获取。

曾经试过一段时间,手动登陆QQ空间拿到skey,然后用这个skey跑签到脚本,它只能用两天…. 至于是24小时还是48小时我也没去仔细测…毕竟就算能用一星期,来回手动换也是很麻烦的,而我的目的是实现全自动。

而其他的一些参数

1
2
3
4
5
6
7
8
9
10
11
12
data = {
'bkn': bkn,#这是根据skey按照一个算法生成的,等下会介绍
"category_id": 9,#类别ID,这是Q群签到的一个选项来着...抓包拿到的是9,可以自行更改
"page": 0,#好像和其他几个有关联?
"pic_id": 124,#签到图片ID
'gc': gc,#QQ群号
'client': '2',#我也不知道,反正我抓包拿到的是2...应该能自行更改
'lgt': '0',#经度
'lat': '0',#纬度
'poi': '签到地点',#可以自行更改
'text': '你想说的话'#可以自行更改
}
1
2
3
4
5
def Getgtk(self):
hashes = 5381
for letter in self.skey:
hashes += (hashes << 5) + ord(letter)
return hashes & 0x7fffffff

这个就是通过skey来求上面说的bkn参数的算法,来源于百度…

经过测试,如果签到成功的话,打印出来的content为{"cgicode":0,"retcode":0,"msg":"","data":{}},所以可以用这个进行判断是否签到成功…当然直接看QQ也可以…

0x02 需要解决的几个困难

如何自动获取skey?

由于QQ空间登陆的时候有滑块验证,这里想用requests库来实现登陆并获取cookie并不太好弄。

这里选择先用selenium库实现QQ空间登陆,获取到cookie(主要是获取cookie里的skey)后直接让requests库利用。(毕竟skey的生存期至少是24小时)

非常幸运的是:QQ空间登陆的时候,滑块只需要拖动170到180之间某个像素位即可,这个值是随机的。

我们取中位数175,然后借助selenium的ActionChains来模拟拖动滑块。

因为是随机的,多试几次碰碰运气,总能成功的。

挂到服务器上执行脚本并没有效果且未报错

这个脚本早在几天前就写好了,但是挂到服务器上一直没有效果…

我一直在找原因,直到昨天,我决定给服务器加个桌面化,通过VNC进行连接服务器,以可视化的方式看看到底出了什么问题。

然后我发现,在拖动好滑块以后,还需要进行手机验证码验证…

大概是腾讯出于安全的考虑,毕竟在服务器登录也算是异地登录…

我尝试着进行了手机验证码验证,但是在下一次模拟登陆的时候依然是需要手机验证码登录…

而且我也检查了我QQ的各种登录保护,确定没开登陆地保护和设备锁之类的保护措施

无奈之下,我尝试了进行二维码扫码登录,这不需要手机验证码验证,但是这不符合我的全自动化要求。

多次尝试各种方法以后,依然需要手机验证码验证…

过了许久,突发奇想,我在服务器(ubuntu18.04)借助远程连接图形化界面下载一个Linux版本的QQ,登陆几次,会不会解决这个问题。

也可能是这个方法生效了,模拟登陆的时候不再需要手机验证码验证了。(o^_^o)

0x03 脚本源码

脚本所用python版本:2.7.17

脚本由UserInfo.py与test.py组成,懒得改名了,就这么命名吧。

其中UserInfo.py算是存放需要进行Q群签到的QQ的信息吧

结构如下

即 [ uin,QQ号,QQ密码,[需要签到的Q群list] ] 这种结构。

test.py内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# -*- coding: UTF-8 -*-
import requests
import time
from selenium import webdriver
from selenium.webdriver import ActionChains
from UserInfo import usersInfo

__Author__ = 'LiuLian'

class Sign:
def __init__(self,uin,Qnum,Qpsd,QqunList):
self.uin = uin
self.skey = ''
self.Qnum = Qnum
self.Qpsd = Qpsd
self.QqunList = QqunList

def Getskey(self):
count = 1
hasfound = False
while True:
print '[*]' + self.Qnum + ' 正在进行第' + str(count) +'次尝试...'
opt = webdriver.ChromeOptions()
opt.add_argument('--headless')
opt.add_argument('--no-sandbox')
driver = webdriver.Chrome(options=opt)
time.sleep(1)
driver.get('https://qzone.qq.com')
#print driver.page_source.encode('utf-8')
driver.switch_to.frame('login_frame')
a_tag = driver.find_element_by_id('switcher_plogin')
a_tag.click()
userName_tag = driver.find_element_by_id('u')
passWord_tag = driver.find_element_by_id('p')
time.sleep(1)
userName_tag.send_keys(self.Qnum)
time.sleep(1)
passWord_tag.send_keys(self.Qpsd)
btn = driver.find_element_by_id("login_button")
btn.click()
time.sleep(1)
iframe = driver.find_element_by_xpath('//*[@id="tcaptcha_iframe"]') # 找到“嵌套”的iframe
driver.switch_to.frame(iframe) # 切换到iframe
time.sleep(1)
button = driver.find_element_by_id('tcaptcha_drag_button') # 寻找滑块
time.sleep(1)
# 开始拖动 perform()用来执行ActionChains中存储的行为
distance = 175
action = ActionChains(driver)
action.reset_actions() # 清除之前的action
action.click_and_hold(button).perform() # click_and_hold 点击并保持
time.sleep(1)
action.move_by_offset(distance, 0).release().perform()
time.sleep(10)
cookies = driver.get_cookies()
for dic in cookies:
if dic.values()[1] == u'skey':
self.skey = dic.values()[2].encode("utf8", "ignore")
print '[+]' + self.Qnum + '的skey = ' + self.skey
hasfound = True
break
# 退出浏览器
driver.quit()
count += 1
if hasfound:
break
if count > 100:
print '[-] 失败太多次了! 终止..'
self.skey = '5555555555'
break

def Getgtk(self):
hashes = 5381
for letter in self.skey:
hashes += (hashes << 5) + ord(letter)
return hashes & 0x7fffffff

def sign(self):
self.Getskey()
url = 'https://qun.qq.com/cgi-bin/qiandao/sign/publish'
headers = {
'Host':'qun.qq.com',
'Origin':'https://qun.qq.com',
'User-Agent': 'Mozilla/5.0 (Linux; Android 9; Redmi Note 7 Pro Build/PKQ1.181203.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.120 MQQBrowser/6.2 TBS/045224 Mobile Safari/537.36 V1_AND_SQ_8.3.9_1424_YYB_D QQ/8.3.9.4635 NetType/4G WebP/0.3.0 Pixel/1080 StatusBarHeight/80 SimpleUISwitch/0 QQTheme/2081',
'Cookie': 'uin=' + self.uin + ';skey=' + self.skey
}
bkn = self.Getgtk()
for gc in self.QqunList:
data = {
'bkn': bkn,#这是根据skey按照一个算法生成的,等下会介绍
"category_id": 9,#类别ID,这是Q群签到的一个选项来着...抓包拿到的是9,可以自行更改
"page": 0,#好像和其他几个有关联?
"pic_id": 124,#签到图片ID
'gc': gc,#QQ群号
'client': '2',#我也不知道,反正我抓包拿到的是2...应该能自行更改
'lgt': '0',#经度
'lat': '0',#纬度
'poi': '签到地点',#可以自行更改
'text': '你想说的话'#可以自行更改
}

if requests.post(url=url, data=data, headers=headers).content \
== '{"cgicode":0,"retcode":0,"msg":"","data":{}}':
print '[+] ' + time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) \
+ 'QQ:' + self.uin[1:] + ' 群号:' + gc + '签到成功!'
else:
print '[-] ' + time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) \
+ 'QQ:' + self.uin[1:] + ' 群号:' + gc + '签到失败!'

if __name__ == '__main__':
for userInfo in usersInfo:
test = Sign(userInfo[0],userInfo[1],userInfo[2],userInfo[3])
test.sign()

0x04 挂载到服务器

要现在服务器安装好脚本所需的必要环境:python2.7、requests库、selenium库及其对应的google-chrome和chromedriver,具体不再多说。

设置定时任务:

这里采用linux的 crontab 。

阿里云的服务器自带…其他的服务器自带与否我也不清楚

执行sudo vim /etc/crontab来编辑设置定时任务

最后一行才是我自己设置的,其余全是服务器本身设置的。

最后一行表示每天早上6点以root权限执行该脚本,并将输出信息追加保存到我的log文件中,log文件自己创就行…保不保存其实都可以,看自己选择了。

这些路径根据自己的实际路径修改就行。

0x05 写在最后

懒惰是人类发展的第一生产力,如果我不是个懒狗,我也不会想着去写这个脚本。