python 實現微信自動回復(自動聊天)


原文地址(本人):https://blog.csdn.net/a5878989/article/details/54974249

介紹

微信自動回復其實主要就是登錄,接收消息,回復消息三個功能,微信沒有提供公開的API,但是可以分析網頁版微信通信原理,通過模擬瀏覽器來實現需要的功能。

下面將給出微信網頁版通信原理以及Python代碼。

分析

-獲取uuid:

GET https://login.wx.qq.com/jslogin?appid=wx782c26e4c19acffb&redirect_uri=https%3A%2F%2Fwx.qq.com%2Fcgi-bin%2Fmmwebwx-bin%2Fwebwxnewloginpage&fun=new&lang=zh_CN&_=1486743163000

Param     _   (13位時間戳)

Response   window.QRLogin.code = 200; window.QRLogin.uuid = "4YyQFP2Daw=="; 

-獲取二維碼:

GET  https://login.weixin.qq.com/qrcode/4YyQFP2Daw==

Param 4YyQFP2Daw==  即上面的uuid

Response  二維碼圖片

-監聽是否掃描二維碼以及是否確認登錄:

GET   https://login.wx.qq.com/cgi-bin/mmwebwx-bin/login?loginicon=true&uuid=4YyQFP2Daw==

Param uuid 同上

Response

window.code=200;window.redirect_uri="https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxnewloginpage?ticket=ARxD7GSdBYtNHOxhK0BF0ek-@qrticket_0&uuid=4YyQFP2Daw==&lang=zh_CN&scan=1486743186";

code:

408 無響應

201 掃描二維碼但沒有登錄(此時響應數據中還包含用戶頭像圖片base64編碼的字符串,UserAvatar)

200 登錄

redirect_uri  為接下來需要請求的地址

-獲取后續訪問所需要的key等

GET https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxnewloginpage?ticket=ARxD7GSdBYtNHOxhK0BF0ek-@qrticket_0&uuid=4YyQFP2Daw==&lang=zh_CN&scan=1486743186p

Param  URL為上次返回的redirect_uri  參數已經帶上了                                                                              

Response 

<error>
   <ret>0</ret>
   <message/>
   <skey>@crypt_828c27e0_e98d62f6954235194f2b1252943f25ad</skey>
   <wxsid>0zEvAdWKm9ZZgYVn</wxsid>
   <wxuin>1564527827</wxuin>
   <pass_ticket>OLxGHwqL%2BWNArxvXaqjDy06qzdrSojq6DJwiBF19sgw2CibZSJBv1WwOXAfKnLIg</pass_ticket>
   <isgrayscale>1</isgrayscale>
< /error>

 

-初始化

POST https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxinit?r=-485039295&lang=zh_CN&pass_ticket=OLxGHwqL%2BWNArxvXaqjDy06qzdrSojq6DJwiBF19sgw2CibZSJBv1WwOXAfKnLIg

Param  r ( -    +  9位隨機數),pass_ticket,{"BaseRequest": {"Uin": "1564527827", "Skey": "@crypt_828c27e0_e98d62f6954235194f2b1252943f25ad", "DeviceID": "e924318232435460", "Sid": "0zEvAdWKm9ZZgYVn"}} 第三個參數其中為json數據,DeviceID為(e + 15位隨機數)

Response  返回json,包含用戶自己的信息,最近聯系人,訂閱的公眾號消息等等;這里只需要關注 UserName=@821c154488cdddbfb04141aa8f681174305d21d67a24cfd6eca3e77a152e52ff  每位用戶都有一個UserName,但是每次登陸UserName都是重新分配的,SyncKey 為一組key ,后面接收消息需要將其作為參數,同時每次接收接收消息時,也會返回一組SyncKey作為在下一次請求的參數,以此類推

 

-狀態檢查

這里會建立一個長連接,每次連接大約20秒左右,若新消息,手機端發出退出網頁登錄指令,或者狀態異常會返回特定的狀態碼

GET https://wx.qq.com/cgi-bin/mmwebwx-bin/synccheck?r=1486743215000&skey=@crypt_828c27e0_e98d62f6954235194f2b1252943f25ad&sid=0zEvAdWKm9Z

ZgYVn&uin=1564527827&deviceid=e891796429.95749&synckey=1_660530221%7C2_660530488%7C3_660530485%7C1000_1486721341&_=1486740215000

Param  

r(時間戳),skey,sid,uin,deviceid,synckey(將SyncKey中的多組key 以 key1_value1|key2_value2 的形式拼接成字符串如:3_660530485|1000_1486721341),_ (時間戳)

Response 

window.synccheck={retcode:"0",selector:"2"} 

retcode=0 正常 ,1101 退出登錄,1102 會話異常  , selector= 0 無變化 2or6 有消息

 

-接收消息

若狀態檢查到有新消息,則請求消息

POST https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxsync?sid=0zEvAdWKm9ZZgYVn&skey=@crypt_828c27e0_e98d62f6954235194f2b1252943f25ad&lang=zh_CN&pass_ticket=OLxGHwqL%2BWNArxvXaqjDy06qzdrSojq6DJwiBF19sgw2CibZSJBv1WwOXAfKnLIg

Param  

sid,skey,pass_ticket 以及 json數據 {"SyncKey": {"Count": 4, "List": [{"Key": 1, "Val": 660530221}, {"Key": 2, "Val": 660530488}, {"Key": 3, "Val": 660530485}, {"Key": 1000, "Val": 1486721341}]}, "BaseRequest": {"Sid": "0zEvAdWKm9ZZgYVn", "Skey": "@crypt_828c27e0_e98d62f6954235194f2b1252943f25ad", "DeviceID": "e141257009.76972", "Uin": "1564527827"}, "rr": "-888098293"} 其中rr (- + 9位隨機數)

Response 

json數據包含消息的所有信息,其中關注 FromUserName=@821c154488cdddbfb04141aa8f681174305d21d67a24cfd6eca3e77a152e52ff  消息發送者以及 Content 消息內容

-發送消息

POST https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxsendmsg?lang=zh_CN&pass_ticket=0%2BoUqOWdYEen6oDVFEIv5ncIIaJcWs1LeSi69C8tUTgcp36azGAl6a8uT02PiaHu

Param   pass_ticket, json數據{"Msg": {"FromUserName": "@9e718026650771acd6d759922e000fafceaa1a5fda83aea7b3b70bc1bd6c3774", "LocalID": "14867488199507670", "ClientMsgId": "14867488199507670", "ToUserName": "@9e718026650771acd6d759922e000fafceaa1a5fda83aea7b3b70bc1bd6c3774", "Content": "消息內容", "Type": "1"}, "BaseRequest": {"Sid": "5Qn7rswOtPRHFw92", "Skey": "@crypt_828c27e0_ad386b3d4d68a282eda03d7d5b2d3104", "DeviceID": "e397471984070243", "Uin": "1564527827"}, "Scene": "0"} 其中LocalID,ClientMsgId 為13位時間戳加上5位隨機數

Response  返回響應的狀態碼,發送成功會返回 LocalID 和 ClientMsgID

以上就是我們需要的知道的,當然其他比如讀取所有聯系人等都是大同小異,這里就不多贅述了。

到這里還有關鍵的一步,那就是如何根據收到的消息自動回復,當然是接入其他可以聊天的程序了,這里為了方便我使用的simsimi聊天機器人的外鏈,它不需要登錄等操作,連request  header 都不用偽造^0^,直接將接受的消息post過去,將返回的消息作為微信回復消息;當然也可以接入更智能的機器人。

-獲取自動回復的消息

POST http://www.niurenqushi.com/api/simsimi/

Param   txt (發送的消息)

Response  {"code":100000,"text":"消息"}

代碼

運行以下代碼,會自動彈出二維碼圖片,手機掃碼登錄之后開始運行,手機端發送退出登錄指令時結束。

重點在於流程和思路,代碼也比較糙,注釋也就不加了,多指教^0dfd^

# -*- coding:utf-8 -*-
#author:fengw
import urllib,urllib2,cStringIO,re,sys,os,cookielib,ssl,requests,time,json,random,threading,warnings
from PIL import Image
from matplotlib import pyplot as plt
import xml.etree.cElementTree as et 

reload(sys)
sys.setdefaultencoding('utf-8')
warnings.filterwarnings("ignore")

def get_device_id():
    return 'e'+str(random.random()*10000000000)[0:10]+str(random.random()*100000)[0:5]
def qrcode_img():
    response=urllib2.urlopen(QRCODE_KEY_URL).read()
    p=re.compile(r'(\d+(\.\d+)?)')
    code=p.findall(response)[0][0]
    if  code =='200':
        p=re.compile(r'\"(.*)\"')
        qrcode_key=p.findall(response)[0]
        qrcode_img_url=QRCODE_IMG_BASE_URL+qrcode_key
        global CHECK_LOGIN_STATUS_BASE_URL
        CHECK_LOGIN_STATUS_BASE_URL=CHECK_LOGIN_STATUS_BASE_URL+qrcode_key
        qrcode_img=Image.open(cStringIO.StringIO(urllib2.urlopen(qrcode_img_url).read()))
        plt.ion()
        plt.figure()
        plt.imshow(qrcode_img)
        plt.figure()
        plt.close(2)        
    else :
        print 'sorry,request qrcode failed...'
        time.sleep(2)
        os._exit(0)
        
def listen_login():
    run=True
    times=0
    msg='please scan the qrcode'
    while run:
        times+=1
        print msg
        response=urllib2.urlopen(CHECK_LOGIN_STATUS_BASE_URL).read()
        p=re.compile(r'(\d+(\.\d+)?)')
        code=p.findall(response)[0][0]
        if  code=='201':
            msg= 'please login...'
            plt.close()
        if  code=='200':
            run=False
            plt.close()
            print  'login sucess,running....'
            p=re.compile(r'\"(.*)\"')
            redirect_url=p.findall(response)[0]
            response=conn.get(url=redirect_url,allow_redirects=False,verify=False)
            msg=response.text
            global ret,message,skey,wxsid,wxuin,pass_ticket,isgrayscale

            xml=et.fromstring(msg)
            ret=xml[0].text
            message=xml[1].text
            skey=xml[2].text
            wxsid=xml[3].text
            wxuin=xml[4].text
            pass_ticket=xml[5].text
            isgrayscale=xml[6].text
        
        
        if  times==20:
            run=False
def update_synckey(msg):
    global synckey,syncheck_key
    synckey=str(msg['SyncKey']).replace("u'","'")        
    for k_v in msg['SyncKey']['List']:
        syncheck_key+='|'+str(k_v['Key'])+'_'+str(k_v['Val'])
    syncheck_key=syncheck_key[1:]
def  wx_init():    
    url='https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxinit?r=-485039295&lang=zh_CN&pass_ticket='+pass_ticket    
    data={'BaseRequest':{'DeviceID':'%s'%get_device_id(),'Sid':'%s'%wxsid,'Skey':'%s'%skey,'Uin':'%s'%wxuin}}
    res=conn.post(url=url,headers=headers,data=json.dumps(data),verify=False)
    response=res.text    
    msg=json.loads(response)
    global user
    user=msg['User']['UserName']
    update_synckey(msg)
    
    
def get_contact_list():
    base_url='https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxgetcontact?lang=zh_CN&seq=0'
    base_url+='&pass_ticket='+pass_ticket+'&r='+str(int(time.time())*1000)+'&skey='+skey
    response=urllib2.urlopen(base_url).read()
    data=json.loads(response)
    f=open(r'd:/linklist.txt','w')
    for friend in data['MemberList']:
        msg=friend['NickName']+","+friend['RemarkName']+"\n"
        f.write(msg.encode('utf-8'))
    f.close()
    
def get_auto_reply(send_msg):
    url='http://www.niurenqushi.com/api/simsimi/'
    data={'txt':'%s'%send_msg}
    res=conn.post(url=url,data=data)
    res.encoding='utf-8'
    return  json.loads(res.text,'')['text']
    

def reply_msg(content,touser):    
    url='https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxsendmsg?lang=zh_CN&pass_ticket='+pass_ticket
    #touser='filehelper'
    ClientMsgId=str(int(time.time()))+str(random.random()*10000000)[0:7]
    print 'recive msg :',content
    sendmsg=get_auto_reply(content)
    data={'BaseRequest':{'Uin':'%s'%wxuin,'Sid':'%s'%wxsid,'Skey':'%s'%skey,'DeviceID':'%s'%get_device_id()},'Msg':{'ClientMsgId':'%s'%ClientMsgId,'Content':'%s'%sendmsg.encode('utf-8'),'FromUserName':'%s'%user,'LocalID':ClientMsgId,'ToUserName':'%s'%touser,'Type':'1'},'Scene':'0'}
    data=json.dumps(data,ensure_ascii=False)
    res=conn.post(url=url,headers=headers,data=data.encode('utf-8'),verify=False)
    print 'reply:',sendmsg
def recive_msg():
    base_url='https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxsync'
    base_url+='?sid='+wxsid+'&skey='+skey+'&lang='+'zh_CN'+'&pass_ticket='+pass_ticket
    while True:
        try:
            rr='-'+str(random.random()*1000000000)[0:9]
            data={'BaseRequest':{'Uin':'%s'%wxuin,'Sid':'%s'%wxsid,'Skey':'%s'%skey,'DeviceID':'%s'%get_device_id()},'SyncKey':eval(synckey),'rr':'%s'%rr}
            res=conn.post(url=base_url,headers=headers,data=json.dumps(data),verify=False)
            res.encoding='utf-8'
            response=res.text        
            if response==None:
                continue
            data=json.loads(response)
            update_synckey(data)
            for msg in data['AddMsgList']:
                content=msg['Content']
                fromuser=msg['FromUserName']
                if fromuser==user:
                    continue
                if content[0:4]=='<':                
                    continue
                #print 'recived msg:',content.decode('unicode_escape'),'from user :',fromuser        
                threading.Thread(target=reply_msg,args=(content,fromuser)).start()
            time.sleep(2)
        except Exception  as e :
            pass
        
def sync_check():
    listen=True
    base_url='https://webpush.wx.qq.com/cgi-bin/mmwebwx-bin/synccheck'
    base_url+='?r='+str(int(time.time())*1000)+'&skey='+skey+'&sid='+wxsid+'&uin='+wxuin+'&deviceid='+get_device_id()+'&synckey='+syncheck_key+'&_='+str(int(time.time())*1000-3000000)
    request = urllib2.Request(url=base_url, headers=headers)
    while listen:
        try:
            res=conn.get(url=base_url,headers=headers,verify=False)
            response=res.text
            p=re.compile(r'(\d+(\.\d+)?)')
            retcode=p.findall(response)[0][0]
            if retcode=='1101' or retcode=='1102':
                print 'login out ...'
                listen=False
                os._exit(0)
            time.sleep(2)
        except Exception:
            pass
if __name__ == '__main__':        
    ssl._create_default_https_context = ssl._create_unverified_context
    QRCODE_KEY_URL='https://login.wx.qq.com/jslogin?appid=wx782c26e4c19acffb&redirect_uri=https%3A%2F%2Fwx.qq.com%2Fcgi-bin%2Fmmwebwx-bin%2Fwebwxnewloginpage&fun=new&lang=zh_CN&_='+str(int(time.time())*1000)
    QRCODE_IMG_BASE_URL='https://login.weixin.qq.com/qrcode/'
    CHECK_LOGIN_STATUS_BASE_URL='https://login.wx.qq.com/cgi-bin/mmwebwx-bin/login?loginicon=true&uuid='
    ret,message,skey,wxsid,wxuin,pass_ticket,isgrayscale='','','','','','',''
    synckey,user='',''
    syncheck_key=''
    cookie=cookielib.CookieJar()
    handler=urllib2.HTTPCookieProcessor(cookie)
    debug_h=urllib2.HTTPSHandler(debuglevel=0)
    opener=urllib2.build_opener(handler,debug_h)
    urllib2.install_opener(opener)
    conn=requests.session()
    headers = {     'Host': 'wx.qq.com',
                    'Connection': 'keep-alive',
                    'Accept': 'application/json, text/plain, */*',
                    'Origin': 'https://wx.qq.com',
                    'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:51.0) Gecko/20100101 Firefox/51.0',
                    'Content-Type': 'application/json;',                
                    'Accept-Encoding': 'gzip, deflate, br',
                    'Accept-Language': 'zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3'
                    }
    #獲取微信二維碼並顯示                    
    qrcode_img()
    #監聽用戶掃描二維碼和登錄動作
    listen_login()
    #微信初始化
    wx_init()
    #開啟子線程監聽登錄狀態
    check_status_task=threading.Thread(target=sync_check)
    check_status_task.start()
    #get_contact_list()
    #主線程監聽消息
    recive_msg()


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM