Browse Source

Refactoring and fix bugs caused by group messages.

liuwons 9 years ago
parent
commit
00d7370bc6
4 changed files with 301 additions and 221 deletions
  1. 2 0
      .gitignore
  2. 43 38
      README.md
  3. 5 3
      test.py
  4. 251 180
      wxbot.py

+ 2 - 0
.gitignore

@@ -63,3 +63,5 @@ target/
 
 
 
 
 *.json
 *.json
+
+.idea/

+ 43 - 38
README.md

@@ -5,7 +5,7 @@ Python包装Web微信实现的微信机器人框架。可以很容易地实现
 ## 环境与依赖
 ## 环境与依赖
 
 
 目前只能运行于Python 2环境 。
 目前只能运行于Python 2环境 。
-**wxBot** 用到了Python **requests** 和 **pyqrcode** 库,使用之前需要安装这两个库:
+**wxBot** 用到了Python **requests** , **pypng** , 以及 **pyqrcode** 库,使用之前需要安装这三个库:
 
 
 ```bash
 ```bash
 pip install requests
 pip install requests
@@ -28,8 +28,8 @@ from wxbot import *
 
 
 class MyWXBot(WXBot):
 class MyWXBot(WXBot):
     def handle_msg_all(self, msg):
     def handle_msg_all(self, msg):
-        if msg['msg_type_id'] == 5 and msg['user_type'] == 'contact':
-            self.send_msg_by_uid('hi', msg['user_id'])
+        if msg['msg_type_id'] == 4 and msg['content']['type'] == 0:
+            self.send_msg_by_uid('hi', msg['user']['id'])
 
 
     def schedule(self):
     def schedule(self):
         self.send_msg('tb', 'schedule')
         self.send_msg('tb', 'schedule')
@@ -74,46 +74,47 @@ python test.py
 ## 接口
 ## 接口
 ### handle_msg_all
 ### handle_msg_all
 
 
-handle_msg_all函数的参数msg是代表一条消息的字典。一般包含以下字段:
+handle_msg_all函数的参数msg是代表一条消息的字典。字段的内容为
 
 
-| 字段名 | 意义 |
+| 字段名 | 字段内容 |
 | ----- | --- |
 | ----- | --- |
-| user_type | 用户类型,具体见用户类型表 |
-| msg_id | 消息id,微信内部数据 |
-| msg_type_id | 消息类型,具体见消息类型表 |
-| user_id | 发送消息的用户的id,微信内部数据 |
-| user_name | 发送消息的用户的名字,为备注名或者微信用户名 |
-| content | 消息体,不同类型消息的此字段内容不同,具体见消息类型表 |
-
-用户类型表:
-
-| 类型名 | 意义 |
-| ----- | ---- |
-| contact | 好友 |
-| public | 公众号 |
-| group | 群 |
-| special | 特殊账号 |
-| unknown | 未知 |
+| msg_type_id | 整数,消息类型,具体解释可以查看消息类型表 |
+| msg_id | 字符串,消息id |
+| content | 字典,消息内容,具体含有的字段请参考消息类型表,一般含有type(数据类型)与data(数据内容)字段,type与data的对应关系可以参考数据类型表 |
+| user | 字典,消息来源,字典包含name(发送者名称)字段与id(发送者id)字段,都是字符串  |
+
 
 
 消息类型表:
 消息类型表:
 
 
-| 类型号 | 类型名称 | 类型描述 | content |
-| ------ | ------- | --- | -------- |
-| 1 | Location | 位置 | 字典,包含location(位置的文本描述)和xml(原始未解析的xml格式文本)字段 |
-| 2 | FileHelper | 文件 | 字符串,文本文件内容 |
-| 3 | Self | 自己发送的消息 | 字符串,文本消息 |
-| 4 | Group | 群消息 | 字典,包含group_id(微信内部id), group_name(群名), user(微信内部id), user_name(用户名称), msg(字符串消息)字段 |
-| 5 | Text | 普通文本消息 | 字符串, 文本消息 |
-| 6 | Image | 图片 | 字符串, 图片url |
-| 7 | Voice | 语音 | 字符串, 语音url |
-| 8 | Recommend | 微信名片 | 字典, 包含nickname(昵称),alias(别名),province(省份),city(城市), gender(性别)字段 |
-| 9 | Animation | 动画 | 字符串, 动画url |
-| 10 | Share | 分享 | 字典,包含type(类型),title(标题),desc(描述),url(链接),from(来自)字段 |
-| 11 | Video | 视频 | 字符串,未解析的xml字符串 |
-| 12 | VideoCall | 视频电话 | 字符串,未解析的xml字符串 |
-| 13 | Redraw | 撤回消息 | 字符串,未解析的xml字符串 |
-| 14 | Init | 微信初始化系统消息,可以忽略 | 字符串,未解析的xml字符串 |
-| 99 | Unknown | 未知类型 | 字符串,未解析的xml字符串 |
+| 类型号 | 消息类型 | content |
+| ----- | --- | ------ |
+| 0 | 初始化消息,内部数据 | 无意义,可以忽略 |
+| 1 | 自己发送的消息 | 无意义,可以忽略 |
+| 2 | 文件消息 | 字典,包含type与data字段 |
+| 3 | 群消息 | 字典, 包含user(字典,包含id与name字段,都是字符串,表示发送此消息的群用户)与type、data字段 |
+| 4 | 联系人消息 | 字典,包含type与data字段 |
+| 5 | 公众号消息 | 字典,包含type与data字段 |
+| 6 | 特殊账号消息 | 字典,包含type与data字段 |
+| 99 | 未知账号消息 | 无意义,可以忽略 |
+
+
+数据类型表:
+
+| type | 数据类型 | data |
+| ---- | ---- | ------ |
+| 0 | 文本 | 字符串,表示文本消息的具体内容 |
+| 1 | 地理位置 | 字符串,表示地理位置 |
+| 3 | 图片 | 字符串,图片数据的url,HTTP POST得到jpg文件格式的数据 |
+| 4 | 语音 | 字符串,语音数据的url,HTTP POST得到mp3文件格式的数据 |
+| 5 | 名片 | 字典,包含nickname(昵称),alias(别名),province(省份),city(城市), gender(性别)字段 |
+| 6 | 动画 | 字符串, 动画url |
+| 7 | 分享 | 字典,包含type(类型),title(标题),desc(描述),url(链接),from(来自)字段 |
+| 8 | 视频 | 字符串,未解析的xml字符串 |
+| 9 | 视频电话 | 字符串,未解析的xml字符串 |
+| 10 | 撤回消息 | 字符串,未解析的xml字符串 |
+| 11 | 空内容 | 空字符串 |
+| 12 | 未知类型 | 字符串,未解析的xml字符串 |
+
 
 
 ### WXBot对象属性
 ### WXBot对象属性
 
 
@@ -123,6 +124,8 @@ WXBot对象在登录并初始化之后,含有以下的可用数据:
 | ---- | ---- |
 | ---- | ---- |
 | contact_list | 当前用户的微信联系人列表 |
 | contact_list | 当前用户的微信联系人列表 |
 | group_list | 当前用户的微信群列表 |
 | group_list | 当前用户的微信群列表 |
+| public_list | 当前用户关注的公众号列表 |
+| special_list | 特殊账号列表 |
 | session | WXBot与WEB微信服务器端交互所用的requests Session对象 |
 | session | WXBot与WEB微信服务器端交互所用的requests Session对象 |
 
 
 WXBot对象还含有一些可以利用的方法:
 WXBot对象还含有一些可以利用的方法:
@@ -136,3 +139,5 @@ WXBot对象还含有一些可以利用的方法:
 | get_user_remark_name(uid) | 获取好友的备注名,没有备注名则获取好友微信号, uid为好友的用户id(Web微信数据) |
 | get_user_remark_name(uid) | 获取好友的备注名,没有备注名则获取好友微信号, uid为好友的用户id(Web微信数据) |
 | 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_public(uid) | 判断id为uid的账号是否是本帐号所关注的公众号,返回True(是)或False(不是) |

+ 5 - 3
test.py

@@ -1,24 +1,26 @@
 #!/usr/bin/env python
 #!/usr/bin/env python
 # coding: utf-8
 # coding: utf-8
 
 
-import time
 from wxbot import *
 from wxbot import *
 
 
+
 class MyWXBot(WXBot):
 class MyWXBot(WXBot):
     def handle_msg_all(self, msg):
     def handle_msg_all(self, msg):
-        if msg['msg_type_id'] == 5 and msg['user_type'] == 'contact':
-            self.send_msg_by_uid('hi', msg['user_id'])
+        if msg['msg_type_id'] == 4 and msg['content']['type'] == 0:
+            self.send_msg_by_uid('hi', msg['user']['id'])
 '''
 '''
     def schedule(self):
     def schedule(self):
         self.send_msg('tb', 'schedule')
         self.send_msg('tb', 'schedule')
         time.sleep(1)
         time.sleep(1)
 '''
 '''
 
 
+
 def main():
 def main():
     bot = MyWXBot()
     bot = MyWXBot()
     bot.DEBUG = True
     bot.DEBUG = True
     bot.conf['qr'] = 'png'
     bot.conf['qr'] = 'png'
     bot.run()
     bot.run()
 
 
+
 if __name__ == '__main__':
 if __name__ == '__main__':
     main()
     main()

+ 251 - 180
wxbot.py

@@ -1,17 +1,20 @@
 #!/usr/bin/env python
 #!/usr/bin/env python
 # coding: utf-8
 # coding: utf-8
-from collections import defaultdict
+
 import pyqrcode
 import pyqrcode
 import requests
 import requests
 import json
 import json
 import xml.dom.minidom
 import xml.dom.minidom
-import multiprocessing
 import urllib
 import urllib
-import time, re, sys, os, random
+import time
+import re
+import random
+
 
 
 def utf82gbk(string):
 def utf82gbk(string):
     return string.decode('utf8').encode('gbk')
     return string.decode('utf8').encode('gbk')
 
 
+
 def make_unicode(data):
 def make_unicode(data):
     if not data:
     if not data:
         return data
         return data
@@ -22,12 +25,13 @@ def make_unicode(data):
         result = data.decode('utf-8')
         result = data.decode('utf-8')
     return result
     return result
 
 
+
 class WXBot:
 class WXBot:
     def __init__(self):
     def __init__(self):
         self.DEBUG = False
         self.DEBUG = False
         self.uuid = ''
         self.uuid = ''
         self.base_uri = ''
         self.base_uri = ''
-        self.redirect_uri= ''
+        self.redirect_uri = ''
         self.uin = ''
         self.uin = ''
         self.sid = ''
         self.sid = ''
         self.skey = ''
         self.skey = ''
@@ -36,7 +40,7 @@ class WXBot:
         self.base_request = {}
         self.base_request = {}
         self.sync_key_str = ''
         self.sync_key_str = ''
         self.sync_key = []
         self.sync_key = []
-        self.user = []
+        self.user = {}
         self.member_list = []
         self.member_list = []
         self.contact_list = []  # contact list
         self.contact_list = []  # contact list
         self.public_list = []   # public account list
         self.public_list = []   # public account list
@@ -46,7 +50,7 @@ class WXBot:
         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):
     def get_uuid(self):
         url = 'https://login.weixin.qq.com/jslogin'
         url = 'https://login.weixin.qq.com/jslogin'
@@ -54,7 +58,7 @@ class WXBot:
             'appid': 'wx782c26e4c19acffb',
             'appid': 'wx782c26e4c19acffb',
             'fun': 'new',
             'fun': 'new',
             'lang': 'zh_CN',
             'lang': 'zh_CN',
-            '_': int(time.time())*1000 + random.randint(1,999),
+            '_': int(time.time())*1000 + random.randint(1, 999),
         }
         }
         r = self.session.get(url, params=params)
         r = self.session.get(url, params=params)
         r.encoding = 'utf-8'
         r.encoding = 'utf-8'
@@ -77,7 +81,8 @@ class WXBot:
             
             
     def wait4login(self, tip):
     def wait4login(self, tip):
         time.sleep(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()))
+        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 = self.session.get(url)
         r.encoding = 'utf-8'
         r.encoding = 'utf-8'
         data = r.text
         data = r.text
@@ -136,11 +141,12 @@ class WXBot:
         dic = json.loads(r.text)
         dic = json.loads(r.text)
         self.sync_key = dic['SyncKey']
         self.sync_key = dic['SyncKey']
         self.user = dic['User']
         self.user = dic['User']
-        self.sync_key_str = '|'.join([ str(keyVal['Key']) + '_' + str(keyVal['Val']) for keyVal in self.sync_key['List'] ])
+        self.sync_key_str = '|'.join([str(keyVal['Key']) + '_' + str(keyVal['Val'])
+                                      for keyVal in self.sync_key['List']])
         return dic['BaseResponse']['Ret'] == 0
         return dic['BaseResponse']['Ret'] == 0
 
 
     def status_notify(self):
     def status_notify(self):
-        url = self.base_uri + '/webwxstatusnotify?lang=zh_CN&pass_ticket=%s' % (self.pass_ticket)
+        url = self.base_uri + '/webwxstatusnotify?lang=zh_CN&pass_ticket=%s' % self.pass_ticket
         self.base_request['Uin'] = int(self.base_request['Uin'])
         self.base_request['Uin'] = int(self.base_request['Uin'])
         params = {
         params = {
             'BaseRequest': self.base_request,
             'BaseRequest': self.base_request,
@@ -155,7 +161,8 @@ class WXBot:
         return dic['BaseResponse']['Ret'] == 0
         return dic['BaseResponse']['Ret'] == 0
 
 
     def get_contact(self):
     def get_contact(self):
-        url = self.base_uri + '/webwxgetcontact?pass_ticket=%s&skey=%s&r=%s' % (self.pass_ticket, self.skey, int(time.time()))
+        url = self.base_uri + '/webwxgetcontact?pass_ticket=%s&skey=%s&r=%s' \
+                              % (self.pass_ticket, self.skey, int(time.time()))
         r = self.session.post(url, data='{}')
         r = self.session.post(url, data='{}')
         r.encoding = 'utf-8'
         r.encoding = 'utf-8'
         if self.DEBUG:
         if self.DEBUG:
@@ -164,22 +171,27 @@ class WXBot:
         dic = json.loads(r.text)
         dic = json.loads(r.text)
         self.member_list = dic['MemberList']
         self.member_list = dic['MemberList']
 
 
-        SpecialUsers = ['newsapp','fmessage','filehelper','weibo','qqmail','fmessage','tmessage','qmessage','qqsync','floatbottle','lbsapp','shakeapp','medianote',
-            'qqfriend','readerapp','blogapp','facebookapp','masssendapp','meishiapp','feedsapp','voip','blogappweixin','weixin','brandsessionholder','weixinreminder','wxid_novlwrv3lqwv11',
-            'gh_22b87fa7cb3c','officialaccounts','notification_messages','wxid_novlwrv3lqwv11','gh_22b87fa7cb3c','wxitil','userexperience_alarm','notification_messages']
+        special_users = ['newsapp', 'fmessage', 'filehelper', 'weibo', 'qqmail',
+                         'fmessage', 'tmessage', 'qmessage', 'qqsync', 'floatbottle',
+                         'lbsapp', 'shakeapp', 'medianote', 'qqfriend', 'readerapp',
+                         'blogapp', 'facebookapp', 'masssendapp', 'meishiapp',
+                         'feedsapp', 'voip', 'blogappweixin', 'weixin', 'brandsessionholder',
+                         'weixinreminder', 'wxid_novlwrv3lqwv11', 'gh_22b87fa7cb3c',
+                         'officialaccounts', 'notification_messages', 'wxid_novlwrv3lqwv11',
+                         'gh_22b87fa7cb3c', 'wxitil', 'userexperience_alarm', 'notification_messages']
 
 
         self.contact_list = []
         self.contact_list = []
         self.public_list = []
         self.public_list = []
         self.special_list = []
         self.special_list = []
         self.group_list = []
         self.group_list = []
         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)
-            elif contact['UserName'] in SpecialUsers: # special account
+            elif contact['UserName'] in special_users:  # special account
                 self.special_list.append(contact)
                 self.special_list.append(contact)
-            elif contact['UserName'].find('@@') != -1: # group
+            elif contact['UserName'].find('@@') != -1:  # group
                 self.group_list.append(contact)
                 self.group_list.append(contact)
-            elif contact['UserName'] == self.user['UserName']: # self
+            elif contact['UserName'] == self.user['UserName']:  # self
                 pass
                 pass
             else:
             else:
                 self.contact_list.append(contact)
                 self.contact_list.append(contact)
@@ -193,6 +205,8 @@ class WXBot:
                 f.write(json.dumps(self.group_list))
                 f.write(json.dumps(self.group_list))
             with open('public_list.json', 'w') as f:
             with open('public_list.json', 'w') as f:
                 f.write(json.dumps(self.public_list))
                 f.write(json.dumps(self.public_list))
+            with open('member_list.json', 'w') as f:
+                f.write(json.dumps(self.member_list))
 
 
         return True
         return True
 
 
@@ -201,17 +215,17 @@ class WXBot:
         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 ]
+            "List": [{"UserName": g['UserName'], "EncryChatRoomId":""} for g in self.group_list]
         }
         }
         r = self.session.post(url, data=params)
         r = self.session.post(url, data=params)
         r.encoding = 'utf-8'
         r.encoding = 'utf-8'
         dic = json.loads(r.text)
         dic = json.loads(r.text)
-        return True
+        return dic
 
 
     def test_sync_check(self):
     def test_sync_check(self):
         for host in ['webpush', 'webpush2']:
         for host in ['webpush', 'webpush2']:
             self.sync_host = host
             self.sync_host = host
-            [retcode, selector] = self.sync_check()
+            retcode = self.sync_check()[0]
             if retcode == '0':
             if retcode == '0':
                 return True
                 return True
         return False
         return False
@@ -230,13 +244,14 @@ class WXBot:
         r = self.session.get(url)
         r = self.session.get(url)
         r.encoding = 'utf-8'
         r.encoding = 'utf-8'
         data = r.text
         data = r.text
-        pm = re.search(r'window.synccheck={retcode:"(\d+)",selector:"(\d+)"}', data)
+        pm = re.search(r'window.synccheck=\{retcode:"(\d+)",selector:"(\d+)"\}', data)
         retcode = pm.group(1)
         retcode = pm.group(1)
         selector = pm.group(2)
         selector = pm.group(2)
         return [retcode, selector]
         return [retcode, selector]
 
 
     def sync(self):
     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)
+        url = self.base_uri + '/webwxsync?sid=%s&skey=%s&lang=en_US&pass_ticket=%s' \
+                              % (self.sid, self.skey, self.pass_ticket)
         params = {
         params = {
             'BaseRequest': self.base_request,
             'BaseRequest': self.base_request,
             'SyncKey': self.sync_key,
             'SyncKey': self.sync_key,
@@ -247,23 +262,24 @@ class WXBot:
         dic = json.loads(r.text)
         dic = json.loads(r.text)
         if dic['BaseResponse']['Ret'] == 0:
         if dic['BaseResponse']['Ret'] == 0:
             self.sync_key = dic['SyncKey']
             self.sync_key = dic['SyncKey']
-            self.sync_key_str = '|'.join([ str(keyVal['Key']) + '_' + str(keyVal['Val']) for keyVal in self.sync_key['List'] ])
+            self.sync_key_str = '|'.join([str(keyVal['Key']) + '_' + str(keyVal['Val'])
+                                          for keyVal in self.sync_key['List']])
         return dic
         return dic
 
 
-    def get_icon(self, id):
-        url = self.base_uri + '/webwxgeticon?username=%s&skey=%s' % (id, self.skey)
+    def get_icon(self, uid):
+        url = self.base_uri + '/webwxgeticon?username=%s&skey=%s' % (uid, self.skey)
         r = self.session.get(url)
         r = self.session.get(url)
         data = r.content
         data = r.content
-        fn = 'img_'+id+'.jpg'
+        fn = 'img_'+uid+'.jpg'
         with open(fn, 'wb') as f:
         with open(fn, 'wb') as f:
             f.write(data)
             f.write(data)
         return fn
         return fn
 
 
-    def get_head_img(self, id):
-        url = self.base_uri + '/webwxgetheadimg?username=%s&skey=%s' % (id, self.skey)
+    def get_head_img(self, uid):
+        url = self.base_uri + '/webwxgetheadimg?username=%s&skey=%s' % (uid, self.skey)
         r = self.session.get(url)
         r = self.session.get(url)
         data = r.content
         data = r.content
-        fn = 'img_'+id+'.jpg'
+        fn = 'img_'+uid+'.jpg'
         with open(fn, 'wb') as f:
         with open(fn, 'wb') as f:
             f.write(data)
             f.write(data)
         return fn
         return fn
@@ -292,7 +308,7 @@ class WXBot:
             f.write(data)
             f.write(data)
         return fn
         return fn
 
 
-    #Get the NickName or RemarkName of an user by user id
+    # Get the NickName or RemarkName of an user by user id
     def get_user_remark_name(self, uid):
     def get_user_remark_name(self, uid):
         name = 'unknown group' if uid[:2] == '@@' else 'stranger'
         name = 'unknown group' if uid[:2] == '@@' else 'stranger'
         for member in self.member_list:
         for member in self.member_list:
@@ -300,7 +316,7 @@ class WXBot:
                 name = member['RemarkName'] if member['RemarkName'] else member['NickName']
                 name = member['RemarkName'] if member['RemarkName'] else member['NickName']
         return name
         return name
 
 
-    #Get user id of an user
+    # Get user id of an user
     def get_user_id(self, name):
     def get_user_id(self, name):
         for member in self.member_list:
         for member in self.member_list:
             if name == member['RemarkName'] or name == member['NickName'] or name == member['UserName']:
             if name == member['RemarkName'] or name == member['NickName'] or name == member['UserName']:
@@ -322,158 +338,211 @@ class WXBot:
                 return 'group'
                 return 'group'
         return 'unknown'
         return 'unknown'
 
 
+    def is_contact(self, uid):
+        for account in self.contact_list:
+            if uid == account['UserName']:
+                return True
+        return False
+
+    def is_public(self, uid):
+        for account in self.public_list:
+            if uid == account['UserName']:
+                return True
+        return False
+
+    def is_special(self, uid):
+        for account in self.special_list:
+            if uid == account['UserName']:
+                return True
+        return False
+
     '''
     '''
     msg:
     msg:
-        user_type
-        msg_id
-        msg_type_id
-        user_id
-        user_name
-        content
+        user
+        type
+        data
+        detail
     '''
     '''
     def handle_msg_all(self, msg):
     def handle_msg_all(self, msg):
         pass
         pass
 
 
     '''
     '''
-    msg_type_id:
+    content_type_id:
+        0 -> Text
         1 -> Location
         1 -> Location
+        3 -> Image
+        4 -> Voice
+        5 -> Recommend
+        6 -> Animation
+        7 -> Share
+        8 -> Video
+        9 -> VideoCall
+        10 -> Redraw
+        11 -> Empty
+        99 -> Unknown
+    '''
+    def extract_msg_content(self, msg_type_id, msg):
+        mtype = msg['MsgType']
+        content = msg['Content'].replace('&lt;', '<').replace('&gt;', '>')
+        msg_id = msg['MsgId']
+
+        msg_content = {}
+        if msg_type_id == 0:
+            return {'type': 11, 'data': ''}
+        elif msg_type_id == 2:  # File Helper
+            return {'type': 0, 'data': content.replace('<br/>', '\n')}
+        elif msg_type_id == 3:  # Group
+            sp = content.find('<br/>')
+            uid = content[:sp]
+            content = content[sp:]
+            content = content.replace('<br/>', '')
+            uid = uid[:-1]
+            msg_content['user'] = {'id': uid, 'name': self.get_user_remark_name(uid)}
+            if self.DEBUG:
+                print msg_content['user']
+        else:                   # Self, Contact, Special, Public, Unknown
+            pass
+
+        if mtype == 1:
+            if content.find('http://weixin.qq.com/cgi-bin/redirectforward?args=') != -1:
+                r = self.session.get(content)
+                r.encoding = 'gbk'
+                data = r.text
+                pos = self.search_content('title', data, 'xml')
+                msg_content['type'] = 1
+                msg_content['data'] = pos
+                msg_content['detail'] = data
+                if self.DEBUG:
+                    print '[Location] I am at %s ' % pos
+            else:
+                msg_content['type'] = 0
+                msg_content['data'] = content
+                if self.DEBUG:
+                    print '[Text] %s' % content
+        elif mtype == 3:
+            msg_content['type'] = 3
+            msg_content['data'] = self.get_msg_img_url(msg_id)
+            if self.DEBUG:
+                image = self.get_msg_img(msg_id)
+                print '[Image] %s' % image
+        elif mtype == 34:
+            msg_content['type'] = 4
+            msg_content['data'] = self.get_voice_url(msg_id)
+            if self.DEBUG:
+                voice = self.get_voice(msg_id)
+                print '[Voice] %s' % voice
+        elif mtype == 42:
+            msg_content['type'] = 5
+            info = msg['RecommendInfo']
+            msg_content['data'] = {'nickname': info['NickName'],
+                                   'alias': info['Alias'],
+                                   'province': info['Province'],
+                                   'city': info['City'],
+                                   'gender': ['unknown', 'male', 'female'][info['Sex']]}
+            if self.DEBUG:
+                print '[Recommend]'
+                print '========================='
+                print '= NickName: %s' % info['NickName']
+                print '= Alias: %s' % info['Alias']
+                print '= Local: %s %s' % (info['Province'], info['City'])
+                print '= Gender: %s' % ['unknown', 'male', 'female'][info['Sex']]
+                print '========================='
+        elif mtype == 47:
+            msg_content['type'] = 6
+            msg_content['data'] = self.search_content('cdnurl', content)
+            if self.DEBUG:
+                print '[Animation] %s' % msg_content['data']
+        elif mtype == 49:
+            msg_content['type'] = 7
+            app_msg_type = ''
+            if msg['AppMsgType'] == 3:
+                app_msg_type = 'music'
+            elif msg['AppMsgType'] == 5:
+                app_msg_type = 'link'
+            elif msg['AppMsgType'] == 7:
+                app_msg_type = 'weibo'
+            else:
+                app_msg_type = 'unknown'
+            msg_content['data'] = {'type': app_msg_type,
+                                   'title': msg['FileName'],
+                                   'desc': self.search_content('des', content, 'xml'),
+                                   'url': msg['Url'],
+                                   'from': self.search_content('appname', content, 'xml')}
+            if self.DEBUG:
+                print '[Share] %s' % app_msg_type
+                print '========================='
+                print '= title: %s' % msg['FileName']
+                print '= desc: %s' % self.search_content('des', content, 'xml')
+                print '= link: %s' % msg['Url']
+                print '= from: %s' % self.search_content('appname', content, 'xml')
+                print '========================='
+
+        elif mtype == 62:
+            msg_content['type'] = 8
+            msg_content['data'] = content
+            if self.DEBUG:
+                print '[Video] Please check on mobiles'
+        elif mtype == 53:
+            msg_content['type'] = 9
+            msg_content['data'] = content
+            if self.DEBUG:
+                print '[Video Call]'
+        elif mtype == 10002:
+            msg_content['type'] = 10
+            msg_content['data'] = content
+            if self.DEBUG:
+                print '[Redraw]'
+        else:
+            msg_content['type'] = 99
+            msg_content['data'] = content
+            if self.DEBUG:
+                print '[Unknown]'
+        return msg_content
+
+    '''
+    msg_type_id:
+        0 -> Init
+        1 -> Self
         2 -> FileHelper
         2 -> FileHelper
-        3 -> Self
-        4 -> Group
-        5 -> User Text Message
-        6 -> Image
-        7 -> Voice
-        8 -> Recommend
-        9 -> Animation
-        10 -> Share
-        11 -> Video
-        12 -> Video Call
-        13 -> Redraw
-        14 -> Init Message
+        3 -> Group
+        4 -> Contact
+        5 -> Public
+        6 -> Special
         99 -> Unknown
         99 -> Unknown
     '''
     '''
     def handle_msg(self, r):
     def handle_msg(self, r):
         for msg in r['AddMsgList']:
         for msg in r['AddMsgList']:
-            mtype = msg['MsgType']
-
-            wx_user_id = msg['FromUserName']
-            user_type = self.get_user_type(wx_user_id)
-
-            name = self.get_user_remark_name(wx_user_id)
-            content = msg['Content'].replace('&lt;','<').replace('&gt;','>')
-            msg_id = msg['MsgId']
             msg_type_id = 99
             msg_type_id = 99
-
-
-            if mtype == 51: #init message
-                msg_type_id = 14
-            elif mtype == 1:
-                if content.find('http://weixin.qq.com/cgi-bin/redirectforward?args=') != -1:
-                    r = self.session.get(content)
-                    r.encoding = 'gbk'
-                    data = r.text
-                    pos = self.search_content('title', data, 'xml')
-                    msg_type_id = 1
-                    content = {'location': pos, 'xml': data}
-                    if self.DEBUG:
-                        print '[Location] %s : I am at %s ' % (name, pos)
-
-                elif msg['ToUserName'] == 'filehelper':
-                    msg_type_id = 2
-                    content = content.replace('<br/>','\n')
-                    if self.DEBUG:
-                        print '[File] %s : %s' % (name, )
-
-                elif msg['FromUserName'] == self.user['UserName']: #self
-                    msg_type_id = 3
-
-                elif msg['FromUserName'][:2] == '@@':
-                    [people, content] = content.split(':<br/>')
-                    group = self.get_user_remark_name(msg['FromUserName'])
-                    name = self.get_user_remark_name(people)
-                    msg_type_id = 4
-                    content = {'group_id': msg['FromUserName'], 'group_name': group, 'user': people, 'user_name': name, 'msg': content}
-                    if self.DEBUG:
-                        print '[Group] |%s| %s: %s' % (group, name, content.replace('<br/>','\n'))
-
-                else:
-                    msg_type_id = 5
-                    if self.DEBUG:
-                        print '[Text] ', name, ' : ', content
-
-            elif mtype == 3:
-                msg_type_id = 6
-                content = self.get_msg_img_url(msg_id)
-                if self.DEBUG:
-                    image = self.get_msg_img(msg_id)
-                    print '[Image] %s : %s' % (name, image)
-
-            elif mtype == 34:
-                msg_type_id = 7
-                content = self.get_voice_url(msg_id)
-                if self.DEBUG:
-                    voice = self.get_voice(msg_id)
-                    print '[Voice] %s : %s' % (name, voice)
-
-            elif mtype == 42:
-                msg_type_id = 8
-
-                info = msg['RecommendInfo']
-                content = {}
-                content['nickname'] = info['NickName']
-                content['alias'] = info['Alias']
-                content['province'] = info['Province']
-                content['city'] = info['City']
-                content['gender'] = ['unknown', 'male', 'female'][info['Sex']]
+            user = {'id': msg['FromUserName']}
+            if msg['MsgType'] == 51:  # init message
+                msg_type_id = 0
+            elif msg['FromUserName'] == self.user['UserName']:  # Self
+                msg_type_id = 1
+                user['name'] = 'self'
+            elif msg['ToUserName'] == 'filehelper':  # File Helper
+                msg_type_id = 2
+                user['name'] = 'file_helper'
+            elif msg['FromUserName'][:2] == '@@':  # Group
+                msg_type_id = 3
+                user['name'] = self.get_user_remark_name(user['id'])
                 if self.DEBUG:
                 if self.DEBUG:
-                    print '[Recommend] %s : ' % name
-                    print '========================='
-                    print '= NickName: %s' % info['NickName']
-                    print '= Alias: %s' % info['Alias']
-                    print '= Local: %s %s' % (info['Province'], info['City'])
-                    print '= Gender: %s' % ['unknown', 'male', 'female'][info['Sex']]
-                    print '========================='
-
-            elif mtype == 47:
-                msg_type_id = 9
-                url = self.search_content('cdnurl', content)
-                content = url
-                if self.DEBUG:
-                    print '[Animation] %s : %s' % (name, url)
-
-            elif mtype == 49:
-                msg_type_id = 10
-                appMsgType = defaultdict(lambda : "")
-                appMsgType.update({5:'link', 3:'music', 7:'weibo'})
-                content = {'type': appMsgType[msg['AppMsgType']], 'title': msg['FileName'], 'desc': self.search_content('des', content, 'xml'), 'url': msg['Url'], 'from': self.search_content('appname', content, 'xml')}
-                if self.DEBUG:
-                    print '[Share] %s : %s' % (name, appMsgType[msg['AppMsgType']])
-                    print '========================='
-                    print '= title: %s' % msg['FileName']
-                    print '= desc: %s' % self.search_content('des', content, 'xml')
-                    print '= link: %s' % msg['Url']
-                    print '= from: %s' % self.search_content('appname', content, 'xml')
-                    print '========================='
-
-            elif mtype == 62:
-                msg_type_id = 11
-                if self.DEBUG:
-                    print '[Video] ', name, ' sent you a video, please check on mobiles'
-
-            elif mtype == 53:
-                msg_type_id = 12
-                if self.DEBUG:
-                    print '[Video Call] ', name, ' call you'
-            elif mtype == 10002:
-                msg_type_id = 13
-                if self.DEBUG:
-                    print '[Redraw] ', name, ' redraw back a message'
+                    print '[From] %s' % user['name']
+            elif self.is_contact(msg['FromUserName']):  # Contact
+                msg_type_id = 4
+                user['name'] = self.get_user_remark_name(user['id'])
+            elif self.is_public(msg['FromUserName']):  # Public
+                msg_type_id = 5
+                user['name'] = self.get_user_remark_name(user['id'])
+            elif self.is_special(msg['FromUserName']):  # Special
+                msg_type_id = 6
+                user['name'] = self.get_user_remark_name(user['id'])
             else:
             else:
-                msg_type_id = 99
-                if self.DEBUG:
-                    print '[Unknown] : %s' % str(mtype)
-                    print msg
-            message = {'user_type': user_type, 'msg_id':msg_id, 'msg_type_id': msg_type_id, 'content': content, 'user_id': msg['FromUserName'], 'user_name': name}
+                pass  # Unknown
+            content = self.extract_msg_content(msg_type_id, msg)
+            message = {'msg_type_id': msg_type_id,
+                       'msg_id': msg['MsgId'],
+                       'content': content,
+                       'user': user}
             self.handle_msg_all(message)
             self.handle_msg_all(message)
 
 
     def schedule(self):
     def schedule(self):
@@ -483,14 +552,14 @@ class WXBot:
         self.test_sync_check()
         self.test_sync_check()
         while True:
         while True:
             [retcode, selector] = self.sync_check()
             [retcode, selector] = self.sync_check()
-            if retcode == '1100': # User have login on mobile
+            if retcode == '1100':  # User have login on mobile
                 pass
                 pass
             elif retcode == '0':
             elif retcode == '0':
                 if selector == '2':
                 if selector == '2':
                     r = self.sync()
                     r = self.sync()
                     if r is not None:
                     if r is not None:
                         self.handle_msg(r)
                         self.handle_msg(r)
-                elif selector == '7': # Play WeChat on mobile
+                elif selector == '7':  # Play WeChat on mobile
                     r = self.sync()
                     r = self.sync()
                     if r is not None:
                     if r is not None:
                         self.handle_msg(r)
                         self.handle_msg(r)
@@ -498,9 +567,9 @@ class WXBot:
                     time.sleep(1)
                     time.sleep(1)
             self.schedule()
             self.schedule()
 
 
-    def send_msg_by_uid(self, word, dst = 'filehelper'):
-        url = self.base_uri + '/webwxsendmsg?pass_ticket=%s' % (self.pass_ticket)
-        msg_id = str(int(time.time()*1000)) + str(random.random())[:5].replace('.','')
+    def send_msg_by_uid(self, word, dst='filehelper'):
+        url = self.base_uri + '/webwxsendmsg?pass_ticket=%s' % self.pass_ticket
+        msg_id = str(int(time.time()*1000)) + str(random.random())[:5].replace('.', '')
         params = {
         params = {
             'BaseRequest': self.base_request,
             'BaseRequest': self.base_request,
             'Msg': {
             'Msg': {
@@ -514,18 +583,18 @@ class WXBot:
         }
         }
         headers = {'content-type': 'application/json; charset=UTF-8'}
         headers = {'content-type': 'application/json; charset=UTF-8'}
         data = json.dumps(params, ensure_ascii=False).encode('utf8')
         data = json.dumps(params, ensure_ascii=False).encode('utf8')
-        r = self.session.post(url, data = data, headers = headers)
+        r = self.session.post(url, data=data, headers=headers)
         dic = r.json()
         dic = r.json()
         return dic['BaseResponse']['Ret'] == 0
         return dic['BaseResponse']['Ret'] == 0
 
 
-    def send_msg(self, name, word, isfile = False):
+    def send_msg(self, name, word, isfile=False):
         uid = self.get_user_id(name)
         uid = self.get_user_id(name)
         if uid:
         if uid:
             if isfile:
             if isfile:
                 with open(word, 'r') as f:
                 with open(word, 'r') as f:
                     result = True
                     result = True
                     for line in f.readlines():
                     for line in f.readlines():
-                        line = line.replace('\n','')
+                        line = line.replace('\n', '')
                         print '-> '+name+': '+line
                         print '-> '+name+': '+line
                         if self.send_msg_by_uid(line, uid):
                         if self.send_msg_by_uid(line, uid):
                             pass
                             pass
@@ -543,13 +612,15 @@ class WXBot:
                 print '[ERROR] This user does not exist .'
                 print '[ERROR] This user does not exist .'
             return True
             return True
 
 
-    def search_content(self, key, content, fmat = 'attr'):
+    def search_content(self, key, content, fmat='attr'):
         if fmat == 'attr':
         if fmat == 'attr':
             pm = re.search(key+'\s?=\s?"([^"<]+)"', content)
             pm = re.search(key+'\s?=\s?"([^"<]+)"', content)
-            if pm: return pm.group(1)
+            if pm:
+                return pm.group(1)
         elif fmat == 'xml':
         elif fmat == 'xml':
-            pm=re.search('<{0}>([^<]+)</{0}>'.format(key),content)
-            if pm: return pm.group(1)
+            pm = re.search('<{0}>([^<]+)</{0}>'.format(key), content)
+            if pm:
+                return pm.group(1)
         return 'unknown'
         return 'unknown'
 
 
     def run(self):
     def run(self):