一:轮询,长轮询,WebSocket了解
轮询:
在前端,设置时间内,一直向后端发送请求。
例如:使用setInterval方法设置定时器,一秒向后端发送一次请求,去主动获取数据,进行更新
由于前端一直请求,后端压力太大。而且当没有数据更新,前端一直去请求,太浪费了,没必要。
代码简单
长轮询:
在轮询的基础上,加以改造。Http请求到来,若是不主动close或者return,则连接会一直存在。但是不要让这个时间太长,会占用太多资源
例如:当前端发送请求,后端拿到后,不去关闭,而是等待一段时间,在这段时间内若是有数据到达,立刻返回,否则直到等待时间结束。
然后返回给前端,前端马上又发起一次请求......
消息是实时获取。
WebSocket:
http是单向请求,客户端去服务端获取数据。服务端不能主动推送消息。
而websocket类似于socket,可以实现双向发送,
实现当数据更新,可以主动推送
二:web微信流程介绍
三:微信登录开发
from django.shortcuts import render,HttpResponse from bs4 import BeautifulSoup import requests import time,re,json CTIME = None #用于保存全局时间戳 QCODE = None #当我们访问二维码时,会产生一个UUID,我们将其存放为全局 TIP = 1 #url中的一个参数tip,当其为1:代表我们还没有扫描二维码,当其为0:扫描了二维码
登录视图login,用于显示二维码
def login(request): global CTIME global QCODE CTIME = int(time.time()) data = { 'appid':'wx782c26e4c19acffb', 'fun':'new', 'lang':'zh_CN', '_':CTIME } response = requests.get( url="https://login.wx.qq.com/jslogin", params=data ) pat_res = re.findall('uuid = "(.*)";',response.text) #正则匹配UUID QCODE = pat_res[0] return render(request,"login.html",{'qcode':QCODE})
check_login用于检测登录状态:408未扫描,201扫描二维码但是未登录,200点击登录
def check_login(request): global TIP ret = {'code':408,'data':None} data = { 'loginicon':"true", 'uuid':QCODE, 'tip':TIP, 'r':'-577317906', '_':int(time.time()) } r1 = requests.get( url='https://login.wx.qq.com/cgi-bin/mmwebwx-bin/login', params=data ) if 'window.code=408' in r1.text: print("无人扫描") return HttpResponse(json.dumps(ret)) elif 'window.code=201' in r1.text: ret['code'] = 201 pat_ret = re.findall("window.userAvatar = '(.*)';",r1.text)[0] ret['data'] = pat_ret TIP = 0 return HttpResponse(json.dumps(ret)) elif 'window.code=200;' in r1.text: ret['code'] = 200 redirect_url = re.findall('window.redirect_uri="(.*)";',r1.text)[0] reponse = requests.get( url=redirect_url+"&fun=new&version=v2" #url不够完整,需要我们完善 ) # print(reponse.text) #<error><ret>0</ret><message></message><skey>@crypt_7358fe11_af06754907ad9c216768337d80cf0ce7</skey><wxsid>icUySQoySDi2OZFK</wxsid><wxuin>2821071261</wxuin><pass_ticket>IWScm1SE%2BGQ%2BNEaghUBCxbF3xPJSzqXUGTO6BYh3TBEGlw8Wa7qETkA9EEAUudYU</pass_ticket><isgrayscale>1</isgrayscale></error> soup = BeautifulSoup(reponse.text,"lxml") info_dict = {} for tag in soup.find("error").children: info_dict[tag.name]=tag.get_text() get_user_info_url = 'https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxinit?r=-613135321&pass_ticket='+info_dict['pass_ticket'] get_user_info_form = { 'BaseRequest': { 'DeviceID':"e055319847811019", 'Sid':info_dict['wxsid'], 'Skey':info_dict['skey'], 'Uin':info_dict['wxuin'] } } reponse2 = requests.post( #获取的是用户信息,最近联系人,公众号,自己信息 url=get_user_info_url, json=get_user_info_form, #注意这里使用的是json,post不允许传送字典 ) reponse2.encoding = "utf-8" print(reponse2.text) return HttpResponse("OK")
''' 新请求 GET 获取跳转地址redirect_uri https://login.wx.qq.com/cgi-bin/mmwebwx-bin/login? loginicon=true &uuid=QfsKELYXow== &tip=0 &r=-613406501 &_=1529621492415 --------------------------------------------------------- window.code=200; window.redirect_uri=" https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxnewloginpage? ticket=ASWg1dxC1oWVbJtZH8V-HhlB@qrticket_0 &uuid=QfsKELYXow== &lang=zh_CN &scan=1529621533"; 新请求 GET 获取凭证pass_ticket 服务端开始设置了cookie,说明在后面的请求中需要携带cookie https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxnewloginpage? ticket=ASWg1dxC1oWVbJtZH8V-HhlB@qrticket_0 &uuid=QfsKELYXow== &lang=zh_CN &scan=1529621533 &fun=new &version=v2 ----------------------------------------------------------------- <error> <ret>0</ret> <message></message> <skey>@crypt_7358fe11_ea821d506c39f7d75a3e83b4233caab4</skey> <wxsid>qUJZlkBIWQ0130QI</wxsid> <wxuin>2821071261</wxuin> <pass_ticket>xNiKeCBgFkMBfEK8oOK3Gp9qj%2F1HfLpcfPrDwGv3A4nltKskVqoxkECrVYEN9eJJ</pass_ticket> <isgrayscale>1</isgrayscale> </error> 新请求:获取用户所有信息,最近联系人和公众号 POST 需要携带数据,数据来自于上面凭证中 https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxinit? r=-613135321 &pass_ticket=xNiKeCBgFkMBfEK8oOK3Gp9qj%252F1HfLpcfPrDwGv3A4nltKskVqoxkECrVYEN9eJJ 数据 { BaseRequest: { DeviceID:"e055319847811019" Sid:"pY7nfHplUAsBOINz" Skey:"@crypt_7358fe11_e0ae163bd19650bea336df66837e9f7a" Uin:"2821071261" } } -------------------------------------------------------------------- { "BaseResponse": { "Ret": 0, "ErrMsg": "" } , "Count": 9, "ContactList": [{ "Uin": 0, "UserName": "filehelper", "NickName": "æ–‡ä»¶ä¼ è¾“åŠ©æ‰‹", "HeadImgUrl": "/cgi-bin/mmwebwx-bin/webwxgeticon?seq=660872310&username=filehelper&skey=@crypt_7358fe11_ea821d506c39f7d75a3e83b4233caab4", "ContactFlag": 2, "MemberCount": 0, "MemberList": [], "RemarkName": "", "HideInputBarFlag": 0, "Sex": 0, "Signature": "", "VerifyFlag": 0, "OwnerUin": 0, "PYInitial": "WJCSZS", "PYQuanPin": "wenjianchuanshuzhushou", "RemarkPYInitial": "", "RemarkPYQuanPin": "", "StarFriend": 0, "AppAccountFlag": 0, "Statues": 0, "AttrStatus": 0, "Province": "", "City": "", "Alias": "", "SnsFlag": 0, "UniFriend": 0, "DisplayName": "", "ChatRoomId": 0, "KeyWord": "fil", "EncryChatRoomId": "", "IsOwner": 0 },还有其他的] } 新请求 https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxinit? r=-613135321 &pass_ticket=xNiKeCBgFkMBfEK8oOK3Gp9qj%252F1HfLpcfPrDwGv3A4nltKskVqoxkECrVYEN9eJJ 新请求 GET 获取所有联系人和公众号 https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxgetcontact? lang=zh_CN &pass_ticket=gWbCT8vTjFeFKXDvfJZ6DtMtHo5d8zzhtLgLoybILn7eeTNSMI4BErA7e9otuPXQ &r=1529642330650 &seq=0 &skey=@crypt_7358fe11_9dc260b8cffb962a3e475ca50e7813c9 ----------------------------------------------------------------------------- { "BaseResponse": { "Ret": 0, "ErrMsg": "" } , "MemberCount": 162, "MemberList": [{ "Uin": 0, "UserName": "@39ef4d4197e9a7388e41fc9de150b3e28bf125082f1e442822814dec4803c6a0", "NickName": "å®é™è‡´è¿œ", "HeadImgUrl": "/cgi-bin/mmwebwx-bin/webwxgeticon?seq=0&username=@39ef4d4197e9a7388e41fc9de150b3e28bf125082f1e442822814dec4803c6a0&skey=@crypt_7358fe11_9dc260b8cffb962a3e475ca50e7813c9", "ContactFlag": 1, "MemberCount": 0, "MemberList": [], "RemarkName": "", "HideInputBarFlag": 0, "Sex": 1, "Signature": "凶巴巴呛è´è´", "VerifyFlag": 0, "OwnerUin": 0, "PYInitial": "NJZY", "PYQuanPin": "ningjingzhiyuan", "RemarkPYInitial": "", "RemarkPYQuanPin": "", "StarFriend": 0, "AppAccountFlag": 0, "Statues": 0, "AttrStatus": 4197, "Province": "æ²³å—", "City": "郑州", "Alias": "", "SnsFlag": 17, "UniFriend": 0, "DisplayName": "", "ChatRoomId": 0, "KeyWord": "", "EncryChatRoomId": "", "IsOwner": 0 }, 还有其他 ] '''
前端代码:显示二维码和头像
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <img id="qrcode" style=" 340px;height: 340px;" src="https://login.weixin.qq.com/qrcode/{{ qcode }}" alt=""> </body> </html> <script src="/static/jquery.js"></script> <script> $(function(){ checkLogin(); }) function checkLogin() { $.ajax({ url:'/check-login.html', type:'GET', dataType:"json", success:function(data){ console.log(data.code); if (data.code==408){ checkLogin(); } else if(data.code==201){ $("#qrcode").attr('src',data.data) checkLogin(); } } }) } </script>
测试返回的最近联系人和公众号信息
user_dict = {} for item in user_dict.items(): print(item) for item in user_dict['ContactList']: #最近联系人 print(item['PYQuanPin'],item['NickName']) for item in user_dict['MPSubscribeMsgList']: #公众号和推送消息 print(item['UserName'],item['NickName']) for item2 in item['MPArticleList']: print(item2['Title'],item2['Cover'],item2['Digest'],item2['Url'])
('ClientVersion', 637929271) ('GrayScale', 1) ('Count', 10) ('SystemTime', 1529661118) ('MPSubscribeMsgList:公众号列表,含有文章推送等信息', [{'UserName': '@393d71e59f81ac2feca148e8e269c0df', 'MPArticleList': [{'Title': '', 'Digest': '', 'Url': '', 'Cover': '图片'}, ], 'MPArticleCount': 2, 'Time': 1529651105, 'NickName': '人工智能头条'},]) ('ChatSet', 'filehelper,@@7c7137978e7349eac97453fa2adc290df295eaba3e7981e07ff85111f94a403c,weixin,@0fdf14d27dc0b2d34d013329ec498aae6284dbc340bdfbd8741227a72b1b3fa4,@393d71e59f81ac2feca148e8e269c0df,@@4d7d0c68e8445a6d69a5e3a2415c57c8f46858724cfdef8618b5790094e5de37,@@6b45638d8a8394a5bea103bd55ef49ce69dad73b8eda94dd5f63a126ec0e6ee4,@@d6c45082c0686e0cef729f3cb20db704b381b2aef67fe0a8a82151869220c8ef,@@03e7d8c59c30bb81dc0f2dc683b8e7a6f4a707f2aac6655c6e5036c349a96fe3,@02bf3be3c826bc38d4461d3ee52704e8,') ('MPSubscribeMsgCount:最近推送的公众号数目', 2) ('BaseResponse', {'ErrMsg': '', 'Ret': 0}) ('SKey', '@crypt_7358fe11_08012eadffc70f5c3189f802236830be') ('ClickReportInterval', 600000) ('InviteStartCount', 40) ('User:用户自己的信息', {'VerifyFlag': 0, 'HeadImgFlag': 1, 'Uin': 2821071261, 'NickName': '宁静致远', 'AppAccountFlag': 0, 'UserName': '@c959c389ab390d9f71d3f528f5a4ee1e81d6c8cd4aaf48d8b1f0077073660c5c', 'HeadImgUrl': '/cgi-bin/mmwebwx-bin/webwxgeticon?seq=1753775271&username=@c959c389ab390d9f71d3f528f5a4ee1e81d6c8cd4aaf48d8b1f0077073660c5c&skey=@crypt_7358fe11_08012eadffc70f5c3189f802236830be', 'ContactFlag': 0, 'RemarkPYInitial': '', 'SnsFlag': 17, 'PYQuanPin': '', 'WebWxPluginSwitch': 0, 'HideInputBarFlag': 0, 'RemarkPYQuanPin': '', 'Signature': '凶巴巴呛贝贝', 'Sex': 1, 'StarFriend': 0, 'PYInitial': '', 'RemarkName': ''}) ('ContactList:最近联系人信息', [ {'VerifyFlag': 0, 'Uin': 0, 'Signature': '', 'AppAccountFlag': 0, 'HeadImgUrl': '/cgi-bin/mmwebwx-bin/webwxgeticon?seq=660872310&username=filehelper&skey=@crypt_7358fe11_08012eadffc70f5c3189f802236830be', 'PYInitial': 'WJCSZS', 'Province': '', 'PYQuanPin': 'wenjianchuanshuzhushou', 'DisplayName': '', 'RemarkName': '', 'IsOwner': 0, 'Sex': 0, 'EncryChatRoomId': '', 'KeyWord': 'fil', 'City': '', 'ChatRoomId': 0, 'RemarkPYQuanPin': '', 'Alias': '', 'UniFriend': 0, 'UserName': 'filehelper', 'MemberCount': 0, 'ContactFlag': 2, 'RemarkPYInitial': '', 'Statues': 0, 'AttrStatus': 0, 'SnsFlag': 0, 'HideInputBarFlag': 0, 'NickName': '文件传输助手', 'OwnerUin': 0, 'StarFriend': 0, 'MemberList': []},]) ('SyncKey', {'List': [{'Key': 1, 'Val': 677540039}, {'Key': 2, 'Val': 677540219}, {'Key': 3, 'Val': 677540036}, {'Key': 1000, 'Val': 1529658962}], 'Count': 4})
四:显示最近联系人和公众号
视图所有代码:对于上面是有所修改的
from django.shortcuts import render,HttpResponse,redirect from bs4 import BeautifulSoup import requests import time,re,json CTIME = None QCODE = None TIP = 1 TICKET_DICT = {} #保存凭证信息 ALL_COOKIE_DICT = {} # Create your views here. def login(request): global CTIME global QCODE CTIME = int(time.time()*1000) data = { 'appid':'wx782c26e4c19acffb', 'fun':'new', 'lang':'zh_CN', '_':CTIME } response = requests.get( url="https://login.wx.qq.com/jslogin", params=data ) pat_res = re.findall('uuid = "(.*)";',response.text) QCODE = pat_res[0] return render(request,"login.html",{'qcode':QCODE}) def check_login(request): global TIP ret = {'code':408,'data':None} data = { 'loginicon':"true", 'uuid':QCODE, 'tip':TIP, 'r':'-577317906', '_':int(time.time()) } r1 = requests.get( url='https://login.wx.qq.com/cgi-bin/mmwebwx-bin/login', params=data ) if 'window.code=408' in r1.text: print("无人扫描") return HttpResponse(json.dumps(ret)) elif 'window.code=201' in r1.text: ret['code'] = 201 pat_ret = re.findall("window.userAvatar = '(.*)';",r1.text)[0] ret['data'] = pat_ret TIP = 0 return HttpResponse(json.dumps(ret)) elif 'window.code=200;' in r1.text: ret['code'] = 200 redirect_url = re.findall('window.redirect_uri="(.*)";', r1.text)[0] reponse = requests.get( #获取凭证,这里也开始设置cookie了,所以我们在这里向后需要记录cookie url=redirect_url + "&fun=new&version=v2" # url不够完整,需要我们完善 ) ALL_COOKIE_DICT.update(reponse.cookies) soup = BeautifulSoup(reponse.text, "lxml") info_dict = {} for tag in soup.find("error").children: info_dict[tag.name] = tag.get_text() global TICKET_DICT TICKET_DICT.update(info_dict) ret['code']=200 return HttpResponse(json.dumps(ret)) def user(request): get_user_info_url = 'https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxinit?r=-613135321&pass_ticket=' + TICKET_DICT[ 'pass_ticket'] get_user_info_form = { 'BaseRequest': { 'DeviceID': "e055319847811019", 'Sid': TICKET_DICT['wxsid'], 'Skey': TICKET_DICT['skey'], 'Uin': TICKET_DICT['wxuin'] } } reponse2 = requests.post( # 获取的是用户信息,几个联系人,公众号,自己信息 url=get_user_info_url, json=get_user_info_form, # 注意这里使用的是json,post不允许传送字典 ) ALL_COOKIE_DICT.update(reponse2.cookies) reponse2.encoding = "utf-8" user_info_dict = json.loads(reponse2.text) # 获取的是用户信息,几个联系人,公众号,自己信息 return render(request, "user.html", {'user_info_dict': user_info_dict})
视图方法user去获取最近联系人
注意:我们将上面的凭证保存到了全局变量中TICKET_DICT方便查询使用
def user(request): get_user_info_url = 'https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxinit?r=-613135321&pass_ticket=' + TICKET_DICT[ 'pass_ticket'] get_user_info_form = { 'BaseRequest': { 'DeviceID': "e055319847811019", 'Sid': TICKET_DICT['wxsid'], 'Skey': TICKET_DICT['skey'], 'Uin': TICKET_DICT['wxuin'] } } reponse2 = requests.post( # 获取的是用户信息,几个联系人,公众号,自己信息 url=get_user_info_url, json=get_user_info_form, # 注意这里使用的是json,post不允许传送字典 ) ALL_COOKIE_DICT.update(reponse2.cookies) reponse2.encoding = "utf-8" user_info_dict = json.loads(reponse2.text) # 获取的是用户信息,几个联系人,公众号,自己信息 return render(request, "user.html", {'user_info_dict': user_info_dict})
前端代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div> <h3>最近联系人</h3> <ul> {% for item in user_info_dict.ContactList %} <li>{{ item.NickName }}</li> {% endfor %} </ul> <a href="/contact-list.html">获取更多联系人</a> </div> <div> <h3>微信公众号</h3> <div> {% for item in user_info_dict.MPSubscribeMsgList %} <h4>{{ item.NickName }}</h4> <ul> {% for item2 in item.MPArticleList %} <li> <a href="{{ item2.Url }}"> {{ item2.Title }} </a> </li> {% endfor %} </ul> {% endfor %} </div> </div> </body> </html>
五:显示所有联系人
视图所有的修改:主要在设置一个全局字典存放网站cookie,注意这里是需要携带cookie的,而cookie是在我们点击登录后,服务器开始设置的,我们需要去获取自那时以后的所有cookie
from django.shortcuts import render,HttpResponse,redirect from bs4 import BeautifulSoup import requests import time,re,json CTIME = None QCODE = None TIP = 1 TICKET_DICT = {} #保存凭证信息 ALL_COOKIE_DICT = {} # Create your views here. def login(request): global CTIME global QCODE CTIME = int(time.time()*1000) data = { 'appid':'wx782c26e4c19acffb', 'fun':'new', 'lang':'zh_CN', '_':CTIME } response = requests.get( url="https://login.wx.qq.com/jslogin", params=data ) pat_res = re.findall('uuid = "(.*)";',response.text) QCODE = pat_res[0] return render(request,"login.html",{'qcode':QCODE}) def check_login(request): global TIP ret = {'code':408,'data':None} data = { 'loginicon':"true", 'uuid':QCODE, 'tip':TIP, 'r':'-577317906', '_':int(time.time()) } r1 = requests.get( url='https://login.wx.qq.com/cgi-bin/mmwebwx-bin/login', params=data ) if 'window.code=408' in r1.text: print("无人扫描") return HttpResponse(json.dumps(ret)) elif 'window.code=201' in r1.text: ret['code'] = 201 pat_ret = re.findall("window.userAvatar = '(.*)';",r1.text)[0] ret['data'] = pat_ret TIP = 0 return HttpResponse(json.dumps(ret)) elif 'window.code=200;' in r1.text: ret['code'] = 200 redirect_url = re.findall('window.redirect_uri="(.*)";', r1.text)[0] reponse = requests.get( #获取凭证,这里也开始设置cookie了,所以我们在这里向后需要记录cookie url=redirect_url + "&fun=new&version=v2" # url不够完整,需要我们完善 ) ALL_COOKIE_DICT.update(reponse.cookies) soup = BeautifulSoup(reponse.text, "lxml") info_dict = {} for tag in soup.find("error").children: info_dict[tag.name] = tag.get_text() global TICKET_DICT TICKET_DICT.update(info_dict) ret['code']=200 return HttpResponse(json.dumps(ret)) def user(request): get_user_info_url = 'https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxinit?r=-613135321&pass_ticket=' + TICKET_DICT[ 'pass_ticket'] get_user_info_form = { 'BaseRequest': { 'DeviceID': "e055319847811019", 'Sid': TICKET_DICT['wxsid'], 'Skey': TICKET_DICT['skey'], 'Uin': TICKET_DICT['wxuin'] } } reponse2 = requests.post( # 获取的是用户信息,几个联系人,公众号,自己信息 url=get_user_info_url, json=get_user_info_form, # 注意这里使用的是json,post不允许传送字典 ) ALL_COOKIE_DICT.update(reponse2.cookies) reponse2.encoding = "utf-8" user_info_dict = json.loads(reponse2.text) # 获取的是用户信息,几个联系人,公众号,自己信息 return render(request, "user.html", {'user_info_dict': user_info_dict}) def contact_list(request): get_all_user_url = 'https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxgetcontact?lang=zh_CN&pass_ticket=%s&r=%s&seq=0&skey=%s' % (TICKET_DICT['pass_ticket'],int(time.time()*1000),TICKET_DICT['skey']) reponse = requests.get( url=get_all_user_url, #这里需要用到cookie cookies=ALL_COOKIE_DICT ) reponse.encoding = "utf-8" contact_info_list = json.loads(reponse.text) return render(request,"contact_info.html",{'contact_info_list':contact_info_list})
不携带cookie情况:
视图方法:contact_list
def contact_list(request): get_all_user_url = 'https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxgetcontact?lang=zh_CN&pass_ticket=%s&r=%s&seq=0&skey=%s' % (TICKET_DICT['pass_ticket'],int(time.time()*1000),TICKET_DICT['skey']) reponse = requests.get( url=get_all_user_url, #这里需要用到cookie cookies=ALL_COOKIE_DICT ) reponse.encoding = "utf-8" contact_info_list = json.loads(reponse.text) return render(request,"contact_info.html",{'contact_info_list':contact_info_list})
前端代码:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div> <h3>全部联系人列表</h3> <ul> {% for item in contact_info_list.MemberList %} <li>{{ item.NickName }}</li> {% endfor %} </ul> </div> </body> </html>
六:模拟发送信息
发送信息的url:
https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxsendmsg?lang=zh_CN&pass_ticket=l8yQcPtEelgrNY6fSnMf72i%252BoP10LCLTdmjnFgEQOCK9n7401krRfKnc0xMbNweJ
POST传递数据内容:
{
"BaseRequest": #这里数据存放在全局凭证中 {"Uin":2821071261, "Sid":"xU10r+19IxmPU8Fb", "Skey":"@crypt_7358fe11_5fc69b570e562f35f5e96aa1039d83aa", "DeviceID":"e308142734343946" }, "Msg": #发送的数据信息 {"Type":1, #文本信息 "Content":"参数", #发送的数据 "FromUserName":"@c63e475396e438ef81d9825832217c06e4cc302269db05b7eae7e2980de2d56d", #我的username "ToUserName":"@94bcc0a92c726c0e2639ffa59618549d222bee0107150515cf247dc8d45f8144", #发给谁username "LocalID":"15296541823070630", #和时间戳一致 "ClientMsgId":"15296541823070630" #时间戳 }, "Scene":0 }
{"BaseRequest": {"Uin":2821071261, "Sid":"xU10r+19IxmPU8Fb", "Skey":"@crypt_7358fe11_5fc69b570e562f35f5e96aa1039d83aa", "DeviceID":"e149192355196085" #可变的设备ID }, "Msg": {"Type":1, "Content":"哈哈哈", #发送内容改变了 "FromUserName":"@c63e475396e438ef81d9825832217c06e4cc302269db05b7eae7e2980de2d56d", "ToUserName":"@94bcc0a92c726c0e2639ffa59618549d222bee0107150515cf247dc8d45f8144", "LocalID":"15296546798310239", #同时间戳一致 "ClientMsgId":"15296546798310239" #时间戳改变了 }, "Scene":0 }
修改前端contact_info.html页面
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link href="/static/css/bootstrap.min.css" rel="stylesheet"> <link href="/static/css/nifty.min.css" rel="stylesheet"> <link href="/static/css/demo/nifty-demo-icons.min.css" rel="stylesheet"> <link href="/static/css/demo/nifty-demo.min.css" rel="stylesheet"> <link href="/static/plugins/pace/pace.min.css" rel="stylesheet"> <script src="/static/js/jquery-2.2.4.min.js"></script> <script> $(function(){ $(".list-group-item").click(function(){ $(".list-unstyled").empty(); $(this).siblings().removeClass("active"); $(this).addClass("active"); var NickName = $(this).first().text().trim(); $(".panel-title").text(NickName); }) }) </script> <script src="/static/plugins/pace/pace.min.js"></script> <script src="/static/js/bootstrap.min.js"></script> <script src="/static/js/nifty.min.js"></script> <script src="/static/js/demo/nifty-demo.min.js"></script> <script src="/static/plugins/flot-charts/jquery.flot.min.js"></script> <script src="/static/plugins/flot-charts/jquery.flot.resize.min.js"></script> <script src="/static/plugins/gauge-js/gauge.min.js"></script> <script src="/static/plugins/skycons/skycons.min.js"></script> <script src="/static/plugins/easy-pie-chart/jquery.easypiechart.min.js"></script> <script src="/static/js/demo/widgets.js"></script> </head> <body> <div id="container" class="effect aside-bright mainnav-sm aside-right aside-in"> <div class="boxed"> <div id="content-container"> <div class="row"> <div class="col-md-8 col-lg-8 col-sm-8"> <!--Chat widget--> <!--===================================================--> <div class="panel" style="height: 640px"> <!--Heading--> <div class="panel-heading"> <h3 class="panel-title">Chat</h3> </div> <!--Widget body--> <div style="height:510px;padding-top:0px;" class="widget-body"> <div class="nano"> <div class="nano-content pad-all"> <ul class="list-unstyled media-block"> <li class="mar-btm"> <div class="media-left"> <img src="img/profile-photos/1.png" class="img-circle img-sm" alt="Profile Picture"> </div> <div class="media-body pad-hor"> <div class="speech"> <a href="#" class="media-heading">Aaron Chavez</a> <p>Hello Lucy, how can I help you today ?</p> <p class="speech-time"> <i class="demo-pli-clock icon-fw"></i>09:23AM </p> </div> </div> </li> <li class="mar-btm"> <div class="media-right"> <img src="img/profile-photos/8.png" class="img-circle img-sm" alt="Profile Picture"> </div> <div class="media-body pad-hor speech-right"> <div class="speech"> <a href="#" class="media-heading">Lucy Doe</a> <p>Hi, I want to buy a new shoes.</p> <p class="speech-time"> <i class="demo-pli-clock icon-fw"></i> 09:23AM </p> </div> </div> </li> </ul> </div> </div> <!--Widget footer--> <div class="panel-footer" style="height: 90px;"> <div class="row"> <div class="col-xs-9"> <input type="text" placeholder="Enter your text" class="form-control chat-input"> </div> <div class="col-xs-3"> <button class="btn btn-primary btn-block" onclick="sendMsg(this);" type="submit">Send</button> </div> </div> </div> </div> </div> <!--===================================================--> <!--Chat widget--> </div> <div class="col-md-4 col-lg-4 col-sm-4"> <aside id="aside-container"> <div id="aside"> <div class="nano has-scrollbar"> <div class="nano-content" tabindex="0" style="right: -17px;"> <!--Nav tabs--> <!--================================--> <ul class="nav nav-tabs nav-justified"> <li class="active"> <a href="#demo-asd-tab-1" data-toggle="tab"> <i class="demo-pli-speech-bubble-7"></i> </a> </li> </ul> <!--================================--> <!--End nav tabs--> <!-- Tabs Content --> <!--================================--> <div class="tab-content"> <div class="tab-pane fade in active" id="demo-asd-tab-1"> <p class="pad-hor text-semibold text-main"> <span class="pull-right badge badge-success">{{ contact_info_list.MemberCount }}</span> Friends </p> <!--Works--> <div class="list-group bg-trans"> {% for item in contact_info_list.MemberList %} <a href="#" for="{{ item.UserName }}" class="list-group-item"> <span class="badge badge-purple badge-icon badge-fw pull-left"></span> {{ item.NickName }} </a> {% endfor %} </div> </div> </div> </div> <div class="nano-pane" style="display: none;"><div class="nano-slider" style="height: 4059px; transform: translate(0px, 0px);"></div></div></div> </div> </aside> </div> </div> </div> </div> </div> </body> </html> <script> function sendMsg(ths){ var sel_tag = $(".list-group").find(".active") if(sel_tag.length==0){ return false; } var msg = $(ths).parents(".panel-footer").find(".chat-input").val(); var sendMsg={ 'ToUserName':sel_tag.attr("for"), 'Type':1, 'Content':msg, 'csrfmiddlewaretoken':'{{ csrf_token }}' } $.ajax({ url:"send-msg.html", data:sendMsg, type:"POST", dataType:"json", success:function(callback){ if (callback.code==200){ var dt = new Date() var now_time = dt.toLocaleString(); console.log(callback); var li = '<li class="mar-btm"><div class="media-right"><img src="'+callback.headImgUrl+'" class="img-circle img-sm" alt="Profile Picture"></div>'; li += '<div class="media-body pad-hor speech-right"><div class="speech"><a href="#" class="media-heading">'+callback.username+'</a>'; li += '<p>'+msg+'</p>'; li += '<p class="speech-time">'; li += '<i class="demo-pli-clock icon-fw"></i>'+now_time; li += '</p></div></div></li>'; $(ths).parents(".widget-body").find(".list-unstyled").append(li); $(ths).parents(".panel-footer").find(".chat-input").val(""); } } }) } </script>
后端代码send_msg
注意:处理传送中文时,在requests模块有点麻烦,下面代码有写解决方法
def send_msg(request): ret = { 'code':200, 'error':'Send Success', 'data':{} } recv_data = request.POST if not recv_data: ret['code']=400 ret['data']="Send failure" return HttpResponse(json.dumps(ret)) #数据整合 Send_data={} Send_data['BaseRequest'] = { 'DeviceID': "e055319847811019", 'Sid': TICKET_DICT['wxsid'], 'Skey': TICKET_DICT['skey'], 'Uin': TICKET_DICT['wxuin'] } Send_data['Msg'] = { 'Type':recv_data.get("Type",1), 'Content':recv_data.get("Content"), 'FromUserName':USER_INIT_DICT['User']['UserName'], 'ToUserName':recv_data.get("ToUserName"), 'LocalID':int(time.time()*1000), 'ClientMsgId':int(time.time()*1000), } Send_data['Scene']=0 send_url = 'https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxsendmsg?lang=zh_CN&pass_ticket=%s'%TICKET_DICT['pass_ticket'] # reponse = requests.post( # url=send_url, # json=Send_data, #注意这里如果使用json,会将中文转换为Unicode # cookies=ALL_COOKIE_DICT # ) reponse = requests.post( url=send_url, #若是有中文,需要加上ensure_ascii=False,若是字符串中含有中文,request传递数据时,将中文转换为字节,无法为我们转换。需要我们提前使用encoding编码,直接传递字节,不让requests为我们转换 #data可以是字典,字符串,字节,既然对于字典,字符串直接含有中文不正确,直接转字节传送,py3默认是utf-8,所以我们直接传送字节就可以 data=bytes(json.dumps(Send_data,ensure_ascii=False),encoding="utf-8"), #注意这里如果使用json,会将中文转换为Unicode cookies=ALL_COOKIE_DICT #携带cookie ) reponse.encoding = "utf-8" ret_status = re.findall('"Ret": (.*),', reponse.text)[0] ret_error = re.findall('"ErrMsg": "(.*)"', reponse.text)[0] if int(ret_status) != 0: ret['code']=401 ret['data']=ret_error return HttpResponse(json.dumps(ret)) ret['username'] = USER_INIT_DICT['User']['NickName'] ret['headImgUrl'] = 'https://wx.qq.com'+USER_INIT_DICT['User']['HeadImgUrl'] return HttpResponse(json.dumps(ret))
七:实现长轮询接收消息
先参考微信的实例:微信依靠两个url实现去后端获取数据
1.webwxsync:ajax使用POST,长轮询去获取发送的消息,和获取一个SyncCheckKey,下面检测是否有消息到了需要携带这个SyncCheckKey https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxsync?sid=NOWucA2Et3xw0l8a&skey=@crypt_7358fe11_181eea60999ab937ed1ff13e1d1f7853&pass_ticket=9ZK9OoNzaLxkdRqVWghy7uGzWFvsIzXNWDjgeJqTskg3Bl08tQxAZ9t0hcEYOzmO
要传递的POST数据
{
"BaseRequest":{ #都是已经获取的数据
"Uin":2821071261,
"Sid":"NOWucA2Et3xw0l8a",
"Skey":"@crypt_7358fe11_181eea60999ab937ed1ff13e1d1f7853",
"DeviceID":"e937865997050874"
},
"SyncKey":{ #我们上一次的SyncKey值
"Count":9,"List":[{"Key":1,"Val":677540346},{"Key":2,"Val":677540435},{"Key":3,"Val":677540036},{"Key":11,"Val":677540181},{"Key":201,"Val":1529672373},{"Key":203,"Val":1529658601},{"Key":1000,"Val":1529658962},{"Key":1001,"Val":1529659033},{"Key":2001,"Val":1529480143}]
},
"rr":-664997335 #未知
}
{ "BaseResponse": { "Ret": 0, "ErrMsg": "" } , "AddMsgCount": 0, "AddMsgList": [], "ModContactCount": 0, "ModContactList": [], "DelContactCount": 0, "DelContactList": [], "ModChatRoomMemberCount": 0, "ModChatRoomMemberList": [], "Profile": { "BitFlag": 0, "UserName": { "Buff": "" } , "NickName": { "Buff": "" } , "BindUin": 0, "BindEmail": { "Buff": "" } , "BindMobile": { "Buff": "" } , "Status": 0, "Sex": 0, "PersonalCard": 0, "Alias": "", "HeadImgUpdateFlag": 0, "HeadImgUrl": "", "Signature": "" } , "ContinueFlag": 0, "SyncKey": { "Count": 9, "List": [ { "Key": 1, "Val": 677540346 } ,{ "Key": 2, "Val": 677540423 } ,{ "Key": 3, "Val": 677540036 } ,{ "Key": 11, "Val": 677540181 } ,{ "Key": 201, "Val": 1529671726 } ,{ "Key": 203, "Val": 1529658601 } ,{ "Key": 1000, "Val": 1529658962 } ,{ "Key": 1001, "Val": 1529659033 } ,{ "Key": 2001, "Val": 1529480143 } ] } , "SKey": "", "SyncCheckKey": { "Count": 9, "List": [ { "Key": 1, "Val": 677540346 } ,{ "Key": 2, "Val": 677540423 } ,{ "Key": 3, "Val": 677540036 } ,{ "Key": 11, "Val": 677540181 } ,{ "Key": 201, "Val": 1529671726 } ,{ "Key": 203, "Val": 1529658601 } ,{ "Key": 1000, "Val": 1529658962 } ,{ "Key": 1001, "Val": 1529659033 } ,{ "Key": 2001, "Val": 1529480143 } ] } }
2.synccheck:轮询请求,用于检测是否有人发消息过来。注意:当我们刚刚登陆时,是有一个初始的synckey的,可以在《相关数据打印(格式)》那里看到
https://webpush.wx.qq.com/cgi-bin/mmwebwx-bin/synccheck?r=1529672369137&skey=%40crypt_7358fe11_181eea60999ab937ed1ff13e1d1f7853&sid=NOWucA2Et3xw0l8a&uin=2821071261&deviceid=e539832593408529&synckey=1_677540346%7C2_677540434%7C3_677540036%7C11_677540181%7C201_1529672366%7C203_1529658601%7C1000_1529658962%7C1001_1529659033%7C2001_1529480143&_=1529671539738
https://webpush.wx.qq.com/cgi-bin/mmwebwx-bin/synccheck? r=1529671730362 #时间戳 &skey=%40crypt_7358fe11_181eea60999ab937ed1ff13e1d1f7853&sid=NOWucA2Et3xw0l8a &uin=2821071261 #和其他数据一起放在TICKET_DICT中 &deviceid=e065841963151848 #设备id &synckey= 1_677540346 1 677540346 #和上一个请求url中接收的数据一致 %7C | 2_677540423 2 677540423 %7C | 3_677540036 %7C 11_677540181%7C201_1529671726%7C203_1529658601%7C1000_1529658962%7C1001_1529659033%7C2001_1529480143 &_=1529671539706 #时间戳 r=1529672904066&skey=%40crypt_7358fe11_181eea60999ab937ed1ff13e1d1f7853&sid=NOWucA2Et3xw0l8a&uin=2821071261&deviceid=e647845000558861&synckey=1_677540346%7C2_677540435%7C3_677540036%7C11_677540181%7C201_1529672373%7C203_1529658601%7C1000_1529658962%7C1001_1529659033%7C2001_1529480143&_=1529671539759
返回值
window.synccheck={retcode:"0",selector:"2"} #有消息到来
window.synccheck={retcode:"0",selector:"0"} #没有消息到来
当有消息到来:我们将从webwxsync中获取到数据
{ "BaseResponse": { "Ret": 0, "ErrMsg": "" } , "AddMsgCount": 1, #到来的消息数目 "AddMsgList": [{ "MsgId": "3138475736544982077", "FromUserName": "@@e780498496215d2077ef72216ce629bfd570595c2d1b99192aa32fae78a3e489", #来自谁的微信 "ToUserName": "@9419ce13e311ec0469803ba1667703ca16fd3e9ec13d5998c52f42dba33f4c3d", #这是我们的微信 "MsgType": 1, "Content": "@0e722870282c2e079ec9ddc7304ef501bb99d45465501efac52f8b050498d8a4:<br/>@A~ç±åµ©ç¬â€…10分钟å§", #发过来的信息,我们未编码 "Status": 3, #状态 "ImgStatus": 1, "CreateTime": 1529673345, "VoiceLength": 0, "PlayLength": 0, "FileName": "", "FileSize": "", "MediaId": "", "Url": "", "AppMsgType": 0, "StatusNotifyCode": 0, "StatusNotifyUserName": "", "RecommendInfo": { "UserName": "", "NickName": "", "QQNum": 0, "Province": "", "City": "", "Content": "", "Signature": "", "Alias": "", "Scene": 0, "VerifyFlag": 0, "AttrStatus": 0, "Sex": 0, "Ticket": "", "OpCode": 0 } , "ForwardFlag": 0, "AppInfo": { "AppID": "", "Type": 0 } , "HasProductId": 0, "Ticket": "", "ImgHeight": 0, "ImgWidth": 0, "SubMsgType": 0, "NewMsgId": 3138475736544982077, "OriContent": "", "EncryFileName": "" } ], "ModContactCount": 0, "ModContactList": [], "DelContactCount": 0, "DelContactList": [], "ModChatRoomMemberCount": 0, "ModChatRoomMemberList": [], "Profile": { "BitFlag": 0, "UserName": { "Buff": "" } , "NickName": { "Buff": "" } , "BindUin": 0, "BindEmail": { "Buff": "" } , "BindMobile": { "Buff": "" } , "Status": 0, "Sex": 0, "PersonalCard": 0, "Alias": "", "HeadImgUpdateFlag": 0, "HeadImgUrl": "", "Signature": "" } , "ContinueFlag": 0, "SyncKey": { #下一次去检测需要的SyncKey,每当我们接受一次真正的消息或者长轮询结束,原来的就失效,需要一个新的SyncKey值 "Count": 9, "List": [{ "Key": 1, "Val": 677540346 } ,{ "Key": 2, "Val": 677540437 } ,{ "Key": 3, "Val": 677540036 } ,{ "Key": 11, "Val": 677540181 } ,{ "Key": 201, "Val": 1529673345 } ,{ "Key": 203, "Val": 1529658601 } ,{ "Key": 1000, "Val": 1529658962 } ,{ "Key": 1001, "Val": 1529659033 } ,{ "Key": 2001, "Val": 1529480143 } ] } , "SKey": "", "SyncCheckKey": { "Count": 9, "List": [{ "Key": 1, "Val": 677540346 } ,{ "Key": 2, "Val": 677540437 } ,{ "Key": 3, "Val": 677540036 } ,{ "Key": 11, "Val": 677540181 } ,{ "Key": 201, "Val": 1529673345 } ,{ "Key": 203, "Val": 1529658601 } ,{ "Key": 1000, "Val": 1529658962 } ,{ "Key": 1001, "Val": 1529659033 } ,{ "Key": 2001, "Val": 1529480143 } ] } }
后台视图方法获取接收的信息
def get_msg(request): ret = { 'code':400, 'msg':'no message arrived' } global SYNCKEY_DICT #设置为全局 #1.先去查询是否有消息到达 req_url = 'https://webpush.wx.qq.com/cgi-bin/mmwebwx-bin/synccheck' sync_key = [] for item in SYNCKEY_DICT['List']: sync_key.append("%s_%s"%(item['Key'],item['Val'])) reponse = requests.get( url=req_url, params={ 'r':int(time.time()*1000), 'skey':TICKET_DICT['skey'], 'sid':TICKET_DICT['wxsid'], 'uin':TICKET_DICT['wxuin'], 'deviceid':'e055319847811019', 'synckey':'|'.join(sync_key) #参数重组 }, cookies = ALL_COOKIE_DICT ) if 'window.synccheck={retcode:"0",selector:"2"}' in reponse.text: ret['code'] = 200 ret['msg'] = "message arrived" #有消息到来,这时我们需要去获取信息,并且更新SYNCKEY_DICT req_url = 'https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxsync' post_dict = { 'BaseRequest': { 'DeviceID': "e055319847811019", 'Sid': TICKET_DICT['wxsid'], 'Skey': TICKET_DICT['skey'], 'Uin': TICKET_DICT['wxuin'] }, 'SyncKey':SYNCKEY_DICT } reponse2 = requests.post( url=req_url, params={ 'sid':TICKET_DICT['wxsid'], 'pass_ticket':TICKET_DICT['pass_ticket'], 'skey':TICKET_DICT['skey'], 'lang': 'zh_CN' }, json = post_dict ) reponse2.encoding = "utf-8" rep_dict = json.loads(reponse2.text) SYNCKEY_DICT = rep_dict.get("SyncKey") if rep_dict['AddMsgCount'] != 0: ret['code'] = 201 ret['msg'] = [] for item in rep_dict['AddMsgList']: ret['msg'].append(item) return HttpResponse(json.dumps(ret))
前端修改:首先是有get_msg方法去长轮询获取接收消息和检测数据是否接收。然后针对谁发送的,谁就高亮
<script> $(function(){ bind_event(); get_msg(); }) function bind_event() { $(".list-group-item").click(function(){ $(".list-unstyled").empty(); $(this).siblings().removeClass("active"); $(this).addClass("active"); var NickName = $(this).first().text().trim(); $(".panel-title").text(NickName); }) } function get_msg(){ $.ajax({ url:"/get-msg.html", type:"GET", dataType:"json", success:function(callback){ if (callback.code==201){ //这里只去看一个人的信息 var info = callback['msg'][0] $(".list-group-item").each(function(){ if($(this).attr("for")==info['FromUserName']){ $(this).addClass("active") return false; } }) } get_msg(); } }) } </script>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link href="/static/css/bootstrap.min.css" rel="stylesheet"> <link href="/static/css/nifty.min.css" rel="stylesheet"> <link href="/static/css/demo/nifty-demo-icons.min.css" rel="stylesheet"> <link href="/static/css/demo/nifty-demo.min.css" rel="stylesheet"> <link href="/static/plugins/pace/pace.min.css" rel="stylesheet"> <script src="/static/js/jquery-2.2.4.min.js"></script> <script> $(function(){ bind_event(); get_msg(); }) function bind_event() { $(".list-group-item").click(function(){ $(".list-unstyled").empty(); $(this).siblings().removeClass("active"); $(this).addClass("active"); var NickName = $(this).first().text().trim(); $(".panel-title").text(NickName); }) } function get_msg(){ $.ajax({ url:"/get-msg.html", type:"GET", dataType:"json", success:function(callback){ if (callback.code==201){ //这里只去看一个人的信息 var info = callback['msg'][0] $(".list-group-item").each(function(){ if($(this).attr("for")==info['FromUserName']){ $(this).addClass("active") return false; } }) } get_msg(); } }) } </script> <script src="/static/plugins/pace/pace.min.js"></script> <script src="/static/js/bootstrap.min.js"></script> <script src="/static/js/nifty.min.js"></script> <script src="/static/js/demo/nifty-demo.min.js"></script> <script src="/static/plugins/flot-charts/jquery.flot.min.js"></script> <script src="/static/plugins/flot-charts/jquery.flot.resize.min.js"></script> <script src="/static/plugins/gauge-js/gauge.min.js"></script> <script src="/static/plugins/skycons/skycons.min.js"></script> <script src="/static/plugins/easy-pie-chart/jquery.easypiechart.min.js"></script> <script src="/static/js/demo/widgets.js"></script> </head> <body> <div id="container" class="effect aside-bright mainnav-sm aside-right aside-in"> <div class="boxed"> <div id="content-container"> <div class="row"> <div class="col-md-8 col-lg-8 col-sm-8"> <!--Chat widget--> <!--===================================================--> <div class="panel" style="height: 640px"> <!--Heading--> <div class="panel-heading"> <h3 class="panel-title">Chat</h3> </div> <!--Widget body--> <div style="height:510px;padding-top:0px;" class="widget-body"> <div class="nano"> <div class="nano-content pad-all"> <ul class="list-unstyled media-block"> <li class="mar-btm"> <div class="media-left"> <img src="img/profile-photos/1.png" class="img-circle img-sm" alt="Profile Picture"> </div> <div class="media-body pad-hor"> <div class="speech"> <a href="#" class="media-heading">Aaron Chavez</a> <p>Hello Lucy, how can I help you today ?</p> <p class="speech-time"> <i class="demo-pli-clock icon-fw"></i>09:23AM </p> </div> </div> </li> <li class="mar-btm"> <div class="media-right"> <img src="img/profile-photos/8.png" class="img-circle img-sm" alt="Profile Picture"> </div> <div class="media-body pad-hor speech-right"> <div class="speech"> <a href="#" class="media-heading">Lucy Doe</a> <p>Hi, I want to buy a new shoes.</p> <p class="speech-time"> <i class="demo-pli-clock icon-fw"></i> 09:23AM </p> </div> </div> </li> </ul> </div> </div> <!--Widget footer--> <div class="panel-footer" style="height: 90px;"> <div class="row"> <div class="col-xs-9"> <input type="text" placeholder="Enter your text" class="form-control chat-input"> </div> <div class="col-xs-3"> <button class="btn btn-primary btn-block" onclick="sendMsg(this);" type="submit">Send</button> </div> </div> </div> </div> </div> <!--===================================================--> <!--Chat widget--> </div> <div class="col-md-4 col-lg-4 col-sm-4"> <aside id="aside-container"> <div id="aside"> <div class="nano has-scrollbar"> <div class="nano-content" tabindex="0" style="right: -17px;"> <!--Nav tabs--> <!--================================--> <ul class="nav nav-tabs nav-justified"> <li class="active"> <a href="#demo-asd-tab-1" data-toggle="tab"> <i class="demo-pli-speech-bubble-7"></i> </a> </li> </ul> <!--================================--> <!--End nav tabs--> <!-- Tabs Content --> <!--================================--> <div class="tab-content"> <div class="tab-pane fade in active" id="demo-asd-tab-1"> <p class="pad-hor text-semibold text-main"> <span class="pull-right badge badge-success">{{ contact_info_list.MemberCount }}</span> Friends </p> <!--Works--> <div class="list-group bg-trans"> {% for item in contact_info_list.MemberList %} <a href="#" for="{{ item.UserName }}" class="list-group-item"> <span class="badge badge-purple badge-icon badge-fw pull-left"></span> {{ item.NickName }} </a> {% endfor %} </div> </div> </div> </div> <div class="nano-pane" style="display: none;"><div class="nano-slider" style="height: 4059px; transform: translate(0px, 0px);"></div></div></div> </div> </aside> </div> </div> </div> </div> </div> </body> </html> <script> function sendMsg(ths){ var sel_tag = $(".list-group").find(".active") if(sel_tag.length==0){ return false; } var msg = $(ths).parents(".panel-footer").find(".chat-input").val(); var sendMsg={ 'ToUserName':sel_tag.attr("for"), 'Type':1, 'Content':msg, 'csrfmiddlewaretoken':'{{ csrf_token }}' } $.ajax({ url:"send-msg.html", data:sendMsg, type:"POST", dataType:"json", success:function(callback){ if (callback.code==200){ var dt = new Date() var now_time = dt.toLocaleString(); console.log(callback); var li = '<li class="mar-btm"><div class="media-right"><img src="'+callback.headImgUrl+'" class="img-circle img-sm" alt="Profile Picture"></div>'; li += '<div class="media-body pad-hor speech-right"><div class="speech"><a href="#" class="media-heading">'+callback.username+'</a>'; li += '<p>'+msg+'</p>'; li += '<p class="speech-time">'; li += '<i class="demo-pli-clock icon-fw"></i>'+now_time; li += '</p></div></div></li>'; $(ths).parents(".widget-body").find(".list-unstyled").append(li); $(ths).parents(".panel-footer").find(".chat-input").val(""); } } }) } </script>
window.synccheck={retcode:"0",selector:"2"} { "BaseResponse": { "Ret": 0, "ErrMsg": "" } , "AddMsgCount": 1, "AddMsgList": [{ "MsgId": "8087080836430146290", "FromUserName": "@7ef22458145d7446b96c0c1612549c00991cab433640c86b9c7d5f9d17dc8a43", "ToUserName": "@fb5a4e79e7d71ec3f11d3351fdec8a1cb5c4df7979041ed9315833e427323198", "MsgType": 1, "Content": "在", "Status": 3, "ImgStatus": 1, "CreateTime": 1529679858, "VoiceLength": 0, "PlayLength": 0, "FileName": "", "FileSize": "", "MediaId": "", "Url": "", "AppMsgType": 0, "StatusNotifyCode": 0, "StatusNotifyUserName": "", "RecommendInfo": { "UserName": "", "NickName": "", "QQNum": 0, "Province": "", "City": "", "Content": "", "Signature": "", "Alias": "", "Scene": 0, "VerifyFlag": 0, "AttrStatus": 0, "Sex": 0, "Ticket": "", "OpCode": 0 } , "ForwardFlag": 0, "AppInfo": { "AppID": "", "Type": 0 } , "HasProductId": 0, "Ticket": "", "ImgHeight": 0, "ImgWidth": 0, "SubMsgType": 0, "NewMsgId": 8087080836430146290, "OriContent": "", "EncryFileName": "" } ], "ModContactCount": 0, "ModContactList": [], "DelContactCount": 0, "DelContactList": [], "ModChatRoomMemberCount": 0, "ModChatRoomMemberList": [], "Profile": { "BitFlag": 0, "UserName": { "Buff": "" } , "NickName": { "Buff": "" } , "BindUin": 0, "BindEmail": { "Buff": "" } , "BindMobile": { "Buff": "" } , "Status": 0, "Sex": 0, "PersonalCard": 0, "Alias": "", "HeadImgUpdateFlag": 0, "HeadImgUrl": "", "Signature": "" } , "ContinueFlag": 0, "SyncKey": { "Count": 8, "List": [{ "Key": 1, "Val": 677540346 } ,{ "Key": 2, "Val": 677540469 } ,{ "Key": 3, "Val": 677540464 } ,{ "Key": 11, "Val": 677540440 } ,{ "Key": 201, "Val": 1529679858 } ,{ "Key": 1000, "Val": 1529658962 } ,{ "Key": 1001, "Val": 1529659033 } ,{ "Key": 2001, "Val": 1529480143 } ] } , "SKey": "", "SyncCheckKey": { "Count": 8, "List": [{ "Key": 1, "Val": 677540346 } ,{ "Key": 2, "Val": 677540469 } ,{ "Key": 3, "Val": 677540464 } ,{ "Key": 11, "Val": 677540440 } ,{ "Key": 201, "Val": 1529679858 } ,{ "Key": 1000, "Val": 1529658962 } ,{ "Key": 1001, "Val": 1529659033 } ,{ "Key": 2001, "Val": 1529480143 } ] } }
其实应该更进一步,显示出来我们接受的数据,但是明天考试..不再继续了
八:全部代码
后端:
from django.shortcuts import render,HttpResponse,redirect from bs4 import BeautifulSoup import requests import time,re,json CTIME = None QCODE = None TIP = 1 TICKET_DICT = {} #保存凭证信息 ALL_COOKIE_DICT = {} #保存所有COOKIE信息 USER_INIT_DICT = {} #保存用户最近联系信息,和自己的信息 SYNCKEY_DICT = {} #用于保存获取请求时需要的SyncKey # Create your views here. def login(request): global CTIME global QCODE CTIME = int(time.time()*1000) data = { 'appid':'wx782c26e4c19acffb', 'fun':'new', 'lang':'zh_CN', '_':CTIME } response = requests.get( url="https://login.wx.qq.com/jslogin", params=data ) pat_res = re.findall('uuid = "(.*)";',response.text) QCODE = pat_res[0] return render(request,"login.html",{'qcode':QCODE}) def check_login(request): global TIP ret = {'code':408,'data':None} data = { 'loginicon':"true", 'uuid':QCODE, 'tip':TIP, 'r':'-577317906', '_':int(time.time()) } r1 = requests.get( url='https://login.wx.qq.com/cgi-bin/mmwebwx-bin/login', params=data ) if 'window.code=408' in r1.text: print("无人扫描") return HttpResponse(json.dumps(ret)) elif 'window.code=201' in r1.text: ret['code'] = 201 pat_ret = re.findall("window.userAvatar = '(.*)';",r1.text)[0] ret['data'] = pat_ret TIP = 0 return HttpResponse(json.dumps(ret)) elif 'window.code=200;' in r1.text: ret['code'] = 200 redirect_url = re.findall('window.redirect_uri="(.*)";', r1.text)[0] reponse = requests.get( #获取凭证,这里也开始设置cookie了,所以我们在这里向后需要记录cookie url=redirect_url + "&fun=new&version=v2" # url不够完整,需要我们完善 ) ALL_COOKIE_DICT.update(reponse.cookies) soup = BeautifulSoup(reponse.text, "lxml") info_dict = {} for tag in soup.find("error").children: info_dict[tag.name] = tag.get_text() global TICKET_DICT TICKET_DICT.update(info_dict) ret['code']=200 return HttpResponse(json.dumps(ret)) def user(request): get_user_info_url = 'https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxinit?r=-613135321&pass_ticket=' + TICKET_DICT[ 'pass_ticket'] get_user_info_form = { 'BaseRequest': { 'DeviceID': "e055319847811019", 'Sid': TICKET_DICT['wxsid'], 'Skey': TICKET_DICT['skey'], 'Uin': TICKET_DICT['wxuin'] } } reponse2 = requests.post( # 获取的是用户信息,几个联系人,公众号,自己信息 url=get_user_info_url, json=get_user_info_form, # 注意这里使用的是json,post不允许传送字典 ) ALL_COOKIE_DICT.update(reponse2.cookies) reponse2.encoding = "utf-8" user_info_dict = json.loads(reponse2.text) # 获取的是用户信息,几个联系人,公众号,自己信息 USER_INIT_DICT.update(user_info_dict) SYNCKEY_DICT.update(user_info_dict['SyncKey']) return render(request, "user.html", {'user_info_dict': user_info_dict}) def contact_list(request): get_all_user_url = 'https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxgetcontact?lang=zh_CN&pass_ticket=%s&r=%s&seq=0&skey=%s' % (TICKET_DICT['pass_ticket'],int(time.time()*1000),TICKET_DICT['skey']) reponse = requests.get( url=get_all_user_url, #这里需要用到cookie cookies=ALL_COOKIE_DICT ) reponse.encoding = "utf-8" contact_info_list = json.loads(reponse.text) return render(request,"contact_info.html",{'contact_info_list':contact_info_list}) def send_msg(request): ret = { 'code':200, 'error':'Send Success', 'data':{} } recv_data = request.POST if not recv_data: ret['code']=400 ret['data']="Send failure" return HttpResponse(json.dumps(ret)) #数据整合 Send_data={} Send_data['BaseRequest'] = { 'DeviceID': "e055319847811019", 'Sid': TICKET_DICT['wxsid'], 'Skey': TICKET_DICT['skey'], 'Uin': TICKET_DICT['wxuin'] } Send_data['Msg'] = { 'Type':recv_data.get("Type",1), 'Content':recv_data.get("Content"), 'FromUserName':USER_INIT_DICT['User']['UserName'], 'ToUserName':recv_data.get("ToUserName"), 'LocalID':int(time.time()*1000), 'ClientMsgId':int(time.time()*1000), } Send_data['Scene']=0 send_url = 'https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxsendmsg?lang=zh_CN&pass_ticket=%s'%TICKET_DICT['pass_ticket'] # reponse = requests.post( # url=send_url, # json=Send_data, #注意这里如果使用json,会将中文转换为Unicode # cookies=ALL_COOKIE_DICT # ) reponse = requests.post( url=send_url, #若是有中文,需要加上ensure_ascii=False,若是字符串中含有中文,request传递数据时,将中文转换为字节,无法为我们转换。需要我们提前使用encoding编码,直接传递字节,不让requests为我们转换 #data可以是字典,字符串,字节,既然对于字典,字符串直接含有中文不正确,直接转字节传送,py3默认是utf-8,所以我们直接传送字节就可以 data=bytes(json.dumps(Send_data,ensure_ascii=False),encoding="utf-8"), #注意这里如果使用json,会将中文转换为Unicode cookies=ALL_COOKIE_DICT ) reponse.encoding = "utf-8" ret_status = re.findall('"Ret": (.*),', reponse.text)[0] ret_error = re.findall('"ErrMsg": "(.*)"', reponse.text)[0] if int(ret_status) != 0: ret['code']=401 ret['data']=ret_error return HttpResponse(json.dumps(ret)) ret['username'] = USER_INIT_DICT['User']['NickName'] ret['headImgUrl'] = 'https://wx.qq.com'+USER_INIT_DICT['User']['HeadImgUrl'] return HttpResponse(json.dumps(ret)) def get_msg(request): ret = { 'code':400, 'msg':'no message arrived' } global SYNCKEY_DICT #1.先去查询是否有消息到达 req_url = 'https://webpush.wx.qq.com/cgi-bin/mmwebwx-bin/synccheck' sync_key = [] for item in SYNCKEY_DICT['List']: sync_key.append("%s_%s"%(item['Key'],item['Val'])) reponse = requests.get( url=req_url, params={ 'r':int(time.time()*1000), 'skey':TICKET_DICT['skey'], 'sid':TICKET_DICT['wxsid'], 'uin':TICKET_DICT['wxuin'], 'deviceid':'e055319847811019', 'synckey':'|'.join(sync_key) }, cookies = ALL_COOKIE_DICT ) if 'window.synccheck={retcode:"0",selector:"2"}' in reponse.text: ret['code'] = 200 ret['msg'] = "message arrived" #有消息到来,这时我们需要去获取信息,并且更新SYNCKEY_DICT req_url = 'https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxsync' post_dict = { 'BaseRequest': { 'DeviceID': "e055319847811019", 'Sid': TICKET_DICT['wxsid'], 'Skey': TICKET_DICT['skey'], 'Uin': TICKET_DICT['wxuin'] }, 'SyncKey':SYNCKEY_DICT } reponse2 = requests.post( url=req_url, params={ 'sid':TICKET_DICT['wxsid'], 'pass_ticket':TICKET_DICT['pass_ticket'], 'skey':TICKET_DICT['skey'], 'lang': 'zh_CN' }, json = post_dict ) reponse2.encoding = "utf-8" rep_dict = json.loads(reponse2.text) SYNCKEY_DICT = rep_dict.get("SyncKey") if rep_dict['AddMsgCount'] != 0: ret['code'] = 201 ret['msg'] = [] for item in rep_dict['AddMsgList']: ret['msg'].append(item) return HttpResponse(json.dumps(ret))
前端:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <img id="qrcode" style=" 340px;height: 340px;" src="https://login.weixin.qq.com/qrcode/{{ qcode }}" alt=""> </body> </html> <script src="/static/jquery.js"></script> <script> $(function(){ checkLogin(); }) function checkLogin() { $.ajax({ url:'/check-login.html', type:'GET', dataType:"json", success:function(data){ console.log(data.code); if (data.code==408){ checkLogin(); } else if(data.code==201){ $("#qrcode").attr('src',data.data) checkLogin(); } else if(data.code==200){ location.href='/user.html' } } }) } </script>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div> <h3>最近联系人</h3> <ul> {% for item in user_info_dict.ContactList %} <li>{{ item.NickName }}</li> {% endfor %} </ul> <a href="/contact-list.html">获取更多联系人</a> </div> <div> <h3>微信公众号</h3> <div> {% for item in user_info_dict.MPSubscribeMsgList %} <h4>{{ item.NickName }}</h4> <ul> {% for item2 in item.MPArticleList %} <li> <a href="{{ item2.Url }}"> {{ item2.Title }} </a> </li> {% endfor %} </ul> {% endfor %} </div> </div> </body> </html>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link href="/static/css/bootstrap.min.css" rel="stylesheet"> <link href="/static/css/nifty.min.css" rel="stylesheet"> <link href="/static/css/demo/nifty-demo-icons.min.css" rel="stylesheet"> <link href="/static/css/demo/nifty-demo.min.css" rel="stylesheet"> <link href="/static/plugins/pace/pace.min.css" rel="stylesheet"> <script src="/static/js/jquery-2.2.4.min.js"></script> <script> $(function(){ bind_event(); get_msg(); }) function bind_event() { $(".list-group-item").click(function(){ $(".list-unstyled").empty(); $(this).siblings().removeClass("active"); $(this).addClass("active"); var NickName = $(this).first().text().trim(); $(".panel-title").text(NickName); }) } function get_msg(){ $.ajax({ url:"/get-msg.html", type:"GET", dataType:"json", success:function(callback){ if (callback.code==201){ //这里只去看一个人的信息 var info = callback['msg'][0] $(".list-group-item").each(function(){ if($(this).attr("for")==info['FromUserName']){ $(this).addClass("active") return false; } }) } get_msg(); } }) } </script> <script src="/static/plugins/pace/pace.min.js"></script> <script src="/static/js/bootstrap.min.js"></script> <script src="/static/js/nifty.min.js"></script> <script src="/static/js/demo/nifty-demo.min.js"></script> <script src="/static/plugins/flot-charts/jquery.flot.min.js"></script> <script src="/static/plugins/flot-charts/jquery.flot.resize.min.js"></script> <script src="/static/plugins/gauge-js/gauge.min.js"></script> <script src="/static/plugins/skycons/skycons.min.js"></script> <script src="/static/plugins/easy-pie-chart/jquery.easypiechart.min.js"></script> <script src="/static/js/demo/widgets.js"></script> </head> <body> <div id="container" class="effect aside-bright mainnav-sm aside-right aside-in"> <div class="boxed"> <div id="content-container"> <div class="row"> <div class="col-md-8 col-lg-8 col-sm-8"> <!--Chat widget--> <!--===================================================--> <div class="panel" style="height: 640px"> <!--Heading--> <div class="panel-heading"> <h3 class="panel-title">Chat</h3> </div> <!--Widget body--> <div style="height:510px;padding-top:0px;" class="widget-body"> <div class="nano"> <div class="nano-content pad-all"> <ul class="list-unstyled media-block"> <li class="mar-btm"> <div class="media-left"> <img src="img/profile-photos/1.png" class="img-circle img-sm" alt="Profile Picture"> </div> <div class="media-body pad-hor"> <div class="speech"> <a href="#" class="media-heading">Aaron Chavez</a> <p>Hello Lucy, how can I help you today ?</p> <p class="speech-time"> <i class="demo-pli-clock icon-fw"></i>09:23AM </p> </div> </div> </li> <li class="mar-btm"> <div class="media-right"> <img src="img/profile-photos/8.png" class="img-circle img-sm" alt="Profile Picture"> </div> <div class="media-body pad-hor speech-right"> <div class="speech"> <a href="#" class="media-heading">Lucy Doe</a> <p>Hi, I want to buy a new shoes.</p> <p class="speech-time"> <i class="demo-pli-clock icon-fw"></i> 09:23AM </p> </div> </div> </li> </ul> </div> </div> <!--Widget footer--> <div class="panel-footer" style="height: 90px;"> <div class="row"> <div class="col-xs-9"> <input type="text" placeholder="Enter your text" class="form-control chat-input"> </div> <div class="col-xs-3"> <button class="btn btn-primary btn-block" onclick="sendMsg(this);" type="submit">Send</button> </div> </div> </div> </div> </div> <!--===================================================--> <!--Chat widget--> </div> <div class="col-md-4 col-lg-4 col-sm-4"> <aside id="aside-container"> <div id="aside"> <div class="nano has-scrollbar"> <div class="nano-content" tabindex="0" style="right: -17px;"> <!--Nav tabs--> <!--================================--> <ul class="nav nav-tabs nav-justified"> <li class="active"> <a href="#demo-asd-tab-1" data-toggle="tab"> <i class="demo-pli-speech-bubble-7"></i> </a> </li> </ul> <!--================================--> <!--End nav tabs--> <!-- Tabs Content --> <!--================================--> <div class="tab-content"> <div class="tab-pane fade in active" id="demo-asd-tab-1"> <p class="pad-hor text-semibold text-main"> <span class="pull-right badge badge-success">{{ contact_info_list.MemberCount }}</span> Friends </p> <!--Works--> <div class="list-group bg-trans"> {% for item in contact_info_list.MemberList %} <a href="#" for="{{ item.UserName }}" class="list-group-item"> <span class="badge badge-purple badge-icon badge-fw pull-left"></span> {{ item.NickName }} </a> {% endfor %} </div> </div> </div> </div> <div class="nano-pane" style="display: none;"><div class="nano-slider" style="height: 4059px; transform: translate(0px, 0px);"></div></div></div> </div> </aside> </div> </div> </div> </div> </div> </body> </html> <script> function sendMsg(ths){ var sel_tag = $(".list-group").find(".active") if(sel_tag.length==0){ return false; } var msg = $(ths).parents(".panel-footer").find(".chat-input").val(); var sendMsg={ 'ToUserName':sel_tag.attr("for"), 'Type':1, 'Content':msg, 'csrfmiddlewaretoken':'{{ csrf_token }}' } $.ajax({ url:"send-msg.html", data:sendMsg, type:"POST", dataType:"json", success:function(callback){ if (callback.code==200){ var dt = new Date() var now_time = dt.toLocaleString(); console.log(callback); var li = '<li class="mar-btm"><div class="media-right"><img src="'+callback.headImgUrl+'" class="img-circle img-sm" alt="Profile Picture"></div>'; li += '<div class="media-body pad-hor speech-right"><div class="speech"><a href="#" class="media-heading">'+callback.username+'</a>'; li += '<p>'+msg+'</p>'; li += '<p class="speech-time">'; li += '<i class="demo-pli-clock icon-fw"></i>'+now_time; li += '</p></div></div></li>'; $(ths).parents(".widget-body").find(".list-unstyled").append(li); $(ths).parents(".panel-footer").find(".chat-input").val(""); } } }) } </script>