diff --git a/QQBot.py b/QQBot.py
index b31a4ba..f438c62 100644
--- a/QQBot.py
+++ b/QQBot.py
@@ -21,18 +21,20 @@
PTWebQQ = ''
APPID = 0
msgId = 0
-FriendList = {}
ThreadList = []
GroupThreadList = []
GroupWatchList = []
GroupNameList = {}
+GroupCodeList = {}
PSessionID = ''
-Referer = 'http://d1.web2.qq.com/proxy.html?v=20151105001&callback=1&id=2'
+Referer = 'http://s.web2.qq.com/proxy.html?v=20130916001&callback=1&id=1'
+httpsReferer = 'https://d1.web2.qq.com/cfproxy.html?v=20151105001&callback=1'
SmartQQUrl = 'https://ui.ptlogin2.qq.com/cgi-bin/login?daid=164&target=self&style=16&mibao_css=m_webqq&appid=501004106&enable_qlogin=0&no_verifyimg=1&s_url=http%3A%2F%2Fw.qq.com%2Fproxy.html&f_url=loginerroralert&strong_login=1&login_state=10&t=20131024001'
VFWebQQ = ''
AdminQQ = '0'
MyUIN = ''
-tulingkey='#TURING KEY'
+tulingkey='#YOUR KEY HERE#'
+tmpUserName = ''
initTime = time.time()
@@ -57,6 +59,9 @@ def get_ts():
ts = int(ts)
return ts
+def CProcess(content):
+ return str(content.replace("\\", r"\\").replace("\n", r"\n").replace("\r", r"\r").replace("\t", r"\t").replace('"', r'\"'))
+
def getQRtoken(qrsig):
e = 0
for i in qrsig:
@@ -108,26 +113,6 @@ def date_to_millis(d):
return int(time.mktime(d.timetuple())) * 1000
-# 查询QQ号,通常首次用时0.2s,以后基本不耗时
-def uin_to_account(tuin):
- # 如果消息的发送者的真实QQ号码不在FriendList中,则自动去取得真实的QQ号码并保存到缓存中
- global FriendList
- if tuin not in FriendList:
- try:
- info = json.loads(HttpClient_Ist.Get('http://s.web2.qq.com/api/get_friend_uin2?tuin={0}&type=1&vfwebqq={1}'.format(tuin, VFWebQQ), Referer))
- logging.info("Get uin to account info:" + str(info))
- if info['retcode'] != 0:
- raise ValueError, info
- info = info['result']
- FriendList[tuin] = info['account']
-
- except Exception as e:
- logging.error(e)
-
- logging.info("Now FriendList:" + str(FriendList))
- return FriendList[tuin]
-
-
def msg_handler(msgObj):
for msg in msgObj:
msgType = msg['poll_type']
@@ -137,10 +122,9 @@ def msg_handler(msgObj):
txt = combine_msg(msg['value']['content'])
tuin = msg['value']['from_uin']
msg_id = msg['value']['msg_id']
- from_account = uin_to_account(tuin)
# print "{0}:{1}".format(from_account, txt)
- targetThread = thread_exist(from_account)
+ targetThread = thread_exist(tuin)
if targetThread:
targetThread.push(txt, msg_id)
else:
@@ -152,11 +136,7 @@ def msg_handler(msgObj):
isSess = 1
service_type = msg['value']['service_type']
myid = msg['value']['id']
- ts = time.time()
- while ts < 1000000000000:
- ts = ts * 10
- ts = int(ts)
- info = json.loads(HttpClient_Ist.Get('http://d1.web2.qq.com/channel/get_c2cmsg_sig2?id={0}&to_uin={1}&clientid={2}&psessionid={3}&service_type={4}&t={5}'.format(myid, tuin, ClientID, PSessionID, service_type, ts), Referer))
+ info = json.loads(HttpClient_Ist.Get('http://d1.web2.qq.com/channel/get_c2cmsg_sig2?id={0}&to_uin={1}&clientid={2}&psessionid={3}&service_type={4}&t={5}'.format(myid, tuin, ClientID, PSessionID, service_type, get_ts()), Referer))
logging.info("Get group sig:" + str(info))
if info['retcode'] != 0:
raise ValueError, info
@@ -185,10 +165,10 @@ def msg_handler(msgObj):
global GroupWatchList
txt = combine_msg(msg['value']['content'])
guin = msg['value']['from_uin']
- gid = msg['value']['group_code']
+ gid = GroupCodeList[int(guin)]
tuin = msg['value']['send_uin']
seq = msg['value']['msg_id']
- if str(gid) in GroupWatchList:
+ if str(guin) in GroupWatchList:
g_exist = group_thread_exist(gid)
if g_exist:
g_exist.handle(tuin, txt, seq)
@@ -201,9 +181,6 @@ def msg_handler(msgObj):
else:
logging.info(str(gid) + "群有动态,但是没有被监控")
- # from_account = uin_to_account(tuin)
- # print "{0}:{1}".format(from_account, txt)
-
# QQ号在另一个地方登陆, 被挤下线
if msgType == 'kick_message':
logging.error(msg['value']['reason'])
@@ -226,26 +203,26 @@ def combine_msg(content):
def send_msg(tuin, content, isSess, group_sig, service_type):
if isSess == 0:
- reqURL = "http://d1.web2.qq.com/channel/send_buddy_msg2"
+ reqURL = "https://d1.web2.qq.com/channel/send_buddy_msg2"
data = (
- ('r', '{{"to":{0}, "face":594, "content":"[\\"{4}\\", [\\"font\\", {{\\"name\\":\\"Arial\\", \\"size\\":\\"10\\", \\"style\\":[0, 0, 0], \\"color\\":\\"000000\\"}}]]", "clientid":{1}, "msg_id":{2}, "psessionid":"{3}"}}'.format(tuin, ClientID, msgId, PSessionID, str(content.replace("\\", "\\\\\\\\").replace("\n", "\\\\n").replace("\t", "\\\\t")).decode("utf-8"))),
+ ('r', '{{"to":{0}, "face":594, "content":"[\\"{4}\\", [\\"font\\", {{\\"name\\":\\"Arial\\", \\"size\\":\\"10\\", \\"style\\":[0, 0, 0], \\"color\\":\\"000000\\"}}]]", "clientid":{1}, "msg_id":{2}, "psessionid":"{3}"}}'.format(tuin, ClientID, msgId, PSessionID, CProcess(content))),
('clientid', ClientID),
('psessionid', PSessionID)
)
- rsp = HttpClient_Ist.Post(reqURL, data, Referer)
+ rsp = HttpClient_Ist.Post(reqURL, data, httpsReferer)
rspp = json.loads(rsp)
if rspp['errCode']!= 0:
logging.error("reply pmchat error"+str(rspp['errCode']))
else:
- reqURL = "http://d1.web2.qq.com/channel/send_sess_msg2"
+ reqURL = "https://d1.web2.qq.com/channel/send_sess_msg2"
data = (
- ('r', '{{"to":{0}, "face":594, "content":"[\\"{4}\\", [\\"font\\", {{\\"name\\":\\"Arial\\", \\"size\\":\\"10\\", \\"style\\":[0, 0, 0], \\"color\\":\\"000000\\"}}]]", "clientid":{1}, "msg_id":{2}, "psessionid":"{3}", "group_sig":"{5}", "service_type":{6}}}'.format(tuin, ClientID, msgId, PSessionID, str(content.replace("\\", "\\\\\\\\").replace("\n", "\\\\n").replace("\t", "\\\\t")).decode("utf-8"), group_sig, service_type)),
+ ('r', '{{"to":{0}, "face":594, "content":"[\\"{4}\\", [\\"font\\", {{\\"name\\":\\"Arial\\", \\"size\\":\\"10\\", \\"style\\":[0, 0, 0], \\"color\\":\\"000000\\"}}]]", "clientid":{1}, "msg_id":{2}, "psessionid":"{3}", "group_sig":"{5}", "service_type":{6}}}'.format(tuin, ClientID, msgId, PSessionID, CProcess(content), group_sig, service_type)),
('clientid', ClientID),
('psessionid', PSessionID),
('group_sig', group_sig),
('service_type',service_type)
)
- rsp = HttpClient_Ist.Post(reqURL, data, Referer)
+ rsp = HttpClient_Ist.Post(reqURL, data, httpsReferer)
rspp = json.loads(rsp)
if rspp['errCode']!= 0:
logging.error("reply temp pmchat error"+str(rspp['errCode']))
@@ -253,10 +230,10 @@ def send_msg(tuin, content, isSess, group_sig, service_type):
return rsp
-def thread_exist(tqq):
+def thread_exist(tuin):
for t in ThreadList:
if t.isAlive():
- if t.tqq == tqq:
+ if t.tuin == tuin:
t.check()
return t
else:
@@ -279,7 +256,7 @@ class Login(HttpClient):
MaxTryTime = 5
def __init__(self, vpath, qq=0):
- global APPID, AdminQQ, PTWebQQ, VFWebQQ, PSessionID, msgId, MyUIN, GroupNameList
+ global APPID, AdminQQ, PTWebQQ, VFWebQQ, PSessionID, msgId, MyUIN, GroupNameList, tmpUserName, GroupCodeList
self.VPath = vpath # QRCode保存路径
AdminQQ = int(qq)
logging.critical("正在获取登陆页面")
@@ -345,9 +322,9 @@ def __init__(self, vpath, qq=0):
try:
html = self.Post('http://d1.web2.qq.com/channel/login2', {
'r': '{{"ptwebqq":"{0}","clientid":{1},"psessionid":"{2}","status":"online"}}'.format(PTWebQQ, ClientID, PSessionID)
- }, Referer)
+ }, 'http://d1.web2.qq.com/proxy.html?v=20151105001&callback=1&id=2')
ret = json.loads(html)
- html2 = self.Get("http://s.web2.qq.com/api/getvfwebqq?ptwebqq={0}&clientid={1}&psessionid={2}&t={3}".format(PTWebQQ, ClientID, PSessionID, get_ts()), 'http://s.web2.qq.com/proxy.html?v=20130916001&callback=1&id=1')
+ html2 = self.Get("http://s.web2.qq.com/api/getvfwebqq?ptwebqq={0}&clientid={1}&psessionid={2}&t={3}".format(PTWebQQ, ClientID, PSessionID, get_ts()), Referer)
logging.info("getvfwebqq html: " + str(html2))
ret2 = json.loads(html2)
LoginError = 0
@@ -369,12 +346,14 @@ def __init__(self, vpath, qq=0):
msgId = int(random.uniform(20000, 50000))
html = self.Post('http://s.web2.qq.com/api/get_group_name_list_mask2', {
'r': '{{"vfwebqq":"{0}","hash":"{1}"}}'.format(str(VFWebQQ),gethash(str(MyUIN),str(PTWebQQ)))
- }, 'http://s.web2.qq.com/proxy.html?v=20130916001&callback=1&id=1')
+ }, Referer)
ret = json.loads(html)
if ret['retcode']!= 0:
raise ValueError, "retcode error when getting group list: retcode="+str(ret['retcode'])
for t in ret['result']['gnamelist']:
GroupNameList[str(t["name"])]=t["gid"]
+ GroupCodeList[int(t["gid"])]=int(t["code"])
+ self.Get('http://d1.web2.qq.com/channel/get_online_buddies2?vfwebqq={0}&clientid={1}&psessionid={2}&t={3}'.format(VFWebQQ,ClientID,PSessionID,get_ts()),Referer)
class check_msg(threading.Thread):
# try:
@@ -441,15 +420,16 @@ def run(self):
# Other retcode e.g.: 103
E += 1
+ HttpClient_Ist.Get('http://d1.web2.qq.com/channel/get_online_buddies2?vfwebqq={0}&clientid={1}&psessionid={2}&t={3}'.format(VFWebQQ,ClientID,PSessionID,get_ts()),Referer)
logging.critical("轮询错误超过五次")
# 向服务器查询新消息
def check(self):
- html = HttpClient_Ist.Post('http://d1.web2.qq.com/channel/poll2', {
+ html = HttpClient_Ist.Post('https://d1.web2.qq.com/channel/poll2', {
'r': '{{"ptwebqq":"{1}","clientid":{2},"psessionid":"{0}","key":""}}'.format(PSessionID, PTWebQQ, ClientID)
- }, Referer)
+ }, httpsReferer)
logging.info("Check html: " + str(html))
try:
ret = json.loads(html)
@@ -473,11 +453,10 @@ def __init__(self, tuin, isSess, group_sig, service_type):
self.isSess = isSess
self.group_sig=group_sig
self.service_type=service_type
- self.tqq = uin_to_account(tuin)
self.lastcheck = time.time()
self.lastseq=0
self.replystreak = 0
- logging.info("私聊线程生成,私聊对象:"+str(self.tqq))
+ logging.info("私聊线程生成,私聊对象UIN:"+str(self.tuin))
def check(self):
self.lastcheck = time.time()
def run(self):
@@ -488,7 +467,7 @@ def run(self):
def reply(self, content):
send_msg(self.tuin, str(content), self.isSess, self.group_sig, self.service_type)
- logging.info("Reply to " + str(self.tqq) + ":" + str(content))
+ logging.info("Reply to " + str(self.tuin) + ":" + str(content))
def push(self, ipContent, seq):
if seq == self.lastseq:
@@ -502,7 +481,7 @@ def push(self, ipContent, seq):
try:
self.replystreak = self.replystreak + 1
logging.info("PM get info from AI: "+ipContent)
- paraf={ 'userid' : str(self.tqq), 'key' : tulingkey, 'info' : ipContent}
+ paraf={ 'userid' : str(self.tuin), 'key' : tulingkey, 'info' : ipContent}
info = HttpClient_Ist.Get('http://www.tuling123.com/openapi/api?'+urllib.urlencode(paraf))
logging.info("AI REPLY:"+str(info))
info = json.loads(info)
@@ -527,9 +506,10 @@ class group_thread(threading.Thread):
lastseq = 0
replyList = {}
followList = []
+ NickList = {}
# 属性
- repeatPicture = False
+ repeatPicture = True
def __init__(self, guin, gcode):
threading.Thread.__init__(self)
@@ -537,6 +517,10 @@ def __init__(self, guin, gcode):
self.gid = gcode
self.load()
self.lastreplytime=0
+ ret = HttpClient_Ist.Get('http://s.web2.qq.com/api/get_group_info_ext2?gcode={0}&vfwebqq={1}&t={2}'.format(gcode,VFWebQQ,get_ts()),Referer)
+ ret = json.loads(ret)
+ for t in ret['result']['minfo']:
+ self.NickList[str(t["nick"])]=int(t["uin"])
def learn(self, key, value, needreply=True):
if key in self.replyList:
@@ -564,14 +548,14 @@ def reply(self, content):
logging.info("REPLY TOO FAST, ABANDON:"+content)
return False
self.lastreplytime = time.time()
- reqURL = "http://d1.web2.qq.com/channel/send_qun_msg2"
+ reqURL = "https://d1.web2.qq.com/channel/send_qun_msg2"
data = (
- ('r', '{{"group_uin":{0}, "face":564,"content":"[\\"{4}\\",[\\"font\\",{{\\"name\\":\\"Arial\\",\\"size\\":\\"10\\",\\"style\\":[0,0,0],\\"color\\":\\"000000\\"}}]]","clientid":{1},"msg_id":{2},"psessionid":"{3}"}}'.format(self.guin, ClientID, msgId, PSessionID, str(content.replace("\\", "\\\\\\\\").replace("\n", "\\\\n").replace("\t", "\\\\t")).decode("utf-8"))),
+ ('r', '{{"group_uin":{0}, "face":564,"content":"[\\"{4}\\",[\\"font\\",{{\\"name\\":\\"Arial\\",\\"size\\":\\"10\\",\\"style\\":[0,0,0],\\"color\\":\\"000000\\"}}]]","clientid":{1},"msg_id":{2},"psessionid":"{3}"}}'.format(self.guin, ClientID, msgId, PSessionID, CProcess(content))),
('clientid', ClientID),
('psessionid', PSessionID)
)
logging.info("Reply package: " + str(data))
- rsp = HttpClient_Ist.Post(reqURL, data, Referer)
+ rsp = HttpClient_Ist.Post(reqURL, data, httpsReferer)
try:
rspp = json.loads(rsp)
if rspp['errCode'] == 0:
@@ -638,24 +622,31 @@ def repeat(self, content):
return False
def follow(self, send_uin, content):
- pattern = re.compile(r'^(?:!|!)(follow|unfollow) (\d+|me)')
+ pattern = re.compile(r'^(?:!|!)(follow|unfollow) (.*)!')
match = pattern.match(content)
if match:
- target = str(match.group(2))
- if target == 'me':
- target = str(uin_to_account(send_uin))
+ target1 = str(match.group(2))
+ if target1 == 'me':
+ target = send_uin
+ target1 = '你'
+ else:
+ if target1 in self.NickList:
+ target = self.NickList[target1]
+ else:
+ self.reply("找不到成员:"+target1)
+ return True
if match.group(1) == 'follow' and target not in self.followList:
self.followList.append(target)
- self.reply("正在关注" + target)
+ self.reply("正在关注" + target1)
return True
if match.group(1) == 'unfollow' and target in self.followList:
self.followList.remove(target)
- self.reply("我不关注" + target + "了!")
+ self.reply("我不关注" + target1 + "了!")
return True
else:
- if str(uin_to_account(send_uin)) in self.followList:
+ if send_uin in self.followList:
self.reply(content)
return True
return False
@@ -683,7 +674,7 @@ def callout(self, send_uin, content):
try:
if match:
logging.info("get info from AI: "+str(match.group(2)).decode('UTF-8'))
- usr = str(uin_to_account(send_uin))
+ usr = str(send_uin)
paraf={ 'userid' : usr+'g', 'key' : tulingkey, 'info' : str(match.group(2)).decode('UTF-8')}
info = HttpClient_Ist.Get('http://www.tuling123.com/openapi/api?'+urllib.urlencode(paraf))
@@ -708,7 +699,7 @@ def aboutme(self, content):
try:
if match:
logging.info("output about info")
- info="小黄鸡3.3 By Jeffery, 源代码:(github.com/zeruniverse/QQRobot)\n使用语法: (按优先级排序,若同时触发则只按优先级最高的类型回复。注意所有!均为半角符号,即英文!)\n\n1.帮助(关于),输入!about,样例:\n!about\n\n2.智能鸡:输入!ai (空格)+问题,小黄鸡自动回复,举例:\n!ai 你是谁?\n\n3.随从鸡:输入!follow QQ号,小黄鸡会重复发送该QQ号所有发送内容,如对自己使用可以直接使用!follow me,举例:\n!follow 123456789\n!follow me\n取消复读则输入!unfollow QQ(或me),举例:\n!unfollow 123456789\n!unfollow me\n\n4.学习鸡:使用!learn {A}{B}命令让小黄鸡学习,以后有人说A的时候小黄鸡会自动说B。!learn后面有空格,全部符号均为半角(英文),例如\n!learn {你是谁}{我是小黄鸡}\n删除该记录则\n!delete {你是谁}{我是小黄鸡}\n一次删除所有记录使用:\n!deleteall\n\n6.复读鸡:当群里连着两次出现同样信息时复读一遍\n\n\n私戳小黄鸡可以私聊,私聊无格式,全部当智能鸡模式处理。"
+ info="小黄鸡3.8 By Jeffery 详细说明见github.com/zeruniverse/QQRobot"
self.reply(info)
return True
except Exception, e:
diff --git a/README.md b/README.md
index 8b15a0d..fd4a22e 100644
--- a/README.md
+++ b/README.md
@@ -26,13 +26,13 @@ This project is a chatting robot in QQ, implemented in Python. The robot uses Ar
登陆时采用QQ安全中心的二维码做为登陆条件, 不需要在程序里输入QQ号码及QQ密码。QQ自动回复私聊(无群聊功能)及留言邮件提醒版本请看[这里](https://github.com/zeruniverse/QQParking)
-##RELEASE
-3.6 (带预配置文件):[点击下载](https://github.com/zeruniverse/QQRobot/releases/tag/3.6)
+## RELEASE
+最新RELEASE:[点击下载](https://github.com/zeruniverse/QQRobot/releases/latest)
~~WINDOWS EXE 32位: [点击下载](https://github.com/zeruniverse/QQRobot/releases/tag/w1.3)~~
目前只有master分支持续更新。建议WINDOWS用户使用技术门槛更低的[QBotWrap](https://github.com/zeruniverse/QBotWebWrap)
-##如何使用
-+ 从http://www.tuling123.com/openapi/ 申请一个API KEY(免费,5000次/天), 贴到```QQBot.py```的第34行 (测试KEY:c7c5abbc9ec9cad3a63bde71d17e3c2c)
+## 如何使用
++ 从http://www.tuling123.com/openapi/ 申请一个API KEY(免费,5000次/天), 贴到```QQBot.py```的第36行 (测试KEY:c7c5abbc9ec9cad3a63bde71d17e3c2c)
+ 修改groupfollow.txt,将需要小黄鸡回复的群的群名写入(小黄鸡必须为群成员),每行一个群名,请不要打多余的空格。(新版WEBQQ已移除获取群号的接口,输入中文群名请务必使用UTF-8编码)
+ ```nohup python2 QQBot.py >qbot.log&```
+ ```ls```
@@ -42,16 +42,16 @@ This project is a chatting robot in QQ, implemented in Python. The robot uses Ar
+ 据反馈此AI平台回复中带有少量广告。。。(如问iphone6价格回复小米799)
-##功能
-注:以下命令皆是在qq中发送,群聊命令发送到所在群中
+## 功能
+注:以下命令皆是在QQ中发送,群聊命令发送到所在群中
+ 关于及帮助,在群聊中发送```!about```
-+ 群聊智能回复,小黄鸡,在群中通过发送```!ai 问题```语句,则机器人向AI平台请求```问题```的回复并回复到群,带有!ai关键字时优先触发此功能
++ 群聊智能回复,在群中通过发送```!ai 问题```语句,则机器人向AI平台请求```问题```的回复并回复到群,带有!ai关键字时优先触发此功能
-+ 私聊智能回复,小黄鸡,对于收到的私聊,机器人向AI平台请求该聊天记录的回复并回复给消息发送者
++ 私聊智能回复,对于收到的私聊,机器人向AI平台请求该聊天记录的回复并回复给消息发送者
-+ 群聊学习功能,类似于小黄鸡,在群中通过发送```!learn {ha}{哈哈}```语句,则机器人检测到发言中包含“ha”时将自动回复“哈哈”。```!delete {ha}{哈哈}```可以删除该内容。学习内容会自动储存在```database.群号.save```文件。```!deleteall```可删除该群所有记录。
++ 群聊学习功能,类似于小黄鸡,在群中通过发送```!learn {ha}{哈哈}```语句,则机器人检测到发言中包含“ha”时将自动回复“哈哈”。```!delete {ha}{哈哈}```可以删除该内容。学习内容会自动储存在```database.群号.save```文件。```!deleteall```可删除该群所有记录。注意`learn`和`{`之间有空格,`{}`与`{}`之间没有。
+ 群聊复读功能,检测到群聊中***连续两个***回复内容相同,将自动复读该内容1次。