Browse Source

Refactor and add group message support.

liuwons 9 years ago
parent
commit
ed6e534fcd
3 changed files with 268 additions and 229 deletions
  1. 2 1
      .gitignore
  2. 2 4
      README.md
  3. 264 224
      wxbot.py

+ 2 - 1
.gitignore

@@ -63,5 +63,6 @@ target/
 
 
 
 
 *.json
 *.json
-
 .idea/
 .idea/
+
+qr.png

+ 2 - 4
README.md

@@ -57,8 +57,6 @@ python test.py
 
 
 程序运行之后,会在当前目录下生成二维码图片文件 qr.png ,用微信扫描此二维码并按操作指示确认登录网页微信。
 程序运行之后,会在当前目录下生成二维码图片文件 qr.png ,用微信扫描此二维码并按操作指示确认登录网页微信。
 
 
-![1](img/1.png)
-
 如果运行在Linux下,还可以通过设置WXBot对象的conf['qr']为'tty'的方式直接在终端打印二维码(此方法只能在Linux终端下使用),效果如下:
 如果运行在Linux下,还可以通过设置WXBot对象的conf['qr']为'tty'的方式直接在终端打印二维码(此方法只能在Linux终端下使用),效果如下:
 
 
 ![login_on_ubuntu](img/login_on_ubuntu.png)
 ![login_on_ubuntu](img/login_on_ubuntu.png)
@@ -81,7 +79,7 @@ handle_msg_all函数的参数msg是代表一条消息的字典。字段的内容
 | msg_type_id | 整数,消息类型,具体解释可以查看消息类型表 |
 | msg_type_id | 整数,消息类型,具体解释可以查看消息类型表 |
 | msg_id | 字符串,消息id |
 | msg_id | 字符串,消息id |
 | content | 字典,消息内容,具体含有的字段请参考消息类型表,一般含有type(数据类型)与data(数据内容)字段,type与data的对应关系可以参考数据类型表 |
 | content | 字典,消息内容,具体含有的字段请参考消息类型表,一般含有type(数据类型)与data(数据内容)字段,type与data的对应关系可以参考数据类型表 |
-| user | 字典,消息来源,字典包含name(发送者名称)字段与id(发送者id)字段,都是字符串  |
+| user | 字典,消息来源,字典包含name(发送者名称,如果是群则为群名称,如果为微信号,有备注则为备注名,否则为微信号或者群昵称)字段与id(发送者id)字段,都是字符串  |
 
 
 
 
 消息类型表:
 消息类型表:
@@ -136,7 +134,7 @@ WXBot对象还含有一些可以利用的方法:
 | get_head_img(id) | 获取用户头像并保存到本地文件 img_[id].jpg,id为用户id(Web微信数据) |
 | get_head_img(id) | 获取用户头像并保存到本地文件 img_[id].jpg,id为用户id(Web微信数据) |
 | get_msg_img(msgid) | 获取图像消息并保存到本地文件 img_[msgid].jpg, msgid为消息id(Web微信数据) |
 | get_msg_img(msgid) | 获取图像消息并保存到本地文件 img_[msgid].jpg, msgid为消息id(Web微信数据) |
 | get_voice(msgid) | 获取语音消息并保存到本地文件 voice_[msgid].mp3, msgid为消息id(Web微信数据) |
 | get_voice(msgid) | 获取语音消息并保存到本地文件 voice_[msgid].mp3, msgid为消息id(Web微信数据) |
-| get_user_remark_name(uid) | 获取好友的备注名,没有备注名则获取好友微信号, uid为好友的用户id(Web微信数据) |
+| get_account_name(uid) | 获取微信id对应的名称,返回一个可能包含remark_name(备注名),nickname(昵称), display_name(群名称)的字典|
 | send_msg_by_uid(word, dst) | 向好友发送消息,word为消息字符串,dst为好友用户id(Web微信数据) |
 | send_msg_by_uid(word, dst) | 向好友发送消息,word为消息字符串,dst为好友用户id(Web微信数据) |
 | send_msg(name, word, isfile) | 向好友发送消息,name为好友的备注名或者好友微信号,isfile为False时word为消息,isfile为True时word为文件路径(此时向好友发送文件里的每一行) |
 | send_msg(name, word, isfile) | 向好友发送消息,name为好友的备注名或者好友微信号,isfile为False时word为消息,isfile为True时word为文件路径(此时向好友发送文件里的每一行) |
 | is_contact(uid) | 判断id为uid的账号是否是本帐号的好友,返回True(是)或False(不是) |
 | is_contact(uid) | 判断id为uid的账号是否是本帐号的好友,返回True(是)或False(不是) |

+ 264 - 224
wxbot.py

@@ -26,125 +26,20 @@ class WXBot:
         self.sync_key_str = ''
         self.sync_key_str = ''
         self.sync_key = []
         self.sync_key = []
         self.user = {}
         self.user = {}
-        self.member_list = []
+        self.account_info = {}
+        self.member_list = []   # all kind of accounts: contacts, public accounts, groups, special accounts
         self.contact_list = []  # contact list
         self.contact_list = []  # contact list
         self.public_list = []   # public account list
         self.public_list = []   # public account list
         self.group_list = []    # group chat list
         self.group_list = []    # group chat list
         self.special_list = []  # special list account
         self.special_list = []  # special list account
+        self.group_members = {}  # members of all groups
         self.sync_host = ''
         self.sync_host = ''
         self.session = requests.Session()
         self.session = requests.Session()
         self.session.headers.update({'User-Agent': 'Mozilla/5.0 (X11; Linux i686; U;) Gecko/20070322 Kazehakase/0.4.5'})
         self.session.headers.update({'User-Agent': 'Mozilla/5.0 (X11; Linux i686; U;) Gecko/20070322 Kazehakase/0.4.5'})
 
 
         self.conf = {'qr': 'png'}
         self.conf = {'qr': 'png'}
 
 
-    def get_uuid(self):
-        url = 'https://login.weixin.qq.com/jslogin'
-        params = {
-            'appid': 'wx782c26e4c19acffb',
-            'fun': 'new',
-            'lang': 'zh_CN',
-            '_': int(time.time())*1000 + random.randint(1, 999),
-        }
-        r = self.session.get(url, params=params)
-        r.encoding = 'utf-8'
-        data = r.text
-        regx = r'window.QRLogin.code = (\d+); window.QRLogin.uuid = "(\S+?)"'
-        pm = re.search(regx, data)
-        if pm:
-            code = pm.group(1)
-            self.uuid = pm.group(2)
-            return code == '200'
-        return False
-
-    def gen_qr_code(self, qr_file_path):
-        string = 'https://login.weixin.qq.com/l/' + self.uuid
-        qr = pyqrcode.create(string)
-        if self.conf['qr'] == 'png':
-            qr.png(qr_file_path)
-        elif self.conf['qr'] == 'tty':
-            print(qr.terminal(quiet_zone=1))
-            
-    def wait4login(self, tip):
-        time.sleep(tip)
-        url = 'https://login.weixin.qq.com/cgi-bin/mmwebwx-bin/login?tip=%s&uuid=%s&_=%s' \
-              % (tip, self.uuid, int(time.time()))
-        r = self.session.get(url)
-        r.encoding = 'utf-8'
-        data = r.text
-        param = re.search(r'window.code=(\d+);', data)
-        code = param.group(1)
-
-        if code == '201':
-            return True
-        elif code == '200':
-            param = re.search(r'window.redirect_uri="(\S+?)";', data)
-            redirect_uri = param.group(1) + '&fun=new'
-            self.redirect_uri = redirect_uri
-            self.base_uri = redirect_uri[:redirect_uri.rfind('/')]
-            return True
-        elif code == '408':
-            print '[ERROR] WeChat login timeout .'
-        else:
-            print '[ERROR] WeChat login exception .'
-        return False
-
-    def login(self):
-        r = self.session.get(self.redirect_uri)
-        r.encoding = 'utf-8'
-        data = r.text
-        doc = xml.dom.minidom.parseString(data)
-        root = doc.documentElement
-
-        for node in root.childNodes:
-            if node.nodeName == 'skey':
-                self.skey = node.childNodes[0].data
-            elif node.nodeName == 'wxsid':
-                self.sid = node.childNodes[0].data
-            elif node.nodeName == 'wxuin':
-                self.uin = node.childNodes[0].data
-            elif node.nodeName == 'pass_ticket':
-                self.pass_ticket = node.childNodes[0].data
-
-        if '' in (self.skey, self.sid, self.uin, self.pass_ticket):
-            return False
-
-        self.base_request = {
-            'Uin': self.uin,
-            'Sid': self.sid,
-            'Skey': self.skey,
-            'DeviceID': self.device_id,
-            }
-        return True
-
-    def init(self):
-        url = self.base_uri + '/webwxinit?r=%i&lang=en_US&pass_ticket=%s' % (int(time.time()), self.pass_ticket)
-        params = {
-            'BaseRequest': self.base_request
-        }
-        r = self.session.post(url, data=json.dumps(params))
-        r.encoding = 'utf-8'
-        dic = json.loads(r.text)
-        self.sync_key = dic['SyncKey']
-        self.user = dic['User']
-        self.sync_key_str = '|'.join([str(keyVal['Key']) + '_' + str(keyVal['Val'])
-                                      for keyVal in self.sync_key['List']])
-        return dic['BaseResponse']['Ret'] == 0
-
-    def status_notify(self):
-        url = self.base_uri + '/webwxstatusnotify?lang=zh_CN&pass_ticket=%s' % self.pass_ticket
-        self.base_request['Uin'] = int(self.base_request['Uin'])
-        params = {
-            'BaseRequest': self.base_request,
-            "Code": 3,
-            "FromUserName": self.user['UserName'],
-            "ToUserName": self.user['UserName'],
-            "ClientMsgId": int(time.time())
-        }
-        r = self.session.post(url, data=json.dumps(params))
-        r.encoding = 'utf-8'
-        dic = json.loads(r.text)
-        return dic['BaseResponse']['Ret'] == 0
-
+    # Get information of all contacts of current account.
     def get_contact(self):
     def get_contact(self):
         url = self.base_uri + '/webwxgetcontact?pass_ticket=%s&skey=%s&r=%s' \
         url = self.base_uri + '/webwxgetcontact?pass_ticket=%s&skey=%s&r=%s' \
                               % (self.pass_ticket, self.skey, int(time.time()))
                               % (self.pass_ticket, self.skey, int(time.time()))
@@ -172,15 +67,26 @@ class WXBot:
         for contact in self.member_list:
         for contact in self.member_list:
             if contact['VerifyFlag'] & 8 != 0:  # public account
             if contact['VerifyFlag'] & 8 != 0:  # public account
                 self.public_list.append(contact)
                 self.public_list.append(contact)
+                self.account_info[contact['UserName']] = {'type': 'public', 'info': contact}
             elif contact['UserName'] in special_users:  # special account
             elif contact['UserName'] in special_users:  # special account
                 self.special_list.append(contact)
                 self.special_list.append(contact)
+                self.account_info[contact['UserName']] = {'type': 'special', 'info': contact}
             elif contact['UserName'].find('@@') != -1:  # group
             elif contact['UserName'].find('@@') != -1:  # group
                 self.group_list.append(contact)
                 self.group_list.append(contact)
+                self.account_info[contact['UserName']] = {'type': 'group', 'info': contact}
             elif contact['UserName'] == self.user['UserName']:  # self
             elif contact['UserName'] == self.user['UserName']:  # self
+                self.account_info[contact['UserName']] = {'type': 'self', 'info': contact}
                 pass
                 pass
             else:
             else:
                 self.contact_list.append(contact)
                 self.contact_list.append(contact)
 
 
+        self.group_members = self.batch_get_group_members()
+
+        for group in self.group_members:
+            for member in self.group_members[group]:
+                if member['UserName'] not in self.account_info:
+                    self.account_info[member['UserName']] = {'type': 'group_member', 'info': member, 'group': group}
+
         if self.DEBUG:
         if self.DEBUG:
             with open('contact_list.json', 'w') as f:
             with open('contact_list.json', 'w') as f:
                 f.write(json.dumps(self.contact_list))
                 f.write(json.dumps(self.contact_list))
@@ -192,122 +98,61 @@ class WXBot:
                 f.write(json.dumps(self.public_list))
                 f.write(json.dumps(self.public_list))
             with open('member_list.json', 'w') as f:
             with open('member_list.json', 'w') as f:
                 f.write(json.dumps(self.member_list))
                 f.write(json.dumps(self.member_list))
-
+            with open('group_users.json', 'w') as f:
+                f.write(json.dumps(self.group_members))
+            with open('account_info.json', 'w') as f:
+                f.write(json.dumps(self.account_info))
         return True
         return True
 
 
-    def batch_get_contact(self):
+    # Get information of accounts in all groups at once.
+    def batch_get_group_members(self):
         url = self.base_uri + '/webwxbatchgetcontact?type=ex&r=%s&pass_ticket=%s' % (int(time.time()), self.pass_ticket)
         url = self.base_uri + '/webwxbatchgetcontact?type=ex&r=%s&pass_ticket=%s' % (int(time.time()), self.pass_ticket)
         params = {
         params = {
             'BaseRequest': self.base_request,
             'BaseRequest': self.base_request,
             "Count": len(self.group_list),
             "Count": len(self.group_list),
-            "List": [{"UserName": g['UserName'], "EncryChatRoomId":""} for g in self.group_list]
-        }
-        r = self.session.post(url, data=params)
-        r.encoding = 'utf-8'
-        dic = json.loads(r.text)
-        return dic
-
-    def test_sync_check(self):
-        for host in ['webpush', 'webpush2']:
-            self.sync_host = host
-            retcode = self.sync_check()[0]
-            if retcode == '0':
-                return True
-        return False
-
-    def sync_check(self):
-        params = {
-            'r': int(time.time()),
-            'sid': self.sid,
-            'uin': self.uin,
-            'skey': self.skey,
-            'deviceid': self.device_id,
-            'synckey': self.sync_key_str,
-            '_': int(time.time()),
-        }
-        url = 'https://' + self.sync_host + '.weixin.qq.com/cgi-bin/mmwebwx-bin/synccheck?' + urllib.urlencode(params)
-        r = self.session.get(url)
-        r.encoding = 'utf-8'
-        data = r.text
-        pm = re.search(r'window.synccheck=\{retcode:"(\d+)",selector:"(\d+)"\}', data)
-        retcode = pm.group(1)
-        selector = pm.group(2)
-        return [retcode, selector]
-
-    def sync(self):
-        url = self.base_uri + '/webwxsync?sid=%s&skey=%s&lang=en_US&pass_ticket=%s' \
-                              % (self.sid, self.skey, self.pass_ticket)
-        params = {
-            'BaseRequest': self.base_request,
-            'SyncKey': self.sync_key,
-            'rr': ~int(time.time())
+            "List": [{"UserName": group['UserName'], "EncryChatRoomId":""} for group in self.group_list]
         }
         }
         r = self.session.post(url, data=json.dumps(params))
         r = self.session.post(url, data=json.dumps(params))
         r.encoding = 'utf-8'
         r.encoding = 'utf-8'
         dic = json.loads(r.text)
         dic = json.loads(r.text)
-        if dic['BaseResponse']['Ret'] == 0:
-            self.sync_key = dic['SyncKey']
-            self.sync_key_str = '|'.join([str(keyVal['Key']) + '_' + str(keyVal['Val'])
-                                          for keyVal in self.sync_key['List']])
-        return dic
-
-    def get_icon(self, uid):
-        url = self.base_uri + '/webwxgeticon?username=%s&skey=%s' % (uid, self.skey)
-        r = self.session.get(url)
-        data = r.content
-        fn = 'img_'+uid+'.jpg'
-        with open(fn, 'wb') as f:
-            f.write(data)
-        return fn
-
-    def get_head_img(self, uid):
-        url = self.base_uri + '/webwxgetheadimg?username=%s&skey=%s' % (uid, self.skey)
-        r = self.session.get(url)
-        data = r.content
-        fn = 'img_'+uid+'.jpg'
-        with open(fn, 'wb') as f:
-            f.write(data)
-        return fn
-
-    def get_msg_img_url(self, msgid):
-        return self.base_uri + '/webwxgetmsgimg?MsgID=%s&skey=%s' % (msgid, self.skey)
-
-    def get_msg_img(self, msgid):
-        url = self.base_uri + '/webwxgetmsgimg?MsgID=%s&skey=%s' % (msgid, self.skey)
-        r = self.session.get(url)
-        data = r.content
-        fn = 'img_'+msgid+'.jpg'
-        with open(fn, 'wb') as f:
-            f.write(data)
-        return fn
-
-    def get_voice_url(self, msgid):
-        return self.base_uri + '/webwxgetvoice?msgid=%s&skey=%s' % (msgid, self.skey)
-
-    def get_voice(self, msgid):
-        url = self.base_uri + '/webwxgetvoice?msgid=%s&skey=%s' % (msgid, self.skey)
-        r = self.session.get(url)
-        data = r.content
-        fn = 'voice_'+msgid+'.mp3'
-        with open(fn, 'wb') as f:
-            f.write(data)
-        return fn
-
-    # Get the NickName or RemarkName of an user by user id
-    def get_user_remark_name(self, uid):
-        name = 'unknown group' if uid[:2] == '@@' else 'stranger'
-        for member in self.member_list:
-            if member['UserName'] == uid:
-                name = member['RemarkName'] if member['RemarkName'] else member['NickName']
+        group_members = {}
+        for group in dic['ContactList']:
+            gid = group['UserName']
+            members = group['MemberList']
+            group_members[gid] = members
+        return group_members
+
+    def get_account_info(self, uid):
+        if uid in self.account_info:
+            return self.account_info[uid]
+        else:
+            return None
+
+    def get_account_name(self, uid):
+        info = self.get_account_info(uid)
+        if info is None:
+            return 'unknown'
+        info = info['info']
+        name = {}
+        if 'RemarkName' in info and info['RemarkName']:
+            name['remark_name'] = info['RemarkName']
+        if 'NickName' in info and info['NickName']:
+            name['nickname'] = info['NickName']
+        if 'DisplayName' in info and info['DisplayName']:
+            name['display_name'] = info['DisplayName']
         return name
         return name
 
 
-    # Get user id of an user
-    def get_user_id(self, name):
-        for member in self.member_list:
-            if name == member['RemarkName'] or name == member['NickName'] or name == member['UserName']:
-                return member['UserName']
-        return None
+    @staticmethod
+    def get_prefer_name(name):
+        if 'remark_name' in name:
+            return name['remark_name']
+        if 'display_name' in name:
+            return name['display_name']
+        if 'nickname' in name:
+            return name['nickname']
+        return 'unknown'
 
 
+    # Get the relationship of a account and current user.
     def get_user_type(self, wx_user_id):
     def get_user_type(self, wx_user_id):
         for account in self.contact_list:
         for account in self.contact_list:
             if wx_user_id == account['UserName']:
             if wx_user_id == account['UserName']:
@@ -321,6 +166,10 @@ class WXBot:
         for account in self.group_list:
         for account in self.group_list:
             if wx_user_id == account['UserName']:
             if wx_user_id == account['UserName']:
                 return 'group'
                 return 'group'
+        for group in self.group_members:
+            for member in self.group_members[group]:
+                if member['UserName'] == wx_user_id:
+                    return 'group_member'
         return 'unknown'
         return 'unknown'
 
 
     def is_contact(self, uid):
     def is_contact(self, uid):
@@ -343,10 +192,10 @@ class WXBot:
 
 
     '''
     '''
     msg:
     msg:
+        msg_id
+        msg_type_id
         user
         user
-        type
-        data
-        detail
+        content
     '''
     '''
     def handle_msg_all(self, msg):
     def handle_msg_all(self, msg):
         pass
         pass
@@ -382,7 +231,7 @@ class WXBot:
             content = content[sp:]
             content = content[sp:]
             content = content.replace('<br/>', '')
             content = content.replace('<br/>', '')
             uid = uid[:-1]
             uid = uid[:-1]
-            msg_content['user'] = {'id': uid, 'name': self.get_user_remark_name(uid)}
+            msg_content['user'] = {'id': uid, 'name': self.get_prefer_name(self.get_account_name(uid))}
             if self.DEBUG:
             if self.DEBUG:
                 print msg_content['user']['name']
                 print msg_content['user']['name']
         else:                   # Self, Contact, Special, Public, Unknown
         else:                   # Self, Contact, Special, Public, Unknown
@@ -509,20 +358,17 @@ class WXBot:
                 user['name'] = 'file_helper'
                 user['name'] = 'file_helper'
             elif msg['FromUserName'][:2] == '@@':  # Group
             elif msg['FromUserName'][:2] == '@@':  # Group
                 msg_type_id = 3
                 msg_type_id = 3
-                user['name'] = self.get_user_remark_name(user['id'])
-                if self.DEBUG:
-                    print '[From] %s' % user['name']
+                user['name'] = self.get_prefer_name(self.get_account_name(user['id']))
             elif self.is_contact(msg['FromUserName']):  # Contact
             elif self.is_contact(msg['FromUserName']):  # Contact
                 msg_type_id = 4
                 msg_type_id = 4
-                user['name'] = self.get_user_remark_name(user['id'])
+                user['name'] = self.get_prefer_name(self.get_account_name(user['id']))
             elif self.is_public(msg['FromUserName']):  # Public
             elif self.is_public(msg['FromUserName']):  # Public
                 msg_type_id = 5
                 msg_type_id = 5
-                user['name'] = self.get_user_remark_name(user['id'])
+                user['name'] = self.get_prefer_name(self.get_account_name(user['id']))
             elif self.is_special(msg['FromUserName']):  # Special
             elif self.is_special(msg['FromUserName']):  # Special
                 msg_type_id = 6
                 msg_type_id = 6
-                user['name'] = self.get_user_remark_name(user['id'])
-            else:
-                user['name'] = 'unknown'  # Unknown
+                user['name'] = self.get_prefer_name(self.get_account_name(user['id']))
+
             if self.DEBUG and msg_type_id != 0:
             if self.DEBUG and msg_type_id != 0:
                 print '[MSG] %s:' % user['name']
                 print '[MSG] %s:' % user['name']
             content = self.extract_msg_content(msg_type_id, msg)
             content = self.extract_msg_content(msg_type_id, msg)
@@ -635,3 +481,197 @@ class WXBot:
         print '[INFO] Get %d contacts' % len(self.contact_list)
         print '[INFO] Get %d contacts' % len(self.contact_list)
         print '[INFO] Start to process messages .'
         print '[INFO] Start to process messages .'
         self.proc_msg()
         self.proc_msg()
+
+    def get_uuid(self):
+        url = 'https://login.weixin.qq.com/jslogin'
+        params = {
+            'appid': 'wx782c26e4c19acffb',
+            'fun': 'new',
+            'lang': 'zh_CN',
+            '_': int(time.time())*1000 + random.randint(1, 999),
+        }
+        r = self.session.get(url, params=params)
+        r.encoding = 'utf-8'
+        data = r.text
+        regx = r'window.QRLogin.code = (\d+); window.QRLogin.uuid = "(\S+?)"'
+        pm = re.search(regx, data)
+        if pm:
+            code = pm.group(1)
+            self.uuid = pm.group(2)
+            return code == '200'
+        return False
+
+    def gen_qr_code(self, qr_file_path):
+        string = 'https://login.weixin.qq.com/l/' + self.uuid
+        qr = pyqrcode.create(string)
+        if self.conf['qr'] == 'png':
+            qr.png(qr_file_path)
+        elif self.conf['qr'] == 'tty':
+            print(qr.terminal(quiet_zone=1))
+
+    def wait4login(self, tip):
+        time.sleep(tip)
+        url = 'https://login.weixin.qq.com/cgi-bin/mmwebwx-bin/login?tip=%s&uuid=%s&_=%s' \
+              % (tip, self.uuid, int(time.time()))
+        r = self.session.get(url)
+        r.encoding = 'utf-8'
+        data = r.text
+        param = re.search(r'window.code=(\d+);', data)
+        code = param.group(1)
+
+        if code == '201':
+            return True
+        elif code == '200':
+            param = re.search(r'window.redirect_uri="(\S+?)";', data)
+            redirect_uri = param.group(1) + '&fun=new'
+            self.redirect_uri = redirect_uri
+            self.base_uri = redirect_uri[:redirect_uri.rfind('/')]
+            return True
+        elif code == '408':
+            print '[ERROR] WeChat login timeout .'
+        else:
+            print '[ERROR] WeChat login exception .'
+        return False
+
+    def login(self):
+        r = self.session.get(self.redirect_uri)
+        r.encoding = 'utf-8'
+        data = r.text
+        doc = xml.dom.minidom.parseString(data)
+        root = doc.documentElement
+
+        for node in root.childNodes:
+            if node.nodeName == 'skey':
+                self.skey = node.childNodes[0].data
+            elif node.nodeName == 'wxsid':
+                self.sid = node.childNodes[0].data
+            elif node.nodeName == 'wxuin':
+                self.uin = node.childNodes[0].data
+            elif node.nodeName == 'pass_ticket':
+                self.pass_ticket = node.childNodes[0].data
+
+        if '' in (self.skey, self.sid, self.uin, self.pass_ticket):
+            return False
+
+        self.base_request = {
+            'Uin': self.uin,
+            'Sid': self.sid,
+            'Skey': self.skey,
+            'DeviceID': self.device_id,
+            }
+        return True
+
+    def init(self):
+        url = self.base_uri + '/webwxinit?r=%i&lang=en_US&pass_ticket=%s' % (int(time.time()), self.pass_ticket)
+        params = {
+            'BaseRequest': self.base_request
+        }
+        r = self.session.post(url, data=json.dumps(params))
+        r.encoding = 'utf-8'
+        dic = json.loads(r.text)
+        self.sync_key = dic['SyncKey']
+        self.user = dic['User']
+        self.sync_key_str = '|'.join([str(keyVal['Key']) + '_' + str(keyVal['Val'])
+                                      for keyVal in self.sync_key['List']])
+        return dic['BaseResponse']['Ret'] == 0
+
+    def status_notify(self):
+        url = self.base_uri + '/webwxstatusnotify?lang=zh_CN&pass_ticket=%s' % self.pass_ticket
+        self.base_request['Uin'] = int(self.base_request['Uin'])
+        params = {
+            'BaseRequest': self.base_request,
+            "Code": 3,
+            "FromUserName": self.user['UserName'],
+            "ToUserName": self.user['UserName'],
+            "ClientMsgId": int(time.time())
+        }
+        r = self.session.post(url, data=json.dumps(params))
+        r.encoding = 'utf-8'
+        dic = json.loads(r.text)
+        return dic['BaseResponse']['Ret'] == 0
+
+    def test_sync_check(self):
+        for host in ['webpush', 'webpush2']:
+            self.sync_host = host
+            retcode = self.sync_check()[0]
+            if retcode == '0':
+                return True
+        return False
+
+    def sync_check(self):
+        params = {
+            'r': int(time.time()),
+            'sid': self.sid,
+            'uin': self.uin,
+            'skey': self.skey,
+            'deviceid': self.device_id,
+            'synckey': self.sync_key_str,
+            '_': int(time.time()),
+        }
+        url = 'https://' + self.sync_host + '.weixin.qq.com/cgi-bin/mmwebwx-bin/synccheck?' + urllib.urlencode(params)
+        r = self.session.get(url)
+        r.encoding = 'utf-8'
+        data = r.text
+        pm = re.search(r'window.synccheck=\{retcode:"(\d+)",selector:"(\d+)"\}', data)
+        retcode = pm.group(1)
+        selector = pm.group(2)
+        return [retcode, selector]
+
+    def sync(self):
+        url = self.base_uri + '/webwxsync?sid=%s&skey=%s&lang=en_US&pass_ticket=%s' \
+                              % (self.sid, self.skey, self.pass_ticket)
+        params = {
+            'BaseRequest': self.base_request,
+            'SyncKey': self.sync_key,
+            'rr': ~int(time.time())
+        }
+        r = self.session.post(url, data=json.dumps(params))
+        r.encoding = 'utf-8'
+        dic = json.loads(r.text)
+        if dic['BaseResponse']['Ret'] == 0:
+            self.sync_key = dic['SyncKey']
+            self.sync_key_str = '|'.join([str(keyVal['Key']) + '_' + str(keyVal['Val'])
+                                          for keyVal in self.sync_key['List']])
+        return dic
+
+    def get_icon(self, uid):
+        url = self.base_uri + '/webwxgeticon?username=%s&skey=%s' % (uid, self.skey)
+        r = self.session.get(url)
+        data = r.content
+        fn = 'img_'+uid+'.jpg'
+        with open(fn, 'wb') as f:
+            f.write(data)
+        return fn
+
+    def get_head_img(self, uid):
+        url = self.base_uri + '/webwxgetheadimg?username=%s&skey=%s' % (uid, self.skey)
+        r = self.session.get(url)
+        data = r.content
+        fn = 'img_'+uid+'.jpg'
+        with open(fn, 'wb') as f:
+            f.write(data)
+        return fn
+
+    def get_msg_img_url(self, msgid):
+        return self.base_uri + '/webwxgetmsgimg?MsgID=%s&skey=%s' % (msgid, self.skey)
+
+    def get_msg_img(self, msgid):
+        url = self.base_uri + '/webwxgetmsgimg?MsgID=%s&skey=%s' % (msgid, self.skey)
+        r = self.session.get(url)
+        data = r.content
+        fn = 'img_'+msgid+'.jpg'
+        with open(fn, 'wb') as f:
+            f.write(data)
+        return fn
+
+    def get_voice_url(self, msgid):
+        return self.base_uri + '/webwxgetvoice?msgid=%s&skey=%s' % (msgid, self.skey)
+
+    def get_voice(self, msgid):
+        url = self.base_uri + '/webwxgetvoice?msgid=%s&skey=%s' % (msgid, self.skey)
+        r = self.session.get(url)
+        data = r.content
+        fn = 'voice_'+msgid+'.mp3'
+        with open(fn, 'wb') as f:
+            f.write(data)
+        return fn